Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
de8ce8c
Setup and structure for testing
jessehartloff Jun 9, 2025
9e3470a
More overall structure for the project. Added roster and lots of TODOs
jessehartloff Jun 11, 2025
b6a6f5d
Queue endpoint documentation update; db file renaming to follow Pytho…
jessehartloff Jun 14, 2025
6492bd1
Formatting
jessehartloff Jun 14, 2025
6393710
install utils for linter
jessehartloff Jun 14, 2025
e544baf
bug fix. need super constructor calls everywhere due to MRO
jessehartloff Jun 14, 2025
1d8b6cb
Add web frontend application
octomaanav Sep 26, 2025
c89c9c1
Changes to readme
octomaanav Sep 26, 2025
e1fcfab
Update README
octomaanav Sep 26, 2025
31567e4
Merge pull request #5 from octomaanav/web-app-frontend
jessehartloff Sep 26, 2025
7645a6e
Merge remote-tracking branch 'origin/testing' into oldIsNewAgain
jessehartloff Oct 24, 2025
61073c0
Remove other front end
jessehartloff Oct 28, 2025
0f3dd47
Implement basic queue backend functionality
jbcarras Dec 15, 2025
88bb2c6
Implement some authentication
jbcarras Dec 15, 2025
f8845d9
Fix threading in database and add temporary frontend
jbcarras Dec 16, 2025
7a3eabe
remove print statements and formatting
jbcarras Dec 16, 2025
cee9e5a
delete weird debugging endpoint
jbcarras Dec 16, 2025
2c73c7e
some frontend and visit tracking
jbcarras Jan 13, 2026
06f630f
more functional frontend
jbcarras Jan 14, 2026
f0aec2a
Big frontend rework. Switch to independent frontend server and using …
jbcarras Jan 15, 2026
00f10a6
preferred name updating and removals
jbcarras Jan 16, 2026
b3b5d73
Show in progress visit after leaving and returning, styling
jbcarras Jan 17, 2026
a58743e
Frontend hits remaining queue endpoints, show error messages, mobile …
jbcarras Jan 17, 2026
1eb02a5
Styling change so iOS doesn't show an emoji
jbcarras Jan 17, 2026
b1b7c7f
Visit mobile view
jbcarras Jan 18, 2026
e5a281c
Course management
jbcarras Jan 18, 2026
12e6f67
Starting to set up prod deployment
jessehartloff Feb 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/formatter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: [ "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
- name: Formatting the code with black
run: |
black $(git ls-files '*.py')
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
- name: Formatting the code with black
run: |
black $(git ls-files '*.py')
29 changes: 15 additions & 14 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
name: Pylint
run-name: Pylint Checker 🐧
on: [push]
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: [ "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r ./api/requirements_dev.txt
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r ./api/requirements.txt
pip install -r ./api/utils.txt
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.venv/
**/__pycache__/
.idea/
.idea/.DS_Store
3 changes: 3 additions & 0 deletions api/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
API_MODE=dev
DB=relational
SQLITE_DB_PATH="./moh.sqlite"
19 changes: 19 additions & 0 deletions api/auth/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from api.database.db import db
import os

AUTOLAB_SECRET = os.getenv("AUTOLAB_CLIENT_SECRET")
AUTOLAB_ID = os.getenv("AUTOLAB_CLIENT_ID")
REDIRECT_URI = os.getenv("AUTOLAB_REDIRECT_URI")

def create_account(username, numeric_identifier, auth_level="student"):
account_id = db.create_account(username, numeric_identifier)
db.add_to_roster(account_id, auth_level)
return account_id

def get_user(cookies):
if "auth_token" not in cookies:
return None

return db.get_authenticated_user(cookies["auth_token"])


84 changes: 77 additions & 7 deletions api/auth/routes.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,101 @@
"""Authentication Blueprint for MOH"""

from flask import Blueprint
import json
import urllib.parse
import requests

from flask import Blueprint, request, make_response
from api.database.db import db
from api.auth.controller import AUTOLAB_ID, AUTOLAB_SECRET, REDIRECT_URI

blueprint = Blueprint("auth", __name__)


@blueprint.route("/login", methods=["POST"])
def login():
"""Checks if the current user has the right credentials to log in
Args:
email: forum data field of email
password: forum data field of password
ubit: form data field of ubit
password: form data field of password

Returns:
The status of the login attempt
"""
return "Login arrived"

ubit = request.form.get("ubit")
pw = request.form.get("password")

auth_token = db.sign_in(ubit, pw)

if not auth_token:
return {"message": "Incorrect username or password"}, 400

res = make_response(json.dumps({"message": "Successfully logged in"}), 200)
res.content_type = "application/json"
res.set_cookie(
"auth_token", auth_token, max_age=int(2.592e6), httponly=True, secure=True
)

return res


@blueprint.route("/signup", methods=["POST"])
def signup():
"""Creates an account using the given credentials,
fails if email already registered for an account
fails if ubit already registered for an account
or if ubit is not in the roster
Args:
email: forum data field of email
ubit: forum data field of ubit
password: forum data field of password

Returns:
The status of the sign-up attempt
"""
return "Signup arrived"

ubit = request.form.get("ubit")
pw = request.form.get("password")

if not db.lookup_identifier(ubit):
return {
"message": "You are not in the roster. If this is an error, please contact the course staff."
}, 400

if not (auth_token := db.sign_up(ubit, pw)):
return {"message": "Sign-in already exists"}, 400

res = make_response(json.dumps({"message": "Successfully created account"}), 200)
res.content_type = "application/json"
res.set_cookie(
"auth_token", auth_token, max_age=int(2.592e6), httponly=True, secure=True
)
return res


@blueprint.route("/signout", methods=["POST"])
def signout():
"""Signs out the currently logged-in user, invalidating their auth token
Args:
Request.cookie: the auth token of the currently logged-in user

Returns:
400 if no auth token is set
200 on success
"""

auth_token = request.cookies.get("auth_token")

if not auth_token:
return {"message": "You are not logged in."}, 400

db.sign_out(auth_token)

res = make_response(json.dumps({"message": "Logged out"}), 200)
res.content_type = "application/json"
res.set_cookie("auth_token", "", max_age=0, httponly=True, secure=True)

return res


# TODO: update preferred name

# TODO: account has UBIT (For AL lookups) and pn (For card swipes)
3 changes: 2 additions & 1 deletion api/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@


class Config:
"""Configuration class for MOJ api server, stores current configuration state of flask api"""
"""Configuration class for MOH api server, stores current configuration state of flask api"""

def __init__(self):
self.API_MODE = os.getenv("API_MODE", "Can not find mode")
self.MAX_CONTENT_LENGTH = 16 * 1000 * 1000
23 changes: 23 additions & 0 deletions api/database/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os

from api.database.relational_db.relational_db import RelationalDB
from api.database.testing_db.testing_db import TestingDB
from api.database.mock_db.mock_db import MockDB


def create_db():
db_type = os.getenv("DB")
match db_type:
case "relational":
return RelationalDB()
case "testing":
return TestingDB()
case "mock":
return MockDB()
case None:
raise EnvironmentError('environment variable "DB" not set')
case _:
raise ModuleNotFoundError("Could not find database named " + db_type)


db = create_db()
18 changes: 18 additions & 0 deletions api/database/db_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from abc import ABC, abstractmethod

from api.database.idb_queue import IQueue
from api.database.idb_ratings import IRatings
from api.database.idb_accounts import IAccounts
from api.database.idb_roster import IRoster


class DBInterface(IQueue, IRatings, IAccounts, IRoster, ABC):

# All database implements must extend this class

def __init__(self):
super().__init__()

@abstractmethod
def connect(self):
pass
55 changes: 55 additions & 0 deletions api/database/idb_accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from abc import ABC, abstractmethod


class IAccounts(ABC):

def __init__(self):
super().__init__()

@abstractmethod
def create_account(self, ubit, pn):
# Creates an account with the provided ubit and pn. Generates, and returns, a unique id for the new account
raise NotImplementedError()

@abstractmethod
def lookup_person_number(self, person_number) -> dict[str, str]:
# Returns the database entry for the user with the specified person number.
raise NotImplementedError()

@abstractmethod
def lookup_identifier(self, identifier) -> dict[str, str]:
# Returns the database entry for the user with the specified identifier.
# resolves UBIT -> person number -> unique id
raise NotImplementedError()

@abstractmethod
def get_authenticated_user(self, auth_token) -> dict[str, str]:
# Returns the database entry for the user with the specified auth token.
raise NotImplementedError()

@abstractmethod
def sign_up(self, username, pw) -> str | None:
# creates a sign in for the requested user
# returns None if the user's ubit isn't in the system
# returns an auth token for the user
raise NotImplementedError()

@abstractmethod
def sign_in(self, username, pw) -> str | None:
# generates and returns a valid auth token for the user if the username and password match
# returns None on error
raise NotImplementedError()

@abstractmethod
def sign_out(self, auth_token):
# invalidates the specified auth token
raise NotImplementedError()

@abstractmethod
def set_preferred_name(self, identifier, name):
# set the user's preferred name based on identifier
raise NotImplementedError()

@abstractmethod
def set_name(self, identifier, first_name, last_name):
raise NotImplementedError()
39 changes: 39 additions & 0 deletions api/database/idb_queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from abc import ABC, abstractmethod


class IQueue(ABC):

def __init__(self):
super().__init__()

@abstractmethod
def enqueue_student(self, student):
raise NotImplementedError()

@abstractmethod
def enqueue_student_front(self, student):
raise NotImplementedError()

@abstractmethod
def dequeue_student(self):
raise NotImplementedError()

@abstractmethod
def get_queue(self):
raise NotImplementedError()

@abstractmethod
def remove_student(self, student):
raise NotImplementedError()

@abstractmethod
def clear_queue(self):
raise NotImplementedError()

@abstractmethod
def set_reason(self, student, reason):
raise NotImplementedError()

@abstractmethod
def move_to_end(self, student):
raise NotImplementedError()
11 changes: 11 additions & 0 deletions api/database/idb_ratings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod


class IRatings(ABC):

def __init__(self):
super().__init__()

@abstractmethod
def rate_student(self, student, rating, feedback):
raise NotImplementedError()
15 changes: 15 additions & 0 deletions api/database/idb_roster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from abc import ABC, abstractmethod


class IRoster(ABC):

def __init__(self):
super().__init__()

@abstractmethod
def add_to_roster(self, user_id, role):
raise NotImplementedError()

@abstractmethod
def get_roster(self):
raise NotImplementedError()
Loading
Loading