From 3cdee226b9d1d7dc6091d1e0c74397fd3378a764 Mon Sep 17 00:00:00 2001 From: tiptenbrink <75669206+tiptenbrink@users.noreply.github.com> Date: Wed, 27 Dec 2023 10:54:18 +0100 Subject: [PATCH 1/4] feat: add metadata to ranking response to know frozen and last updated --- backend/src/apiserver/app/routers/ranking.py | 39 ++++++++++++++++++- .../src/apiserver/data/context/app_context.py | 3 +- backend/src/apiserver/data/context/ranking.py | 11 +++++- backend/src/apiserver/lib/model/entities.py | 6 +++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/backend/src/apiserver/app/routers/ranking.py b/backend/src/apiserver/app/routers/ranking.py index d9e9c4bc..4a575d64 100644 --- a/backend/src/apiserver/app/routers/ranking.py +++ b/backend/src/apiserver/app/routers/ranking.py @@ -23,6 +23,7 @@ ClassEvent, NewEvent, NewTrainingEvent, + RankingInfo, UserEvent, UserPointsNames, UserPointsNamesList, @@ -75,10 +76,46 @@ async def get_classification( debug_key="bad_ranking", ) - user_points = await context_most_recent_class_points(ctx, dsrc, rank_type, admin) + user_points = ( + await context_most_recent_class_points(ctx, dsrc, rank_type, admin) + ).points return RawJSONResponse(UserPointsNamesList.dump_json(user_points)) +async def get_classification_with_meta( + dsrc: Source, ctx: RankingContext, rank_type: str, admin: bool = False +) -> RawJSONResponse: + if not is_rank_type(rank_type): + reason = f"Ranking {rank_type} is unknown!" + raise ErrorResponse( + status_code=400, + err_type="invalid_ranking", + err_desc=reason, + debug_key="bad_ranking", + ) + + ranking_info = await context_most_recent_class_points(ctx, dsrc, rank_type, admin) + return RawJSONResponse(RankingInfo.model_dump_json(ranking_info).encode()) + + +@ranking_members_router.get("/get_meta/{rank_type}/", response_model=RankingInfo) +async def member_classification_meta( + rank_type: str, dsrc: SourceDep, app_context: AppContext +) -> RawJSONResponse: + return await get_classification_with_meta( + dsrc, app_context.rank_ctx, rank_type, False + ) + + +@ranking_admin_router.get("/get_meta/{rank_type}/", response_model=RankingInfo) +async def member_classification_admin_meta( + rank_type: str, dsrc: SourceDep, app_context: AppContext +) -> RawJSONResponse: + return await get_classification_with_meta( + dsrc, app_context.rank_ctx, rank_type, True + ) + + @ranking_members_router.get("/get/{rank_type}/", response_model=list[UserPointsNames]) async def member_classification( rank_type: str, dsrc: SourceDep, app_context: AppContext diff --git a/backend/src/apiserver/data/context/app_context.py b/backend/src/apiserver/data/context/app_context.py index aa99c7e0..d06fdd74 100644 --- a/backend/src/apiserver/data/context/app_context.py +++ b/backend/src/apiserver/data/context/app_context.py @@ -19,6 +19,7 @@ from apiserver.lib.model.entities import ( ClassEvent, NewEvent, + RankingInfo, UserData, User, UserEvent, @@ -76,7 +77,7 @@ async def context_most_recent_class_id_of_type( @classmethod async def context_most_recent_class_points( cls, dsrc: Source, rank_type: Literal["points", "training"], is_admin: bool - ) -> list[UserPointsNames]: + ) -> RankingInfo: raise ContextNotImpl() @classmethod diff --git a/backend/src/apiserver/data/context/ranking.py b/backend/src/apiserver/data/context/ranking.py index ec75dfc7..f07c2b3f 100644 --- a/backend/src/apiserver/data/context/ranking.py +++ b/backend/src/apiserver/data/context/ranking.py @@ -1,3 +1,4 @@ +from datetime import date from apiserver.data.api.trainings import add_training_event from datacontext.context import ContextRegistry from typing import Any, Literal @@ -11,6 +12,7 @@ ClassView, NewEvent, NewTrainingEvent, + RankingInfo, UserEvent, UserPointsNames, ) @@ -141,14 +143,19 @@ async def context_most_recent_class_id_of_type( @ctx_reg.register(RankingContext) async def context_most_recent_class_points( dsrc: Source, rank_type: Literal["points", "training"], is_admin: bool -) -> list[UserPointsNames]: +) -> RankingInfo: async with get_conn(dsrc) as conn: class_view = await most_recent_class_of_type(conn, rank_type) user_points = await all_points_in_class( conn, class_view.classification_id, is_admin ) - return user_points + is_frozen = date.today() >= class_view.hidden_date + ranking_info = RankingInfo( + points=user_points, last_updated=class_view.last_updated, frozen=is_frozen + ) + + return ranking_info @ctx_reg.register(RankingContext) diff --git a/backend/src/apiserver/lib/model/entities.py b/backend/src/apiserver/lib/model/entities.py index fbedea1c..d13d5786 100644 --- a/backend/src/apiserver/lib/model/entities.py +++ b/backend/src/apiserver/lib/model/entities.py @@ -223,6 +223,12 @@ class UserPointsNames(BaseModel): UserPointsNamesList = TypeAdapter(List[UserPointsNames]) +class RankingInfo(BaseModel): + last_updated: date + frozen: bool + points: list[UserPointsNames] + + # class PointsData(BaseModel): # points: int From 217f5be076c4a92c3df6a92553666eedfb5b3f2a Mon Sep 17 00:00:00 2001 From: tiptenbrink <75669206+tiptenbrink@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:25:20 +0100 Subject: [PATCH 2/4] feat: correct get meta, update class, new class --- backend/src/apiserver/app/routers/ranking.py | 41 +++++++++------ .../src/apiserver/data/api/classifications.py | 22 ++++++-- .../src/apiserver/data/context/app_context.py | 21 ++++++++ backend/src/apiserver/data/context/ranking.py | 51 ++++++++++++++++--- backend/src/apiserver/lib/model/entities.py | 13 +++++ backend/src/store/db.py | 21 ++++++++ dev.nu | 4 +- 7 files changed, 145 insertions(+), 28 deletions(-) diff --git a/backend/src/apiserver/app/routers/ranking.py b/backend/src/apiserver/app/routers/ranking.py index 4a575d64..7ae49252 100644 --- a/backend/src/apiserver/app/routers/ranking.py +++ b/backend/src/apiserver/app/routers/ranking.py @@ -15,12 +15,18 @@ from apiserver.data.context.ranking import ( add_new_event, add_new_training, + context_modify_class, context_most_recent_class_points, + context_new_classes, + most_recent_classes, sync_publish_ranking, ) from apiserver.lib.logic.ranking import is_rank_type from apiserver.lib.model.entities import ( ClassEvent, + ClassMetaList, + ClassUpdate, + ClassView, NewEvent, NewTrainingEvent, RankingInfo, @@ -98,22 +104,13 @@ async def get_classification_with_meta( return RawJSONResponse(RankingInfo.model_dump_json(ranking_info).encode()) -@ranking_members_router.get("/get_meta/{rank_type}/", response_model=RankingInfo) -async def member_classification_meta( - rank_type: str, dsrc: SourceDep, app_context: AppContext +@ranking_admin_router.get("/get_meta/{recent_number}/", response_model=list[ClassView]) +async def get_classifications( + recent_number: int, dsrc: SourceDep, app_context: AppContext ) -> RawJSONResponse: - return await get_classification_with_meta( - dsrc, app_context.rank_ctx, rank_type, False - ) - - -@ranking_admin_router.get("/get_meta/{rank_type}/", response_model=RankingInfo) -async def member_classification_admin_meta( - rank_type: str, dsrc: SourceDep, app_context: AppContext -) -> RawJSONResponse: - return await get_classification_with_meta( - dsrc, app_context.rank_ctx, rank_type, True - ) + recent_classes = await most_recent_classes(app_context.rank_ctx, dsrc, recent_number) + + return RawJSONResponse(ClassMetaList.dump_json(recent_classes)) @ranking_members_router.get("/get/{rank_type}/", response_model=list[UserPointsNames]) @@ -205,3 +202,17 @@ async def get_event_users( ) return RawJSONResponse(UserPointsNamesList.dump_json(event_users)) + + +@ranking_admin_router.post("/new/") +async def new_classes( + dsrc: SourceDep, app_context: AppContext, +) -> None: + await context_new_classes(app_context.rank_ctx, dsrc) + + +@ranking_admin_router.post("/modify/") +async def modify_class( + updated_class: ClassUpdate, dsrc: SourceDep, app_context: AppContext, +) -> None: + await context_modify_class(app_context.rank_ctx, dsrc, updated_class) \ No newline at end of file diff --git a/backend/src/apiserver/data/api/classifications.py b/backend/src/apiserver/data/api/classifications.py index e9907a30..ffba5183 100644 --- a/backend/src/apiserver/data/api/classifications.py +++ b/backend/src/apiserver/data/api/classifications.py @@ -1,11 +1,15 @@ from datetime import date, timedelta from typing import Literal +from schema.model.model import CLASS_END_DATE from sqlalchemy import RowMapping from sqlalchemy.ext.asyncio import AsyncConnection from apiserver.lib.model.entities import ( ClassEvent, + ClassMeta, + ClassMetaList, + ClassUpdate, Classification, ClassView, EventDate, @@ -46,7 +50,9 @@ lit_model, select_some_join_where, select_some_where, + update_by_unique, update_column_by_unique, + upsert_by_unique, ) from store.error import DataError, NoDataError, DbError, DbErrors @@ -75,8 +81,8 @@ async def insert_classification( async def most_recent_class_of_type( - conn: AsyncConnection, class_type: Literal["training", "points"] -) -> ClassView: + conn: AsyncConnection, class_type: Literal["training", "points"], amount: int = 1 +) -> list[ClassMeta]: if class_type == "training": query_class_type = "training" elif class_type == "points": @@ -90,11 +96,11 @@ async def most_recent_class_of_type( largest_class_list = await get_largest_where( conn, CLASSIFICATION_TABLE, - {CLASS_ID, CLASS_LAST_UPDATED, CLASS_START_DATE, CLASS_HIDDEN_DATE}, + {CLASS_ID, CLASS_LAST_UPDATED, CLASS_START_DATE, CLASS_HIDDEN_DATE, CLASS_END_DATE}, CLASS_TYPE, query_class_type, CLASS_START_DATE, - 1, + amount, ) if len(largest_class_list) == 0: raise NoDataError( @@ -102,7 +108,7 @@ async def most_recent_class_of_type( "no_most_recent_training_class", ) - return ClassView.model_validate(largest_class_list[0]) + return ClassMetaList.validate_python(largest_class_list) async def all_points_in_class( @@ -260,3 +266,9 @@ async def class_update_last_updated( return await update_column_by_unique( conn, CLASSIFICATION_TABLE, CLASS_LAST_UPDATED, date, CLASS_ID, class_id ) + +async def update_classification( + conn: AsyncConnection, class_view: ClassUpdate +) -> None: + + await update_by_unique(conn, CLASSIFICATION_TABLE, lit_model(class_view), "classification_id", class_view.classification_id) diff --git a/backend/src/apiserver/data/context/app_context.py b/backend/src/apiserver/data/context/app_context.py index d06fdd74..1d96e744 100644 --- a/backend/src/apiserver/data/context/app_context.py +++ b/backend/src/apiserver/data/context/app_context.py @@ -18,6 +18,9 @@ from apiserver.data import Source from apiserver.lib.model.entities import ( ClassEvent, + ClassMeta, + ClassUpdate, + ClassView, NewEvent, RankingInfo, UserData, @@ -101,6 +104,24 @@ async def context_get_event_users( cls, dsrc: Source, event_id: str ) -> list[UserPointsNames]: raise ContextNotImpl() + + @classmethod + async def most_recent_classes( + cls, dsrc: Source, amount: int = 10 + ) -> list[ClassMeta]: + raise ContextNotImpl() + + @classmethod + async def context_new_classes( + cls, dsrc: Source + ) -> None: + raise ContextNotImpl() + + @classmethod + async def context_modify_class( + cls, dsrc: Source, class_update: ClassUpdate + ) -> None: + raise ContextNotImpl() class AuthorizeAppContext(Context): diff --git a/backend/src/apiserver/data/context/ranking.py b/backend/src/apiserver/data/context/ranking.py index f07c2b3f..ed074a7e 100644 --- a/backend/src/apiserver/data/context/ranking.py +++ b/backend/src/apiserver/data/context/ranking.py @@ -9,6 +9,8 @@ from apiserver.lib.model.entities import ( ClassEvent, + ClassMeta, + ClassUpdate, ClassView, NewEvent, NewTrainingEvent, @@ -25,7 +27,9 @@ class_update_last_updated, events_in_class, get_event_user_points, + insert_classification, most_recent_class_of_type, + update_classification, ) from apiserver.data.context import RankingContext from apiserver.data.source import get_conn @@ -49,7 +53,7 @@ async def add_new_event(dsrc: Source, new_event: NewEvent) -> None: date. Use the 'publish' function to force them to be equal.""" async with get_conn(dsrc) as conn: try: - classification = await most_recent_class_of_type(conn, new_event.class_type) + classification = (await most_recent_class_of_type(conn, new_event.class_type))[0] except DataError as e: if e.key != "incorrect_class_type": raise e @@ -135,17 +139,17 @@ async def context_most_recent_class_id_of_type( dsrc: Source, rank_type: Literal["points", "training"] ) -> int: async with get_conn(dsrc) as conn: - class_id = (await most_recent_class_of_type(conn, rank_type)).classification_id + class_id = (await most_recent_class_of_type(conn, rank_type))[0].classification_id return class_id @ctx_reg.register(RankingContext) async def context_most_recent_class_points( - dsrc: Source, rank_type: Literal["points", "training"], is_admin: bool + dsrc: Source, rank_type: Literal["points", "training"], is_admin: bool, ) -> RankingInfo: async with get_conn(dsrc) as conn: - class_view = await most_recent_class_of_type(conn, rank_type) + class_view = (await most_recent_class_of_type(conn, rank_type))[0] user_points = await all_points_in_class( conn, class_view.classification_id, is_admin ) @@ -161,8 +165,8 @@ async def context_most_recent_class_points( @ctx_reg.register(RankingContext) async def sync_publish_ranking(dsrc: Source, publish: bool) -> None: async with get_conn(dsrc) as conn: - training_class = await most_recent_class_of_type(conn, "training") - points_class = await most_recent_class_of_type(conn, "points") + training_class = (await most_recent_class_of_type(conn, "training"))[0] + points_class = (await most_recent_class_of_type(conn, "points"))[0] await update_class_points(conn, training_class.classification_id, publish) await update_class_points(conn, points_class.classification_id, publish) @@ -192,3 +196,38 @@ async def context_get_event_users(dsrc: Source, event_id: str) -> list[UserPoint events_points = await get_event_user_points(conn, event_id) return events_points + + +@ctx_reg.register(RankingContext) +async def most_recent_classes( + dsrc: Source, amount: int = 10 +) -> list[ClassMeta]: + if amount < 2 or amount % 2 != 0: + raise AppError( + ErrorKeys.DATA, + "Request at least 2 classes and make sure it is an even number!", + "most_recent_too_few", + ) + + async with get_conn(dsrc) as conn: + training_classes = (await most_recent_class_of_type(conn, "training", amount // 2)) + points_classes = (await most_recent_class_of_type(conn, "points", amount // 2)) + + return training_classes + points_classes + + +@ctx_reg.register(RankingContext) +async def context_new_classes( + dsrc: Source +) -> None: + async with get_conn(dsrc) as conn: + await insert_classification(conn, "training") + await insert_classification(conn, "points") + + +@ctx_reg.register(RankingContext) +async def context_modify_class( + dsrc: Source, class_update: ClassUpdate +) -> None: + async with get_conn(dsrc) as conn: + await update_classification(conn, class_update) \ No newline at end of file diff --git a/backend/src/apiserver/lib/model/entities.py b/backend/src/apiserver/lib/model/entities.py index d13d5786..f4a7e736 100644 --- a/backend/src/apiserver/lib/model/entities.py +++ b/backend/src/apiserver/lib/model/entities.py @@ -282,3 +282,16 @@ class NewTrainingEvent(BaseModel): class EventDate(BaseModel): date: date + + +class ClassMeta(ClassView): + end_date: date + + +class ClassUpdate(BaseModel): + classification_id: int + start_date: date + hidden_date: date + end_date: date + +ClassMetaList = TypeAdapter(List[ClassMeta]) \ No newline at end of file diff --git a/backend/src/store/db.py b/backend/src/store/db.py index 055730bc..311457f8 100644 --- a/backend/src/store/db.py +++ b/backend/src/store/db.py @@ -227,6 +227,27 @@ async def upsert_by_unique( return row_cnt(res) +async def update_by_unique( + conn: AsyncConnection, + table: LiteralString, + set_dict: LiteralDict, + unique_column: LiteralString, + value: Any, +) -> int: + """Note that while the values are safe from injection, the column names are not.""" + + _, _, row_keys_set = _row_keys_vars_set(set_dict) + + query = text( + f"UPDATE {table} SET {row_keys_set} WHERE {unique_column} = :val;" + ) + val_dict: LiteralDict = {"val": value} + params = set_dict | val_dict + + res = await execute_catch(conn, query, parameters=params) + return row_cnt(res) + + async def update_column_by_unique( conn: AsyncConnection, table: LiteralString, diff --git a/dev.nu b/dev.nu index eb9c9f84..26272273 100755 --- a/dev.nu +++ b/dev.nu @@ -10,7 +10,7 @@ def pull [envnmt: string, env_file: string, profile: string] { } def up [envnmt: string, env_file: string, profile: string] { - pull $envnmt $env_file $profile + # pull $envnmt $env_file $profile docker compose -f $"($deploy_dir)/use/($envnmt)/docker-compose.yml" --env-file $"($deploy_dir)/use/($envnmt)/($env_file).env" --profile $profile up -d } @@ -49,4 +49,4 @@ def "main backend" [] { # important for the command to be exposed to the outside # Useful development commands for starting and stopping databases -def main [] {} \ No newline at end of file +def main [] {} From 6f13757d633e6069ff50f53ba5aee9cbc73bfbb0 Mon Sep 17 00:00:00 2001 From: tiptenbrink <75669206+tiptenbrink@users.noreply.github.com> Date: Thu, 4 Jan 2024 03:48:20 +0100 Subject: [PATCH 3/4] feat: also remove class --- backend/actions/local_actions.py | 8 +-- backend/src/apiserver/app/ops/startup.py | 8 ++- backend/src/apiserver/app/routers/ranking.py | 31 +++++++++-- .../src/apiserver/data/api/classifications.py | 36 +++++++++--- .../src/apiserver/data/context/app_context.py | 15 ++--- backend/src/apiserver/data/context/ranking.py | 55 +++++++++++-------- backend/src/apiserver/lib/model/entities.py | 4 +- backend/src/store/db.py | 4 +- 8 files changed, 104 insertions(+), 57 deletions(-) diff --git a/backend/actions/local_actions.py b/backend/actions/local_actions.py index 710e4e9d..1b34f38f 100644 --- a/backend/actions/local_actions.py +++ b/backend/actions/local_actions.py @@ -166,12 +166,12 @@ async def test_add_classification(local_dsrc): @pytest.mark.asyncio async def test_update_points(local_dsrc): async with get_conn(local_dsrc) as conn: - training_class = await data.classifications.most_recent_class_of_type( + training_class = (await data.classifications.most_recent_class_of_type( conn, "training" - ) - points_class = await data.classifications.most_recent_class_of_type( + ))[0] + points_class = (await data.classifications.most_recent_class_of_type( conn, "points" - ) + ))[0] await update_class_points(conn, training_class.classification_id) await update_class_points(conn, points_class.classification_id) diff --git a/backend/src/apiserver/app/ops/startup.py b/backend/src/apiserver/app/ops/startup.py index 791baf96..f32be385 100644 --- a/backend/src/apiserver/app/ops/startup.py +++ b/backend/src/apiserver/app/ops/startup.py @@ -1,4 +1,5 @@ from apiserver.app.error import AppError, ErrorKeys +from apiserver.data.special import update_class_points from loguru import logger from asyncio import sleep from datetime import date @@ -199,8 +200,11 @@ async def initial_population(dsrc: Source, config: Config) -> None: user_id = await data.user.insert_return_user_id(conn, fake_user) assert user_id == "1_fakerecord" - await insert_classification(conn, "training") - await insert_classification(conn, "points") + new_training_id = await insert_classification(conn, "training") + new_points_id = await insert_classification(conn, "points") + + await update_class_points(conn, new_training_id, False) + await update_class_points(conn, new_points_id, False) async def get_keystate(dsrc: Source) -> KeyState: diff --git a/backend/src/apiserver/app/routers/ranking.py b/backend/src/apiserver/app/routers/ranking.py index 7ae49252..d8b93870 100644 --- a/backend/src/apiserver/app/routers/ranking.py +++ b/backend/src/apiserver/app/routers/ranking.py @@ -9,7 +9,10 @@ mod_user_events_in_class, ) from apiserver.app.response import RawJSONResponse -from apiserver.data.api.classifications import get_event_user_points +from apiserver.data.api.classifications import ( + get_event_user_points, + remove_classification, +) from apiserver.data import Source from apiserver.data.context.app_context import RankingContext, conn_wrap from apiserver.data.context.ranking import ( @@ -108,8 +111,10 @@ async def get_classification_with_meta( async def get_classifications( recent_number: int, dsrc: SourceDep, app_context: AppContext ) -> RawJSONResponse: - recent_classes = await most_recent_classes(app_context.rank_ctx, dsrc, recent_number) - + recent_classes = await most_recent_classes( + app_context.rank_ctx, dsrc, recent_number + ) + return RawJSONResponse(ClassMetaList.dump_json(recent_classes)) @@ -206,13 +211,27 @@ async def get_event_users( @ranking_admin_router.post("/new/") async def new_classes( - dsrc: SourceDep, app_context: AppContext, + dsrc: SourceDep, + app_context: AppContext, ) -> None: await context_new_classes(app_context.rank_ctx, dsrc) @ranking_admin_router.post("/modify/") async def modify_class( - updated_class: ClassUpdate, dsrc: SourceDep, app_context: AppContext, + updated_class: ClassUpdate, + dsrc: SourceDep, + app_context: AppContext, ) -> None: - await context_modify_class(app_context.rank_ctx, dsrc, updated_class) \ No newline at end of file + await context_modify_class(app_context.rank_ctx, dsrc, updated_class) + + +@ranking_admin_router.post("/remove/{class_id}/") +async def remove_class( + class_id: int, + dsrc: SourceDep, + app_context: AppContext, +) -> None: + await ctxlize_wrap(remove_classification, conn_wrap)( + app_context.rank_ctx, dsrc, class_id + ) diff --git a/backend/src/apiserver/data/api/classifications.py b/backend/src/apiserver/data/api/classifications.py index ffba5183..120a6da9 100644 --- a/backend/src/apiserver/data/api/classifications.py +++ b/backend/src/apiserver/data/api/classifications.py @@ -11,7 +11,6 @@ ClassMetaList, ClassUpdate, Classification, - ClassView, EventDate, UserPoints, UserPointsNames, @@ -44,15 +43,16 @@ ) from store.db import ( LiteralDict, + delete_by_column, get_largest_where, insert, insert_many, + insert_return_col, lit_model, select_some_join_where, select_some_where, update_by_unique, update_column_by_unique, - upsert_by_unique, ) from store.error import DataError, NoDataError, DbError, DbErrors @@ -67,7 +67,7 @@ def parse_user_points(user_points: list[RowMapping]) -> list[UserPointsNames]: async def insert_classification( conn: AsyncConnection, class_type: str, start_date: date | None = None -) -> None: +) -> int: if start_date is None: start_date = date.today() new_classification = Classification( @@ -77,7 +77,10 @@ async def insert_classification( end_date=start_date + timedelta(days=30 * 5), hidden_date=start_date + timedelta(days=30 * 4), ) - await insert(conn, CLASSIFICATION_TABLE, lit_model(new_classification)) + return_id: int = await insert_return_col( + conn, CLASSIFICATION_TABLE, lit_model(new_classification), CLASS_ID + ) + return return_id async def most_recent_class_of_type( @@ -96,7 +99,14 @@ async def most_recent_class_of_type( largest_class_list = await get_largest_where( conn, CLASSIFICATION_TABLE, - {CLASS_ID, CLASS_LAST_UPDATED, CLASS_START_DATE, CLASS_HIDDEN_DATE, CLASS_END_DATE}, + { + CLASS_ID, + CLASS_TYPE, + CLASS_LAST_UPDATED, + CLASS_START_DATE, + CLASS_HIDDEN_DATE, + CLASS_END_DATE, + }, CLASS_TYPE, query_class_type, CLASS_START_DATE, @@ -267,8 +277,16 @@ async def class_update_last_updated( conn, CLASSIFICATION_TABLE, CLASS_LAST_UPDATED, date, CLASS_ID, class_id ) -async def update_classification( - conn: AsyncConnection, class_view: ClassUpdate -) -> None: - await update_by_unique(conn, CLASSIFICATION_TABLE, lit_model(class_view), "classification_id", class_view.classification_id) +async def update_classification(conn: AsyncConnection, class_view: ClassUpdate) -> None: + await update_by_unique( + conn, + CLASSIFICATION_TABLE, + lit_model(class_view), + "classification_id", + class_view.classification_id, + ) + + +async def remove_classification(conn: AsyncConnection, class_id: int) -> None: + await delete_by_column(conn, CLASSIFICATION_TABLE, "classification_id", class_id) diff --git a/backend/src/apiserver/data/context/app_context.py b/backend/src/apiserver/data/context/app_context.py index 1d96e744..de7005a1 100644 --- a/backend/src/apiserver/data/context/app_context.py +++ b/backend/src/apiserver/data/context/app_context.py @@ -20,7 +20,6 @@ ClassEvent, ClassMeta, ClassUpdate, - ClassView, NewEvent, RankingInfo, UserData, @@ -104,23 +103,21 @@ async def context_get_event_users( cls, dsrc: Source, event_id: str ) -> list[UserPointsNames]: raise ContextNotImpl() - + @classmethod async def most_recent_classes( cls, dsrc: Source, amount: int = 10 ) -> list[ClassMeta]: raise ContextNotImpl() - + @classmethod - async def context_new_classes( - cls, dsrc: Source - ) -> None: + async def context_new_classes(cls, dsrc: Source) -> None: raise ContextNotImpl() - + @classmethod async def context_modify_class( - cls, dsrc: Source, class_update: ClassUpdate - ) -> None: + cls, dsrc: Source, class_update: ClassUpdate + ) -> None: raise ContextNotImpl() diff --git a/backend/src/apiserver/data/context/ranking.py b/backend/src/apiserver/data/context/ranking.py index ed074a7e..52f30a33 100644 --- a/backend/src/apiserver/data/context/ranking.py +++ b/backend/src/apiserver/data/context/ranking.py @@ -53,7 +53,9 @@ async def add_new_event(dsrc: Source, new_event: NewEvent) -> None: date. Use the 'publish' function to force them to be equal.""" async with get_conn(dsrc) as conn: try: - classification = (await most_recent_class_of_type(conn, new_event.class_type))[0] + classification = ( + await most_recent_class_of_type(conn, new_event.class_type) + )[0] except DataError as e: if e.key != "incorrect_class_type": raise e @@ -139,14 +141,18 @@ async def context_most_recent_class_id_of_type( dsrc: Source, rank_type: Literal["points", "training"] ) -> int: async with get_conn(dsrc) as conn: - class_id = (await most_recent_class_of_type(conn, rank_type))[0].classification_id + class_id = (await most_recent_class_of_type(conn, rank_type))[ + 0 + ].classification_id return class_id @ctx_reg.register(RankingContext) async def context_most_recent_class_points( - dsrc: Source, rank_type: Literal["points", "training"], is_admin: bool, + dsrc: Source, + rank_type: Literal["points", "training"], + is_admin: bool, ) -> RankingInfo: async with get_conn(dsrc) as conn: class_view = (await most_recent_class_of_type(conn, rank_type))[0] @@ -198,36 +204,39 @@ async def context_get_event_users(dsrc: Source, event_id: str) -> list[UserPoint return events_points +MIN_AMOUNT = 2 + + @ctx_reg.register(RankingContext) -async def most_recent_classes( - dsrc: Source, amount: int = 10 -) -> list[ClassMeta]: - if amount < 2 or amount % 2 != 0: +async def most_recent_classes(dsrc: Source, amount: int = 10) -> list[ClassMeta]: + if amount < MIN_AMOUNT or amount % 2 != 0: raise AppError( - ErrorKeys.DATA, - "Request at least 2 classes and make sure it is an even number!", - "most_recent_too_few", - ) - + ErrorKeys.DATA, + "Request at least 2 classes and make sure it is an even number!", + "most_recent_too_few", + ) + async with get_conn(dsrc) as conn: - training_classes = (await most_recent_class_of_type(conn, "training", amount // 2)) - points_classes = (await most_recent_class_of_type(conn, "points", amount // 2)) + training_classes = await most_recent_class_of_type( + conn, "training", amount // 2 + ) + points_classes = await most_recent_class_of_type(conn, "points", amount // 2) return training_classes + points_classes @ctx_reg.register(RankingContext) -async def context_new_classes( - dsrc: Source -) -> None: +async def context_new_classes(dsrc: Source) -> None: async with get_conn(dsrc) as conn: - await insert_classification(conn, "training") - await insert_classification(conn, "points") + new_training_id = await insert_classification(conn, "training") + new_points_id = await insert_classification(conn, "points") + + await update_class_points(conn, new_training_id, False) + await update_class_points(conn, new_points_id, False) @ctx_reg.register(RankingContext) -async def context_modify_class( - dsrc: Source, class_update: ClassUpdate -) -> None: +async def context_modify_class(dsrc: Source, class_update: ClassUpdate) -> None: async with get_conn(dsrc) as conn: - await update_classification(conn, class_update) \ No newline at end of file + await update_classification(conn, class_update) + await update_class_points(conn, class_update.classification_id, False) diff --git a/backend/src/apiserver/lib/model/entities.py b/backend/src/apiserver/lib/model/entities.py index f4a7e736..1f821428 100644 --- a/backend/src/apiserver/lib/model/entities.py +++ b/backend/src/apiserver/lib/model/entities.py @@ -285,6 +285,7 @@ class EventDate(BaseModel): class ClassMeta(ClassView): + type: Literal["points", "training"] end_date: date @@ -294,4 +295,5 @@ class ClassUpdate(BaseModel): hidden_date: date end_date: date -ClassMetaList = TypeAdapter(List[ClassMeta]) \ No newline at end of file + +ClassMetaList = TypeAdapter(List[ClassMeta]) diff --git a/backend/src/store/db.py b/backend/src/store/db.py index 311457f8..b4ad53d8 100644 --- a/backend/src/store/db.py +++ b/backend/src/store/db.py @@ -238,9 +238,7 @@ async def update_by_unique( _, _, row_keys_set = _row_keys_vars_set(set_dict) - query = text( - f"UPDATE {table} SET {row_keys_set} WHERE {unique_column} = :val;" - ) + query = text(f"UPDATE {table} SET {row_keys_set} WHERE {unique_column} = :val;") val_dict: LiteralDict = {"val": value} params = set_dict | val_dict From 20e84e4bc7b50a2905f936bd28dca029e34abcd7 Mon Sep 17 00:00:00 2001 From: tiptenbrink <75669206+tiptenbrink@users.noreply.github.com> Date: Thu, 4 Jan 2024 05:17:35 +0100 Subject: [PATCH 4/4] feat: info for members classification --- backend/src/apiserver/app/routers/ranking.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/src/apiserver/app/routers/ranking.py b/backend/src/apiserver/app/routers/ranking.py index d8b93870..c965e8e3 100644 --- a/backend/src/apiserver/app/routers/ranking.py +++ b/backend/src/apiserver/app/routers/ranking.py @@ -91,7 +91,7 @@ async def get_classification( return RawJSONResponse(UserPointsNamesList.dump_json(user_points)) -async def get_classification_with_meta( +async def get_classification_with_info( dsrc: Source, ctx: RankingContext, rank_type: str, admin: bool = False ) -> RawJSONResponse: if not is_rank_type(rank_type): @@ -107,6 +107,15 @@ async def get_classification_with_meta( return RawJSONResponse(RankingInfo.model_dump_json(ranking_info).encode()) +@ranking_members_router.get("/get_with_info/{rank_type}/", response_model=RankingInfo) +async def member_classification_with_info( + rank_type: str, dsrc: SourceDep, app_context: AppContext +) -> RawJSONResponse: + return await get_classification_with_info( + dsrc, app_context.rank_ctx, rank_type, False + ) + + @ranking_admin_router.get("/get_meta/{recent_number}/", response_model=list[ClassView]) async def get_classifications( recent_number: int, dsrc: SourceDep, app_context: AppContext