Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
564984d
clean up assets
timothywashburn May 29, 2026
9068c10
rename controller
timothywashburn May 29, 2026
eda30a1
mypy strict typing
timothywashburn May 29, 2026
5f75d83
first pass major refactoring (removing slop)
timothywashburn May 30, 2026
d8c9dec
fixing mypy issues
timothywashburn May 31, 2026
769d8c1
getting the program to a state where it can start up again
timothywashburn May 31, 2026
c793d8a
start of ui refactoring
timothywashburn Jun 1, 2026
f170443
refactor fields and fix mypy errors
timothywashburn Jun 1, 2026
b8ef5d4
rename hardware impl
timothywashburn Jun 1, 2026
547cc84
remove old file
timothywashburn Jun 1, 2026
a2c2c83
fix text alignment
timothywashburn Jun 1, 2026
0bc517c
merge duplicate outer border creation
timothywashburn Jun 1, 2026
6137cc5
minor refactoring
timothywashburn Jun 1, 2026
5d9c938
rewrite navigation, harden boot sequence
timothywashburn Jun 1, 2026
41a9cb1
minor
timothywashburn Jun 1, 2026
e9b4f74
minor
timothywashburn Jun 1, 2026
9da471b
refactor ui
timothywashburn Jun 1, 2026
1e1af97
refactor ui 2
timothywashburn Jun 1, 2026
70c6475
update to match the rest of the screens
timothywashburn Jun 1, 2026
8f578a1
merge duplicate field generation code
timothywashburn Jun 1, 2026
431e146
pre fix prep for account controller
timothywashburn Jun 1, 2026
2b28c6e
clean up controllers, finish health system impl
timothywashburn Jun 2, 2026
08a7597
more hardening and logging
timothywashburn Jun 2, 2026
dd241ed
fix
timothywashburn Jun 2, 2026
c5bd15c
reimplement health server
timothywashburn Jun 3, 2026
019f168
more major refactoring
timothywashburn Jun 3, 2026
f79e689
fix type issue
timothywashburn Jun 3, 2026
527b3db
remove request logging for health api
timothywashburn Jun 3, 2026
2bb107b
switch to static font
timothywashburn Jun 3, 2026
385c1ab
add cache for docker image build
timothywashburn Jun 3, 2026
ad5b2c8
consistent font builder using pixelsize
timothywashburn Jun 3, 2026
b510ea8
disable reset duration on hover for toasts
timothywashburn Jun 3, 2026
acc1f7c
add comment
timothywashburn Jun 3, 2026
8e90c17
transient error catching and logging
timothywashburn Jun 3, 2026
8a99b8e
minor
timothywashburn Jun 3, 2026
e46829d
increase timeout tolerance for account
timothywashburn Jun 4, 2026
0c1a352
require rfid card to create account
timothywashburn Jun 4, 2026
dd23e0a
remove ucsd api call from account creation
timothywashburn Jun 4, 2026
5c92e67
update compose
timothywashburn Jun 4, 2026
b794b59
require rfid in account request
timothywashburn Jun 5, 2026
71c57f7
fix watchtower timezone
timothywashburn Jun 5, 2026
3b6e4cc
fix transition screen text rendering
timothywashburn Jun 5, 2026
d863bf4
minor
timothywashburn Jun 5, 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
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ jobs:
tags: |
ghcr.io/${{ steps.lowercase.outputs.owner }}/${{ steps.image.outputs.name }}:${{ github.sha }}
${{ steps.image.outputs.extra_tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
5 changes: 2 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
services:
check-in:
image: ghcr.io/ucsd-makerspace/check-in:latest
image: ghcr.io/ucsd-makerspace/check-in-dev:latest
pull_policy: always
privileged: true
network_mode: host
environment:
- DISPLAY=:0
- CHECK_IN_API_URL=http://10.8.0.1:8000
env_file:
- .env
volumes:
Expand All @@ -21,5 +20,5 @@ services:
# Testing: check every 30 seconds
# command: --interval 30
# Production: swap the line above for the two below to check every 5 minutes overnight only (midnight–6am)
command: --schedule "0 */5 0-5 * * *"
command: --schedule "CRON_TZ=America/Los_Angeles 0 */5 0-5 * * *"
restart: unless-stopped
Binary file removed fonts/Montserrat-VariableFont_wght.ttf
Binary file not shown.
13 changes: 13 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[mypy]
strict = true
explicit_package_bases = true
mypy_path = src

[mypy-adafruit_pn532.*]
ignore_missing_imports = true

[mypy-qtawesome]
ignore_missing_imports = true

[mypy-pyqttoast.*]
ignore_missing_imports = true
8 changes: 7 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
pydantic==2.13.4
pyserial==3.5
requests==2.32.5
adafruit-circuitpython-pn532
RPi.GPIO; sys_platform == 'linux'
pyqt6==6.11.0
pyqt6-stubs
qtawesome
pyqt6-sip
pyqt6-sip
pyqt-toast-notification==1.3.3
mypy==2.1.0
types-pyserial
types-requests
8 changes: 1 addition & 7 deletions run_dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,4 @@ for arg in "$@"; do
fi
done

output_file="log.txt"

echo "" >> "$output_file"

date "+%Y-%m-%d %H:%M:%S" >> "$output_file"

python src/main.py "$@" 2>&1 | tee -a log.txt
python src/main.py "$@"
33 changes: 0 additions & 33 deletions src/app_context.py

This file was deleted.

File renamed without changes
Binary file removed src/assets/check_in_manual/button_check_in.png
Binary file not shown.
Binary file removed src/assets/check_in_rfid/icon_check_in.png
Binary file not shown.
Binary file not shown.
Binary file removed src/assets/create_account_barcode/outline_1.png
Binary file not shown.
Binary file removed src/assets/create_account_barcode/outline_2.png
Binary file not shown.
Binary file removed src/assets/create_account_manual/outline_1.png
Binary file not shown.
Binary file removed src/assets/create_account_manual/outline_2.png
Binary file not shown.
Binary file removed src/assets/create_account_manual/register.png
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Black.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-BlackItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Bold.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-BoldItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-ExtraBold.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-ExtraBoldItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-ExtraLight.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-ExtraLightItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Italic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Light.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-LightItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Medium.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-MediumItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Regular.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-SemiBold.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-SemiBoldItalic.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-Thin.ttf
Binary file not shown.
Binary file added src/assets/fonts/Montserrat-ThinItalic.ttf
Binary file not shown.
File renamed without changes
File renamed without changes
Binary file removed src/assets/shared/button_generic.png
Binary file not shown.
Binary file removed src/assets/shared/field.png
Binary file not shown.
Binary file removed src/assets/shared/icon_checked_box.png
Binary file not shown.
Binary file removed src/assets/shared/icon_home.png
Binary file not shown.
Binary file removed src/assets/shared/icon_unchecked_box.png
Binary file not shown.
Binary file removed src/assets/shared/outline_full.png
Binary file not shown.
Binary file removed src/assets/sign_waiver/button_done_scanning.png
Binary file not shown.
Binary file removed src/assets/sign_waiver/outline_1.png
Binary file not shown.
Binary file removed src/assets/sign_waiver/outline_2.png
Binary file not shown.
Binary file removed src/assets/sign_waiver/outline_3.png
Binary file not shown.
Binary file removed src/assets/sign_waiver/qr_waiver.png
Binary file not shown.
3 changes: 0 additions & 3 deletions src/config.py

This file was deleted.

147 changes: 53 additions & 94 deletions src/controllers/account_controller.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,66 @@
from __future__ import annotations

import logging
from threading import Thread
from typing import Literal

from PyQt6.QtCore import QTimer
from pyqttoast import ToastPreset

from controllers.api_controller import ApiController, ExternalApiError
from controllers.api_controller import ExternalApiError
from misc.api_models import CreateAccountResponse, Student, StudentResponse
from misc.global_context import context


class AccountController:
def __init__(self, ctx):
self.ctx = ctx

def go_to_review_from_barcode(self, barcode):
self.ctx.nav.show_status("Looking up student...")
logging.info(f"looking up student by barcode: {barcode}")
Thread(target=self._lookup_barcode_worker, args=(barcode,), daemon=True).start()

def _lookup_barcode_worker(self, barcode):
try:
student = ApiController.lookup_by_barcode(barcode)
except ExternalApiError as e:
self.ctx.dispatcher.call.emit(lambda: self._on_external_api_error(e.api))
return
self.ctx.dispatcher.call.emit(lambda s=student: self._on_barcode_result(s))

def _on_barcode_result(self, student):
self.ctx.nav.hide_status()
if student is None:
self.ctx.nav.show_status("Student not found. Please enter your details manually.")
QTimer.singleShot(3000, self.ctx.nav.hide_status)
return
self.ctx.nav.go_to_create_account_review(
pid=student["pid"],
first_name=student["first_name"],
last_name=student["last_name"],
email=student["email"],
)
def __init__(self) -> None:
logging.info("account controller initialized")

def go_to_review_from_pid(self, pid):
self.ctx.nav.show_status("Looking up student...")
logging.info(f"looking up student by PID: {pid}")
Thread(target=self._lookup_pid_worker, args=(pid,), daemon=True).start()

def _lookup_pid_worker(self, pid):
def lookup(self, by: Literal["pid", "barcode"], value: str) -> Student | None:
context().main_window.show_toast_async("Looking Up Student", "", ToastPreset.INFORMATION)
logging.info(f"looking up student by {by}: {value}")
try:
student = ApiController.lookup_by_pid(pid)
response = context().api_controller.request("GET", f"/accounts/{by}/{value}")
if response.status_code == 404:
context().main_window.show_toast_async("Student Not Found", "Please enter your details manually", ToastPreset.ERROR)
return None
response.raise_for_status()
return StudentResponse.model_validate(response.json()).student
except ExternalApiError as e:
self.ctx.dispatcher.call.emit(lambda: self._on_external_api_error(e.api))
return
self.ctx.dispatcher.call.emit(lambda s=student: self._on_pid_result(s, pid))

def _on_pid_result(self, student, pid):
self.ctx.nav.hide_status()
if student is None:
self.ctx.nav.show_status("Student not found. Please check your PID.")
QTimer.singleShot(3000, self.ctx.nav.hide_status)
return
self.ctx.nav.go_to_create_account_review(
pid=pid,
first_name=student["first_name"],
last_name=student["last_name"],
email=student["email"],
)

def create_account_from_review(self, *, first_name, last_name, email, pid):
if pid:
self._create(pid=pid)
else:
self._create(first_name=first_name, last_name=last_name, email=email)
context().main_window.show_toast_async(f"System Error: {e.api}", "Please talk to a staff member", ToastPreset.ERROR)
return None
except Exception as e:
logging.error(f"error looking up student by {by}: {e}")
context().main_window.show_toast_async("Student Not Found", "Please enter your details manually", ToastPreset.ERROR)
return None

def _create(self, *, barcode=None, pid=None, first_name=None, last_name=None, email=None):
self.ctx.nav.show_status("Account creation in progress!")
def create_account(
self,
*,
barcode: str | None = None,
pid: str | None = None,
first_name: str | None = None,
last_name: str | None = None,
email: str | None = None,
) -> bool:
context().main_window.show_toast_async("Account creation in progress!", "", ToastPreset.INFORMATION)
logging.info(f"creating account: pid={pid} barcode={barcode}")
Thread(
target=self._create_worker,
kwargs=dict(barcode=barcode, pid=pid, first_name=first_name, last_name=last_name, email=email),
daemon=True,
).start()

def _on_external_api_error(self, api: str):
self.ctx.nav.hide_status()
self.ctx.nav.show_status(f"system error ({api.upper()} api). please talk to a staff member.")
QTimer.singleShot(4000, self.ctx.nav.hide_status)

def _create_worker(self, *, barcode, pid, first_name, last_name, email):
payload = {k: v for k, v in {
"rfid": context().session.rfid,
"barcode": barcode,
"pid": pid,
"first_name": first_name,
"last_name": last_name,
"email": email,
}.items() if v is not None}
try:
result = ApiController.create_account(
self.ctx.rfid,
barcode=barcode,
pid=pid,
first_name=first_name,
last_name=last_name,
email=email,
)
response = context().api_controller.request("POST", "/accounts", json=payload, timeout=8)
response.raise_for_status()
CreateAccountResponse.model_validate(response.json())
logging.info("account creation succeeded")
return True
except ExternalApiError as e:
self.ctx.dispatcher.call.emit(lambda: self._on_external_api_error(e.api))
return
self.ctx.dispatcher.call.emit(lambda r=result: self._on_create_result(r))

def _on_create_result(self, result):
self.ctx.nav.hide_status()
if result is None:
self.ctx.nav.show_status("ERROR! Could not create account, please try manually.")
QTimer.singleShot(3000, self.ctx.nav.hide_status)
return
logging.info("account creation succeeded")
self.ctx.nav.pop()
context().main_window.show_toast_async(f"System Error: {e.api}", "Please talk to a staff member", ToastPreset.ERROR)
return False
except Exception as e:
logging.error(f"error creating account: {e}")
context().main_window.show_toast_async("Account Creation Failed", "Please try again or talk to a staff member", ToastPreset.ERROR)
return False
Loading
Loading