From 911e5efdabba91cf6461510a056173fbd848756f Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Sun, 28 Jun 2026 14:56:45 -0230 Subject: [PATCH 01/15] #824 Maybe working once per hour currency update --- src/backend_api/app/api/routes/currency.py | 59 ++++++++++-- src/backend_api/app/crud/__init__.py | 8 +- .../app/crud/extensions/crud_currency.py | 94 +++++++++++++++++++ .../app/crud/extensions/crud_modifier.py | 4 +- .../external_data_retrieval/main.py | 34 ++++++- 5 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 src/backend_api/app/crud/extensions/crud_currency.py diff --git a/src/backend_api/app/api/routes/currency.py b/src/backend_api/app/api/routes/currency.py index 89a009310..3ca3497e4 100644 --- a/src/backend_api/app/api/routes/currency.py +++ b/src/backend_api/app/api/routes/currency.py @@ -1,7 +1,6 @@ from typing import Annotated from fastapi import APIRouter, Depends, Query, Request, Response -from sqlalchemy import text from sqlalchemy.orm import Session import app.core.schemas as schemas @@ -100,15 +99,61 @@ async def get_latest_currency_id( db: Session = Depends(get_db), ): """ - Get the latest currencyId + Get the latest currencyId, returns 1 if table is empty Can only be used safely on an empty table or directly after an insertion. """ - result = db.execute(text("""SELECT MAX("currencyId") FROM currency""")).fetchone() - if result: - return int(result[0]) - else: - return 1 + return await CRUD_currency.get_latest_currency_id(db) + + +@router.get( + "/latest_hour/", + response_model=int, + tags=["latest_hour"], + dependencies=[ + Depends(get_current_active_user), + ], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_latest_hour( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + db: Session = Depends(get_db), +): + """ + Return -1 if database is empty + """ + return await CRUD_currency.get_latest_hour(db) + + +@router.get( + "/latest_currencies/", + response_model=list[schemas.Currency], + tags=["latest_currencies"], + dependencies=[ + Depends(get_current_active_user), + ], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_latest_currencies( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + db: Session = Depends(get_db), +): + """ + Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + """ + return await CRUD_currency.get_latest_currencies(db) @router.post( diff --git a/src/backend_api/app/crud/__init__.py b/src/backend_api/app/crud/__init__.py index 62977e8ea..ed0100cef 100644 --- a/src/backend_api/app/crud/__init__.py +++ b/src/backend_api/app/crud/__init__.py @@ -23,16 +23,12 @@ UnidentifiedItemUpdate, ) from app.crud.extensions.crud_modifier import CRUDModifier +from app.crud.extensions.crud_currency import CRUDCurrency from app.crud.user import CRUDUser from .base import CRUDBase -CRUD_currency = CRUDBase[ - model_Currency, - Currency, - CurrencyCreate, - CurrencyUpdate, -]( +CRUD_currency = CRUDCurrency( model=model_Currency, schema=Currency, create_schema=CurrencyCreate, diff --git a/src/backend_api/app/crud/extensions/crud_currency.py b/src/backend_api/app/crud/extensions/crud_currency.py new file mode 100644 index 000000000..577a601fe --- /dev/null +++ b/src/backend_api/app/crud/extensions/crud_currency.py @@ -0,0 +1,94 @@ +from pydantic import TypeAdapter +from sqlalchemy import select +from sqlalchemy.orm import Session +from sqlalchemy.sql.expression import func + +from app.core.models.models import Currency as model_Currency +from app.core.schemas.currency import ( + Currency, + CurrencyCreate, + CurrencyUpdate, +) +from app.crud.base import CRUDBase + + +class CRUDCurrency( + CRUDBase[ + model_Currency, + Currency, + CurrencyCreate, + CurrencyUpdate, + ] +): + async def get_latest_currency_id(self, db: Session) -> int: + stmt = select( + func.max(model_Currency.currencyId).label("latestCurrencyId") + ).limit(1) + + db_latest_currency_id = db.execute(stmt).mappings().first() + if db_latest_currency_id is not None: + latest_currency_id = db_latest_currency_id["latestCurrencyId"] + else: + latest_currency_id = 1 + + validate = TypeAdapter(int).validate_python + + return validate(latest_currency_id) + + async def get_latest_hour(self, db: Session) -> int: + stmt = ( + select(model_Currency.createdHoursSinceLaunch) + .order_by(model_Currency.currencyId.desc()) + .limit(1) + ) + + db_latest_hour = db.execute(stmt).mappings().first() + if db_latest_hour is not None: + latest_hour = db_latest_hour["createdHoursSinceLaunch"] + else: + latest_hour = -1 + + validate = TypeAdapter(int).validate_python + + return validate(latest_hour) + + async def get_latest_currencies(self, db: Session) -> list[Currency]: + latest_hour = await self.get_latest_hour(db) + + # All rows from that hour, plus the next lower currencyId + hour_rows = ( + select( + model_Currency.currencyId, + model_Currency.createdHoursSinceLaunch, + func.lead(model_Currency.currencyId) + .over(order_by=model_Currency.currencyId.desc()) + .label("next_id"), + ).where(model_Currency.createdHoursSinceLaunch == latest_hour) + ).cte("hour_rows") + + # The first place where the IDs stop being consecutive + boundary = ( + select(hour_rows.c.next_id) + .where(hour_rows.c.currencyId - hour_rows.c.next_id > 1) + .order_by(hour_rows.c.currencyId.desc()) + .limit(1) + .scalar_subquery() + ) + # If no gap exists, include all rows for that hour + min_id = func.coalesce( + boundary, + select(func.min(hour_rows.c.currencyId)).scalar_subquery(), + ) + stmt = ( + select(model_Currency.__table__) + .where( + model_Currency.createdHoursSinceLaunch == latest_hour, + model_Currency.currencyId >= min_id, + ) + .order_by(model_Currency.currencyId.desc()) + ) + latest_currencies = db.execute(stmt).mappings().all() + + validate = TypeAdapter(list[Currency]).validate_python + + return validate(latest_currencies) diff --git a/src/backend_api/app/crud/extensions/crud_modifier.py b/src/backend_api/app/crud/extensions/crud_modifier.py index 864e6bf61..6f732811f 100644 --- a/src/backend_api/app/crud/extensions/crud_modifier.py +++ b/src/backend_api/app/crud/extensions/crud_modifier.py @@ -22,7 +22,9 @@ class CRUDModifier( ModifierUpdate, ] ): - async def get_grouped_modifier_by_effect(self, db: Session): + async def get_grouped_modifier_by_effect( + self, db: Session + ) -> GroupedModifierByEffect: stmt = select( model_Modifier.modifierId, model_Modifier.effect, diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py index 5629a7a03..d8cd6f103 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py @@ -33,6 +33,7 @@ from data_retrieval_app.pom_api_authentication import ( get_superuser_token_headers, ) +from data_retrieval_app.utils import find_hours_since_launch class ContinuousDataRetrieval: @@ -43,6 +44,7 @@ class ContinuousDataRetrieval: backend_base_url = settings.BACKEND_BASE_URL modifier_url = f"{backend_base_url}/modifier/" item_base_type_url = f"{backend_base_url}/itemBaseType/" + currency_url = f"{backend_base_url}/currency/" pom_auth_headers = get_superuser_token_headers(backend_base_url) def __init__( @@ -147,8 +149,31 @@ def _categorize_new_items(self, df: pd.DataFrame) -> dict[str, pd.DataFrame]: return split_dfs def _get_new_currency_data(self) -> pd.DataFrame: - currency_df = self.currency_api_handler.make_request() - currency_df = self.currency_transformer.transform_into_tables(currency_df) + response = requests.get( + self.currency_url + "latest_hour/", headers=self.pom_auth_headers + ) + if response.status_code != 200: + logger.error( + f"Recieved response code {response} when retrieving latest currency hour" + ) + response.raise_for_status() + latest_hour = response.json() + current_hour = find_hours_since_launch() + if latest_hour == current_hour: + response = requests.get( + self.currency_url + "latest_currencies/", headers=self.pom_auth_headers + ) + if response.status_code != 200: + logger.error( + f"Recieved response code {response} when retrieving latest currency hour" + ) + response.raise_for_status() + + currency_df = pd.DataFrame(response.json()) + else: + currency_df = self.currency_api_handler.make_request() + currency_df = self.currency_transformer.transform_into_tables(currency_df) + return currency_df def _initialize_data_stream_threads( @@ -163,11 +188,10 @@ def _follow_data_dump_stream(self): logger.info("Retrieving modifiers from db.") modifier_dfs = self._get_modifiers() item_base_types = self._get_item_base_types() + currency_df = self._get_new_currency_data() get_df = self.poe_api_handler.dump_stream() - for i, df in enumerate(get_df): + for df in get_df: split_dfs = self._categorize_new_items(df) - if i % 50 == 0: - currency_df = self._get_new_currency_data() for data_transformer_type in self.data_transformers: self.data_transformers[data_transformer_type].transform_into_tables( df=split_dfs[data_transformer_type], From 84f5e9c627090b19e8723d28e4d405c3449eba50 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Tue, 30 Jun 2026 16:15:00 -0230 Subject: [PATCH 02/15] Working new league table and migration, no backend data retrieval --- .../965e766db0a0_added_league_table.py | 198 ++++++++++++++++++ src/backend_api/app/core/models/models.py | 27 ++- 2 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py diff --git a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py new file mode 100644 index 000000000..35274e037 --- /dev/null +++ b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py @@ -0,0 +1,198 @@ +"""Added league table + +Revision ID: 965e766db0a0 +Revises: 17daa1c96438 +Create Date: 2026-06-30 15:49:45.173827 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "965e766db0a0" +down_revision: Union[str, None] = "17daa1c96438" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "league", + sa.Column("leagueId", sa.Integer(), sa.Identity(always=False), nullable=False), + sa.Column("name", sa.Text(), nullable=False), + sa.PrimaryKeyConstraint("leagueId"), + ) + op.execute(""" + INSERT INTO league (name) + SELECT DISTINCT league + FROM item + WHERE league IS NOT NULL + """) + + # ---- Item table ---- + op.add_column("item", sa.Column("leagueId", sa.SmallInteger())) + op.execute(""" + UPDATE item + SET "leagueId" = l."leagueId" + FROM league l + WHERE item.league = l.name + """) + op.alter_column("item", "leagueId", nullable=False) + op.create_foreign_key( + "fk_item_league", + "item", + "league", + ["leagueId"], + ["leagueId"], + ondelete="RESTRICT", + onupdate="CASCADE", + ) + op.drop_column("item", "league") + + # ---- Unidentified item table ---- + op.add_column("unidentified_item", sa.Column("leagueId", sa.SmallInteger())) + op.execute(""" + UPDATE unidentified_item + SET "leagueId" = l."leagueId" + FROM league l + WHERE league = l.name + """) + op.alter_column("unidentified_item", "leagueId", nullable=False) + op.create_foreign_key( + "fk_unidentified_item_league", + "unidentified_item", + "league", + ["leagueId"], + ["leagueId"], + ondelete="RESTRICT", + onupdate="CASCADE", + ) + op.drop_column("unidentified_item", "league") + + # ---- Currency table ---- + op.add_column("currency", sa.Column("leagueId", sa.SmallInteger())) + # Deletes all currency ids that are not used + op.execute(""" + DELETE FROM currency c + WHERE NOT EXISTS ( + SELECT 1 + FROM item i + WHERE i."currencyId" = c."currencyId" + ) + """) + op.execute(""" + UPDATE currency c + SET "leagueId" = i.leagueId + FROM ( + SELECT item."currencyId", MIN(item."leagueId") AS leagueId + FROM item + GROUP BY item."currencyId" + ) i + WHERE c."currencyId" = i."currencyId" + """) + op.alter_column("currency", "leagueId", nullable=False) + op.create_foreign_key( + "fk_currency_league", + "currency", + "league", + ["leagueId"], + ["leagueId"], + ondelete="RESTRICT", + onupdate="CASCADE", + ) + + # ---- Indexes ---- + # Manual SQL to drop query instead of `op.drop_index` is needed due to hypertables + op.execute(""" + DROP INDEX IF EXISTS ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_league + """) + op.create_index( + "ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId", + "item", + [ + "name", + "itemBaseTypeId", + "createdHoursSinceLaunch", + "leagueId", + ], + unique=False, + ) + + op.execute(""" + DROP INDEX IF EXISTS ix_unid_item_name_itemBaseTypeId_createdHoursSinceLaunch_league + """) + op.create_index( + "ix_unid_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId", + "unidentified_item", + [ + "name", + "itemBaseTypeId", + "createdHoursSinceLaunch", + "leagueId", + ], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + # ---- Item table ---- + op.add_column( + "item", + sa.Column("league", sa.TEXT(), autoincrement=False), + ) + op.execute(""" + UPDATE item i + SET league = l.name + FROM league l + WHERE i."leagueId" = l."leagueId" + """) + op.alter_column("item", "league", nullable=False) + op.drop_constraint("fk_item_league", "item", type_="foreignkey") + op.execute(""" + DROP INDEX IF EXISTS ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId + """) + op.create_index( + "ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_league", + "item", + ["name", "itemBaseTypeId", "createdHoursSinceLaunch", "league"], + unique=False, + ) + op.drop_column("item", "leagueId") + + # ---- Unidentified item table ---- + op.add_column( + "unidentified_item", + sa.Column("league", sa.TEXT(), autoincrement=False), + ) + op.execute(""" + UPDATE unidentified_item ui + SET league = l.name + FROM league l + WHERE ui."leagueId" = l."leagueId" + """) + op.alter_column("unidentified_item", "league", nullable=False) + op.drop_constraint( + "fk_unidentified_item_league", "unidentified_item", type_="foreignkey" + ) + op.execute(""" + DROP INDEX IF EXISTS ix_unid_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId + """) + op.create_index( + "ix_unid_item_name_itemBaseTypeId_createdHoursSinceLaunch_league", + "unidentified_item", + ["name", "itemBaseTypeId", "createdHoursSinceLaunch", "league"], + unique=False, + ) + op.drop_column("unidentified_item", "leagueId") + + # ---- Currency table ---- + op.drop_column("currency", "leagueId") + + op.drop_table("league") + # ### end Alembic commands ### diff --git a/src/backend_api/app/core/models/models.py b/src/backend_api/app/core/models/models.py index 0f5951bf4..bae9aad4a 100644 --- a/src/backend_api/app/core/models/models.py +++ b/src/backend_api/app/core/models/models.py @@ -24,6 +24,15 @@ from app.core.models.database import Base +class League(Base): + __tablename__ = "league" + + leagueId: Mapped[SmallInteger] = mapped_column( + Integer, Identity(), primary_key=True + ) + name: Mapped[str] = mapped_column(Text, nullable=False) + + class Currency(Base): __tablename__ = "currency" @@ -31,6 +40,11 @@ class Currency(Base): createdHoursSinceLaunch: Mapped[int] = mapped_column(SmallInteger, nullable=False) valueInChaos: Mapped[float] = mapped_column(Float(4), nullable=False) tradeName: Mapped[str] = mapped_column(Text, nullable=False) + leagueId: Mapped[SmallInteger] = mapped_column( + SmallInteger, + ForeignKey("league.leagueId", ondelete="RESTRICT", onupdate="CASCADE"), + nullable=False, + ) class ItemBaseType(Base): @@ -61,8 +75,9 @@ class _ItemBase: nullable=False, ) createdHoursSinceLaunch: Mapped[int] = mapped_column(SmallInteger, nullable=False) - league: Mapped[str] = mapped_column( - Text, + leagueId: Mapped[SmallInteger] = mapped_column( + SmallInteger, + ForeignKey("league.leagueId", ondelete="RESTRICT", onupdate="CASCADE"), nullable=False, ) itemId: Mapped[int] = mapped_column( @@ -104,11 +119,11 @@ class Item(_ItemBase, Base): __table_args__ = ( Index( - "ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_league", + "ix_item_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId", "name", "itemBaseTypeId", "createdHoursSinceLaunch", - "league", + "leagueId", ), ) @@ -127,11 +142,11 @@ class UnidentifiedItem(_ItemBase, Base): __table_args__ = ( Index( - "ix_unid_item_name_itemBaseTypeId_createdHoursSinceLaunch_league", + "ix_unid_name_itemBaseTypeId_createdHoursSinceLaunch_leagueId", "name", "itemBaseTypeId", "createdHoursSinceLaunch", - "league", + "leagueId", ), CheckConstraint( """ From acb3513a9a1632bcd92cde871a8564f90debecdd Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Thu, 2 Jul 2026 21:50:07 -0230 Subject: [PATCH 03/15] Added league api endpoint and valid to/from --- .../965e766db0a0_added_league_table.py | 6 +- src/backend_api/app/api/api.py | 5 + src/backend_api/app/api/routes/__init__.py | 1 + src/backend_api/app/api/routes/league.py | 104 ++++++++++++++++++ src/backend_api/app/core/models/models.py | 4 +- src/backend_api/app/core/schemas/__init__.py | 46 ++++---- src/backend_api/app/core/schemas/league.py | 37 +++++++ src/backend_api/app/crud/__init__.py | 12 +- .../app/crud/extensions/crud_league.py | 29 +++++ .../external_data_retrieval/main.py | 13 +++ 10 files changed, 233 insertions(+), 24 deletions(-) create mode 100644 src/backend_api/app/api/routes/league.py create mode 100644 src/backend_api/app/core/schemas/league.py create mode 100644 src/backend_api/app/crud/extensions/crud_league.py diff --git a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py index 35274e037..d88ef396d 100644 --- a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py +++ b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py @@ -22,8 +22,12 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( "league", - sa.Column("leagueId", sa.Integer(), sa.Identity(always=False), nullable=False), + sa.Column( + "leagueId", sa.SmallInteger(), sa.Identity(always=False), nullable=False + ), sa.Column("name", sa.Text(), nullable=False), + sa.Column("validFrom", sa.DateTime(timezone=True), nullable=False), + sa.Column("validTo", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("leagueId"), ) op.execute(""" diff --git a/src/backend_api/app/api/api.py b/src/backend_api/app/api/api.py index 778627286..67c6b5496 100644 --- a/src/backend_api/app/api/api.py +++ b/src/backend_api/app/api/api.py @@ -9,6 +9,8 @@ item_modifier, item_modifier_prefix, item_prefix, + league, + league_prefix, login, login_prefix, modifier, @@ -42,6 +44,9 @@ api_router.include_router( item.router, prefix=f"/{item_prefix}", tags=[f"{item_prefix}s"] ) +api_router.include_router( + league.router, prefix=f"/{league_prefix}", tags=[f"{league_prefix}s"] +) api_router.include_router( unidentified_item.router, prefix=f"/{unidentified_item_prefix}", diff --git a/src/backend_api/app/api/routes/__init__.py b/src/backend_api/app/api/routes/__init__.py index 1ddd59632..8cc7f8c08 100644 --- a/src/backend_api/app/api/routes/__init__.py +++ b/src/backend_api/app/api/routes/__init__.py @@ -9,3 +9,4 @@ from app.api.routes.turnstile import turnstile_prefix from app.api.routes.user import user_prefix from app.api.routes.test import test_prefix +from app.api.routes.league import league_prefix diff --git a/src/backend_api/app/api/routes/league.py b/src/backend_api/app/api/routes/league.py new file mode 100644 index 000000000..95fef7b88 --- /dev/null +++ b/src/backend_api/app/api/routes/league.py @@ -0,0 +1,104 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, Query, Request, Response +from sqlalchemy.orm import Session + +import app.core.schemas as schemas +from app.api.deps import ( + get_current_active_superuser, + get_current_active_user, + get_db, +) +from app.api.params import FilterParams +from app.core.rate_limit.rate_limit_config import rate_limit_settings +from app.core.rate_limit.rate_limiters import ( + apply_user_rate_limits, +) +from app.crud import CRUD_league + +router = APIRouter() + + +league_prefix = "league" + + +@router.get( + "/{leagueId}", + response_model=schemas.League, + dependencies=[ + Depends(get_current_active_user), + ], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_league( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + leagueId: int, + db: Session = Depends(get_db), +): + """ + Get league by key and value for "leagueId". + + Always returns one league. + """ + + league_map = {"leagueId": leagueId} + league = await CRUD_league.get(db=db, filter=league_map) + + return league + + +@router.get( + "/", + response_model=schemas.League | list[schemas.League], + dependencies=[ + Depends(get_current_active_superuser), + ], +) +async def get_all_leagues( + filter_params: Annotated[FilterParams, Query()], + db: Session = Depends(get_db), +): + """ + Get all leagues. + + Returns a list of all leagues. + """ + + all_leagues = await CRUD_league.get(db=db, filter_params=filter_params) + + return all_leagues + + +@router.get( + f"/active_{league_prefix}/", + response_model=list[str], + dependencies=[ + Depends(get_current_active_user), + ], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_active_leagues( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + db: Session = Depends(get_db), +): + """ + Get leagues that are still valid/active + + Always returns a list, but it may be empty. + """ + + leagues = await CRUD_league.get_active_leagues(db=db) + + return leagues diff --git a/src/backend_api/app/core/models/models.py b/src/backend_api/app/core/models/models.py index bae9aad4a..8f9d259be 100644 --- a/src/backend_api/app/core/models/models.py +++ b/src/backend_api/app/core/models/models.py @@ -28,9 +28,11 @@ class League(Base): __tablename__ = "league" leagueId: Mapped[SmallInteger] = mapped_column( - Integer, Identity(), primary_key=True + SmallInteger, Identity(), primary_key=True ) name: Mapped[str] = mapped_column(Text, nullable=False) + validFrom: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) + validTo: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True) class Currency(Base): diff --git a/src/backend_api/app/core/schemas/__init__.py b/src/backend_api/app/core/schemas/__init__.py index bc5805628..4d69acff8 100644 --- a/src/backend_api/app/core/schemas/__init__.py +++ b/src/backend_api/app/core/schemas/__init__.py @@ -1,43 +1,49 @@ "All schemas are imported here and then exported to the main file" -from .currency import Currency, CurrencyInDB, CurrencyCreate, CurrencyUpdate -from .modifier import ( - Modifier, - GroupedModifierByEffect, - ModifierInDB, - ModifierCreate, - ModifierUpdate, -) +from .currency import Currency, CurrencyCreate, CurrencyInDB, CurrencyUpdate +from .item import Item, ItemCreate, ItemInDB, ItemUpdate from .item_base_type import ( ItemBaseType, - ItemBaseTypeInDB, ItemBaseTypeCreate, + ItemBaseTypeInDB, ItemBaseTypeUpdate, ) from .item_modifier import ( ItemModifier, - ItemModifierInDB, ItemModifierCreate, + ItemModifierInDB, ItemModifierUpdate, ) -from .item import Item, ItemInDB, ItemCreate, ItemUpdate +from .league import ( + League, + LeagueCreate, + LeagueInDB, + LeagueUpdate, +) +from .message import Message +from .modifier import ( + GroupedModifierByEffect, + Modifier, + ModifierCreate, + ModifierInDB, + ModifierUpdate, +) +from .token import NewPassword, Token, TokenPayload +from .turnstile import TurnstileQuery, TurnstileResponse from .unidentified_item import ( UnidentifiedItem, - UnidentifiedItemInDB, UnidentifiedItemCreate, + UnidentifiedItemInDB, UnidentifiedItemUpdate, ) -from .turnstile import TurnstileQuery, TurnstileResponse from .user import ( + UpdatePassword, + User, UserCreate, - UserUpdate, + UserInDB, + UserPublic, UserRegisterPreEmailConfirmation, UsersPublic, - UserPublic, + UserUpdate, UserUpdateMe, - UpdatePassword, - User, - UserInDB, ) -from .token import Token, TokenPayload, NewPassword -from .message import Message diff --git a/src/backend_api/app/core/schemas/league.py b/src/backend_api/app/core/schemas/league.py new file mode 100644 index 000000000..d7b89a2d3 --- /dev/null +++ b/src/backend_api/app/core/schemas/league.py @@ -0,0 +1,37 @@ +import datetime as _dt + +import pydantic as _pydantic + + +# Shared League props +class _BaseLeague(_pydantic.BaseModel): + model_config = _pydantic.ConfigDict(from_attributes=True) + + name: str + validTo: _dt.datetime + validFrom: _dt.datetime + + +# Properties to receive on League creation +class LeagueCreate(_BaseLeague): + pass + + +# Properties to receive on update +class LeagueUpdate(_BaseLeague): + leagueId: int + + +# Properties shared by models stored in DB +class LeagueInDBBase(_BaseLeague): + leagueId: int + + +# Properties to return to client +class League(LeagueInDBBase): + pass + + +# Properties stored in DB +class LeagueInDB(LeagueInDBBase): + pass diff --git a/src/backend_api/app/crud/__init__.py b/src/backend_api/app/crud/__init__.py index ed0100cef..4058962ac 100644 --- a/src/backend_api/app/crud/__init__.py +++ b/src/backend_api/app/crud/__init__.py @@ -2,9 +2,10 @@ from app.core.models.models import Item as model_Item from app.core.models.models import ItemBaseType as model_ItemBaseType from app.core.models.models import ItemModifier as model_ItemModifier +from app.core.models.models import League as model_League from app.core.models.models import Modifier as model_Modifier from app.core.models.models import UnidentifiedItem as model_UnidentifiedItem -from app.core.schemas.currency import Currency, CurrencyCreate, CurrencyUpdate +from app.core.schemas.currency import Currency, CurrencyCreate from app.core.schemas.item import Item, ItemCreate, ItemUpdate from app.core.schemas.item_base_type import ( ItemBaseType, @@ -16,18 +17,25 @@ ItemModifierCreate, ItemModifierUpdate, ) +from app.core.schemas.league import ( + League, + LeagueCreate, +) from app.core.schemas.modifier import Modifier, ModifierCreate from app.core.schemas.unidentified_item import ( UnidentifiedItem, UnidentifiedItemCreate, UnidentifiedItemUpdate, ) -from app.crud.extensions.crud_modifier import CRUDModifier from app.crud.extensions.crud_currency import CRUDCurrency +from app.crud.extensions.crud_league import CRUDLeague +from app.crud.extensions.crud_modifier import CRUDModifier from app.crud.user import CRUDUser from .base import CRUDBase +CRUD_league = CRUDLeague(model=model_League, schema=League, create_schema=LeagueCreate) + CRUD_currency = CRUDCurrency( model=model_Currency, schema=Currency, diff --git a/src/backend_api/app/crud/extensions/crud_league.py b/src/backend_api/app/crud/extensions/crud_league.py new file mode 100644 index 000000000..ae6ef946e --- /dev/null +++ b/src/backend_api/app/crud/extensions/crud_league.py @@ -0,0 +1,29 @@ +from datetime import datetime + +from pydantic import TypeAdapter +from sqlalchemy import select +from sqlalchemy.orm import Session + +from app.core.models.models import League as model_League +from app.core.schemas.league import ( + League, + LeagueCreate, + LeagueUpdate, +) +from app.crud.base import CRUDBase + + +class CRUDLeague(CRUDBase[model_League, League, LeagueCreate, LeagueUpdate]): + async def get_active_leagues(self, db: Session) -> list[str]: + now = datetime.now() + + stmt = select(model_League.name).where( + model_League.validFrom <= now, + model_League.validTo.is_(None) | (model_League.validTo >= now), + ) + + leagues = db.execute(stmt).mappings().all() + + validate = TypeAdapter(list[str]).validate_python + + return validate(leagues) diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py index d8cd6f103..7b539f399 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py @@ -43,6 +43,7 @@ class ContinuousDataRetrieval: backend_base_url = settings.BACKEND_BASE_URL modifier_url = f"{backend_base_url}/modifier/" + league_url = f"{backend_base_url}/league/" item_base_type_url = f"{backend_base_url}/itemBaseType/" currency_url = f"{backend_base_url}/currency/" pom_auth_headers = get_superuser_token_headers(backend_base_url) @@ -99,6 +100,17 @@ def _get_modifiers(self) -> dict[str, pd.DataFrame]: ] return modifier_dfs + def _get_leagues(self) -> list[str]: + response = requests.get(self.league_url, headers=self.pom_auth_headers) + + if response.status_code != 200: + logger.error(f"Recieved response code {response} when retrieving leagues") + response.raise_for_status() + + leagues = response.json() + + return leagues + def _get_item_base_types(self) -> dict[str, int]: response = requests.get(self.item_base_type_url, headers=self.pom_auth_headers) item_base_type_mapped = {} @@ -187,6 +199,7 @@ def _follow_data_dump_stream(self): try: logger.info("Retrieving modifiers from db.") modifier_dfs = self._get_modifiers() + # leagues = self._get_leagues() item_base_types = self._get_item_base_types() currency_df = self._get_new_currency_data() get_df = self.poe_api_handler.dump_stream() From 983326e581498dc6c68cea3782fc0258181d0431 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Fri, 3 Jul 2026 14:34:36 -0230 Subject: [PATCH 04/15] #824 Added league data deposit and changed active league api route --- src/backend_api/app/api/routes/league.py | 47 +++++++++- src/backend_api/app/api/routes/modifier.py | 4 +- src/backend_api/app/core/schemas/league.py | 7 +- .../app/crud/extensions/crud_league.py | 8 +- .../item_base_type_data_depositor.py | 2 +- .../data_deposit/league/__init__.py | 0 .../league/league_data/leagues.csv | 9 ++ .../league/league_data_depositor.py | 93 +++++++++++++++++++ .../data_retrieval_app/data_deposit/main.py | 4 + 9 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/league/__init__.py create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv create mode 100644 src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py diff --git a/src/backend_api/app/api/routes/league.py b/src/backend_api/app/api/routes/league.py index 95fef7b88..9977d336b 100644 --- a/src/backend_api/app/api/routes/league.py +++ b/src/backend_api/app/api/routes/league.py @@ -77,7 +77,7 @@ async def get_all_leagues( @router.get( f"/active_{league_prefix}/", - response_model=list[str], + response_model=list[schemas.League], dependencies=[ Depends(get_current_active_user), ], @@ -102,3 +102,48 @@ async def get_active_leagues( leagues = await CRUD_league.get_active_leagues(db=db) return leagues + + +@router.post( + "/", + response_model=schemas.LeagueCreate | list[schemas.LeagueCreate] | None, + dependencies=[Depends(get_current_active_superuser)], +) +async def create_league( + league: schemas.LeagueCreate | list[schemas.LeagueCreate], + return_nothing: bool | None = None, + db: Session = Depends(get_db), +): + """ + Create one or a list of new leagues. + + Returns the created league or list of leagues. + """ + + return await CRUD_league.create(db=db, obj_in=league, return_nothing=return_nothing) + + +@router.put( + "/{leagueI}", + response_model=schemas.League, + dependencies=[Depends(get_current_active_superuser)], +) +async def update_modifier( + leagueId: int, + league_update: schemas.LeagueUpdate, + db: Session = Depends(get_db), +): + """ + Update a league by key and value for "leagueId" + + Returns the updated league. + """ + + modifier_map = {"leagueId": leagueId} + + modifier = await CRUD_league.get( + db=db, + filter=modifier_map, + ) + + return await CRUD_league.update(db_obj=modifier, obj_in=league_update, db=db) diff --git a/src/backend_api/app/api/routes/modifier.py b/src/backend_api/app/api/routes/modifier.py index 7b32506af..cd83a6e25 100644 --- a/src/backend_api/app/api/routes/modifier.py +++ b/src/backend_api/app/api/routes/modifier.py @@ -149,9 +149,7 @@ async def update_modifier( db: Session = Depends(get_db), ): """ - Update a modifier by key and value for "modifierId" - - Dominant key is "modifierId". + Update a modifier by key and value for "modifierId" and "position" Returns the updated modifier. """ diff --git a/src/backend_api/app/core/schemas/league.py b/src/backend_api/app/core/schemas/league.py index d7b89a2d3..ebbe2da6b 100644 --- a/src/backend_api/app/core/schemas/league.py +++ b/src/backend_api/app/core/schemas/league.py @@ -8,8 +8,8 @@ class _BaseLeague(_pydantic.BaseModel): model_config = _pydantic.ConfigDict(from_attributes=True) name: str - validTo: _dt.datetime validFrom: _dt.datetime + validTo: _dt.datetime | None = None # Properties to receive on League creation @@ -18,8 +18,11 @@ class LeagueCreate(_BaseLeague): # Properties to receive on update -class LeagueUpdate(_BaseLeague): +class LeagueUpdate(_pydantic.BaseModel): leagueId: int + name: str + validFrom: _dt.datetime + validTo: _dt.datetime | None = None # Properties shared by models stored in DB diff --git a/src/backend_api/app/crud/extensions/crud_league.py b/src/backend_api/app/crud/extensions/crud_league.py index ae6ef946e..01af97287 100644 --- a/src/backend_api/app/crud/extensions/crud_league.py +++ b/src/backend_api/app/crud/extensions/crud_league.py @@ -14,16 +14,16 @@ class CRUDLeague(CRUDBase[model_League, League, LeagueCreate, LeagueUpdate]): - async def get_active_leagues(self, db: Session) -> list[str]: + async def get_active_leagues(self, db: Session) -> list[League]: now = datetime.now() - stmt = select(model_League.name).where( + stmt = select(model_League).where( model_League.validFrom <= now, model_League.validTo.is_(None) | (model_League.validTo >= now), ) - leagues = db.execute(stmt).mappings().all() + leagues = db.execute(stmt).scalars().all() - validate = TypeAdapter(list[str]).validate_python + validate = TypeAdapter(list[League]).validate_python return validate(leagues) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py index bad7d88bb..b19033019 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py @@ -93,7 +93,7 @@ def _update_duplicates(self, duplicate_df: pd.DataFrame): response.raise_for_status() except Exception as e: logger.error( - f"The following error occurred while making request during _update_duplicates modifiers: {e}" + f"The following error occurred while making request during _update_duplicates item base types: {e}" ) raise e diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/__init__.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv new file mode 100644 index 000000000..7515787ff --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv @@ -0,0 +1,9 @@ +# Source: https://www.poewiki.net/wiki/League#Challenge_leagues +name,validTo,validFrom +Mirage,2026-03-06T19:00:00Z,2026-07-20T21:00:00Z +Phrecia2.0,2026-01-29T19:00:00Z,2026-02-19T21:00:00Z +Keepers,2025-10-31T19:00:00Z,2026-03-02T21:00:00Z +Mercenaries,2025-06-13T20:00:00Z,2025-10-27T21:00:00Z +Phrecia,2025-02-20T20:00:00Z,2025-04-23T21:00:00Z +Necro Settlers,2024-11-07T20:00:00Z,2025-02-20T19:00:00Z +Settlers,2024-07-26T20:00:00Z,2025-06-09T22:00:00Z \ No newline at end of file diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py new file mode 100644 index 000000000..bb0555aac --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py @@ -0,0 +1,93 @@ +from io import StringIO + +import pandas as pd +import requests + +from data_retrieval_app.data_deposit.data_depositor_base import DataDepositorBase +from data_retrieval_app.logs.logger import data_deposit_logger as logger + + +class LeagueDataDepositor(DataDepositorBase): + def __init__(self) -> None: + super().__init__(data_type="league") + + self.update_url = str(self.data_url) + self.data_url += "?on_duplicate_pkey_do_nothing=true" + self.current_league_df = self._get_current_leagues() + + def _get_current_leagues(self) -> pd.DataFrame: + logger.info("Retrieving previously deposited data.") + + response = requests.get(self.data_url, headers=self.pom_auth_headers) + + df = pd.DataFrame() + # Check if the request was successful + if response.status_code == 200: + # Load the JSON data into a pandas DataFrame + json_io = StringIO(response.content.decode("utf-8")) + df = pd.read_json(json_io, dtype=str) + + if df.empty: + logger.info("Found no previously deposited data.") + return pd.DataFrame(columns=["name", "leagueId", "validTo", "validFrom"]) + else: + logger.info("Successfully retrieved previously deposited data.") + return df + + def _update_duplicates(self, duplicate_df: pd.DataFrame): + for _, duplicate_league_row in duplicate_df.iterrows(): + league_id = int(duplicate_league_row["leagueId"]) + data = {"leagueId": league_id, "name": duplicate_league_row["name"]} + do_update = False + + old_valid_to = duplicate_league_row["validTo_y"] + new_valid_to = duplicate_league_row["validTo"] + data["validTo"] = new_valid_to + if old_valid_to != new_valid_to: + do_update = True + + old_valid_from = duplicate_league_row["validFrom_y"] + new_valid_from = duplicate_league_row["validFrom"] + data["validFrom"] = new_valid_from + if old_valid_from != new_valid_from: + do_update = True + + if do_update: + headers = { + "accept": "application/json", + "Content-Type": "application/json", + } + headers.update(self.pom_auth_headers) + try: + response = requests.put( + self.update_url + str(league_id), + json=data, + headers=headers, + # add HTTP Basic Auth + ) + response.raise_for_status() + except Exception as e: + logger.error( + f"The following error occurred while making request during _update_duplicates league: {e}" + ) + raise e + + def _process_data(self, df: pd.DataFrame) -> pd.DataFrame: + merged_df = pd.merge( + df, + self.current_league_df, + how="left", + on="name", + suffixes=("", "_y"), + ) + non_duplicate_mask = merged_df["leagueId"].isna() + non_duplicate_df = merged_df[non_duplicate_mask] + non_duplicate_df = non_duplicate_df.drop( + columns=[ + column for column in non_duplicate_df.columns if column.endswith("_y") + ] + ) + + self._update_duplicates(merged_df[~non_duplicate_mask]) + + return non_duplicate_df diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py index d5e5490ac..09ff9e81e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py @@ -4,6 +4,9 @@ from data_retrieval_app.data_deposit.item_base_type.item_base_type_data_depositor import ( ItemBaseTypeDataDepositor, ) +from data_retrieval_app.data_deposit.league.league_data_depositor import ( + LeagueDataDepositor, +) from data_retrieval_app.data_deposit.modifier.modifier_data_depositor import ( ModifierDataDepositor, ) @@ -17,6 +20,7 @@ def main(): data_depositors: dict[Literal["modifier", "itemBaseType"], DataDepositorBase] = { "modifer": ModifierDataDepositor(), "itemBaseType": ItemBaseTypeDataDepositor(), + "league": LeagueDataDepositor(), } for key, data_depositor in data_depositors.items(): logger.info(f"Depositing {key} data.") From c22eea9bf935190edee793867727a7ea398fcb91 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Fri, 3 Jul 2026 14:58:53 -0230 Subject: [PATCH 05/15] #824 Small bug fix for update league --- src/backend_api/app/api/routes/league.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend_api/app/api/routes/league.py b/src/backend_api/app/api/routes/league.py index 9977d336b..c24b3758d 100644 --- a/src/backend_api/app/api/routes/league.py +++ b/src/backend_api/app/api/routes/league.py @@ -124,7 +124,7 @@ async def create_league( @router.put( - "/{leagueI}", + "/{leagueId}", response_model=schemas.League, dependencies=[Depends(get_current_active_superuser)], ) From 8bb755a8132f7acac41451cd1727cdeec60d881d Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Wed, 8 Jul 2026 16:06:50 -0230 Subject: [PATCH 06/15] Fixed unidentified items and cleaned up thread exiting --- .../965e766db0a0_added_league_table.py | 61 +++++++ src/backend_api/app/api/routes/currency.py | 26 +++ .../app/api/routes/unidentified_item.py | 40 +++++ src/backend_api/app/core/schemas/currency.py | 7 + src/backend_api/app/core/schemas/item.py | 2 +- .../app/core/schemas/unidentified_item.py | 4 +- src/backend_api/app/crud/__init__.py | 9 +- .../app/crud/extensions/crud_currency.py | 31 +++- .../crud/extensions/crud_unidentifiedItem.py | 61 +++++++ .../item_base_type_data_depositor.py | 13 +- .../league/league_data/leagues.csv | 2 +- .../league/league_data_depositor.py | 16 +- .../modifier/modifier_data_depositor.py | 14 +- .../data_retrieval/currency_api_handler.py | 34 ++-- .../data_retrieval/poe_api_handler.py | 55 +++--- .../external_data_retrieval/detectors/base.py | 14 +- .../external_data_retrieval/main.py | 157 +++++++++--------- .../transform_currency_api_data.py | 49 +++--- .../transform_poe_api_data.py | 155 +++++++++++++---- .../external_data_retrieval/utils.py | 2 +- .../utils/data_deposit_test_data_creator.py | 4 +- .../utils/scrap_and_mock_poe_api_docs_objs.py | 6 +- .../data_retrieval_app/utils.py | 20 +++ 23 files changed, 551 insertions(+), 231 deletions(-) create mode 100644 src/backend_api/app/crud/extensions/crud_unidentifiedItem.py diff --git a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py index d88ef396d..6b0bbd5b0 100644 --- a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py +++ b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py @@ -11,12 +11,67 @@ from alembic import op import sqlalchemy as sa +from app.alembic.replaceable_objects.main import ReplaceableTrigger + # revision identifiers, used by Alembic. revision: str = "965e766db0a0" down_revision: Union[str, None] = "17daa1c96438" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None +unidentified_aggregation_trigger_old = ReplaceableTrigger( + "aggregate_unidentified", + "unidentified_item", + """ + RETURNS TRIGGER AS ${name}$ + DECLARE + current_hour INT; + divine_id INT; + divine_value FLOAT; + BEGIN + IF EXISTS (SELECT 1 FROM unidentified_item AS unid WHERE unid."createdHoursSinceLaunch" = NEW."createdHoursSinceLaunch") THEN + RETURN NEW; + END IF; + + current_hour := (SELECT MAX(unid."createdHoursSinceLaunch") FROM unidentified_item AS unid); + divine_id := (SELECT MAX(cur."currencyId") FROM currency AS cur WHERE cur."tradeName"='divine'); + divine_value := (SELECT cur."valueInChaos" FROM currency AS cur WHERE cur."currencyId"=divine_id); + + + WITH aggregates AS ( + SELECT name, unid."itemBaseTypeId", unid."createdHoursSinceLaunch", league, ilvl, stddev(unid."currencyAmount" * cur."valueInChaos") AS std, AVG(unid."currencyAmount" * cur."valueInChaos") AS calc_avg, COUNT(unid."itemId") AS calc_count + FROM unidentified_item AS unid + NATURAL JOIN currency as cur + WHERE unid."createdHoursSinceLaunch" = current_hour + AND NOT aggregated + GROUP BY name, unid."itemBaseTypeId", unid."createdHoursSinceLaunch", league, ilvl + ), affected_item_ids AS ( + SELECT unid."itemId" FROM unidentified_item AS unid WHERE NOT aggregated AND unid."createdHoursSinceLaunch"=current_hour + ) + + INSERT INTO unidentified_item (name, "itemBaseTypeId", "createdHoursSinceLaunch", league, "currencyId", ilvl, "currencyAmount", "nItems", identified, rarity, aggregated) + SELECT name, unid."itemBaseTypeId", unid."createdHoursSinceLaunch", league, divine_id, ilvl, AVG(unid."currencyAmount" * cur."valueInChaos") / divine_value, calc_count, identified, rarity, TRUE + FROM unidentified_item AS unid + NATURAL JOIN aggregates + NATURAL JOIN currency AS cur + WHERE unid."currencyAmount" * cur."valueInChaos" <= calc_avg + 1.97 * std + AND unid."currencyAmount" * cur."valueInChaos" >= calc_avg - 1.97 * std + GROUP BY name, unid."itemBaseTypeId", unid."createdHoursSinceLaunch", league, ilvl, identified, rarity, calc_count; + + DELETE FROM unidentified_item as unid + WHERE NOT aggregated AND unid."createdHoursSinceLaunch"=current_hour; + + RETURN NEW; + END; + ${name}$ LANGUAGE plpgsql; + """, + """ + BEFORE INSERT ON {table} + FOR EACH ROW + EXECUTE FUNCTION {name}(); + """, +) + def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### @@ -140,6 +195,9 @@ def upgrade() -> None: ], unique=False, ) + + # ---- triggers ---- + op.drop_trigger(unidentified_aggregation_trigger_old) # ### end Alembic commands ### @@ -199,4 +257,7 @@ def downgrade() -> None: op.drop_column("currency", "leagueId") op.drop_table("league") + + # ---- triggers ---- + op.create_trigger(unidentified_aggregation_trigger_old) # ### end Alembic commands ### diff --git a/src/backend_api/app/api/routes/currency.py b/src/backend_api/app/api/routes/currency.py index c4c432cbc..4bdd861f4 100644 --- a/src/backend_api/app/api/routes/currency.py +++ b/src/backend_api/app/api/routes/currency.py @@ -18,6 +18,7 @@ from app.core.rate_limit.rate_limiters import ( apply_user_rate_limits, ) +from app.core.schemas.currency import CurrencyQuery from app.crud import CRUD_currency router = APIRouter() @@ -156,6 +157,31 @@ async def get_latest_currencies( return await CRUD_currency.get_latest_currencies(db) +@router.post( + "/from_query/", + response_model=list[schemas.Currency], + dependencies=[ + Depends(get_current_active_user), + ], +) +@apply_user_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, +) +async def get_currency_from_query( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 + query_list: list[CurrencyQuery], + db: Session = Depends(get_db), +): + """ + Returns a list of currencies that match any of the queries + """ + return await CRUD_currency.get_currency_from_query(db, query_list) + + @router.post( "/", response_model=schemas.CurrencyCreate | list[schemas.CurrencyCreate] | None, diff --git a/src/backend_api/app/api/routes/unidentified_item.py b/src/backend_api/app/api/routes/unidentified_item.py index 84ffbcb69..b355eea8a 100644 --- a/src/backend_api/app/api/routes/unidentified_item.py +++ b/src/backend_api/app/api/routes/unidentified_item.py @@ -94,3 +94,43 @@ async def create_item( return await CRUD_unidentifiedItem.create( db=db, obj_in=item, return_nothing=return_nothing ) + + +@router.get( + "/non_aggregated/", + response_model=list[schemas.UnidentifiedItem], + dependencies=[Depends(get_current_active_superuser)], +) +async def get_non_aggregated( + db: Session = Depends(get_db), +): + """ + Get the non aggregated unidentified items for the last 10 recorded hours + (not necessarily the last 10 hours) + """ + + all_items = await CRUD_unidentifiedItem.get_non_aggregated(db=db) + + return all_items + + +@router.post( + "/add_aggregated/", + response_model=list[schemas.UnidentifiedItem], + dependencies=[Depends(get_current_active_superuser)], +) +async def add_aggregated( + aggregated_objs: list[schemas.UnidentifiedItemCreate], + db: Session = Depends(get_db), +): + """ + Deletes the non aggregated unidentified items for the last 10 recorded hours + (not necessarily the last 10 hours) + And inserts the new items + """ + + deleted_non_aggregated = await CRUD_unidentifiedItem.add_aggregated( + db=db, aggregated_objs=aggregated_objs + ) + + return deleted_non_aggregated diff --git a/src/backend_api/app/core/schemas/currency.py b/src/backend_api/app/core/schemas/currency.py index 95c6b7441..0f09d102a 100644 --- a/src/backend_api/app/core/schemas/currency.py +++ b/src/backend_api/app/core/schemas/currency.py @@ -7,6 +7,7 @@ class _BaseCurrency(_pydantic.BaseModel): tradeName: str valueInChaos: float + leagueId: int # Properties to receive on currency creation @@ -33,3 +34,9 @@ class Currency(CurrencyInDBBase): # Properties stored in DB class CurrencyInDB(CurrencyInDBBase): pass + + +class CurrencyQuery(_pydantic.BaseModel): + createdHoursSinceLaunch: int | None = None + tradeName: str | None = None + leagueId: int | None = None diff --git a/src/backend_api/app/core/schemas/item.py b/src/backend_api/app/core/schemas/item.py index 6b0179be5..c42c04a3f 100644 --- a/src/backend_api/app/core/schemas/item.py +++ b/src/backend_api/app/core/schemas/item.py @@ -15,7 +15,7 @@ class _BaseItem(_pydantic.BaseModel): model_config = _pydantic.ConfigDict(from_attributes=True) name: str | None = None - league: str + leagueId: int itemBaseTypeId: int ilvl: int rarity: str diff --git a/src/backend_api/app/core/schemas/unidentified_item.py b/src/backend_api/app/core/schemas/unidentified_item.py index 342db99c6..da90f8473 100644 --- a/src/backend_api/app/core/schemas/unidentified_item.py +++ b/src/backend_api/app/core/schemas/unidentified_item.py @@ -6,13 +6,15 @@ class _BaseUnidentifiedItem(_pydantic.BaseModel): model_config = _pydantic.ConfigDict(from_attributes=True) name: str | None = None - league: str + leagueId: int itemBaseTypeId: int ilvl: int rarity: str identified: bool = False currencyAmount: float | None = None currencyId: int | None = None + nItems: int = 1 + aggregated: bool = False # Properties to receive on item creation diff --git a/src/backend_api/app/crud/__init__.py b/src/backend_api/app/crud/__init__.py index 4058962ac..e0b656aa6 100644 --- a/src/backend_api/app/crud/__init__.py +++ b/src/backend_api/app/crud/__init__.py @@ -25,11 +25,11 @@ from app.core.schemas.unidentified_item import ( UnidentifiedItem, UnidentifiedItemCreate, - UnidentifiedItemUpdate, ) from app.crud.extensions.crud_currency import CRUDCurrency from app.crud.extensions.crud_league import CRUDLeague from app.crud.extensions.crud_modifier import CRUDModifier +from app.crud.extensions.crud_unidentifiedItem import CRUDUnidentifiedItem from app.crud.user import CRUDUser from .base import CRUDBase @@ -66,12 +66,7 @@ ItemUpdate, ](model=model_Item, schema=Item, create_schema=ItemCreate) -CRUD_unidentifiedItem = CRUDBase[ - model_UnidentifiedItem, - UnidentifiedItem, - UnidentifiedItemCreate, - UnidentifiedItemUpdate, -]( +CRUD_unidentifiedItem = CRUDUnidentifiedItem( model=model_UnidentifiedItem, schema=UnidentifiedItem, create_schema=UnidentifiedItemCreate, diff --git a/src/backend_api/app/crud/extensions/crud_currency.py b/src/backend_api/app/crud/extensions/crud_currency.py index 577a601fe..68a686a6f 100644 --- a/src/backend_api/app/crud/extensions/crud_currency.py +++ b/src/backend_api/app/crud/extensions/crud_currency.py @@ -1,12 +1,13 @@ from pydantic import TypeAdapter from sqlalchemy import select from sqlalchemy.orm import Session -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import and_, func, or_ from app.core.models.models import Currency as model_Currency from app.core.schemas.currency import ( Currency, CurrencyCreate, + CurrencyQuery, CurrencyUpdate, ) from app.crud.base import CRUDBase @@ -92,3 +93,31 @@ async def get_latest_currencies(self, db: Session) -> list[Currency]: validate = TypeAdapter(list[Currency]).validate_python return validate(latest_currencies) + + async def get_currency_from_query( + self, db: Session, query_list: list[CurrencyQuery] + ) -> list[Currency]: + filters = [] + for query in query_list: + sub_filter = [] + if query.createdHoursSinceLaunch is not None: + sub_filter.append( + model_Currency.createdHoursSinceLaunch + == query.createdHoursSinceLaunch + ) + + if query.tradeName is not None: + sub_filter.append(model_Currency.tradeName == query.tradeName) + + if query.leagueId is not None: + sub_filter.append(model_Currency.leagueId == query.leagueId) + + if sub_filter: + filters.append(and_(*sub_filter)) + stmt = select(model_Currency).where(or_(*filters)) + + currencies = db.execute(stmt).scalars().all() + + validate = TypeAdapter(list[Currency]).validate_python + + return validate(currencies) diff --git a/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py new file mode 100644 index 000000000..7b1d510d5 --- /dev/null +++ b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py @@ -0,0 +1,61 @@ +from pydantic import TypeAdapter +from sqlalchemy import delete, select +from sqlalchemy.orm import Session + +from app.core.models.models import UnidentifiedItem as model_UnidentifiedItem +from app.core.schemas.unidentified_item import ( + UnidentifiedItem, + UnidentifiedItemCreate, + UnidentifiedItemUpdate, +) +from app.crud.base import CRUDBase + + +class CRUDUnidentifiedItem( + CRUDBase[ + model_UnidentifiedItem, + UnidentifiedItem, + UnidentifiedItemCreate, + UnidentifiedItemUpdate, + ] +): + async def get_non_aggregated(self, db: Session) -> list[UnidentifiedItem]: + """ + Returns the non aggregated unidentified items for the last 10 recorded hours + (not necessarily the last 10 hours) + """ + last_10_recorded_hours = ( + select(model_UnidentifiedItem.createdHoursSinceLaunch.distinct()) + .where(model_UnidentifiedItem.aggregated.isnot(True)) + .order_by(model_UnidentifiedItem.createdHoursSinceLaunch.desc()) + .limit(10) + ) + stmt = select(model_UnidentifiedItem).where( + model_UnidentifiedItem.createdHoursSinceLaunch.in_(last_10_recorded_hours), + model_UnidentifiedItem.aggregated.isnot(True), + ) + + non_aggregated = db.execute(stmt).scalars().all() + + validate = TypeAdapter(list[UnidentifiedItem]).validate_python + + return validate(non_aggregated) + + async def add_aggregated( + self, db: Session, aggregated_objs: list[UnidentifiedItemCreate] + ) -> list[UnidentifiedItem]: + last_10_recorded_hours = ( + select(model_UnidentifiedItem.createdHoursSinceLaunch.distinct()) + .where(model_UnidentifiedItem.aggregated.isnot(True)) + .order_by(model_UnidentifiedItem.createdHoursSinceLaunch.desc()) + .limit(10) + ) + stmt = delete(model_UnidentifiedItem).where( + model_UnidentifiedItem.createdHoursSinceLaunch.in_(last_10_recorded_hours), + model_UnidentifiedItem.aggregated.isnot(True), + ) + non_aggregated_objs = db.execute(stmt) + + await self.create(db, obj_in=aggregated_objs, return_nothing=True) + + return non_aggregated_objs diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py index b19033019..8d871faaf 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/item_base_type/item_base_type_data_depositor.py @@ -5,6 +5,7 @@ from data_retrieval_app.data_deposit.data_depositor_base import DataDepositorBase from data_retrieval_app.logs.logger import data_deposit_logger as logger +from data_retrieval_app.utils import get_data_safe class ItemBaseTypeDataDepositor(DataDepositorBase): @@ -18,14 +19,12 @@ def __init__(self) -> None: def _get_current_base_types(self) -> pd.DataFrame: logger.info("Retrieving previously deposited data.") - response = requests.get(self.data_url, headers=self.pom_auth_headers) + response = get_data_safe( + self.data_url, headers=self.pom_auth_headers, logger=logger + ) - df = pd.DataFrame() - # Check if the request was successful - if response.status_code == 200: - # Load the JSON data into a pandas DataFrame - json_io = StringIO(response.content.decode("utf-8")) - df = pd.read_json(json_io, dtype=str) + json_io = StringIO(response.content.decode("utf-8")) + df = pd.read_json(json_io, dtype=str) if df.empty: logger.info("Found no previously deposited data.") diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv index 7515787ff..45526fdb2 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv @@ -1,5 +1,5 @@ # Source: https://www.poewiki.net/wiki/League#Challenge_leagues -name,validTo,validFrom +name,validFrom,validTo Mirage,2026-03-06T19:00:00Z,2026-07-20T21:00:00Z Phrecia2.0,2026-01-29T19:00:00Z,2026-02-19T21:00:00Z Keepers,2025-10-31T19:00:00Z,2026-03-02T21:00:00Z diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py index bb0555aac..d8eef9ff0 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py @@ -5,6 +5,7 @@ from data_retrieval_app.data_deposit.data_depositor_base import DataDepositorBase from data_retrieval_app.logs.logger import data_deposit_logger as logger +from data_retrieval_app.utils import get_data_safe class LeagueDataDepositor(DataDepositorBase): @@ -18,14 +19,12 @@ def __init__(self) -> None: def _get_current_leagues(self) -> pd.DataFrame: logger.info("Retrieving previously deposited data.") - response = requests.get(self.data_url, headers=self.pom_auth_headers) + response = get_data_safe( + self.data_url, headers=self.pom_auth_headers, logger=logger + ) - df = pd.DataFrame() - # Check if the request was successful - if response.status_code == 200: - # Load the JSON data into a pandas DataFrame - json_io = StringIO(response.content.decode("utf-8")) - df = pd.read_json(json_io, dtype=str) + json_io = StringIO(response.content.decode("utf-8")) + df = pd.read_json(json_io, dtype=str) if df.empty: logger.info("Found no previously deposited data.") @@ -73,6 +72,9 @@ def _update_duplicates(self, duplicate_df: pd.DataFrame): raise e def _process_data(self, df: pd.DataFrame) -> pd.DataFrame: + hardcore_df = df.copy() + hardcore_df["name"] = "Hardcore " + hardcore_df["name"] + df = pd.concat((df, hardcore_df), ignore_index=True) merged_df = pd.merge( df, self.current_league_df, diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py index 7d16ee9f2..d9c606af0 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/modifier/modifier_data_depositor.py @@ -13,7 +13,7 @@ do_update_regex, ) from data_retrieval_app.logs.logger import data_deposit_logger as logger -from data_retrieval_app.utils import df_to_JSON +from data_retrieval_app.utils import df_to_JSON, get_data_safe CASCADING_UPDATE = True @@ -40,14 +40,12 @@ def __init__(self) -> None: def _get_current_modifiers(self) -> pd.DataFrame: logger.info("Retrieving previously deposited data.") - response = requests.get(self.data_url, headers=self.pom_auth_headers) + response = get_data_safe( + self.data_url, headers=self.pom_auth_headers, logger=logger + ) - df = pd.DataFrame() - # Check if the request was successful - if response.status_code == 200: - # Load the JSON data into a pandas DataFrame - json_io = StringIO(response.content.decode("utf-8")) - df = pd.read_json(json_io, dtype=str) + json_io = StringIO(response.content.decode("utf-8")) + df = pd.read_json(json_io, dtype=str) if df.empty: logger.info("Found no previously deposited data.") diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/currency_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/currency_api_handler.py index c78fbb70b..a5c1b6e47 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/currency_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/currency_api_handler.py @@ -1,7 +1,9 @@ +from typing import Any + import pandas as pd -import requests from data_retrieval_app.logs.logger import external_data_retrieval_logger as logger +from data_retrieval_app.utils import get_data_safe class CurrencyAPIHandler: @@ -13,25 +15,25 @@ def _json_to_df(self, currencies: list) -> pd.DataFrame: return df - def make_request(self) -> pd.DataFrame: + def make_request(self, leagues: list[dict[str, Any]]) -> pd.DataFrame: """ Makes an initial, synchronous, API call. """ - try: - response = requests.get(self.url) - response.raise_for_status() - except Exception as e: - logger.error( - f"The following error occurred while making request in poe ninja currency handler: {e}" + df = None + for league in leagues: + response = get_data_safe( + self.url.format(league=league["name"].replace(" ", "+")), logger=logger ) - raise e - response_json = response.json() - - # items_df = pd.DataFrame(response_json["items"]) - items_df = pd.json_normalize(response_json["items"]) - currency_df = items_df[items_df["category"] == "currency"] - - return currency_df + response_json = response.json() + + items_df = pd.json_normalize(response_json["items"]) + currency_df = items_df[items_df["category"] == "currency"] + currency_df["leagueId"] = league["leagueId"] + if df is None: + df = currency_df + else: + df = pd.concat((df, currency_df)) + return df def store_data_to_csv(self, path: str) -> None: """ diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py index 68440a14f..48a51de7e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py @@ -3,10 +3,10 @@ import time from collections.abc import Iterator from concurrent.futures import Future, ThreadPoolExecutor +from typing import Any import aiohttp import pandas as pd -import requests from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.external_data_retrieval.detectors.unique_detector import ( @@ -18,12 +18,13 @@ UniqueWeaponDetector, ) from data_retrieval_app.external_data_retrieval.utils import ( - ProgramRunTooLongException, + ProgramFinished, ProgramTooSlowException, WrongLeagueSetException, sync_timing_tracker, ) from data_retrieval_app.logs.logger import data_retrieval_logger as logger +from data_retrieval_app.utils import get_data_safe pd.options.mode.chained_assignment = None # default='warn' @@ -37,6 +38,7 @@ def __init__( self, url: str, auth_token: str, + leagues: list[dict[str, Any]], *, n_wanted_items: int = 100, n_unique_wanted_items: int = 5, @@ -53,11 +55,11 @@ def __init__( logger.debug("Initializing PoEAPIHandler.") if item_detectors is None: item_detectors = [ - UniqueArmourDetector(), - UniqueJewelDetector(), - UniqueJewelleryDetector(), - UniqueWeaponDetector(), - UniqueUnidentifiedDetector(), + UniqueArmourDetector(leagues), + UniqueJewelDetector(leagues), + UniqueJewelleryDetector(leagues), + UniqueWeaponDetector(leagues), + UniqueUnidentifiedDetector(leagues), ] logger.debug("Item detectors set to: " + str(item_detectors)) self.url = url @@ -81,6 +83,7 @@ def __init__( self.skip_program_too_slow = False self._program_too_slow = False + self._program_finished = False self.time_of_launch = time.perf_counter() self.run_program_for_n_seconds = settings.TIME_BETWEEN_RESTART logger.info("PoEAPIHandler successfully initialized.") @@ -172,21 +175,15 @@ def _get_latest_change_id(self) -> str: ): # For testing purposes, set manual next_change_id next_change_id = settings.NEXT_CHANGE_ID return next_change_id - try: - # Can't have authorization header, so we make a new header - headers = { - "User-Agent": f"OAuth pathofmodifiers/0.1.0 (contact: {settings.OATH_ACC_TOKEN_CONTACT_EMAIL}) StrictMode" - } - response = requests.get( - "https://www.pathofexile.com/api/trade/data/change-ids", - headers=headers, - ) - response.raise_for_status() - except Exception as e: - logger.error( - f"The following error occurred while making request _get_latest_change_id: {e}" - ) - raise e + # Can't have authorization header, so we make a new header + headers = { + "User-Agent": f"OAuth pathofmodifiers/0.1.0 (contact: {settings.OATH_ACC_TOKEN_CONTACT_EMAIL}) StrictMode" + } + response = get_data_safe( + "https://www.pathofexile.com/api/trade/data/change-ids", + headers=headers, + logger=logger, + ) response_json = response.json() next_change_id = response_json["psapi"] logger.info(f"Retrieved latest change id: {next_change_id}. Sleeping for 310s") @@ -221,6 +218,8 @@ async def _send_n_recursion_requests( return [] if self._program_too_slow: raise ProgramTooSlowException + if self._program_finished: + raise ProgramFinished waiting_for_next_id_lock.acquire() @@ -347,6 +346,8 @@ async def _follow_stream( stashes = await self._send_n_recursion_requests( 5, session, waiting_for_next_id_lock, mini_batch_size ) + if self._program_finished: + return logger.debug(f"Thread {threading.get_ident()} finished 5 requests") stash_lock.acquire() self.stashes += stashes @@ -449,9 +450,15 @@ def set_program_too_slow(self): """ self._program_too_slow = True + def set_program_finished(self): + """ + Used to forceall threads to crash, letting the program shut down. + """ + self._program_finished = True + def initialize_data_stream_threads( self, executor: ThreadPoolExecutor, listeners: int, has_crashed: bool - ) -> dict[Future, str] | Future: + ) -> dict[Future, str] | Future | list[Future]: """ Creates the communication tools between threads and store them for later use. Gets the latest change id, and initializes the listeners. @@ -527,4 +534,4 @@ def dump_stream(self, track_progress: bool = True) -> Iterator[pd.DataFrame]: current_time = time.perf_counter() time_since_launch = current_time - self.time_of_launch if time_since_launch > self.run_program_for_n_seconds: - raise ProgramRunTooLongException + raise ProgramFinished diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py index 4fd711b75..74f83020d 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py @@ -1,9 +1,9 @@ import inspect import time +from typing import Any import pandas as pd -from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import data_retrieval_logger as logger @@ -15,19 +15,18 @@ class DetectorBase: wanted_items = {} found_items = {} - def __init__(self, enable_pbar: bool = False) -> None: + def __init__( + self, leagues: list[dict[str, Any]], enable_pbar: bool = False + ) -> None: """ `self.n_unique_items_found` needs to be stored inbetween item detector sessions. """ self.n_unique_items_found = 0 + self.leagues = [league["name"] for league in leagues] + self.league_to_id = {league["name"]: league["leagueId"] for league in leagues} self.prev_item_hashes_found = {} - self.leagues = [ - settings.CURRENT_SOFTCORE_LEAGUE, - settings.CURRENT_HARDCORE_LEAGUE, - ] - self.pbar_enabled = enable_pbar def _general_filter(self, df: pd.DataFrame) -> pd.DataFrame: @@ -65,6 +64,7 @@ def _general_filter(self, df: pd.DataFrame) -> pd.DataFrame: return pd.DataFrame(columns=df.columns) df = df.loc[df["league"].isin(self.leagues)] + df["leagueId"] = df["league"].map(self.league_to_id) return df diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py index 7b539f399..02f50d93e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py @@ -1,14 +1,14 @@ from concurrent.futures import ( ALL_COMPLETED, - FIRST_EXCEPTION, + FIRST_COMPLETED, Future, ThreadPoolExecutor, wait, ) from io import StringIO +from typing import Any import pandas as pd -import requests from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.external_data_retrieval.data_retrieval.currency_api_handler import ( @@ -25,7 +25,7 @@ UniquePoEAPIDataTransformer, ) from data_retrieval_app.external_data_retrieval.utils import ( - ProgramRunTooLongException, + ProgramFinished, ProgramTooSlowException, ) from data_retrieval_app.logs.logger import external_data_retrieval_logger as logger @@ -33,7 +33,7 @@ from data_retrieval_app.pom_api_authentication import ( get_superuser_token_headers, ) -from data_retrieval_app.utils import find_hours_since_launch +from data_retrieval_app.utils import find_hours_since_launch, get_data_safe class ContinuousDataRetrieval: @@ -43,7 +43,7 @@ class ContinuousDataRetrieval: backend_base_url = settings.BACKEND_BASE_URL modifier_url = f"{backend_base_url}/modifier/" - league_url = f"{backend_base_url}/league/" + active_league_url = f"{backend_base_url}/league/active_league/" item_base_type_url = f"{backend_base_url}/itemBaseType/" currency_url = f"{backend_base_url}/currency/" pom_auth_headers = get_superuser_token_headers(backend_base_url) @@ -53,30 +53,32 @@ def __init__( items_per_batch: int, data_transformers: dict[str, PoEAPIDataTransformerBase], ): - self.data_transformers = { + self.data_transformers: dict[str, PoEAPIDataTransformerBase] = { key: data_transformer() for key, data_transformer in data_transformers.items() } + self.leagues = self._get_leagues() + self.poe_api_handler = PoEAPIHandler( url=self.stash_tab_url, auth_token=self.auth_token, + leagues=self.leagues, n_wanted_items=items_per_batch, n_unique_wanted_items=10, ) self.currency_api_handler = CurrencyAPIHandler( - url=f"https://api.poe.watch/exchange/ratios?league={self.current_league}&game=poe1" + url="https://api.poe.watch/exchange/ratios?league={league}&game=poe1" ) self.currency_transformer = TransformCurrencyAPIData() def _get_modifiers(self) -> dict[str, pd.DataFrame]: - response = requests.get(self.modifier_url, headers=self.pom_auth_headers) + response = get_data_safe( + self.modifier_url, headers=self.pom_auth_headers, logger=logger + ) # Check if the request was successful modifier_df = pd.DataFrame() - if response.status_code != 200: - logger.error(f"Recieved response code {response} when retrieving modifiers") - response.raise_for_status() # Load the JSON data into a pandas DataFrame json_io = StringIO(response.content.decode("utf-8")) modifier_df = pd.read_json(json_io, dtype=str) @@ -100,28 +102,21 @@ def _get_modifiers(self) -> dict[str, pd.DataFrame]: ] return modifier_dfs - def _get_leagues(self) -> list[str]: - response = requests.get(self.league_url, headers=self.pom_auth_headers) - - if response.status_code != 200: - logger.error(f"Recieved response code {response} when retrieving leagues") - response.raise_for_status() - + def _get_leagues(self) -> list[dict[str, Any]]: + response = get_data_safe( + self.active_league_url, headers=self.pom_auth_headers, logger=logger + ) leagues = response.json() return leagues def _get_item_base_types(self) -> dict[str, int]: - response = requests.get(self.item_base_type_url, headers=self.pom_auth_headers) + response = get_data_safe( + self.item_base_type_url, headers=self.pom_auth_headers, logger=logger + ) item_base_type_mapped = {} item_base_types = [] - if response.status_code != 200: - logger.error( - f"Recieved response code {response} when retrieving item base types" - ) - response.raise_for_status() - item_base_types = response.json() if not isinstance(item_base_types, list): item_base_types = [item_base_types] @@ -160,31 +155,27 @@ def _categorize_new_items(self, df: pd.DataFrame) -> dict[str, pd.DataFrame]: return split_dfs - def _get_new_currency_data(self) -> pd.DataFrame: - response = requests.get( - self.currency_url + "latest_hour/", headers=self.pom_auth_headers + def _get_new_currency_data(self, current_hour: int) -> pd.DataFrame: + response = get_data_safe( + self.currency_url + "latest_hour/", + headers=self.pom_auth_headers, + logger=logger, ) - if response.status_code != 200: - logger.error( - f"Recieved response code {response} when retrieving latest currency hour" - ) - response.raise_for_status() + latest_hour = response.json() - current_hour = find_hours_since_launch() if latest_hour == current_hour: - response = requests.get( - self.currency_url + "latest_currencies/", headers=self.pom_auth_headers + response = get_data_safe( + self.currency_url + "latest_currencies/", + headers=self.pom_auth_headers, + logger=logger, ) - if response.status_code != 200: - logger.error( - f"Recieved response code {response} when retrieving latest currency hour" - ) - response.raise_for_status() currency_df = pd.DataFrame(response.json()) else: - currency_df = self.currency_api_handler.make_request() - currency_df = self.currency_transformer.transform_into_tables(currency_df) + currency_df = self.currency_api_handler.make_request(self.leagues) + currency_df = self.currency_transformer.transform_into_tables( + currency_df, current_hour + ) return currency_df @@ -197,13 +188,15 @@ def _initialize_data_stream_threads( def _follow_data_dump_stream(self): try: + current_hour = find_hours_since_launch() + next_hour = current_hour + 1 logger.info("Retrieving modifiers from db.") modifier_dfs = self._get_modifiers() - # leagues = self._get_leagues() item_base_types = self._get_item_base_types() - currency_df = self._get_new_currency_data() + currency_df = self._get_new_currency_data(current_hour) get_df = self.poe_api_handler.dump_stream() - for df in get_df: + while current_hour < next_hour: + df = next(get_df) split_dfs = self._categorize_new_items(df) for data_transformer_type in self.data_transformers: self.data_transformers[data_transformer_type].transform_into_tables( @@ -211,7 +204,18 @@ def _follow_data_dump_stream(self): modifier_df=modifier_dfs[data_transformer_type], currency_df=currency_df.copy(deep=True), item_base_types=item_base_types, + current_hour=current_hour, ) + current_hour = find_hours_since_launch() + for data_transformer_type in self.data_transformers: + self.data_transformers[data_transformer_type].end_of_hour_cleanup() + + raise ProgramFinished + + except ProgramFinished: + raise + except ProgramTooSlowException: + raise except Exception as e: logger.exception( f"The following exception occured during '_follow_data_dump_stream': {e}" @@ -222,28 +226,28 @@ def retrieve_data(self): logger.info("Program starting up.") logger.info("Initiating data stream.") max_workers = 3 - listeners = max_workers - 1 # minus one because of transformation threads - try: - with ThreadPoolExecutor(max_workers=max_workers) as executor: - futures = self._initialize_data_stream_threads( - executor, listeners=listeners - ) - follow_future = executor.submit(self._follow_data_dump_stream) - futures[follow_future] = "data_processing" - logger.info("Waiting for futures to crash.") - while True: - done_futures, not_done_futures = wait( - futures, return_when=FIRST_EXCEPTION - ) - crashed_future = list(done_futures)[0] - future_job = futures.pop(crashed_future) - logger.info( - f"The future '{future_job}' has crashed. Finding exception..." - ) + listeners = max( + max_workers - 1, 1 + ) # minus one because of transformation threads + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = self._initialize_data_stream_threads( + executor, listeners=listeners + ) + follow_future = executor.submit(self._follow_data_dump_stream) + futures[follow_future] = "data_processing" + logger.info("Waiting for futures to crash.") + finished = False + while True: + if finished: + return + done_futures, _ = wait(futures, return_when=FIRST_COMPLETED) + while done_futures: + future = done_futures.pop() + future_job = futures.pop(future) + if future_job == "data_processing": - crashed_future_exception = crashed_future.exception() try: - raise crashed_future_exception + future.result() except ProgramTooSlowException: logger.info( f"The job '{future_job}' was too slow. Restarting..." @@ -252,39 +256,30 @@ def retrieve_data(self): wait(futures, return_when=ALL_COMPLETED) - raise ProgramTooSlowException - except ProgramRunTooLongException: + finished = True + except ProgramFinished: logger.info( - f"The job '{future_job}' has been running too long. Restarting..." + f"The job '{future_job}' has finished this cycle. Restarting..." ) - self.poe_api_handler.set_program_too_slow() + self.poe_api_handler.set_program_finished() wait(futures, return_when=ALL_COMPLETED) - raise ProgramRunTooLongException + finished = True except Exception: logger.exception( - f"The following exception occured in job '{future_job}': {crashed_future_exception}" + f"The following exception occured in job '{future_job}': {future.exception()}" ) follow_future = executor.submit( self._follow_data_dump_stream ) futures[follow_future] = "data_processing" elif future_job == "listener": - logger.exception(crashed_future.exception().with_traceback()) - raise crashed_future.exception() new_future = self._initialize_data_stream_threads( executor, listeners=1, has_crashed=True, ) futures[new_future] = "listener" - except ProgramTooSlowException: - logger.info("Program was too slow. Restarting...") - except ProgramRunTooLongException: - logger.info("Program has run too long. Restarting...") - except Exception as e: - logger.exception(f"The following exception occured: {e}") - raise e def main(): diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py index c4fb87fd4..3d2764b6e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py @@ -1,5 +1,4 @@ import pandas as pd -import requests from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.external_data_retrieval.data_retrieval.currency_api_handler import ( @@ -7,7 +6,7 @@ ) from data_retrieval_app.logs.logger import transform_logger as logger from data_retrieval_app.pom_api_authentication import get_superuser_token_headers -from data_retrieval_app.utils import find_hours_since_launch, insert_data +from data_retrieval_app.utils import get_data_safe, insert_data def load_currency_data(): @@ -41,16 +40,11 @@ def _get_name_to_trade_name_dict(self) -> dict: headers = { "User-Agent": f"OAuth pathofmodifiers/0.1.0 (contact: {settings.OATH_ACC_TOKEN_CONTACT_EMAIL}) StrictMode" } - try: - response = requests.get( - "https://www.pathofexile.com/api/trade/data/static", headers=headers - ) - response.raise_for_status() - except Exception as e: - logger.error( - f"The following error occurred while making request _get_latest_change_id: {e}" - ) - raise e + response = get_data_safe( + "https://www.pathofexile.com/api/trade/data/static", + headers=headers, + logger=logger, + ) response_json = response.json() result = response_json["result"] @@ -83,8 +77,10 @@ def _transform_currency_table( "name": ["Chaos Orb"], "chaos.chaosValue": [1], } - chaos_df = pd.DataFrame.from_dict(chaos_dict) - currency_df = pd.concat((currency_df, chaos_df), ignore_index=True) + for league_id in currency_df["leagueId"].unique(): + chaos_dict["leagueId"] = [league_id] + chaos_df = pd.DataFrame.from_dict(chaos_dict) + currency_df = pd.concat((currency_df, chaos_df), ignore_index=True) currency_df["tradeName"] = currency_df["name"].map( lambda name: self.name_to_trade_name.get(name, pd.NA) @@ -101,7 +97,7 @@ def _clean_currency_table(self, currency_df: pd.DataFrame) -> pd.DataFrame: currency_df = currency_df.drop( currency_df.columns.difference( - ["tradeName", "valueInChaos", "createdHoursSinceLaunch"] + ["tradeName", "valueInChaos", "createdHoursSinceLaunch", "leagueId"] ), axis=1, ) @@ -111,17 +107,11 @@ def _clean_currency_table(self, currency_df: pd.DataFrame) -> pd.DataFrame: return currency_df def _get_latest_currency_id_series(self, currency_df: pd.DataFrame) -> pd.Series: - try: - response = requests.get( - f"{self.base_url}/currency/latest_currency_id/", - headers=self.pom_api_headers, - ) - response.raise_for_status() - except Exception as e: - logger.error( - f"The following error occurred while making request _get_latest_currency_id_series: {e}" - ) - raise e + response = get_data_safe( + f"{self.base_url}/currency/latest_currency_id/", + headers=self.pom_api_headers, + logger=logger, + ) latest_currency_id = int(response.text) currency_id = pd.Series( @@ -130,13 +120,14 @@ def _get_latest_currency_id_series(self, currency_df: pd.DataFrame) -> pd.Series ) return currency_id - def transform_into_tables(self, currency_df: pd.DataFrame) -> pd.DataFrame: + def transform_into_tables( + self, currency_df: pd.DataFrame, current_hour: int + ) -> pd.DataFrame: """ Transforms the data into tables and transforms with help functions. """ - hours_since_launch = find_hours_since_launch() logger.debug("Transforming data into tables.") - currency_df = self._transform_currency_table(currency_df, hours_since_launch) + currency_df = self._transform_currency_table(currency_df, current_hour) logger.debug("Successfully transformed data into tables.") logger.debug("Cleaning currency table data.") diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py index 70a0da357..7eee86308 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py @@ -1,5 +1,4 @@ import pandas as pd -import requests from requests.exceptions import HTTPError from data_retrieval_app.external_data_retrieval.config import settings @@ -9,7 +8,7 @@ from data_retrieval_app.external_data_retrieval.utils import sync_timing_tracker from data_retrieval_app.logs.logger import transform_logger as logger from data_retrieval_app.pom_api_authentication import get_superuser_token_headers -from data_retrieval_app.utils import find_hours_since_launch, insert_data +from data_retrieval_app.utils import get_data_safe, insert_data pd.options.mode.chained_assignment = None # default="warn" @@ -24,9 +23,7 @@ def __init__(self) -> None: logger.debug("Initializing PoEAPIDataTransformer done.") - def _create_item_table( - self, df: pd.DataFrame, hours_since_launch: int - ) -> pd.DataFrame: + def _create_item_table(self, df: pd.DataFrame, current_hour: int) -> pd.DataFrame: """ Creates the basis of the `item` table. """ @@ -34,7 +31,7 @@ def _create_item_table( "itemId", "id", "name", - "league", + "leagueId", "baseType", "typeLine", "ilvl", @@ -64,7 +61,7 @@ def _create_item_table( :, [column for column in self.item_columns if column in df.columns] ] # Can't guarantee all columns are present - item_df["createdHoursSinceLaunch"] = hours_since_launch + item_df["createdHoursSinceLaunch"] = current_hour return item_df def _find_not_too_highly_priced_item_mask( @@ -167,8 +164,8 @@ def transform_influences(row: pd.DataFrame, influence_columns: list[str]): item_df = item_df.merge( currency_df, how="left", - left_on="currencyType", - right_on="tradeName", + left_on=["currencyType", "leagueId"], + right_on=["tradeName", "leagueId"], suffixes=(None, "_y"), ) @@ -195,7 +192,7 @@ def item_table_columns_to_not_drop(self) -> set[str]: dont_drop_columns = { "gameItemId", "name", - "league", + "leagueId", "itemBaseTypeId", "ilvl", "rarity", @@ -244,16 +241,11 @@ def _clean_item_table(self, item_df: pd.DataFrame) -> pd.DataFrame: return item_df def _get_latest_item_id_series(self, item_df: pd.DataFrame) -> pd.Series: - try: - response = requests.get( - f"{self.base_url}/item/latest_item_id/", headers=self.pom_auth_headers - ) - response.raise_for_status() - except Exception as e: - logger.error( - f"The following error occurred while making request _get_latest_item_id_series: {e}" - ) - raise e + response = get_data_safe( + f"{self.base_url}/item/latest_item_id/", + headers=self.pom_auth_headers, + logger=logger, + ) latest_item_id = int(response.text) item_id = pd.Series( @@ -267,12 +259,12 @@ def _process_item_table( df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], - hours_since_launch: int, + current_hour: int, ) -> pd.Series: """ Needs to return item ids, as it is used to connect the item modifiers """ - item_df = self._create_item_table(df, hours_since_launch=hours_since_launch) + item_df = self._create_item_table(df, current_hour=current_hour) item_df = self._transform_item_table(item_df, currency_df, item_base_types) item_df = self._clean_item_table(item_df) insert_data( @@ -286,6 +278,38 @@ def _process_item_table( logger.debug("Latest item id found: " + str(item_id)) return item_id + @sync_timing_tracker + def _transform_unidentified_item_table( + self, + item_df: pd.DataFrame, + currency_df: pd.DataFrame, + item_base_types: dict[str, int], + ) -> pd.DataFrame: + """ + For convenience, all unid items are stored in divine prices + """ + item_df = self._transform_item_table(item_df, currency_df, item_base_types) + + item_df["chaos_value"] = ( + item_df["currencyAmount"].astype(float) * item_df["valueInChaos"] + ) + # TODO why do items sometime have chaos currency Id? + for league in item_df["leagueId"].unique(): + divine_row = currency_df.loc[ + (currency_df["leagueId"] == league) + & (currency_df["tradeName"] == "divine") + ].iloc[0] + + divine_id = divine_row["currencyId"] + divine_value = divine_row["valueInChaos"] + + item_league_mask = item_df["leagueId"] == league + item_df.loc[item_league_mask, "currencyId"] = divine_id + item_df.loc[item_league_mask, "currencyAmount"] = ( + item_df.loc[item_league_mask, "chaos_value"] / divine_value + ) + return item_df + def _clean_unidentified_item_table(self, item_df: pd.DataFrame) -> pd.DataFrame: """ Gets rid of unnecessay information, so that only fields needed for the DB remains. @@ -297,7 +321,7 @@ def _clean_unidentified_item_table(self, item_df: pd.DataFrame) -> pd.DataFrame: "name", "itemBaseTypeId", "createdHoursSinceLaunch", - "league", + "leagueId", "currencyId", "ilvl", "currencyAmount", @@ -316,15 +340,73 @@ def _clean_unidentified_item_table(self, item_df: pd.DataFrame) -> pd.DataFrame: return unidentified_item_df + def _aggregate_unidentified_item_table( + self, + ): + """ + Should run once at the end of every hour. For safety, it also aggregates all previous + hours, in case of previous unfortunate errors. + """ + response = get_data_safe( + f"{self.base_url}/unidentifiedItem/non_aggregated/", + headers=self.pom_auth_headers, + logger=logger, + ) + unid_df = pd.DataFrame(response.json()) + if unid_df.empty: + logger.info("Found no unidentified items to aggregate") + return + + group_cols = [ + "leagueId", + "name", + "itemBaseTypeId", + "createdHoursSinceLaunch", + "ilvl", + "identified", + "currencyId", + "rarity", + ] + + g = unid_df.groupby(group_cols) + + unid_df["calc_avg"] = g["currencyAmount"].transform("mean") + unid_df["calc_std"] = g["currencyAmount"].transform("std") + unid_df["calc_count"] = g["itemId"].transform("count") + + filtered_df = unid_df[ + unid_df["currencyAmount"].between( + unid_df["calc_avg"] - 1.97 * unid_df["calc_std"], + unid_df["calc_avg"] + 1.97 * unid_df["calc_std"], + ) + | unid_df["calc_std"].isna() + ] + + result_df = filtered_df.groupby(group_cols, as_index=False).agg( + currencyAmount=("currencyAmount", "mean"), + nItems=("calc_count", "first"), + ) + result_df["aggregated"] = True + logger.info("Pushing aggregated unidentified items") + insert_data( + result_df, + url=self.base_url, + table_name="unidentifiedItem/add_aggregated", + logger=logger, + headers=self.pom_auth_headers, + ) + def _process_unidentified_item_table( self, df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], - hours_since_launch: int, + current_hour: int, ) -> None: - item_df = self._create_item_table(df, hours_since_launch=hours_since_launch) - item_df = self._transform_item_table(item_df, currency_df, item_base_types) + item_df = self._create_item_table(df, current_hour=current_hour) + item_df = self._transform_unidentified_item_table( + item_df, currency_df, item_base_types + ) item_df = self._clean_unidentified_item_table(item_df) insert_data( item_df, @@ -335,7 +417,7 @@ def _process_unidentified_item_table( ) def _create_item_modifier_table( - self, df: pd.DataFrame, *, item_id: pd.Series, hours_since_launch: int + self, df: pd.DataFrame, *, item_id: pd.Series, current_hour: int ) -> pd.DataFrame: """ The `item_modifier` table heavily relies on what type of item the modifiers @@ -368,10 +450,10 @@ def _process_item_modifier_table( self, df: pd.DataFrame, item_id: pd.Series, - hours_since_launch: int, + current_hour: int, ) -> None: item_modifier_df = self._create_item_modifier_table( - df, item_id=item_id, hours_since_launch=hours_since_launch + df, item_id=item_id, current_hour=current_hour ) item_modifier_df = self._transform_item_modifier_table(item_modifier_df) item_modifier_df = self._clean_item_modifier_table(item_modifier_df) @@ -390,28 +472,28 @@ def transform_into_tables( modifier_df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], + current_hour: int, ) -> None: self.roll_processor.add_modifier_df(modifier_df) try: logger.debug("Transforming data into tables.") logger.debug("Processing data tables.") - hours_since_launch = find_hours_since_launch() item_id = self._process_item_table( df.copy(deep=True), currency_df=currency_df, item_base_types=item_base_types, - hours_since_launch=hours_since_launch, + current_hour=current_hour, ) self._process_unidentified_item_table( df.copy(deep=True), currency_df=currency_df, item_base_types=item_base_types, - hours_since_launch=hours_since_launch, + current_hour=current_hour, ) self._process_item_modifier_table( df.copy(deep=True), item_id=item_id, - hours_since_launch=hours_since_launch, + current_hour=current_hour, ) logger.debug("Successfully transformed data into tables.") @@ -419,11 +501,14 @@ def transform_into_tables( logger.exception(f"Something went wrong:\n{repr(e)}") raise e + def end_of_hour_cleanup(self): + self._aggregate_unidentified_item_table() + class UniquePoEAPIDataTransformer(PoEAPIDataTransformerBase): @sync_timing_tracker def _create_item_modifier_table( - self, df: pd.DataFrame, *, item_id: pd.Series, hours_since_launch: int + self, df: pd.DataFrame, *, item_id: pd.Series, current_hour: int ) -> pd.DataFrame: """ A similiar process to creating the item table, only this time the @@ -444,7 +529,7 @@ def _create_item_modifier_table( item_modifier_df.rename({"explicitMods": "modifier"}, axis=1, inplace=True) - item_modifier_df["createdHoursSinceLaunch"] = hours_since_launch + item_modifier_df["createdHoursSinceLaunch"] = current_hour return item_modifier_df diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/utils.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/utils.py index b038c2d28..00d949609 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/utils.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/utils.py @@ -79,7 +79,7 @@ class ProgramTooSlowException(Exception): pass -class ProgramRunTooLongException(Exception): +class ProgramFinished(Exception): pass diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py index de8694d4b..60fdd17e3 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py @@ -5,7 +5,6 @@ from typing import Any import pandas as pd -import requests from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import test_logger @@ -16,6 +15,7 @@ script_settings, ) from data_retrieval_app.tests.utils import random_float, random_int +from data_retrieval_app.utils import get_data_safe class DataDepositTestDataCreator: @@ -59,7 +59,7 @@ def __init__(self, n_of_items: int) -> None: def _get_df_from_url(self, route: str) -> pd.DataFrame: headers = {"accept": "application/json", "Content-Type": "application/json"} headers.update(self.pom_auth_headers) - response = requests.get(f"{self.base_url}/{route}", headers=headers) + response = get_data_safe(f"{self.base_url}/{route}", headers=headers) df = pd.read_json(StringIO(response.text), orient="records") return df diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/scrap_and_mock_poe_api_docs_objs.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/scrap_and_mock_poe_api_docs_objs.py index fbbda98ec..0bf398a9e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/scrap_and_mock_poe_api_docs_objs.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/scrap_and_mock_poe_api_docs_objs.py @@ -2,9 +2,10 @@ import uuid from typing import Any -import requests from bs4 import BeautifulSoup +from data_retrieval_app.utils import get_data_safe + class ScrapAndMockPoEAPIDocs: def __init__(self) -> None: @@ -19,8 +20,7 @@ def _fetch_schema_from_web(self, url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36" } - response = requests.get(url, headers=headers) - response.raise_for_status() + response = get_data_safe(url, headers=headers) return response.text # Parse the schema table using BeautifulSoup diff --git a/src/backend_data_retrieval/data_retrieval_app/utils.py b/src/backend_data_retrieval/data_retrieval_app/utils.py index e45cbc0ec..6a37e09a7 100644 --- a/src/backend_data_retrieval/data_retrieval_app/utils.py +++ b/src/backend_data_retrieval/data_retrieval_app/utils.py @@ -122,3 +122,23 @@ def insert_data( f"Recieved a {response.status_code} response. Error msg: {response.text[:10000]}" ) response.raise_for_status() + + +def get_data_safe( + url: str, + *, + logger: logging.Logger = None, + params: dict | list = None, + headers: dict[str, str] | None = None, +) -> requests.Response: + try: + response = requests.get(url, params=params, headers=headers) + response.raise_for_status() + except Exception as e: + if logger is not None: + logger.error( + f"The following error occurred while making request a request to {url}: {e}" + ) + raise e + + return response From f390d9daccafe3788437996c1fc5db544a925f5c Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Wed, 8 Jul 2026 17:12:38 -0230 Subject: [PATCH 07/15] Fixed tests, added tests for league --- src/backend_api/app/api/README.md | 302 ------------------ src/backend_api/app/tests/README.md | 2 +- .../api/routes/test_currency.py | 43 ++- .../api/routes/test_league.py | 128 ++++++++ .../crud/crud_models/test_league.py | 52 +++ .../app/tests/utils/model_utils/currency.py | 45 ++- .../app/tests/utils/model_utils/item.py | 13 +- .../app/tests/utils/model_utils/league.py | 43 +++ src/backend_api/app/tests/utils/utils.py | 15 +- .../tests/data_deposit/league/__init__.py | 0 .../league/test_league_depositor.py | 3 + 11 files changed, 324 insertions(+), 322 deletions(-) delete mode 100644 src/backend_api/app/api/README.md create mode 100644 src/backend_api/app/tests/test_simulating_env/api/routes/test_league.py create mode 100644 src/backend_api/app/tests/test_simulating_env/crud/crud_models/test_league.py create mode 100644 src/backend_api/app/tests/utils/model_utils/league.py create mode 100644 src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/__init__.py create mode 100644 src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/test_league_depositor.py diff --git a/src/backend_api/app/api/README.md b/src/backend_api/app/api/README.md deleted file mode 100644 index 2602df6ed..000000000 --- a/src/backend_api/app/api/README.md +++ /dev/null @@ -1,302 +0,0 @@ -# Currently outdated. Check out $DOMAIN/docs for updated contents - -## Table of contents -- [API documentation for POM](#api-documentation-for-pom) - - [/currency](#currency) - - [\[GET\] "/currency/{currencyId}" Get Currency](#get-currencycurrencyid-get-currency) - - [\[GET\] "/currency/" Get All Currencies](#get-currency-get-all-currencies) - - [\[POST\] "/currency/" Create Currency](#post-currency-create-currency) - - [\[PUT\] "/currency/{currencyId}" Update Currency](#put-currencycurrencyid-update-currency) - - [\[DELETE\] "/currency/{currencyId}" Delete Currency](#delete-currencycurrencyid-delete-currency) - - [/itemBaseType](#itembasetype) - - [\[GET\] "/itemBaseType/{baseType}" Get Item Base Type](#get-itembasetypebasetype-get-item-base-type) - - [\[GET\] "/itemBaseType/" Get All Item Base Types](#get-itembasetype-get-all-item-base-types) - - [\[POST\] "/itemBaseType/" Create Item Base Type](#post-itembasetype-create-item-base-type) - - [\[PUT\] "/itemBaseType/{baseType}" Update Item Base Type](#put-itembasetypebasetype-update-item-base-type) - - [\[DELETE\] "/itemBaseType/{baseType}" Delete Item Base Type](#delete-itembasetypebasetype-delete-item-base-type) - - [/itemModifier](#itemmodifier) - - [\[GET\] "/itemModifier/{itemId}" Get Item Modifier](#get-itemmodifieritemid-get-item-modifier) - - [\[GET\] "/itemModifier/" Get All Item Modifiers](#get-itemmodifier-get-all-item-modifiers) - - [\[POST\] "/itemModifier/" Create Item Modifier](#post-itemmodifier-create-item-modifier) - - [\[PUT\] "/itemModifier/{itemId}" Update Item Modifier](#put-itemmodifieritemid-update-item-modifier) - - [\[DELETE\] "/itemModifier/{itemId}" Delete Item Modifier](#delete-itemmodifieritemid-delete-item-modifier) - - [/item](#item) - - [\[GET\] "/item/{itemId}" Get Item](#get-itemitemid--get-item) - - [\[GET\] "/item/" Get All Items](#get-item-get-all-items) - - [\[POST\] "/item/" Create Item](#post-item-create-item) - - [\[PUT\] "/item/{itemId}" Update Item](#put-itemitemid-update-item) - - [\[DELETE\] "/item/{itemId}" Delete Item](#delete-itemitemid-delete-item) - - [/modifier](#modifier) - - [\[GET\] "/modifier/{modifierId}" Get Modifier](#get-modifiermodifierid--get-modifier) - - [\[GET\] "/modifier/" Get All Modifiers](#get-modifier-get-all-modifiers) - - [\[POST\] "/modifier/" Create Modifier](#post-modifier-create-modifier) - - [\[PUT\] "/modifier/{modifierId}" Update Modifier](#put-modifiermodifierid-update-modifier) - - [\[DELETE\] "/modifier/{modifierId}" Delete Modifier](#delete-modifiermodifierid-delete-modifier) - -# API documentation for POM - -This section covers the API functions for version 1 in the POM app. - -Each function serves one of five basic operations: retrieving specific data ``Get X``, retrieving all data ``Get All X``, creating data ``Create X``, updating data ``Update X``, and deleting data ``Delete X``. - -The ``Get X`` function retrieves an object by mapping to its primary key. Some primary key attributes are essential and can't be null, while others in the query can be nullable. For ``Get X``, if any or all of the non-essential attributes are null, the query fetches all objects with non-null values for the primary key. It's worth noting that this feature is currently limited to ``Get X`` and isn't available for ``Update X`` and ``Delete X``. However, these capabilities are planned for future releases. - - -## /currency - -### [GET] "/currency/{currencyId}" Get Currency - -Get currency by key and value for "currencyId". - -Always returns one currency. - -![get_currency](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/58422369-bc4c-4b25-ac14-7c90a21b8a10) - -### [GET] "/currency/" Get All Currencies - -Get all currencies. - -Returns a list of all currencies. - -![get_all_currencies](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/d7d7b131-2631-4d2e-ae10-aafa0e430f82) - -### [POST] "/currency/" Create Currency - -Create one or a list of currencies. - -Returns the created currency or list of currencies. - -![create_currency](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/c0daccb6-97e5-4f35-9954-6ff287ac3fce) - - -### [PUT] "/currency/{currencyId}" Update Currency - -Update a currency by key and value for "currencyId". - -Returns the updated currency. - -![update_currency](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/66822fa2-9325-42e2-b2ea-a1103f833fad) - - -### [DELETE] "/currency/{currencyId}" Delete Currency - -Delete a currency by key and value for "currencyId". - -Returns a message indicating the currency was deleted. - -Always deletes one currency. - -![delete_currency](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/1b84d3bf-7bdd-43a5-be06-2cd57149b72b) - - - - - -## /itemBaseType - -### [GET] "/itemBaseType/{baseType}" Get Item Base Type - -Get item base type by key and value for "baseType". - -Always returns one item base type. - -![get_item_base_type](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/0867c743-bc6e-4622-b95b-2ac225b91352) - -### [GET] "/itemBaseType/" Get All Item Base Types - -Get all item base types. - -Returns a list of all item base types. - -![get_all_item_base_types](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/f8e8e65b-1041-40b9-9b8f-97fd1087b0a1) - -### [POST] "/itemBaseType/" Create Item Base Type - -Create one or a list of new item base types. - -Returns the created item base type or list of item base types. - -![create_item_base_type](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/04fb935e-ee30-4def-9dfa-996b432da00e) - - -### [PUT] "/itemBaseType/{baseType}" Update Item Base Type - -Update an item base type by key and value for "baseType". - -Returns the updated item base type. - -![update_item_base_type](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/8c4b78bc-aaf0-47b0-a182-5e68bfe4d2d4) - - -### [DELETE] "/itemBaseType/{baseType}" Delete Item Base Type - -Delete an item base type by key and value for "baseType". - -Returns a message that the item base type was deleted successfully. - -Always deletes one item base type. - -![delete_item_base_type](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/315593bd-694c-4bea-9249-f2ed29bca0ce) - - - -## /itemModifier - -### [GET] "/itemModifier/{itemId}" Get Item Modifier - -Get item modifier or list of item modifiers by key and -value for "itemId", optional "modifierId" and optional "position". - -Dominant key is "itemId". - -Returns one or a list of item modifiers. - -![get_item_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/2eca9b4f-af3b-4b8b-bde5-ada32c338348) - -### [GET] "/itemModifier/" Get All Item Modifiers - -Get all item modifiers. - -Returns a list of all item modifiers. - -![get_all_item_modifiers](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/c378172c-6195-42eb-ab94-6828302454ca) - -### [POST] "/itemModifier/" Create Item Modifier - -Create one or a list item modifiers. - -Returns the created item modifier or list of item modifiers. - -![create_item_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/4f922a54-8545-4894-a21c-f88a4910f3bd) - -### [PUT] "/itemModifier/{itemId}" Update Item Modifier - -Update an item modifier by key and value for -"itemId", optional "modifierId" and optional "position". - -Dominant key is "itemId". - -Returns the updated item modifier. - -![update_item_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/f72a1144-efc1-4080-8d60-414e50d6e624) - -### [DELETE] "/itemModifier/{itemId}" Delete Item Modifier - -Delete an item modifier by key and value for -"itemId", optional "modifierId" and optional "position". - -Dominant key is "itemId". - -Returns a message that the item modifier was deleted successfully. - -Always deletes one item modifier. - -![delete_item_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/a9c951d8-7aa6-4b81-b53f-e6904ae3a807) - - - - -## /item - -### [GET] "/item/{itemId}" Get Item - -Get item by key and value for "itemId". - -Always returns one item. - -![get_item](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/08588a4b-cddc-4aaf-86ea-d0c8f0d48c78) - -### [GET] "/item/" Get All Items - -Get all items. - -Returns a list of all items. - -![get_all_items](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/b450097f-b75c-4e7a-9f10-62a23ff9573b) - -### [POST] "/item/" Create Item - -Create one or a list of new items. - -Returns the created item or list of items. - -![create_item](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/42404020-3e8d-454d-88c8-06e3cc07b88a) - - -### [PUT] "/item/{itemId}" Update Item - -Update an item by key and value for "itemId". - -Returns the updated item. - -![update_item](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/54ca0201-a0eb-4dad-86c4-f57eefe01321) - - -### [DELETE] "/item/{itemId}" Delete Item - -Delete an item by key and value for "itemId". - -Returns a message indicating the item was deleted. - -Always deletes one item. - -![delete_item](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/974f7267-b31b-45ab-88e4-de37fc90d856) - - - - -## /modifier - -### [GET] "/modifier/{modifierId}" Get Modifier - -Get modifier or list of modifiers by key and -value for "modifierId" and optional "position" - -Dominant key is "modifierId". - -Returns one or a list of modifiers. - -![get_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/73a009ee-cef7-450a-bdae-af8264f79a99) - -### [GET] "/modifier/" Get All Modifiers - -Get all modifiers. - -Returns a list of all modifiers. - -![get_all_modifiers](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/1de82b32-0673-4cd7-80a4-86e6f15a30e5) - -### [POST] "/modifier/" Create Modifier - -Create one or a list of new modifiers. - -Returns the created modifier or list of modifiers. - -![create_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/421d446c-7657-4877-b3b8-f644adc1ed78) - - -### [PUT] "/modifier/{modifierId}" Update Modifier - -Update a modifier by key and value for "modifierId" and "position". - -Dominant key is "modifierId". - -Returns the updated modifier. - -![update_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/e7e972f8-b78c-499c-9f96-14dba050f774) - - -### [DELETE] "/modifier/{modifierId}" Delete Modifier - -Delete a modifier by key and value for "modifierId" -and optional "position". - -Dominant key is "modifierId". - -Returns a message that the modifier was deleted. - -Always deletes one modifier. - -![delete_modifier](https://github.com/Ivareh/pathofmodifiersapp/assets/69577035/e906d3ef-d7ae-4154-90a2-c1d047262422) - - diff --git a/src/backend_api/app/tests/README.md b/src/backend_api/app/tests/README.md index 52d7edb3a..e11ba50bd 100644 --- a/src/backend_api/app/tests/README.md +++ b/src/backend_api/app/tests/README.md @@ -14,7 +14,7 @@ The purpose of `test_simulating_env` is to check if base case usage of all the r We test base cases and some outliers for all the crud and route models. -The routes for `currency`, `item_base_type`, `item_modifier`, `item` and `modifier` all inherit from the same generalized crud `base` class. These uses the same test files and classes with specified fixtures for each route and crud. +The routes for `item_base_type`, `item_modifier`, `item` and `modifier` all inherit from the same generalized crud `base` class. These uses the same test files and classes with specified fixtures for each route and crud. There are also specialized tests for routes that doesn't use the crud `base` class. These are `login`, `user`, `plot` and `turnstile`. It doesn't make sence to inherit a base test class with these routes. diff --git a/src/backend_api/app/tests/test_simulating_env/api/routes/test_currency.py b/src/backend_api/app/tests/test_simulating_env/api/routes/test_currency.py index 6a74b4d22..5ed1a9df5 100644 --- a/src/backend_api/app/tests/test_simulating_env/api/routes/test_currency.py +++ b/src/backend_api/app/tests/test_simulating_env/api/routes/test_currency.py @@ -7,10 +7,10 @@ from httpx import AsyncClient import app.tests.test_simulating_env.api.api_routes_test_base as test_api -from app.api.routes import currency_prefix +from app.api.routes import currency_prefix, league_prefix from app.api.routes.currency import get_currency from app.core.config import settings -from app.core.models.models import Currency +from app.core.models.models import Currency, League from app.crud import CRUD_currency from app.crud.base import CRUDBase, ModelType from app.tests.utils.model_utils.currency import ( @@ -91,6 +91,45 @@ def object_generator_func() -> Callable[[], tuple[dict, ModelType]]: return generate_random_currency +@pytest.fixture(scope="module") +def object_generator_func_w_deps() -> ( + Callable[[], tuple[dict, Currency, list[dict | League]]] +): + def generate_random_item_w_deps( + db, + ) -> Callable[ + [], + tuple[ + dict, + Currency, + list[dict | League | Currency], + ], + ]: + return generate_random_currency(db, retrieve_dependencies=True) + + return generate_random_item_w_deps + + +@pytest.fixture(scope="module") +def api_deps_instances() -> list[list[str]]: + """Fixture for API dependencies instances. + + Dependencies in return list needs to be in correct order. + If a dependency is dependent on another, the dependency needs to occur later than + the one its dependent on. The order is defined by 'generate_random_item'. + + Returns: + List[list[str]]: API dependencies instances. Format: [dep_route_prefix: dep_unique_identifier] + """ + return [ + [ + league_prefix, + get_model_unique_identifier(League), + League.__tablename__, + ] + ] + + @pytest.fixture(scope="module") def get_high_permissions() -> bool: """Some models require high permissions to test GET requests diff --git a/src/backend_api/app/tests/test_simulating_env/api/routes/test_league.py b/src/backend_api/app/tests/test_simulating_env/api/routes/test_league.py new file mode 100644 index 000000000..249220af2 --- /dev/null +++ b/src/backend_api/app/tests/test_simulating_env/api/routes/test_league.py @@ -0,0 +1,128 @@ +from collections.abc import Awaitable, Callable +from typing import Any + +import pytest +import pytest_asyncio +from fastapi import Response +from httpx import AsyncClient +from sqlalchemy.orm import Session + +import app.tests.test_simulating_env.api.api_routes_test_base as test_api +from app.api.routes import item_prefix +from app.core.config import settings +from app.core.models.models import League +from app.crud import CRUD_item +from app.crud.base import CRUDBase, ModelType +from app.tests.utils.model_utils.item import ( + create_random_item_dict, + generate_random_item, +) +from app.tests.utils.utils import get_model_table_name, get_model_unique_identifier + + +@pytest.fixture(scope="module") +def route_prefix() -> str: + return item_prefix + + +@pytest.fixture(scope="module") +def is_hypertable() -> bool: + return True + + +@pytest.fixture(scope="module") +def crud_instance() -> CRUDBase: + return CRUD_item + + +@pytest.fixture(scope="module") +def on_duplicate_params() -> tuple[bool, str | None]: + """ + In tuple: + First item: `on_duplicate_do_nothing`. + Second item: `on_duplicate_constraint` (unique constraint to check the duplicate on) + """ + return (False, None) + + +@pytest.fixture(scope="module") +def model_table_name() -> str: + return get_model_table_name(League) + + +@pytest.fixture(scope="module") +def update_request_params() -> bool: + """Some models require params to PUT request in the url + + Returns: + bool: True if the model requires params in the PUT request + """ + return False + + +@pytest.fixture(scope="module") +def ignore_test_columns() -> list[str]: + """Ignore these columns when testing the model + + createdAt are ignored because currently, the API returns + time in a different format than the one stored in the database + + Returns: + List[str]: List of columns to ignore + """ + return [] + + +@pytest.fixture(scope="module") +def unique_identifier() -> str: + unique_identifier = get_model_unique_identifier(League) + return unique_identifier + + +@pytest.fixture(scope="module") +def get_high_permissions() -> bool: + """Some models require high permissions to test GET requests + + Returns: + bool: True if the model requires high permissions to test GET requests + """ + return False + + +@pytest.fixture(scope="module") +def object_generator_func() -> Callable[[], tuple[dict, ModelType]]: + return generate_random_item + + +@pytest.fixture(scope="module") +def create_random_object_func() -> Callable[[Session], Awaitable[dict]]: + async def create_object(db: Session) -> dict: + return await create_random_item_dict(db) + + return create_object + + +@pytest_asyncio.fixture +async def get_object_from_api_normal_user( + async_client: AsyncClient, + route_prefix: str, + unique_identifier: str, + normal_user_token_headers: dict[str, str], +) -> Callable[[Any, Any], Awaitable[Any]]: + async def _get_object(object_pk_map: dict[str, Any]) -> Response: + response = await async_client.get( + f"{settings.API_V1_STR}/{route_prefix}/{object_pk_map[unique_identifier]}", + headers=normal_user_token_headers, + ) + return response + + return _get_object + + +@pytest.fixture(scope="module") +def update_request_params_deps() -> list[str]: + return [] + + +class TestLeague(test_api.TestAPI): + pass diff --git a/src/backend_api/app/tests/test_simulating_env/crud/crud_models/test_league.py b/src/backend_api/app/tests/test_simulating_env/crud/crud_models/test_league.py new file mode 100644 index 000000000..e5dccb9c6 --- /dev/null +++ b/src/backend_api/app/tests/test_simulating_env/crud/crud_models/test_league.py @@ -0,0 +1,52 @@ +from collections.abc import Callable, Generator + +import pytest +from sqlalchemy.orm import Session + +import app.tests.test_simulating_env.crud.crud_test_base as test_crud +from app.core.models.database import engine +from app.crud import CRUD_league +from app.crud.base import CRUDBase +from app.tests.utils.model_utils.league import generate_random_league + + +@pytest.fixture(scope="session") +def db() -> Generator: + with Session(engine) as session: + yield session + session.rollback() + session.close() + + +@pytest.fixture(scope="module") +def is_hypertable() -> bool: + return False + + +@pytest.fixture(scope="module") +def on_duplicate_params() -> tuple[bool, str | None]: + """ + In tuple: + First item: `on_duplicate_do_nothing`. + Second item: `on_duplicate_constraint` (unique constraint to check the duplicate on) + """ + return (False, None) + + +@pytest.fixture(scope="module") +def object_generator_func() -> Callable[[], dict]: + return generate_random_league + + +@pytest.fixture(scope="module") +def main_key() -> str: + return None + + +@pytest.fixture(scope="module") +def crud_instance() -> CRUDBase: + return CRUD_league + + +class TestLeagueCRUD(test_crud.TestCRUD): + pass diff --git a/src/backend_api/app/tests/utils/model_utils/currency.py b/src/backend_api/app/tests/utils/model_utils/currency.py index 7fdc6beee..7b733aba5 100644 --- a/src/backend_api/app/tests/utils/model_utils/currency.py +++ b/src/backend_api/app/tests/utils/model_utils/currency.py @@ -1,8 +1,9 @@ from sqlalchemy.orm import Session from app import crud -from app.core.models.models import Currency +from app.core.models.models import Currency, League from app.core.schemas import CurrencyCreate +from app.tests.utils.model_utils.league import generate_random_league from app.tests.utils.utils import ( random_float, random_int, @@ -10,35 +11,61 @@ ) -def create_random_currency_dict() -> dict: +async def create_random_currency_dict( + db: Session, retrieve_dependencies: bool | None = False +) -> dict | tuple[dict, list[dict | League]]: """Create a random currency dictionary. + Args: + db (Session): DB session. + retrieve_dependencies (bool, optional): Whether to retrieve dependencies. Defaults to False. + Returns: - Dict: Currency dictionary with random values. + Union[ Dict, Tuple[ Dict, List[ Union[ Dict, League] ], ], ]: \n + Random Currency dictionary or tuple with random Currency dictionary and dependencies. """ tradeName = random_lower_string() valueInChaos = random_float() createdHoursSinceLaunch = random_int(small_int=True) + league_dict, league = await generate_random_league(db) + leagueId = league.leagueId + currency = { "tradeName": tradeName, "valueInChaos": valueInChaos, "createdHoursSinceLaunch": createdHoursSinceLaunch, + "leagueId": leagueId, } - - return currency + if not retrieve_dependencies: + return currency + else: + deps = [] + deps += [league_dict, league] + return currency, deps -async def generate_random_currency(db: Session) -> tuple[dict, Currency]: +async def generate_random_currency( + db: Session, retrieve_dependencies: bool | None = False +) -> tuple[dict, Currency, list[dict | League] | None]: """Generates a random currency. Args: db (Session): DB session. Returns: - Tuple[Dict, Currency]: Random currency dict and Currency db object. + Tuple[ Dict, Currency, List[ Union[ Dict, League, ] ] ], ]: \n + Random currency dict and Currency db object and optional dependencies. """ - currency_dict = create_random_currency_dict() + output = await create_random_currency_dict(db, retrieve_dependencies) + if not retrieve_dependencies: + currency_dict = output + else: + currency_dict, deps = output currency_create = CurrencyCreate(**currency_dict) currency = await crud.CRUD_currency.create(db, obj_in=currency_create) - return currency_dict, currency + + if not retrieve_dependencies: + return currency_dict, currency + else: + return currency_dict, currency, deps diff --git a/src/backend_api/app/tests/utils/model_utils/item.py b/src/backend_api/app/tests/utils/model_utils/item.py index da80ba6a0..7144bbffa 100644 --- a/src/backend_api/app/tests/utils/model_utils/item.py +++ b/src/backend_api/app/tests/utils/model_utils/item.py @@ -1,10 +1,11 @@ from sqlalchemy.orm import Session from app import crud -from app.core.models.models import Currency, Item, ItemBaseType +from app.core.models.models import Currency, Item, ItemBaseType, League from app.core.schemas.item import ItemCreate from app.tests.utils.model_utils.currency import generate_random_currency from app.tests.utils.model_utils.item_base_type import generate_random_item_base_type +from app.tests.utils.model_utils.league import generate_random_league from app.tests.utils.utils import ( random_bool, random_float, @@ -16,7 +17,7 @@ async def create_random_item_dict( db: Session, retrieve_dependencies: bool | None = False -) -> dict | tuple[dict, list[dict | ItemBaseType | Currency]]: +) -> dict | tuple[dict, list[dict | ItemBaseType | Currency | League]]: """Create a random item dictionary. Args: @@ -24,11 +25,10 @@ async def create_random_item_dict( retrieve_dependencies (bool, optional): Whether to retrieve dependencies. Defaults to False. Returns: - Union[ Dict, Tuple[ Dict, List[ Union[ Dict, ItemBaseType, Currency, ] ], ], ]: \n + Union[ Dict, Tuple[ Dict, List[ Union[ Dict, ItemBaseType, Currency, League] ], ], ]: \n Random item dictionary or tuple with random item dictionary and dependencies. """ name = random_lower_string() - league = random_lower_string() ilvl = random_int(small_int=True) rarity = random_lower_string() identified = random_bool() @@ -58,10 +58,12 @@ async def create_random_item_dict( itemBaseTypeId = item_base_type.itemBaseTypeId currency_dict, currency = await generate_random_currency(db) currencyId = currency.currencyId + league_dict, league = await generate_random_league(db) + leagueId = league.leagueId item = { "name": name, - "league": league, + "leagueId": leagueId, "itemBaseTypeId": itemBaseTypeId, "createdHoursSinceLaunch": createdHoursSinceLaunch, "ilvl": ilvl, @@ -88,6 +90,7 @@ async def create_random_item_dict( deps = [] deps += [item_base_type_dict, item_base_type] deps += [currency_dict, currency] + deps += [league_dict, league] return item, deps diff --git a/src/backend_api/app/tests/utils/model_utils/league.py b/src/backend_api/app/tests/utils/model_utils/league.py new file mode 100644 index 000000000..a8b27545d --- /dev/null +++ b/src/backend_api/app/tests/utils/model_utils/league.py @@ -0,0 +1,43 @@ +from sqlalchemy.orm import Session + +from app import crud +from app.core.models.models import League +from app.core.schemas import LeagueCreate +from app.tests.utils.utils import ( + random_datetime, + random_lower_string, +) + + +def create_random_league_dict() -> dict: + """Create a random league dictionary. + + Returns: + Dict: League dictionary with random values. + """ + name = random_lower_string() + validFrom = random_datetime() + validTo = random_datetime(min_time=validFrom) + + league = { + "name": name, + "validFrom": validFrom, + "validTo": validTo, + } + + return league + + +async def generate_random_league(db: Session) -> tuple[dict, League]: + """Generates a random league. + + Args: + db (Session): DB session. + + Returns: + Tuple[Dict, League]: Random league dict and League db object. + """ + league_dict = create_random_league_dict() + league_create = LeagueCreate(**league_dict) + league = await crud.CRUD_league.create(db, obj_in=league_create) + return league_dict, league diff --git a/src/backend_api/app/tests/utils/utils.py b/src/backend_api/app/tests/utils/utils.py index 0dfa5c441..405a6ed61 100644 --- a/src/backend_api/app/tests/utils/utils.py +++ b/src/backend_api/app/tests/utils/utils.py @@ -1,7 +1,7 @@ import random import string from collections.abc import Callable -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from inspect import iscoroutinefunction from typing import Any @@ -130,13 +130,22 @@ def random_url() -> str: return f"https://{random_lower_string()}.{random_lower_string(small_string=True)}" -def random_datetime() -> datetime: +def random_datetime(min_time: datetime = None, max_time: datetime = None) -> datetime: """Generate a random datetime. Returns: datetime: Random datetime. """ - return datetime.now() + random.uniform(-5, 5) * timedelta(days=1) + time = datetime.now(tz=timezone.utc) + bottom = -5 + top = 5 + if min_time is not None: + time = min(time, min_time) + bottom = 0 + if max_time is not None: + time = max(time, max_time) + top = 0 + return time + random.uniform(bottom, top) * timedelta(days=1) def random_based_on_type(reference: str | float | int) -> str | int | float: diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/__init__.py b/src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/test_league_depositor.py b/src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/test_league_depositor.py new file mode 100644 index 000000000..cae116f6d --- /dev/null +++ b/src/backend_data_retrieval/data_retrieval_app/tests/data_deposit/league/test_league_depositor.py @@ -0,0 +1,3 @@ +class TestLeagueDataDepositor: + # TODO: Add tests if needed + pass From 2dca75a264684622a036d1516a9d15e0f5674d08 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Wed, 8 Jul 2026 17:57:33 -0230 Subject: [PATCH 08/15] Fixed plotting (at least tests), still missing frontend --- .../app/core/schemas/plot/input.py | 2 +- .../app/core/schemas/plot/output.py | 2 +- src/backend_api/app/plotting/plotter.py | 22 ++++++++--------- .../app/tests/utils/model_utils/item.py | 2 +- .../tests/utils/model_utils/item_modifier.py | 5 ++-- .../app/tests/utils/model_utils/plot.py | 24 +++---------------- .../external_data_retrieval/config.py | 1 - .../data_retrieval/poe_api_handler.py | 6 ----- 8 files changed, 20 insertions(+), 44 deletions(-) diff --git a/src/backend_api/app/core/schemas/plot/input.py b/src/backend_api/app/core/schemas/plot/input.py index a7f333eb2..9071528c3 100644 --- a/src/backend_api/app/core/schemas/plot/input.py +++ b/src/backend_api/app/core/schemas/plot/input.py @@ -39,7 +39,7 @@ class WantedModifier(_pydantic.BaseModel): class BasePlotQuery(_pydantic.BaseModel): - league: list[str] | str + leagueId: list[int] | int itemSpecifications: ItemSpecs | None = None baseSpecifications: BaseSpecs | None = None end: int | None = None diff --git a/src/backend_api/app/core/schemas/plot/output.py b/src/backend_api/app/core/schemas/plot/output.py index 998f3b19c..248ec4c6a 100644 --- a/src/backend_api/app/core/schemas/plot/output.py +++ b/src/backend_api/app/core/schemas/plot/output.py @@ -11,7 +11,7 @@ class Datum(_pydantic.BaseModel): class TimeseriesData(_pydantic.BaseModel): - name: str + name: int data: list[Datum] confidenceRating: Literal["low", "medium", "high"] diff --git a/src/backend_api/app/plotting/plotter.py b/src/backend_api/app/plotting/plotter.py index bbdb033eb..6bcd96320 100644 --- a/src/backend_api/app/plotting/plotter.py +++ b/src/backend_api/app/plotting/plotter.py @@ -97,7 +97,7 @@ def _init_stmt( select_args: list[InstrumentedAttribute[Any] | Label[Any]] = [ item_model.itemId, item_model.createdHoursSinceLaunch, - item_model.league, + item_model.leagueId, item_model.itemBaseTypeId, item_model.currencyId, item_model.currencyAmount, @@ -114,12 +114,12 @@ def _init_stmt( model_Currency, item_model.currencyId == model_Currency.currencyId ) - if isinstance(query.league, list): + if isinstance(query.leagueId, list): stmt = stmt.where( - or_(*[item_model.league == league for league in query.league]) + or_(*[item_model.leagueId == league for league in query.leagueId]) ) else: - stmt = stmt.where(model_Item.league == query.league) + stmt = stmt.where(model_Item.leagueId == query.leagueId) if start is not None: stmt = stmt.where(item_model.createdHoursSinceLaunch >= start) @@ -223,8 +223,8 @@ def _create_plot_data(self, df: pd.DataFrame) -> PlotData: 0, "divine" ) # TODO: set enum data = [] - for league in df["league"].unique(): - league_df: pd.DataFrame = df.loc[df["league"] == league] + for league in df["leagueId"].unique(): + league_df: pd.DataFrame = df.loc[df["leagueId"] == league] league_data = league_df[ [ "hoursSinceLaunch", @@ -443,7 +443,7 @@ def _create_plot_statement(self, query: IdentifiedPlotQuery) -> Select: base_query.c.tradeName.label("mostCommonTradeName"), func.count(base_query.c.tradeName).label("nameCount"), ) - .group_by(base_query.c.league, base_query.c.tradeName) + .group_by(base_query.c.leagueId, base_query.c.tradeName) .order_by(desc("nameCount")) # Can use literal_column in complex cases .limit(1) .cte("mostCommon") @@ -486,7 +486,7 @@ def _create_plot_statement(self, query: IdentifiedPlotQuery) -> Select: prices = ( select( base_query.c.createdHoursSinceLaunch, - base_query.c.league, + base_query.c.leagueId, (base_query.c.currencyAmount * base_query.c.valueInChaos).label( "valueInChaos" ), @@ -519,7 +519,7 @@ def _create_plot_statement(self, query: IdentifiedPlotQuery) -> Select: filtered_prices = ( select( ranked_prices.c.createdHoursSinceLaunch, - ranked_prices.c.league, + ranked_prices.c.leagueId, ranked_prices.c.valueInChaos, ranked_prices.c.valueInMostCommonCurrencyUsed, ranked_prices.c.mostCommonCurrencyUsed, @@ -537,7 +537,7 @@ def _create_plot_statement(self, query: IdentifiedPlotQuery) -> Select: final_query = ( select( filtered_prices.c.createdHoursSinceLaunch.label("hoursSinceLaunch"), - filtered_prices.c.league, + filtered_prices.c.leagueId, func.avg(filtered_prices.c.valueInChaos).label("valueInChaos"), func.avg(filtered_prices.c.valueInMostCommonCurrencyUsed).label( "valueInMostCommonCurrencyUsed" @@ -548,7 +548,7 @@ def _create_plot_statement(self, query: IdentifiedPlotQuery) -> Select: func.min(filtered_prices.c.confidence).label("confidence"), ) .group_by( - filtered_prices.c.createdHoursSinceLaunch, filtered_prices.c.league + filtered_prices.c.createdHoursSinceLaunch, filtered_prices.c.leagueId ) .order_by(filtered_prices.c.createdHoursSinceLaunch) ) diff --git a/src/backend_api/app/tests/utils/model_utils/item.py b/src/backend_api/app/tests/utils/model_utils/item.py index 7144bbffa..c9dde2dd4 100644 --- a/src/backend_api/app/tests/utils/model_utils/item.py +++ b/src/backend_api/app/tests/utils/model_utils/item.py @@ -99,7 +99,7 @@ async def generate_random_item( ) -> tuple[ dict, Item, - list[dict | ItemBaseType | Currency] | None, + list[dict | ItemBaseType | Currency | League] | None, ]: """Generate a random item. diff --git a/src/backend_api/app/tests/utils/model_utils/item_modifier.py b/src/backend_api/app/tests/utils/model_utils/item_modifier.py index b287d9e18..d28735bf9 100644 --- a/src/backend_api/app/tests/utils/model_utils/item_modifier.py +++ b/src/backend_api/app/tests/utils/model_utils/item_modifier.py @@ -6,6 +6,7 @@ Item, ItemBaseType, ItemModifier, + League, Modifier, ) from app.core.schemas.item_modifier import ItemModifierCreate @@ -20,7 +21,7 @@ async def create_random_item_modifier_dict( dict | tuple[ dict, - list[dict | ItemBaseType | Currency | Item | Modifier] | None, + list[dict | ItemBaseType | Currency | League | Item | Modifier] | None, ] ): """Create a random item modifier dictionary. @@ -66,7 +67,7 @@ async def generate_random_item_modifier( ) -> tuple[ dict, ItemModifier, - list[dict | ItemBaseType | Currency | Item | Modifier] | None, + list[dict | ItemBaseType | Currency | League | Item | Modifier] | None, ]: """Generate a random item modifier. diff --git a/src/backend_api/app/tests/utils/model_utils/plot.py b/src/backend_api/app/tests/utils/model_utils/plot.py index eae196ddd..f71e91e68 100644 --- a/src/backend_api/app/tests/utils/model_utils/plot.py +++ b/src/backend_api/app/tests/utils/model_utils/plot.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session from app.core.models.models import ( - Item as model_Item, + League as model_League, ) from app.core.models.models import ( Modifier as model_Modifier, @@ -24,26 +24,9 @@ async def create_minimal_random_plot_query_dict(db: Session) -> dict[str, Any]: db, retrieve_dependencies=True ) - item_dep: model_Item = item_modifier_deps[-3] + league_dep: model_League = item_modifier_deps[-3] modifier_dep: model_Modifier = item_modifier_deps[-1] - item_specs = { - "name": None, - "identified": None, - "minIlvl": None, - "maxIlvl": None, - "rarity": None, - "corrupted": None, - "delve": None, - "fractured": None, - "synthesised": None, - "replica": None, - "influences": None, - "searing": None, - "tangled": None, - "foilVariation": None, - } - wanted_modifiers = [ { "modifierId": modifier_dep.modifierId, @@ -51,8 +34,7 @@ async def create_minimal_random_plot_query_dict(db: Session) -> dict[str, Any]: ] plot_query = { - "league": item_dep.league, - "itemSpecifications": item_specs, + "leagueId": league_dep.leagueId, "wantedModifiers": wanted_modifiers, } diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py index edeed8ab6..5cd7323f9 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py @@ -41,7 +41,6 @@ def CURRENT_HARDCORE_LEAGUE(self) -> str: MINI_BATCH_SIZE: int = 30 N_CHECKPOINTS_PER_TRANSFORMATION: int = 10 - TIME_BETWEEN_RESTART: int = 3600 MAX_TIME_PER_MINI_BATCH: int = 3 * 60 LEAGUE_LAUNCH_TIME: str diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py index 48a51de7e..84ef47182 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py @@ -84,8 +84,6 @@ def __init__( self._program_too_slow = False self._program_finished = False - self.time_of_launch = time.perf_counter() - self.run_program_for_n_seconds = settings.TIME_BETWEEN_RESTART logger.info("PoEAPIHandler successfully initialized.") self.n_checkpoints_per_transfromation = ( @@ -531,7 +529,3 @@ def dump_stream(self, track_progress: bool = True) -> Iterator[pd.DataFrame]: yield df.reset_index() del df logger.info("Finished transformation phase") - current_time = time.perf_counter() - time_since_launch = current_time - self.time_of_launch - if time_since_launch > self.run_program_for_n_seconds: - raise ProgramFinished From 1905f830235b402fdfedb076d29c6e3df3d2ceb6 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Sun, 12 Jul 2026 09:54:00 -0230 Subject: [PATCH 09/15] #824 Updated the frontend api --- src/backend_api/app/api/api.py | 28 +- src/backend_api/app/api/routes/league.py | 12 +- src/frontend/openapi.json | 1300 +++++++++++++---- src/frontend/src/client/index.ts | 11 + src/frontend/src/client/models/Currency.ts | 1 + .../src/client/models/CurrencyCreate.ts | 1 + .../src/client/models/CurrencyQuery.ts | 10 + .../src/client/models/CurrencyUpdate.ts | 1 + src/frontend/src/client/models/Item.ts | 2 +- src/frontend/src/client/models/ItemCreate.ts | 2 +- src/frontend/src/client/models/League.ts | 11 + .../src/client/models/LeagueCreate.ts | 10 + .../src/client/models/LeagueUpdate.ts | 11 + src/frontend/src/client/models/PlotQuery.ts | 2 +- .../src/client/models/TimeseriesData.ts | 2 +- .../src/client/models/UnidentifiedItem.ts | 6 +- .../client/models/UnidentifiedItemCreate.ts | 4 +- src/frontend/src/client/schemas/$Currency.ts | 4 + .../src/client/schemas/$CurrencyCreate.ts | 4 + .../src/client/schemas/$CurrencyQuery.ts | 32 + .../src/client/schemas/$CurrencyUpdate.ts | 4 + src/frontend/src/client/schemas/$Item.ts | 4 +- .../src/client/schemas/$ItemCreate.ts | 4 +- src/frontend/src/client/schemas/$League.ts | 30 + .../src/client/schemas/$LeagueCreate.ts | 26 + .../src/client/schemas/$LeagueUpdate.ts | 30 + src/frontend/src/client/schemas/$PlotQuery.ts | 6 +- .../src/client/schemas/$TimeseriesData.ts | 2 +- .../src/client/schemas/$UnidentifiedItem.ts | 16 +- .../client/schemas/$UnidentifiedItemCreate.ts | 10 +- .../src/client/services/CurrencysService.ts | 154 +- .../client/services/ItemBaseTypesService.ts | 106 +- .../services/LatestCurrenciesService.ts | 22 + .../services/LatestCurrencyIdService.ts | 2 +- .../src/client/services/LatestHourService.ts | 21 + .../src/client/services/LeaguesService.ts | 139 ++ .../src/client/services/ModifiersService.ts | 62 +- .../services/UnidentifiedItemsService.ts | 36 + 38 files changed, 1647 insertions(+), 481 deletions(-) create mode 100644 src/frontend/src/client/models/CurrencyQuery.ts create mode 100644 src/frontend/src/client/models/League.ts create mode 100644 src/frontend/src/client/models/LeagueCreate.ts create mode 100644 src/frontend/src/client/models/LeagueUpdate.ts create mode 100644 src/frontend/src/client/schemas/$CurrencyQuery.ts create mode 100644 src/frontend/src/client/schemas/$League.ts create mode 100644 src/frontend/src/client/schemas/$LeagueCreate.ts create mode 100644 src/frontend/src/client/schemas/$LeagueUpdate.ts create mode 100644 src/frontend/src/client/services/LatestCurrenciesService.ts create mode 100644 src/frontend/src/client/services/LatestHourService.ts create mode 100644 src/frontend/src/client/services/LeaguesService.ts diff --git a/src/backend_api/app/api/api.py b/src/backend_api/app/api/api.py index 67c6b5496..19175c6a9 100644 --- a/src/backend_api/app/api/api.py +++ b/src/backend_api/app/api/api.py @@ -29,43 +29,37 @@ api_router.include_router( - currency.router, prefix=f"/{currency_prefix}", tags=[f"{currency_prefix}s"] + currency.router, prefix=f"/{currency_prefix}", tags=[currency_prefix] ) api_router.include_router( item_base_type.router, prefix=f"/{item_base_type_prefix}", - tags=[f"{item_base_type_prefix}s"], + tags=[item_base_type_prefix], ) api_router.include_router( item_modifier.router, prefix=f"/{item_modifier_prefix}", - tags=[f"{item_modifier_prefix}s"], + tags=[item_modifier_prefix], ) +api_router.include_router(item.router, prefix=f"/{item_prefix}", tags=[item_prefix]) api_router.include_router( - item.router, prefix=f"/{item_prefix}", tags=[f"{item_prefix}s"] -) -api_router.include_router( - league.router, prefix=f"/{league_prefix}", tags=[f"{league_prefix}s"] + league.router, prefix=f"/{league_prefix}", tags=[league_prefix] ) api_router.include_router( unidentified_item.router, prefix=f"/{unidentified_item_prefix}", - tags=[f"{unidentified_item_prefix}s"], -) -api_router.include_router( - modifier.router, prefix=f"/{modifier_prefix}", tags=[f"{modifier_prefix}s"] -) -api_router.include_router( - plot.router, prefix=f"/{plot_prefix}", tags=[f"{plot_prefix}s"] + tags=[unidentified_item_prefix], ) api_router.include_router( - turnstile.router, prefix=f"/{turnstile_prefix}", tags=[f"{turnstile_prefix}s"] + modifier.router, prefix=f"/{modifier_prefix}", tags=[modifier_prefix] ) +api_router.include_router(plot.router, prefix=f"/{plot_prefix}", tags=[plot_prefix]) api_router.include_router( - test.router, prefix=f"/{test_prefix}", tags=[f"{test_prefix}s"] + turnstile.router, prefix=f"/{turnstile_prefix}", tags=[turnstile_prefix] ) +api_router.include_router(test.router, prefix=f"/{test_prefix}", tags=[test_prefix]) api_router.include_router( login.router, prefix=f"/{login_prefix}", - tags=[f"{login_prefix}s"], + tags=[login_prefix], ) diff --git a/src/backend_api/app/api/routes/league.py b/src/backend_api/app/api/routes/league.py index c24b3758d..af8877bfe 100644 --- a/src/backend_api/app/api/routes/league.py +++ b/src/backend_api/app/api/routes/league.py @@ -12,6 +12,7 @@ from app.api.params import FilterParams from app.core.rate_limit.rate_limit_config import rate_limit_settings from app.core.rate_limit.rate_limiters import ( + apply_ip_rate_limits, apply_user_rate_limits, ) from app.crud import CRUD_league @@ -56,11 +57,16 @@ async def get_league( @router.get( "/", response_model=schemas.League | list[schemas.League], - dependencies=[ - Depends(get_current_active_superuser), - ], +) +@apply_ip_rate_limits( + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_SECOND, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_MINUTE, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, + rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, ) async def get_all_leagues( + request: Request, # noqa: ARG001 + response: Response, # noqa: ARG001 filter_params: Annotated[FilterParams, Query()], db: Session = Depends(get_db), ): diff --git a/src/frontend/openapi.json b/src/frontend/openapi.json index fdf3cf208..b449ee04e 100644 --- a/src/frontend/openapi.json +++ b/src/frontend/openapi.json @@ -51,6 +51,214 @@ } } } + } + }, + "/api/api_v1/currency/": { + "get": { + "tags": [ + "currencys" + ], + "summary": "Get All Currencies", + "description": "Get all currencies.\n\nReturns a list of all currencies.", + "operationId": "get_all_currencies", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer", + "exclusiveMinimum": 0 + }, + { + "type": "null" + } + ], + "title": "Limit" + } + }, + { + "name": "skip", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "null" + } + ], + "title": "Skip" + } + }, + { + "name": "sort_key", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort Key" + } + }, + { + "name": "sort_method", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "enum": [ + "asc", + "desc" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sort Method" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/Currency" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + } + } + ], + "title": "Response Currencys-Get All Currencies" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "currencys" + ], + "summary": "Create Currency", + "description": "Create one or a list of currencies.\n\nReturns the created currency or list of currencies.", + "operationId": "create_currency", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "return_nothing", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Return Nothing" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/CurrencyCreate" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/CurrencyCreate" + } + } + ], + "title": "Currency" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/CurrencyCreate" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/CurrencyCreate" + } + }, + { + "type": "null" + } + ], + "title": "Response Currencys-Create Currency" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } }, "put": { "tags": [ @@ -67,7 +275,7 @@ "parameters": [ { "name": "currencyId", - "in": "path", + "in": "query", "required": true, "schema": { "type": "integer", @@ -110,11 +318,205 @@ }, "delete": { "tags": [ - "currencys" + "currencys" + ], + "summary": "Delete Currency", + "description": "Delete a currency by key and value for \"currencyId\".\n\nReturns a message indicating the currency was deleted.\nAlways deletes one currency.", + "operationId": "delete_currency", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "currencyId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Currencyid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "string", + "title": "Response Currencys-Delete Currency" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/api_v1/currency/latest_currency_id/": { + "get": { + "tags": [ + "currencys", + "latest_currency_id" + ], + "summary": "Get Latest Currency Id", + "description": "Get the latest currencyId, returns 1 if table is empty\n\nCan only be used safely on an empty table or directly after an insertion.", + "operationId": "get_latest_currency_id", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Response Currencys-Get Latest Currency Id" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/api_v1/currency/latest_hour/": { + "get": { + "tags": [ + "currencys", + "latest_hour" + ], + "summary": "Get Latest Hour", + "description": "Return -1 if database is empty", + "operationId": "get_latest_hour", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Response Currencys-Get Latest Hour" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/api_v1/currency/latest_currencies/": { + "get": { + "tags": [ + "currencys", + "latest_currencies" + ], + "summary": "Get Latest Currencies", + "description": "Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint.", + "operationId": "get_latest_currencies", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Currency" + }, + "type": "array", + "title": "Response Currencys-Get Latest Currencies" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/api_v1/currency/from_query/": { + "post": { + "tags": [ + "currencys" + ], + "summary": "Get Currency From Query", + "description": "Returns a list of currencies that match any of the queries", + "operationId": "get_currency_from_query", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CurrencyQuery" + }, + "type": "array", + "title": "Query List" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Currency" + }, + "type": "array", + "title": "Response Currencys-Get Currency From Query" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/api_v1/itemBaseType/{itemBaseTypeId}": { + "get": { + "tags": [ + "itemBaseTypes" ], - "summary": "Delete Currency", - "description": "Delete a currency by key and value for \"currencyId\".\n\nReturns a message indicating the currency was deleted.\nAlways deletes one currency.", - "operationId": "delete_currency", + "summary": "Get Item Base Type", + "description": "Get item base type by key and value for \"itemBaseTypeId\".\n\nAlways returns one item base type.", + "operationId": "get_item_base_type", "security": [ { "OAuth2PasswordBearer": [] @@ -122,12 +524,12 @@ ], "parameters": [ { - "name": "currencyId", + "name": "itemBaseTypeId", "in": "path", "required": true, "schema": { - "type": "integer", - "title": "Currencyid" + "type": "string", + "title": "Itembasetypeid" } } ], @@ -137,8 +539,7 @@ "content": { "application/json": { "schema": { - "type": "string", - "title": "Response Currencys-Delete Currency" + "$ref": "#/components/schemas/ItemBaseType" } } } @@ -156,19 +557,14 @@ } } }, - "/api/api_v1/currency/": { + "/api/api_v1/itemBaseType/": { "get": { "tags": [ - "currencys" - ], - "summary": "Get All Currencies", - "description": "Get all currencies.\n\nReturns a list of all currencies.", - "operationId": "get_all_currencies", - "security": [ - { - "OAuth2PasswordBearer": [] - } + "itemBaseTypes" ], + "summary": "Get All Item Base Types", + "description": "Get all item base types.\n\nReturns a list of all item base types.", + "operationId": "get_all_item_base_types", "parameters": [ { "name": "limit", @@ -249,16 +645,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/Currency" + "$ref": "#/components/schemas/ItemBaseType" }, { "type": "array", "items": { - "$ref": "#/components/schemas/Currency" + "$ref": "#/components/schemas/ItemBaseType" } } ], - "title": "Response Currencys-Get All Currencies" + "title": "Response Itembasetypes-Get All Item Base Types" } } } @@ -277,17 +673,33 @@ }, "post": { "tags": [ - "currencys" + "itemBaseTypes" ], - "summary": "Create Currency", - "description": "Create one or a list of currencies.\n\nReturns the created currency or list of currencies.", - "operationId": "create_currency", + "summary": "Create Item Base Type", + "description": "Create one or a list of new item base types.\n\nReturns the created item base type or list of item base types.", + "operationId": "create_item_base_type", "security": [ { "OAuth2PasswordBearer": [] } ], "parameters": [ + { + "name": "on_duplicate_pkey_do_nothing", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "On Duplicate Pkey Do Nothing" + } + }, { "name": "return_nothing", "in": "query", @@ -312,16 +724,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/CurrencyCreate" + "$ref": "#/components/schemas/ItemBaseTypeCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/CurrencyCreate" + "$ref": "#/components/schemas/ItemBaseTypeCreate" } } ], - "title": "Currency" + "title": "Itembasetype" } } } @@ -334,96 +746,19 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/CurrencyCreate" + "$ref": "#/components/schemas/ItemBaseTypeCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/CurrencyCreate" + "$ref": "#/components/schemas/ItemBaseTypeCreate" } }, { "type": "null" } ], - "title": "Response Currencys-Create Currency" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/api/api_v1/currency/latest_currency_id/": { - "get": { - "tags": [ - "currencys", - "latest_currency_id" - ], - "summary": "Get Latest Currency Id", - "description": "Get the latest currencyId\n\nCan only be used safely on an empty table or directly after an insertion.", - "operationId": "get_latest_currency_id", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "integer", - "title": "Response Currencys-Get Latest Currency Id" - } - } - } - } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ] - } - }, - "/api/api_v1/itemBaseType/{itemBaseTypeId}": { - "get": { - "tags": [ - "itemBaseTypes" - ], - "summary": "Get Item Base Type", - "description": "Get item base type by key and value for \"itemBaseTypeId\".\n\nAlways returns one item base type.", - "operationId": "get_item_base_type", - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], - "parameters": [ - { - "name": "itemBaseTypeId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "title": "Itembasetypeid" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ItemBaseType" + "title": "Response Itembasetypes-Create Item Base Type" } } } @@ -455,7 +790,7 @@ "parameters": [ { "name": "itemBaseTypeId", - "in": "path", + "in": "query", "required": true, "schema": { "type": "integer", @@ -511,7 +846,7 @@ "parameters": [ { "name": "itemBaseTypeId", - "in": "path", + "in": "query", "required": true, "schema": { "type": "integer", @@ -544,14 +879,19 @@ } } }, - "/api/api_v1/itemBaseType/": { + "/api/api_v1/itemModifier/": { "get": { "tags": [ - "itemBaseTypes" + "itemModifiers" + ], + "summary": "Get All Item Modifiers", + "description": "Get all item modifiers.\n\nReturns a list of all item modifiers.", + "operationId": "get_all_item_modifiers", + "security": [ + { + "OAuth2PasswordBearer": [] + } ], - "summary": "Get All Item Base Types", - "description": "Get all item base types.\n\nReturns a list of all item base types.", - "operationId": "get_all_item_base_types", "parameters": [ { "name": "limit", @@ -632,16 +972,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemBaseType" + "$ref": "#/components/schemas/ItemModifier" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemBaseType" + "$ref": "#/components/schemas/ItemModifier" } } ], - "title": "Response Itembasetypes-Get All Item Base Types" + "title": "Response Itemmodifiers-Get All Item Modifiers" } } } @@ -660,33 +1000,17 @@ }, "post": { "tags": [ - "itemBaseTypes" + "itemModifiers" ], - "summary": "Create Item Base Type", - "description": "Create one or a list of new item base types.\n\nReturns the created item base type or list of item base types.", - "operationId": "create_item_base_type", + "summary": "Create Item Modifier", + "description": "Create one or a list item modifiers.\n\nReturns the created item modifier or list of item modifiers.", + "operationId": "create_item_modifier", "security": [ { "OAuth2PasswordBearer": [] } ], "parameters": [ - { - "name": "on_duplicate_pkey_do_nothing", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "On Duplicate Pkey Do Nothing" - } - }, { "name": "return_nothing", "in": "query", @@ -711,16 +1035,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemBaseTypeCreate" + "$ref": "#/components/schemas/ItemModifierCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemBaseTypeCreate" + "$ref": "#/components/schemas/ItemModifierCreate" } } ], - "title": "Itembasetype" + "title": "Itemmodifier" } } } @@ -733,44 +1057,80 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemBaseTypeCreate" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/ItemBaseTypeCreate" - } + "$ref": "#/components/schemas/ItemModifierCreate" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemModifierCreate" + } + }, + { + "type": "null" + } + ], + "title": "Response Itemmodifiers-Create Item Modifier" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/api_v1/item/latest_item_id/": { + "get": { + "tags": [ + "items", + "latest_item_id" + ], + "summary": "Get Latest Item Id", + "description": "Get the latest \"itemId\"\n\nCan only be used safely on an empty table or directly after an insertion.", + "operationId": "get_latest_item_id", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "type": "integer" }, { "type": "null" } ], - "title": "Response Itembasetypes-Create Item Base Type" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Response Items-Get Latest Item Id" } } } } - } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] } }, - "/api/api_v1/itemModifier/": { + "/api/api_v1/item/": { "get": { "tags": [ - "itemModifiers" + "items" ], - "summary": "Get All Item Modifiers", - "description": "Get all item modifiers.\n\nReturns a list of all item modifiers.", - "operationId": "get_all_item_modifiers", + "summary": "Get All Items", + "description": "Get all items.\n\nReturns a list of all items.", + "operationId": "get_all_items", "security": [ { "OAuth2PasswordBearer": [] @@ -856,16 +1216,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemModifier" + "$ref": "#/components/schemas/Item" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemModifier" + "$ref": "#/components/schemas/Item" } } ], - "title": "Response Itemmodifiers-Get All Item Modifiers" + "title": "Response Items-Get All Items" } } } @@ -884,11 +1244,11 @@ }, "post": { "tags": [ - "itemModifiers" + "items" ], - "summary": "Create Item Modifier", - "description": "Create one or a list item modifiers.\n\nReturns the created item modifier or list of item modifiers.", - "operationId": "create_item_modifier", + "summary": "Create Item", + "description": "Create one or a list of new items.\n\nReturns the created item or list of items.", + "operationId": "create_item", "security": [ { "OAuth2PasswordBearer": [] @@ -919,16 +1279,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemModifierCreate" + "$ref": "#/components/schemas/ItemCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemModifierCreate" + "$ref": "#/components/schemas/ItemCreate" } } ], - "title": "Itemmodifier" + "title": "Item" } } } @@ -941,19 +1301,19 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemModifierCreate" + "$ref": "#/components/schemas/ItemCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemModifierCreate" + "$ref": "#/components/schemas/ItemCreate" } }, { "type": "null" } ], - "title": "Response Itemmodifiers-Create Item Modifier" + "title": "Response Items-Create Item" } } } @@ -971,50 +1331,118 @@ } } }, - "/api/api_v1/item/latest_item_id/": { + "/api/api_v1/league/{leagueId}": { "get": { "tags": [ - "items", - "latest_item_id" + "leagues" + ], + "summary": "Get League", + "description": "Get league by key and value for \"leagueId\".\n\nAlways returns one league.", + "operationId": "get_league", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "leagueId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Leagueid" + } + } ], - "summary": "Get Latest Item Id", - "description": "Get the latest \"itemId\"\n\nCan only be used safely on an empty table or directly after an insertion.", - "operationId": "get_latest_item_id", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Response Items-Get Latest Item Id" + "$ref": "#/components/schemas/League" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" } } } } - }, + } + }, + "put": { + "tags": [ + "leagues" + ], + "summary": "Update Modifier", + "description": "Update a league by key and value for \"leagueId\"\n\nReturns the updated league.", + "operationId": "update_modifier", "security": [ { "OAuth2PasswordBearer": [] } - ] + ], + "parameters": [ + { + "name": "leagueId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Leagueid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeagueUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/League" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } } }, - "/api/api_v1/item/": { + "/api/api_v1/league/": { "get": { "tags": [ - "items" + "leagues" ], - "summary": "Get All Items", - "description": "Get all items.\n\nReturns a list of all items.", - "operationId": "get_all_items", + "summary": "Get All Leagues", + "description": "Get all leagues.\n\nReturns a list of all leagues.", + "operationId": "get_all_leagues", "security": [ { "OAuth2PasswordBearer": [] @@ -1100,16 +1528,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/Item" + "$ref": "#/components/schemas/League" }, { "type": "array", "items": { - "$ref": "#/components/schemas/Item" + "$ref": "#/components/schemas/League" } } ], - "title": "Response Items-Get All Items" + "title": "Response Leagues-Get All Leagues" } } } @@ -1128,11 +1556,11 @@ }, "post": { "tags": [ - "items" + "leagues" ], - "summary": "Create Item", - "description": "Create one or a list of new items.\n\nReturns the created item or list of items.", - "operationId": "create_item", + "summary": "Create League", + "description": "Create one or a list of new leagues.\n\nReturns the created league or list of leagues.", + "operationId": "create_league", "security": [ { "OAuth2PasswordBearer": [] @@ -1163,16 +1591,16 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemCreate" + "$ref": "#/components/schemas/LeagueCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemCreate" + "$ref": "#/components/schemas/LeagueCreate" } } ], - "title": "Item" + "title": "League" } } } @@ -1185,19 +1613,19 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ItemCreate" + "$ref": "#/components/schemas/LeagueCreate" }, { "type": "array", "items": { - "$ref": "#/components/schemas/ItemCreate" + "$ref": "#/components/schemas/LeagueCreate" } }, { "type": "null" } ], - "title": "Response Items-Create Item" + "title": "Response Leagues-Create League" } } } @@ -1215,6 +1643,37 @@ } } }, + "/api/api_v1/league/active_league/": { + "get": { + "tags": [ + "leagues" + ], + "summary": "Get Active Leagues", + "description": "Get leagues that are still valid/active\n\nAlways returns a list, but it may be empty.", + "operationId": "get_active_leagues", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/League" + }, + "type": "array", + "title": "Response Leagues-Get Active Leagues" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, "/api/api_v1/unidentifiedItem/latest_item_id/": { "get": { "tags": [ @@ -1459,48 +1918,70 @@ } } }, - "/api/api_v1/modifier/{modifierId}": { + "/api/api_v1/unidentifiedItem/non_aggregated/": { "get": { "tags": [ - "modifiers" + "unidentifiedItems" ], - "summary": "Get Modifier", - "description": "Get modifier or list of modifiers by key and\nvalue for \"modifierId\"\n\nDominant key is \"modifierId\".\n\nReturns one or a list of modifiers.", - "operationId": "get_modifier", + "summary": "Get Non Aggregated", + "description": "Get the non aggregated unidentified items for the last 10 recorded hours\n (not necessarily the last 10 hours)", + "operationId": "get_non_aggregated", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/UnidentifiedItem" + }, + "type": "array", + "title": "Response Unidentifieditems-Get Non Aggregated" + } + } + } + } + }, "security": [ { "OAuth2PasswordBearer": [] } + ] + } + }, + "/api/api_v1/unidentifiedItem/add_aggregated/": { + "post": { + "tags": [ + "unidentifiedItems" ], - "parameters": [ - { - "name": "modifierId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "title": "Modifierid" + "summary": "Add Aggregated", + "description": "Deletes the non aggregated unidentified items for the last 10 recorded hours\n (not necessarily the last 10 hours)\nAnd inserts the new items", + "operationId": "add_aggregated", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/UnidentifiedItemCreate" + }, + "type": "array", + "title": "Aggregated Objs" + } } - } - ], + }, + "required": true + }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/Modifier" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Modifier" - } - } - ], - "title": "Response Modifiers-Get Modifier" + "items": { + "$ref": "#/components/schemas/UnidentifiedItem" + }, + "type": "array", + "title": "Response Unidentifieditems-Add Aggregated" } } } @@ -1515,15 +1996,22 @@ } } } - } - }, - "delete": { + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/api_v1/modifier/{modifierId}": { + "get": { "tags": [ "modifiers" ], - "summary": "Delete Modifier", - "description": "Delete a modifier by key and value for \"modifierId\"\n\nReturns a message that the modifier was deleted.\nAlways deletes one modifier.", - "operationId": "delete_modifier", + "summary": "Get Modifier", + "description": "Get modifier or list of modifiers by key and\nvalue for \"modifierId\"\n\nDominant key is \"modifierId\".\n\nReturns one or a list of modifiers.", + "operationId": "get_modifier", "security": [ { "OAuth2PasswordBearer": [] @@ -1538,15 +2026,6 @@ "type": "integer", "title": "Modifierid" } - }, - { - "name": "position", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "title": "Position" - } } ], "responses": { @@ -1555,8 +2034,18 @@ "content": { "application/json": { "schema": { - "type": "string", - "title": "Response Modifiers-Delete Modifier" + "anyOf": [ + { + "$ref": "#/components/schemas/Modifier" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Modifier" + } + } + ], + "title": "Response Modifiers-Get Modifier" } } } @@ -1786,7 +2275,7 @@ "modifiers" ], "summary": "Update Modifier", - "description": "Update a modifier by key and value for \"modifierId\"\n\nDominant key is \"modifierId\".\n\nReturns the updated modifier.", + "description": "Update a modifier by key and value for \"modifierId\" and \"position\"\n\nReturns the updated modifier.", "operationId": "update_modifier", "security": [ { @@ -1845,6 +2334,62 @@ } } } + }, + "delete": { + "tags": [ + "modifiers" + ], + "summary": "Delete Modifier", + "description": "Delete a modifier by key and value for \"modifierId\"\n\nReturns a message that the modifier was deleted.\nAlways deletes one modifier.", + "operationId": "delete_modifier", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "modifierId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Modifierid" + } + }, + { + "name": "position", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "title": "Position" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "string", + "title": "Response Modifiers-Delete Modifier" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } } }, "/api/api_v1/modifier/grouped_modifiers_by_effect/": { @@ -2219,6 +2764,10 @@ "type": "number", "title": "Valueinchaos" }, + "leagueId": { + "type": "integer", + "title": "Leagueid" + }, "createdHoursSinceLaunch": { "type": "integer", "title": "Createdhourssincelaunch" @@ -2232,6 +2781,7 @@ "required": [ "tradeName", "valueInChaos", + "leagueId", "createdHoursSinceLaunch", "currencyId" ], @@ -2247,6 +2797,10 @@ "type": "number", "title": "Valueinchaos" }, + "leagueId": { + "type": "integer", + "title": "Leagueid" + }, "createdHoursSinceLaunch": { "type": "integer", "title": "Createdhourssincelaunch" @@ -2256,10 +2810,50 @@ "required": [ "tradeName", "valueInChaos", + "leagueId", "createdHoursSinceLaunch" ], "title": "CurrencyCreate" }, + "CurrencyQuery": { + "properties": { + "createdHoursSinceLaunch": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Createdhourssincelaunch" + }, + "tradeName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tradename" + }, + "leagueId": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Leagueid" + } + }, + "type": "object", + "title": "CurrencyQuery" + }, "CurrencyUpdate": { "properties": { "tradeName": { @@ -2269,12 +2863,17 @@ "valueInChaos": { "type": "number", "title": "Valueinchaos" + }, + "leagueId": { + "type": "integer", + "title": "Leagueid" } }, "type": "object", "required": [ "tradeName", - "valueInChaos" + "valueInChaos", + "leagueId" ], "title": "CurrencyUpdate" }, @@ -2491,9 +3090,9 @@ ], "title": "Name" }, - "league": { - "type": "string", - "title": "League" + "leagueId": { + "type": "integer", + "title": "Leagueid" }, "itemBaseTypeId": { "type": "integer", @@ -2676,7 +3275,7 @@ }, "type": "object", "required": [ - "league", + "leagueId", "itemBaseTypeId", "ilvl", "rarity", @@ -2823,9 +3422,9 @@ ], "title": "Name" }, - "league": { - "type": "string", - "title": "League" + "leagueId": { + "type": "integer", + "title": "Leagueid" }, "itemBaseTypeId": { "type": "integer", @@ -3004,7 +3603,7 @@ }, "type": "object", "required": [ - "league", + "leagueId", "itemBaseTypeId", "ilvl", "rarity", @@ -3249,6 +3848,109 @@ "type": "object", "title": "ItemSpecs" }, + "League": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "validFrom": { + "type": "string", + "format": "date-time", + "title": "Validfrom" + }, + "validTo": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Validto" + }, + "leagueId": { + "type": "integer", + "title": "Leagueid" + } + }, + "type": "object", + "required": [ + "name", + "validFrom", + "leagueId" + ], + "title": "League" + }, + "LeagueCreate": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "validFrom": { + "type": "string", + "format": "date-time", + "title": "Validfrom" + }, + "validTo": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Validto" + } + }, + "type": "object", + "required": [ + "name", + "validFrom" + ], + "title": "LeagueCreate" + }, + "LeagueUpdate": { + "properties": { + "leagueId": { + "type": "integer", + "title": "Leagueid" + }, + "name": { + "type": "string", + "title": "Name" + }, + "validFrom": { + "type": "string", + "format": "date-time", + "title": "Validfrom" + }, + "validTo": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Validto" + } + }, + "type": "object", + "required": [ + "leagueId", + "name", + "validFrom" + ], + "title": "LeagueUpdate" + }, "MetadataObject": { "properties": { "interactive": { @@ -3907,19 +4609,19 @@ }, "PlotQuery": { "properties": { - "league": { + "leagueId": { "anyOf": [ { "items": { - "type": "string" + "type": "integer" }, "type": "array" }, { - "type": "string" + "type": "integer" } ], - "title": "League" + "title": "Leagueid" }, "itemSpecifications": { "anyOf": [ @@ -3980,7 +4682,7 @@ }, "type": "object", "required": [ - "league" + "leagueId" ], "title": "PlotQuery", "description": "Plots for items with or without modifiers" @@ -3988,7 +4690,7 @@ "TimeseriesData": { "properties": { "name": { - "type": "string", + "type": "integer", "title": "Name" }, "data": { @@ -4141,9 +4843,9 @@ ], "title": "Name" }, - "league": { - "type": "string", - "title": "League" + "leagueId": { + "type": "integer", + "title": "Leagueid" }, "itemBaseTypeId": { "type": "integer", @@ -4184,14 +4886,6 @@ ], "title": "Currencyid" }, - "createdHoursSinceLaunch": { - "type": "integer", - "title": "Createdhourssincelaunch" - }, - "itemId": { - "type": "integer", - "title": "Itemid" - }, "nItems": { "type": "integer", "title": "Nitems" @@ -4199,18 +4893,26 @@ "aggregated": { "type": "boolean", "title": "Aggregated" + }, + "createdHoursSinceLaunch": { + "type": "integer", + "title": "Createdhourssincelaunch" + }, + "itemId": { + "type": "integer", + "title": "Itemid" } }, "type": "object", "required": [ - "league", + "leagueId", "itemBaseTypeId", "ilvl", "rarity", - "createdHoursSinceLaunch", - "itemId", "nItems", - "aggregated" + "aggregated", + "createdHoursSinceLaunch", + "itemId" ], "title": "UnidentifiedItem" }, @@ -4227,9 +4929,9 @@ ], "title": "Name" }, - "league": { - "type": "string", - "title": "League" + "leagueId": { + "type": "integer", + "title": "Leagueid" }, "itemBaseTypeId": { "type": "integer", @@ -4270,6 +4972,16 @@ ], "title": "Currencyid" }, + "nItems": { + "type": "integer", + "title": "Nitems", + "default": 1 + }, + "aggregated": { + "type": "boolean", + "title": "Aggregated", + "default": false + }, "createdHoursSinceLaunch": { "type": "integer", "title": "Createdhourssincelaunch" @@ -4277,7 +4989,7 @@ }, "type": "object", "required": [ - "league", + "leagueId", "itemBaseTypeId", "ilvl", "rarity", diff --git a/src/frontend/src/client/index.ts b/src/frontend/src/client/index.ts index 8ac0e3140..81d6f70be 100644 --- a/src/frontend/src/client/index.ts +++ b/src/frontend/src/client/index.ts @@ -11,6 +11,7 @@ export type { BaseSpecs } from './models/BaseSpecs'; export type { Body_logins_login_access_session } from './models/Body_logins_login_access_session'; export type { Currency } from './models/Currency'; export type { CurrencyCreate } from './models/CurrencyCreate'; +export type { CurrencyQuery } from './models/CurrencyQuery'; export type { CurrencyUpdate } from './models/CurrencyUpdate'; export type { Datum } from './models/Datum'; export type { GroupedModifierByEffect } from './models/GroupedModifierByEffect'; @@ -25,6 +26,9 @@ export type { ItemCreate } from './models/ItemCreate'; export type { ItemModifier } from './models/ItemModifier'; export type { ItemModifierCreate } from './models/ItemModifierCreate'; export type { ItemSpecs } from './models/ItemSpecs'; +export type { League } from './models/League'; +export type { LeagueCreate } from './models/LeagueCreate'; +export type { LeagueUpdate } from './models/LeagueUpdate'; export type { MetadataObject } from './models/MetadataObject'; export type { Modifier } from './models/Modifier'; export type { ModifierCreate } from './models/ModifierCreate'; @@ -45,6 +49,7 @@ export { $BaseSpecs } from './schemas/$BaseSpecs'; export { $Body_logins_login_access_session } from './schemas/$Body_logins_login_access_session'; export { $Currency } from './schemas/$Currency'; export { $CurrencyCreate } from './schemas/$CurrencyCreate'; +export { $CurrencyQuery } from './schemas/$CurrencyQuery'; export { $CurrencyUpdate } from './schemas/$CurrencyUpdate'; export { $Datum } from './schemas/$Datum'; export { $GroupedModifierByEffect } from './schemas/$GroupedModifierByEffect'; @@ -59,6 +64,9 @@ export { $ItemCreate } from './schemas/$ItemCreate'; export { $ItemModifier } from './schemas/$ItemModifier'; export { $ItemModifierCreate } from './schemas/$ItemModifierCreate'; export { $ItemSpecs } from './schemas/$ItemSpecs'; +export { $League } from './schemas/$League'; +export { $LeagueCreate } from './schemas/$LeagueCreate'; +export { $LeagueUpdate } from './schemas/$LeagueUpdate'; export { $MetadataObject } from './schemas/$MetadataObject'; export { $Modifier } from './schemas/$Modifier'; export { $ModifierCreate } from './schemas/$ModifierCreate'; @@ -79,8 +87,11 @@ export { CurrencysService } from './services/CurrencysService'; export { ItemBaseTypesService } from './services/ItemBaseTypesService'; export { ItemModifiersService } from './services/ItemModifiersService'; export { ItemsService } from './services/ItemsService'; +export { LatestCurrenciesService } from './services/LatestCurrenciesService'; export { LatestCurrencyIdService } from './services/LatestCurrencyIdService'; +export { LatestHourService } from './services/LatestHourService'; export { LatestItemIdService } from './services/LatestItemIdService'; +export { LeaguesService } from './services/LeaguesService'; export { LoginsService } from './services/LoginsService'; export { ModifiersService } from './services/ModifiersService'; export { PlotsService } from './services/PlotsService'; diff --git a/src/frontend/src/client/models/Currency.ts b/src/frontend/src/client/models/Currency.ts index 2a8dbe85e..3b317f296 100644 --- a/src/frontend/src/client/models/Currency.ts +++ b/src/frontend/src/client/models/Currency.ts @@ -5,6 +5,7 @@ export type Currency = { tradeName: string; valueInChaos: number; + leagueId: number; createdHoursSinceLaunch: number; currencyId: number; }; diff --git a/src/frontend/src/client/models/CurrencyCreate.ts b/src/frontend/src/client/models/CurrencyCreate.ts index 56807d6bb..e9a852c88 100644 --- a/src/frontend/src/client/models/CurrencyCreate.ts +++ b/src/frontend/src/client/models/CurrencyCreate.ts @@ -5,6 +5,7 @@ export type CurrencyCreate = { tradeName: string; valueInChaos: number; + leagueId: number; createdHoursSinceLaunch: number; }; diff --git a/src/frontend/src/client/models/CurrencyQuery.ts b/src/frontend/src/client/models/CurrencyQuery.ts new file mode 100644 index 000000000..fcc3e5385 --- /dev/null +++ b/src/frontend/src/client/models/CurrencyQuery.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CurrencyQuery = { + createdHoursSinceLaunch?: (number | null); + tradeName?: (string | null); + leagueId?: (number | null); +}; + diff --git a/src/frontend/src/client/models/CurrencyUpdate.ts b/src/frontend/src/client/models/CurrencyUpdate.ts index 798b25ee3..dff118825 100644 --- a/src/frontend/src/client/models/CurrencyUpdate.ts +++ b/src/frontend/src/client/models/CurrencyUpdate.ts @@ -5,5 +5,6 @@ export type CurrencyUpdate = { tradeName: string; valueInChaos: number; + leagueId: number; }; diff --git a/src/frontend/src/client/models/Item.ts b/src/frontend/src/client/models/Item.ts index f82c264ec..b2fa4a8f3 100644 --- a/src/frontend/src/client/models/Item.ts +++ b/src/frontend/src/client/models/Item.ts @@ -5,7 +5,7 @@ import type { Influences } from './Influences'; export type Item = { name?: (string | null); - league: string; + leagueId: number; itemBaseTypeId: number; ilvl: number; rarity: string; diff --git a/src/frontend/src/client/models/ItemCreate.ts b/src/frontend/src/client/models/ItemCreate.ts index e371bdcb5..7dc3d527c 100644 --- a/src/frontend/src/client/models/ItemCreate.ts +++ b/src/frontend/src/client/models/ItemCreate.ts @@ -5,7 +5,7 @@ import type { Influences } from './Influences'; export type ItemCreate = { name?: (string | null); - league: string; + leagueId: number; itemBaseTypeId: number; ilvl: number; rarity: string; diff --git a/src/frontend/src/client/models/League.ts b/src/frontend/src/client/models/League.ts new file mode 100644 index 000000000..7df8d2165 --- /dev/null +++ b/src/frontend/src/client/models/League.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type League = { + name: string; + validFrom: string; + validTo?: (string | null); + leagueId: number; +}; + diff --git a/src/frontend/src/client/models/LeagueCreate.ts b/src/frontend/src/client/models/LeagueCreate.ts new file mode 100644 index 000000000..1eab1893b --- /dev/null +++ b/src/frontend/src/client/models/LeagueCreate.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type LeagueCreate = { + name: string; + validFrom: string; + validTo?: (string | null); +}; + diff --git a/src/frontend/src/client/models/LeagueUpdate.ts b/src/frontend/src/client/models/LeagueUpdate.ts new file mode 100644 index 000000000..783f75ee2 --- /dev/null +++ b/src/frontend/src/client/models/LeagueUpdate.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type LeagueUpdate = { + leagueId: number; + name: string; + validFrom: string; + validTo?: (string | null); +}; + diff --git a/src/frontend/src/client/models/PlotQuery.ts b/src/frontend/src/client/models/PlotQuery.ts index d00b12cff..515126382 100644 --- a/src/frontend/src/client/models/PlotQuery.ts +++ b/src/frontend/src/client/models/PlotQuery.ts @@ -9,7 +9,7 @@ import type { WantedModifier } from './WantedModifier'; * Plots for items with or without modifiers */ export type PlotQuery = { - league: (Array | string); + leagueId: (Array | number); itemSpecifications?: (ItemSpecs | null); baseSpecifications?: (BaseSpecs | null); end?: (number | null); diff --git a/src/frontend/src/client/models/TimeseriesData.ts b/src/frontend/src/client/models/TimeseriesData.ts index 08fff7a17..6dc744fa8 100644 --- a/src/frontend/src/client/models/TimeseriesData.ts +++ b/src/frontend/src/client/models/TimeseriesData.ts @@ -4,7 +4,7 @@ /* eslint-disable */ import type { Datum } from './Datum'; export type TimeseriesData = { - name: string; + name: number; data: Array; confidenceRating: 'low' | 'medium' | 'high'; }; diff --git a/src/frontend/src/client/models/UnidentifiedItem.ts b/src/frontend/src/client/models/UnidentifiedItem.ts index 5fc7cee12..0a6fbbd82 100644 --- a/src/frontend/src/client/models/UnidentifiedItem.ts +++ b/src/frontend/src/client/models/UnidentifiedItem.ts @@ -4,16 +4,16 @@ /* eslint-disable */ export type UnidentifiedItem = { name?: (string | null); - league: string; + leagueId: number; itemBaseTypeId: number; ilvl: number; rarity: string; identified?: boolean; currencyAmount?: (number | null); currencyId?: (number | null); - createdHoursSinceLaunch: number; - itemId: number; nItems: number; aggregated: boolean; + createdHoursSinceLaunch: number; + itemId: number; }; diff --git a/src/frontend/src/client/models/UnidentifiedItemCreate.ts b/src/frontend/src/client/models/UnidentifiedItemCreate.ts index 5a2a81257..7b4995f0c 100644 --- a/src/frontend/src/client/models/UnidentifiedItemCreate.ts +++ b/src/frontend/src/client/models/UnidentifiedItemCreate.ts @@ -4,13 +4,15 @@ /* eslint-disable */ export type UnidentifiedItemCreate = { name?: (string | null); - league: string; + leagueId: number; itemBaseTypeId: number; ilvl: number; rarity: string; identified?: boolean; currencyAmount?: (number | null); currencyId?: (number | null); + nItems?: number; + aggregated?: boolean; createdHoursSinceLaunch: number; }; diff --git a/src/frontend/src/client/schemas/$Currency.ts b/src/frontend/src/client/schemas/$Currency.ts index f65be2195..1ec0e51c9 100644 --- a/src/frontend/src/client/schemas/$Currency.ts +++ b/src/frontend/src/client/schemas/$Currency.ts @@ -12,6 +12,10 @@ export const $Currency = { type: 'number', isRequired: true, }, + leagueId: { + type: 'number', + isRequired: true, + }, createdHoursSinceLaunch: { type: 'number', isRequired: true, diff --git a/src/frontend/src/client/schemas/$CurrencyCreate.ts b/src/frontend/src/client/schemas/$CurrencyCreate.ts index 815ece5d9..07dc7433a 100644 --- a/src/frontend/src/client/schemas/$CurrencyCreate.ts +++ b/src/frontend/src/client/schemas/$CurrencyCreate.ts @@ -12,6 +12,10 @@ export const $CurrencyCreate = { type: 'number', isRequired: true, }, + leagueId: { + type: 'number', + isRequired: true, + }, createdHoursSinceLaunch: { type: 'number', isRequired: true, diff --git a/src/frontend/src/client/schemas/$CurrencyQuery.ts b/src/frontend/src/client/schemas/$CurrencyQuery.ts new file mode 100644 index 000000000..8584c1511 --- /dev/null +++ b/src/frontend/src/client/schemas/$CurrencyQuery.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CurrencyQuery = { + properties: { + createdHoursSinceLaunch: { + type: 'any-of', + contains: [{ + type: 'number', + }, { + type: 'null', + }], + }, + tradeName: { + type: 'any-of', + contains: [{ + type: 'string', + }, { + type: 'null', + }], + }, + leagueId: { + type: 'any-of', + contains: [{ + type: 'number', + }, { + type: 'null', + }], + }, + }, +} as const; diff --git a/src/frontend/src/client/schemas/$CurrencyUpdate.ts b/src/frontend/src/client/schemas/$CurrencyUpdate.ts index a757d8a76..c98f26a6b 100644 --- a/src/frontend/src/client/schemas/$CurrencyUpdate.ts +++ b/src/frontend/src/client/schemas/$CurrencyUpdate.ts @@ -12,5 +12,9 @@ export const $CurrencyUpdate = { type: 'number', isRequired: true, }, + leagueId: { + type: 'number', + isRequired: true, + }, }, } as const; diff --git a/src/frontend/src/client/schemas/$Item.ts b/src/frontend/src/client/schemas/$Item.ts index 6beee37e6..ec1728b0f 100644 --- a/src/frontend/src/client/schemas/$Item.ts +++ b/src/frontend/src/client/schemas/$Item.ts @@ -12,8 +12,8 @@ export const $Item = { type: 'null', }], }, - league: { - type: 'string', + leagueId: { + type: 'number', isRequired: true, }, itemBaseTypeId: { diff --git a/src/frontend/src/client/schemas/$ItemCreate.ts b/src/frontend/src/client/schemas/$ItemCreate.ts index 1995e1f36..5044d1e3f 100644 --- a/src/frontend/src/client/schemas/$ItemCreate.ts +++ b/src/frontend/src/client/schemas/$ItemCreate.ts @@ -12,8 +12,8 @@ export const $ItemCreate = { type: 'null', }], }, - league: { - type: 'string', + leagueId: { + type: 'number', isRequired: true, }, itemBaseTypeId: { diff --git a/src/frontend/src/client/schemas/$League.ts b/src/frontend/src/client/schemas/$League.ts new file mode 100644 index 000000000..cf64c511b --- /dev/null +++ b/src/frontend/src/client/schemas/$League.ts @@ -0,0 +1,30 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $League = { + properties: { + name: { + type: 'string', + isRequired: true, + }, + validFrom: { + type: 'string', + isRequired: true, + format: 'date-time', + }, + validTo: { + type: 'any-of', + contains: [{ + type: 'string', + format: 'date-time', + }, { + type: 'null', + }], + }, + leagueId: { + type: 'number', + isRequired: true, + }, + }, +} as const; diff --git a/src/frontend/src/client/schemas/$LeagueCreate.ts b/src/frontend/src/client/schemas/$LeagueCreate.ts new file mode 100644 index 000000000..a9dd8ff30 --- /dev/null +++ b/src/frontend/src/client/schemas/$LeagueCreate.ts @@ -0,0 +1,26 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $LeagueCreate = { + properties: { + name: { + type: 'string', + isRequired: true, + }, + validFrom: { + type: 'string', + isRequired: true, + format: 'date-time', + }, + validTo: { + type: 'any-of', + contains: [{ + type: 'string', + format: 'date-time', + }, { + type: 'null', + }], + }, + }, +} as const; diff --git a/src/frontend/src/client/schemas/$LeagueUpdate.ts b/src/frontend/src/client/schemas/$LeagueUpdate.ts new file mode 100644 index 000000000..d09bbf72c --- /dev/null +++ b/src/frontend/src/client/schemas/$LeagueUpdate.ts @@ -0,0 +1,30 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $LeagueUpdate = { + properties: { + leagueId: { + type: 'number', + isRequired: true, + }, + name: { + type: 'string', + isRequired: true, + }, + validFrom: { + type: 'string', + isRequired: true, + format: 'date-time', + }, + validTo: { + type: 'any-of', + contains: [{ + type: 'string', + format: 'date-time', + }, { + type: 'null', + }], + }, + }, +} as const; diff --git a/src/frontend/src/client/schemas/$PlotQuery.ts b/src/frontend/src/client/schemas/$PlotQuery.ts index 502d1d031..ef30c8bb9 100644 --- a/src/frontend/src/client/schemas/$PlotQuery.ts +++ b/src/frontend/src/client/schemas/$PlotQuery.ts @@ -5,15 +5,15 @@ export const $PlotQuery = { description: `Plots for items with or without modifiers`, properties: { - league: { + leagueId: { type: 'any-of', contains: [{ type: 'array', contains: { - type: 'string', + type: 'number', }, }, { - type: 'string', + type: 'number', }], isRequired: true, }, diff --git a/src/frontend/src/client/schemas/$TimeseriesData.ts b/src/frontend/src/client/schemas/$TimeseriesData.ts index dc3e0eb8c..4a2a2a376 100644 --- a/src/frontend/src/client/schemas/$TimeseriesData.ts +++ b/src/frontend/src/client/schemas/$TimeseriesData.ts @@ -5,7 +5,7 @@ export const $TimeseriesData = { properties: { name: { - type: 'string', + type: 'number', isRequired: true, }, data: { diff --git a/src/frontend/src/client/schemas/$UnidentifiedItem.ts b/src/frontend/src/client/schemas/$UnidentifiedItem.ts index f1be8b31f..a9ac8dc70 100644 --- a/src/frontend/src/client/schemas/$UnidentifiedItem.ts +++ b/src/frontend/src/client/schemas/$UnidentifiedItem.ts @@ -12,8 +12,8 @@ export const $UnidentifiedItem = { type: 'null', }], }, - league: { - type: 'string', + leagueId: { + type: 'number', isRequired: true, }, itemBaseTypeId: { @@ -47,20 +47,20 @@ export const $UnidentifiedItem = { type: 'null', }], }, - createdHoursSinceLaunch: { + nItems: { type: 'number', isRequired: true, }, - itemId: { - type: 'number', + aggregated: { + type: 'boolean', isRequired: true, }, - nItems: { + createdHoursSinceLaunch: { type: 'number', isRequired: true, }, - aggregated: { - type: 'boolean', + itemId: { + type: 'number', isRequired: true, }, }, diff --git a/src/frontend/src/client/schemas/$UnidentifiedItemCreate.ts b/src/frontend/src/client/schemas/$UnidentifiedItemCreate.ts index 0099c08b5..9a15f3eaf 100644 --- a/src/frontend/src/client/schemas/$UnidentifiedItemCreate.ts +++ b/src/frontend/src/client/schemas/$UnidentifiedItemCreate.ts @@ -12,8 +12,8 @@ export const $UnidentifiedItemCreate = { type: 'null', }], }, - league: { - type: 'string', + leagueId: { + type: 'number', isRequired: true, }, itemBaseTypeId: { @@ -47,6 +47,12 @@ export const $UnidentifiedItemCreate = { type: 'null', }], }, + nItems: { + type: 'number', + }, + aggregated: { + type: 'boolean', + }, createdHoursSinceLaunch: { type: 'number', isRequired: true, diff --git a/src/frontend/src/client/services/CurrencysService.ts b/src/frontend/src/client/services/CurrencysService.ts index 067a54090..5f2df8b92 100644 --- a/src/frontend/src/client/services/CurrencysService.ts +++ b/src/frontend/src/client/services/CurrencysService.ts @@ -4,6 +4,7 @@ /* eslint-disable */ import type { Currency } from '../models/Currency'; import type { CurrencyCreate } from '../models/CurrencyCreate'; +import type { CurrencyQuery } from '../models/CurrencyQuery'; import type { CurrencyUpdate } from '../models/CurrencyUpdate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; @@ -33,59 +34,6 @@ export class CurrencysService { }, }); } - /** - * Update Currency - * Update a currency by key and value for "currencyId". - * - * Returns the updated currency. - * @returns Currency Successful Response - * @throws ApiError - */ - public static updateCurrency({ - currencyId, - requestBody, - }: { - currencyId: number, - requestBody: CurrencyUpdate, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/api_v1/currency/{currencyId}', - path: { - 'currencyId': currencyId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - /** - * Delete Currency - * Delete a currency by key and value for "currencyId". - * - * Returns a message indicating the currency was deleted. - * Always deletes one currency. - * @returns string Successful Response - * @throws ApiError - */ - public static deleteCurrency({ - currencyId, - }: { - currencyId: number, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/api_v1/currency/{currencyId}', - path: { - 'currencyId': currencyId, - }, - errors: { - 422: `Validation Error`, - }, - }); - } /** * Get All Currencies * Get all currencies. @@ -147,9 +95,62 @@ export class CurrencysService { }, }); } + /** + * Update Currency + * Update a currency by key and value for "currencyId". + * + * Returns the updated currency. + * @returns Currency Successful Response + * @throws ApiError + */ + public static updateCurrency({ + currencyId, + requestBody, + }: { + currencyId: number, + requestBody: CurrencyUpdate, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/api_v1/currency/', + query: { + 'currencyId': currencyId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Currency + * Delete a currency by key and value for "currencyId". + * + * Returns a message indicating the currency was deleted. + * Always deletes one currency. + * @returns string Successful Response + * @throws ApiError + */ + public static deleteCurrency({ + currencyId, + }: { + currencyId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/api_v1/currency/', + query: { + 'currencyId': currencyId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } /** * Get Latest Currency Id - * Get the latest currencyId + * Get the latest currencyId, returns 1 if table is empty * * Can only be used safely on an empty table or directly after an insertion. * @returns number Successful Response @@ -161,4 +162,49 @@ export class CurrencysService { url: '/api/api_v1/currency/latest_currency_id/', }); } + /** + * Get Latest Hour + * Return -1 if database is empty + * @returns number Successful Response + * @throws ApiError + */ + public static getLatestHour(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/currency/latest_hour/', + }); + } + /** + * Get Latest Currencies + * Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + * @returns Currency Successful Response + * @throws ApiError + */ + public static getLatestCurrencies(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/currency/latest_currencies/', + }); + } + /** + * Get Currency From Query + * Returns a list of currencies that match any of the queries + * @returns Currency Successful Response + * @throws ApiError + */ + public static getCurrencyFromQuery({ + requestBody, + }: { + requestBody: Array, + }): CancelablePromise> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/api_v1/currency/from_query/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } } diff --git a/src/frontend/src/client/services/ItemBaseTypesService.ts b/src/frontend/src/client/services/ItemBaseTypesService.ts index 18e05dda0..8f215a086 100644 --- a/src/frontend/src/client/services/ItemBaseTypesService.ts +++ b/src/frontend/src/client/services/ItemBaseTypesService.ts @@ -33,59 +33,6 @@ export class ItemBaseTypesService { }, }); } - /** - * Update Item Base Type - * Update an item base type by key and value for "itemBaseTypeId". - * - * Returns the updated item base type. - * @returns ItemBaseType Successful Response - * @throws ApiError - */ - public static updateItemBaseType({ - itemBaseTypeId, - requestBody, - }: { - itemBaseTypeId: number, - requestBody: ItemBaseTypeUpdate, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/api/api_v1/itemBaseType/{itemBaseTypeId}', - path: { - 'itemBaseTypeId': itemBaseTypeId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 422: `Validation Error`, - }, - }); - } - /** - * Delete Item Base Type - * Delete an item base type by key and value for "itemBaseTypeId". - * - * Returns a message that the item base type was deleted successfully. - * Always deletes one item base type. - * @returns string Successful Response - * @throws ApiError - */ - public static deleteItemBaseType({ - itemBaseTypeId, - }: { - itemBaseTypeId: number, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/api_v1/itemBaseType/{itemBaseTypeId}', - path: { - 'itemBaseTypeId': itemBaseTypeId, - }, - errors: { - 422: `Validation Error`, - }, - }); - } /** * Get All Item Base Types * Get all item base types. @@ -150,4 +97,57 @@ export class ItemBaseTypesService { }, }); } + /** + * Update Item Base Type + * Update an item base type by key and value for "itemBaseTypeId". + * + * Returns the updated item base type. + * @returns ItemBaseType Successful Response + * @throws ApiError + */ + public static updateItemBaseType({ + itemBaseTypeId, + requestBody, + }: { + itemBaseTypeId: number, + requestBody: ItemBaseTypeUpdate, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/api_v1/itemBaseType/', + query: { + 'itemBaseTypeId': itemBaseTypeId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Item Base Type + * Delete an item base type by key and value for "itemBaseTypeId". + * + * Returns a message that the item base type was deleted successfully. + * Always deletes one item base type. + * @returns string Successful Response + * @throws ApiError + */ + public static deleteItemBaseType({ + itemBaseTypeId, + }: { + itemBaseTypeId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/api_v1/itemBaseType/', + query: { + 'itemBaseTypeId': itemBaseTypeId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } } diff --git a/src/frontend/src/client/services/LatestCurrenciesService.ts b/src/frontend/src/client/services/LatestCurrenciesService.ts new file mode 100644 index 000000000..4b42c6e36 --- /dev/null +++ b/src/frontend/src/client/services/LatestCurrenciesService.ts @@ -0,0 +1,22 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Currency } from '../models/Currency'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class LatestCurrenciesService { + /** + * Get Latest Currencies + * Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + * @returns Currency Successful Response + * @throws ApiError + */ + public static getLatestCurrencies(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/currency/latest_currencies/', + }); + } +} diff --git a/src/frontend/src/client/services/LatestCurrencyIdService.ts b/src/frontend/src/client/services/LatestCurrencyIdService.ts index 5df88f8d8..b46df99c0 100644 --- a/src/frontend/src/client/services/LatestCurrencyIdService.ts +++ b/src/frontend/src/client/services/LatestCurrencyIdService.ts @@ -8,7 +8,7 @@ import { request as __request } from '../core/request'; export class LatestCurrencyIdService { /** * Get Latest Currency Id - * Get the latest currencyId + * Get the latest currencyId, returns 1 if table is empty * * Can only be used safely on an empty table or directly after an insertion. * @returns number Successful Response diff --git a/src/frontend/src/client/services/LatestHourService.ts b/src/frontend/src/client/services/LatestHourService.ts new file mode 100644 index 000000000..25abb44e7 --- /dev/null +++ b/src/frontend/src/client/services/LatestHourService.ts @@ -0,0 +1,21 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class LatestHourService { + /** + * Get Latest Hour + * Return -1 if database is empty + * @returns number Successful Response + * @throws ApiError + */ + public static getLatestHour(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/currency/latest_hour/', + }); + } +} diff --git a/src/frontend/src/client/services/LeaguesService.ts b/src/frontend/src/client/services/LeaguesService.ts new file mode 100644 index 000000000..f8d18660f --- /dev/null +++ b/src/frontend/src/client/services/LeaguesService.ts @@ -0,0 +1,139 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { League } from '../models/League'; +import type { LeagueCreate } from '../models/LeagueCreate'; +import type { LeagueUpdate } from '../models/LeagueUpdate'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class LeaguesService { + /** + * Get League + * Get league by key and value for "leagueId". + * + * Always returns one league. + * @returns League Successful Response + * @throws ApiError + */ + public static getLeague({ + leagueId, + }: { + leagueId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/league/{leagueId}', + path: { + 'leagueId': leagueId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Update Modifier + * Update a league by key and value for "leagueId" + * + * Returns the updated league. + * @returns League Successful Response + * @throws ApiError + */ + public static updateModifier({ + leagueId, + requestBody, + }: { + leagueId: number, + requestBody: LeagueUpdate, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/api/api_v1/league/{leagueId}', + path: { + 'leagueId': leagueId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get All Leagues + * Get all leagues. + * + * Returns a list of all leagues. + * @returns any Successful Response + * @throws ApiError + */ + public static getAllLeagues({ + limit, + skip, + sortKey, + sortMethod, + }: { + limit?: (number | null), + skip?: (number | null), + sortKey?: (string | null), + sortMethod?: ('asc' | 'desc' | null), + }): CancelablePromise<(League | Array)> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/league/', + query: { + 'limit': limit, + 'skip': skip, + 'sort_key': sortKey, + 'sort_method': sortMethod, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Create League + * Create one or a list of new leagues. + * + * Returns the created league or list of leagues. + * @returns any Successful Response + * @throws ApiError + */ + public static createLeague({ + requestBody, + returnNothing, + }: { + requestBody: (LeagueCreate | Array), + returnNothing?: (boolean | null), + }): CancelablePromise<(LeagueCreate | Array | null)> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/api_v1/league/', + query: { + 'return_nothing': returnNothing, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Active Leagues + * Get leagues that are still valid/active + * + * Always returns a list, but it may be empty. + * @returns League Successful Response + * @throws ApiError + */ + public static getActiveLeagues(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/league/active_league/', + }); + } +} diff --git a/src/frontend/src/client/services/ModifiersService.ts b/src/frontend/src/client/services/ModifiersService.ts index 840e2ada3..97223d5c6 100644 --- a/src/frontend/src/client/services/ModifiersService.ts +++ b/src/frontend/src/client/services/ModifiersService.ts @@ -37,36 +37,6 @@ export class ModifiersService { }, }); } - /** - * Delete Modifier - * Delete a modifier by key and value for "modifierId" - * - * Returns a message that the modifier was deleted. - * Always deletes one modifier. - * @returns string Successful Response - * @throws ApiError - */ - public static deleteModifier({ - modifierId, - position, - }: { - modifierId: number, - position: number, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/api_v1/modifier/{modifierId}', - path: { - 'modifierId': modifierId, - }, - query: { - 'position': position, - }, - errors: { - 422: `Validation Error`, - }, - }); - } /** * Get All Modifiers * Get all modifiers. @@ -130,9 +100,7 @@ export class ModifiersService { } /** * Update Modifier - * Update a modifier by key and value for "modifierId" - * - * Dominant key is "modifierId". + * Update a modifier by key and value for "modifierId" and "position" * * Returns the updated modifier. * @returns Modifier Successful Response @@ -161,6 +129,34 @@ export class ModifiersService { }, }); } + /** + * Delete Modifier + * Delete a modifier by key and value for "modifierId" + * + * Returns a message that the modifier was deleted. + * Always deletes one modifier. + * @returns string Successful Response + * @throws ApiError + */ + public static deleteModifier({ + modifierId, + position, + }: { + modifierId: number, + position: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/api/api_v1/modifier/', + query: { + 'modifierId': modifierId, + 'position': position, + }, + errors: { + 422: `Validation Error`, + }, + }); + } /** * Get Grouped Modifier By Effect * Get all grouped modifiers by effect. diff --git a/src/frontend/src/client/services/UnidentifiedItemsService.ts b/src/frontend/src/client/services/UnidentifiedItemsService.ts index 48665ea49..456d84b63 100644 --- a/src/frontend/src/client/services/UnidentifiedItemsService.ts +++ b/src/frontend/src/client/services/UnidentifiedItemsService.ts @@ -83,4 +83,40 @@ export class UnidentifiedItemsService { }, }); } + /** + * Get Non Aggregated + * Get the non aggregated unidentified items for the last 10 recorded hours + * (not necessarily the last 10 hours) + * @returns UnidentifiedItem Successful Response + * @throws ApiError + */ + public static getNonAggregated(): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/unidentifiedItem/non_aggregated/', + }); + } + /** + * Add Aggregated + * Deletes the non aggregated unidentified items for the last 10 recorded hours + * (not necessarily the last 10 hours) + * And inserts the new items + * @returns UnidentifiedItem Successful Response + * @throws ApiError + */ + public static addAggregated({ + requestBody, + }: { + requestBody: Array, + }): CancelablePromise> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/api_v1/unidentifiedItem/add_aggregated/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } } From d36e38de4969ab25eb9f2307da83e0c85c44c479 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Sun, 12 Jul 2026 09:55:02 -0230 Subject: [PATCH 10/15] #824 Restructured frontend using leagues from database --- src/frontend/.env | 3 - .../Common/DateDaysHoursSinceLaunchStats.tsx | 58 +++++++---- .../Common/MultiSelectButtonGrid.tsx | 95 +++++++++--------- .../src/components/Common/QueryButtons.tsx | 8 +- .../src/components/Graph/CustomTooltip.tsx | 4 +- .../src/components/Graph/GraphComponent.tsx | 17 ++-- .../src/components/Input/LeagueInput.tsx | 26 +++-- src/frontend/src/config.tsx | 7 +- .../hooks/getData/getBaseTypeCategories.tsx | 96 +++++++++---------- src/frontend/src/hooks/getData/getLeagues.tsx | 18 ++++ .../hooks/graphing/processPlottingData.tsx | 53 +++++----- src/frontend/src/hooks/graphing/utils.tsx | 52 +++++++--- src/frontend/src/routes/_layout/index.tsx | 74 +++++++++----- src/frontend/src/schemas/Datum.tsx | 6 +- src/frontend/src/store/GraphInputStore.tsx | 21 +++- .../src/store/LeagueLaunchStatsStore.tsx | 18 ++++ src/frontend/src/store/StateInterface.tsx | 13 +++ src/frontend/src/utils.ts | 29 +----- 18 files changed, 366 insertions(+), 232 deletions(-) create mode 100644 src/frontend/src/hooks/getData/getLeagues.tsx create mode 100644 src/frontend/src/store/LeagueLaunchStatsStore.tsx diff --git a/src/frontend/.env b/src/frontend/.env index bf5722493..a54216fe7 100644 --- a/src/frontend/.env +++ b/src/frontend/.env @@ -1,6 +1,3 @@ VITE_API_URL=http://localhost:8000 # This is the URL for the FastAPI backend -VITE_APP_DEFAULT_LEAGUES="Mirage|Keepers|Mercenaries|Phrecia" # This is the default leagues, the first one is the current league to show on the frontend -VITE_APP_ADDITIONAL_LEAGUES="Hardcore Mirage|Hardcore Keepers|Hardcore Mercenaries|Hardcore Phrecia" # NB: Separate by '|' for each league VITE_APP_TURNSTILE_SITE_KEY=1x00000000000000000000AA # This is the site key for the Turnstile captcha -VITE_APP_LEAGUE_LAUNCH_TIME=2026-03-06T19:00:00Z # ISO 8601 format. Round backwards to whole hour number VITE_APP_PLOTTING_WINDOW_HOURS=336 diff --git a/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx b/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx index bd9737ea4..d26231257 100644 --- a/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx +++ b/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx @@ -4,42 +4,68 @@ import { useEffect, useState } from "react"; import { getHoursSinceLaunch, formatHoursSinceLaunch, - LEAGUE_LAUNCH_DATETIME, } from "../../hooks/graphing/utils"; -import { DEFAULT_LEAGUES } from "../../config"; import { setupHourlyUpdate } from "../../utils"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; const DateDaysHoursSinceLaunchStats = (props: StatProps) => { - const defaultLeague = DEFAULT_LEAGUES[0]; + const [currentTime, setCurrentTime] = useState(new Date()); + const { league, leagueLaunch, hoursSinceLaunch, setHoursSinceLaunch } = + useLeagueLaunchStats(); + useEffect(() => { + return setupHourlyUpdate(setCurrentTime); + }, []); - const leagueLaunchDay = LEAGUE_LAUNCH_DATETIME.getDate(); - const leagueLaunchMonth = LEAGUE_LAUNCH_DATETIME.toLocaleString("default", { + const leagueLaunchDay = leagueLaunch.getDate(); + const leagueLaunchMonth = leagueLaunch.toLocaleString("default", { month: "short", }); + const leagueLaunchYear = leagueLaunch.toLocaleString("default", { + year: "numeric", + }); - const [currentTime, setCurrentTime] = useState(new Date()); const currentDay = currentTime.getDate(); const currentMonth = currentTime.toLocaleString("default", { month: "short", }); + const currentYear = currentTime.toLocaleString("default", { + year: "numeric", + }); - const hoursSinceLaunch = getHoursSinceLaunch(currentTime); - const daysHoursSinceLaunchFormat = formatHoursSinceLaunch(hoursSinceLaunch); - const [sinceLaunchDays, sinceLaunchHours] = - daysHoursSinceLaunchFormat.split("T"); - + const curHoursSinceLaunch = getHoursSinceLaunch(currentTime, leagueLaunch); + const [localHoursSinceLaunch, setLocalHoursSinceLaunch] = + useState(curHoursSinceLaunch); useEffect(() => { - return setupHourlyUpdate(setCurrentTime); - }, []); + console.log(currentTime, leagueLaunch); + const curHoursSinceLaunch = getHoursSinceLaunch(currentTime, leagueLaunch); + setLocalHoursSinceLaunch(hoursSinceLaunch); + if (curHoursSinceLaunch !== hoursSinceLaunch) { + setHoursSinceLaunch(curHoursSinceLaunch); + } + }, [ + leagueLaunch, + hoursSinceLaunch, + setHoursSinceLaunch, + currentTime, + localHoursSinceLaunch, + ]); + + const daysHoursSinceLaunchFormat = formatHoursSinceLaunch( + localHoursSinceLaunch, + ); + const [yearsDays, sinceLaunchHours] = daysHoursSinceLaunchFormat.split("T"); + const [sinceLaunchYears, sinceLaunchDays] = yearsDays.split("Y"); return ( - {sinceLaunchDays} days and {sinceLaunchHours} hours since{" "} - {defaultLeague} launched + {sinceLaunchYears !== "0" ? `${sinceLaunchYears} years, ` : ""} + {sinceLaunchDays} days and {sinceLaunchHours} hours since {league.name}{" "} + launched - {leagueLaunchMonth} {leagueLaunchDay} - {currentMonth} {currentDay} + {leagueLaunchMonth} {leagueLaunchDay} {leagueLaunchYear} -{" "} + {currentMonth} {currentDay} {currentYear} ); diff --git a/src/frontend/src/components/Common/MultiSelectButtonGrid.tsx b/src/frontend/src/components/Common/MultiSelectButtonGrid.tsx index 5bd7abf79..35501aea9 100644 --- a/src/frontend/src/components/Common/MultiSelectButtonGrid.tsx +++ b/src/frontend/src/components/Common/MultiSelectButtonGrid.tsx @@ -1,6 +1,4 @@ -import { - Box, Flex, FlexProps -} from "@chakra-ui/layout"; +import { Box, Flex, FlexProps } from "@chakra-ui/layout"; import { Button } from "@chakra-ui/react"; import { capitalizeFirstLetter } from "../../hooks/utils"; import { TextWithUnderline } from "../Text/TextWithUnderline"; @@ -24,12 +22,18 @@ const MultiSelectButtonGrid = (props: MultiSelectProps) => { let defaultSelectedOptionsBoolean: boolean[]; if (defaultSelectedOptions !== undefined) { - defaultSelectedOptionsBoolean = options.map((val) => defaultSelectedOptions.includes(val)); + defaultSelectedOptionsBoolean = options.map((val) => + defaultSelectedOptions.includes(val), + ); } else { - defaultSelectedOptionsBoolean = new Array(numberOfOptions).fill(false); + defaultSelectedOptionsBoolean = new Array(numberOfOptions).fill( + false, + ); } - const [selectedOptions, setSelectedOptions] = useState(defaultSelectedOptionsBoolean); + const [selectedOptions, setSelectedOptions] = useState( + defaultSelectedOptionsBoolean, + ); const clearClicked = useGraphInputStore((state) => state.clearClicked); @@ -38,47 +42,48 @@ const MultiSelectButtonGrid = (props: MultiSelectProps) => { props.onClearClick(); setSelectedOptions(defaultSelectedOptionsBoolean); } - }, [clearClicked]) + }, [clearClicked, defaultSelectedOptionsBoolean, props]); const buttons = options.map((val, idx) => { - return - - - }) - return - - - {...buttons} + return ( + + + + ); + }); + return ( + + + + {...buttons} + - -} + ); +}; export default MultiSelectButtonGrid; diff --git a/src/frontend/src/components/Common/QueryButtons.tsx b/src/frontend/src/components/Common/QueryButtons.tsx index f8bf3d5b8..96355461a 100644 --- a/src/frontend/src/components/Common/QueryButtons.tsx +++ b/src/frontend/src/components/Common/QueryButtons.tsx @@ -9,6 +9,7 @@ import { import { useErrorStore } from "../../store/ErrorStore"; import { ErrorMessage } from "../../components/Input/StandardLayoutInput/ErrorMessage"; import { getOptimizedPlotQuery } from "../../hooks/graphing/utils"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; const QueryButtons = (props: FlexProps) => { const { setExpandedGraphInputFilters } = useExpandedComponentStore(); @@ -20,6 +21,7 @@ const QueryButtons = (props: FlexProps) => { modifiersUnidentifiedError, baseSpecDoesNotMatchError, } = useErrorStore(); + const { hoursSinceLaunch } = useLeagueLaunchStats(); const { stateHash, fetchStatus, setHashFromStore, setPlotQuery } = useGraphInputStore(); const isFetching = fetchStatus === "fetching"; @@ -42,13 +44,13 @@ const QueryButtons = (props: FlexProps) => { }, 10); }; - const handlePlotQuery = () => { + const handlePlotQuery = (hoursSinceLaunch: number) => { if (isFetching) return; if (stateHash) return; const leagueValid = checkGraphQueryLeagueInput(); const modifierValid = checkGraphQueryModifierInput(); if (!leagueValid || !modifierValid) return; - const plotQuery = getOptimizedPlotQuery(); + const plotQuery = getOptimizedPlotQuery(hoursSinceLaunch); if (plotQuery === undefined) return; setPlotQuery(plotQuery); useGraphInputStore.getState().setQueryClicked(); @@ -89,7 +91,7 @@ const QueryButtons = (props: FlexProps) => { borderColor="ui.grey" width={["inputSizes.defaultBox", "inputSizes.lgBox"]} maxW="98vw" - onClick={handlePlotQuery} + onClick={() => handlePlotQuery(hoursSinceLaunch)} disabled={isFetching} opacity={isFetching ? 0.5 : 1} cursor={isFetching ? "not-allowed" : "pointer"} diff --git a/src/frontend/src/components/Graph/CustomTooltip.tsx b/src/frontend/src/components/Graph/CustomTooltip.tsx index 75ca4458a..f9514fcf8 100644 --- a/src/frontend/src/components/Graph/CustomTooltip.tsx +++ b/src/frontend/src/components/Graph/CustomTooltip.tsx @@ -12,6 +12,7 @@ import { capitalizeFirstLetter } from "../../hooks/utils"; import { BiError } from "react-icons/bi"; import { getHoursSinceLaunch } from "../../hooks/graphing/utils"; import { setupHourlyUpdate } from "../../utils"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; interface CustomTooltipProps extends TooltipContentProps { upperBoundry: number; @@ -21,10 +22,11 @@ interface CustomTooltipProps extends TooltipContentProps { export const CustomTooltip = (props: CustomTooltipProps) => { const [currentTime, setCurrentTime] = useState(new Date()); + const { leagueLaunch } = useLeagueLaunchStats(); let hoursAgo = -1; if (typeof props.label == "number") { - hoursAgo = getHoursSinceLaunch(currentTime) - props.label; + hoursAgo = getHoursSinceLaunch(currentTime, leagueLaunch) - props.label; } const daysToDisplay = Math.floor(hoursAgo / 24); diff --git a/src/frontend/src/components/Graph/GraphComponent.tsx b/src/frontend/src/components/Graph/GraphComponent.tsx index 8ab205acd..386736616 100644 --- a/src/frontend/src/components/Graph/GraphComponent.tsx +++ b/src/frontend/src/components/Graph/GraphComponent.tsx @@ -33,7 +33,7 @@ import PlotCustomizationButtons from "../Common/PlotCustomizationButtons"; * and a graph if there has been */ function GraphComponent(props: BoxProps) { - const { plotQuery, leagues } = useGraphInputStore(); + const { plotQuery, choosableLeagues } = useGraphInputStore(); const { result: plotData, mostCommonCurrencyUsed, @@ -51,7 +51,10 @@ function GraphComponent(props: BoxProps) { const showSecondary = usePlotSettingsStore((state) => state.showSecondary); if (error || plotData == undefined) return; - const fetchedLeagues = plotData.data.map((val) => val.name); + const fetchedLeagueIds = plotData.data.map((val) => val.name); + const fetchedLeagues = choosableLeagues + .filter((league) => fetchedLeagueIds.includes(league.leagueId)) + .map((league) => league.name); const isLowConfidence = confidenceRating === "low"; const isMediumConfidence = confidenceRating === "medium"; @@ -171,13 +174,13 @@ function GraphComponent(props: BoxProps) { {plotData.data.map( (series, idx) => - leagues.includes(series.name) && ( + fetchedLeagueIds.includes(series.name) && ( - leagues.includes(series.name) && ( + fetchedLeagueIds.includes(series.name) && ( { - const { leagues, addLeague, removeLeague } = useGraphInputStore(); - - - const selectLeagueOptions: string[] = [...DEFAULT_LEAGUES, ...ADDITIONAL_LEAGUES] + const { league } = useLeagueLaunchStats(); + const { leagues, choosableLeagues, addLeague, removeLeague } = + useGraphInputStore(); + const selectLeagueOptions: string[] = choosableLeagues.map( + (league) => league.name, + ); + let defaultLeagues; + if (leagues.length > 0) { + defaultLeagues = leagues; + } else { + defaultLeagues = league ? [league.name] : undefined; + } return ( useGraphInputStore.setState({ leagues: DEFAULT_LEAGUES })} + onClearClick={() => + useGraphInputStore.setState({ leagues: defaultLeagues }) + } /> - ) + ); }; diff --git a/src/frontend/src/config.tsx b/src/frontend/src/config.tsx index 22b134a85..2f9e9bbba 100644 --- a/src/frontend/src/config.tsx +++ b/src/frontend/src/config.tsx @@ -2,8 +2,5 @@ export const VITE_API_URL = import.meta.env.VITE_API_URL; export const TURNSTILE_SITE_KEY = import.meta.env.VITE_APP_TURNSTILE_SITE_KEY; -export const LEAGUE_LAUNCH_TIME = import.meta.env.VITE_APP_LEAGUE_LAUNCH_TIME; -export const PLOTTING_WINDOW_HOURS = import.meta.env.VITE_APP_PLOTTING_WINDOW_HOURS; - -export const DEFAULT_LEAGUES = (import.meta.env.VITE_APP_DEFAULT_LEAGUES).split("|") as Array; -export const ADDITIONAL_LEAGUES = (import.meta.env.VITE_APP_ADDITIONAL_LEAGUES).split("|") as Array; +export const PLOTTING_WINDOW_HOURS = import.meta.env + .VITE_APP_PLOTTING_WINDOW_HOURS; diff --git a/src/frontend/src/hooks/getData/getBaseTypeCategories.tsx b/src/frontend/src/hooks/getData/getBaseTypeCategories.tsx index 202b845eb..86e030e0a 100644 --- a/src/frontend/src/hooks/getData/getBaseTypeCategories.tsx +++ b/src/frontend/src/hooks/getData/getBaseTypeCategories.tsx @@ -1,54 +1,50 @@ -import { useQueryClient } from "@tanstack/react-query"; +import { QueryClient } from "@tanstack/react-query"; import { ItemBaseTypesService, ItemBaseType } from "../../client"; // Fetches all item base type data and processes related unique item names -export const useGetItemBaseTypes = async () => { - const queryClient = useQueryClient(); - - try { - // Fetch and return item base types directly - const itemBaseTypes = await queryClient.fetchQuery({ - queryKey: ["baseTypeValues"], - queryFn: async () => { - const data = await ItemBaseTypesService.getAllItemBaseTypes({}); - return Array.isArray(data) ? data : [data]; - }, - }); - - const createItemNameArray = (itemBaseType: ItemBaseType[]): string[] => { - const reduceItemNameArray = ( - prev: string[] | undefined, - cur: string[] | undefined - ): string[] => { - prev = prev || []; - if (cur) { - const newItemNames = cur.filter((value) => !prev.includes(value)); - prev.push(...newItemNames); - } - return prev; - }; - - const arrayOfRelatedUniques = itemBaseType.map((baseType) => { - const relatedUniques = baseType.relatedUniques; - return relatedUniques ? relatedUniques.split("|") : []; - }); - - return arrayOfRelatedUniques.reduce(reduceItemNameArray, []); - }; - - const itemNames = createItemNameArray(itemBaseTypes); - - return { - itemBaseTypes, - itemNames, - }; - - } catch (error) { - console.error("Error fetching item base types:", error); - return { - itemBaseTypes: [], - itemNames: [], - }; - } +export const useGetItemBaseTypes = async (queryClient: QueryClient) => { + try { + // Fetch and return item base types directly + const itemBaseTypes = await queryClient.fetchQuery({ + queryKey: ["baseTypeValues"], + queryFn: async () => { + const data = await ItemBaseTypesService.getAllItemBaseTypes({}); + return Array.isArray(data) ? data : [data]; + }, + }); + + const createItemNameArray = (itemBaseType: ItemBaseType[]): string[] => { + const reduceItemNameArray = ( + prev: string[] | undefined, + cur: string[] | undefined, + ): string[] => { + prev = prev || []; + if (cur) { + const newItemNames = cur.filter((value) => !prev.includes(value)); + prev.push(...newItemNames); + } + return prev; + }; + + const arrayOfRelatedUniques = itemBaseType.map((baseType) => { + const relatedUniques = baseType.relatedUniques; + return relatedUniques ? relatedUniques.split("|") : []; + }); + + return arrayOfRelatedUniques.reduce(reduceItemNameArray, []); + }; + + const itemNames = createItemNameArray(itemBaseTypes); + + return { + itemBaseTypes, + itemNames, + }; + } catch (error) { + console.error("Error fetching item base types:", error); + return { + itemBaseTypes: [], + itemNames: [], + }; + } }; - diff --git a/src/frontend/src/hooks/getData/getLeagues.tsx b/src/frontend/src/hooks/getData/getLeagues.tsx new file mode 100644 index 000000000..fb0404910 --- /dev/null +++ b/src/frontend/src/hooks/getData/getLeagues.tsx @@ -0,0 +1,18 @@ +import { LeaguesService } from "../../client"; +import { QueryClient } from "@tanstack/react-query"; + +export const useGetLeagues = async (queryClient: QueryClient) => { + try { + const leagues = await queryClient.fetchQuery({ + queryKey: ["allModifiers"], + queryFn: async () => { + const data = await LeaguesService.getAllLeagues({}); + return Array.isArray(data) ? data : [data]; + }, + }); + return { leagues }; + } catch (error) { + console.log(error); + return { leagues: [] }; + } +}; diff --git a/src/frontend/src/hooks/graphing/processPlottingData.tsx b/src/frontend/src/hooks/graphing/processPlottingData.tsx index 8549e5831..09abac0c1 100644 --- a/src/frontend/src/hooks/graphing/processPlottingData.tsx +++ b/src/frontend/src/hooks/graphing/processPlottingData.tsx @@ -3,8 +3,9 @@ import { PlotQuery } from "../../client"; import { FilledPlotData } from "../../schemas/Datum"; import { useEffect } from "react"; import useCustomToast from "../useCustomToast"; -import { findWinsorUpperBound, getHoursSinceLaunch } from "./utils"; +import { findWinsorUpperBound } from "./utils"; import { PLOTTING_WINDOW_HOURS } from "../../config"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; /** * A hook that takes the current plot query and returns @@ -22,6 +23,7 @@ function useGetPlotData(plotQuery: PlotQuery): { } { const { plotData, fetchStatus, isFetched, isError, error } = usePostPlottingData(plotQuery); + const { hoursSinceLaunch } = useLeagueLaunchStats(); const showToast = useCustomToast(); useEffect(() => { if (isError && isFetched) { @@ -33,39 +35,38 @@ function useGetPlotData(plotQuery: PlotQuery): { if (plotData !== undefined) { const mostCommonCurrencyUsed = plotData.mostCommonCurrencyUsed; - const currentHour = getHoursSinceLaunch(new Date()); - const firstHour = currentHour - PLOTTING_WINDOW_HOURS; + const firstHour = hoursSinceLaunch - PLOTTING_WINDOW_HOURS; const filledPlotData: FilledPlotData = { mostCommonCurrencyUsed: mostCommonCurrencyUsed, - data: plotData.data.map( - (val) => ( - { - confidenceRating: val.confidenceRating, - name: val.name, - data: [] - })) + data: plotData.data.map((val) => ({ + confidenceRating: val.confidenceRating, + name: val.name, + data: [], + })), }; - const currentIdxArray = plotData.data.reduce((prev) => [...prev, 0], [] as number[]); + const currentIdxArray = plotData.data.reduce( + (prev) => [...prev, 0], + [] as number[], + ); let currentIdx: number; // Makes a filled plot data object, meaning that every hour has an entry // Missing entries from the original plot data is replaced by a "null" entry - for (let hour = firstHour; hour <= currentHour; hour++) { + for (let hour = firstHour; hour <= hoursSinceLaunch; hour++) { for (let seriesIdx = 0; seriesIdx < currentIdxArray.length; seriesIdx++) { currentIdx = currentIdxArray[seriesIdx]; if ( plotData.data[seriesIdx].data.length <= currentIdx || plotData.data[seriesIdx].data[currentIdx].hoursSinceLaunch !== hour ) { - filledPlotData.data[seriesIdx].data.push( - { - hoursSinceLaunch: hour, - valueInChaos: null, - valueInMostCommonCurrencyUsed: null, - confidence: null - }) + filledPlotData.data[seriesIdx].data.push({ + hoursSinceLaunch: hour, + valueInChaos: null, + valueInMostCommonCurrencyUsed: null, + confidence: null, + }); } else { filledPlotData.data[seriesIdx].data.push( - plotData.data[seriesIdx].data[currentIdx] + plotData.data[seriesIdx].data[currentIdx], ); currentIdxArray[seriesIdx] = currentIdx + 1; } @@ -74,14 +75,14 @@ function useGetPlotData(plotQuery: PlotQuery): { const upperBoundryChaos = findWinsorUpperBound( plotData.data[0].data.reduce( (prev, cur) => [...prev, cur.valueInChaos], - [] as number[] - ) + [] as number[], + ), ); const upperBoundrySecondary = findWinsorUpperBound( plotData.data[0].data.reduce( (prev, cur) => [...prev, cur.valueInMostCommonCurrencyUsed], - [] as number[] - ) + [] as number[], + ), ); const confidenceRating = plotData.data[0].confidenceRating; return { @@ -91,8 +92,8 @@ function useGetPlotData(plotQuery: PlotQuery): { upperBoundryChaos, upperBoundrySecondary, fetchStatus, - error - } + error, + }; } return { result: undefined, diff --git a/src/frontend/src/hooks/graphing/utils.tsx b/src/frontend/src/hooks/graphing/utils.tsx index aa08cfd04..5bf714f25 100644 --- a/src/frontend/src/hooks/graphing/utils.tsx +++ b/src/frontend/src/hooks/graphing/utils.tsx @@ -1,4 +1,4 @@ -import { PlotQuery } from "../../client"; +import { League, PlotQuery } from "../../client"; import { useErrorStore } from "../../store/ErrorStore"; import { useGraphInputStore } from "../../store/GraphInputStore"; import { @@ -6,9 +6,31 @@ import { GraphInputState, ModifierLimitationState, } from "../../store/StateInterface"; -import { LEAGUE_LAUNCH_TIME, PLOTTING_WINDOW_HOURS } from "../../config"; +import { PLOTTING_WINDOW_HOURS } from "../../config"; -export const LEAGUE_LAUNCH_DATETIME = new Date(LEAGUE_LAUNCH_TIME); +export const getCurrentLeague = (choosableLeagues: League | League[]) => { + choosableLeagues = Array.isArray(choosableLeagues) + ? choosableLeagues + : [choosableLeagues]; + const now = Date.now(); + const currentLeague = choosableLeagues.reduce( + (latest, league) => { + const validFrom = Date.parse(league.validFrom); + const validTo = league.validTo + ? Date.parse(league.validTo) + : Number.POSITIVE_INFINITY; + if (validFrom > now || now > validTo) { + return latest; + } + if (!latest || validFrom > Date.parse(latest.validFrom)) { + return league; + } + return latest; + }, + undefined, + ); + return currentLeague; +}; const calcMean = (values: number[]) => { const sumValues = values.reduce((prev, cur) => prev + cur, 0); @@ -31,17 +53,22 @@ export const findWinsorUpperBound = (values: number[]) => { }; export function formatHoursSinceLaunch(hoursSinceLaunch: number): string { - const daysSinceLaunch = Math.floor(hoursSinceLaunch / 24); + let daysSinceLaunch = Math.floor(hoursSinceLaunch / 24); + const yearsSinceLaunch = Math.floor(daysSinceLaunch / 365); + daysSinceLaunch = daysSinceLaunch - yearsSinceLaunch * 365; const remainder = hoursSinceLaunch % 24; // Combine the date and time into the desired format - return `${daysSinceLaunch}T${remainder}`; + return `${yearsSinceLaunch}Y${daysSinceLaunch}T${remainder}`; } -export const getHoursSinceLaunch = (currentTime: Date): number => { +export const getHoursSinceLaunch = ( + currentTime: Date, + leagueLaunch: Date, +): number => { const getCurrentTimeDate = currentTime.getTime(); const hoursSinceLaunch = Math.floor( - (getCurrentTimeDate - LEAGUE_LAUNCH_DATETIME.getTime()) / (1000 * 3600), + (getCurrentTimeDate - leagueLaunch.getTime()) / (1000 * 3600), ); return hoursSinceLaunch; }; @@ -152,7 +179,9 @@ export const updateNumericalRoll = ( return { wantedModifierExtended: updatedModifiersExtended }; }; -export const getOptimizedPlotQuery = (): PlotQuery | undefined => { +export const getOptimizedPlotQuery = ( + hoursSinceLaunch: number, +): PlotQuery | undefined => { // currently always runs, needs to be in if check // when Non-unique rarity is possible const state = useGraphInputStore.getState(); @@ -238,13 +267,14 @@ export const getOptimizedPlotQuery = (): PlotQuery | undefined => { modifierId: wantedMoidifierExtended.modifierId, modifierLimitations: wantedMoidifierExtended.modifierLimitations, })); - const currentTime = new Date(); - const end = getHoursSinceLaunch(currentTime); + const end = hoursSinceLaunch; const window = PLOTTING_WINDOW_HOURS; const start = end - window; return { - league: state.leagues, + leagueId: state.choosableLeagues + .filter((league) => state.leagues.includes(league.name)) + .map((league) => league.leagueId), itemSpecifications: itemSpec, baseSpecifications: baseSpec, wantedModifiers: wantedModifier, diff --git a/src/frontend/src/routes/_layout/index.tsx b/src/frontend/src/routes/_layout/index.tsx index 64d366d0f..ba478d973 100644 --- a/src/frontend/src/routes/_layout/index.tsx +++ b/src/frontend/src/routes/_layout/index.tsx @@ -1,47 +1,69 @@ -import { useEffect, useRef } from 'react' -import { createFileRoute } from '@tanstack/react-router' +import { useEffect, useRef } from "react"; +import { createFileRoute } from "@tanstack/react-router"; -import { MainPage } from '../../components/Common/MainPage' -import { useGraphInputStore } from '../../store/GraphInputStore' -import { useGetGroupedModifiers } from '../../hooks/getData/prefetchGroupedModifiers' -import { useGetItemBaseTypes } from '../../hooks/getData/getBaseTypeCategories' -import { validateAndSetSearchParams } from '../../utils' +import { MainPage } from "../../components/Common/MainPage"; +import { useGraphInputStore } from "../../store/GraphInputStore"; +import { useGetGroupedModifiers } from "../../hooks/getData/prefetchGroupedModifiers"; +import { useGetItemBaseTypes } from "../../hooks/getData/getBaseTypeCategories"; +import { useGetLeagues } from "../../hooks/getData/getLeagues"; +import { validateAndSetSearchParams } from "../../utils"; +import { getCurrentLeague } from "../../hooks/graphing/utils"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; +import { useQueryClient } from "@tanstack/react-query"; export const Route = createFileRoute("/_layout/")({ beforeLoad: async () => { - const searchParams = new URLSearchParams(location.hash.slice(1)) - validateAndSetSearchParams(searchParams) + // const searchParams = new URLSearchParams(location.hash.slice(1)); + validateAndSetSearchParams(); }, component: Index, -}) +}); // Index Component - This component is the main component for the index route. function Index() { const { setChoosableModifiers, setChoosableItemBaseType, + addLeague, + setChoosableLeagues, setChoosableItemNames, getStoreFromHash, - } = useGraphInputStore() - const requestGroupedModifiers = useGetGroupedModifiers() - const requestItemBaseTypes = useGetItemBaseTypes() - const isFetched = useRef(false) + setHashFromStore, + } = useGraphInputStore(); + const { setLeague, setLeagueLaunch } = useLeagueLaunchStats(); + const queryClient = useQueryClient(); + const requestGroupedModifiers = useGetGroupedModifiers(); + const requestItemBaseTypes = useGetItemBaseTypes(queryClient); + const requestLeagues = useGetLeagues(queryClient); + const isFetched = useRef(false); useEffect(() => { if (!isFetched.current) { const fetchData = async () => { - const groupedModifiers = await requestGroupedModifiers - const itemBaseTypes = await requestItemBaseTypes + const groupedModifiers = await requestGroupedModifiers; + const itemBaseTypes = await requestItemBaseTypes; + const leagues = await requestLeagues; if (groupedModifiers) { - setChoosableModifiers(groupedModifiers.groupedModifiers) + setChoosableModifiers(groupedModifiers.groupedModifiers); } if (itemBaseTypes) { - setChoosableItemBaseType(itemBaseTypes.itemBaseTypes) - setChoosableItemNames(itemBaseTypes.itemNames) + setChoosableItemBaseType(itemBaseTypes.itemBaseTypes); + setChoosableItemNames(itemBaseTypes.itemNames); } - isFetched.current = true - } - fetchData() + if (leagues) { + setChoosableLeagues(leagues.leagues); + const currentLeague = getCurrentLeague(leagues.leagues); + if (currentLeague !== undefined) { + const leagueLaunch = new Date(currentLeague.validFrom); + setLeagueLaunch(leagueLaunch); + setLeague(currentLeague); + addLeague(currentLeague.name); + setHashFromStore(); + } + } + isFetched.current = true; + }; + fetchData(); } }, [ requestGroupedModifiers, @@ -50,6 +72,10 @@ function Index() { setChoosableItemBaseType, setChoosableItemNames, getStoreFromHash, - ]) - return + requestLeagues, + setChoosableLeagues, + setLeagueLaunch, + setLeague, + ]); + return ; } diff --git a/src/frontend/src/schemas/Datum.tsx b/src/frontend/src/schemas/Datum.tsx index d79c11fa0..5e05b5360 100644 --- a/src/frontend/src/schemas/Datum.tsx +++ b/src/frontend/src/schemas/Datum.tsx @@ -6,12 +6,12 @@ type Datum = { valueInChaos: number | null; valueInMostCommonCurrencyUsed: number | null; confidence: "low" | "medium" | "high" | null; -} +}; type TimeseriesData = { - name: string; + name: number; data: Array; - confidenceRating: 'low' | 'medium' | 'high'; + confidenceRating: "low" | "medium" | "high"; }; export type FilledPlotData = { diff --git a/src/frontend/src/store/GraphInputStore.tsx b/src/frontend/src/store/GraphInputStore.tsx index fad2acf9c..445f4299f 100644 --- a/src/frontend/src/store/GraphInputStore.tsx +++ b/src/frontend/src/store/GraphInputStore.tsx @@ -9,9 +9,13 @@ import { WantedModifier, WantedModifierExtended, } from "./StateInterface"; -import { GroupedModifierByEffect, ItemBaseType, PlotQuery } from "../client"; +import { + GroupedModifierByEffect, + ItemBaseType, + League, + PlotQuery, +} from "../client"; import { encodeHash, decodeHash } from "./utils"; -import { DEFAULT_LEAGUES } from "../config"; import { updateNumericalRoll } from "../hooks/graphing/utils"; // Graph Input Store - This store is used to store graph input data. @@ -26,7 +30,8 @@ export const useGraphInputStore = create((set) => ({ choosableItemBaseType: [], choosableItemNames: [], - leagues: [DEFAULT_LEAGUES[0]], + leagues: [], + choosableLeagues: [], itemName: undefined, itemSpec: undefined, @@ -35,7 +40,7 @@ export const useGraphInputStore = create((set) => ({ wantedModifierExtended: [], plotQuery: { - league: DEFAULT_LEAGUES, + leagueId: -1, wantedModifiers: [], }, @@ -192,7 +197,13 @@ export const useGraphInputStore = create((set) => ({ set(() => ({ leagues: [], })), - + setChoosableLeagues: (leagues: League | League[]) => + set(() => { + leagues = Array.isArray(leagues) ? leagues : [leagues]; + return { + choosableLeagues: leagues, + }; + }), setItemSpec: (itemSpec: ItemSpecState) => set(() => ({ itemSpec: itemSpec })), setItemSpecIdentified: (identified: boolean | undefined) => diff --git a/src/frontend/src/store/LeagueLaunchStatsStore.tsx b/src/frontend/src/store/LeagueLaunchStatsStore.tsx new file mode 100644 index 000000000..e108b74f2 --- /dev/null +++ b/src/frontend/src/store/LeagueLaunchStatsStore.tsx @@ -0,0 +1,18 @@ +import { create } from "zustand"; +import { LeagueLaunchStats } from "./StateInterface"; +import { League } from "../client"; + +export const useLeagueLaunchStats = create((set) => ({ + league: { + name: "Path of Exile", + validFrom: "2013-10-23T21:00:00Z", + leagueId: -1, + }, + leagueLaunch: new Date("2013-10-23T21:00:00Z"), + hoursSinceLaunch: Number.NEGATIVE_INFINITY, + setLeague: (league: League) => set(() => ({ league: league })), + setLeagueLaunch: (leagueLaunch: Date) => + set(() => ({ leagueLaunch: leagueLaunch })), + setHoursSinceLaunch: (hoursSinceLaunch: number) => + set(() => ({ hoursSinceLaunch: hoursSinceLaunch })), +})); diff --git a/src/frontend/src/store/StateInterface.tsx b/src/frontend/src/store/StateInterface.tsx index 2d6c2543d..545b8564a 100644 --- a/src/frontend/src/store/StateInterface.tsx +++ b/src/frontend/src/store/StateInterface.tsx @@ -1,10 +1,21 @@ import { GroupedModifierByEffect, ItemBaseType, + League, PlotQuery, TurnstileResponse, } from "../client"; +export interface LeagueLaunchStats { + league: League; + leagueLaunch: Date; + hoursSinceLaunch: number; + + setLeague: (league: League) => void; + setLeagueLaunch: (leagueLaunch: Date) => void; + setHoursSinceLaunch: (hoursSinceLaunch: number) => void; +} + export interface ChoosableModifiersExtended extends GroupedModifierByEffect { isNotChoosable?: boolean; } @@ -90,6 +101,7 @@ export interface GraphInputState { choosableItemNames: string[]; leagues: string[]; + choosableLeagues: League[]; itemName: string | undefined; itemSpec: ItemSpecState | undefined; @@ -119,6 +131,7 @@ export interface GraphInputState { addLeague: (league: string) => void; removeLeague: (league: string) => void; removeAllLeagues: () => void; + setChoosableLeagues: (leagues: League | League[]) => void; setItemName: (name: string | undefined) => void; diff --git a/src/frontend/src/utils.ts b/src/frontend/src/utils.ts index 3e28cbdcb..ffec83398 100644 --- a/src/frontend/src/utils.ts +++ b/src/frontend/src/utils.ts @@ -1,6 +1,5 @@ import type { ApiError } from "./client"; import { Toast } from "./hooks/useCustomToast"; -import { DEFAULT_LEAGUES } from "./config"; import { useGraphInputStore } from "./store/GraphInputStore"; export const emailPattern = { @@ -85,30 +84,8 @@ export const setupHourlyUpdate = (setCurrentTime: SetDateFunction) => { return () => clearTimeout(timeoutId); }; -const validateLeagues = (searchParams: URLSearchParams) => { - const leagues = searchParams.get("league"); - if (searchParams.size === 1 && leagues) { - if (leagues.length > 1 || !leagues.includes(DEFAULT_LEAGUES[0])) { - throw "default league not in simple url"; - } - } else if (!leagues || leagues.length === 0) { - throw "leagues not set in url"; - } -}; - -export const validateAndSetSearchParams = (searchParams: URLSearchParams) => { +export const validateAndSetSearchParams = () => { const graphState = useGraphInputStore.getState(); - try { - graphState.setHashFromStore(); - validateLeagues(searchParams); - } catch (error) { - const graphState = useGraphInputStore.getState(); - graphState.removeAllLeagues(); - graphState.addLeague(DEFAULT_LEAGUES[0]); - const searchParams = new URLSearchParams(); - searchParams.set("league", DEFAULT_LEAGUES[0]); - graphState.setHashFromStore(); - } finally { - graphState.setStateHash(undefined); - } + graphState.setHashFromStore(); + graphState.setStateHash(undefined); }; From 57c3bbe68b445cddda1f18399e0b0c3d4d2142f4 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Sun, 12 Jul 2026 11:02:34 -0230 Subject: [PATCH 11/15] #824 Removed league .env backend and fixed different hours per league --- src/.env | 13 +--- .../external_data_retrieval/config.py | 15 ---- .../data_retrieval/poe_api_handler.py | 2 +- .../external_data_retrieval/detectors/base.py | 1 - .../external_data_retrieval/main.py | 15 ++-- .../transform_currency_api_data.py | 16 ---- .../transform_poe_api_data.py | 75 ++++++++++++------- .../create_public_stashes_test_data/config.py | 13 ---- .../create_public_stashes_test_data/main.py | 9 +-- .../utils/data_deposit_test_data_creator.py | 10 +-- .../tests/scripts/test_with_sim_api.py | 58 +++++++------- .../data_retrieval_app/utils.py | 25 ++++--- src/backend_data_retrieval/prestart.sh | 4 - 13 files changed, 116 insertions(+), 140 deletions(-) diff --git a/src/.env b/src/.env index 3018f8397..a78e29dc6 100644 --- a/src/.env +++ b/src/.env @@ -18,7 +18,7 @@ DOCKER_IMAGE_FRONTEND=pom_frontend # Backend PRIVATIZE_API= -TESTING=True +TESTING= RATE_LIMIT= BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:5174,https://localhost,https://localhost:5173,https://localhost:5174" PROJECT_NAME="pathofmodifiers" @@ -35,20 +35,15 @@ SMTP_USER=noreply@pathofmodifiers.com SMTP_PASSWORD=changethis TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA -# League -CURRENT_SOFTCORE_LEAGUE="Mirage" -ALL_SOFTCORE_LEAGUES="Mirage|Keepers|Mercenaries|Phrecia" - -LEAGUE_LAUNCH_TIME=2026-03-06T19:00:00Z # ISO 8601 format. Round backwards to whole hour number # Data retrieval MANUAL_NEXT_CHANGE_ID=True -# Change ID Retrieved 02.09.2024: -NEXT_CHANGE_ID=2621291179-2592930469-2516326916-2790039644-2710714910 +# Change ID Retrieved 28.06.2026: +NEXT_CHANGE_ID=3176140940-3103957281-3029294684-3370770619-3261964453 POE_PUBLIC_STASHES_AUTH_TOKEN=changethis # Enables the public stashes creation script in data_ret/app/tests/scripts. # Set value "True" to activate: -DATA_RET_TEST_PUB_STASH_SIM_DATA_DEPOSIT_ENABLED=True +DATA_RET_TEST_PUB_STASH_SIM_DATA_DEPOSIT_ENABLED= # Postgres POSTGRES_PORT=5432 diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py index 5cd7323f9..59796e8e3 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/config.py @@ -1,5 +1,3 @@ -from datetime import datetime - from pydantic import ( HttpUrl, computed_field, @@ -27,12 +25,6 @@ def BACKEND_BASE_URL(self) -> HttpUrl: OATH_ACC_TOKEN_CONTACT_EMAIL: str FIRST_SUPERUSER: str FIRST_SUPERUSER_PASSWORD: str - CURRENT_SOFTCORE_LEAGUE: str - - @computed_field # type: ignore[prop-decorator] - @property - def CURRENT_HARDCORE_LEAGUE(self) -> str: - return f"Hardcore {self.CURRENT_SOFTCORE_LEAGUE}" POE_PUBLIC_STASHES_AUTH_TOKEN: str OAUTH_CLIENT_ID: str @@ -43,12 +35,5 @@ def CURRENT_HARDCORE_LEAGUE(self) -> str: MAX_TIME_PER_MINI_BATCH: int = 3 * 60 - LEAGUE_LAUNCH_TIME: str - - @computed_field # type: ignore[prop-decorator] - @property - def LEAGUE_LAUNCH_DATETIME_OBJECT(self) -> datetime: - return datetime.fromisoformat(self.LEAGUE_LAUNCH_TIME) - settings = Settings() # type: ignore diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py index 84ef47182..a5443794a 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py @@ -38,8 +38,8 @@ def __init__( self, url: str, auth_token: str, - leagues: list[dict[str, Any]], *, + leagues: list[dict[str, Any]], n_wanted_items: int = 100, n_unique_wanted_items: int = 5, item_detectors: list[UniqueDetector] | None = None, diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py index 74f83020d..f07f62820 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py @@ -64,7 +64,6 @@ def _general_filter(self, df: pd.DataFrame) -> pd.DataFrame: return pd.DataFrame(columns=df.columns) df = df.loc[df["league"].isin(self.leagues)] - df["leagueId"] = df["league"].map(self.league_to_id) return df diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py index 02f50d93e..d9b612d0e 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py @@ -38,7 +38,6 @@ class ContinuousDataRetrieval: auth_token = settings.POE_PUBLIC_STASHES_AUTH_TOKEN - current_league = settings.CURRENT_SOFTCORE_LEAGUE stash_tab_url = "https://api.pathofexile.com/public-stash-tabs" backend_base_url = settings.BACKEND_BASE_URL @@ -53,13 +52,12 @@ def __init__( items_per_batch: int, data_transformers: dict[str, PoEAPIDataTransformerBase], ): + self.leagues = self._get_leagues() self.data_transformers: dict[str, PoEAPIDataTransformerBase] = { - key: data_transformer() + key: data_transformer(self.leagues) for key, data_transformer in data_transformers.items() } - self.leagues = self._get_leagues() - self.poe_api_handler = PoEAPIHandler( url=self.stash_tab_url, auth_token=self.auth_token, @@ -188,7 +186,9 @@ def _initialize_data_stream_threads( def _follow_data_dump_stream(self): try: - current_hour = find_hours_since_launch() + current_hours = find_hours_since_launch(self.leagues) + # Only need to refer to one league to see when a new hour starts + current_hour = current_hours[self.leagues[0]["leagueId"]] next_hour = current_hour + 1 logger.info("Retrieving modifiers from db.") modifier_dfs = self._get_modifiers() @@ -204,9 +204,10 @@ def _follow_data_dump_stream(self): modifier_df=modifier_dfs[data_transformer_type], currency_df=currency_df.copy(deep=True), item_base_types=item_base_types, - current_hour=current_hour, + current_hours=current_hours, ) - current_hour = find_hours_since_launch() + current_hours = find_hours_since_launch(self.leagues) + current_hour = current_hours[self.leagues[0]["leagueId"]] for data_transformer_type in self.data_transformers: self.data_transformers[data_transformer_type].end_of_hour_cleanup() diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py index 3d2764b6e..dce2604de 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py @@ -1,27 +1,11 @@ import pandas as pd from data_retrieval_app.external_data_retrieval.config import settings -from data_retrieval_app.external_data_retrieval.data_retrieval.currency_api_handler import ( - CurrencyAPIHandler, -) from data_retrieval_app.logs.logger import transform_logger as logger from data_retrieval_app.pom_api_authentication import get_superuser_token_headers from data_retrieval_app.utils import get_data_safe, insert_data -def load_currency_data(): - """ - Loads data from the poe.ninja currency API. - """ - poe_ninja_currency_api_handler = CurrencyAPIHandler( - url=f"https://poe.ninja/api/data/currencyoverview?league={settings.CURRENT_SOFTCORE_LEAGUE}&type=Currency" - ) - - currencies_df = poe_ninja_currency_api_handler.make_request() - - return currencies_df - - class TransformCurrencyAPIData: def __init__(self) -> None: logger.debug("Initializing TransformCurrencyAPIData.") diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py index 7eee86308..015a7d9d5 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py @@ -1,3 +1,5 @@ +from typing import Any + import pandas as pd from requests.exceptions import HTTPError @@ -14,16 +16,21 @@ class PoEAPIDataTransformerBase: - def __init__(self) -> None: + def __init__( + self, + leagues: list[dict[str, Any]], + ) -> None: logger.debug("Initializing PoEAPIDataTransformer") self.base_url = settings.BACKEND_BASE_URL self.pom_auth_headers = get_superuser_token_headers(self.base_url) self.roll_processor = RollProcessor() + self.league_to_id = {league["name"]: league["leagueId"] for league in leagues} + logger.debug("Initializing PoEAPIDataTransformer done.") - def _create_item_table(self, df: pd.DataFrame, current_hour: int) -> pd.DataFrame: + def _create_item_table(self, df: pd.DataFrame) -> pd.DataFrame: """ Creates the basis of the `item` table. """ @@ -31,7 +38,7 @@ def _create_item_table(self, df: pd.DataFrame, current_hour: int) -> pd.DataFram "itemId", "id", "name", - "leagueId", + "league", "baseType", "typeLine", "ilvl", @@ -61,11 +68,12 @@ def _create_item_table(self, df: pd.DataFrame, current_hour: int) -> pd.DataFram :, [column for column in self.item_columns if column in df.columns] ] # Can't guarantee all columns are present - item_df["createdHoursSinceLaunch"] = current_hour return item_df def _find_not_too_highly_priced_item_mask( - self, currency_df: pd.DataFrame, item_currency_merged_df: pd.DataFrame + self, + currency_df: pd.DataFrame, + item_currency_merged_df: pd.DataFrame, ) -> pd.Series: """ Some items are too highly priced to be legitimate. A boundary of the price equivelant to @@ -91,6 +99,7 @@ def _transform_item_table( item_df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], + current_hours: dict[int, int], ) -> pd.DataFrame: """ The `item` table requires a foreign key to the `currency` table. @@ -126,6 +135,9 @@ def transform_influences(row: pd.DataFrame, influence_columns: list[str]): ] = True return influence_dict + item_df["leagueId"] = item_df["league"].map(self.league_to_id) + item_df["createdHoursSinceLaunch"] = item_df["leagueId"].map(current_hours) + base_type_series = item_df["baseType"] item_df["itemBaseTypeId"] = base_type_series.apply(transform_base_types) @@ -259,13 +271,15 @@ def _process_item_table( df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], - current_hour: int, + current_hours: dict[int, int], ) -> pd.Series: """ Needs to return item ids, as it is used to connect the item modifiers """ - item_df = self._create_item_table(df, current_hour=current_hour) - item_df = self._transform_item_table(item_df, currency_df, item_base_types) + item_df = self._create_item_table(df) + item_df = self._transform_item_table( + item_df, currency_df, item_base_types, current_hours=current_hours + ) item_df = self._clean_item_table(item_df) insert_data( item_df, @@ -284,11 +298,14 @@ def _transform_unidentified_item_table( item_df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], + current_hours: dict[int, int], ) -> pd.DataFrame: """ For convenience, all unid items are stored in divine prices """ - item_df = self._transform_item_table(item_df, currency_df, item_base_types) + item_df = self._transform_item_table( + item_df, currency_df, item_base_types, current_hours=current_hours + ) item_df["chaos_value"] = ( item_df["currencyAmount"].astype(float) * item_df["valueInChaos"] @@ -401,11 +418,11 @@ def _process_unidentified_item_table( df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], - current_hour: int, + current_hours: dict[int, int], ) -> None: - item_df = self._create_item_table(df, current_hour=current_hour) + item_df = self._create_item_table(df) item_df = self._transform_unidentified_item_table( - item_df, currency_df, item_base_types + item_df, currency_df, item_base_types, current_hours=current_hours ) item_df = self._clean_unidentified_item_table(item_df) insert_data( @@ -417,7 +434,7 @@ def _process_unidentified_item_table( ) def _create_item_modifier_table( - self, df: pd.DataFrame, *, item_id: pd.Series, current_hour: int + self, df: pd.DataFrame, *, item_id: pd.Series ) -> pd.DataFrame: """ The `item_modifier` table heavily relies on what type of item the modifiers @@ -428,6 +445,7 @@ def _create_item_modifier_table( def _transform_item_modifier_table( self, item_modifier_df: pd.DataFrame, + current_hours: dict[int, int], ) -> pd.DataFrame: """ The `item_modifier` table heavily relies on what type of item the modifiers @@ -450,14 +468,13 @@ def _process_item_modifier_table( self, df: pd.DataFrame, item_id: pd.Series, - current_hour: int, + current_hours: dict[int, int], ) -> None: item_modifier_df = self._create_item_modifier_table( - df, item_id=item_id, current_hour=current_hour + df, item_id=item_id, current_hours=current_hours ) item_modifier_df = self._transform_item_modifier_table(item_modifier_df) item_modifier_df = self._clean_item_modifier_table(item_modifier_df) - insert_data( item_modifier_df, url=self.base_url, @@ -472,7 +489,7 @@ def transform_into_tables( modifier_df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], - current_hour: int, + current_hours: dict[int, int], ) -> None: self.roll_processor.add_modifier_df(modifier_df) try: @@ -482,18 +499,18 @@ def transform_into_tables( df.copy(deep=True), currency_df=currency_df, item_base_types=item_base_types, - current_hour=current_hour, + current_hours=current_hours, ) self._process_unidentified_item_table( df.copy(deep=True), currency_df=currency_df, item_base_types=item_base_types, - current_hour=current_hour, + current_hours=current_hours, ) self._process_item_modifier_table( df.copy(deep=True), item_id=item_id, - current_hour=current_hour, + current_hours=current_hours, ) logger.debug("Successfully transformed data into tables.") @@ -508,13 +525,17 @@ def end_of_hour_cleanup(self): class UniquePoEAPIDataTransformer(PoEAPIDataTransformerBase): @sync_timing_tracker def _create_item_modifier_table( - self, df: pd.DataFrame, *, item_id: pd.Series, current_hour: int + self, + df: pd.DataFrame, + *, + item_id: pd.Series, + current_hours: dict[int, int], ) -> pd.DataFrame: """ A similiar process to creating the item table, only this time the relevant column contains a list and not a JSON-object """ - item_modifier_columns = ["name", "explicitMods"] + item_modifier_columns = ["name", "explicitMods", "league"] item_modifier_df = df.loc[ self.price_found_mask, item_modifier_columns, @@ -525,19 +546,23 @@ def _create_item_modifier_table( ].reset_index() item_modifier_df["itemId"] = item_id + item_modifier_df["leagueId"] = item_modifier_df["league"].map(self.league_to_id) + item_modifier_df["createdHoursSinceLaunch"] = item_modifier_df["leagueId"].map( + current_hours + ) item_modifier_df = item_modifier_df.explode("explicitMods", ignore_index=True) item_modifier_df.rename({"explicitMods": "modifier"}, axis=1, inplace=True) - item_modifier_df["createdHoursSinceLaunch"] = current_hour - return item_modifier_df @sync_timing_tracker def _transform_item_modifier_table( - self, item_modifier_df: pd.DataFrame + self, + item_modifier_df: pd.DataFrame, ) -> pd.DataFrame: item_modifier_df = self.roll_processor.add_rolls(df=item_modifier_df) + return item_modifier_df @property diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/config.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/config.py index 46c18da79..3f84eb831 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/config.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/config.py @@ -1,4 +1,3 @@ -from pydantic import computed_field from pydantic_settings import BaseSettings @@ -26,17 +25,5 @@ def dispersed_timing_enabled(self) -> bool: ] # , "divine", "mirror", etc. MEAN_ITEM_PRICE: int = 200 - ALL_SOFTCORE_LEAGUES: str - - @computed_field # type: ignore[prop-decorator] - @property - def SOFTCORE_LEAGUES(self) -> list[str]: - return self.ALL_SOFTCORE_LEAGUES.split("|") - - @computed_field # type: ignore[prop-decorator] - @property - def HARDCORE_LEAGUES(self) -> list[str]: - return [f"Hardcore {league}" for league in self.SOFTCORE_LEAGUES] - script_settings = PoEPublicStashesTestDataSettings() diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/main.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/main.py index e01e59c4b..7e60fd7d3 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/main.py @@ -21,7 +21,8 @@ class PublicStashMockAPI: - def __init__(self) -> None: + def __init__(self, leagues: list[dict[str, Any]]) -> None: + self.leagues = [league["name"] for league in leagues] scrap_and_mock_poe_api_docs_objs = ScrapAndMockPoEAPIDocs() ( self.public_stashes_mock_obj, @@ -29,13 +30,9 @@ def __init__(self) -> None: ) = scrap_and_mock_poe_api_docs_objs.produce_mocks_from_docs() self.public_stashes_modifier_test_data_creator = DataDepositTestDataCreator( - n_of_items=script_settings.N_OF_ITEMS_PER_MODIFIER_FILE + n_of_items=script_settings.N_OF_ITEMS_PER_MODIFIER_FILE, leagues=leagues ) self.public_stashes_modifier_test_data_creator.create_templates() - self.leagues = [ - *script_settings.SOFTCORE_LEAGUES, - *script_settings.HARDCORE_LEAGUES, - ] def get_test_data(self) -> list[dict[str, Any]]: """Generates test data by modifying mock stashes and items based on league.""" diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py index 60fdd17e3..5328f1fc2 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/create_public_stashes_test_data/utils/data_deposit_test_data_creator.py @@ -19,7 +19,7 @@ class DataDepositTestDataCreator: - def __init__(self, n_of_items: int) -> None: + def __init__(self, n_of_items: int, leagues: list[dict[str, Any]]) -> None: """ Creates a number of items from the data deposit files. @@ -34,10 +34,7 @@ def __init__(self, n_of_items: int) -> None: self.base_url = settings.BACKEND_BASE_URL self.pom_auth_headers = get_superuser_token_headers(self.base_url) - self.leagues = [ - *script_settings.SOFTCORE_LEAGUES, - *script_settings.HARDCORE_LEAGUES, - ] + self.leagues = [league["name"] for league in leagues] self.base_type_df = self._get_df_from_url("itemBaseType/") self.grouped_modifier_df = self._get_df_from_url( @@ -425,7 +422,8 @@ def create_test_data( def main() -> int: - test = DataDepositTestDataCreator(50) + leagues = [{"name": "testing_league"}] + test = DataDepositTestDataCreator(50, leagues=leagues) test.create_templates() print(list(test.create_test_data())) return 0 diff --git a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/test_with_sim_api.py b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/test_with_sim_api.py index 131f81601..9ecfedb14 100644 --- a/src/backend_data_retrieval/data_retrieval_app/tests/scripts/test_with_sim_api.py +++ b/src/backend_data_retrieval/data_retrieval_app/tests/scripts/test_with_sim_api.py @@ -1,4 +1,5 @@ import threading +from typing import Any import numpy as np import pandas as pd @@ -18,26 +19,29 @@ from data_retrieval_app.tests.scripts.create_public_stashes_test_data.main import ( PublicStashMockAPI, ) -from data_retrieval_app.utils import find_hours_since_launch class TestUniquePoEAPIDataTransformer(UniquePoEAPIDataTransformer): - def __init__(self): - super().__init__() + def __init__(self, leagues: dict[str, Any]): + super().__init__(leagues) def _create_random_time_column(self, length: int) -> pd.Series: - start = find_hours_since_launch() - random_times = np.random.randint(0, script_settings.TIMING_PERIOD * 24, length) - time_column = start - random_times - - return pd.Series(time_column, dtype=str) + return pd.Series(random_times, dtype=int) - def _create_item_table(self, df, hours_since_launch: int): - item_df = super()._create_item_table(df, hours_since_launch) + def _transform_item_table( + self, + item_df: pd.DataFrame, + currency_df: pd.DataFrame, + item_base_types: dict[str, int], + current_hours: dict[int, int], + ) -> pd.DataFrame: + item_df = super()._transform_item_table( + item_df, currency_df, item_base_types, current_hours + ) if script_settings.dispersed_timing_enabled: - item_df["createdHoursSinceLaunch"] = self.time_column + item_df["createdHoursSinceLaunch"] += self.time_column return item_df @@ -46,18 +50,15 @@ def _create_item_modifier_table( df: pd.DataFrame, *, item_id: pd.Series, - hours_since_launch: int, # noqa: ARG003 + current_hours: dict[int, int], ) -> pd.DataFrame: """ A similiar process to creating the item table, only this time the relevant column contains a list and not a JSON-object """ - item_modifier_columns = ["name", "explicitMods"] - - if script_settings.dispersed_timing_enabled: - df["createdHoursSinceLaunch"] = self.time_column - item_modifier_columns.append("createdHoursSinceLaunch") - + # This method does the exact same thing, except adding random time if enabled + # This needs to be done before explode, but after filtering extremly priced items + item_modifier_columns = ["name", "explicitMods", "league"] item_modifier_df = df.loc[ self.price_found_mask, item_modifier_columns, @@ -68,10 +69,13 @@ def _create_item_modifier_table( ].reset_index() item_modifier_df["itemId"] = item_id - item_modifier_df = item_modifier_df.explode("explicitMods", ignore_index=True) - test_logger.info( - f"Transforming {len(item_modifier_df)} item modifiers, making them ready to insert into db" + item_modifier_df["leagueId"] = item_modifier_df["league"].map(self.league_to_id) + item_modifier_df["createdHoursSinceLaunch"] = item_modifier_df["leagueId"].map( + current_hours ) + if script_settings.dispersed_timing_enabled: + item_modifier_df["createdHoursSinceLaunch"] += self.time_column + item_modifier_df = item_modifier_df.explode("explicitMods", ignore_index=True) item_modifier_df.rename({"explicitMods": "modifier"}, axis=1, inplace=True) @@ -83,6 +87,7 @@ def transform_into_tables( modifier_df: pd.DataFrame, currency_df: pd.DataFrame, item_base_types: dict[str, int], + current_hours: dict[int, int], ) -> None: if script_settings.dispersed_timing_enabled: self.time_column = self._create_random_time_column(length=len(df)) @@ -90,17 +95,15 @@ def transform_into_tables( test_logger.info( f"Transforming {len(df)} items, making them ready to insert into db" ) - super().transform_into_tables(df, modifier_df, currency_df, item_base_types) + super().transform_into_tables( + df, modifier_df, currency_df, item_base_types, current_hours + ) class PoEMockAPIHandler(PoEAPIHandler): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - - self.mock_api = PublicStashMockAPI() - - for detector in self.item_detectors: - detector.leagues = self.mock_api.leagues + self.mock_api = PublicStashMockAPI(leagues=kwargs["leagues"]) self.run_program_for_n_seconds = script_settings.CREATE_TEST_DATA_FOR_N_SECONDS self.n_checkpoints_per_transfromation = ( @@ -137,6 +140,7 @@ def __init__( self.poe_api_handler = PoEMockAPIHandler( url=self.stash_tab_url, auth_token=self.auth_token, + leagues=self.leagues, n_wanted_items=items_per_batch, n_unique_wanted_items=10, ) diff --git a/src/backend_data_retrieval/data_retrieval_app/utils.py b/src/backend_data_retrieval/data_retrieval_app/utils.py index 6a37e09a7..0e0d6f892 100644 --- a/src/backend_data_retrieval/data_retrieval_app/utils.py +++ b/src/backend_data_retrieval/data_retrieval_app/utils.py @@ -7,7 +7,6 @@ import requests from pydantic import HttpUrl -from data_retrieval_app.external_data_retrieval.config import settings from data_retrieval_app.logs.logger import main_logger as logger @@ -54,19 +53,25 @@ def df_to_JSON( ) -def find_hours_since_launch() -> int: +def find_hours_since_launch(leagues_df: list[dict]) -> dict[int, int]: + """ + Finds the number of hours since launch for each of the leagues in the given dataframe + """ current_time = datetime.now(UTC) - league_launch_time = settings.LEAGUE_LAUNCH_DATETIME_OBJECT + hours_since_launch_dict = {} + for league in leagues_df: + league_launch_time = datetime.fromisoformat(league["validFrom"]) - time_since_launch = current_time - league_launch_time + time_since_launch = current_time - league_launch_time - days_since_launch, seconds_since_launch = ( - time_since_launch.days, - time_since_launch.seconds, - ) - hours_since_launch = days_since_launch * 24 + seconds_since_launch // 3600 + days_since_launch, seconds_since_launch = ( + time_since_launch.days, + time_since_launch.seconds, + ) + hours_since_launch = days_since_launch * 24 + seconds_since_launch // 3600 + hours_since_launch_dict[league["leagueId"]] = hours_since_launch - return hours_since_launch + return hours_since_launch_dict def insert_data( diff --git a/src/backend_data_retrieval/prestart.sh b/src/backend_data_retrieval/prestart.sh index 799ec4e88..a3a406ce3 100755 --- a/src/backend_data_retrieval/prestart.sh +++ b/src/backend_data_retrieval/prestart.sh @@ -28,10 +28,6 @@ if [[ -z "${POE_PUBLIC_STASHES_AUTH_TOKEN}" ]] || [[ "${POE_PUBLIC_STASHES_AUTH_ echo "Env variable POE_PUBLIC_STASHES_AUTH_TOKEN is not set" exit 1 fi -if [[ -z "${CURRENT_SOFTCORE_LEAGUE}" ]] || [[ "${CURRENT_SOFTCORE_LEAGUE}" == "changethis" ]]; then - echo "Env variable CURRENT_SOFTCORE_LEAGUE is not set" - exit 1 -fi if [[ -z "${OATH_ACC_TOKEN_CONTACT_EMAIL}" ]] || [[ "${OATH_ACC_TOKEN_CONTACT_EMAIL}" == "changethis" ]]; then echo "Env variable OATH_ACC_TOKEN_CONTACT_EMAIL is not set" exit 1 From eb2252f9d0d7a03d7819a145d932d9a2be425218 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Tue, 14 Jul 2026 14:29:04 -0230 Subject: [PATCH 12/15] #824 Backend changes --- .../965e766db0a0_added_league_table.py | 8 +- src/backend_api/app/api/routes/currency.py | 24 +++--- src/backend_api/app/core/models/models.py | 1 + src/backend_api/app/core/schemas/league.py | 6 +- .../app/core/schemas/plot/output.py | 2 +- .../app/core/schemas/unidentified_item.py | 2 - .../app/crud/extensions/crud_currency.py | 80 ++++++++----------- .../crud/extensions/crud_unidentifiedItem.py | 17 +--- src/backend_api/app/plotting/plotter.py | 2 +- .../app/tests/utils/model_utils/league.py | 3 + .../league/league_data/leagues.csv | 16 ++-- .../league/league_data_depositor.py | 32 ++++---- .../data_retrieval_app/data_deposit/main.py | 4 +- .../external_data_retrieval/detectors/base.py | 1 - .../external_data_retrieval/main.py | 45 ++++++++--- .../transform_currency_api_data.py | 10 ++- .../transform_poe_api_data.py | 2 +- 17 files changed, 133 insertions(+), 122 deletions(-) diff --git a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py index 6b0bbd5b0..7586677d9 100644 --- a/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py +++ b/src/backend_api/app/alembic/versions/965e766db0a0_added_league_table.py @@ -83,11 +83,15 @@ def upgrade() -> None: sa.Column("name", sa.Text(), nullable=False), sa.Column("validFrom", sa.DateTime(timezone=True), nullable=False), sa.Column("validTo", sa.DateTime(timezone=True), nullable=True), + sa.Column("version", sa.Float(), nullable=False), sa.PrimaryKeyConstraint("leagueId"), ) op.execute(""" - INSERT INTO league (name) - SELECT DISTINCT league + INSERT INTO league (name, "validFrom", version) + SELECT DISTINCT + league, + TIMESTAMP '1970-01-01 00:00:00', + 0.0 FROM item WHERE league IS NOT NULL """) diff --git a/src/backend_api/app/api/routes/currency.py b/src/backend_api/app/api/routes/currency.py index 4bdd861f4..bc05d1dfc 100644 --- a/src/backend_api/app/api/routes/currency.py +++ b/src/backend_api/app/api/routes/currency.py @@ -108,9 +108,9 @@ async def get_latest_currency_id( @router.get( - "/latest_hour/", - response_model=int, - tags=["latest_hour"], + "/latest_hours/", + response_model=dict[int, int], + tags=["latest_hours"], dependencies=[ Depends(get_current_active_user), ], @@ -121,20 +121,23 @@ async def get_latest_currency_id( rate_limit_settings.DEFAULT_USER_RATE_LIMIT_HOUR, rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, ) -async def get_latest_hour( +async def get_latest_hours( + league_ids: Annotated[list[int], Query(min_length=1)], request: Request, # noqa: ARG001 response: Response, # noqa: ARG001 db: Session = Depends(get_db), ): """ - Return -1 if database is empty + Returns a dict mapping league id to the most recent hour relating to that league. Dict is empty if no currencies exist. + + Does not guarantee an entry for every league id. """ - return await CRUD_currency.get_latest_hour(db) + return await CRUD_currency.get_latest_hours(db, league_ids) @router.get( "/latest_currencies/", - response_model=list[schemas.Currency], + response_model=dict[int, list[schemas.Currency]], tags=["latest_currencies"], dependencies=[ Depends(get_current_active_user), @@ -147,14 +150,17 @@ async def get_latest_hour( rate_limit_settings.DEFAULT_USER_RATE_LIMIT_DAY, ) async def get_latest_currencies( + league_ids: Annotated[list[int], Query(min_length=1)], request: Request, # noqa: ARG001 response: Response, # noqa: ARG001 db: Session = Depends(get_db), ): """ - Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + Returns a dict mapping league id to most recent currencies relating to that league. Dict is empty if no currencies exist. + + Does not guarantee an entry for every league id. """ - return await CRUD_currency.get_latest_currencies(db) + return await CRUD_currency.get_latest_currencies(db, league_ids) @router.post( diff --git a/src/backend_api/app/core/models/models.py b/src/backend_api/app/core/models/models.py index 8f9d259be..bbe6f325c 100644 --- a/src/backend_api/app/core/models/models.py +++ b/src/backend_api/app/core/models/models.py @@ -33,6 +33,7 @@ class League(Base): name: Mapped[str] = mapped_column(Text, nullable=False) validFrom: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) validTo: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True) + version: Mapped[float] = mapped_column(Float, nullable=False) class Currency(Base): diff --git a/src/backend_api/app/core/schemas/league.py b/src/backend_api/app/core/schemas/league.py index ebbe2da6b..a9e4f900a 100644 --- a/src/backend_api/app/core/schemas/league.py +++ b/src/backend_api/app/core/schemas/league.py @@ -10,6 +10,7 @@ class _BaseLeague(_pydantic.BaseModel): name: str validFrom: _dt.datetime validTo: _dt.datetime | None = None + version: float # Properties to receive on League creation @@ -18,11 +19,8 @@ class LeagueCreate(_BaseLeague): # Properties to receive on update -class LeagueUpdate(_pydantic.BaseModel): +class LeagueUpdate(_BaseLeague): leagueId: int - name: str - validFrom: _dt.datetime - validTo: _dt.datetime | None = None # Properties shared by models stored in DB diff --git a/src/backend_api/app/core/schemas/plot/output.py b/src/backend_api/app/core/schemas/plot/output.py index 248ec4c6a..d1e85c094 100644 --- a/src/backend_api/app/core/schemas/plot/output.py +++ b/src/backend_api/app/core/schemas/plot/output.py @@ -11,7 +11,7 @@ class Datum(_pydantic.BaseModel): class TimeseriesData(_pydantic.BaseModel): - name: int + leagueId: int data: list[Datum] confidenceRating: Literal["low", "medium", "high"] diff --git a/src/backend_api/app/core/schemas/unidentified_item.py b/src/backend_api/app/core/schemas/unidentified_item.py index da90f8473..90214f4d5 100644 --- a/src/backend_api/app/core/schemas/unidentified_item.py +++ b/src/backend_api/app/core/schemas/unidentified_item.py @@ -31,8 +31,6 @@ class UnidentifiedItemUpdate(_BaseUnidentifiedItem): class UnidentifiedItemInDBBase(_BaseUnidentifiedItem): createdHoursSinceLaunch: int itemId: int - nItems: int - aggregated: bool # Properties to return to client diff --git a/src/backend_api/app/crud/extensions/crud_currency.py b/src/backend_api/app/crud/extensions/crud_currency.py index 68a686a6f..eea9bba39 100644 --- a/src/backend_api/app/crud/extensions/crud_currency.py +++ b/src/backend_api/app/crud/extensions/crud_currency.py @@ -1,3 +1,5 @@ +from collections import defaultdict + from pydantic import TypeAdapter from sqlalchemy import select from sqlalchemy.orm import Session @@ -36,61 +38,45 @@ async def get_latest_currency_id(self, db: Session) -> int: return validate(latest_currency_id) - async def get_latest_hour(self, db: Session) -> int: - stmt = ( - select(model_Currency.createdHoursSinceLaunch) - .order_by(model_Currency.currencyId.desc()) - .limit(1) + def _latest_hours_stmt(self, league_ids: list[int]): + return ( + select( + model_Currency.leagueId, + func.max(model_Currency.createdHoursSinceLaunch).label("latest_hour"), + ) + .where(model_Currency.leagueId.in_(league_ids)) + .group_by(model_Currency.leagueId) ) - db_latest_hour = db.execute(stmt).mappings().first() - if db_latest_hour is not None: - latest_hour = db_latest_hour["createdHoursSinceLaunch"] - else: - latest_hour = -1 + async def get_latest_hours( + self, db: Session, league_ids: list[int] + ) -> dict[int, int]: + stmt = self._latest_hours_stmt(league_ids) - validate = TypeAdapter(int).validate_python + objs = db.execute(stmt).mappings().all() + id_hour_map = {obj["leagueId"]: obj["latest_hour"] for obj in objs} - return validate(latest_hour) + validate = TypeAdapter(dict[int, int]).validate_python - async def get_latest_currencies(self, db: Session) -> list[Currency]: - latest_hour = await self.get_latest_hour(db) + return validate(id_hour_map) - # All rows from that hour, plus the next lower currencyId - hour_rows = ( - select( - model_Currency.currencyId, - model_Currency.createdHoursSinceLaunch, - func.lead(model_Currency.currencyId) - .over(order_by=model_Currency.currencyId.desc()) - .label("next_id"), - ).where(model_Currency.createdHoursSinceLaunch == latest_hour) - ).cte("hour_rows") - - # The first place where the IDs stop being consecutive - boundary = ( - select(hour_rows.c.next_id) - .where(hour_rows.c.currencyId - hour_rows.c.next_id > 1) - .order_by(hour_rows.c.currencyId.desc()) - .limit(1) - .scalar_subquery() - ) - # If no gap exists, include all rows for that hour - min_id = func.coalesce( - boundary, - select(func.min(hour_rows.c.currencyId)).scalar_subquery(), - ) - stmt = ( - select(model_Currency.__table__) - .where( - model_Currency.createdHoursSinceLaunch == latest_hour, - model_Currency.currencyId >= min_id, - ) - .order_by(model_Currency.currencyId.desc()) + async def get_latest_currencies( + self, db: Session, league_ids: list[int] + ) -> dict[int, list[Currency]]: + latest_hours = self._latest_hours_stmt(league_ids).subquery() + + stmt = select(model_Currency).join( + latest_hours, + (model_Currency.leagueId == latest_hours.c.leagueId) + & (model_Currency.createdHoursSinceLaunch == latest_hours.c.latest_hour), ) - latest_currencies = db.execute(stmt).mappings().all() + currencies = db.scalars(stmt).all() - validate = TypeAdapter(list[Currency]).validate_python + latest_currencies: dict[int, list[Currency]] = defaultdict(list) + for currency in currencies: + latest_currencies[currency.leagueId].append(currency) + + validate = TypeAdapter(dict[int, list[Currency]]).validate_python return validate(latest_currencies) diff --git a/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py index 7b1d510d5..dffa30452 100644 --- a/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py +++ b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py @@ -21,17 +21,9 @@ class CRUDUnidentifiedItem( ): async def get_non_aggregated(self, db: Session) -> list[UnidentifiedItem]: """ - Returns the non aggregated unidentified items for the last 10 recorded hours - (not necessarily the last 10 hours) + Returns the non aggregated unidentified items """ - last_10_recorded_hours = ( - select(model_UnidentifiedItem.createdHoursSinceLaunch.distinct()) - .where(model_UnidentifiedItem.aggregated.isnot(True)) - .order_by(model_UnidentifiedItem.createdHoursSinceLaunch.desc()) - .limit(10) - ) stmt = select(model_UnidentifiedItem).where( - model_UnidentifiedItem.createdHoursSinceLaunch.in_(last_10_recorded_hours), model_UnidentifiedItem.aggregated.isnot(True), ) @@ -44,14 +36,7 @@ async def get_non_aggregated(self, db: Session) -> list[UnidentifiedItem]: async def add_aggregated( self, db: Session, aggregated_objs: list[UnidentifiedItemCreate] ) -> list[UnidentifiedItem]: - last_10_recorded_hours = ( - select(model_UnidentifiedItem.createdHoursSinceLaunch.distinct()) - .where(model_UnidentifiedItem.aggregated.isnot(True)) - .order_by(model_UnidentifiedItem.createdHoursSinceLaunch.desc()) - .limit(10) - ) stmt = delete(model_UnidentifiedItem).where( - model_UnidentifiedItem.createdHoursSinceLaunch.in_(last_10_recorded_hours), model_UnidentifiedItem.aggregated.isnot(True), ) non_aggregated_objs = db.execute(stmt) diff --git a/src/backend_api/app/plotting/plotter.py b/src/backend_api/app/plotting/plotter.py index 6bcd96320..013a88dbd 100644 --- a/src/backend_api/app/plotting/plotter.py +++ b/src/backend_api/app/plotting/plotter.py @@ -234,7 +234,7 @@ def _create_plot_data(self, df: pd.DataFrame) -> PlotData: ] ].to_dict(orient="records") # type: ignore timeseries_data = { - "name": league, + "leagueId": league, "confidenceRating": league_df["confidence"].mode()[0], "data": league_data, } diff --git a/src/backend_api/app/tests/utils/model_utils/league.py b/src/backend_api/app/tests/utils/model_utils/league.py index a8b27545d..aa2fc3088 100644 --- a/src/backend_api/app/tests/utils/model_utils/league.py +++ b/src/backend_api/app/tests/utils/model_utils/league.py @@ -5,6 +5,7 @@ from app.core.schemas import LeagueCreate from app.tests.utils.utils import ( random_datetime, + random_float, random_lower_string, ) @@ -18,11 +19,13 @@ def create_random_league_dict() -> dict: name = random_lower_string() validFrom = random_datetime() validTo = random_datetime(min_time=validFrom) + version = random_float(small_float=True, max_value=3.99) league = { "name": name, "validFrom": validFrom, "validTo": validTo, + "version": version, } return league diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv index 45526fdb2..339f675a8 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data/leagues.csv @@ -1,9 +1,9 @@ # Source: https://www.poewiki.net/wiki/League#Challenge_leagues -name,validFrom,validTo -Mirage,2026-03-06T19:00:00Z,2026-07-20T21:00:00Z -Phrecia2.0,2026-01-29T19:00:00Z,2026-02-19T21:00:00Z -Keepers,2025-10-31T19:00:00Z,2026-03-02T21:00:00Z -Mercenaries,2025-06-13T20:00:00Z,2025-10-27T21:00:00Z -Phrecia,2025-02-20T20:00:00Z,2025-04-23T21:00:00Z -Necro Settlers,2024-11-07T20:00:00Z,2025-02-20T19:00:00Z -Settlers,2024-07-26T20:00:00Z,2025-06-09T22:00:00Z \ No newline at end of file +name,validFrom,validTo,version +Mirage,2026-03-06T19:00:00Z,2026-07-20T22:00:00Z,3.28 +Phrecia2.0,2026-01-29T19:00:00Z,2026-02-19T21:00:00Z,3.27 +Keepers,2025-10-31T19:00:00Z,2026-03-02T21:00:00Z,3.27 +Mercenaries,2025-06-13T20:00:00Z,2025-10-27T21:00:00Z,3.26 +Phrecia,2025-02-20T20:00:00Z,2025-04-23T21:00:00Z,3.25 +Necro Settlers,2024-11-07T20:00:00Z,2025-02-20T19:00:00Z,3.25 +Settlers,2024-07-26T20:00:00Z,2025-06-09T22:00:00Z,3.25 \ No newline at end of file diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py index d8eef9ff0..bc87ef22d 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/league/league_data_depositor.py @@ -36,20 +36,23 @@ def _get_current_leagues(self) -> pd.DataFrame: def _update_duplicates(self, duplicate_df: pd.DataFrame): for _, duplicate_league_row in duplicate_df.iterrows(): league_id = int(duplicate_league_row["leagueId"]) - data = {"leagueId": league_id, "name": duplicate_league_row["name"]} - do_update = False - old_valid_to = duplicate_league_row["validTo_y"] - new_valid_to = duplicate_league_row["validTo"] - data["validTo"] = new_valid_to - if old_valid_to != new_valid_to: - do_update = True + old_df = duplicate_league_row[ + [col for col in duplicate_league_row.index if not col.endswith("_y")] + ] + old_df.index = old_df.index.str.removesuffix("_x") + old = old_df.to_dict() + new_df = duplicate_league_row[ + [col for col in duplicate_league_row.index if not col.endswith("_x")] + ] + new_df.index = new_df.index.str.removesuffix("_y") + new = new_df.to_dict() - old_valid_from = duplicate_league_row["validFrom_y"] - new_valid_from = duplicate_league_row["validFrom"] - data["validFrom"] = new_valid_from - if old_valid_from != new_valid_from: - do_update = True + do_update = False + for key, old_val in old.items(): + if new[key] != old_val: + do_update = True + break if do_update: headers = { @@ -60,7 +63,7 @@ def _update_duplicates(self, duplicate_df: pd.DataFrame): try: response = requests.put( self.update_url + str(league_id), - json=data, + json=new, headers=headers, # add HTTP Basic Auth ) @@ -80,7 +83,7 @@ def _process_data(self, df: pd.DataFrame) -> pd.DataFrame: self.current_league_df, how="left", on="name", - suffixes=("", "_y"), + suffixes=("_x", "_y"), ) non_duplicate_mask = merged_df["leagueId"].isna() non_duplicate_df = merged_df[non_duplicate_mask] @@ -89,6 +92,7 @@ def _process_data(self, df: pd.DataFrame) -> pd.DataFrame: column for column in non_duplicate_df.columns if column.endswith("_y") ] ) + non_duplicate_df.columns = non_duplicate_df.columns.str.removesuffix("_x") self._update_duplicates(merged_df[~non_duplicate_mask]) diff --git a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py index 09ff9e81e..342c0bbc5 100644 --- a/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/data_deposit/main.py @@ -1,5 +1,3 @@ -from typing import Literal - from data_retrieval_app.data_deposit.data_depositor_base import DataDepositorBase from data_retrieval_app.data_deposit.item_base_type.item_base_type_data_depositor import ( ItemBaseTypeDataDepositor, @@ -17,7 +15,7 @@ def main(): setup_logging() logger.info("Starting deposit phase.") - data_depositors: dict[Literal["modifier", "itemBaseType"], DataDepositorBase] = { + data_depositors: dict[str, DataDepositorBase] = { "modifer": ModifierDataDepositor(), "itemBaseType": ItemBaseTypeDataDepositor(), "league": LeagueDataDepositor(), diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py index f07f62820..30b0c9fc2 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/detectors/base.py @@ -23,7 +23,6 @@ def __init__( """ self.n_unique_items_found = 0 self.leagues = [league["name"] for league in leagues] - self.league_to_id = {league["name"]: league["leagueId"] for league in leagues} self.prev_item_hashes_found = {} diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py index d9b612d0e..4a420ec0f 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/main.py @@ -153,27 +153,54 @@ def _categorize_new_items(self, df: pd.DataFrame) -> dict[str, pd.DataFrame]: return split_dfs - def _get_new_currency_data(self, current_hour: int) -> pd.DataFrame: + def _get_new_currency_data(self, current_hours: dict[int, int]) -> pd.DataFrame: + league_ids = list(current_hours.keys()) response = get_data_safe( - self.currency_url + "latest_hour/", + self.currency_url + "latest_hours/", + params={"league_ids": league_ids}, headers=self.pom_auth_headers, logger=logger, ) - latest_hour = response.json() - if latest_hour == current_hour: + latest_hours: dict[str, int] = response.json() + need_new_data = [] + need_old_data = [] + if latest_hours: + for league_id, latest_hour in latest_hours.items(): + league_id = int(league_id) + if ( + league_id in current_hours + and latest_hour == current_hours[league_id] + ): + need_old_data.append(league_id) + else: + need_new_data.append(league_id) + else: + need_new_data = league_ids + + currency_df = None + if need_old_data: response = get_data_safe( self.currency_url + "latest_currencies/", + params={"league_ids": need_old_data}, headers=self.pom_auth_headers, logger=logger, ) currency_df = pd.DataFrame(response.json()) - else: - currency_df = self.currency_api_handler.make_request(self.leagues) - currency_df = self.currency_transformer.transform_into_tables( - currency_df, current_hour + + if need_new_data: + needed_leagues = [ + league for league in self.leagues if league["leagueId"] in need_new_data + ] + new_data = self.currency_api_handler.make_request(needed_leagues) + new_data = self.currency_transformer.transform_into_tables( + new_data, current_hours ) + if currency_df is None: + currency_df = new_data + else: + currency_df = pd.concat((currency_df, new_data)) return currency_df @@ -193,7 +220,7 @@ def _follow_data_dump_stream(self): logger.info("Retrieving modifiers from db.") modifier_dfs = self._get_modifiers() item_base_types = self._get_item_base_types() - currency_df = self._get_new_currency_data(current_hour) + currency_df = self._get_new_currency_data(current_hours) get_df = self.poe_api_handler.dump_stream() while current_hour < next_hour: df = next(get_df) diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py index dce2604de..8465d070a 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_currency_api_data.py @@ -43,7 +43,7 @@ def _get_name_to_trade_name_dict(self) -> dict: return currencies def _transform_currency_table( - self, currency_df: pd.DataFrame, hours_since_launch: int + self, currency_df: pd.DataFrame, current_hours: dict[int, int] ) -> pd.DataFrame: """ Since a chaos orb is always worth one chaos orb, ninja does not include it in its price api. @@ -70,7 +70,9 @@ def _transform_currency_table( lambda name: self.name_to_trade_name.get(name, pd.NA) ) - currency_df["createdHoursSinceLaunch"] = hours_since_launch + currency_df["createdHoursSinceLaunch"] = currency_df["leagueId"].map( + current_hours + ) return currency_df def _clean_currency_table(self, currency_df: pd.DataFrame) -> pd.DataFrame: @@ -105,13 +107,13 @@ def _get_latest_currency_id_series(self, currency_df: pd.DataFrame) -> pd.Series return currency_id def transform_into_tables( - self, currency_df: pd.DataFrame, current_hour: int + self, currency_df: pd.DataFrame, current_hours: dict[int, int] ) -> pd.DataFrame: """ Transforms the data into tables and transforms with help functions. """ logger.debug("Transforming data into tables.") - currency_df = self._transform_currency_table(currency_df, current_hour) + currency_df = self._transform_currency_table(currency_df, current_hours) logger.debug("Successfully transformed data into tables.") logger.debug("Cleaning currency table data.") diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py index 015a7d9d5..493137cfe 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/transforming_data/transform_poe_api_data.py @@ -310,7 +310,7 @@ def _transform_unidentified_item_table( item_df["chaos_value"] = ( item_df["currencyAmount"].astype(float) * item_df["valueInChaos"] ) - # TODO why do items sometime have chaos currency Id? + for league in item_df["leagueId"].unique(): divine_row = currency_df.loc[ (currency_df["leagueId"] == league) From 119c63f3299772c426fc25ddefe7a010472d2547 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Tue, 14 Jul 2026 14:51:27 -0230 Subject: [PATCH 13/15] #824 Frontend changes --- src/frontend/openapi.json | 519 +++++++----------- src/frontend/src/client/index.ts | 186 +++---- ....ts => Body_login_login_access_session.ts} | 2 +- src/frontend/src/client/models/League.ts | 10 +- .../src/client/models/LeagueCreate.ts | 8 +- .../src/client/models/LeagueUpdate.ts | 10 +- .../src/client/models/TimeseriesData.ts | 2 +- .../src/client/models/UnidentifiedItem.ts | 4 +- ...ts => $Body_login_login_access_session.ts} | 2 +- src/frontend/src/client/schemas/$League.ts | 49 +- .../src/client/schemas/$LeagueCreate.ts | 41 +- .../src/client/schemas/$LeagueUpdate.ts | 49 +- .../src/client/schemas/$TimeseriesData.ts | 2 +- .../src/client/schemas/$UnidentifiedItem.ts | 2 - ...CurrencysService.ts => CurrencyService.ts} | 38 +- ...TypesService.ts => ItemBaseTypeService.ts} | 2 +- ...fiersService.ts => ItemModifierService.ts} | 2 +- .../{ItemsService.ts => ItemService.ts} | 2 +- .../services/LatestCurrenciesService.ts | 16 +- .../src/client/services/LatestHourService.ts | 21 - .../src/client/services/LatestHoursService.ts | 33 ++ .../{LeaguesService.ts => LeagueService.ts} | 2 +- .../{LoginsService.ts => LoginService.ts} | 6 +- ...ModifiersService.ts => ModifierService.ts} | 2 +- .../{PlotsService.ts => PlotService.ts} | 2 +- .../{TestsService.ts => TestService.ts} | 2 +- ...rnstilesService.ts => TurnstileService.ts} | 2 +- ...sService.ts => UnidentifiedItemService.ts} | 2 +- .../Common/DateDaysHoursSinceLaunchStats.tsx | 1 - .../src/components/Common/QueryButtons.tsx | 8 +- .../src/components/Graph/GraphComponent.tsx | 10 +- .../hooks/getData/getBaseTypeCategories.tsx | 4 +- .../src/hooks/getData/getGroupedModifiers.tsx | 6 +- src/frontend/src/hooks/getData/getLeagues.tsx | 4 +- .../getData/prefetchGroupedModifiers.tsx | 33 +- .../src/hooks/graphing/postPlottingData.tsx | 4 +- .../hooks/graphing/processPlottingData.tsx | 2 +- src/frontend/src/hooks/graphing/utils.tsx | 16 +- .../hooks/validation/turnstileValidation.tsx | 8 +- src/frontend/src/routes/_layout/index.tsx | 11 +- src/frontend/src/schemas/Datum.tsx | 2 +- src/frontend/src/store/GraphInputStore.tsx | 11 +- .../src/store/LeagueLaunchStatsStore.tsx | 1 + src/frontend/src/store/StateInterface.tsx | 2 +- 44 files changed, 545 insertions(+), 596 deletions(-) rename src/frontend/src/client/models/{Body_logins_login_access_session.ts => Body_login_login_access_session.ts} (86%) rename src/frontend/src/client/schemas/{$Body_logins_login_access_session.ts => $Body_login_login_access_session.ts} (95%) rename src/frontend/src/client/services/{CurrencysService.ts => CurrencyService.ts} (83%) rename src/frontend/src/client/services/{ItemBaseTypesService.ts => ItemBaseTypeService.ts} (99%) rename src/frontend/src/client/services/{ItemModifiersService.ts => ItemModifierService.ts} (98%) rename src/frontend/src/client/services/{ItemsService.ts => ItemService.ts} (98%) delete mode 100644 src/frontend/src/client/services/LatestHourService.ts create mode 100644 src/frontend/src/client/services/LatestHoursService.ts rename src/frontend/src/client/services/{LeaguesService.ts => LeagueService.ts} (99%) rename src/frontend/src/client/services/{LoginsService.ts => LoginService.ts} (83%) rename src/frontend/src/client/services/{ModifiersService.ts => ModifierService.ts} (99%) rename src/frontend/src/client/services/{PlotsService.ts => PlotService.ts} (97%) rename src/frontend/src/client/services/{TestsService.ts => TestService.ts} (98%) rename src/frontend/src/client/services/{TurnstilesService.ts => TurnstileService.ts} (97%) rename src/frontend/src/client/services/{UnidentifiedItemsService.ts => UnidentifiedItemService.ts} (98%) diff --git a/src/frontend/openapi.json b/src/frontend/openapi.json index b449ee04e..6bd2aaab2 100644 --- a/src/frontend/openapi.json +++ b/src/frontend/openapi.json @@ -7,9 +7,7 @@ "paths": { "/api/api_v1/currency/{currencyId}": { "get": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Get Currency", "description": "Get currency by key and value for \"currencyId\".\n\nAlways returns one currency.", "operationId": "get_currency", @@ -55,9 +53,7 @@ }, "/api/api_v1/currency/": { "get": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Get All Currencies", "description": "Get all currencies.\n\nReturns a list of all currencies.", "operationId": "get_all_currencies", @@ -124,10 +120,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -155,7 +148,7 @@ } } ], - "title": "Response Currencys-Get All Currencies" + "title": "Response Currency-Get All Currencies" } } } @@ -173,9 +166,7 @@ } }, "post": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Create Currency", "description": "Create one or a list of currencies.\n\nReturns the created currency or list of currencies.", "operationId": "create_currency", @@ -243,7 +234,7 @@ "type": "null" } ], - "title": "Response Currencys-Create Currency" + "title": "Response Currency-Create Currency" } } } @@ -261,9 +252,7 @@ } }, "put": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Update Currency", "description": "Update a currency by key and value for \"currencyId\".\n\nReturns the updated currency.", "operationId": "update_currency", @@ -317,9 +306,7 @@ } }, "delete": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Delete Currency", "description": "Delete a currency by key and value for \"currencyId\".\n\nReturns a message indicating the currency was deleted.\nAlways deletes one currency.", "operationId": "delete_currency", @@ -346,7 +333,7 @@ "application/json": { "schema": { "type": "string", - "title": "Response Currencys-Delete Currency" + "title": "Response Currency-Delete Currency" } } } @@ -366,10 +353,7 @@ }, "/api/api_v1/currency/latest_currency_id/": { "get": { - "tags": [ - "currencys", - "latest_currency_id" - ], + "tags": ["currency", "latest_currency_id"], "summary": "Get Latest Currency Id", "description": "Get the latest currencyId, returns 1 if table is empty\n\nCan only be used safely on an empty table or directly after an insertion.", "operationId": "get_latest_currency_id", @@ -380,7 +364,7 @@ "application/json": { "schema": { "type": "integer", - "title": "Response Currencys-Get Latest Currency Id" + "title": "Response Currency-Get Latest Currency Id" } } } @@ -393,72 +377,120 @@ ] } }, - "/api/api_v1/currency/latest_hour/": { + "/api/api_v1/currency/latest_hours/": { "get": { - "tags": [ - "currencys", - "latest_hour" + "tags": ["currency", "latest_hours"], + "summary": "Get Latest Hours", + "description": "Returns a dict mapping league id to the most recent hour relating to that league. Dict is empty if no currencies exist.\n\nDoes not guarantee an entry for every league id.", + "operationId": "get_latest_hours", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "league_ids", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "title": "League Ids" + } + } ], - "summary": "Get Latest Hour", - "description": "Return -1 if database is empty", - "operationId": "get_latest_hour", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "type": "integer", - "title": "Response Currencys-Get Latest Hour" + "type": "object", + "additionalProperties": { + "type": "integer" + }, + "title": "Response Currency-Get Latest Hours" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" } } } } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ] + } } }, "/api/api_v1/currency/latest_currencies/": { "get": { - "tags": [ - "currencys", - "latest_currencies" - ], + "tags": ["currency", "latest_currencies"], "summary": "Get Latest Currencies", - "description": "Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint.", + "description": "Returns a dict mapping league id to most recent currencies relating to that league. Dict is empty if no currencies exist.\n\nDoes not guarantee an entry for every league id.", "operationId": "get_latest_currencies", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "league_ids", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "title": "League Ids" + } + } + ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/Currency" + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + } }, - "type": "array", - "title": "Response Currencys-Get Latest Currencies" + "title": "Response Currency-Get Latest Currencies" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" } } } } - }, - "security": [ - { - "OAuth2PasswordBearer": [] - } - ] + } } }, "/api/api_v1/currency/from_query/": { "post": { - "tags": [ - "currencys" - ], + "tags": ["currency"], "summary": "Get Currency From Query", "description": "Returns a list of currencies that match any of the queries", "operationId": "get_currency_from_query", @@ -486,7 +518,7 @@ "$ref": "#/components/schemas/Currency" }, "type": "array", - "title": "Response Currencys-Get Currency From Query" + "title": "Response Currency-Get Currency From Query" } } } @@ -511,9 +543,7 @@ }, "/api/api_v1/itemBaseType/{itemBaseTypeId}": { "get": { - "tags": [ - "itemBaseTypes" - ], + "tags": ["itemBaseType"], "summary": "Get Item Base Type", "description": "Get item base type by key and value for \"itemBaseTypeId\".\n\nAlways returns one item base type.", "operationId": "get_item_base_type", @@ -559,9 +589,7 @@ }, "/api/api_v1/itemBaseType/": { "get": { - "tags": [ - "itemBaseTypes" - ], + "tags": ["itemBaseType"], "summary": "Get All Item Base Types", "description": "Get all item base types.\n\nReturns a list of all item base types.", "operationId": "get_all_item_base_types", @@ -623,10 +651,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -654,7 +679,7 @@ } } ], - "title": "Response Itembasetypes-Get All Item Base Types" + "title": "Response Itembasetype-Get All Item Base Types" } } } @@ -672,9 +697,7 @@ } }, "post": { - "tags": [ - "itemBaseTypes" - ], + "tags": ["itemBaseType"], "summary": "Create Item Base Type", "description": "Create one or a list of new item base types.\n\nReturns the created item base type or list of item base types.", "operationId": "create_item_base_type", @@ -758,7 +781,7 @@ "type": "null" } ], - "title": "Response Itembasetypes-Create Item Base Type" + "title": "Response Itembasetype-Create Item Base Type" } } } @@ -776,9 +799,7 @@ } }, "put": { - "tags": [ - "itemBaseTypes" - ], + "tags": ["itemBaseType"], "summary": "Update Item Base Type", "description": "Update an item base type by key and value for \"itemBaseTypeId\".\n\nReturns the updated item base type.", "operationId": "update_item_base_type", @@ -832,9 +853,7 @@ } }, "delete": { - "tags": [ - "itemBaseTypes" - ], + "tags": ["itemBaseType"], "summary": "Delete Item Base Type", "description": "Delete an item base type by key and value for \"itemBaseTypeId\".\n\nReturns a message that the item base type was deleted successfully.\nAlways deletes one item base type.", "operationId": "delete_item_base_type", @@ -861,7 +880,7 @@ "application/json": { "schema": { "type": "string", - "title": "Response Itembasetypes-Delete Item Base Type" + "title": "Response Itembasetype-Delete Item Base Type" } } } @@ -881,9 +900,7 @@ }, "/api/api_v1/itemModifier/": { "get": { - "tags": [ - "itemModifiers" - ], + "tags": ["itemModifier"], "summary": "Get All Item Modifiers", "description": "Get all item modifiers.\n\nReturns a list of all item modifiers.", "operationId": "get_all_item_modifiers", @@ -950,10 +967,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -981,7 +995,7 @@ } } ], - "title": "Response Itemmodifiers-Get All Item Modifiers" + "title": "Response Itemmodifier-Get All Item Modifiers" } } } @@ -999,9 +1013,7 @@ } }, "post": { - "tags": [ - "itemModifiers" - ], + "tags": ["itemModifier"], "summary": "Create Item Modifier", "description": "Create one or a list item modifiers.\n\nReturns the created item modifier or list of item modifiers.", "operationId": "create_item_modifier", @@ -1069,7 +1081,7 @@ "type": "null" } ], - "title": "Response Itemmodifiers-Create Item Modifier" + "title": "Response Itemmodifier-Create Item Modifier" } } } @@ -1089,10 +1101,7 @@ }, "/api/api_v1/item/latest_item_id/": { "get": { - "tags": [ - "items", - "latest_item_id" - ], + "tags": ["item", "latest_item_id"], "summary": "Get Latest Item Id", "description": "Get the latest \"itemId\"\n\nCan only be used safely on an empty table or directly after an insertion.", "operationId": "get_latest_item_id", @@ -1110,7 +1119,7 @@ "type": "null" } ], - "title": "Response Items-Get Latest Item Id" + "title": "Response Item-Get Latest Item Id" } } } @@ -1125,9 +1134,7 @@ }, "/api/api_v1/item/": { "get": { - "tags": [ - "items" - ], + "tags": ["item"], "summary": "Get All Items", "description": "Get all items.\n\nReturns a list of all items.", "operationId": "get_all_items", @@ -1194,10 +1201,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -1225,7 +1229,7 @@ } } ], - "title": "Response Items-Get All Items" + "title": "Response Item-Get All Items" } } } @@ -1243,9 +1247,7 @@ } }, "post": { - "tags": [ - "items" - ], + "tags": ["item"], "summary": "Create Item", "description": "Create one or a list of new items.\n\nReturns the created item or list of items.", "operationId": "create_item", @@ -1313,7 +1315,7 @@ "type": "null" } ], - "title": "Response Items-Create Item" + "title": "Response Item-Create Item" } } } @@ -1333,9 +1335,7 @@ }, "/api/api_v1/league/{leagueId}": { "get": { - "tags": [ - "leagues" - ], + "tags": ["league"], "summary": "Get League", "description": "Get league by key and value for \"leagueId\".\n\nAlways returns one league.", "operationId": "get_league", @@ -1379,9 +1379,7 @@ } }, "put": { - "tags": [ - "leagues" - ], + "tags": ["league"], "summary": "Update Modifier", "description": "Update a league by key and value for \"leagueId\"\n\nReturns the updated league.", "operationId": "update_modifier", @@ -1437,17 +1435,10 @@ }, "/api/api_v1/league/": { "get": { - "tags": [ - "leagues" - ], + "tags": ["league"], "summary": "Get All Leagues", "description": "Get all leagues.\n\nReturns a list of all leagues.", "operationId": "get_all_leagues", - "security": [ - { - "OAuth2PasswordBearer": [] - } - ], "parameters": [ { "name": "limit", @@ -1506,10 +1497,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -1537,7 +1525,7 @@ } } ], - "title": "Response Leagues-Get All Leagues" + "title": "Response League-Get All Leagues" } } } @@ -1555,9 +1543,7 @@ } }, "post": { - "tags": [ - "leagues" - ], + "tags": ["league"], "summary": "Create League", "description": "Create one or a list of new leagues.\n\nReturns the created league or list of leagues.", "operationId": "create_league", @@ -1625,7 +1611,7 @@ "type": "null" } ], - "title": "Response Leagues-Create League" + "title": "Response League-Create League" } } } @@ -1645,9 +1631,7 @@ }, "/api/api_v1/league/active_league/": { "get": { - "tags": [ - "leagues" - ], + "tags": ["league"], "summary": "Get Active Leagues", "description": "Get leagues that are still valid/active\n\nAlways returns a list, but it may be empty.", "operationId": "get_active_leagues", @@ -1661,7 +1645,7 @@ "$ref": "#/components/schemas/League" }, "type": "array", - "title": "Response Leagues-Get Active Leagues" + "title": "Response League-Get Active Leagues" } } } @@ -1676,10 +1660,7 @@ }, "/api/api_v1/unidentifiedItem/latest_item_id/": { "get": { - "tags": [ - "unidentifiedItems", - "latest_item_id" - ], + "tags": ["unidentifiedItem", "latest_item_id"], "summary": "Get Latest Item Id", "description": "Get the latest \"itemId\"\n\nCan only be used safely on an empty table or directly after an insertion.", "operationId": "get_latest_item_id", @@ -1697,7 +1678,7 @@ "type": "null" } ], - "title": "Response Unidentifieditems-Get Latest Item Id" + "title": "Response Unidentifieditem-Get Latest Item Id" } } } @@ -1712,9 +1693,7 @@ }, "/api/api_v1/unidentifiedItem/": { "get": { - "tags": [ - "unidentifiedItems" - ], + "tags": ["unidentifiedItem"], "summary": "Get All Items", "description": "Get all items.\n\nReturns a list of all items.", "operationId": "get_all_items", @@ -1781,10 +1760,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -1812,7 +1788,7 @@ } } ], - "title": "Response Unidentifieditems-Get All Items" + "title": "Response Unidentifieditem-Get All Items" } } } @@ -1830,9 +1806,7 @@ } }, "post": { - "tags": [ - "unidentifiedItems" - ], + "tags": ["unidentifiedItem"], "summary": "Create Item", "description": "Create one or a list of new items.\n\nReturns the created item or list of items.", "operationId": "create_item", @@ -1900,7 +1874,7 @@ "type": "null" } ], - "title": "Response Unidentifieditems-Create Item" + "title": "Response Unidentifieditem-Create Item" } } } @@ -1920,9 +1894,7 @@ }, "/api/api_v1/unidentifiedItem/non_aggregated/": { "get": { - "tags": [ - "unidentifiedItems" - ], + "tags": ["unidentifiedItem"], "summary": "Get Non Aggregated", "description": "Get the non aggregated unidentified items for the last 10 recorded hours\n (not necessarily the last 10 hours)", "operationId": "get_non_aggregated", @@ -1936,7 +1908,7 @@ "$ref": "#/components/schemas/UnidentifiedItem" }, "type": "array", - "title": "Response Unidentifieditems-Get Non Aggregated" + "title": "Response Unidentifieditem-Get Non Aggregated" } } } @@ -1951,9 +1923,7 @@ }, "/api/api_v1/unidentifiedItem/add_aggregated/": { "post": { - "tags": [ - "unidentifiedItems" - ], + "tags": ["unidentifiedItem"], "summary": "Add Aggregated", "description": "Deletes the non aggregated unidentified items for the last 10 recorded hours\n (not necessarily the last 10 hours)\nAnd inserts the new items", "operationId": "add_aggregated", @@ -1981,7 +1951,7 @@ "$ref": "#/components/schemas/UnidentifiedItem" }, "type": "array", - "title": "Response Unidentifieditems-Add Aggregated" + "title": "Response Unidentifieditem-Add Aggregated" } } } @@ -2006,9 +1976,7 @@ }, "/api/api_v1/modifier/{modifierId}": { "get": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Get Modifier", "description": "Get modifier or list of modifiers by key and\nvalue for \"modifierId\"\n\nDominant key is \"modifierId\".\n\nReturns one or a list of modifiers.", "operationId": "get_modifier", @@ -2045,7 +2013,7 @@ } } ], - "title": "Response Modifiers-Get Modifier" + "title": "Response Modifier-Get Modifier" } } } @@ -2065,9 +2033,7 @@ }, "/api/api_v1/modifier/": { "get": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Get All Modifiers", "description": "Get all modifiers.\n\nReturns a list of all modifiers.", "operationId": "get_all_modifiers", @@ -2134,10 +2100,7 @@ "schema": { "anyOf": [ { - "enum": [ - "asc", - "desc" - ], + "enum": ["asc", "desc"], "type": "string" }, { @@ -2165,7 +2128,7 @@ } } ], - "title": "Response Modifiers-Get All Modifiers" + "title": "Response Modifier-Get All Modifiers" } } } @@ -2183,9 +2146,7 @@ } }, "post": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Create Modifier", "description": "Create one or a list of new modifiers.\n\nReturns the created modifier or list of modifiers.", "operationId": "create_modifier", @@ -2253,7 +2214,7 @@ "type": "null" } ], - "title": "Response Modifiers-Create Modifier" + "title": "Response Modifier-Create Modifier" } } } @@ -2271,9 +2232,7 @@ } }, "put": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Update Modifier", "description": "Update a modifier by key and value for \"modifierId\" and \"position\"\n\nReturns the updated modifier.", "operationId": "update_modifier", @@ -2336,9 +2295,7 @@ } }, "delete": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Delete Modifier", "description": "Delete a modifier by key and value for \"modifierId\"\n\nReturns a message that the modifier was deleted.\nAlways deletes one modifier.", "operationId": "delete_modifier", @@ -2374,7 +2331,7 @@ "application/json": { "schema": { "type": "string", - "title": "Response Modifiers-Delete Modifier" + "title": "Response Modifier-Delete Modifier" } } } @@ -2394,9 +2351,7 @@ }, "/api/api_v1/modifier/grouped_modifiers_by_effect/": { "get": { - "tags": [ - "modifiers" - ], + "tags": ["modifier"], "summary": "Get Grouped Modifier By Effect", "description": "Get all grouped modifiers by effect.\n\nReturns a list of all grouped modifiers by effect.", "operationId": "get_grouped_modifier_by_effect", @@ -2417,7 +2372,7 @@ "type": "array" } ], - "title": "Response Modifiers-Get Grouped Modifier By Effect" + "title": "Response Modifier-Get Grouped Modifier By Effect" } } } @@ -2427,9 +2382,7 @@ }, "/api/api_v1/plot/": { "post": { - "tags": [ - "plots" - ], + "tags": ["plot"], "summary": "Get Plot Data", "description": "Takes a query based on the 'PlotQuery' schema and retrieves data\nto be used for plotting in the format of the 'PlotData' schema.\n\nThe 'PlotQuery' schema allows for modifier restriction and item specifications.", "operationId": "get_plot_data", @@ -2469,9 +2422,7 @@ }, "/api/api_v1/turnstile/": { "post": { - "tags": [ - "turnstiles" - ], + "tags": ["turnstile"], "summary": "Get Turnstile Validation", "description": "Takes a query based on the 'TurnstileQuery' schema and retrieves data\nbased on the cloudfare challenge turnstile validation response.\n\nArgs:\n query (schemas.TurnstileQuery): Query based on the 'TurnstileQuery' schema.\n verification (bool, optional): Verification flag. Defaults to Depends(verification).\n\nRaises:\n HTTPException: If verification fails.\n\nReturns:\n _type_: Returns a response based on the 'TurnstileResponse' schema.", "operationId": "get_turnstile_validation", @@ -2511,9 +2462,7 @@ }, "/api/api_v1/test/bulk-insert-test": { "post": { - "tags": [ - "tests" - ], + "tags": ["test"], "summary": "Bulk Insert Test", "description": "Can only be used in `settings.ENVIRONMENT=local` environment.\n\nTest route for bulk inserting records.\n\nReturns a success message once the insertion is complete.", "operationId": "bulk_insert_test", @@ -2541,7 +2490,7 @@ "schema": { "type": "object", "additionalProperties": true, - "title": "Response Tests-Bulk Insert Test" + "title": "Response Test-Bulk Insert Test" } } } @@ -2561,9 +2510,7 @@ }, "/api/api_v1/test/bulk-insert-users-and-verify": { "post": { - "tags": [ - "tests" - ], + "tags": ["test"], "summary": "Bulk Insert Users And Verify", "description": "Can only be used in `settings.ENVIRONMENT=local` environment.\n\nTest route for bulk inserting users and verifying them.\n\nReturns the access tokens for the created users.", "operationId": "bulk_insert_users_and_verify", @@ -2593,7 +2540,7 @@ "items": { "type": "string" }, - "title": "Response Tests-Bulk Insert Users And Verify" + "title": "Response Test-Bulk Insert Users And Verify" } } } @@ -2613,9 +2560,7 @@ }, "/api/api_v1/login/access-token": { "post": { - "tags": [ - "logins" - ], + "tags": ["login"], "summary": "Login Access Session", "description": "OAuth2 compatible session login.", "operationId": "login_access_session", @@ -2623,7 +2568,7 @@ "content": { "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/Body_logins-login_access_session" + "$ref": "#/components/schemas/Body_login-login_access_session" } } }, @@ -2695,7 +2640,7 @@ "type": "object", "title": "BaseSpecs" }, - "Body_logins-login_access_session": { + "Body_login-login_access_session": { "properties": { "grant_type": { "anyOf": [ @@ -2748,11 +2693,8 @@ } }, "type": "object", - "required": [ - "username", - "password" - ], - "title": "Body_logins-login_access_session" + "required": ["username", "password"], + "title": "Body_login-login_access_session" }, "Currency": { "properties": { @@ -2870,11 +2812,7 @@ } }, "type": "object", - "required": [ - "tradeName", - "valueInChaos", - "leagueId" - ], + "required": ["tradeName", "valueInChaos", "leagueId"], "title": "CurrencyUpdate" }, "Datum": { @@ -2893,11 +2831,7 @@ }, "confidence": { "type": "string", - "enum": [ - "low", - "medium", - "high" - ], + "enum": ["low", "medium", "high"], "title": "Confidence" } }, @@ -2986,10 +2920,7 @@ } }, "type": "object", - "required": [ - "position", - "textRolls" - ], + "required": ["position", "textRolls"], "title": "GroupedModifierProperties" }, "HTTPValidationError": { @@ -3322,11 +3253,7 @@ } }, "type": "object", - "required": [ - "baseType", - "category", - "itemBaseTypeId" - ], + "required": ["baseType", "category", "itemBaseTypeId"], "title": "ItemBaseType" }, "ItemBaseTypeCreate": { @@ -3363,10 +3290,7 @@ } }, "type": "object", - "required": [ - "baseType", - "category" - ], + "required": ["baseType", "category"], "title": "ItemBaseTypeCreate" }, "ItemBaseTypeUpdate": { @@ -3403,10 +3327,7 @@ } }, "type": "object", - "required": [ - "baseType", - "category" - ], + "required": ["baseType", "category"], "title": "ItemBaseTypeUpdate" }, "ItemCreate": { @@ -3871,17 +3792,17 @@ ], "title": "Validto" }, + "version": { + "type": "number", + "title": "Versionminor" + }, "leagueId": { "type": "integer", "title": "Leagueid" } }, "type": "object", - "required": [ - "name", - "validFrom", - "leagueId" - ], + "required": ["name", "validFrom", "version", "leagueId"], "title": "League" }, "LeagueCreate": { @@ -3906,21 +3827,18 @@ } ], "title": "Validto" + }, + "version": { + "type": "number", + "title": "Versionminor" } }, "type": "object", - "required": [ - "name", - "validFrom" - ], + "required": ["name", "validFrom", "version"], "title": "LeagueCreate" }, "LeagueUpdate": { "properties": { - "leagueId": { - "type": "integer", - "title": "Leagueid" - }, "name": { "type": "string", "title": "Name" @@ -3941,14 +3859,18 @@ } ], "title": "Validto" + }, + "version": { + "type": "number", + "title": "Versionminor" + }, + "leagueId": { + "type": "integer", + "title": "Leagueid" } }, "type": "object", - "required": [ - "leagueId", - "name", - "validFrom" - ], + "required": ["name", "validFrom", "version", "leagueId"], "title": "LeagueUpdate" }, "MetadataObject": { @@ -4166,12 +4088,7 @@ } }, "type": "object", - "required": [ - "position", - "effect", - "modifierId", - "createdAt" - ], + "required": ["position", "effect", "modifierId", "createdAt"], "title": "Modifier" }, "ModifierCreate": { @@ -4351,10 +4268,7 @@ } }, "type": "object", - "required": [ - "position", - "effect" - ], + "required": ["position", "effect"], "title": "ModifierCreate" }, "ModifierLimitation": { @@ -4398,9 +4312,7 @@ } }, "type": "object", - "required": [ - "position" - ], + "required": ["position"], "title": "ModifierLimitation" }, "ModifierUpdate": { @@ -4580,10 +4492,7 @@ } }, "type": "object", - "required": [ - "position", - "effect" - ], + "required": ["position", "effect"], "title": "ModifierUpdate" }, "PlotData": { @@ -4601,10 +4510,7 @@ } }, "type": "object", - "required": [ - "mostCommonCurrencyUsed", - "data" - ], + "required": ["mostCommonCurrencyUsed", "data"], "title": "PlotData" }, "PlotQuery": { @@ -4681,17 +4587,15 @@ } }, "type": "object", - "required": [ - "leagueId" - ], + "required": ["leagueId"], "title": "PlotQuery", "description": "Plots for items with or without modifiers" }, "TimeseriesData": { "properties": { - "name": { + "leagueId": { "type": "integer", - "title": "Name" + "title": "Leagueid" }, "data": { "items": { @@ -4702,20 +4606,12 @@ }, "confidenceRating": { "type": "string", - "enum": [ - "low", - "medium", - "high" - ], + "enum": ["low", "medium", "high"], "title": "Confidencerating" } }, "type": "object", - "required": [ - "name", - "data", - "confidenceRating" - ], + "required": ["leagueId", "data", "confidenceRating"], "title": "TimeseriesData" }, "Token": { @@ -4726,9 +4622,7 @@ } }, "type": "object", - "required": [ - "access_token" - ], + "required": ["access_token"], "title": "Token" }, "TurnstileQuery": { @@ -4743,10 +4637,7 @@ } }, "type": "object", - "required": [ - "token", - "ip" - ], + "required": ["token", "ip"], "title": "TurnstileQuery" }, "TurnstileResponse": { @@ -4825,9 +4716,7 @@ } }, "type": "object", - "required": [ - "success" - ], + "required": ["success"], "title": "TurnstileResponse" }, "UnidentifiedItem": { @@ -4888,11 +4777,13 @@ }, "nItems": { "type": "integer", - "title": "Nitems" + "title": "Nitems", + "default": 1 }, "aggregated": { "type": "boolean", - "title": "Aggregated" + "title": "Aggregated", + "default": false }, "createdHoursSinceLaunch": { "type": "integer", @@ -4909,8 +4800,6 @@ "itemBaseTypeId", "ilvl", "rarity", - "nItems", - "aggregated", "createdHoursSinceLaunch", "itemId" ], @@ -5023,11 +4912,7 @@ } }, "type": "object", - "required": [ - "loc", - "msg", - "type" - ], + "required": ["loc", "msg", "type"], "title": "ValidationError" }, "WantedModifier": { @@ -5052,9 +4937,7 @@ } }, "type": "object", - "required": [ - "modifierId" - ], + "required": ["modifierId"], "title": "WantedModifier" } }, diff --git a/src/frontend/src/client/index.ts b/src/frontend/src/client/index.ts index 81d6f70be..54f96b3c4 100644 --- a/src/frontend/src/client/index.ts +++ b/src/frontend/src/client/index.ts @@ -2,99 +2,99 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; +export { ApiError } from "./core/ApiError"; +export { CancelablePromise, CancelError } from "./core/CancelablePromise"; +export { OpenAPI } from "./core/OpenAPI"; +export type { OpenAPIConfig } from "./core/OpenAPI"; -export type { BaseSpecs } from './models/BaseSpecs'; -export type { Body_logins_login_access_session } from './models/Body_logins_login_access_session'; -export type { Currency } from './models/Currency'; -export type { CurrencyCreate } from './models/CurrencyCreate'; -export type { CurrencyQuery } from './models/CurrencyQuery'; -export type { CurrencyUpdate } from './models/CurrencyUpdate'; -export type { Datum } from './models/Datum'; -export type { GroupedModifierByEffect } from './models/GroupedModifierByEffect'; -export type { GroupedModifierProperties } from './models/GroupedModifierProperties'; -export type { HTTPValidationError } from './models/HTTPValidationError'; -export type { Influences } from './models/Influences'; -export type { Item } from './models/Item'; -export type { ItemBaseType } from './models/ItemBaseType'; -export type { ItemBaseTypeCreate } from './models/ItemBaseTypeCreate'; -export type { ItemBaseTypeUpdate } from './models/ItemBaseTypeUpdate'; -export type { ItemCreate } from './models/ItemCreate'; -export type { ItemModifier } from './models/ItemModifier'; -export type { ItemModifierCreate } from './models/ItemModifierCreate'; -export type { ItemSpecs } from './models/ItemSpecs'; -export type { League } from './models/League'; -export type { LeagueCreate } from './models/LeagueCreate'; -export type { LeagueUpdate } from './models/LeagueUpdate'; -export type { MetadataObject } from './models/MetadataObject'; -export type { Modifier } from './models/Modifier'; -export type { ModifierCreate } from './models/ModifierCreate'; -export type { ModifierLimitation } from './models/ModifierLimitation'; -export type { ModifierUpdate } from './models/ModifierUpdate'; -export type { PlotData } from './models/PlotData'; -export type { PlotQuery } from './models/PlotQuery'; -export type { TimeseriesData } from './models/TimeseriesData'; -export type { Token } from './models/Token'; -export type { TurnstileQuery } from './models/TurnstileQuery'; -export type { TurnstileResponse } from './models/TurnstileResponse'; -export type { UnidentifiedItem } from './models/UnidentifiedItem'; -export type { UnidentifiedItemCreate } from './models/UnidentifiedItemCreate'; -export type { ValidationError } from './models/ValidationError'; -export type { WantedModifier } from './models/WantedModifier'; +export type { BaseSpecs } from "./models/BaseSpecs"; +export type { Body_login_login_access_session } from "./models/Body_login_login_access_session"; +export type { Currency } from "./models/Currency"; +export type { CurrencyCreate } from "./models/CurrencyCreate"; +export type { CurrencyQuery } from "./models/CurrencyQuery"; +export type { CurrencyUpdate } from "./models/CurrencyUpdate"; +export type { Datum } from "./models/Datum"; +export type { GroupedModifierByEffect } from "./models/GroupedModifierByEffect"; +export type { GroupedModifierProperties } from "./models/GroupedModifierProperties"; +export type { HTTPValidationError } from "./models/HTTPValidationError"; +export type { Influences } from "./models/Influences"; +export type { Item } from "./models/Item"; +export type { ItemBaseType } from "./models/ItemBaseType"; +export type { ItemBaseTypeCreate } from "./models/ItemBaseTypeCreate"; +export type { ItemBaseTypeUpdate } from "./models/ItemBaseTypeUpdate"; +export type { ItemCreate } from "./models/ItemCreate"; +export type { ItemModifier } from "./models/ItemModifier"; +export type { ItemModifierCreate } from "./models/ItemModifierCreate"; +export type { ItemSpecs } from "./models/ItemSpecs"; +export type { League } from "./models/League"; +export type { LeagueCreate } from "./models/LeagueCreate"; +export type { LeagueUpdate } from "./models/LeagueUpdate"; +export type { MetadataObject } from "./models/MetadataObject"; +export type { Modifier } from "./models/Modifier"; +export type { ModifierCreate } from "./models/ModifierCreate"; +export type { ModifierLimitation } from "./models/ModifierLimitation"; +export type { ModifierUpdate } from "./models/ModifierUpdate"; +export type { PlotData } from "./models/PlotData"; +export type { PlotQuery } from "./models/PlotQuery"; +export type { TimeseriesData } from "./models/TimeseriesData"; +export type { Token } from "./models/Token"; +export type { TurnstileQuery } from "./models/TurnstileQuery"; +export type { TurnstileResponse } from "./models/TurnstileResponse"; +export type { UnidentifiedItem } from "./models/UnidentifiedItem"; +export type { UnidentifiedItemCreate } from "./models/UnidentifiedItemCreate"; +export type { ValidationError } from "./models/ValidationError"; +export type { WantedModifier } from "./models/WantedModifier"; -export { $BaseSpecs } from './schemas/$BaseSpecs'; -export { $Body_logins_login_access_session } from './schemas/$Body_logins_login_access_session'; -export { $Currency } from './schemas/$Currency'; -export { $CurrencyCreate } from './schemas/$CurrencyCreate'; -export { $CurrencyQuery } from './schemas/$CurrencyQuery'; -export { $CurrencyUpdate } from './schemas/$CurrencyUpdate'; -export { $Datum } from './schemas/$Datum'; -export { $GroupedModifierByEffect } from './schemas/$GroupedModifierByEffect'; -export { $GroupedModifierProperties } from './schemas/$GroupedModifierProperties'; -export { $HTTPValidationError } from './schemas/$HTTPValidationError'; -export { $Influences } from './schemas/$Influences'; -export { $Item } from './schemas/$Item'; -export { $ItemBaseType } from './schemas/$ItemBaseType'; -export { $ItemBaseTypeCreate } from './schemas/$ItemBaseTypeCreate'; -export { $ItemBaseTypeUpdate } from './schemas/$ItemBaseTypeUpdate'; -export { $ItemCreate } from './schemas/$ItemCreate'; -export { $ItemModifier } from './schemas/$ItemModifier'; -export { $ItemModifierCreate } from './schemas/$ItemModifierCreate'; -export { $ItemSpecs } from './schemas/$ItemSpecs'; -export { $League } from './schemas/$League'; -export { $LeagueCreate } from './schemas/$LeagueCreate'; -export { $LeagueUpdate } from './schemas/$LeagueUpdate'; -export { $MetadataObject } from './schemas/$MetadataObject'; -export { $Modifier } from './schemas/$Modifier'; -export { $ModifierCreate } from './schemas/$ModifierCreate'; -export { $ModifierLimitation } from './schemas/$ModifierLimitation'; -export { $ModifierUpdate } from './schemas/$ModifierUpdate'; -export { $PlotData } from './schemas/$PlotData'; -export { $PlotQuery } from './schemas/$PlotQuery'; -export { $TimeseriesData } from './schemas/$TimeseriesData'; -export { $Token } from './schemas/$Token'; -export { $TurnstileQuery } from './schemas/$TurnstileQuery'; -export { $TurnstileResponse } from './schemas/$TurnstileResponse'; -export { $UnidentifiedItem } from './schemas/$UnidentifiedItem'; -export { $UnidentifiedItemCreate } from './schemas/$UnidentifiedItemCreate'; -export { $ValidationError } from './schemas/$ValidationError'; -export { $WantedModifier } from './schemas/$WantedModifier'; +export { $BaseSpecs } from "./schemas/$BaseSpecs"; +export { $Body_login_login_access_session } from "./schemas/$Body_login_login_access_session"; +export { $Currency } from "./schemas/$Currency"; +export { $CurrencyCreate } from "./schemas/$CurrencyCreate"; +export { $CurrencyQuery } from "./schemas/$CurrencyQuery"; +export { $CurrencyUpdate } from "./schemas/$CurrencyUpdate"; +export { $Datum } from "./schemas/$Datum"; +export { $GroupedModifierByEffect } from "./schemas/$GroupedModifierByEffect"; +export { $GroupedModifierProperties } from "./schemas/$GroupedModifierProperties"; +export { $HTTPValidationError } from "./schemas/$HTTPValidationError"; +export { $Influences } from "./schemas/$Influences"; +export { $Item } from "./schemas/$Item"; +export { $ItemBaseType } from "./schemas/$ItemBaseType"; +export { $ItemBaseTypeCreate } from "./schemas/$ItemBaseTypeCreate"; +export { $ItemBaseTypeUpdate } from "./schemas/$ItemBaseTypeUpdate"; +export { $ItemCreate } from "./schemas/$ItemCreate"; +export { $ItemModifier } from "./schemas/$ItemModifier"; +export { $ItemModifierCreate } from "./schemas/$ItemModifierCreate"; +export { $ItemSpecs } from "./schemas/$ItemSpecs"; +export { $League } from "./schemas/$League"; +export { $LeagueCreate } from "./schemas/$LeagueCreate"; +export { $LeagueUpdate } from "./schemas/$LeagueUpdate"; +export { $MetadataObject } from "./schemas/$MetadataObject"; +export { $Modifier } from "./schemas/$Modifier"; +export { $ModifierCreate } from "./schemas/$ModifierCreate"; +export { $ModifierLimitation } from "./schemas/$ModifierLimitation"; +export { $ModifierUpdate } from "./schemas/$ModifierUpdate"; +export { $PlotData } from "./schemas/$PlotData"; +export { $PlotQuery } from "./schemas/$PlotQuery"; +export { $TimeseriesData } from "./schemas/$TimeseriesData"; +export { $Token } from "./schemas/$Token"; +export { $TurnstileQuery } from "./schemas/$TurnstileQuery"; +export { $TurnstileResponse } from "./schemas/$TurnstileResponse"; +export { $UnidentifiedItem } from "./schemas/$UnidentifiedItem"; +export { $UnidentifiedItemCreate } from "./schemas/$UnidentifiedItemCreate"; +export { $ValidationError } from "./schemas/$ValidationError"; +export { $WantedModifier } from "./schemas/$WantedModifier"; -export { CurrencysService } from './services/CurrencysService'; -export { ItemBaseTypesService } from './services/ItemBaseTypesService'; -export { ItemModifiersService } from './services/ItemModifiersService'; -export { ItemsService } from './services/ItemsService'; -export { LatestCurrenciesService } from './services/LatestCurrenciesService'; -export { LatestCurrencyIdService } from './services/LatestCurrencyIdService'; -export { LatestHourService } from './services/LatestHourService'; -export { LatestItemIdService } from './services/LatestItemIdService'; -export { LeaguesService } from './services/LeaguesService'; -export { LoginsService } from './services/LoginsService'; -export { ModifiersService } from './services/ModifiersService'; -export { PlotsService } from './services/PlotsService'; -export { TestsService } from './services/TestsService'; -export { TurnstilesService } from './services/TurnstilesService'; -export { UnidentifiedItemsService } from './services/UnidentifiedItemsService'; +export { CurrencyService } from "./services/CurrencyService"; +export { ItemService } from "./services/ItemService"; +export { ItemBaseTypeService } from "./services/ItemBaseTypeService"; +export { ItemModifierService } from "./services/ItemModifierService"; +export { LatestCurrenciesService } from "./services/LatestCurrenciesService"; +export { LatestCurrencyIdService } from "./services/LatestCurrencyIdService"; +export { LatestHoursService } from "./services/LatestHoursService"; +export { LatestItemIdService } from "./services/LatestItemIdService"; +export { LeagueService } from "./services/LeagueService"; +export { LoginService } from "./services/LoginService"; +export { ModifierService } from "./services/ModifierService"; +export { PlotService } from "./services/PlotService"; +export { TestService } from "./services/TestService"; +export { TurnstileService } from "./services/TurnstileService"; +export { UnidentifiedItemService } from "./services/UnidentifiedItemService"; diff --git a/src/frontend/src/client/models/Body_logins_login_access_session.ts b/src/frontend/src/client/models/Body_login_login_access_session.ts similarity index 86% rename from src/frontend/src/client/models/Body_logins_login_access_session.ts rename to src/frontend/src/client/models/Body_login_login_access_session.ts index 24a927e14..28dedc0cc 100644 --- a/src/frontend/src/client/models/Body_logins_login_access_session.ts +++ b/src/frontend/src/client/models/Body_login_login_access_session.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type Body_logins_login_access_session = { +export type Body_login_login_access_session = { grant_type?: (string | null); username: string; password: string; diff --git a/src/frontend/src/client/models/League.ts b/src/frontend/src/client/models/League.ts index 7df8d2165..d2af09e3f 100644 --- a/src/frontend/src/client/models/League.ts +++ b/src/frontend/src/client/models/League.ts @@ -3,9 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type League = { - name: string; - validFrom: string; - validTo?: (string | null); - leagueId: number; + name: string; + validFrom: string; + validTo?: string | null; + version: number; + leagueId: number; }; - diff --git a/src/frontend/src/client/models/LeagueCreate.ts b/src/frontend/src/client/models/LeagueCreate.ts index 1eab1893b..7a1446143 100644 --- a/src/frontend/src/client/models/LeagueCreate.ts +++ b/src/frontend/src/client/models/LeagueCreate.ts @@ -3,8 +3,8 @@ /* tslint:disable */ /* eslint-disable */ export type LeagueCreate = { - name: string; - validFrom: string; - validTo?: (string | null); + name: string; + validFrom: string; + validTo?: string | null; + version: number; }; - diff --git a/src/frontend/src/client/models/LeagueUpdate.ts b/src/frontend/src/client/models/LeagueUpdate.ts index 783f75ee2..5bee70b47 100644 --- a/src/frontend/src/client/models/LeagueUpdate.ts +++ b/src/frontend/src/client/models/LeagueUpdate.ts @@ -3,9 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type LeagueUpdate = { - leagueId: number; - name: string; - validFrom: string; - validTo?: (string | null); + name: string; + validFrom: string; + validTo?: string | null; + version: number; + leagueId: number; }; - diff --git a/src/frontend/src/client/models/TimeseriesData.ts b/src/frontend/src/client/models/TimeseriesData.ts index 6dc744fa8..847a0b199 100644 --- a/src/frontend/src/client/models/TimeseriesData.ts +++ b/src/frontend/src/client/models/TimeseriesData.ts @@ -4,7 +4,7 @@ /* eslint-disable */ import type { Datum } from './Datum'; export type TimeseriesData = { - name: number; + leagueId: number; data: Array; confidenceRating: 'low' | 'medium' | 'high'; }; diff --git a/src/frontend/src/client/models/UnidentifiedItem.ts b/src/frontend/src/client/models/UnidentifiedItem.ts index 0a6fbbd82..447a26c5b 100644 --- a/src/frontend/src/client/models/UnidentifiedItem.ts +++ b/src/frontend/src/client/models/UnidentifiedItem.ts @@ -11,8 +11,8 @@ export type UnidentifiedItem = { identified?: boolean; currencyAmount?: (number | null); currencyId?: (number | null); - nItems: number; - aggregated: boolean; + nItems?: number; + aggregated?: boolean; createdHoursSinceLaunch: number; itemId: number; }; diff --git a/src/frontend/src/client/schemas/$Body_logins_login_access_session.ts b/src/frontend/src/client/schemas/$Body_login_login_access_session.ts similarity index 95% rename from src/frontend/src/client/schemas/$Body_logins_login_access_session.ts rename to src/frontend/src/client/schemas/$Body_login_login_access_session.ts index 57daaa868..d8b8a4efe 100644 --- a/src/frontend/src/client/schemas/$Body_logins_login_access_session.ts +++ b/src/frontend/src/client/schemas/$Body_login_login_access_session.ts @@ -2,7 +2,7 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export const $Body_logins_login_access_session = { +export const $Body_login_login_access_session = { properties: { grant_type: { type: 'any-of', diff --git a/src/frontend/src/client/schemas/$League.ts b/src/frontend/src/client/schemas/$League.ts index cf64c511b..b5c8e7004 100644 --- a/src/frontend/src/client/schemas/$League.ts +++ b/src/frontend/src/client/schemas/$League.ts @@ -3,28 +3,35 @@ /* tslint:disable */ /* eslint-disable */ export const $League = { - properties: { - name: { - type: 'string', - isRequired: true, - }, - validFrom: { - type: 'string', - isRequired: true, - format: 'date-time', - }, - validTo: { - type: 'any-of', - contains: [{ - type: 'string', - format: 'date-time', - }, { - type: 'null', - }], + properties: { + name: { + type: "string", + isRequired: true, + }, + validFrom: { + type: "string", + isRequired: true, + format: "date-time", + }, + validTo: { + type: "any-of", + contains: [ + { + type: "string", + format: "date-time", }, - leagueId: { - type: 'number', - isRequired: true, + { + type: "null", }, + ], + }, + version: { + type: "number", + isRequired: true, + }, + leagueId: { + type: "number", + isRequired: true, }, + }, } as const; diff --git a/src/frontend/src/client/schemas/$LeagueCreate.ts b/src/frontend/src/client/schemas/$LeagueCreate.ts index a9dd8ff30..abd09a3ed 100644 --- a/src/frontend/src/client/schemas/$LeagueCreate.ts +++ b/src/frontend/src/client/schemas/$LeagueCreate.ts @@ -3,24 +3,31 @@ /* tslint:disable */ /* eslint-disable */ export const $LeagueCreate = { - properties: { - name: { - type: 'string', - isRequired: true, - }, - validFrom: { - type: 'string', - isRequired: true, - format: 'date-time', + properties: { + name: { + type: "string", + isRequired: true, + }, + validFrom: { + type: "string", + isRequired: true, + format: "date-time", + }, + validTo: { + type: "any-of", + contains: [ + { + type: "string", + format: "date-time", }, - validTo: { - type: 'any-of', - contains: [{ - type: 'string', - format: 'date-time', - }, { - type: 'null', - }], + { + type: "null", }, + ], + }, + version: { + type: "number", + isRequired: true, }, + }, } as const; diff --git a/src/frontend/src/client/schemas/$LeagueUpdate.ts b/src/frontend/src/client/schemas/$LeagueUpdate.ts index d09bbf72c..ed324181d 100644 --- a/src/frontend/src/client/schemas/$LeagueUpdate.ts +++ b/src/frontend/src/client/schemas/$LeagueUpdate.ts @@ -3,28 +3,35 @@ /* tslint:disable */ /* eslint-disable */ export const $LeagueUpdate = { - properties: { - leagueId: { - type: 'number', - isRequired: true, - }, - name: { - type: 'string', - isRequired: true, - }, - validFrom: { - type: 'string', - isRequired: true, - format: 'date-time', + properties: { + name: { + type: "string", + isRequired: true, + }, + validFrom: { + type: "string", + isRequired: true, + format: "date-time", + }, + validTo: { + type: "any-of", + contains: [ + { + type: "string", + format: "date-time", }, - validTo: { - type: 'any-of', - contains: [{ - type: 'string', - format: 'date-time', - }, { - type: 'null', - }], + { + type: "null", }, + ], + }, + version: { + type: "number", + isRequired: true, + }, + leagueId: { + type: "number", + isRequired: true, }, + }, } as const; diff --git a/src/frontend/src/client/schemas/$TimeseriesData.ts b/src/frontend/src/client/schemas/$TimeseriesData.ts index 4a2a2a376..fbcbef95e 100644 --- a/src/frontend/src/client/schemas/$TimeseriesData.ts +++ b/src/frontend/src/client/schemas/$TimeseriesData.ts @@ -4,7 +4,7 @@ /* eslint-disable */ export const $TimeseriesData = { properties: { - name: { + leagueId: { type: 'number', isRequired: true, }, diff --git a/src/frontend/src/client/schemas/$UnidentifiedItem.ts b/src/frontend/src/client/schemas/$UnidentifiedItem.ts index a9ac8dc70..944323d95 100644 --- a/src/frontend/src/client/schemas/$UnidentifiedItem.ts +++ b/src/frontend/src/client/schemas/$UnidentifiedItem.ts @@ -49,11 +49,9 @@ export const $UnidentifiedItem = { }, nItems: { type: 'number', - isRequired: true, }, aggregated: { type: 'boolean', - isRequired: true, }, createdHoursSinceLaunch: { type: 'number', diff --git a/src/frontend/src/client/services/CurrencysService.ts b/src/frontend/src/client/services/CurrencyService.ts similarity index 83% rename from src/frontend/src/client/services/CurrencysService.ts rename to src/frontend/src/client/services/CurrencyService.ts index 5f2df8b92..53938b96a 100644 --- a/src/frontend/src/client/services/CurrencysService.ts +++ b/src/frontend/src/client/services/CurrencyService.ts @@ -9,7 +9,7 @@ import type { CurrencyUpdate } from '../models/CurrencyUpdate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class CurrencysService { +export class CurrencyService { /** * Get Currency * Get currency by key and value for "currencyId". @@ -163,27 +163,51 @@ export class CurrencysService { }); } /** - * Get Latest Hour - * Return -1 if database is empty + * Get Latest Hours + * Returns a dict mapping league id to the most recent hour relating to that league. Dict is empty if no currencies exist. + * + * Does not guarantee an entry for every league id. * @returns number Successful Response * @throws ApiError */ - public static getLatestHour(): CancelablePromise { + public static getLatestHours({ + leagueIds, + }: { + leagueIds: Array, + }): CancelablePromise> { return __request(OpenAPI, { method: 'GET', - url: '/api/api_v1/currency/latest_hour/', + url: '/api/api_v1/currency/latest_hours/', + query: { + 'league_ids': leagueIds, + }, + errors: { + 422: `Validation Error`, + }, }); } /** * Get Latest Currencies - * Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + * Returns a dict mapping league id to most recent currencies relating to that league. Dict is empty if no currencies exist. + * + * Does not guarantee an entry for every league id. * @returns Currency Successful Response * @throws ApiError */ - public static getLatestCurrencies(): CancelablePromise> { + public static getLatestCurrencies({ + leagueIds, + }: { + leagueIds: Array, + }): CancelablePromise>> { return __request(OpenAPI, { method: 'GET', url: '/api/api_v1/currency/latest_currencies/', + query: { + 'league_ids': leagueIds, + }, + errors: { + 422: `Validation Error`, + }, }); } /** diff --git a/src/frontend/src/client/services/ItemBaseTypesService.ts b/src/frontend/src/client/services/ItemBaseTypeService.ts similarity index 99% rename from src/frontend/src/client/services/ItemBaseTypesService.ts rename to src/frontend/src/client/services/ItemBaseTypeService.ts index 8f215a086..320dfaa99 100644 --- a/src/frontend/src/client/services/ItemBaseTypesService.ts +++ b/src/frontend/src/client/services/ItemBaseTypeService.ts @@ -8,7 +8,7 @@ import type { ItemBaseTypeUpdate } from '../models/ItemBaseTypeUpdate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class ItemBaseTypesService { +export class ItemBaseTypeService { /** * Get Item Base Type * Get item base type by key and value for "itemBaseTypeId". diff --git a/src/frontend/src/client/services/ItemModifiersService.ts b/src/frontend/src/client/services/ItemModifierService.ts similarity index 98% rename from src/frontend/src/client/services/ItemModifiersService.ts rename to src/frontend/src/client/services/ItemModifierService.ts index 5468dc76e..bad76ffe0 100644 --- a/src/frontend/src/client/services/ItemModifiersService.ts +++ b/src/frontend/src/client/services/ItemModifierService.ts @@ -7,7 +7,7 @@ import type { ItemModifierCreate } from '../models/ItemModifierCreate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class ItemModifiersService { +export class ItemModifierService { /** * Get All Item Modifiers * Get all item modifiers. diff --git a/src/frontend/src/client/services/ItemsService.ts b/src/frontend/src/client/services/ItemService.ts similarity index 98% rename from src/frontend/src/client/services/ItemsService.ts rename to src/frontend/src/client/services/ItemService.ts index 0c10cf6a2..1bf7d4480 100644 --- a/src/frontend/src/client/services/ItemsService.ts +++ b/src/frontend/src/client/services/ItemService.ts @@ -7,7 +7,7 @@ import type { ItemCreate } from '../models/ItemCreate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class ItemsService { +export class ItemService { /** * Get Latest Item Id * Get the latest "itemId" diff --git a/src/frontend/src/client/services/LatestCurrenciesService.ts b/src/frontend/src/client/services/LatestCurrenciesService.ts index 4b42c6e36..690aee2d1 100644 --- a/src/frontend/src/client/services/LatestCurrenciesService.ts +++ b/src/frontend/src/client/services/LatestCurrenciesService.ts @@ -9,14 +9,26 @@ import { request as __request } from '../core/request'; export class LatestCurrenciesService { /** * Get Latest Currencies - * Returns a list of the latest currencies, which all share the same `createdHoursSinceLaunch` as defined by `latest_hour` endpoint. + * Returns a dict mapping league id to most recent currencies relating to that league. Dict is empty if no currencies exist. + * + * Does not guarantee an entry for every league id. * @returns Currency Successful Response * @throws ApiError */ - public static getLatestCurrencies(): CancelablePromise> { + public static getLatestCurrencies({ + leagueIds, + }: { + leagueIds: Array, + }): CancelablePromise>> { return __request(OpenAPI, { method: 'GET', url: '/api/api_v1/currency/latest_currencies/', + query: { + 'league_ids': leagueIds, + }, + errors: { + 422: `Validation Error`, + }, }); } } diff --git a/src/frontend/src/client/services/LatestHourService.ts b/src/frontend/src/client/services/LatestHourService.ts deleted file mode 100644 index 25abb44e7..000000000 --- a/src/frontend/src/client/services/LatestHourService.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; -export class LatestHourService { - /** - * Get Latest Hour - * Return -1 if database is empty - * @returns number Successful Response - * @throws ApiError - */ - public static getLatestHour(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/api_v1/currency/latest_hour/', - }); - } -} diff --git a/src/frontend/src/client/services/LatestHoursService.ts b/src/frontend/src/client/services/LatestHoursService.ts new file mode 100644 index 000000000..76b5753b2 --- /dev/null +++ b/src/frontend/src/client/services/LatestHoursService.ts @@ -0,0 +1,33 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class LatestHoursService { + /** + * Get Latest Hours + * Returns a dict mapping league id to the most recent hour relating to that league. Dict is empty if no currencies exist. + * + * Does not guarantee an entry for every league id. + * @returns number Successful Response + * @throws ApiError + */ + public static getLatestHours({ + leagueIds, + }: { + leagueIds: Array, + }): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/api_v1/currency/latest_hours/', + query: { + 'league_ids': leagueIds, + }, + errors: { + 422: `Validation Error`, + }, + }); + } +} diff --git a/src/frontend/src/client/services/LeaguesService.ts b/src/frontend/src/client/services/LeagueService.ts similarity index 99% rename from src/frontend/src/client/services/LeaguesService.ts rename to src/frontend/src/client/services/LeagueService.ts index f8d18660f..8812b05f5 100644 --- a/src/frontend/src/client/services/LeaguesService.ts +++ b/src/frontend/src/client/services/LeagueService.ts @@ -8,7 +8,7 @@ import type { LeagueUpdate } from '../models/LeagueUpdate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class LeaguesService { +export class LeagueService { /** * Get League * Get league by key and value for "leagueId". diff --git a/src/frontend/src/client/services/LoginsService.ts b/src/frontend/src/client/services/LoginService.ts similarity index 83% rename from src/frontend/src/client/services/LoginsService.ts rename to src/frontend/src/client/services/LoginService.ts index ebdaca76d..1277e64be 100644 --- a/src/frontend/src/client/services/LoginsService.ts +++ b/src/frontend/src/client/services/LoginService.ts @@ -2,12 +2,12 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Body_logins_login_access_session } from '../models/Body_logins_login_access_session'; +import type { Body_login_login_access_session } from '../models/Body_login_login_access_session'; import type { Token } from '../models/Token'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class LoginsService { +export class LoginService { /** * Login Access Session * OAuth2 compatible session login. @@ -17,7 +17,7 @@ export class LoginsService { public static loginAccessSession({ formData, }: { - formData: Body_logins_login_access_session, + formData: Body_login_login_access_session, }): CancelablePromise { return __request(OpenAPI, { method: 'POST', diff --git a/src/frontend/src/client/services/ModifiersService.ts b/src/frontend/src/client/services/ModifierService.ts similarity index 99% rename from src/frontend/src/client/services/ModifiersService.ts rename to src/frontend/src/client/services/ModifierService.ts index 97223d5c6..90b8b3188 100644 --- a/src/frontend/src/client/services/ModifiersService.ts +++ b/src/frontend/src/client/services/ModifierService.ts @@ -9,7 +9,7 @@ import type { ModifierUpdate } from '../models/ModifierUpdate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class ModifiersService { +export class ModifierService { /** * Get Modifier * Get modifier or list of modifiers by key and diff --git a/src/frontend/src/client/services/PlotsService.ts b/src/frontend/src/client/services/PlotService.ts similarity index 97% rename from src/frontend/src/client/services/PlotsService.ts rename to src/frontend/src/client/services/PlotService.ts index bfc21dcd1..b6d774258 100644 --- a/src/frontend/src/client/services/PlotsService.ts +++ b/src/frontend/src/client/services/PlotService.ts @@ -7,7 +7,7 @@ import type { PlotQuery } from '../models/PlotQuery'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class PlotsService { +export class PlotService { /** * Get Plot Data * Takes a query based on the 'PlotQuery' schema and retrieves data diff --git a/src/frontend/src/client/services/TestsService.ts b/src/frontend/src/client/services/TestService.ts similarity index 98% rename from src/frontend/src/client/services/TestsService.ts rename to src/frontend/src/client/services/TestService.ts index 8f8686985..4d9bba7b9 100644 --- a/src/frontend/src/client/services/TestsService.ts +++ b/src/frontend/src/client/services/TestService.ts @@ -5,7 +5,7 @@ import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class TestsService { +export class TestService { /** * Bulk Insert Test * Can only be used in `settings.ENVIRONMENT=local` environment. diff --git a/src/frontend/src/client/services/TurnstilesService.ts b/src/frontend/src/client/services/TurnstileService.ts similarity index 97% rename from src/frontend/src/client/services/TurnstilesService.ts rename to src/frontend/src/client/services/TurnstileService.ts index 7ab9afc52..a520df9c9 100644 --- a/src/frontend/src/client/services/TurnstilesService.ts +++ b/src/frontend/src/client/services/TurnstileService.ts @@ -7,7 +7,7 @@ import type { TurnstileResponse } from '../models/TurnstileResponse'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class TurnstilesService { +export class TurnstileService { /** * Get Turnstile Validation * Takes a query based on the 'TurnstileQuery' schema and retrieves data diff --git a/src/frontend/src/client/services/UnidentifiedItemsService.ts b/src/frontend/src/client/services/UnidentifiedItemService.ts similarity index 98% rename from src/frontend/src/client/services/UnidentifiedItemsService.ts rename to src/frontend/src/client/services/UnidentifiedItemService.ts index 456d84b63..c6112bdcf 100644 --- a/src/frontend/src/client/services/UnidentifiedItemsService.ts +++ b/src/frontend/src/client/services/UnidentifiedItemService.ts @@ -7,7 +7,7 @@ import type { UnidentifiedItemCreate } from '../models/UnidentifiedItemCreate'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; -export class UnidentifiedItemsService { +export class UnidentifiedItemService { /** * Get Latest Item Id * Get the latest "itemId" diff --git a/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx b/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx index d26231257..57b1e7b2e 100644 --- a/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx +++ b/src/frontend/src/components/Common/DateDaysHoursSinceLaunchStats.tsx @@ -36,7 +36,6 @@ const DateDaysHoursSinceLaunchStats = (props: StatProps) => { const [localHoursSinceLaunch, setLocalHoursSinceLaunch] = useState(curHoursSinceLaunch); useEffect(() => { - console.log(currentTime, leagueLaunch); const curHoursSinceLaunch = getHoursSinceLaunch(currentTime, leagueLaunch); setLocalHoursSinceLaunch(hoursSinceLaunch); if (curHoursSinceLaunch !== hoursSinceLaunch) { diff --git a/src/frontend/src/components/Common/QueryButtons.tsx b/src/frontend/src/components/Common/QueryButtons.tsx index 96355461a..f8bf3d5b8 100644 --- a/src/frontend/src/components/Common/QueryButtons.tsx +++ b/src/frontend/src/components/Common/QueryButtons.tsx @@ -9,7 +9,6 @@ import { import { useErrorStore } from "../../store/ErrorStore"; import { ErrorMessage } from "../../components/Input/StandardLayoutInput/ErrorMessage"; import { getOptimizedPlotQuery } from "../../hooks/graphing/utils"; -import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; const QueryButtons = (props: FlexProps) => { const { setExpandedGraphInputFilters } = useExpandedComponentStore(); @@ -21,7 +20,6 @@ const QueryButtons = (props: FlexProps) => { modifiersUnidentifiedError, baseSpecDoesNotMatchError, } = useErrorStore(); - const { hoursSinceLaunch } = useLeagueLaunchStats(); const { stateHash, fetchStatus, setHashFromStore, setPlotQuery } = useGraphInputStore(); const isFetching = fetchStatus === "fetching"; @@ -44,13 +42,13 @@ const QueryButtons = (props: FlexProps) => { }, 10); }; - const handlePlotQuery = (hoursSinceLaunch: number) => { + const handlePlotQuery = () => { if (isFetching) return; if (stateHash) return; const leagueValid = checkGraphQueryLeagueInput(); const modifierValid = checkGraphQueryModifierInput(); if (!leagueValid || !modifierValid) return; - const plotQuery = getOptimizedPlotQuery(hoursSinceLaunch); + const plotQuery = getOptimizedPlotQuery(); if (plotQuery === undefined) return; setPlotQuery(plotQuery); useGraphInputStore.getState().setQueryClicked(); @@ -91,7 +89,7 @@ const QueryButtons = (props: FlexProps) => { borderColor="ui.grey" width={["inputSizes.defaultBox", "inputSizes.lgBox"]} maxW="98vw" - onClick={() => handlePlotQuery(hoursSinceLaunch)} + onClick={handlePlotQuery} disabled={isFetching} opacity={isFetching ? 0.5 : 1} cursor={isFetching ? "not-allowed" : "pointer"} diff --git a/src/frontend/src/components/Graph/GraphComponent.tsx b/src/frontend/src/components/Graph/GraphComponent.tsx index 386736616..3e192fc66 100644 --- a/src/frontend/src/components/Graph/GraphComponent.tsx +++ b/src/frontend/src/components/Graph/GraphComponent.tsx @@ -51,7 +51,7 @@ function GraphComponent(props: BoxProps) { const showSecondary = usePlotSettingsStore((state) => state.showSecondary); if (error || plotData == undefined) return; - const fetchedLeagueIds = plotData.data.map((val) => val.name); + const fetchedLeagueIds = plotData.data.map((val) => val.leagueId); const fetchedLeagues = choosableLeagues .filter((league) => fetchedLeagueIds.includes(league.leagueId)) .map((league) => league.name); @@ -174,12 +174,12 @@ function GraphComponent(props: BoxProps) { {plotData.data.map( (series, idx) => - fetchedLeagueIds.includes(series.name) && ( + fetchedLeagueIds.includes(series.leagueId) && ( - fetchedLeagueIds.includes(series.name) && ( + fetchedLeagueIds.includes(series.leagueId) && ( { @@ -8,7 +8,7 @@ export const useGetItemBaseTypes = async (queryClient: QueryClient) => { const itemBaseTypes = await queryClient.fetchQuery({ queryKey: ["baseTypeValues"], queryFn: async () => { - const data = await ItemBaseTypesService.getAllItemBaseTypes({}); + const data = await ItemBaseTypeService.getAllItemBaseTypes({}); return Array.isArray(data) ? data : [data]; }, }); diff --git a/src/frontend/src/hooks/getData/getGroupedModifiers.tsx b/src/frontend/src/hooks/getData/getGroupedModifiers.tsx index cc919d702..fc898b92f 100644 --- a/src/frontend/src/hooks/getData/getGroupedModifiers.tsx +++ b/src/frontend/src/hooks/getData/getGroupedModifiers.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { ModifiersService, GroupedModifierByEffect } from "../../client"; +import { ModifierService, GroupedModifierByEffect } from "../../client"; import { useQuery } from "@tanstack/react-query"; // Get all grouped modifiers by effect @@ -11,9 +11,7 @@ export const GetGroupedModifiersByEffect = () => { useQuery({ queryKey: ["allModifiers"], queryFn: async () => { - setModifiers( - await ModifiersService.getGroupedModifierByEffect() - ); + setModifiers(await ModifierService.getGroupedModifierByEffect()); return 1; }, }); diff --git a/src/frontend/src/hooks/getData/getLeagues.tsx b/src/frontend/src/hooks/getData/getLeagues.tsx index fb0404910..ca4efd8b9 100644 --- a/src/frontend/src/hooks/getData/getLeagues.tsx +++ b/src/frontend/src/hooks/getData/getLeagues.tsx @@ -1,4 +1,4 @@ -import { LeaguesService } from "../../client"; +import { LeagueService } from "../../client"; import { QueryClient } from "@tanstack/react-query"; export const useGetLeagues = async (queryClient: QueryClient) => { @@ -6,7 +6,7 @@ export const useGetLeagues = async (queryClient: QueryClient) => { const leagues = await queryClient.fetchQuery({ queryKey: ["allModifiers"], queryFn: async () => { - const data = await LeaguesService.getAllLeagues({}); + const data = await LeagueService.getAllLeagues({}); return Array.isArray(data) ? data : [data]; }, }); diff --git a/src/frontend/src/hooks/getData/prefetchGroupedModifiers.tsx b/src/frontend/src/hooks/getData/prefetchGroupedModifiers.tsx index a8396f8fb..19c3efac2 100644 --- a/src/frontend/src/hooks/getData/prefetchGroupedModifiers.tsx +++ b/src/frontend/src/hooks/getData/prefetchGroupedModifiers.tsx @@ -1,23 +1,22 @@ import { useQueryClient } from "@tanstack/react-query"; -import { ModifiersService } from "../../client"; +import { ModifierService } from "../../client"; export const useGetGroupedModifiers = async () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient(); - try { - // Fetch and return the grouped modifiers directly - const groupedModifiers = await queryClient.fetchQuery({ - queryKey: ["prefetchedGroupedModifiers"], - queryFn: async () => { - const fetchedData = - await ModifiersService.getGroupedModifierByEffect(); - return Array.isArray(fetchedData) ? fetchedData : [fetchedData]; - }, - }); + try { + // Fetch and return the grouped modifiers directly + const groupedModifiers = await queryClient.fetchQuery({ + queryKey: ["prefetchedGroupedModifiers"], + queryFn: async () => { + const fetchedData = await ModifierService.getGroupedModifierByEffect(); + return Array.isArray(fetchedData) ? fetchedData : [fetchedData]; + }, + }); - return { groupedModifiers }; - } catch (error) { - console.log(error); - return { groupedModifiers: [] }; // Return an empty array on error - } + return { groupedModifiers }; + } catch (error) { + console.log(error); + return { groupedModifiers: [] }; // Return an empty array on error + } }; diff --git a/src/frontend/src/hooks/graphing/postPlottingData.tsx b/src/frontend/src/hooks/graphing/postPlottingData.tsx index 9ba8ea10b..54c8de8e2 100755 --- a/src/frontend/src/hooks/graphing/postPlottingData.tsx +++ b/src/frontend/src/hooks/graphing/postPlottingData.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { PlotsService, PlotQuery, PlotData } from "../../client"; +import { PlotService, PlotQuery, PlotData } from "../../client"; import { useQuery } from "@tanstack/react-query"; import { useGraphInputStore } from "../../store/GraphInputStore"; @@ -22,7 +22,7 @@ function usePostPlottingData(plotQuery: PlotQuery): { const { fetchStatus, refetch, isFetched, isError, error } = useQuery({ queryKey: ["allPlotData"], queryFn: async () => { - const returnBody = await PlotsService.getPlotData({ + const returnBody = await PlotService.getPlotData({ requestBody: plotQuery, }); diff --git a/src/frontend/src/hooks/graphing/processPlottingData.tsx b/src/frontend/src/hooks/graphing/processPlottingData.tsx index 09abac0c1..fe87959e9 100644 --- a/src/frontend/src/hooks/graphing/processPlottingData.tsx +++ b/src/frontend/src/hooks/graphing/processPlottingData.tsx @@ -40,7 +40,7 @@ function useGetPlotData(plotQuery: PlotQuery): { mostCommonCurrencyUsed: mostCommonCurrencyUsed, data: plotData.data.map((val) => ({ confidenceRating: val.confidenceRating, - name: val.name, + leagueId: val.leagueId, data: [], })), }; diff --git a/src/frontend/src/hooks/graphing/utils.tsx b/src/frontend/src/hooks/graphing/utils.tsx index 5bf714f25..1d5bc2ced 100644 --- a/src/frontend/src/hooks/graphing/utils.tsx +++ b/src/frontend/src/hooks/graphing/utils.tsx @@ -7,14 +7,16 @@ import { ModifierLimitationState, } from "../../store/StateInterface"; import { PLOTTING_WINDOW_HOURS } from "../../config"; +import { useLeagueLaunchStats } from "../../store/LeagueLaunchStatsStore"; export const getCurrentLeague = (choosableLeagues: League | League[]) => { choosableLeagues = Array.isArray(choosableLeagues) ? choosableLeagues : [choosableLeagues]; const now = Date.now(); - const currentLeague = choosableLeagues.reduce( - (latest, league) => { + const currentLeague = choosableLeagues + .filter((league) => !league.name.startsWith("Hardcore")) + .reduce((latest, league) => { const validFrom = Date.parse(league.validFrom); const validTo = league.validTo ? Date.parse(league.validTo) @@ -26,9 +28,7 @@ export const getCurrentLeague = (choosableLeagues: League | League[]) => { return league; } return latest; - }, - undefined, - ); + }, undefined); return currentLeague; }; @@ -179,9 +179,7 @@ export const updateNumericalRoll = ( return { wantedModifierExtended: updatedModifiersExtended }; }; -export const getOptimizedPlotQuery = ( - hoursSinceLaunch: number, -): PlotQuery | undefined => { +export const getOptimizedPlotQuery = (): PlotQuery | undefined => { // currently always runs, needs to be in if check // when Non-unique rarity is possible const state = useGraphInputStore.getState(); @@ -267,7 +265,7 @@ export const getOptimizedPlotQuery = ( modifierId: wantedMoidifierExtended.modifierId, modifierLimitations: wantedMoidifierExtended.modifierLimitations, })); - const end = hoursSinceLaunch; + const end = useLeagueLaunchStats.getState().hoursSinceLaunch; const window = PLOTTING_WINDOW_HOURS; const start = end - window; diff --git a/src/frontend/src/hooks/validation/turnstileValidation.tsx b/src/frontend/src/hooks/validation/turnstileValidation.tsx index b95f8bd8c..7c98996df 100644 --- a/src/frontend/src/hooks/validation/turnstileValidation.tsx +++ b/src/frontend/src/hooks/validation/turnstileValidation.tsx @@ -3,7 +3,7 @@ import { ApiError, TurnstileQuery, TurnstileResponse, - TurnstilesService, + TurnstileService, } from "../../client"; import { useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -37,7 +37,7 @@ const useTurnstileValidation = (requestBody?: TurnstileQuery) => { } = useQuery({ queryKey: ["turnstile", hasCompletedCaptcha()], queryFn: async (): Promise => { - const turnstileResponse = await TurnstilesService.getTurnstileValidation({ + const turnstileResponse = await TurnstileService.getTurnstileValidation({ requestBody: { token: requestBody?.token ?? token, ip: requestBody?.ip ?? ip, @@ -62,7 +62,7 @@ const useTurnstileValidation = (requestBody?: TurnstileQuery) => { const completeCaptcha = async (data: TurnstileQuery) => { console.log("Completing captcha..."); - await TurnstilesService.getTurnstileValidation({ + await TurnstileService.getTurnstileValidation({ requestBody: data, }); }; @@ -74,7 +74,7 @@ const useTurnstileValidation = (requestBody?: TurnstileQuery) => { setToken(requestBody?.token ?? ""); localStorage.setItem( "turnstile_captcha_token", - requestBody?.token ?? token + requestBody?.token ?? token, ); navigate({ to: from ? "/" + `${from}` : "/" }); }, diff --git a/src/frontend/src/routes/_layout/index.tsx b/src/frontend/src/routes/_layout/index.tsx index ba478d973..db2ad685b 100644 --- a/src/frontend/src/routes/_layout/index.tsx +++ b/src/frontend/src/routes/_layout/index.tsx @@ -51,12 +51,19 @@ function Index() { setChoosableItemNames(itemBaseTypes.itemNames); } if (leagues) { - setChoosableLeagues(leagues.leagues); + setChoosableLeagues( + leagues.leagues.sort( + (league1, league2) => + Date.parse(league2.validFrom) - Date.parse(league1.validFrom), + ), + ); const currentLeague = getCurrentLeague(leagues.leagues); if (currentLeague !== undefined) { const leagueLaunch = new Date(currentLeague.validFrom); setLeagueLaunch(leagueLaunch); + // setLeague is used to create the header setLeague(currentLeague); + // addLeague is for the plot query, and needed for default league selection addLeague(currentLeague.name); setHashFromStore(); } @@ -76,6 +83,8 @@ function Index() { setChoosableLeagues, setLeagueLaunch, setLeague, + addLeague, + setHashFromStore, ]); return ; } diff --git a/src/frontend/src/schemas/Datum.tsx b/src/frontend/src/schemas/Datum.tsx index 5e05b5360..766282aef 100644 --- a/src/frontend/src/schemas/Datum.tsx +++ b/src/frontend/src/schemas/Datum.tsx @@ -9,7 +9,7 @@ type Datum = { }; type TimeseriesData = { - name: number; + leagueId: number; data: Array; confidenceRating: "low" | "medium" | "high"; }; diff --git a/src/frontend/src/store/GraphInputStore.tsx b/src/frontend/src/store/GraphInputStore.tsx index 445f4299f..5f5e23976 100644 --- a/src/frontend/src/store/GraphInputStore.tsx +++ b/src/frontend/src/store/GraphInputStore.tsx @@ -133,6 +133,10 @@ export const useGraphInputStore = create((set) => ({ set(() => ({ choosableItemNames: choosableItemNames, })), + setChoosableLeagues: (leagues: League[]) => + set(() => ({ + choosableLeagues: leagues, + })), updateChoosable: (itemName: string | undefined) => set((state) => { @@ -197,13 +201,6 @@ export const useGraphInputStore = create((set) => ({ set(() => ({ leagues: [], })), - setChoosableLeagues: (leagues: League | League[]) => - set(() => { - leagues = Array.isArray(leagues) ? leagues : [leagues]; - return { - choosableLeagues: leagues, - }; - }), setItemSpec: (itemSpec: ItemSpecState) => set(() => ({ itemSpec: itemSpec })), setItemSpecIdentified: (identified: boolean | undefined) => diff --git a/src/frontend/src/store/LeagueLaunchStatsStore.tsx b/src/frontend/src/store/LeagueLaunchStatsStore.tsx index e108b74f2..223065bf4 100644 --- a/src/frontend/src/store/LeagueLaunchStatsStore.tsx +++ b/src/frontend/src/store/LeagueLaunchStatsStore.tsx @@ -7,6 +7,7 @@ export const useLeagueLaunchStats = create((set) => ({ name: "Path of Exile", validFrom: "2013-10-23T21:00:00Z", leagueId: -1, + version: 1.0, }, leagueLaunch: new Date("2013-10-23T21:00:00Z"), hoursSinceLaunch: Number.NEGATIVE_INFINITY, diff --git a/src/frontend/src/store/StateInterface.tsx b/src/frontend/src/store/StateInterface.tsx index 545b8564a..deeb68166 100644 --- a/src/frontend/src/store/StateInterface.tsx +++ b/src/frontend/src/store/StateInterface.tsx @@ -131,7 +131,7 @@ export interface GraphInputState { addLeague: (league: string) => void; removeLeague: (league: string) => void; removeAllLeagues: () => void; - setChoosableLeagues: (leagues: League | League[]) => void; + setChoosableLeagues: (leagues: League[]) => void; setItemName: (name: string | undefined) => void; From 0417cf56c8945a36ee7d331810ae7c22c141e56c Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Tue, 14 Jul 2026 15:03:29 -0230 Subject: [PATCH 14/15] #824 Fixed format of latest currencies --- src/backend_api/app/api/routes/currency.py | 6 ++---- src/backend_api/app/crud/extensions/crud_currency.py | 12 +++--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/backend_api/app/api/routes/currency.py b/src/backend_api/app/api/routes/currency.py index bc05d1dfc..d9e3ed033 100644 --- a/src/backend_api/app/api/routes/currency.py +++ b/src/backend_api/app/api/routes/currency.py @@ -137,7 +137,7 @@ async def get_latest_hours( @router.get( "/latest_currencies/", - response_model=dict[int, list[schemas.Currency]], + response_model=list[schemas.Currency], tags=["latest_currencies"], dependencies=[ Depends(get_current_active_user), @@ -156,9 +156,7 @@ async def get_latest_currencies( db: Session = Depends(get_db), ): """ - Returns a dict mapping league id to most recent currencies relating to that league. Dict is empty if no currencies exist. - - Does not guarantee an entry for every league id. + Returns a list of currencies for all queried leagues (if data exists). """ return await CRUD_currency.get_latest_currencies(db, league_ids) diff --git a/src/backend_api/app/crud/extensions/crud_currency.py b/src/backend_api/app/crud/extensions/crud_currency.py index eea9bba39..3a094bb98 100644 --- a/src/backend_api/app/crud/extensions/crud_currency.py +++ b/src/backend_api/app/crud/extensions/crud_currency.py @@ -1,5 +1,3 @@ -from collections import defaultdict - from pydantic import TypeAdapter from sqlalchemy import select from sqlalchemy.orm import Session @@ -62,7 +60,7 @@ async def get_latest_hours( async def get_latest_currencies( self, db: Session, league_ids: list[int] - ) -> dict[int, list[Currency]]: + ) -> list[Currency]: latest_hours = self._latest_hours_stmt(league_ids).subquery() stmt = select(model_Currency).join( @@ -72,13 +70,9 @@ async def get_latest_currencies( ) currencies = db.scalars(stmt).all() - latest_currencies: dict[int, list[Currency]] = defaultdict(list) - for currency in currencies: - latest_currencies[currency.leagueId].append(currency) - - validate = TypeAdapter(dict[int, list[Currency]]).validate_python + validate = TypeAdapter(list[Currency]).validate_python - return validate(latest_currencies) + return validate(currencies) async def get_currency_from_query( self, db: Session, query_list: list[CurrencyQuery] From 0e260a74a7628e0e3077f978b226185ffc1ec053 Mon Sep 17 00:00:00 2001 From: Bogadisa Date: Tue, 14 Jul 2026 15:49:57 -0230 Subject: [PATCH 15/15] #824 Fixed problem with aggregating items --- src/backend_api/app/api/routes/unidentified_item.py | 7 +------ .../app/crud/extensions/crud_unidentifiedItem.py | 6 ++---- .../data_retrieval/poe_api_handler.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/backend_api/app/api/routes/unidentified_item.py b/src/backend_api/app/api/routes/unidentified_item.py index b355eea8a..cc55c311a 100644 --- a/src/backend_api/app/api/routes/unidentified_item.py +++ b/src/backend_api/app/api/routes/unidentified_item.py @@ -116,7 +116,6 @@ async def get_non_aggregated( @router.post( "/add_aggregated/", - response_model=list[schemas.UnidentifiedItem], dependencies=[Depends(get_current_active_superuser)], ) async def add_aggregated( @@ -129,8 +128,4 @@ async def add_aggregated( And inserts the new items """ - deleted_non_aggregated = await CRUD_unidentifiedItem.add_aggregated( - db=db, aggregated_objs=aggregated_objs - ) - - return deleted_non_aggregated + await CRUD_unidentifiedItem.add_aggregated(db=db, aggregated_objs=aggregated_objs) diff --git a/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py index dffa30452..3e3ad12eb 100644 --- a/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py +++ b/src/backend_api/app/crud/extensions/crud_unidentifiedItem.py @@ -35,12 +35,10 @@ async def get_non_aggregated(self, db: Session) -> list[UnidentifiedItem]: async def add_aggregated( self, db: Session, aggregated_objs: list[UnidentifiedItemCreate] - ) -> list[UnidentifiedItem]: + ): stmt = delete(model_UnidentifiedItem).where( model_UnidentifiedItem.aggregated.isnot(True), ) - non_aggregated_objs = db.execute(stmt) + db.execute(stmt) await self.create(db, obj_in=aggregated_objs, return_nothing=True) - - return non_aggregated_objs diff --git a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py index a5443794a..ab500cb28 100644 --- a/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py +++ b/src/backend_data_retrieval/data_retrieval_app/external_data_retrieval/data_retrieval/poe_api_handler.py @@ -302,6 +302,12 @@ async def _send_n_recursion_requests( logger.exception( f"The following exception occured during {self._send_n_recursion_requests.__name__}: {e}" ) + raise + except ProgramTooSlowException: + pass + except ProgramFinished: + pass + finally: if waiting_for_next_id_lock.locked(): logger.info("Released lock after crash") if headers is not None: @@ -321,7 +327,6 @@ async def _send_n_recursion_requests( ) waiting_for_next_id_lock.release() - raise async def _follow_stream( self, @@ -355,6 +360,10 @@ async def _follow_stream( stashes = [] stashes_ready_event.set() await asyncio.sleep(1) + except ProgramTooSlowException: + pass + except ProgramFinished: + pass except Exception as e: logger.info( f"The following exception occured during {self._follow_stream}: {e}"