Skip to content

Commit

Permalink
Sessions (#122)
Browse files Browse the repository at this point in the history
* basic login/ register

* sessions working

* cookie expiry added

* removed http.cookie import

* removed checkLogin() handler (used for debugging)

* Apply suggestions from code review

Co-Authored-By: Arjoonn Sharma <[email protected]>

* message display

* bug fixes

* changed name for decorator function

* Apply suggestions from code review

Co-Authored-By: Arjoonn Sharma <[email protected]>

* authentication added to more handlers
  • Loading branch information
rishabhKalakoti authored and theSage21 committed May 16, 2019
1 parent f5146b0 commit e3debe1
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 27 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ venv/

.pytest_cache
submission_record.db

user_record.db
session_record.db
112 changes: 92 additions & 20 deletions server.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
from bottle import Bottle, run, template, static_file, request, route, redirect,error
import os
import sys
import datetime
import bottle
import os, sys, datetime
import string, random

from collections import defaultdict, namedtuple
import shelve

path = os.path.abspath(__file__)
dir_path = os.path.dirname(path)
app = Bottle()
app = bottle.Bottle()

database_path = "submission_record.db"
user_db = "user_record.db"
sessions_db = "session_record.db"
questions = {}
contests = {}
question_dir = "files/questions"

Question = namedtuple("Question", "output statement")
Submission = namedtuple("Submission", "question time output is_correct contest")
# questions, code, description, start_time, end_time
Contest = namedtuple("Contest", "description questions start_time end_time")
User = namedtuple("User", "password")

# dummy contests
contests["PRACTICE"] = Contest(
Expand Down Expand Up @@ -48,53 +50,69 @@
for i in os.listdir(question_dir):
if not i.isdigit():
continue
# read the correct output as bytes object
with open(os.path.join(question_dir, i, "output.txt"), "rb") as fl:
output = fl.read()
with open(os.path.join(question_dir, i, "statement.txt"), "r") as fl:
statement = fl.read()
questions[i] = Question(output=output, statement=statement)


def login_required(function):
def login_redirect(*args, **kwargs):
if not logggedIn():
return bottle.template("home.html", message="Login required.")
return function(*args, **kwargs)
return login_redirect

@app.route("/")
def changePath():
return redirect("/dashboard")
return bottle.redirect("/home")


@app.get("/home")
def home():
if logggedIn():
return bottle.redirect("/dashboard")
return bottle.template("home.html", message="")


@app.get("/dashboard")
@login_required
def dashboard():
return template("dashboard.html", contests=contests)
return bottle.template("dashboard.html", contests=contests)


@app.get("/contest/<code>/<number>")
@login_required
def contest(code, number):
if not code in contests:
return "Contest does not exist"
if contests[code].start_time > datetime.datetime.now():
return "The contest had not started yet."
statement = questions[number].statement
return template(
"index.html", question_number=number, contest=code, question=statement
return bottle.template(
"question.html", question_number=number, contest=code, question=statement
)


@app.get("/contest/<code>")
@login_required
def contest(code):
if not code in contests:
return "Contest does not exist"
if contests[code].start_time > datetime.datetime.now():
return "The contest had not started yet."
return template("contest.html", code=code, contest=contests[code])
return bottle.template("contest.html", code=code, contest=contests[code])


@app.get("/question/<path:path>")
def download(path):
return static_file(path, root=question_dir)
return bottle.static_file(path, root=question_dir)


@app.get("/static/<filepath:path>")
def server_static(filepath):
return static_file(filepath, root=os.path.join(dir_path, "static"))
return bottle.static_file(filepath, root=os.path.join(dir_path, "static"))


@app.get("/ranking/<code>")
Expand Down Expand Up @@ -124,7 +142,7 @@ def contest_ranking(code):
order.sort(key=lambda x: x[1], reverse=True)
order = [entry for entry in order if entry[1] > 0]
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
return template("rankings.html", people=order)
return bottle.template("rankings.html", people=order)


@app.get("/ranking")
Expand All @@ -149,12 +167,68 @@ def rankings():
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
return template("rankings.html", people=order)

def logggedIn():
if not bottle.request.get_cookie("s_id"):
return False
with shelve.open(sessions_db) as sessions:
return bottle.request.get_cookie("s_id") in sessions


def createSession(username):
session_id = "".join(
random.choice(string.ascii_letters + string.digits) for i in range(20)
)
bottle.response.set_cookie(
"s_id",
session_id,
expires=datetime.datetime.now() + datetime.timedelta(days=30),
)
with shelve.open(sessions_db) as sessions:
sessions[session_id] = username
return bottle.redirect("/dashboard")


@app.post("/login")
def login():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
with shelve.open(user_db) as users:
if not username in users:
return bottle.template("home.html", message="User does not exist.")
if users[username].password != password:
return bottle.template("home.html", message="Incorrect password.")
return createSession(username)


@app.post("/register")
def register():
username = bottle.request.forms.get("username")
password = bottle.request.forms.get("password")
with shelve.open(user_db) as users:
if username in users:
return bottle.template(
"home.html",
message="Username already exists. Select a different username",
)
users[username] = User(password=password)
return createSession(username)


@app.get("/logout")
def logout():
with shelve.open(sessions_db) as sessions:
del sessions[bottle.request.get_cookie("s_id")]
bottle.response.delete_cookie("s_id")
return bottle.redirect("/home")


@app.post("/check/<code>/<number>")
@login_required
def file_upload(code, number):
u_name = request.forms.get("username") # accepting username
with shelve.open(sessions_db) as sessions:
u_name = sessions[bottle.request.get_cookie("s_id")]
time = datetime.datetime.now()
uploaded = request.files.get("upload").file.read()
uploaded = bottle.request.files.get("upload").file.read()
expected = questions[number].output
expected = expected.strip()
uploaded = uploaded.strip()
Expand All @@ -164,7 +238,6 @@ def file_upload(code, number):
submissions = (
[] if u_name not in submission_record else submission_record[u_name]
)
# submissions = submission_record.get(u_name, list())
submissions.append(
Submission(
question=number,
Expand All @@ -186,5 +259,4 @@ def file_upload(code, number):
def error404(error):
return template("error.html" ,errorcode=error.status_code , errorbody = error.body)


run(app, host="localhost", port=8080)
bottle.run(app, host="localhost", port=8080)
3 changes: 3 additions & 0 deletions views/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ <h1>{{code}}</h1>
{{contest.description}}
</p>
</div>
<div class="container">
<a href="/ranking/{{code}}" class="btn btn-primary">Rankings</a>
</div>
<div class="container">
% for qno in range(len(contest.questions)):
<a href="/contest/{{code}}/{{contest.questions[qno]}}">{{qno+1}}</a><br />
Expand Down
3 changes: 3 additions & 0 deletions views/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ <h1>Contests</h1>
</tbody>
</table>
</div>
<div class="container">
<a href="/logout" class="btn btn-primary">Logout</a>
</div>
</body>
</html>
44 changes: 44 additions & 0 deletions views/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
% include('base.html', title="PyJudge")
<body>
<div class="container text-center">
<h1>
PyJudge
</h1>
</div>
% if message:
<div class="alert alert-success alert-dismissible" style='width:auto;'>
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>Error!</strong> {{message}}
</div>
% end
<div class="container">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#register">Register</a>
</li>
</ul>
<div class="tab-content">
<div id="login" class="tab-pane container active">
<form name="login" method="post" action="/login">
<label>Username:</label>
<input type="text" name="username" required /><br />
<label>Password</label>
<input type="password" name="password" required /><br />
<input class="btn btn-primary" type="submit" value="Login" />
</form>
</div>
<div id="register" class="tab-pane container fade">
<form name="register" method="post" action="/register">
<label>Username:</label>
<input type="text" name="username" required /><br />
<label>Password:</label>
<input type="password" name="password" required /><br />
<input class="btn btn-primary" type="submit" value="Register" />
</form>
</div>
</div>
</div>
</body>
8 changes: 2 additions & 6 deletions views/index.html → views/question.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ <h1>Submission Page</h1>
You can submit the code in any language.
You can upload the file below.
</p>
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
<div class="form-group">
<label for="username">Username: </label>
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input class="form-control" type="text" name="username" required />
</div>
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
Output file: <br />
<input type="file" name="upload" required />
<button class="btn btn-primary" type="submit">Upload</button>
</form>
Expand Down

0 comments on commit e3debe1

Please sign in to comment.