Skip to content

Commit

Permalink
Fix wall of shame (#349)
Browse files Browse the repository at this point in the history
* fix: tabnav being dumb

* fix: wall of shame loading slow

* fix: various small bugs
  • Loading branch information
Terbau authored Mar 24, 2024
1 parent 5da7e74 commit b154cc9
Show file tree
Hide file tree
Showing 24 changed files with 307 additions and 255 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ dist-ssr

tmpMock.ts
.python-version
.env
2 changes: 1 addition & 1 deletion backend/app/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


oidc = OpenIdConnect(
openIdConnectUrl="https://old.online.ntnu.no/openid/.well-known/openid-configuration",
openIdConnectUrl="https://auth.online.ntnu.no/openid/.well-known/openid-configuration",
)


Expand Down
30 changes: 25 additions & 5 deletions backend/app/api/endpoints/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
User endpoints
"""

from app.models.punishment import LeaderboardPunishmentRead
from fastapi import APIRouter, Depends, HTTPException, Query

from app.api import APIRoute, Request, oidc
from app.exceptions import NotFound
from app.models.group import UserWithGroups
from app.models.user import LeaderboardUser
from app.models.user import MinifiedLeaderboardUser
from app.utils.pagination import Page, Pagination
from app.types import UserId

router = APIRouter(
prefix="/users",
Expand Down Expand Up @@ -61,14 +63,14 @@ async def get_me(

@router.get(
"/leaderboard",
response_model=Page[LeaderboardUser],
response_model=Page[MinifiedLeaderboardUser],
dependencies=[Depends(oidc)],
)
async def get_leadeboard(
request: Request,
page: int = Query(title="Page number", default=0, ge=0),
page_size: int = Query(title="Page size", default=30, ge=1, le=50),
) -> Page[LeaderboardUser]:
) -> Page[MinifiedLeaderboardUser]:
access_token = request.raise_if_missing_authorization()

app = request.app
Expand All @@ -81,11 +83,29 @@ async def get_leadeboard(
status_code=403, detail="Du har ikke tilgang til denne ressursen"
)

pagination = Pagination[LeaderboardUser](
pagination = Pagination[MinifiedLeaderboardUser](
request=request,
total_coro=app.db.users.get_leaderboard_count,
results_coro=app.db.users.get_leaderboard,
# results_coro=app.db.users.get_leaderboard,
results_coro=app.db.users.get_minified_leaderboard,
page=page,
page_size=page_size,
)
return await pagination.paginate(conn=conn)

@router.get(
"/leaderboard/punishments/{user_id}",
response_model=list[LeaderboardPunishmentRead],
dependencies=[Depends(oidc)],
)
async def get_user_punishments(
request: Request,
user_id: UserId,
) -> list[LeaderboardPunishmentRead]:
access_token = request.raise_if_missing_authorization()

app = request.app
_, _ = await app.ow_sync.sync_for_access_token(access_token)

async with app.db.pool.acquire() as conn:
return await app.db.users.get_punishments_for_leaderboard_user(user_id, conn=conn)
6 changes: 1 addition & 5 deletions backend/app/api/init_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async def shutdown_handler() -> None:

def init_api(**db_settings: str) -> FastAPI:
oauth = {
"clientId": "219919",
"clientId": "5rOMfB8Ztegz",
"appName": "Vengeful Vineyard Docs",
"usePkceWithAuthorizationCodeGrant": True,
"scopes": "openid email profile onlineweb4",
Expand All @@ -122,10 +122,6 @@ def init_api(**db_settings: str) -> FastAPI:
swagger_ui_oauth2_redirect_url="/docs/oauth2-redirect",
)

@app.get("/crash")
async def crash():
raise Exception("wow i crashed")

app.openapi_version = "3.0.0"
app.router.route_class = APIRoute
init_middlewares(app)
Expand Down
1 change: 1 addition & 0 deletions backend/app/db/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ async def insert_members(
FROM
unnest($1::group_members[]) as m
)
ON CONFLICT (group_id, user_id) DO NOTHING
RETURNING *
"""

Expand Down
182 changes: 94 additions & 88 deletions backend/app/db/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from app.exceptions import DatabaseIntegrityException, NotFound
from app.models.group import Group
from app.models.user import LeaderboardUser, User, UserCreate, UserUpdate
from app.models.user import LeaderboardUser, MinifiedLeaderboardUser, User, UserCreate, UserUpdate
from app.models.punishment import LeaderboardPunishmentRead
from app.types import InsertOrUpdateUser, OWUserId, UserId
from app.utils.db import MaybeAcquire

Expand Down Expand Up @@ -44,93 +45,6 @@ async def get_leaderboard_count(
assert isinstance(res, int)
return res

async def get_leaderboard(
self,
offset: int,
limit: int,
force_include_reasons: bool = False,
conn: Optional[Pool] = None,
) -> list[LeaderboardUser]:
async with MaybeAcquire(conn, self.db.pool) as conn:
query = """
WITH punishments_with_reactions AS (
SELECT
gp.*,
COALESCE(NULLIF(u.first_name, ''), u.email) || ' ' || u.last_name as created_by_name,
COALESCE(json_agg(json_build_object(
'punishment_reaction_id', pr.punishment_reaction_id,
'punishment_id', pr.punishment_id,
'emoji', pr.emoji,
'created_at', pr.created_at,
'created_by', pr.created_by,
'created_by_name', (SELECT COALESCE(NULLIF(first_name, ''), email) || ' ' || last_name FROM users WHERE user_id = pr.created_by)
)) FILTER (WHERE pr.punishment_reaction_id IS NOT NULL), '[]') as reactions
FROM group_punishments gp
LEFT JOIN punishment_reactions pr
ON pr.punishment_id = gp.punishment_id
LEFT JOIN users u
ON u.user_id = gp.created_by
LEFT JOIN groups g
ON g.group_id = gp.group_id
WHERE g.ow_group_id IS NOT NULL OR special
GROUP BY gp.punishment_id, created_by_name
)
SELECT u.*,
COALESCE(json_agg(
json_build_object(
'punishment_id', pwr.punishment_id,
'user_id', pwr.user_id,
'punishment_type_id', pwr.punishment_type_id,
'reason', pwr.reason,
'reason_hidden', pwr.reason_hidden,
'amount', pwr.amount,
'created_by', pwr.created_by,
'created_by_name', pwr.created_by_name,
'created_at', pwr.created_at,
'group_id', pwr.group_id,
'paid', pwr.paid,
'paid_at', pwr.paid_at,
'marked_paid_by', pwr.marked_paid_by,
'reactions', pwr.reactions,
'punishment_type', (SELECT json_build_object(
'punishment_type_id', pt.punishment_type_id,
'name', pt.name,
'value', pt.value,
'emoji', pt.emoji,
'created_at', pt.created_at,
'created_by', pt.created_by,
'updated_at', pt.updated_at
) FROM punishment_types pt WHERE pt.punishment_type_id = pwr.punishment_type_id)
)
) FILTER (WHERE pwr.punishment_id IS NOT NULL), '[]') AS punishments,
COALESCE(SUM(pwr.amount * pt.value), 0) as total_value
FROM users u
LEFT JOIN punishments_with_reactions pwr
ON pwr.user_id = u.user_id
LEFT JOIN punishment_types pt
ON pt.punishment_type_id = pwr.punishment_type_id
INNER JOIN groups g
ON g.group_id = pwr.group_id AND g.ow_group_id IS NOT NULL OR special
GROUP BY u.user_id
ORDER BY total_value DESC, u.first_name ASC
OFFSET $1
LIMIT $2"""
res = await conn.fetch(
query,
offset,
limit,
)

users = [LeaderboardUser(**r) for r in res]

if not force_include_reasons:
for user in users:
for punishment in user.punishments:
if punishment.reason_hidden:
punishment.reason = ""

return users

async def get_all_raw(
self,
conn: Optional[Pool] = None,
Expand Down Expand Up @@ -432,3 +346,95 @@ async def get_groups(

result = await conn.fetch(query, user_id)
return [Group(**row) for row in result]

async def get_minified_leaderboard(
self,
offset: int,
limit: int,
conn: Optional[Pool] = None,
) -> list[MinifiedLeaderboardUser]:
async with MaybeAcquire(conn, self.db.pool) as conn:
query = """
SELECT
DISTINCT u.user_id,
u.first_name,
u.last_name,
u.email,
u.ow_user_id,
COALESCE(p.total_value, 0) AS total_value,
COALESCE(p.emojis, '') AS emojis,
COALESCE(p.amount_punishments, 0) AS amount_punishments,
COALESCE(p.amount_unique_punishments, 0) AS amount_unique_punishments
FROM users u
LEFT JOIN (
SELECT
p.user_id,
SUM(pt.value * p.amount) AS total_value,
STRING_AGG(REPEAT(pt.emoji, p.amount), '') AS emojis,
SUM(p.amount) AS amount_punishments,
COUNT(DISTINCT p.punishment_type_id) AS amount_unique_punishments
FROM group_punishments p
LEFT JOIN punishment_types pt
ON pt.punishment_type_id = p.punishment_type_id
LEFT JOIN groups g
ON g.group_id = p.group_id
WHERE g.ow_group_id IS NOT NULL
GROUP BY p.user_id
) p ON p.user_id = u.user_id
LEFT JOIN group_members gm
ON gm.user_id = u.user_id
LEFT JOIN groups g
ON g.group_id = gm.group_id
WHERE g.ow_group_id IS NOT NULL OR g.special
ORDER BY total_value DESC, u.first_name ASC
OFFSET $1
LIMIT $2
"""
res = await conn.fetch(
query,
offset,
limit,
)

return [MinifiedLeaderboardUser(**r) for r in res]

async def get_punishments_for_leaderboard_user(
self,
user_id: UserId,
conn: Optional[Pool] = None,
) -> list[LeaderboardPunishmentRead]:
async with MaybeAcquire(conn, self.db.pool) as conn:
query = """
SELECT
gp.*,
CONCAT(COALESCE(NULLIF(users.first_name, ''), users.email), ' ', users.last_name) AS created_by_name,
COALESCE(json_agg(pr) FILTER (WHERE pr.punishment_reaction_id IS NOT NULL), '[]') as reactions,
json_build_object(
'punishment_type_id', pt.punishment_type_id,
'name', pt.name,
'value', pt.value,
'emoji', pt.emoji,
'created_at', pt.created_at,
'created_by', pt.created_by,
'updated_at', pt.updated_at
) AS punishment_type
FROM group_punishments gp
LEFT JOIN punishment_types pt
ON pt.punishment_type_id = gp.punishment_type_id
LEFT JOIN (
SELECT pr1.*
FROM punishment_reactions pr1
JOIN group_members gm ON pr1.created_by = gm.user_id
GROUP BY pr1.punishment_reaction_id
) pr ON pr.punishment_id = gp.punishment_id
LEFT JOIN users ON gp.created_by = users.user_id
WHERE gp.user_id = $1
GROUP BY gp.punishment_id, created_by_name, pt.punishment_type_id
ORDER BY gp.created_at DESC
"""
res = await conn.fetch(
query,
user_id,
)

return [LeaderboardPunishmentRead(**r) for r in res]
5 changes: 5 additions & 0 deletions backend/app/models/punishment.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime
from typing import Optional

from app.models.punishment_type import PunishmentTypeRead
from pydantic import BaseModel # pylint: disable=no-name-in-module

from app.models.punishment_reaction import PunishmentReactionRead
Expand Down Expand Up @@ -42,6 +43,10 @@ class PunishmentRead(PunishmentOut):
user_id: UserId


class LeaderboardPunishmentRead(PunishmentRead):
punishment_type: PunishmentTypeRead


class PunishmentStreaks(BaseModel):
current_streak: int
longest_streak: int
Expand Down
7 changes: 7 additions & 0 deletions backend/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ class LeaderboardPunishmentOut(PunishmentOut):
class LeaderboardUser(User):
punishments: list[LeaderboardPunishmentOut]
total_value: int


class MinifiedLeaderboardUser(User):
total_value: int
emojis: str
amount_punishments: int
amount_unique_punishments: int
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react-router-dom": "^6.15.0",
"react-simple-oauth2-login": "^0.5.4",
"react-table": "^7.8.0",
"tailwind-scrollbar-hide": "^1.1.7",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
15 changes: 11 additions & 4 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b154cc9

Please sign in to comment.