Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8b6fbc7
branch init
affe4ever Oct 9, 2025
37c56a0
Refactor authentication and admin access control; improve login templ…
HampusSiik Oct 14, 2025
373b4ea
Refactor HTML templates for consistency in formating.
HampusSiik Oct 14, 2025
ec03010
Add initial Docker configuration files including .dockerignore, Docke…
HampusSiik Oct 14, 2025
abe1671
Update docker-compose.yml to simplify environment file reference and …
HampusSiik Oct 17, 2025
a92d3bb
Update docker-compose.yml to change port mapping to 5000 and specify …
HampusSiik Oct 21, 2025
c589989
small diagram
affe4ever Nov 11, 2025
06f6cae
i want to add that a meeting has documents... i dont know how
affe4ever Nov 11, 2025
e382b8e
det finns ett eer diagram nu :)
affe4ever Dec 2, 2025
5428d63
made a db
affe4ever Dec 2, 2025
c27fc41
changed ports and removed run command from compose file
affe4ever Dec 2, 2025
41a3f65
det finns en databas fr fr this time
affe4ever Dec 2, 2025
0681e3a
Added more files and directories to .dockerignore and order them dire…
HampusSiik Dec 5, 2025
aa6ca64
Add database module for database connections. Needs more work, maybe …
HampusSiik Dec 5, 2025
97b6b34
works on my computer
affe4ever Dec 9, 2025
1abb59c
waowzie
affe4ever Dec 9, 2025
31cb78f
Added some primal data handling functions and updated the admin page
affe4ever Dec 9, 2025
11a3621
databased
affe4ever Dec 16, 2025
18f2129
reduced script block to single beatiful row :)
affe4ever Dec 16, 2025
dd13791
working file upload
affe4ever Feb 5, 2026
3c94ffb
added functionality such that certain document types can be required
affe4ever Feb 5, 2026
f40704b
Added additional selections for upload frontend
affe4ever Feb 10, 2026
d4b908d
added uploading with doc type
affe4ever Feb 10, 2026
57616ae
added functionality to choose doc owner
affe4ever Feb 10, 2026
3ad6761
Added primal viewing function for docs
affe4ever Feb 10, 2026
85a0385
Added view of documents that you own belonging to specific meeting
affe4ever Feb 10, 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
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
__pycache__/
.github/
.venv/
.vscode/
documentation/
instance/
summit_database/
tests/
.dockerignore
.env
.env-example
.gitignore
docker-compose.yml
Dockerfile
LICENSE
README.md
14 changes: 14 additions & 0 deletions .env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
GAMMA_CLIENT_ID=
GAMMA_CLIENT_SECRET=
GAMMA_CLIENT_API_KEY=
AUTH_HEADER=pre-shared ...
APP_SECRET_KEY="supersecretkey_B)"
FLASK_APP=project
FLASK_DEBUG=1
DEV_ENV=1
DB_HOST=summit_db
DB_PORT=5432
POSTGRES_DB=summit_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=
PGUSER=postgres
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:3.13-alpine

RUN pip install uv

RUN uv --version

WORKDIR /app

COPY pyproject.toml uv.lock* ./

RUN uv sync --no-dev

COPY . .

EXPOSE 5000

CMD ["uv", "run", "gunicorn", "--workers", "3", \
"--bind", "0.0.0.0:5000", "wsgi:app"]
32 changes: 32 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
services:
summit_web:
build: .
ports:
- "5000:5000"
env_file:
- .env
volumes:
- summit_uploads:/data/uploads
depends_on:
summit_db:
condition: service_healthy
restart: on-failure

summit_db:
build: summit_database
env_file:
- .env
ports:
- "5432:5432"
volumes:
- summit_database_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'pg_isready']
interval: 5s
timeout: 5s
retries: 5
start_period: 10s

volumes:
summit_database_data:
summit_uploads:
95 changes: 95 additions & 0 deletions documentation/er.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@startchen data_base_diagram
left to right direction

entity "Document" as Doc {
document_id : id <<key>>
document_name : STRING
file_path : STRING <<key>>
uploaded : datetime
}

entity "Division Document" as DivisionDoc {

}

entity "Division Document Type" as DivisionDocType {

}

entity "Meeting Document" as MeetingDoc {

}

entity "Meeting Document Type" as MeetingDocType {

}

entity "Committe" as Committee {
id : groupId <<key>>
}

entity "Member" as Member {
id : userId <<key>>
}

entity "Document Owner" as DocOwner{
id : id <<key>>

}

relationship "Owns" as DocOwnerOwnsDoc {

}

DocOwner -N- DocOwnerOwnsDoc
DocOwnerOwnsDoc =1= Doc
DocOwner =>= d {Committee, Member}

entity "Study Period" as StudyPeriod {
year : INT <<key>>
period : INT <<key>>
id : id <<key>>
}

entity "Meeting" as Meeting {
id : id <<key>>
date : timestamp <<key>>
}

relationship "Has" as StudyPeriodHasMeeting {
}

Meeting =N= StudyPeriodHasMeeting
StudyPeriodHasMeeting -1- StudyPeriod

Doc =>= d {DivisionDoc, MeetingDoc}

relationship "Has" as StudyPeriodHasDivisionDoc {

}

StudyPeriod -1- StudyPeriodHasDivisionDoc
StudyPeriodHasDivisionDoc =N= DivisionDoc

relationship "Has" as MeetingHasMeetingDoc {

}

Meeting -1- MeetingHasMeetingDoc
MeetingHasMeetingDoc =N= MeetingDoc

relationship "Is of type" as DivOfType {

}

relationship "Is of type" as MeetOfType {

}

DivisionDocType -1- DivOfType
DivOfType =N= DivisionDoc

MeetingDocType -1- MeetOfType
MeetOfType =N= MeetingDoc

@endchen
3 changes: 3 additions & 0 deletions project/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from authlib.integrations.flask_client import OAuth
from .auth import auth as auth_blueprint
from .main import main as main_blueprint
from .database import db
import os


Expand All @@ -14,6 +15,8 @@ def create_app():

app = Flask(__name__)

db.init_app(app)

app.config["SECRET_KEY"] = os.getenv("APP_SECRET_KEY", "")

# Initialize OAuth with the Flask app
Expand Down
129 changes: 87 additions & 42 deletions project/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@
session,
current_app,
g,
request
request,
)
from authlib.integrations.flask_client import OAuth
from functools import wraps
from dotenv import load_dotenv
import os
import requests

load_dotenv()


def devmode_active():
return os.getenv("DEV_ENV", "False").lower() in ("true", "1", "t")


# Allow HTTP for local development (required for OAuth2Session)
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
if devmode_active():
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

load_dotenv()
gamma_root = os.getenv("GAMMA_ROOT", "https://auth.chalmers.it")
auth_header = os.getenv("AUTH_HEADER", "")
client_api_root = f"{gamma_root}/api/client/v1"
Expand All @@ -34,12 +40,37 @@ def get_gamma():
return oauth.gamma


def is_authenticated():
return session.get("authenticated", False)


def set_user_in_g():
g.user = session.get("user")


def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not session.get("authenticated"):
if not is_authenticated():
return redirect(url_for("auth.login"))
g.user = session.get("user")
set_user_in_g()
return f(*args, **kwargs)

return decorated_function


def is_admin():
user = session.get("user", {})
user_groups = [group.get("name", "") for group in user.get("groups", [])]
return any(group in user_groups for group in ["motespresidit", "styrit"])


def login_as_admin_required(f):
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
if not is_admin():
return render_template("denied.html"), 401
return f(*args, **kwargs)

return decorated_function
Expand All @@ -55,7 +86,8 @@ def login():
@auth.route("/authorize")
def authorize():
gamma = get_gamma()
session['selected_extra_groups'] = request.args.getlist("extra_groups")
if devmode_active():
session["selected_extra_groups"] = request.args.getlist("extra_groups")
return gamma.authorize_redirect(url_for("auth.callback", _external=True))


Expand All @@ -81,62 +113,74 @@ def callback():
id = user_info.get("sub")
r = requests.Session()
r.headers.update({"Authorization": auth_header})
groups_response = r.get(F"{client_api_groups_for}/{id}").json()
groups_response = r.get(f"{client_api_groups_for}/{id}").json()

# Filter groups to only include committees
active_groups = [
{
"id": group.get("id"),
"prettyName": group.get("prettyName", {}),
"name": group.get("superGroup", {}).get("name"),
"post": group.get("post", {}).get("enName"),
}
for group in groups_response
if group.get("superGroup", {}).get("type") != "alumni"
]
# {
# "id": "ab44f720-8ed9-48b4-ba2a-6fb2a03db8f6",
# "name": "digit25",
# "prettyName": "digIT 25",
# "superGroup": {
# "id": "dea3493e-66e4-44b2-a657-cb57a6840dab",
# "name": "digit",
# "prettyName": "digIT",
# "type": "committee",
# "svDescription": "Digitala system",
# "enDescription": "Digital systems"
# },
# "post": {
# "id": "2cf9773d-da45-4532-8203-b085baaaf413",
# "version": 30,
# "svName": "Vice Ordförande",
# "enName": "Vice Chairman"
# }
# }
# {
# "id": "ab44f720-8ed9-48b4-ba2a-6fb2a03db8f6",
# "name": "digit25",
# "prettyName": "digIT 25",
# "superGroup": {
# "id": "dea3493e-66e4-44b2-a657-cb57a6840dab",
# "name": "digit",
# "prettyName": "digIT",
# "type": "committee",
# "svDescription": "Digitala system",
# "enDescription": "Digital systems"
# },
# "post": {
# "id": "2cf9773d-da45-4532-8203-b085baaaf413",
# "version": 30,
# "svName": "Vice Ordförande",
# "enName": "Vice Chairman"
# }
# }

except Exception as e:
print(f"Failed to get api information: {e}")
active_groups = "N/A"
active_groups = []

extra_groups = [
{
"name": "devit",
"post": "Chairman",
}
if args == 'devit_ordf'
else {
"name": "devit",
"post": "Treasurer",
}
if args == 'devit_kass'
else {
"name": args,
"post": "Member",
}
for args in session['selected_extra_groups']
(
{
"id": "dev-group-id-devit",
"name": "devit",
"prettyName": "DevIT (Chairman)",
"post": "Chairman",
}
if args == "devit_ordf"
else (
{
"id": "dev-group-id-devit",
"name": "devit",
"prettyName": "DevIT (Treasurer)",
"post": "Treasurer",
}
if args == "devit_kass"
else {
"id": f"dev-group-id-{args}",
"name": args,
"prettyName": "MötespresidIT" if args == "motespresidit" else ("styrIT" if args == "styrit" else args.upper()),
"post": "Member",
}
)
)
for args in session.get("selected_extra_groups", [])
]
session.pop("selected_extra_groups", None)

essential_user_info = {
"id": user_info.get("sub"),
"name": user_info.get("name"),
"email": user_info.get("email"),
"cid": user_info.get("cid"),
Expand All @@ -149,6 +193,7 @@ def callback():
session["user"] = essential_user_info
# Don't store the full token to save space
session["authenticated"] = True
session["admin"] = is_admin()

return redirect(url_for("main.profile"))

Expand Down
Loading