Skip to content

Commit dd96f12

Browse files
authored
Merge branch 'main' into dev-84
2 parents 290cfaf + efe6e59 commit dd96f12

31 files changed

+367
-134
lines changed

.github/workflows/ruff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
- uses: actions/checkout@v4
88
- uses: chartboost/ruff-action@v1
99
with:
10-
version: 0.4.4
10+
version: 0.6.9

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ google_key.json
99
__pycache__/
1010
*.py[cod]
1111
*$py.class
12+
13+
.venv
14+
.DS_Store

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ The backend & REST API for the CSSS website. Anything the website frontend does
88

99
## Local Development
1010

11-
See [the csss-backend wiki](https://github.com/CSSS/csss-site-backend/wiki/Local-Setup) for details on how to run the REST API locally on your own machine.
11+
See [the csss-backend wiki](https://github.com/CSSS/csss-site-backend/wiki/1.-Local-Setup) for details on how to run the REST API locally on your own machine.
1212

13-
If you're planning to read through the source code, please check out this project's [naming conventions](https://github.com/CSSS/csss-site-backend/wiki/Naming-conventions).
13+
If you're planning to read through the source code, please check out this project's [naming conventions](https://github.com/CSSS/csss-site-backend/wiki/Style-Guide#naming-conventions).
1414

1515
## Important Directories
1616

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
fastapi==0.109.1
33
gunicorn==21.2.0
44
uvicorn[standard]==0.27.1
5-
sqlalchemy==2.0.27
5+
sqlalchemy[asyncio]==2.0.27
66
asyncpg==0.29.0
77
alembic==1.13.1
88
google-api-python-client==2.143.0
@@ -13,7 +13,7 @@ xmltodict==0.13.0 # for parsing responses from sfu it's auth api
1313
requests==2.31.0
1414

1515
# dev
16-
ruff
16+
ruff==0.6.9
1717
# pre-commit
1818

1919
# test

src/alembic/env.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import asyncio
22
from logging.config import fileConfig
33

4+
from sqlalchemy import pool
5+
from sqlalchemy.engine import Connection
6+
from sqlalchemy.ext.asyncio import async_engine_from_config
7+
48
import auth.tables
9+
import blog.tables
510
import database
611
import officers.tables
712
from alembic import context
8-
from sqlalchemy import pool
9-
from sqlalchemy.engine import Connection
10-
from sqlalchemy.ext.asyncio import async_engine_from_config
1113

1214
# this is the Alembic Config object, which provides
1315
# access to the values within the .ini file in use.

src/alembic/versions/166f3772fce7_auth_officer_init.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Union
1212

1313
import sqlalchemy as sa
14+
1415
from alembic import op
1516

1617
# revision identifiers, used by Alembic.
@@ -26,6 +27,7 @@ def upgrade() -> None:
2627
sa.Column("computing_id", sa.String(32), primary_key=True),
2728
sa.Column("first_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
2829
sa.Column("last_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
30+
sa.Column("profile_picture_url", sa.Text(), nullable=True),
2931
)
3032
op.create_table(
3133
"user_session",
@@ -56,7 +58,7 @@ def upgrade() -> None:
5658
sa.Column("discord_id", sa.String(length=18), nullable=True),
5759
sa.Column("discord_name", sa.String(length=32), nullable=True),
5860
sa.Column("discord_nickname", sa.String(length=32), nullable=True),
59-
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("user_session.computing_id"), primary_key=True),
61+
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("site_user.computing_id"), primary_key=True),
6062
sa.Column("phone_number", sa.String(length=24), nullable=True),
6163
sa.Column("github_username", sa.String(length=39), nullable=True),
6264
sa.Column("google_drive_email", sa.String(length=256), nullable=True),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""blog_posts
2+
3+
Revision ID: 2a6ea95342dc
4+
Revises: 43f71e4bd6fc
5+
Create Date: 2024-08-31 03:06:11.516362
6+
7+
"""
8+
from collections.abc import Sequence
9+
from typing import Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "2a6ea95342dc"
17+
down_revision: str | None = "166f3772fce7"
18+
branch_labels: str | Sequence[str] | None = None
19+
depends_on: str | Sequence[str] | None = None
20+
21+
22+
def upgrade() -> None:
23+
op.create_table("blog_posts",
24+
sa.Column("title", sa.String(length=128), primary_key=True, nullable=False),
25+
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("officer_info.computing_id"), nullable=False),
26+
sa.Column("date_created", sa.DateTime(), nullable=False),
27+
sa.Column("last_edited", sa.DateTime(), nullable=False),
28+
sa.Column("html_content", sa.Text(), nullable=False),
29+
sa.Column("post_tags", sa.String(length=128), nullable=True),
30+
)
31+
32+
33+
def downgrade() -> None:
34+
op.drop_table("blog_posts")

src/auth/crud.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import logging
22
from datetime import datetime, timedelta
3-
from typing import Optional
43

54
import sqlalchemy
6-
from auth.tables import SiteUser, UserSession
75
from sqlalchemy.ext.asyncio import AsyncSession
86

7+
from auth.tables import SiteUser, UserSession
8+
from auth.types import SiteUserData
9+
910

1011
# TODO: put "task_" before
1112
async def create_user_session(db_session: AsyncSession, session_id: str, computing_id: str):
@@ -52,30 +53,20 @@ async def create_user_session(db_session: AsyncSession, session_id: str, computi
5253
issue_time=datetime.now(),
5354
))
5455

56+
new_user_session = UserSession(
57+
issue_time=datetime.now(),
58+
session_id=session_id,
59+
computing_id=computing_id,
60+
)
61+
db_session.add(new_user_session)
62+
5563

5664
async def remove_user_session(db_session: AsyncSession, session_id: str) -> dict:
5765
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
5866
user_session = await db_session.scalars(query)
5967
await db_session.delete(user_session.first())
6068

6169

62-
async def check_user_session(db_session: AsyncSession, session_id: str) -> dict:
63-
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
64-
existing_user_session = (await db_session.scalars(query)).first()
65-
66-
if existing_user_session:
67-
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == existing_user_session.computing_id)
68-
existing_user = (await db_session.scalars(query)).first()
69-
return {
70-
"is_valid": True,
71-
"computing_id": existing_user_session.computing_id,
72-
"first_logged_in": existing_user.first_logged_in.isoformat(),
73-
"last_logged_in": existing_user.last_logged_in.isoformat()
74-
}
75-
else:
76-
return {"is_valid": False}
77-
78-
7970
async def get_computing_id(db_session: AsyncSession, session_id: str) -> str | None:
8071
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
8172
existing_user_session = (await db_session.scalars(query)).first()
@@ -91,7 +82,8 @@ async def task_clean_expired_user_sessions(db_session: AsyncSession):
9182
await db_session.commit()
9283

9384

94-
async def user_info(db_session: AsyncSession, session_id: str) -> None | dict:
85+
# get the site user given a session ID; returns None when session is invalid
86+
async def get_site_user(db_session: AsyncSession, session_id: str) -> None | SiteUserData:
9587
query = (
9688
sqlalchemy
9789
.select(UserSession)
@@ -110,8 +102,43 @@ async def user_info(db_session: AsyncSession, session_id: str) -> None | dict:
110102
if user is None:
111103
return None
112104

113-
return {
114-
"computing_id": user_session.computing_id,
115-
"first_logged_in": user.first_logged_in.isoformat(),
116-
"last_logged_in": user.last_logged_in.isoformat()
117-
}
105+
return SiteUserData(
106+
user_session.computing_id,
107+
user.first_logged_in.isoformat(),
108+
user.last_logged_in.isoformat(),
109+
user.profile_picture_url
110+
)
111+
112+
113+
# update the optional user info for a given site user (e.g., display name, profile picture, ...)
114+
async def update_site_user(
115+
db_session: AsyncSession,
116+
session_id: str,
117+
profile_picture_url: str
118+
) -> None | SiteUserData:
119+
query = (
120+
sqlalchemy
121+
.select(UserSession)
122+
.where(UserSession.session_id == session_id)
123+
)
124+
user_session = await db_session.scalar(query)
125+
if user_session is None:
126+
return None
127+
128+
query = (
129+
sqlalchemy
130+
.update(SiteUser)
131+
.where(SiteUser.computing_id == user_session.computing_id)
132+
.values(profile_picture_url = profile_picture_url)
133+
.returning(SiteUser) # returns all columns of SiteUser
134+
)
135+
user = await db_session.scalar(query)
136+
if user is None:
137+
return None
138+
139+
return SiteUserData(
140+
user_session.computing_id,
141+
user.first_logged_in.isoformat(),
142+
user.last_logged_in.isoformat(),
143+
user.profile_picture_url
144+
)

src/auth/tables.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from datetime import datetime
22

3+
from sqlalchemy import Column, DateTime, ForeignKey, String, Text
4+
35
from constants import COMPUTING_ID_LEN, SESSION_ID_LEN
46
from database import Base
5-
from sqlalchemy import Column, DateTime, ForeignKey, String
67

78

89
class UserSession(Base):
@@ -37,3 +38,6 @@ class SiteUser(Base):
3738
# note: default date (for pre-existing columns) is June 16th, 2024
3839
first_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))
3940
last_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))
41+
42+
# optional user information for display purposes
43+
profile_picture_url = Column(Text, nullable=True)

src/auth/types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class SiteUserData:
6+
computing_id: str
7+
first_logged_in: str
8+
last_logged_in: str
9+
profile_picture_url: None | str
10+
11+
def serializable_dict(self):
12+
return {
13+
"computing_id": self.computing_id,
14+
"first_logged_in": self.first_logged_in,
15+
"last_logged_in": self.last_logged_in,
16+
"profile_picture_url": self.profile_picture_url
17+
}

0 commit comments

Comments
 (0)