From 9d01a1745332e94ae6affbbbcc94fd9a652399df Mon Sep 17 00:00:00 2001 From: Ernest Litvinenko Date: Mon, 8 Jul 2024 18:35:04 +0300 Subject: [PATCH] Add graphql queries. Update handlers --- .idea/dataSources.xml | 12 + .idea/vcs.xml | 6 + core/helpers/profile_helpers.py | 4 +- .../note/__init__.py} | 0 core/model/note/db.py | 10 + core/model/task/db2.py | 587 ++++++------------ core/storage/__init__.py | 8 +- core/storage/base.py | 10 +- core/storage/note_storage.py | 58 ++ core/transport/graphql/schema.py | 189 ++++-- core/transport/rest/auth/handlers.py | 10 +- core/transport/rest/note/__init__.py | 0 core/transport/rest/note/handlers.py | 0 core/transport/rest/tasks/handlers.py | 30 +- 14 files changed, 451 insertions(+), 473 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/vcs.xml rename core/{storage/notification_storage.py => model/note/__init__.py} (100%) create mode 100644 core/model/note/db.py create mode 100644 core/storage/note_storage.py create mode 100644 core/transport/rest/note/__init__.py create mode 100644 core/transport/rest/note/handlers.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..91a9383 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + firebird + true + org.firebirdsql.jdbc.FBDriver + jdbc:firebirdsql://10.2.100.126:3050/NETDBS_2?lc_ctype=WIN1251 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/core/helpers/profile_helpers.py b/core/helpers/profile_helpers.py index aaf2425..e1ebb20 100644 --- a/core/helpers/profile_helpers.py +++ b/core/helpers/profile_helpers.py @@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer from core.config import Config from core.model.profile.db import ProfileDB -from core.storage import profile +from core.storage import profile_storage oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/phone") @@ -18,4 +18,4 @@ def get_user_from_token(token: str = Depends(oauth2_scheme)) -> ProfileDB: except jwt.exceptions.InvalidSignatureError as err: raise HTTPException(status_code=401, detail=str(err)) - return profile.get_profile_by_id(data['profile_id']) + return profile_storage.get_profile_by_id(data['profile_id']) diff --git a/core/storage/notification_storage.py b/core/model/note/__init__.py similarity index 100% rename from core/storage/notification_storage.py rename to core/model/note/__init__.py diff --git a/core/model/note/db.py b/core/model/note/db.py new file mode 100644 index 0000000..69ae64d --- /dev/null +++ b/core/model/note/db.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class AppNoteDB(BaseModel): + id: int + user_id: int + task_id: int + note_status: int + tip: int + text: str diff --git a/core/model/task/db2.py b/core/model/task/db2.py index 3265647..124e3fb 100644 --- a/core/model/task/db2.py +++ b/core/model/task/db2.py @@ -1,414 +1,241 @@ -import functools -import json from datetime import date, datetime -from typing import Self, Optional, Union, Any, Callable - -from firebird.driver import Cursor -from pydantic import BaseModel, Field, Json -from pypika import Query, Table -from pypika.queries import QueryBuilder - -from core.database.db import redis_cache_obj -from core.storage.base import BaseStorage - -from redis_cache import RedisCache +from typing import Optional +from sqlalchemy import BIGINT, Column, ForeignKey +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, aliased -class __BaseDB(BaseModel, BaseStorage): - __tablename__: str - __read_only: bool = False - __table: Table | None = None - __state_changed = {} - __query: QueryBuilder | None = None - - @classmethod - def cache(cls) -> RedisCache: - return redis_cache_obj() - - @classmethod - def get_keys(cls): - return [x for x in cls.__dict__['__annotations__'].keys() if not x.startswith("__")] - - @classmethod - def get_table(cls) -> Self: - return Table(cls.__tablename__) - - @classmethod - def fetch_one(cls, id: int) -> type[Self]: - t = cls.get_table() - keys = cls.get_keys() - stmt = Query.from_(t).select(*keys).where(getattr(t, keys[0]) == id) - with cls.get_cursor() as cur: - cur: Cursor - data = cur.execute(stmt.get_sql()).fetchone() - - return cls(**{key: val for key, val in zip(keys, data)}) - - @classmethod - def parse_orm(cls, row): - return cls(**{key: val for key, val in zip(cls.get_keys(), row)}) - - @classmethod - def fetch_all(cls, where_query: QueryBuilder = None) -> list['Self']: - table = cls.get_table() - if not where_query: - stmt = Query.from_(table).select(*cls.get_keys()) - else: - stmt = Query.from_(table).select(*cls.get_keys()).where(where_query) - - with cls.get_cursor() as cursor: - cursor: Cursor - stmt: QueryBuilder - print(stmt) - data = cursor.execute(stmt.get_sql()).fetchall() - return [cls.parse_orm(d) for d in data] - - @classmethod - def fetch_related(cls) -> 'Self': - table = cls.get_table() - - cls.__query = Query.from_(table) - return cls - - @classmethod - def create(cls, model: Self) -> Self: - if model.__read_only: - raise Exception("This model is read only") - - # table = self.get_table() - - # data = self.model_dump(mode='json', exclude_none=True, exclude={'id'}, exclude_unset=True) - # stmt = Query.into(table).columns(*[self.get_keys()]).insert( - # *[kwargs[key] for key in self.get_keys() if key in kwargs.keys()]) - # with self.get_cursor() as cursor: - # cursor: Cursor - # cursor.execute(stmt.get_sql()) - - @classmethod - def update(cls, model: Self) -> Self: - if model.__read_only: - raise Exception("This model is read only") +class __Base(DeclarativeBase): + pass -class MPLSTDB(__BaseDB): - __tablename__: str = "LST" - ID_LST: int - LST_ID_VLST: int - LST_NAME: str - LST_NAME_SH: str - - @classmethod - def fetch_one(cls, id: int) -> type[Self]: - cache = cls.cache() - - @cache.cache() - def wrapper(id): - return super(MPLSTDB, cls).fetch_one(id) - - return wrapper(id) +class MPLSTDB(__Base): + __tablename__ = "LST" + ID_LST: Mapped[int] = mapped_column(BIGINT, primary_key=True) + LST_ID_VLST: Mapped[int] + LST_NAME: Mapped[str] + LST_NAME_SH: Mapped[str] -class MPAppTaskDB(__BaseDB): +class MPAppTaskDB(__Base): __tablename__: str = "APP_TASK" - ID_APP_TASK: int | None = None - APP_TASK_ID_SOTR: int = None - APP_TASK_ID_APP_TASK: int = None - APP_TASK_DT_START_PLN: datetime = None - APP_TASK_DT_END_PLN: datetime = None - APP_TASK_DT_START_FACT: datetime | None = None - APP_TASK_DT_END_FACT: datetime | None = None - APP_TASK_STATUS: int = None - APP_TASK_TIP: int = None - APP_TASK_TEXT: str = None - APP_TASK_DEL: int = None + ID_APP_TASK: Mapped[int] = mapped_column(BIGINT, primary_key=True) + APP_TASK_ID_SOTR: Mapped[int] = mapped_column(BIGINT) + APP_TASK_ID_APP_TASK: Mapped[int] = mapped_column(BIGINT, ForeignKey("APP_TASK.ID_APP_TASK")) + APP_TASK_DT_START_PLN: Mapped[datetime] + APP_TASK_DT_END_PLN: Mapped[datetime] + APP_TASK_DT_START_FACT: Mapped[datetime | None] + APP_TASK_DT_END_FACT: Mapped[datetime | None] + APP_TASK_STATUS: Mapped[int] + APP_TASK_TIP: Mapped[int] + APP_TASK_TEXT: Mapped[str] + APP_TASK_DEL: Mapped[int] + + subtasks: Mapped['MPAppTaskDB'] = relationship('MPAppTaskDB', remote_side=[ID_APP_TASK]) - @property - def status(self) -> MPLSTDB: - return MPLSTDB.fetch_one(self.APP_TASK_STATUS) @property def is_subtask(self) -> bool: return self.ID_APP_TASK != self.APP_TASK_ID_APP_TASK - @property - def task_type(self) -> MPLSTDB: - return MPLSTDB.fetch_one(self.APP_TASK_TIP) - @property - def subtasks(self) -> list['MPAppTaskDB']: - t: MPAppTaskDB = MPAppTaskDB.get_table() - return MPAppTaskDB.fetch_all( - (t.APP_TASK_ID_APP_TASK == self.ID_APP_TASK) & (t.APP_TASK_ID_APP_TASK != t.ID_APP_TASK) & ( - t.APP_TASK_DEL == 0)) - - @property - def events(self) -> list['MPAppEventDB']: - return MPAppEventDB.fetch_all(MPAppEventDB.get_table().APP_EVENT_ID_REC == self.ID_APP_TASK) - - @property - def params(self) -> list['MPAppParamDB']: - return MPAppParamDB.fetch_all(MPAppParamDB.get_table().APP_PARAM_ID_REC == self.ID_APP_TASK) - - -class EventData(__BaseDB): - key: MPLSTDB - value: Any - - -class MPAppEventDB(__BaseDB): +class MPAppEventDB(__Base): __tablename__: str = "APP_EVENT" - ID_APP_EVENT: int = None - APP_EVENT_ID_SOTR: int = None - APP_EVENT_ID_REC: int = None - APP_EVENT_VID: int = None - APP_EVENT_TIP: int = None - APP_EVENT_DT: datetime | None = None - APP_EVENT_TEXT: str | None = None - APP_EVENT_DATA: Json - APP_EVENT_DEL: int = 0 - - @property - def event_type(self) -> MPLSTDB: - return MPLSTDB.fetch_one(self.APP_EVENT_TIP) - - @property - def event_data(self) -> list[EventData]: - return [EventData(key=MPLSTDB.fetch_one(key), value=value) for d in list(self.APP_EVENT_DATA) for key, value in - d.items()] + ID_APP_EVENT: Mapped[int] = mapped_column(primary_key=True) + APP_EVENT_ID_SOTR: Mapped[int] + APP_EVENT_ID_REC: Mapped[int] + APP_EVENT_VID: Mapped[int] + APP_EVENT_TIP: Mapped[int] + APP_EVENT_DT: Mapped[datetime | None] + APP_EVENT_TEXT: Mapped[str | None] + APP_EVENT_DATA: Mapped[str] + APP_EVENT_DEL: Mapped[int] -class MPMSTDB(__BaseDB): - __read_only = True +class MPMSTDB(__Base): __tablename__ = "MST" - ID_MST: int = Field(default=0) - MST_PR_OTHER: int = Field(default=0) - MST_ID_KG: int = Field(default=0) - MST_ID_SRV: int = Field(default=0) - MST_ID_SETTLEMENT: Optional[int] = Field(default=0) - MST_SID: Optional[str] = Field(default=None) - MST_NAME: Optional[str] = Field(default=None) - MST_CLI_NAME: Optional[str] = Field(default=None) - MST_CODE: int = Field(default=0) - MST_CODE_PODR_NDS: int | None = Field(default=0) - MST_CODE_PODR_BN: int | None = Field(default=0) - MST_PR_TTNINPUT: int = Field(default=0) - MST_PR_TTNOUTPUT: int = Field(default=0) - MST_PR_AEX: int = Field(default=0) - MST_PR_AEX_ADR: Optional[int] = Field(default=0) - MST_ID_MST_TTNOUTPUT: int = Field(default=0) - MST_PR_SORT: int = Field(default=0) - MST_PR_PVZ: int = Field(default=0) - MST_PR_VIRT: int = Field(default=0) - MST_PR_INOTHER: int = Field(default=0) - MST_PR_ZAKG: int = Field(default=0) - MST_PR_FAR: int = Field(default=0) - MST_PR_KKT: int = Field(default=0) - MST_PR_CC: int = Field(default=0) - MST_PR_AS: int = Field(default=0) - MST_KM: int = Field(default=0) - MST_MP: int = Field(default=0) - MST_ID_AGENT_AS: int = Field(default=0) - MST_PR_NOLIM_AS: int = Field(default=0) - MST_PR_WC_AS: int = Field(default=0) - MST_PR_TRS: int = Field(default=0) - MST_ID_REGION: int = Field(default=0) - MST_ADDRESS_CODE: int = Field(default=0) - MST_ID_KLADR_DOM: Optional[int] = Field(default=0) - MST_SHIR: float = Field(default=0.0) - MST_DOLG: float = Field(default=0.0) - MST_ADR_STOR: Optional[str] = Field(default=None) - MST_FUNC_MASK: int = Field(default=0) - MST_ID_SRV_CALL: int = Field(default=0) - MST_ID_MST_CALL: int = Field(default=0) - MST_PR_DIRECT: int = Field(default=0) - MST_NAME_DIRECT: Optional[str] = Field(default=None) - MST_PR_NOTE: int = Field(default=0) - MST_PR_NOTSITE: int = Field(default=0) - MST_PR_GREEN: int = Field(default=0) - MST_PR_GREENORK: int = Field(default=0) - MST_PR_GREENPRINTER: int = Field(default=0) - MST_PR_VID_TR_VD: int = Field(default=0) - MST_PR_BAN_IN: int = Field(default=0) - MST_PR_NO_CLIENT_CODES: int = Field(default=0) - MST_PR_NO_STTN02: int = Field(default=0) - MST_PR_NO_EEU: int = Field(default=0) - MST_VID_CALC_FOBYOM: int = Field(default=0) - MST_TXT: Optional[str] = Field(default=None) - MST_DEL: int = Field(default=0) - MST_CH: Optional[datetime] = Field(default=None) - MST_WCH: int = Field(default=0) - MST_IMP: Optional[datetime] = Field(default=None) - MST_MPOST: int = Field(default=0) - MST_SEANS: int = Field(default=0) - MST_OWNERMST: int = Field(default=0) - MST_CR: Optional[datetime] = Field(default=None) - MST_WCR: int = Field(default=0) - MST_FIMP: Optional[datetime] = Field(default=None) - MST_ID_MST_SYNONYM: int = Field(default=0) - MST_NAME_OLD: Optional[str] = Field(default=None) - MST_SRC_OLD: int = Field(default=0) - MST_UPPERNAME_OLD: Optional[str] = Field(default=None) - MST_TXT_AEX: Optional[str] = Field(default=None) - MST_PR_NODOOR_AEX: int = Field(default=0) - - @classmethod - def fetch_one(cls, id: int) -> type[Self]: - cache = cls.cache() - - @cache.cache() - def wrapper(id): - return super(MPMSTDB, cls).fetch_one(id) - - return wrapper(id) + ID_MST: Mapped[int] = mapped_column(primary_key=True) + MST_PR_OTHER: Mapped[int] + MST_ID_KG: Mapped[int] + MST_ID_SRV: Mapped[int] + MST_ID_SETTLEMENT: Mapped[Optional[int]] + MST_SID: Mapped[Optional[str]] + MST_NAME: Mapped[Optional[str]] + MST_CLI_NAME: Mapped[Optional[str]] + MST_CODE: Mapped[int] + MST_CODE_PODR_NDS: Mapped[int | None] + MST_CODE_PODR_BN: Mapped[int | None] + MST_PR_TTNINPUT: Mapped[int] + MST_PR_TTNOUTPUT: Mapped[int] + MST_PR_AEX: Mapped[int] + MST_PR_AEX_ADR: Mapped[Optional[int]] + MST_ID_MST_TTNOUTPUT: Mapped[int] + MST_PR_SORT: Mapped[int] + MST_PR_PVZ: Mapped[int] + MST_PR_VIRT: Mapped[int] + MST_PR_INOTHER: Mapped[int] + MST_PR_ZAKG: Mapped[int] + MST_PR_FAR: Mapped[int] + MST_PR_KKT: Mapped[int] + MST_PR_CC: Mapped[int] + MST_PR_AS: Mapped[int] + MST_KM: Mapped[int] + MST_MP: Mapped[int] + MST_ID_AGENT_AS: Mapped[int] + MST_PR_NOLIM_AS: Mapped[int] + MST_PR_WC_AS: Mapped[int] + MST_PR_TRS: Mapped[int] + MST_ID_REGION: Mapped[int] + MST_ADDRESS_CODE: Mapped[int] + MST_ID_KLADR_DOM: Mapped[Optional[int]] + MST_SHIR: Mapped[float] + MST_DOLG: Mapped[float] + MST_ADR_STOR: Mapped[Optional[str]] + MST_FUNC_MASK: Mapped[int] + MST_ID_SRV_CALL: Mapped[int] + MST_ID_MST_CALL: Mapped[int] + MST_PR_DIRECT: Mapped[int] + MST_NAME_DIRECT: Mapped[Optional[str]] + MST_PR_NOTE: Mapped[int] + MST_PR_NOTSITE: Mapped[int] + MST_PR_GREEN: Mapped[int] + MST_PR_GREENORK: Mapped[int] + MST_PR_GREENPRINTER: Mapped[int] + MST_PR_VID_TR_VD: Mapped[int] + MST_PR_BAN_IN: Mapped[int] + MST_PR_NO_CLIENT_CODES: Mapped[int] + MST_PR_NO_STTN02: Mapped[int] + MST_PR_NO_EEU: Mapped[int] + MST_VID_CALC_FOBYOM: Mapped[int] + MST_TXT: Mapped[Optional[str]] + MST_DEL: Mapped[int] + MST_CH: Mapped[Optional[datetime]] + MST_WCH: Mapped[int] + MST_IMP: Mapped[Optional[datetime]] + MST_MPOST: Mapped[int] + MST_SEANS: Mapped[int] + MST_OWNERMST: Mapped[int] + MST_CR: Mapped[Optional[datetime]] + MST_WCR: Mapped[int] + MST_FIMP: Mapped[Optional[datetime]] + MST_ID_MST_SYNONYM: Mapped[int] + MST_NAME_OLD: Mapped[Optional[str]] + MST_SRC_OLD: Mapped[int] + MST_UPPERNAME_OLD: Mapped[Optional[str]] + MST_TXT_AEX: Mapped[Optional[str]] + MST_PR_NODOOR_AEX: Mapped[int] -class MPTRSDB(__BaseDB): +class MPTRSDB(__Base): __tablename__ = "TRS" - __read_only = True - ID_TRS: int = Field(default=0) - TRS_PR_TEST: int = Field(default=0) - TRS_PR_TEST_ID_SOTR: int = Field(default=0) - TRS_PR_TEST_DT: Optional[datetime] = Field(default=None) - TRS_PR: int = Field(default=0) - TRS_PR_UP: int = Field(default=0) - TRS_ID_LST_PR: int = Field(default=0) - TRS_ID_LST_VID: int = Field(default=0) - TRS_ID_LSTU_TIP: int = Field(default=0) - TRS_SID: Optional[str] = Field(default=None) - TRS_SID_GOST: Optional[str] = Field(default=None) - TRS_SID_OLD: Optional[str] = Field(default=None) - TRS_SRC_OLD: int = Field(default=0) - TRS_PR_VLAD: int = Field(default=0) - TRS_ID_AGENT_AS: int = Field(default=0) - TRS_VES: float = Field(default=0.0) - TRS_OBYOM: float = Field(default=0.0) - TRS_PR_LTOR: int = Field(default=0) - TRS_PR_LLEN: int = Field(default=0) - TRS_PR_LTOP: int = Field(default=0) - TRS_PR_TEPL: int = Field(default=0) - TRS_PR_TEPL_WHERE: int = Field(default=0) - TRS_OBYOM_TEPL: float = Field(default=0.0) - TRS_PR_NOZAGRGRUZ: int = Field(default=0) - TRS_CNT_AXIS: int = Field(default=0) - TRS_PRIM: Optional[str] = Field(default=None) - TRS_INFO: Optional[str] = Field(default=None) - TRS_TARA: Optional[float] = Field(default=0.0) - TRS_TYPEPROPERTY: int = Field(default=0) - TRS_DOGAREND: Optional[str] = Field(default=None) - TRS_1C_D_AKT: Optional[date] = Field(default=None) - TRS_1C_NOMMSG: int = Field(default=0) - TRS_1C_DEL: int = Field(default=0) - TRS_1C_DATEEND: Optional[date] = Field(default=None) - TRS_DEL: int = Field(default=0) - TRS_CR: Optional[datetime] = Field(default=None) - TRS_WCR: int = Field(default=0) - TRS_CH: Optional[datetime] = Field(default=None) - TRS_WCH: int = Field(default=0) - TRS_OWNERMST: int = Field(default=0) - TRS_SEANS: int = Field(default=0) - TRS_IMP: Optional[datetime] = Field(default=None) - TRS_FIMP: Optional[datetime] = Field(default=None) - TRS_MPOST: int = Field(default=0) - - @classmethod - def fetch_one(cls, id: int) -> type[Self]: - cache = cls.cache() - - @cache.cache() - def wrapper(id): - return super(MPTRSDB, cls).fetch_one(id) - - return wrapper(id) + ID_TRS: Mapped[int] = mapped_column(primary_key=True) + TRS_PR_TEST: Mapped[int] + TRS_PR_TEST_ID_SOTR: Mapped[int] + TRS_PR_TEST_DT: Mapped[Optional[datetime]] + TRS_PR: Mapped[int] + TRS_PR_UP: Mapped[int] + TRS_ID_LST_PR: Mapped[int] + TRS_ID_LST_VID: Mapped[int] + TRS_ID_LSTU_TIP: Mapped[int] + TRS_SID: Mapped[Optional[str]] + TRS_SID_GOST: Mapped[Optional[str]] + TRS_SID_OLD: Mapped[Optional[str]] + TRS_SRC_OLD: Mapped[int] + TRS_PR_VLAD: Mapped[int] + TRS_ID_AGENT_AS: Mapped[int] + TRS_VES: Mapped[float] + TRS_OBYOM: Mapped[float] + TRS_PR_LTOR: Mapped[int] + TRS_PR_LLEN: Mapped[int] + TRS_PR_LTOP: Mapped[int] + TRS_PR_TEPL: Mapped[int] + TRS_PR_TEPL_WHERE: Mapped[int] + TRS_OBYOM_TEPL: Mapped[float] + TRS_PR_NOZAGRGRUZ: Mapped[int] + TRS_CNT_AXIS: Mapped[int] + TRS_PRIM: Mapped[Optional[str]] + TRS_INFO: Mapped[Optional[str]] + TRS_TARA: Mapped[Optional[float]] + TRS_TYPEPROPERTY: Mapped[int] + TRS_DOGAREND: Mapped[Optional[str]] + TRS_1C_D_AKT: Mapped[Optional[date]] + TRS_1C_NOMMSG: Mapped[int] + TRS_1C_DEL: Mapped[int] + TRS_1C_DATEEND: Mapped[Optional[date]] + TRS_DEL: Mapped[int] + TRS_CR: Mapped[Optional[datetime]] + TRS_WCR: Mapped[int] + TRS_CH: Mapped[Optional[datetime]] + TRS_WCH: Mapped[int] + TRS_OWNERMST: Mapped[int] + TRS_SEANS: Mapped[int] + TRS_IMP: Mapped[Optional[datetime]] + TRS_FIMP: Mapped[Optional[datetime]] + TRS_MPOST: Mapped[int] -class MPMarshDB(__BaseDB): - __read_only = True +class MPMarshDB(__Base): __tablename__ = "MARSH" - ID_MARSH: int = Field(default=0) - MARSH_PR: int = Field(default=0) - MARSH_PR_PLAN: int = Field(default=0) - MARSH_PR_VLAD: int = Field(default=0) - MARSH_PR_DOP: int = Field(default=0) - MARSH_PR_TEPL: int = Field(default=0) - MARSH_KEY_GPREF: Optional[str] = Field(default=None, min_length=1, max_length=16) - MARSH_KEY_PREF: Optional[str] = Field(default=None, min_length=1, max_length=32) - MARSH_NAME: Optional[str] = Field(default=None, min_length=1, max_length=128) - MARSH_D_N: Optional[date] = Field(default=None) - MARSH_D_K: Optional[date] = Field(default=None) - MARSH_ID_MST_OTPR: int = Field(default=0) - MARSH_ID_MST_NAZN: int = Field(default=0) - MARSH_DAYS_WEEK: int = Field(default=0) - MARSH_T_OTPR: float = Field(default=0.0) - MARSH_DATE_OUT: Optional[date] = Field(default=None) - MARSH_PRICE: Optional[float] = Field(default=0.0) - MARSH_KM: Optional[float] = Field(default=0.0) - MARSH_TXT: Optional[str] = Field(default=None, min_length=1, max_length=512) - MARSH_DEL: int = Field(default=0) + ID_MARSH: Mapped[int] = mapped_column(primary_key=True) + MARSH_PR: Mapped[int] + MARSH_PR_PLAN: Mapped[int] + MARSH_PR_VLAD: Mapped[int] + MARSH_PR_DOP: Mapped[int] + MARSH_PR_TEPL: Mapped[int] + MARSH_KEY_GPREF: Mapped[Optional[str]] + MARSH_KEY_PREF: Mapped[Optional[str]] + MARSH_NAME: Mapped[Optional[str]] + MARSH_D_N: Mapped[Optional[date]] + MARSH_D_K: Mapped[Optional[date]] + MARSH_ID_MST_OTPR: Mapped[int] + MARSH_ID_MST_NAZN: Mapped[int] + MARSH_DAYS_WEEK: Mapped[int] + MARSH_T_OTPR: Mapped[float] + MARSH_DATE_OUT: Mapped[Optional[date]] + MARSH_PRICE: Mapped[Optional[float]] + MARSH_KM: Mapped[Optional[float]] + MARSH_TXT: Mapped[Optional[str]] + MARSH_DEL: Mapped[int] -class MPMarshTRSDB(__BaseDB): +class MPMarshTRSDB(__Base): __tablename__ = "MARSH_TRS" - __read_only = True - ID_MARSH_TRS: int = Field(default=0) - MARSH_TRS_ID_MARSH: int = Field(default=0) - MARSH_TRS_DATE: Optional[date] = Field(default=None) - MARSH_TRS_ID_TRS: int = Field(default=0) - MARSH_TRS_TRS_PR_COLDONLY: Optional[int] = Field(default=0) - MARSH_TRS_ID_PRIC: int = Field(default=0) - MARSH_TRS_PRIC_PR_COLDONLY: Optional[int] = Field(default=0) - MARSH_TRS_ID_SOTR: int = Field(default=0) - MARSH_TRS_DT_DELIVERY: Optional[datetime] = Field(default=None) - MARSH_TRS_PR: int = Field(default=0) - MARSH_TRS_COMMENT: Optional[str] = Field(default=None, max_length=4096) - MARSH_TRS_TARIFF: float = Field(default=0.0) - MARSH_TRS_DEL: int = Field(default=0) - MARSH_TRS_OWNERMST: int = Field(default=0) - MARSH_TRS_MPOST: Optional[int] = Field(default=0) - MARSH_TRS_CR: Optional[datetime] = Field(default=None) - MARSH_TRS_WCR: int = Field(default=0) - MARSH_TRS_IMP: Optional[datetime] = Field(default=None) - MARSH_TRS_CH: Optional[datetime] = Field(default=None) - MARSH_TRS_WCH: int = Field(default=0) - MARSH_TRS_SEANS: int = Field(default=0) - MARSH_TRS_FIMP: Optional[datetime] = Field(default=None) - - @property - def marsh(self) -> MPMarshDB: - return MPMarshDB.fetch_one(self.MARSH_TRS_ID_MARSH) - - @property - def trs(self) -> MPTRSDB: - return MPTRSDB.fetch_one(self.MARSH_TRS_ID_TRS) - - @property - def trailer(self) -> MPTRSDB: - return MPTRSDB.fetch_one(self.MARSH_TRS_ID_PRIC) + ID_MARSH_TRS: Mapped[int] = mapped_column(primary_key=True) + MARSH_TRS_ID_MARSH: Mapped[int] + MARSH_TRS_DATE: Mapped[Optional[date]] + MARSH_TRS_ID_TRS: Mapped[int] + MARSH_TRS_TRS_PR_COLDONLY: Mapped[Optional[int]] + MARSH_TRS_ID_PRIC: Mapped[int] + MARSH_TRS_PRIC_PR_COLDONLY: Mapped[Optional[int]] + MARSH_TRS_ID_SOTR: Mapped[int] + MARSH_TRS_DT_DELIVERY: Mapped[Optional[datetime]] + MARSH_TRS_PR: Mapped[int] + MARSH_TRS_COMMENT: Mapped[Optional[str]] + MARSH_TRS_TARIFF: Mapped[float] + MARSH_TRS_DEL: Mapped[int] + MARSH_TRS_OWNERMST: Mapped[int] + MARSH_TRS_MPOST: Mapped[Optional[int]] + MARSH_TRS_CR: Mapped[Optional[datetime]] + MARSH_TRS_WCR: Mapped[int] + MARSH_TRS_IMP: Mapped[Optional[datetime]] + MARSH_TRS_CH: Mapped[Optional[datetime]] + MARSH_TRS_WCH: Mapped[int] + MARSH_TRS_SEANS: Mapped[int] + MARSH_TRS_FIMP: Mapped[Optional[datetime]] -class MPAppParamDB(__BaseDB): +class MPAppParamDB(__Base): __tablename__ = "APP_PARAM" - __read_only = True - APP_PARAM_ID_REC: int = Field(default=0) - APP_PARAM_STR: Optional[str] = Field(default=None, min_length=1, max_length=1024) - APP_PARAM_VID: int = Field(default=0) - APP_PARAM_TIP: int = Field(default=0) - APP_PARAM_DEL: int = Field(default=0) - APP_PARAM_CR: Optional[datetime] = Field(default=None) - APP_PARAM_WCR: int = Field(default=0) - APP_PARAM_CH: Optional[datetime] = Field(default=None) - APP_PARAM_WCH: int = Field(default=0) + APP_PARAM_ID_REC: Mapped[int] = mapped_column(primary_key=True) + APP_PARAM_STR: Mapped[Optional[str]] + APP_PARAM_VID: Mapped[int] + APP_PARAM_TIP: Mapped[int] + APP_PARAM_DEL: Mapped[int] + APP_PARAM_CR: Mapped[Optional[datetime]] + APP_PARAM_WCR: Mapped[int] + APP_PARAM_CH: Mapped[Optional[datetime]] + APP_PARAM_WCH: Mapped[int] - @property - def param_type(self) -> MPLSTDB: - return MPLSTDB.fetch_one(self.APP_PARAM_TIP) - @property - def related_table(self) -> Union['MPMSTDB', 'MPMarshTRSDB', 'MPTRSDB', 'MPMarshDB']: - query = { - "ID_MARSH_TRS": MPMarshTRSDB, - "ID_MST": MPMSTDB, - "ID_TRS": MPTRSDB, - "ID_MARSH": MPMarshDB - } - return query[self.param_type.LST_NAME_SH].fetch_one(self.APP_PARAM_STR) + +# SET RELATIONS diff --git a/core/storage/__init__.py b/core/storage/__init__.py index 7f1baa3..78aa995 100644 --- a/core/storage/__init__.py +++ b/core/storage/__init__.py @@ -1,6 +1,10 @@ +from .base import BaseStorage from .profile_storage import Storage as ProfileStorage from .task_storage import Storage as TaskStorage +from .note_storage import Storage as NoteStorage +base_storage = BaseStorage() +profile_storage = ProfileStorage() +task_storage = TaskStorage() +note_storage = NoteStorage() -profile = ProfileStorage() -task = TaskStorage() diff --git a/core/storage/base.py b/core/storage/base.py index 3e928df..0d3c9cd 100644 --- a/core/storage/base.py +++ b/core/storage/base.py @@ -1,6 +1,6 @@ import socket from contextlib import contextmanager -from typing import ContextManager +from typing import ContextManager, NewType, TypeVar import sqlalchemy from firebird.driver import Cursor, Connection @@ -9,6 +9,11 @@ from sqlalchemy import text from core.database.db import engine +def row_to_type(self: sqlalchemy.Row): + return type("KeyedROW", (), {key.upper(): val for key, val in self._mapping.items()}) + + + class BaseStorage: _pool = engine @@ -56,3 +61,6 @@ where ID_SEANS = RDB$GET_CONTEXT('USER_SESSION', 'ID_SEANS'); # yield cursor # finally: # cursor.close() + + +sqlalchemy.Row.row_to_type = row_to_type diff --git a/core/storage/note_storage.py b/core/storage/note_storage.py new file mode 100644 index 0000000..71aa446 --- /dev/null +++ b/core/storage/note_storage.py @@ -0,0 +1,58 @@ +import datetime +import json +from typing import Optional + +import sqlalchemy +from sqlalchemy import text +from .base import BaseStorage +from ..model.note.db import AppNoteDB + + + +class Storage(BaseStorage): + def fetch_all_notes_for_user(self, user_id: int): + stmt = text(""" + select * from APP_NOTE where APP_NOTE_ID_SOTR = :user_id and APP_NOTE_DEL = 0; + """) + with self.get_session() as session: + session: sqlalchemy.Connection + res = [x.row_to_type() for x in session.execute(stmt, { + "user_id": user_id + }).fetchall()] + return [AppNoteDB(id=row.ID_APP_NOTE, user_id=row.APP_NOTE_ID_SOTR, task_id=row.APP_NOTE_ID_APP_TASK, + note_status=row.APP_NOTE_STATUS, tip=row.APP_NOTE_TIP, text=row.APP_NOTE_TEXT) for row in res] + + def update_note(self, id: int, status: int, user_id: int): + stmt = text(""" + insert into APP_EVENT (APP_EVENT_DT, APP_EVENT_ID_SOTR, APP_EVENT_TEXT, APP_EVENT_DATA, APP_EVENT_VID, APP_EVENT_ID_REC) values (current_timestamp, :user_id, 'Изменен статус уведомлений', :event_data, 8797, :note_id) + """) + + event_data = json.dumps([{"8794": str(status)}], separators=(',', ":")) + + with self.get_session() as session: + session: sqlalchemy.Connection + session.execute(stmt, { + "user_id": user_id, + "event_data": event_data, + "note_id": id + }) + session.commit() + + def create_note(self, user_id: int, note_text: str, time_created: datetime.datetime, task_id: Optional[int] = None): + stmt = text(""" + insert into APP_EVENT (APP_EVENT_DT, APP_EVENT_ID_SOTR, APP_EVENT_TEXT, APP_EVENT_DATA, APP_EVENT_VID) values (:time_created, :user_id, :text, :event_data, 8795) + """) + + if (task_id is not None): + event_data = json.dumps([{"ID_APP_TASK": str(task_id)}], separators=(",", ":")) + else: + event_data = None + with self.get_session() as session: + session: sqlalchemy.Connection + session.execute(stmt, { + "time_created": time_created, + "user_id": user_id, + "text": note_text, + "event_data": event_data + }) + session.commit() diff --git a/core/transport/graphql/schema.py b/core/transport/graphql/schema.py index 5dedf4e..a607ef2 100644 --- a/core/transport/graphql/schema.py +++ b/core/transport/graphql/schema.py @@ -1,13 +1,17 @@ import typing -from dataclasses import field + +from core.model.note.db import AppNoteDB +from core.model.task.db import DBAppTask, DBSubTask, DBEvent, DBMarsh, DBTRS, DBMST, Location + from datetime import datetime from enum import Enum -from typing import Union import strawberry +from strawberry.types import Info +from sqlalchemy import select -from core.model.task.enums import MarshTemperatureProperty -from core.storage import task +from core.model.task.enums import StatusEnum +from core.storage import base_storage, task_storage, note_storage @strawberry.enum @@ -30,87 +34,136 @@ class MarshTemperaturePropertyQl(Enum): UNDEFINED = 0 +@strawberry.enum +class MarshTemperaturePropertyQL(Enum): + HOT = 1 + COLD = 2 + UNDEFINED = 0 + + @strawberry.type class Query: @strawberry.field - def tasks(self, user_id: str) -> list['AppTaskQL']: - tasks = task.fetch_tasks_with_subtasks(user_id=int(user_id)) - returned_list: list['AppTaskQL'] = [] - for t in tasks: - updated_task = AppTaskQL( - id=str(t.id), - start_pln=t.start_pln, - end_pln=t.end_pln, - start_fact=t.start_fact, - end_fact=t.end_fact, - status=t.status.value, - task_type=t.task_type.value, - text=t.text, - route=MarshQL( - id=str(t.route.id), - temperature_property=t.route.temperature_property.value, - name=t.route.name, - trailer=TRSQL( - id=str(t.route.trailer.id), - gost=t.route.trailer.gost - ) if t.route.trailer else None, - truck=TRSQL( - id=str(t.route.truck.id), - gost=t.route.truck.gost - ) if t.route.truck else None, - ) if t.route else None, - events=[EventQl( - id=e.id, - type=e.type, - text=e.text, - event_data=e.event_data, - event_datetime=e.event_datetime - ) for e in t.events] - ) - returned_list.append(updated_task) + def tasks(self, user_id: str, is_planned: typing.Optional[bool] = False, is_completed: typing.Optional[bool] = False) -> list['AppTaskQL']: + tasks = task_storage.fetch_tasks_with_subtasks(user_id) - return returned_list + if is_planned: + return [x for x in tasks if x.status == StatusEnum.NOT_DEFINED] + if is_completed: + return [x for x in tasks if x.status == StatusEnum.COMPLETED] + + return tasks + + @strawberry.field + def task(self, user_id: str, task_id: typing.Optional[str] = None, is_active: typing.Optional[bool] = None) -> \ + typing.Optional[ + 'AppTaskQL']: + tasks = task_storage.fetch_tasks_with_subtasks(user_id) + + if task_id is not None: + try: + return next(t for t in tasks if t.id == int(task_id)) + except StopIteration: + return None + if is_active is not None: + try: + return next(t for t in tasks if t.status == StatusEnum.IN_PROGRESS) + except StopIteration: + return None + + @strawberry.field + def notes(self, user_id: str) -> list['AppNoteQL']: + return note_storage.fetch_all_notes_for_user(int(user_id)) + + @strawberry.field + def subtask(self, user_id: str, subtask_id: str) -> typing.Optional['SubtaskQL']: + try: + return next(s for t in task_storage.fetch_tasks_with_subtasks(int(user_id)) for s in t.subtasks if + s.id == int(subtask_id)) + except StopIteration: + return None -@strawberry.type -class AppTaskQL: +@strawberry.experimental.pydantic.type(model=Location) +class LocationQL: + lat: float + lon: float + + +@strawberry.experimental.pydantic.type(model=DBMST) +class MSTQL: + name: str + location: LocationQL + + +@strawberry.experimental.pydantic.type(model=DBSubTask) +class SubtaskQL: id: str - start_pln: datetime - end_pln: datetime - start_fact: datetime | None - end_fact: datetime | None + start_pln: strawberry.auto + end_pln: strawberry.auto + start_fact: strawberry.auto + end_fact: strawberry.auto status: StatusEnumQl - task_type: TaskTypeEnumQl + task_type: int text: str - events = [] - subtasks = [] - route = None + station: typing.Optional[MSTQL] = None -@strawberry.type -class MarshQL: +@strawberry.experimental.pydantic.type(model=DBEvent) +class AppEventQL: id: str - temperature_property: MarshTemperaturePropertyQl - name: str - trailer: typing.Optional['TRSQL'] = None - truck: typing.Optional['TRSQL'] = None - - -@strawberry.type -class TRSQL: - id: str | None - gost: str | None - - -@strawberry.type -class EventQl: - id: int type: str text: str - event_data: dict event_datetime: datetime +@strawberry.experimental.pydantic.type(model=DBTRS) +class TRSQL: + gost: str | None + + +@strawberry.experimental.pydantic.type(model=DBMarsh) +class AppRouteQL: + temperature_property: MarshTemperaturePropertyQL + name: str + trailer: typing.Optional[TRSQL] + truck: typing.Optional[TRSQL] + + +@strawberry.experimental.pydantic.type(model=AppNoteDB) +class AppNoteQL: + id: str + user_id: str + task_id: str + note_status: int + tip: int + text: str + + +@strawberry.experimental.pydantic.type(model=DBAppTask) +class AppTaskQL: + id: str + profile_id: strawberry.auto + start_pln: strawberry.auto + end_pln: strawberry.auto + start_fact: strawberry.auto + end_fact: strawberry.auto + status: StatusEnumQl + task_type: int + text: strawberry.auto + + events: list[AppEventQL] + subtasks: list[SubtaskQL] + route: 'AppRouteQL' + + @strawberry.field + def active_subtask(self) -> typing.Optional[SubtaskQL]: + try: + return next(x for x in self.subtasks if x.status == x.status.IN_PROGRESS) + except StopIteration: + return None + + schema = strawberry.Schema(query=Query) diff --git a/core/transport/rest/auth/handlers.py b/core/transport/rest/auth/handlers.py index a6426bd..478d9e5 100644 --- a/core/transport/rest/auth/handlers.py +++ b/core/transport/rest/auth/handlers.py @@ -8,7 +8,7 @@ from pydantic_extra_types.phone_numbers import PhoneNumber from core.config import Config from core.errors.auth.errors import profile_not_founded, incorrect_phone_number -from core.storage import profile +from core.storage import profile_storage router = APIRouter(prefix="/auth") @@ -34,19 +34,19 @@ async def get_authorization_code(req: PhoneNumberRequest): if not 10 <= len(req.phoneNumber) <= 12: incorrect_phone_number() - p = profile.get_profile_by_phone("".join(char for char in req.phoneNumber if char.isdigit())) + p = profile_storage.get_profile_by_phone("".join(char for char in req.phoneNumber if char.isdigit())) if not p: profile_not_founded() - return {"code": profile.generate_profile_auth_code(p.id, p.phone_number)} + return {"code": profile_storage.generate_profile_auth_code(p.id, p.phone_number)} @router.post("/phone/code") async def get_access_token(form_data: OAuth2PasswordRequestForm = Depends()) -> Token: - p = profile.get_profile_by_phone(''.join(char for char in form_data.username if char.isdigit())) + p = profile_storage.get_profile_by_phone(''.join(char for char in form_data.username if char.isdigit())) if not p: profile_not_founded() - check = profile.check_profile_code(p.id, form_data.password) + check = profile_storage.check_profile_code(p.id, form_data.password) if not check: raise HTTPException(status_code=403, detail="Incorrect code") diff --git a/core/transport/rest/note/__init__.py b/core/transport/rest/note/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/transport/rest/note/handlers.py b/core/transport/rest/note/handlers.py new file mode 100644 index 0000000..e69de29 diff --git a/core/transport/rest/tasks/handlers.py b/core/transport/rest/tasks/handlers.py index 8e8cbad..3e2888d 100644 --- a/core/transport/rest/tasks/handlers.py +++ b/core/transport/rest/tasks/handlers.py @@ -10,21 +10,21 @@ from core.model.profile.db import ProfileDB from core.model.task.db import DBAppTask, DBSubTask, DBEvent from core.model.task.enums import StatusEnum from core.model.task.requests import SetTaskStatusActiveRequest, SetSubtaskStatusRequest, UpdTaskRequest, UpdTaskData -from core.storage import task +from core.storage import task_storage from core.model.task.db2 import MPAppTaskDB router = APIRouter(prefix="/tasks", tags=["Tasks and subtasks"]) -@router.get("/test", description="Fetch Task for authenticated user") -async def get_task_test(user: ProfileDB = Depends(get_user_from_token)) -> list[MPAppTaskDB]: - return MPAppTaskDB.fetch_all() +# @router.get("/test", description="Fetch Task for authenticated user") +# async def get_task_test(user: ProfileDB = Depends(get_user_from_token)) -> list[MPAppTaskDB]: +# return MPAppTaskDB.fetch_all() @router.get("", description="Fetch Task for authenticated user") async def get_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: - return task.fetch_tasks_with_subtasks(user.id) + return task_storage.fetch_tasks_with_subtasks(user.id) @router.post("") @@ -83,7 +83,7 @@ async def upd_task(req: UpdTaskRequest, user: ProfileDB = Depends(get_user_from_ } } - tasks = task.fetch_tasks_with_subtasks(user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user.id) available_tasks_ids = [x.id for x in tasks] + [sbt.id for t in tasks for sbt in t.subtasks] req.data.sort(key=lambda u: u.dt) @@ -123,21 +123,21 @@ async def upd_task(req: UpdTaskRequest, user: ProfileDB = Depends(get_user_from_ except KeyError as exc: update_task_by_chain_failed(exc) - [task.update_task(event, user.id) for event in req.data] - return task.fetch_tasks_with_subtasks(user.id) + [task_storage.update_task(event, user.id) for event in req.data] + return task_storage.fetch_tasks_with_subtasks(user.id) @router.get("/planned") async def get_planned_tasks(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: # TODO Rebuild this method to fetch only planned tasks - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) return [x for x in tasks if x.status == StatusEnum.NOT_DEFINED] @router.get("/active") async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> DBAppTask | dict: # TODO Rebuild this method to fetch only active tasks - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) try: return next(x for x in tasks if x.status == StatusEnum.IN_PROGRESS) except StopIteration: @@ -147,7 +147,7 @@ async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> DBA @router.get("/completed") async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> list[DBAppTask]: # TODO Rebuild this method to fetch only active tasks - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) return [x for x in tasks if x.status == StatusEnum.COMPLETED] @@ -170,7 +170,7 @@ async def get_active_task(user: ProfileDB = Depends(get_user_from_token)) -> lis @router.get("/{task_id}/subtasks") async def get_subtasks(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBSubTask]: # TODO Rebuild this method to fetch only subtasks - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) try: t = next(x for x in tasks if x.id == task_id) return t.subtasks @@ -181,11 +181,11 @@ async def get_subtasks(user: ProfileDB = Depends(get_user_from_token), task_id: @router.post("/subtask") async def set_status_to_subtask(req_data: SetSubtaskStatusRequest, user: ProfileDB = Depends(get_user_from_token)) -> DBSubTask: - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) try: subtask = next(subtask for t in tasks for subtask in t.subtasks if subtask.id == req_data.subtask_id) - task.set_subtask_to_completed(subtask_id=subtask.id, profile_id=user.id, dt=req_data.finished_dt) + task_storage.set_subtask_to_completed(subtask_id=subtask.id, profile_id=user.id, dt=req_data.finished_dt) subtask.status = StatusEnum.COMPLETED return subtask @@ -196,7 +196,7 @@ async def set_status_to_subtask(req_data: SetSubtaskStatusRequest, @router.get("/{task_id}/events") async def get_events(user: ProfileDB = Depends(get_user_from_token), task_id: int = Path()) -> list[DBEvent]: # TODO Rebuild this method to fetch only events - tasks = task.fetch_tasks_with_subtasks(user_id=user.id) + tasks = task_storage.fetch_tasks_with_subtasks(user_id=user.id) try: t = next(x for x in tasks if x.id == task_id) return [x for x in t.events if x.type == "Change"]