From e2e618a83e1d2545fe718b1272ff4014f44adf2e Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:46:15 +0300 Subject: [PATCH 01/26] f --- requirements.txt | 4 ++-- userdata_api/models/db.py | 2 +- userdata_api/routes/param.py | 44 +++++++++++++++++++++++------------- userdata_api/routes/user.py | 2 +- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1675d1f..cf773d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ alembic auth-lib-profcomff[fastapi] -fastapi +fastapi==0.100.1 fastapi-sqlalchemy gunicorn logging-profcomff psycopg2-binary pydantic[dotenv] SQLAlchemy -uvicorn +uvicorn==0.23.1 pydantic-settings event_schema_profcomff confluent_kafka diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py index 22fa08d..1d4fe35 100644 --- a/userdata_api/models/db.py +++ b/userdata_api/models/db.py @@ -56,7 +56,7 @@ class Param(BaseDbModel): Например, параметрами может являться почта и номер телефона, а параметры эти могут лежать в категории "контакты" """ - + is_hidden: Mapped[bool] = mapped_column(Boolean, default=True) name: Mapped[str] = mapped_column(String) category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) is_required: Mapped[bool] = mapped_column(Boolean, default=False) diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py index e851344..fe239cf 100644 --- a/userdata_api/routes/param.py +++ b/userdata_api/routes/param.py @@ -10,16 +10,15 @@ from userdata_api.schemas.param import ParamGet, ParamPatch, ParamPost from userdata_api.schemas.response_model import StatusResponseModel - param = APIRouter(prefix="/category/{category_id}/param", tags=["Param"]) @param.post("", response_model=ParamGet) async def create_param( - request: Request, - category_id: int, - param_inp: ParamPost, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.create"], allow_none=False, auto_error=True)), + request: Request, + category_id: int, + param_inp: ParamPost, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.create"], allow_none=False, auto_error=True)), ) -> ParamGet: """ Создать поле внутри категории. Ответ на пользовательские данные будет такой {..., category: {...,param: '', ...}} @@ -37,17 +36,30 @@ async def create_param( @param.get("/{id}", response_model=ParamGet) -async def get_param(id: int, category_id: int) -> ParamGet: +async def get_param( + id: int, + category_id: int, + _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), +) -> ParamGet: """ Получить параметр по айди \f :param id: Айди параметра - :param category_id: айди категории в которой этот параметр находиится + :param category_id: айди категории в которой этот параметр находится :return: ParamGet - полученный параметр + :param _: Аутентификация """ + res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none() if not res: raise ObjectNotFound(Param, id) + if res.is_hidden: + category_scopes = set(Category.query(session=db.session).filter(Category.id == category_id).one_or_none() + .read_scope) + user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) + if category_scopes - user_scopes: + raise ObjectNotFound(Param, id) + ParamGet.model_validate(res) return ParamGet.model_validate(res) @@ -65,11 +77,11 @@ async def get_params(category_id: int) -> list[ParamGet]: @param.patch("/{id}", response_model=ParamGet) async def patch_param( - request: Request, - id: int, - category_id: int, - param_inp: ParamPatch, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.update"], allow_none=False, auto_error=True)), + request: Request, + id: int, + category_id: int, + param_inp: ParamPatch, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.update"], allow_none=False, auto_error=True)), ) -> ParamGet: """ Обновить параметр внутри категории @@ -92,10 +104,10 @@ async def patch_param( @param.delete("/{id}", response_model=StatusResponseModel) async def delete_param( - request: Request, - id: int, - category_id: int, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.delete"], allow_none=False, auto_error=True)), + request: Request, + id: int, + category_id: int, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.delete"], allow_none=False, auto_error=True)), ) -> StatusResponseModel: """ Удалить параметр внутри категории diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 093f839..618452b 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -53,7 +53,7 @@ async def update_user( Чтобы обновить от имени админиа, надо иметь скоуп `userdata.info.admin` Чтобы обновить неизменяемую информацию надо обладать скоупом `userdata.info.update` Для обновления своей информации(источник `user`) не нужны скоупы на обновление соответствующих категорий - Для обновления чужой информации от имени админа(источник `admin`) + Для обновления чужой информации от имени админа(источник `admin`) нужны скоупы на обновление всех указанных в теле запроса категорий пользовательских данных данных \f :param request: Запрос из fastapi From 0bf9ecc19d52485fdf603f4722c35dc4249398b4 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:09:12 +0300 Subject: [PATCH 02/26] f --- userdata_api/routes/param.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py index fe239cf..767134d 100644 --- a/userdata_api/routes/param.py +++ b/userdata_api/routes/param.py @@ -59,7 +59,7 @@ async def get_param( user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) if category_scopes - user_scopes: raise ObjectNotFound(Param, id) - ParamGet.model_validate(res) + return ParamGet.model_validate(res) return ParamGet.model_validate(res) From 43ce24b09ffbd15f6093b3f02242f4f8b3605aa7 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Wed, 3 Apr 2024 08:41:57 +0300 Subject: [PATCH 03/26] f --- userdata_api/routes/param.py | 1 + 1 file changed, 1 insertion(+) diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py index 767134d..9431960 100644 --- a/userdata_api/routes/param.py +++ b/userdata_api/routes/param.py @@ -10,6 +10,7 @@ from userdata_api.schemas.param import ParamGet, ParamPatch, ParamPost from userdata_api.schemas.response_model import StatusResponseModel + param = APIRouter(prefix="/category/{category_id}/param", tags=["Param"]) From 5a053850f1a6d0caf44c4e718745f253ddd48c5b Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Wed, 3 Apr 2024 08:55:02 +0300 Subject: [PATCH 04/26] f --- userdata_api/models/db.py | 1 + userdata_api/routes/param.py | 37 ++++++++++++++++++------------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py index 1d4fe35..67b879d 100644 --- a/userdata_api/models/db.py +++ b/userdata_api/models/db.py @@ -56,6 +56,7 @@ class Param(BaseDbModel): Например, параметрами может являться почта и номер телефона, а параметры эти могут лежать в категории "контакты" """ + is_hidden: Mapped[bool] = mapped_column(Boolean, default=True) name: Mapped[str] = mapped_column(String) category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py index 9431960..e37e58e 100644 --- a/userdata_api/routes/param.py +++ b/userdata_api/routes/param.py @@ -16,10 +16,10 @@ @param.post("", response_model=ParamGet) async def create_param( - request: Request, - category_id: int, - param_inp: ParamPost, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.create"], allow_none=False, auto_error=True)), + request: Request, + category_id: int, + param_inp: ParamPost, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.create"], allow_none=False, auto_error=True)), ) -> ParamGet: """ Создать поле внутри категории. Ответ на пользовательские данные будет такой {..., category: {...,param: '', ...}} @@ -38,9 +38,9 @@ async def create_param( @param.get("/{id}", response_model=ParamGet) async def get_param( - id: int, - category_id: int, - _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), + id: int, + category_id: int, + _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), ) -> ParamGet: """ Получить параметр по айди @@ -55,8 +55,9 @@ async def get_param( if not res: raise ObjectNotFound(Param, id) if res.is_hidden: - category_scopes = set(Category.query(session=db.session).filter(Category.id == category_id).one_or_none() - .read_scope) + category_scopes = set( + Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope + ) user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) if category_scopes - user_scopes: raise ObjectNotFound(Param, id) @@ -78,11 +79,11 @@ async def get_params(category_id: int) -> list[ParamGet]: @param.patch("/{id}", response_model=ParamGet) async def patch_param( - request: Request, - id: int, - category_id: int, - param_inp: ParamPatch, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.update"], allow_none=False, auto_error=True)), + request: Request, + id: int, + category_id: int, + param_inp: ParamPatch, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.update"], allow_none=False, auto_error=True)), ) -> ParamGet: """ Обновить параметр внутри категории @@ -105,10 +106,10 @@ async def patch_param( @param.delete("/{id}", response_model=StatusResponseModel) async def delete_param( - request: Request, - id: int, - category_id: int, - _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.delete"], allow_none=False, auto_error=True)), + request: Request, + id: int, + category_id: int, + _: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.param.delete"], allow_none=False, auto_error=True)), ) -> StatusResponseModel: """ Удалить параметр внутри категории From bf7774aaafca66facb1b13153c98e323bd617fb5 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Sun, 12 May 2024 23:02:50 +0300 Subject: [PATCH 05/26] f --- requirements.txt | 4 ++-- userdata_api/models/db.py | 2 +- userdata_api/routes/user.py | 10 +++++++--- userdata_api/schemas/param.py | 2 ++ userdata_api/utils/user.py | 21 ++++++++++++++++++--- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index cf773d8..1675d1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ alembic auth-lib-profcomff[fastapi] -fastapi==0.100.1 +fastapi fastapi-sqlalchemy gunicorn logging-profcomff psycopg2-binary pydantic[dotenv] SQLAlchemy -uvicorn==0.23.1 +uvicorn pydantic-settings event_schema_profcomff confluent_kafka diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py index 67b879d..76f1333 100644 --- a/userdata_api/models/db.py +++ b/userdata_api/models/db.py @@ -57,7 +57,7 @@ class Param(BaseDbModel): а параметры эти могут лежать в категории "контакты" """ - is_hidden: Mapped[bool] = mapped_column(Boolean, default=True) + visible_in_user_response: Mapped[bool] = mapped_column(Boolean, default=False) name: Mapped[str] = mapped_column(String) category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) is_required: Mapped[bool] = mapped_column(Boolean, default=False) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 618452b..82e2a5f 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -1,7 +1,7 @@ from typing import Any from auth_lib.fastapi import UnionAuth -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Query from userdata_api.schemas.response_model import StatusResponseModel from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate @@ -14,12 +14,15 @@ @user.get("/{id}", response_model=UserInfoGet) async def get_user_info( - id: int, user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)) + id: int, + additional_data: list[str] = Query(default=[]), + user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), ) -> UserInfoGet: """ Получить информацию о пользователе \f :param id: Айди овнера информации(пользователя) + :additional_data: список невидимых по дефолту параметров :param user: Аутентфикация :return: Словарь, ключи - категории на которые хватило прав(овнеру не нужны права, он получает всё). Значения - словари, ключи которых - имена параметров, @@ -30,7 +33,8 @@ async def get_user_info( ... } """ - return UserInfoGet.model_validate(await get(id, user)) + + return UserInfoGet.model_validate(await get(id, user, additional_data)) @user.post("/{id}", response_model=StatusResponseModel) diff --git a/userdata_api/schemas/param.py b/userdata_api/schemas/param.py index 5d8c2b9..ba4e3b5 100644 --- a/userdata_api/schemas/param.py +++ b/userdata_api/schemas/param.py @@ -6,6 +6,7 @@ class ParamPost(Base): + visible_in_user_response: bool = True name: constr(min_length=1) is_required: bool changeable: bool @@ -13,6 +14,7 @@ class ParamPost(Base): class ParamPatch(Base): + visible_in_user_response: bool = True name: constr(min_length=1) | None = None is_required: bool | None = None changeable: bool | None = None diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 1925259..1927dca 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi_sqlalchemy import db -from sqlalchemy import not_ +from sqlalchemy import String, cast, func, not_, or_ from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType @@ -90,7 +90,9 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int continue -async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> UserInfoGet: +async def get_user_info( + user_id: int, user: dict[str, int | list[dict[str, str | int]]], additional_data: list[str] +) -> UserInfoGet: """ Возвращает информауию о пользователе в соотетствии с переданным токеном. @@ -102,13 +104,26 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | :param user: Сессия выполняющего запрос данных :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя """ + print(additional_data) infos: list[Info] = ( Info.query(session=db.session) .join(Param) .join(Category) - .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) + .filter( + Info.owner_id == user_id, + not_(Param.is_deleted), + not_(Category.is_deleted), + or_( + Param.visible_in_user_response, + func.concat('category', cast(Category.id, String), '.param', cast(Param.id, String)).in_( + additional_data + ), + ), + ) .all() ) + for info in infos: + print(f'category{info.category.id}.param{info.param.id}') if not infos: raise ObjectNotFound(Info, user_id) scope_names = [scope["name"] for scope in user["session_scopes"]] From 1d41a147404aff2b7f05dd9b9581abe0d7ba588f Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Sun, 12 May 2024 23:09:19 +0300 Subject: [PATCH 06/26] f --- userdata_api/utils/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 1927dca..d2f725c 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -115,7 +115,7 @@ async def get_user_info( not_(Category.is_deleted), or_( Param.visible_in_user_response, - func.concat('category', cast(Category.id, String), '.param', cast(Param.id, String)).in_( + func.concat('category', cast(Category.id, String), '.field', cast(Param.id, String)).in_( additional_data ), ), From 756e75be82447080cf19989ee0427467e18f9602 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Sun, 12 May 2024 23:16:42 +0300 Subject: [PATCH 07/26] f --- userdata_api/utils/user.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index d2f725c..b7718be 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -104,7 +104,6 @@ async def get_user_info( :param user: Сессия выполняющего запрос данных :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя """ - print(additional_data) infos: list[Info] = ( Info.query(session=db.session) .join(Param) @@ -122,8 +121,6 @@ async def get_user_info( ) .all() ) - for info in infos: - print(f'category{info.category.id}.param{info.param.id}') if not infos: raise ObjectNotFound(Info, user_id) scope_names = [scope["name"] for scope in user["session_scopes"]] From fcb865c3d750119fdb43868e04369bcd09e44fb7 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:11:56 +0300 Subject: [PATCH 08/26] f --- userdata_api/models/db.py | 2 +- userdata_api/routes/param.py | 18 ++---------------- userdata_api/utils/user.py | 1 + 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py index 76f1333..6ca1629 100644 --- a/userdata_api/models/db.py +++ b/userdata_api/models/db.py @@ -57,7 +57,7 @@ class Param(BaseDbModel): а параметры эти могут лежать в категории "контакты" """ - visible_in_user_response: Mapped[bool] = mapped_column(Boolean, default=False) + visible_in_user_response: Mapped[bool] = mapped_column(Boolean, default=True) name: Mapped[str] = mapped_column(String) category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) is_required: Mapped[bool] = mapped_column(Boolean, default=False) diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py index e37e58e..e851344 100644 --- a/userdata_api/routes/param.py +++ b/userdata_api/routes/param.py @@ -37,31 +37,17 @@ async def create_param( @param.get("/{id}", response_model=ParamGet) -async def get_param( - id: int, - category_id: int, - _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), -) -> ParamGet: +async def get_param(id: int, category_id: int) -> ParamGet: """ Получить параметр по айди \f :param id: Айди параметра - :param category_id: айди категории в которой этот параметр находится + :param category_id: айди категории в которой этот параметр находиится :return: ParamGet - полученный параметр - :param _: Аутентификация """ - res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none() if not res: raise ObjectNotFound(Param, id) - if res.is_hidden: - category_scopes = set( - Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope - ) - user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) - if category_scopes - user_scopes: - raise ObjectNotFound(Param, id) - return ParamGet.model_validate(res) return ParamGet.model_validate(res) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index b7718be..d8be521 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -102,6 +102,7 @@ async def get_user_info( :param user_id: Айди пользователя :param user: Сессия выполняющего запрос данных + :additional_data: Список невидимых по дефолту параметров :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя """ infos: list[Info] = ( From 1e40c58fde0c8b1d2a31279860c965a91399b0bb Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:07:31 +0300 Subject: [PATCH 09/26] f --- .idea/.gitignore | 3 ++ .../inspectionProfiles/profiles_settings.xml | 6 ++++ .idea/misc.xml | 7 +++++ .idea/modules.xml | 8 ++++++ .idea/userdata-api.iml | 15 ++++++++++ .idea/vcs.xml | 6 ++++ ..._add_visible_in_user_response_field_to_.py | 28 +++++++++++++++++++ 7 files changed, 73 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/userdata-api.iml create mode 100644 .idea/vcs.xml create mode 100644 migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74e4297 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0958c0c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/userdata-api.iml b/.idea/userdata-api.iml new file mode 100644 index 0000000..dd0594a --- /dev/null +++ b/.idea/userdata-api.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py b/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py new file mode 100644 index 0000000..f411bb1 --- /dev/null +++ b/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py @@ -0,0 +1,28 @@ +"""Add visible_in_user_response field to Param table + +Revision ID: ea9459426db1 +Revises: f8c57101c0f6 +Create Date: 2024-05-12 20:27:45.549332 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = 'ea9459426db1' +down_revision = 'f8c57101c0f6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('param', sa.Column('visible_in_user_response', sa.Boolean(), nullable=False, server_default='0')) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('param', 'visible_in_user_response') + # ### end Alembic commands ### From 5819ffdcc96564ea195eae079ed4ac248b7102ba Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:02:47 +0300 Subject: [PATCH 10/26] f --- .idea/inspectionProfiles/Project_Default.xml | 22 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .../shelved.patch | 88 ++++++ .../shelved.patch | 0 ...Checkout_at_04_05_2024_15_16__Changes_.xml | 4 + .../shelved.patch | 94 +++++++ .../shelved.patch | 0 ...Checkout_at_07_05_2024_16_51__Changes_.xml | 4 + .../shelved.patch | 0 ...Checkout_at_18_07_2024_19_52__Changes_.xml | 4 + .idea/userdata-api.iml | 10 + .idea/vcs.xml | 6 + .idea/workspace.xml | 254 ++++++++++++++++++ 14 files changed, 496 insertions(+) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]1/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]1/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52_[Changes]/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml create mode 100644 .idea/userdata-api.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..e4d7a92 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6ce1a79 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch new file mode 100644 index 0000000..4b2d62c --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch @@ -0,0 +1,88 @@ +Index: userdata_api/models/db.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from __future__ import annotations\r\n\r\nfrom datetime import datetime\r\nfrom enum import Enum\r\nfrom typing import Final\r\n\r\nfrom sqlalchemy import Boolean, DateTime\r\nfrom sqlalchemy import Enum as DbEnum\r\nfrom sqlalchemy import ForeignKey, Integer, String\r\nfrom sqlalchemy.ext.hybrid import hybrid_property\r\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\r\n\r\nfrom userdata_api.models.base import BaseDbModel\r\n\r\n\r\nclass ViewType(str, Enum):\r\n \"\"\"\r\n Тип отображения пользоватльских данных в ответе `GET /user/{user_id}`\r\n ALL: {category: {param: [val1, val2, ...]}}\r\n LAST: {category: {param: last_modified_value}}\r\n MOST_TRUSTED: {category: {param: most_trusted_value}}\r\n \"\"\"\r\n\r\n ALL: Final[str] = \"all\"\r\n LAST: Final[str] = \"last\"\r\n MOST_TRUSTED: Final[str] = \"most_trusted\"\r\n\r\n\r\nclass Category(BaseDbModel):\r\n \"\"\"\r\n Категория - объеденение параметров пользовательских данных.\r\n Если параметром может быть, например, номер студенческого и номер профсоюзного,\r\n то категорией, их объединяющей, может быть \"студенческая информация\" или \"документы\"\r\n \"\"\"\r\n\r\n name: Mapped[str] = mapped_column(String)\r\n read_scope: Mapped[str] = mapped_column(String, nullable=True)\r\n update_scope: Mapped[str] = mapped_column(String, nullable=True)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n params: Mapped[list[Param]] = relationship(\r\n \"Param\",\r\n foreign_keys=\"Param.category_id\",\r\n back_populates=\"category\",\r\n primaryjoin=\"and_(Category.id==Param.category_id, not_(Param.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n\r\nclass Param(BaseDbModel):\r\n \"\"\"\r\n Параметр - находится внутри категории,\r\n к нему можно задавать значение у конкретного пользователя.\r\n Например, параметрами может являться почта и номер телефона,\r\n а параметры эти могут лежать в категории \"контакты\"\r\n \"\"\"\r\n\r\n is_hidden: Mapped[bool] = mapped_column(Boolean, default=True)\r\n name: Mapped[str] = mapped_column(String)\r\n category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id))\r\n is_required: Mapped[bool] = mapped_column(Boolean, default=False)\r\n changeable: Mapped[bool] = mapped_column(Boolean, default=True)\r\n type: Mapped[ViewType] = mapped_column(DbEnum(ViewType, native_enum=False))\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n category: Mapped[Category] = relationship(\r\n \"Category\",\r\n foreign_keys=\"Param.category_id\",\r\n back_populates=\"params\",\r\n primaryjoin=\"and_(Param.category_id==Category.id, not_(Category.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n values: Mapped[list[Info]] = relationship(\r\n \"Info\",\r\n foreign_keys=\"Info.param_id\",\r\n back_populates=\"param\",\r\n primaryjoin=\"and_(Param.id==Info.param_id, not_(Info.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n @property\r\n def pytype(self) -> type[str | list[str]]:\r\n return list[str] if self.type == ViewType.ALL else str\r\n\r\n\r\nclass Source(BaseDbModel):\r\n \"\"\"\r\n Источник данных - субъект изменения польщовательских данных - тот, кто меняет данные\r\n В HTTP методах доступно только два источника - user/admin\r\n Субъект может менять только данные, созданные собой же.\r\n У источника есть уровень доверия, который влияет на вид ответа `GET /user/{user_id}`\r\n \"\"\"\r\n\r\n name: Mapped[str] = mapped_column(String, unique=True)\r\n trust_level: Mapped[int] = mapped_column(Integer, default=0, nullable=False)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n values: Mapped[Info] = relationship(\r\n \"Info\",\r\n foreign_keys=\"Info.source_id\",\r\n back_populates=\"source\",\r\n primaryjoin=\"and_(Source.id==Info.source_id, not_(Info.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n\r\nclass Info(BaseDbModel):\r\n \"\"\"\r\n Значения параметров для конкретных польщзователей\r\n Если, например, телефон - параметр, то здесь указывается его значение для\r\n польщзователя(owner_id) - объекта изменения пользовательских данных\r\n \"\"\"\r\n\r\n param_id: Mapped[int] = mapped_column(Integer, ForeignKey(Param.id))\r\n source_id: Mapped[int] = mapped_column(Integer, ForeignKey(Source.id))\r\n owner_id: Mapped[int] = mapped_column(Integer, nullable=False)\r\n value: Mapped[str] = mapped_column(String, nullable=False)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n param: Mapped[Param] = relationship(\r\n \"Param\",\r\n foreign_keys=\"Info.param_id\",\r\n back_populates=\"values\",\r\n primaryjoin=\"and_(Info.param_id==Param.id, not_(Param.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n source: Mapped[Source] = relationship(\r\n \"Source\",\r\n foreign_keys=\"Info.source_id\",\r\n back_populates=\"values\",\r\n primaryjoin=\"and_(Info.source_id==Source.id, not_(Source.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n @hybrid_property\r\n def category(self) -> Category:\r\n return self.param.category\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py +--- a/userdata_api/models/db.py ++++ b/userdata_api/models/db.py +@@ -57,7 +57,7 @@ + а параметры эти могут лежать в категории "контакты" + """ + +- is_hidden: Mapped[bool] = mapped_column(Boolean, default=True) ++ visible_in_user_response: Mapped[bool] = mapped_column(Boolean, default=False) + name: Mapped[str] = mapped_column(String) + category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) + is_required: Mapped[bool] = mapped_column(Boolean, default=False) +Index: userdata_api/routes/param.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from typing import Any\r\n\r\nfrom auth_lib.fastapi import UnionAuth\r\nfrom fastapi import APIRouter, Depends, Request\r\nfrom fastapi_sqlalchemy import db\r\nfrom pydantic.type_adapter import TypeAdapter\r\n\r\nfrom userdata_api.exceptions import AlreadyExists, ObjectNotFound\r\nfrom userdata_api.models.db import Category, Param\r\nfrom userdata_api.schemas.param import ParamGet, ParamPatch, ParamPost\r\nfrom userdata_api.schemas.response_model import StatusResponseModel\r\n\r\n\r\nparam = APIRouter(prefix=\"/category/{category_id}/param\", tags=[\"Param\"])\r\n\r\n\r\n@param.post(\"\", response_model=ParamGet)\r\nasync def create_param(\r\n request: Request,\r\n category_id: int,\r\n param_inp: ParamPost,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.create\"], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Создать поле внутри категории. Ответ на пользовательские данные будет такой {..., category: {...,param: '', ...}}\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param category_id: Айди котегории в которой создавать параметр\r\n :param param_inp: Модель для создания\r\n :param _: Аутентификация\r\n :return: ParamGet - созданный параметр\r\n \"\"\"\r\n Category.get(category_id, session=db.session)\r\n if Param.query(session=db.session).filter(Param.category_id == category_id, Param.name == param_inp.name).all():\r\n raise AlreadyExists(Param, param_inp.name)\r\n return ParamGet.model_validate(Param.create(session=db.session, **param_inp.dict(), category_id=category_id))\r\n\r\n\r\n@param.get(\"/{id}\", response_model=ParamGet)\r\nasync def get_param(\r\n id: int,\r\n category_id: int,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Получить параметр по айди\r\n \\f\r\n :param id: Айди параметра\r\n :param category_id: айди категории в которой этот параметр находится\r\n :return: ParamGet - полученный параметр\r\n :param _: Аутентификация\r\n \"\"\"\r\n\r\n res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none()\r\n if not res:\r\n raise ObjectNotFound(Param, id)\r\n if res.is_hidden:\r\n category_scopes = set(\r\n Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope\r\n )\r\n user_scopes = set([scope[\"name\"].lower() for scope in _[\"session_scopes\"]])\r\n if category_scopes - user_scopes:\r\n raise ObjectNotFound(Param, id)\r\n return ParamGet.model_validate(res)\r\n return ParamGet.model_validate(res)\r\n\r\n\r\n@param.get(\"\", response_model=list[ParamGet])\r\nasync def get_params(category_id: int) -> list[ParamGet]:\r\n \"\"\"\r\n Получить все параметры категории\r\n \\f\r\n :param category_id: Айди категории\r\n :return: list[ParamGet] - список полученных параметров\r\n \"\"\"\r\n type_adapter = TypeAdapter(list[ParamGet])\r\n return type_adapter.validate_python(Param.query(session=db.session).filter(Param.category_id == category_id).all())\r\n\r\n\r\n@param.patch(\"/{id}\", response_model=ParamGet)\r\nasync def patch_param(\r\n request: Request,\r\n id: int,\r\n category_id: int,\r\n param_inp: ParamPatch,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.update\"], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Обновить параметр внутри категории\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param id: Айди обновляемого параметра\r\n :param category_id: Адйи категории в которой находится параметр\r\n :param param_inp: Модель для создания параметра\r\n :param _: Аутентификация\r\n :return: ParamGet- Обновленный параметр\r\n \"\"\"\r\n if category_id:\r\n Category.get(category_id, session=db.session)\r\n if category_id:\r\n return ParamGet.from_orm(\r\n Param.update(id, session=db.session, **param_inp.dict(exclude_unset=True), category_id=category_id)\r\n )\r\n return ParamGet.model_validate(Param.update(id, session=db.session, **param_inp.dict(exclude_unset=True)))\r\n\r\n\r\n@param.delete(\"/{id}\", response_model=StatusResponseModel)\r\nasync def delete_param(\r\n request: Request,\r\n id: int,\r\n category_id: int,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.delete\"], allow_none=False, auto_error=True)),\r\n) -> StatusResponseModel:\r\n \"\"\"\r\n Удалить параметр внутри категории\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param id: Айди удаляемого параметра\r\n :param category_id: Айди категории в которой находится удлаляемый параметр\r\n :param _: Аутентификация\r\n :return: None\r\n \"\"\"\r\n res: Param = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none()\r\n if not res:\r\n raise ObjectNotFound(Param, id)\r\n res.is_deleted = True\r\n db.session.commit()\r\n return StatusResponseModel(status=\"Success\", message=\"Param deleted\")\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py +--- a/userdata_api/routes/param.py ++++ b/userdata_api/routes/param.py +@@ -37,32 +37,7 @@ + + + @param.get("/{id}", response_model=ParamGet) +-async def get_param( +- id: int, +- category_id: int, +- _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), +-) -> ParamGet: +- """ +- Получить параметр по айди +- \f +- :param id: Айди параметра +- :param category_id: айди категории в которой этот параметр находится +- :return: ParamGet - полученный параметр +- :param _: Аутентификация +- """ + +- res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none() +- if not res: +- raise ObjectNotFound(Param, id) +- if res.is_hidden: +- category_scopes = set( +- Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope +- ) +- user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) +- if category_scopes - user_scopes: +- raise ObjectNotFound(Param, id) +- return ParamGet.model_validate(res) +- return ParamGet.model_validate(res) + + + @param.get("", response_model=list[ParamGet]) +Index: requirements.txt +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>alembic\r\nauth-lib-profcomff[fastapi]\r\nfastapi==0.100.1\r\nfastapi-sqlalchemy\r\ngunicorn\r\nlogging-profcomff\r\npsycopg2-binary\r\npydantic[dotenv]\r\nSQLAlchemy\r\nuvicorn==0.23.1\r\npydantic-settings\r\nevent_schema_profcomff\r\nconfluent_kafka\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/requirements.txt b/requirements.txt +--- a/requirements.txt ++++ b/requirements.txt +@@ -1,13 +1,13 @@ + alembic + auth-lib-profcomff[fastapi] +-fastapi==0.100.1 ++fastapi + fastapi-sqlalchemy + gunicorn + logging-profcomff + psycopg2-binary + pydantic[dotenv] + SQLAlchemy +-uvicorn==0.23.1 ++uvicorn + pydantic-settings + event_schema_profcomff + confluent_kafka diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]1/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]1/shelved.patch new file mode 100644 index 0000000..e69de29 diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml new file mode 100644 index 0000000..a70855b --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch new file mode 100644 index 0000000..7ce3d68 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch @@ -0,0 +1,94 @@ +Index: userdata_api/models/db.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from __future__ import annotations\r\n\r\nfrom datetime import datetime\r\nfrom enum import Enum\r\nfrom typing import Final\r\n\r\nfrom sqlalchemy import Boolean, DateTime\r\nfrom sqlalchemy import Enum as DbEnum\r\nfrom sqlalchemy import ForeignKey, Integer, String\r\nfrom sqlalchemy.ext.hybrid import hybrid_property\r\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\r\n\r\nfrom userdata_api.models.base import BaseDbModel\r\n\r\n\r\nclass ViewType(str, Enum):\r\n \"\"\"\r\n Тип отображения пользоватльских данных в ответе `GET /user/{user_id}`\r\n ALL: {category: {param: [val1, val2, ...]}}\r\n LAST: {category: {param: last_modified_value}}\r\n MOST_TRUSTED: {category: {param: most_trusted_value}}\r\n \"\"\"\r\n\r\n ALL: Final[str] = \"all\"\r\n LAST: Final[str] = \"last\"\r\n MOST_TRUSTED: Final[str] = \"most_trusted\"\r\n\r\n\r\nclass Category(BaseDbModel):\r\n \"\"\"\r\n Категория - объеденение параметров пользовательских данных.\r\n Если параметром может быть, например, номер студенческого и номер профсоюзного,\r\n то категорией, их объединяющей, может быть \"студенческая информация\" или \"документы\"\r\n \"\"\"\r\n\r\n name: Mapped[str] = mapped_column(String)\r\n read_scope: Mapped[str] = mapped_column(String, nullable=True)\r\n update_scope: Mapped[str] = mapped_column(String, nullable=True)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n params: Mapped[list[Param]] = relationship(\r\n \"Param\",\r\n foreign_keys=\"Param.category_id\",\r\n back_populates=\"category\",\r\n primaryjoin=\"and_(Category.id==Param.category_id, not_(Param.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n\r\nclass Param(BaseDbModel):\r\n \"\"\"\r\n Параметр - находится внутри категории,\r\n к нему можно задавать значение у конкретного пользователя.\r\n Например, параметрами может являться почта и номер телефона,\r\n а параметры эти могут лежать в категории \"контакты\"\r\n \"\"\"\r\n\r\n is_hidden: Mapped[bool] = mapped_column(Boolean, default=True)\r\n name: Mapped[str] = mapped_column(String)\r\n category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id))\r\n is_required: Mapped[bool] = mapped_column(Boolean, default=False)\r\n changeable: Mapped[bool] = mapped_column(Boolean, default=True)\r\n type: Mapped[ViewType] = mapped_column(DbEnum(ViewType, native_enum=False))\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n category: Mapped[Category] = relationship(\r\n \"Category\",\r\n foreign_keys=\"Param.category_id\",\r\n back_populates=\"params\",\r\n primaryjoin=\"and_(Param.category_id==Category.id, not_(Category.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n values: Mapped[list[Info]] = relationship(\r\n \"Info\",\r\n foreign_keys=\"Info.param_id\",\r\n back_populates=\"param\",\r\n primaryjoin=\"and_(Param.id==Info.param_id, not_(Info.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n @property\r\n def pytype(self) -> type[str | list[str]]:\r\n return list[str] if self.type == ViewType.ALL else str\r\n\r\n\r\nclass Source(BaseDbModel):\r\n \"\"\"\r\n Источник данных - субъект изменения польщовательских данных - тот, кто меняет данные\r\n В HTTP методах доступно только два источника - user/admin\r\n Субъект может менять только данные, созданные собой же.\r\n У источника есть уровень доверия, который влияет на вид ответа `GET /user/{user_id}`\r\n \"\"\"\r\n\r\n name: Mapped[str] = mapped_column(String, unique=True)\r\n trust_level: Mapped[int] = mapped_column(Integer, default=0, nullable=False)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n values: Mapped[Info] = relationship(\r\n \"Info\",\r\n foreign_keys=\"Info.source_id\",\r\n back_populates=\"source\",\r\n primaryjoin=\"and_(Source.id==Info.source_id, not_(Info.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n\r\nclass Info(BaseDbModel):\r\n \"\"\"\r\n Значения параметров для конкретных польщзователей\r\n Если, например, телефон - параметр, то здесь указывается его значение для\r\n польщзователя(owner_id) - объекта изменения пользовательских данных\r\n \"\"\"\r\n\r\n param_id: Mapped[int] = mapped_column(Integer, ForeignKey(Param.id))\r\n source_id: Mapped[int] = mapped_column(Integer, ForeignKey(Source.id))\r\n owner_id: Mapped[int] = mapped_column(Integer, nullable=False)\r\n value: Mapped[str] = mapped_column(String, nullable=False)\r\n create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)\r\n modify_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)\r\n is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)\r\n\r\n param: Mapped[Param] = relationship(\r\n \"Param\",\r\n foreign_keys=\"Info.param_id\",\r\n back_populates=\"values\",\r\n primaryjoin=\"and_(Info.param_id==Param.id, not_(Param.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n source: Mapped[Source] = relationship(\r\n \"Source\",\r\n foreign_keys=\"Info.source_id\",\r\n back_populates=\"values\",\r\n primaryjoin=\"and_(Info.source_id==Source.id, not_(Source.is_deleted))\",\r\n lazy=\"joined\",\r\n )\r\n\r\n @hybrid_property\r\n def category(self) -> Category:\r\n return self.param.category\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/userdata_api/models/db.py b/userdata_api/models/db.py +--- a/userdata_api/models/db.py ++++ b/userdata_api/models/db.py +@@ -57,7 +57,7 @@ + а параметры эти могут лежать в категории "контакты" + """ + +- is_hidden: Mapped[bool] = mapped_column(Boolean, default=True) ++ visible_in_user_response: Mapped[bool] = mapped_column(Boolean, default=True) + name: Mapped[str] = mapped_column(String) + category_id: Mapped[int] = mapped_column(Integer, ForeignKey(Category.id)) + is_required: Mapped[bool] = mapped_column(Boolean, default=False) +Index: userdata_api/routes/param.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from typing import Any\r\n\r\nfrom auth_lib.fastapi import UnionAuth\r\nfrom fastapi import APIRouter, Depends, Request\r\nfrom fastapi_sqlalchemy import db\r\nfrom pydantic.type_adapter import TypeAdapter\r\n\r\nfrom userdata_api.exceptions import AlreadyExists, ObjectNotFound\r\nfrom userdata_api.models.db import Category, Param\r\nfrom userdata_api.schemas.param import ParamGet, ParamPatch, ParamPost\r\nfrom userdata_api.schemas.response_model import StatusResponseModel\r\n\r\n\r\nparam = APIRouter(prefix=\"/category/{category_id}/param\", tags=[\"Param\"])\r\n\r\n\r\n@param.post(\"\", response_model=ParamGet)\r\nasync def create_param(\r\n request: Request,\r\n category_id: int,\r\n param_inp: ParamPost,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.create\"], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Создать поле внутри категории. Ответ на пользовательские данные будет такой {..., category: {...,param: '', ...}}\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param category_id: Айди котегории в которой создавать параметр\r\n :param param_inp: Модель для создания\r\n :param _: Аутентификация\r\n :return: ParamGet - созданный параметр\r\n \"\"\"\r\n Category.get(category_id, session=db.session)\r\n if Param.query(session=db.session).filter(Param.category_id == category_id, Param.name == param_inp.name).all():\r\n raise AlreadyExists(Param, param_inp.name)\r\n return ParamGet.model_validate(Param.create(session=db.session, **param_inp.dict(), category_id=category_id))\r\n\r\n\r\n@param.get(\"/{id}\", response_model=ParamGet)\r\nasync def get_param(\r\n id: int,\r\n category_id: int,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Получить параметр по айди\r\n \\f\r\n :param id: Айди параметра\r\n :param category_id: айди категории в которой этот параметр находится\r\n :return: ParamGet - полученный параметр\r\n :param _: Аутентификация\r\n \"\"\"\r\n\r\n res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none()\r\n if not res:\r\n raise ObjectNotFound(Param, id)\r\n if res.is_hidden:\r\n category_scopes = set(\r\n Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope\r\n )\r\n user_scopes = set([scope[\"name\"].lower() for scope in _[\"session_scopes\"]])\r\n if category_scopes - user_scopes:\r\n raise ObjectNotFound(Param, id)\r\n return ParamGet.model_validate(res)\r\n return ParamGet.model_validate(res)\r\n\r\n\r\n@param.get(\"\", response_model=list[ParamGet])\r\nasync def get_params(category_id: int) -> list[ParamGet]:\r\n \"\"\"\r\n Получить все параметры категории\r\n \\f\r\n :param category_id: Айди категории\r\n :return: list[ParamGet] - список полученных параметров\r\n \"\"\"\r\n type_adapter = TypeAdapter(list[ParamGet])\r\n return type_adapter.validate_python(Param.query(session=db.session).filter(Param.category_id == category_id).all())\r\n\r\n\r\n@param.patch(\"/{id}\", response_model=ParamGet)\r\nasync def patch_param(\r\n request: Request,\r\n id: int,\r\n category_id: int,\r\n param_inp: ParamPatch,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.update\"], allow_none=False, auto_error=True)),\r\n) -> ParamGet:\r\n \"\"\"\r\n Обновить параметр внутри категории\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param id: Айди обновляемого параметра\r\n :param category_id: Адйи категории в которой находится параметр\r\n :param param_inp: Модель для создания параметра\r\n :param _: Аутентификация\r\n :return: ParamGet- Обновленный параметр\r\n \"\"\"\r\n if category_id:\r\n Category.get(category_id, session=db.session)\r\n if category_id:\r\n return ParamGet.from_orm(\r\n Param.update(id, session=db.session, **param_inp.dict(exclude_unset=True), category_id=category_id)\r\n )\r\n return ParamGet.model_validate(Param.update(id, session=db.session, **param_inp.dict(exclude_unset=True)))\r\n\r\n\r\n@param.delete(\"/{id}\", response_model=StatusResponseModel)\r\nasync def delete_param(\r\n request: Request,\r\n id: int,\r\n category_id: int,\r\n _: dict[str, Any] = Depends(UnionAuth(scopes=[\"userdata.param.delete\"], allow_none=False, auto_error=True)),\r\n) -> StatusResponseModel:\r\n \"\"\"\r\n Удалить параметр внутри категории\r\n \\f\r\n :param request: https://fastapi.tiangolo.com/advanced/using-request-directly/\r\n :param id: Айди удаляемого параметра\r\n :param category_id: Айди категории в которой находится удлаляемый параметр\r\n :param _: Аутентификация\r\n :return: None\r\n \"\"\"\r\n res: Param = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none()\r\n if not res:\r\n raise ObjectNotFound(Param, id)\r\n res.is_deleted = True\r\n db.session.commit()\r\n return StatusResponseModel(status=\"Success\", message=\"Param deleted\")\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/userdata_api/routes/param.py b/userdata_api/routes/param.py +--- a/userdata_api/routes/param.py ++++ b/userdata_api/routes/param.py +@@ -37,34 +37,21 @@ + + + @param.get("/{id}", response_model=ParamGet) +-async def get_param( +- id: int, +- category_id: int, +- _: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), +-) -> ParamGet: ++async def get_param(id: int, category_id: int) -> ParamGet: + """ + Получить параметр по айди + \f + :param id: Айди параметра +- :param category_id: айди категории в которой этот параметр находится ++ :param category_id: айди категории в которой этот параметр находиится + :return: ParamGet - полученный параметр +- :param _: Аутентификация + """ +- + res = Param.query(session=db.session).filter(Param.id == id, Param.category_id == category_id).one_or_none() + if not res: + raise ObjectNotFound(Param, id) +- if res.is_hidden: +- category_scopes = set( +- Category.query(session=db.session).filter(Category.id == category_id).one_or_none().read_scope +- ) +- user_scopes = set([scope["name"].lower() for scope in _["session_scopes"]]) +- if category_scopes - user_scopes: +- raise ObjectNotFound(Param, id) +- return ParamGet.model_validate(res) + return ParamGet.model_validate(res) + + ++ + @param.get("", response_model=list[ParamGet]) + async def get_params(category_id: int) -> list[ParamGet]: + """ +Index: userdata_api/routes/user.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from typing import Any\r\n\r\nfrom auth_lib.fastapi import UnionAuth\r\nfrom fastapi import APIRouter, Depends\r\n\r\nfrom userdata_api.schemas.response_model import StatusResponseModel\r\nfrom userdata_api.schemas.user import UserInfoGet, UserInfoUpdate\r\nfrom userdata_api.utils.user import get_user_info as get\r\nfrom userdata_api.utils.user import patch_user_info as patch\r\n\r\n\r\nuser = APIRouter(prefix=\"/user\", tags=[\"User\"])\r\n\r\n\r\n@user.get(\"/{id}\", response_model=UserInfoGet)\r\nasync def get_user_info(\r\n id: int, user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True))\r\n) -> UserInfoGet:\r\n \"\"\"\r\n Получить информацию о пользователе\r\n \\f\r\n :param id: Айди овнера информации(пользователя)\r\n :param user: Аутентфикация\r\n :return: Словарь, ключи - категории на которые хватило прав(овнеру не нужны права, он получает всё).\r\n Значения - словари, ключи которых - имена параметров,\r\n внутри соответствующих категорий, значния - значения этих параметров у конкретного пользователя\r\n Например:\r\n {student: {card: 123, group: 342},\r\n profcomff: {card: 1231231, is_member: True},\r\n ...\r\n }\r\n \"\"\"\r\n return UserInfoGet.model_validate(await get(id, user))\r\n\r\n\r\n@user.post(\"/{id}\", response_model=StatusResponseModel)\r\nasync def update_user(\r\n new_info: UserInfoUpdate,\r\n id: int,\r\n user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)),\r\n) -> StatusResponseModel:\r\n \"\"\"\r\n Обновить информацию о пользователе.\r\n Объект - пользователь, информацию которого обновляют\r\n Субъект - пользователь, который обновляет - источник\r\n\r\n Если не указать параметр внутри категории, то ничего не обновится, если указать что-то,\r\n то либо создастся новая запись(в случае, если она отсутствовала у данного источника), либо отредактируется\r\n старая. Если в значении параметра указан None, то соответствующая информациия удаляется из данного источника\r\n\r\n Обновлять через эту ручку можно только от имени источников admin и user.\r\n\r\n Чтобы обновить от имени админиа, надо иметь скоуп `userdata.info.admin`\r\n Чтобы обновить неизменяемую информацию надо обладать скоупом `userdata.info.update`\r\n Для обновления своей информации(источник `user`) не нужны скоупы на обновление соответствующих категорий\r\n Для обновления чужой информации от имени админа(источник `admin`)\r\n нужны скоупы на обновление всех указанных в теле запроса категорий пользовательских данных данных\r\n \\f\r\n :param request: Запрос из fastapi\r\n :param user_id: Айди объекта обновленя\r\n :param _: Модель запроса\r\n :param user:\r\n :return:\r\n \"\"\"\r\n await patch(new_info, id, user)\r\n return StatusResponseModel(status='Success', message='User patch succeeded')\r\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py +--- a/userdata_api/routes/user.py ++++ b/userdata_api/routes/user.py +@@ -1,4 +1,4 @@ +-from typing import Any ++from typing import Any, List + + from auth_lib.fastapi import UnionAuth + from fastapi import APIRouter, Depends +@@ -30,7 +30,9 @@ + ... + } + """ +- return UserInfoGet.model_validate(await get(id, user)) ++ if additional_data is None: ++ additional_data = [] ++ return UserInfoGet.model_validate(await get(id, user)) + + + @user.post("/{id}", response_model=StatusResponseModel) diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]1/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]1/shelved.patch new file mode 100644 index 0000000..e69de29 diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml new file mode 100644 index 0000000..df07dea --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52_[Changes]/shelved.patch new file mode 100644 index 0000000..e69de29 diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml new file mode 100644 index 0000000..33cfd76 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/userdata-api.iml b/.idea/userdata-api.iml new file mode 100644 index 0000000..aad402c --- /dev/null +++ b/.idea/userdata-api.iml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..6ac0a42 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "lastFilter": { + "state": "OPEN", + "assignee": "gitfresnel" + } +} + + + + + + { + "associatedIndex": 2 +} + + + + { + "keyToString": { + "Python tests.pytest for test_category.test_create_with_scopes.executor": "Run", + "Python tests.pytest for test_user_get.test_get.executor": "Run", + "Python tests.pytest for test_user_post_then_get.test_create_new.executor": "Run", + "Python tests.pytest for test_user_post_then_get.test_update_from_user_source.executor": "Run", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "git-widget-placeholder": "main", + "last_opened_file_path": "C:/Users/Huawei/Desktop/стажа/userdata-api", + "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PythonContentEntriesConfigurable" + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1707596139843 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b29ffd4846bd9207efc5173ffab930145004e16a Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Thu, 18 Jul 2024 22:42:08 +0300 Subject: [PATCH 11/26] f --- .../ea9459426db1_add_visible_in_user_response_field_to_.py | 1 + migrations/versions/f8c57101c0f6_init.py | 1 + 2 files changed, 2 insertions(+) diff --git a/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py b/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py index f411bb1..c4201ff 100644 --- a/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py +++ b/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py @@ -5,6 +5,7 @@ Create Date: 2024-05-12 20:27:45.549332 """ + import sqlalchemy as sa from alembic import op diff --git a/migrations/versions/f8c57101c0f6_init.py b/migrations/versions/f8c57101c0f6_init.py index a64888f..59a0ef0 100644 --- a/migrations/versions/f8c57101c0f6_init.py +++ b/migrations/versions/f8c57101c0f6_init.py @@ -5,6 +5,7 @@ Create Date: 2023-05-09 12:48:25.550608 """ + import sqlalchemy as sa from alembic import op From 18210482092e7fab6d723721138dfcf5fe794c41 Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:36:38 +0300 Subject: [PATCH 12/26] f --- userdata_api/utils/user.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index d8be521..8c221dd 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -115,9 +115,7 @@ async def get_user_info( not_(Category.is_deleted), or_( Param.visible_in_user_response, - func.concat('category', cast(Category.id, String), '.field', cast(Param.id, String)).in_( - additional_data - ), + func.concat('Param.', cast(Param.id, String)).in_(additional_data), ), ) .all() From 53a7ec959fe6d3dff5bfa2d7f41f4eb0da183e5a Mon Sep 17 00:00:00 2001 From: gitfresnel <151745312+gitfresnel@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:06:58 +0300 Subject: [PATCH 13/26] f --- .idea/userdata-api.iml | 9 +-- userdata_api/routes/user.py | 19 ++++- userdata_api/schemas/user.py | 8 +++ userdata_api/utils/user.py | 135 +++++++++++++++++++++++++---------- 4 files changed, 127 insertions(+), 44 deletions(-) diff --git a/.idea/userdata-api.iml b/.idea/userdata-api.iml index dd0594a..55d3632 100644 --- a/.idea/userdata-api.iml +++ b/.idea/userdata-api.iml @@ -5,11 +5,8 @@ - - - -