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
+
+
+ 1707596139843
+
+
+
+ 1712090853119
+
+
+
+ 1712090853119
+
+
+
+ 1712092152686
+
+
+
+ 1712092152686
+
+
+
+ 1712122921605
+
+
+
+ 1712122921605
+
+
+
+ 1712123704665
+
+
+
+ 1712123704665
+
+
+
+ 1715544178848
+
+
+
+ 1715544178848
+
+
+
+ 1715544568606
+
+
+
+ 1715544568606
+
+
+
+ 1721315516322
+
+
+
+ 1721315516322
+
+
+
+ 1721318851341
+
+
+
+ 1721318851341
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py
index 82e2a5f..16f5faa 100644
--- a/userdata_api/routes/user.py
+++ b/userdata_api/routes/user.py
@@ -2,10 +2,12 @@
from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, Query
+from fastapi_sqlalchemy import db
from userdata_api.schemas.response_model import StatusResponseModel
-from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate
+from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet
from userdata_api.utils.user import get_user_info as get
+from userdata_api.utils.user import get_users_info_batch as get_users
from userdata_api.utils.user import patch_user_info as patch
@@ -68,3 +70,18 @@ async def update_user(
"""
await patch(new_info, id, user)
return StatusResponseModel(status='Success', message='User patch succeeded')
+
+
+@user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True)
+async def get_users_info(
+ users: list[int] = Query(),
+ categories: list[int] = Query(),
+ user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)),
+) -> UsersInfoGet:
+ """
+ Получить информацию о пользователях.
+ :param users: список id юзеров, про которых нужно вернуть информацию
+ :param categories: список id категорий, параметры которых нужно вернуть
+ :return: список данных о пользователях и данных категориях
+ """
+ return UsersInfoGet.model_validate(await get_users(users, categories, user))
diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py
index 0b16cc3..b48d27f 100644
--- a/userdata_api/schemas/user.py
+++ b/userdata_api/schemas/user.py
@@ -9,6 +9,10 @@ class UserInfo(Base):
value: str | None = None
+class ExtendedUserInfo(UserInfo):
+ user_id: int
+
+
class UserInfoGet(Base):
items: list[UserInfo]
@@ -24,5 +28,9 @@ def unique_validator(cls, v):
return v
+class UsersInfoGet(Base):
+ items: list[ExtendedUserInfo]
+
+
class UserInfoUpdate(UserInfoGet):
source: constr(min_length=1)
diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py
index 8c221dd..d2582ee 100644
--- a/userdata_api/utils/user.py
+++ b/userdata_api/utils/user.py
@@ -5,7 +5,7 @@
from userdata_api.exceptions import Forbidden, ObjectNotFound
from userdata_api.models.db import Category, Info, Param, Source, ViewType
-from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate
+from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet
async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> None:
@@ -90,57 +90,66 @@ 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]]], additional_data: list[str]
-) -> UserInfoGet:
- """
- Возвращает информауию о пользователе в соотетствии с переданным токеном.
+async def get_users_info(
+ user_ids: list[int],
+ category_ids: list[int] | None,
+ user: dict[str, int | list[dict[str, str | int]]],
+ additional_data: list[str],
+) -> list[dict[str, str | None]]:
+ """.
+ Возвращает информацию о данных пользователей в указанных категориях
- Пользователь может прочитать любую информацию о себе
-
- Токен с доступом к read_scope категории может получить доступ к данным категории у любых пользователей
-
- :param user_id: Айди пользователя
+ :param user_ids: Список айди юзеров
+ :param category_ids: Список айди необходимых категорий, если None, то мы запрашиваем информацию только обо одном пользователе user_ids[0] обо всех досутпных категориях
:param user: Сессия выполняющего запрос данных
- :additional_data: Список невидимых по дефолту параметров
- :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя
+ :return: Список словарей содержащих id пользователя, категорию, параметр категории и значение этого параметра у пользователя
"""
- infos: list[Info] = (
+ is_single_user = category_ids is None
+ scope_names = [scope["name"] for scope in user["session_scopes"]]
+ param_dict: dict[Param, dict[int, list[Info] | Info | None] | None] = {}
+ query: list[Info] = (
Info.query(session=db.session)
.join(Param)
.join(Category)
.filter(
- Info.owner_id == user_id,
+ Info.owner_id.in_(user_ids),
not_(Param.is_deleted),
not_(Category.is_deleted),
+ not_(Info.is_deleted),
or_(
Param.visible_in_user_response,
func.concat('Param.', cast(Param.id, String)).in_(additional_data),
),
)
- .all()
)
+ if not is_single_user:
+ query = query.filter(Param.category_id.in_(category_ids))
+ infos = query.all()
if not infos:
- raise ObjectNotFound(Info, user_id)
- scope_names = [scope["name"] for scope in user["session_scopes"]]
- param_dict: dict[Param, list[Info] | Info | None] = {}
+ raise ObjectNotFound(Info, user_ids)
+ result = []
for info in infos:
- ## Проверка доступов - нужен либо скоуп на категориию либо нужно быть овнером информации
- if info.category.read_scope and info.category.read_scope not in scope_names and user["id"] != user_id:
+ if (
+ info.category.read_scope
+ and info.category.read_scope not in scope_names
+ and (not is_single_user or info.owner_id != user["id"])
+ ):
continue
- if info.param not in param_dict.keys():
- param_dict[info.param] = [] if info.param.pytype == list[str] else None
+ if info.param not in param_dict:
+ param_dict[info.param] = {}
+ if info.owner_id not in param_dict[info.param]:
+ param_dict[info.param][info.owner_id] = [] if info.param.type == ViewType.ALL else None
if info.param.type == ViewType.ALL:
- param_dict[info.param].append(info)
- elif (param_dict[info.param] is None) or (
- (info.param.type == ViewType.LAST and info.create_ts > param_dict[info.param].create_ts)
+ param_dict[info.param][info.owner_id].append(info)
+ elif param_dict[info.param][info.owner_id] is None or (
+ (info.param.type == ViewType.LAST and info.create_ts > param_dict[info.param][info.owner_id].create_ts)
or (
info.param.type == ViewType.MOST_TRUSTED
and (
- param_dict[info.param].source.trust_level < info.source.trust_level
+ param_dict[info.param][info.owner_id].source.trust_level < info.source.trust_level
or (
- param_dict[info.param].source.trust_level <= info.source.trust_level
- and info.create_ts > param_dict[info.param].create_ts
+ param_dict[info.param][info.owner_id].source.trust_level <= info.source.trust_level
+ and info.create_ts > param_dict[info.param][info.owner_id].create_ts
)
)
)
@@ -155,13 +164,65 @@ async def get_user_info(
Если у параметра отображение по времени то более релевантная - более позднаяя
"""
- param_dict[info.param] = info
+ param_dict[info.param][info.owner_id] = info
result = []
- for item in param_dict.values():
- if isinstance(item, list):
- result.extend(
- [{"category": _item.category.name, "param": _item.param.name, "value": _item.value} for _item in item]
- )
- else:
- result.append({"category": item.category.name, "param": item.param.name, "value": item.value})
+ for param, user_dict in param_dict.items():
+ for owner_id, item in user_dict.items():
+ if isinstance(item, list):
+ result.extend(
+ [
+ {
+ "user_id": owner_id,
+ "category": _item.category.name,
+ "param": param.name,
+ "value": _item.value,
+ }
+ for _item in item
+ ]
+ )
+ else:
+ result.append(
+ {
+ "user_id": owner_id,
+ "category": item.category.name,
+ "param": param.name,
+ "value": item.value,
+ }
+ )
+ return result
+
+
+async def get_users_info_batch(
+ user_ids: list[int],
+ category_ids: list[int],
+ user: dict[str, int | list[dict[str, str | int]]],
+ additional_data: list[str],
+) -> UsersInfoGet:
+ """.
+ Возвращает информацию о данных пользователей в указанных категориях
+
+ :param user_ids: Список айди юзеров
+ :param category_ids: Список айди необходимых категорий
+ :param user: Сессия выполняющего запрос данных
+ :return: Список словарей содержащих id пользователя, категорию, параметр категории и значение этого параметра у пользователя
+ """
+ return UsersInfoGet(items=await get_users_info(user_ids, category_ids, user, additional_data))
+
+
+async def get_user_info(
+ user_id: int, user: dict[str, int | list[dict[str, str | int]]], additional_data: list[str]
+) -> UserInfoGet:
+ """Возвращает информауию о пользователе в соотетствии с переданным токеном.
+
+ Пользователь может прочитать любую информацию о себе
+
+ Токен с доступом к read_scope категории может получить доступ к данным категории у любых пользователей
+
+ :param user_id: Айди пользователя
+ :param user: Сессия выполняющего запрос данных
+ :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у пользователя
+ """
+ result = await get_users_info([user_id], None, user, additional_data)
+ for value in result:
+ del value["user_id"]
return UserInfoGet(items=result)
From 6af51d3703f12997e60eb1cddeb242792d44918f Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Thu, 22 Aug 2024 18:53:24 +0300
Subject: [PATCH 14/26] f
---
tests/test_routes/test_users_get.py | 156 ++++++++++++++++++++++++++++
userdata_api/routes/user.py | 3 +-
2 files changed, 158 insertions(+), 1 deletion(-)
create mode 100644 tests/test_routes/test_users_get.py
diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py
new file mode 100644
index 0000000..65d1753
--- /dev/null
+++ b/tests/test_routes/test_users_get.py
@@ -0,0 +1,156 @@
+from time import sleep
+
+import pytest
+
+from userdata_api.models.db import Info, Param
+from userdata_api.utils.utils import random_string
+
+
+@pytest.mark.authenticated("userdata.info.admin")
+def test_get(client, dbsession, category_no_scopes, source):
+ source = source()
+ category1 = category_no_scopes()
+ category2 = category_no_scopes()
+ category3 = category_no_scopes()
+ param1 = Param(
+ name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True
+ )
+ param2 = Param(
+ name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True
+ )
+ param3 = Param(
+ name=f"test{random_string()}", category_id=category2.id, type="last", changeable=True, is_required=True
+ )
+ param4 = Param(
+ name=f"test{random_string()}", category_id=category3.id, type="last", changeable=True, is_required=True
+ )
+ dbsession.add_all([param1, param2, param3, param4])
+ dbsession.flush()
+ info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=0)
+ info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param2.id, owner_id=1)
+ info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param3.id, owner_id=0)
+ info4 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param4.id, owner_id=1)
+ dbsession.add_all([info1, info2, info3, info4])
+ dbsession.commit()
+ response = client.get(f"/user", params={"users": [0, 1], "categories": [category1.id, category2.id, category3.id]})
+ assert response.status_code == 200
+ assert {"user_id": 1, "category": category1.name, "param": info2.param.name, "value": info2.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 1, "category": category3.name, "param": info4.param.name, "value": info4.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 0, "category": category1.name, "param": info1.param.name, "value": info1.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 0, "category": category2.name, "param": info3.param.name, "value": info3.value} in list(
+ response.json()["items"]
+ )
+ dbsession.delete(info1)
+ dbsession.delete(info2)
+ dbsession.delete(info3)
+ dbsession.delete(info4)
+ dbsession.flush()
+ dbsession.delete(param1)
+ dbsession.delete(param2)
+ dbsession.delete(param3)
+ dbsession.delete(param4)
+ dbsession.flush()
+ dbsession.delete(category1)
+ dbsession.delete(category2)
+ dbsession.delete(category3)
+ dbsession.commit()
+
+
+@pytest.mark.authenticated("userdata.info.admin")
+def test_get_some_users(client, dbsession, category_no_scopes, source):
+ source = source()
+ category1 = category_no_scopes()
+ param1 = Param(
+ name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True
+ )
+ dbsession.add_all([param1])
+ dbsession.flush()
+ info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=1)
+ info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=2)
+ info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=3)
+ dbsession.add_all([info1, info2, info3])
+ dbsession.commit()
+ response = client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]})
+ assert response.status_code == 200
+ assert {"user_id": 1, "category": category1.name, "param": param1.name, "value": info1.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 2, "category": category1.name, "param": param1.name, "value": info2.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 3, "category": category1.name, "param": param1.name, "value": info3.value} not in list(
+ response.json()["items"]
+ )
+ response = client.get(f"/user", params={"users": [3], "categories": [category1.id]})
+ assert response.status_code == 200
+ assert len(response.json()["items"]) == 1
+ assert {"user_id": 3, "category": category1.name, "param": param1.name, "value": info3.value} in list(
+ response.json()["items"]
+ )
+ dbsession.delete(info1)
+ dbsession.delete(info2)
+ dbsession.delete(info3)
+ dbsession.flush()
+ dbsession.delete(param1)
+ dbsession.flush()
+ dbsession.delete(category1)
+ dbsession.commit()
+
+
+@pytest.mark.authenticated("userdata.info.admin")
+def test_get_some_categories(client, dbsession, category_no_scopes, source):
+ source = source()
+ category1 = category_no_scopes()
+ category2 = category_no_scopes()
+ category3 = category_no_scopes()
+ param1 = Param(
+ name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True
+ )
+ param2 = Param(
+ name=f"test{random_string()}", category_id=category2.id, type="last", changeable=True, is_required=True
+ )
+ param3 = Param(
+ name=f"test{random_string()}", category_id=category3.id, type="last", changeable=True, is_required=True
+ )
+ dbsession.add_all([param1, param2, param3])
+ dbsession.flush()
+ info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=1)
+ info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param2.id, owner_id=1)
+ info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param3.id, owner_id=1)
+ dbsession.add_all([info1, info2, info3])
+ dbsession.commit()
+ response = client.get(f"/user", params={"users": [1], "categories": [category1.id, category2.id]})
+ assert response.status_code == 200
+ assert {"user_id": 1, "category": category1.name, "param": info1.param.name, "value": info1.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 1, "category": category2.name, "param": info2.param.name, "value": info2.value} in list(
+ response.json()["items"]
+ )
+ assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} not in list(
+ response.json()["items"]
+ )
+
+ response = client.get(f"/user", params={"users": [1], "categories": [category3.id]})
+ assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} in list(
+ response.json()["items"]
+ )
+
+ dbsession.delete(info1)
+ dbsession.delete(info2)
+ dbsession.delete(info3)
+ dbsession.flush()
+ dbsession.delete(param1)
+ dbsession.delete(param2)
+ dbsession.delete(param3)
+ dbsession.flush()
+ dbsession.delete(category1)
+ dbsession.delete(category2)
+ dbsession.delete(category3)
+ dbsession.commit()
diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py
index 16f5faa..a17218e 100644
--- a/userdata_api/routes/user.py
+++ b/userdata_api/routes/user.py
@@ -77,6 +77,7 @@ async def get_users_info(
users: list[int] = Query(),
categories: list[int] = Query(),
user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)),
+ additional_data: list[str] = Query(default=[]),
) -> UsersInfoGet:
"""
Получить информацию о пользователях.
@@ -84,4 +85,4 @@ async def get_users_info(
:param categories: список id категорий, параметры которых нужно вернуть
:return: список данных о пользователях и данных категориях
"""
- return UsersInfoGet.model_validate(await get_users(users, categories, user))
+ return UsersInfoGet.model_validate(await get_users(users, categories, user, additional_data))
From 2200b979aa4d080361dab702df8bfef65c7f594a Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Thu, 22 Aug 2024 18:58:42 +0300
Subject: [PATCH 15/26] f
---
userdata_api/routes/user.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py
index 0c13991..b94bc1b 100644
--- a/userdata_api/routes/user.py
+++ b/userdata_api/routes/user.py
@@ -73,7 +73,6 @@ async def update_user(
return StatusResponseModel(status="Success", message="User patch succeeded", ru="Изменение успешно")
-
@user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True)
async def get_users_info(
users: list[int] = Query(),
From d847f04f66ee3a34948a36e73cdf115804b69792 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Sun, 1 Sep 2024 12:19:23 +0300
Subject: [PATCH 16/26] f
---
userdata_api/routes/user.py | 2 +-
userdata_api/utils/user.py | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py
index b94bc1b..a95c80b 100644
--- a/userdata_api/routes/user.py
+++ b/userdata_api/routes/user.py
@@ -78,7 +78,7 @@ async def get_users_info(
users: list[int] = Query(),
categories: list[int] = Query(),
user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)),
- additional_data: list[str] = Query(default=[]),
+ additional_data: list[int] = Query(default=[]),
) -> UsersInfoGet:
"""
Получить информацию о пользователях.
diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py
index 26dd6f5..998a017 100644
--- a/userdata_api/utils/user.py
+++ b/userdata_api/utils/user.py
@@ -110,7 +110,7 @@ async def get_users_info(
user_ids: list[int],
category_ids: list[int] | None,
user: dict[str, int | list[dict[str, str | int]]],
- additional_data: list[str],
+ additional_data: list[int],
) -> list[dict[str, str | None]]:
""".
Возвращает информацию о данных пользователей в указанных категориях
@@ -134,7 +134,7 @@ async def get_users_info(
not_(Info.is_deleted),
or_(
Param.visible_in_user_response,
- func.concat('Param.', cast(Param.id, String)).in_(additional_data),
+ Param.id.in_(additional_data),
),
)
)
@@ -212,7 +212,7 @@ async def get_users_info_batch(
user_ids: list[int],
category_ids: list[int],
user: dict[str, int | list[dict[str, str | int]]],
- additional_data: list[str],
+ additional_data: list[int],
) -> UsersInfoGet:
""".
Возвращает информацию о данных пользователей в указанных категориях
@@ -227,7 +227,7 @@ async def get_users_info_batch(
async def get_user_info(
- user_id: int, user: dict[str, int | list[dict[str, str | int]]], additional_data: list[str]
+ user_id: int, user: dict[str, int | list[dict[str, str | int]]], additional_data: list[int]
) -> UserInfoGet:
"""Возвращает информауию о пользователе в соотетствии с переданным токеном.
From 1ed868d776fad57b406a7591a153fa6db92875f5 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Mon, 2 Sep 2024 08:20:32 +0300
Subject: [PATCH 17/26] f
---
migrations/versions/665f07866b38_.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 migrations/versions/665f07866b38_.py
diff --git a/migrations/versions/665f07866b38_.py b/migrations/versions/665f07866b38_.py
new file mode 100644
index 0000000..54e66de
--- /dev/null
+++ b/migrations/versions/665f07866b38_.py
@@ -0,0 +1,24 @@
+"""empty message
+
+Revision ID: 665f07866b38
+Revises: ea9459426db1
+Create Date: 2024-09-02 08:20:08.846764
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '665f07866b38'
+down_revision = 'ea9459426db1'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ pass
+
+
+def downgrade():
+ pass
From 4f6fb5670a2cf76715877a2192b29eb28ab1d0d4 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Tue, 3 Sep 2024 10:55:52 +0300
Subject: [PATCH 18/26] f
---
.idea/userdata-api.iml | 4 +++
.idea/workspace.xml | 34 ++++++++++++-------
migrations/versions/665f07866b38_.py | 24 -------------
..._add_visible_in_user_response_field_to_.py | 29 ----------------
4 files changed, 25 insertions(+), 66 deletions(-)
delete mode 100644 migrations/versions/665f07866b38_.py
delete mode 100644 migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py
diff --git a/.idea/userdata-api.iml b/.idea/userdata-api.iml
index aad402c..30c0dd3 100644
--- a/.idea/userdata-api.iml
+++ b/.idea/userdata-api.iml
@@ -1,5 +1,9 @@
+
+
+
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 6ac0a42..23fbcab 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,15 @@
-
+
+
+
+
+
+
+
+
+
@@ -61,19 +69,19 @@
- {
- "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"
+
+}]]>
diff --git a/migrations/versions/665f07866b38_.py b/migrations/versions/665f07866b38_.py
deleted file mode 100644
index 54e66de..0000000
--- a/migrations/versions/665f07866b38_.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""empty message
-
-Revision ID: 665f07866b38
-Revises: ea9459426db1
-Create Date: 2024-09-02 08:20:08.846764
-
-"""
-from alembic import op
-import sqlalchemy as sa
-
-
-# revision identifiers, used by Alembic.
-revision = '665f07866b38'
-down_revision = 'ea9459426db1'
-branch_labels = None
-depends_on = None
-
-
-def upgrade():
- pass
-
-
-def downgrade():
- pass
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
deleted file mode 100644
index c4201ff..0000000
--- a/migrations/versions/ea9459426db1_add_visible_in_user_response_field_to_.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""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 9156cbc25dd1a361ab7c8371b15030dc3e7c7b9d Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Wed, 4 Sep 2024 14:18:37 +0300
Subject: [PATCH 19/26] f
---
.../shelved.patch | 0
...Checkout_at_18_07_2024_19_52__Changes_.xml | 4 --
.idea/workspace.xml | 44 ++++++++++---------
3 files changed, 24 insertions(+), 24 deletions(-)
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52_[Changes]/shelved.patch
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml
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
deleted file mode 100644
index e69de29..0000000
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
deleted file mode 100644
index 33cfd76..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_18_07_2024_19_52__Changes_.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 23fbcab..f23c0e6 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,13 +5,9 @@
-
-
-
-
+
+
-
-
@@ -69,19 +65,19 @@
- {
+ "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": "14-скрытие-части-юзердаты-от-пользователя",
+ "last_opened_file_path": "C:/Users/Huawei/Desktop/стажа/userdata-api",
+ "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PythonContentEntriesConfigurable"
}
-}]]>
+}
@@ -147,7 +143,7 @@
-
+
@@ -229,7 +225,15 @@
1721318851341
-
+
+
+ 1725350154762
+
+
+
+ 1725350154762
+
+
From 6882a2ff920ad565c12fa9945de03d96e471599b Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Wed, 4 Sep 2024 14:21:05 +0300
Subject: [PATCH 20/26] f
---
..._add_visible_in_user_response_field_to_.py | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
diff --git a/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py b/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
new file mode 100644
index 0000000..10897c5
--- /dev/null
+++ b/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
@@ -0,0 +1,29 @@
+"""Add visible_in_user_response field to Param table
+
+Revision ID: 5a6490c55c81
+Revises: 0932ab8ca14a
+Create Date: 2024-09-03 10:54:14.554987
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+
+# revision identifiers, used by Alembic.
+revision = '5a6490c55c81'
+down_revision = '0932ab8ca14a'
+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))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('param', 'visible_in_user_response')
+ # ### end Alembic commands ###
From c17ab8cfbfebaee5aa38bffe0851069d7e17cbe3 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Thu, 12 Sep 2024 08:02:56 +0300
Subject: [PATCH 21/26] Remove .idea directory from repository and add to
.gitignore
---
.idea/.gitignore | 3 -
.idea/inspectionProfiles/Project_Default.xml | 22 --
.../inspectionProfiles/profiles_settings.xml | 6 -
.idea/misc.xml | 4 -
.idea/modules.xml | 8 -
.../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 -
.idea/userdata-api.iml | 14 -
.idea/vcs.xml | 6 -
.idea/workspace.xml | 266 ------------------
14 files changed, 519 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/inspectionProfiles/Project_Default.xml
delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml
delete mode 100644 .idea/misc.xml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]1/shelved.patch
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]1/shelved.patch
delete mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml
delete mode 100644 .idea/userdata-api.iml
delete mode 100644 .idea/vcs.xml
delete mode 100644 .idea/workspace.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index e4d7a92..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 6ce1a79..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 0958c0c..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ 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
deleted file mode 100644
index 4b2d62c..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16_[Changes]/shelved.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-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
deleted file mode 100644
index e69de29..0000000
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
deleted file mode 100644
index a70855b..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_04_05_2024_15_16__Changes_.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ 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
deleted file mode 100644
index 7ce3d68..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51_[Changes]/shelved.patch
+++ /dev/null
@@ -1,94 +0,0 @@
-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
deleted file mode 100644
index e69de29..0000000
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
deleted file mode 100644
index df07dea..0000000
--- a/.idea/shelf/Uncommitted_changes_before_Checkout_at_07_05_2024_16_51__Changes_.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/userdata-api.iml b/.idea/userdata-api.iml
deleted file mode 100644
index 30c0dd3..0000000
--- a/.idea/userdata-api.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index f23c0e6..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "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": "14-скрытие-части-юзердаты-от-пользователя",
- "last_opened_file_path": "C:/Users/Huawei/Desktop/стажа/userdata-api",
- "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PythonContentEntriesConfigurable"
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1707596139843
-
-
- 1707596139843
-
-
-
- 1712090853119
-
-
-
- 1712090853119
-
-
-
- 1712092152686
-
-
-
- 1712092152686
-
-
-
- 1712122921605
-
-
-
- 1712122921605
-
-
-
- 1712123704665
-
-
-
- 1712123704665
-
-
-
- 1715544178848
-
-
-
- 1715544178848
-
-
-
- 1715544568606
-
-
-
- 1715544568606
-
-
-
- 1721315516322
-
-
-
- 1721315516322
-
-
-
- 1721318851341
-
-
-
- 1721318851341
-
-
-
- 1725350154762
-
-
-
- 1725350154762
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
From 9c6aa4c4e4832e80bf360632c79a956af311875b Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Thu, 12 Sep 2024 12:19:39 +0300
Subject: [PATCH 22/26] f
---
userdata_api/schemas/types/scope.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/userdata_api/schemas/types/scope.py b/userdata_api/schemas/types/scope.py
index e3216ff..fbf1741 100644
--- a/userdata_api/schemas/types/scope.py
+++ b/userdata_api/schemas/types/scope.py
@@ -1,5 +1,5 @@
import string
-from typing import Any
+from typing import Any, Callable
from pydantic._internal import _schema_generation_shared
from pydantic.json_schema import JsonSchemaValue
@@ -16,10 +16,7 @@ class Scope:
"""
@classmethod
- def __get_pydantic_core_schema__(
- cls,
- source: type[Any],
- ) -> core_schema.CoreSchema:
+ def __get_pydantic_core_schema__(cls, source: type[Any], handler: Callable) -> core_schema.CoreSchema:
return core_schema.general_after_validator_function(cls._validate, core_schema.str_schema())
@classmethod
From 1529804e3ae7f24efe60273189b4138b4c8069e5 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Sat, 21 Sep 2024 17:02:58 +0300
Subject: [PATCH 23/26] f
---
userdata_api/routes/user.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py
index a95c80b..e74b10d 100644
--- a/userdata_api/routes/user.py
+++ b/userdata_api/routes/user.py
@@ -18,7 +18,7 @@
@user.get("/{id}", response_model=UserInfoGet)
async def get_user_info(
id: int,
- additional_data: list[str] = Query(default=[]),
+ additional_data: list[int] = Query(default=[]),
user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)),
) -> UserInfoGet:
"""
From d9f2be46c19db76189e0cbc87c38044aa9ac512a Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Fri, 27 Sep 2024 17:47:46 +0300
Subject: [PATCH 24/26] f
---
.../5a6490c55c81_add_visible_in_user_response_field_to_.py | 4 +++-
userdata_api/schemas/types/scope.py | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py b/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
index 10897c5..17d8b45 100644
--- a/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
+++ b/migrations/versions/5a6490c55c81_add_visible_in_user_response_field_to_.py
@@ -19,7 +19,9 @@
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.add_column('param', sa.Column('visible_in_user_response', sa.Boolean(), nullable=False))
+ op.add_column('param', sa.Column('visible_in_user_response', sa.Boolean(), nullable=True))
+ op.execute(f'UPDATE "param" SET visible_in_user_response = True')
+ op.alter_column('param', 'visible_in_user_response', nullable=False)
# ### end Alembic commands ###
diff --git a/userdata_api/schemas/types/scope.py b/userdata_api/schemas/types/scope.py
index fbf1741..ab31e8b 100644
--- a/userdata_api/schemas/types/scope.py
+++ b/userdata_api/schemas/types/scope.py
@@ -4,7 +4,7 @@
from pydantic._internal import _schema_generation_shared
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
-
+from pydantic import GetCoreSchemaHandler
class Scope:
"""
@@ -16,7 +16,7 @@ class Scope:
"""
@classmethod
- def __get_pydantic_core_schema__(cls, source: type[Any], handler: Callable) -> core_schema.CoreSchema:
+ def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.general_after_validator_function(cls._validate, core_schema.str_schema())
@classmethod
From 5c462daaed97c3efb2fdeee6b736653946e34187 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Fri, 27 Sep 2024 17:49:46 +0300
Subject: [PATCH 25/26] f
---
userdata_api/schemas/types/scope.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/userdata_api/schemas/types/scope.py b/userdata_api/schemas/types/scope.py
index ab31e8b..6fcb48c 100644
--- a/userdata_api/schemas/types/scope.py
+++ b/userdata_api/schemas/types/scope.py
@@ -1,10 +1,11 @@
import string
from typing import Any, Callable
+from pydantic import GetCoreSchemaHandler
from pydantic._internal import _schema_generation_shared
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
-from pydantic import GetCoreSchemaHandler
+
class Scope:
"""
From 1ae95102bff0d3fd079a1a11775a6152a9037f21 Mon Sep 17 00:00:00 2001
From: gitfresnel <151745312+gitfresnel@users.noreply.github.com>
Date: Sat, 28 Sep 2024 17:15:03 +0300
Subject: [PATCH 26/26] f
---
userdata_api/schemas/types/scope.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/userdata_api/schemas/types/scope.py b/userdata_api/schemas/types/scope.py
index 6fcb48c..4f50bde 100644
--- a/userdata_api/schemas/types/scope.py
+++ b/userdata_api/schemas/types/scope.py
@@ -1,8 +1,7 @@
import string
-from typing import Any, Callable
+from typing import Any
-from pydantic import GetCoreSchemaHandler
-from pydantic._internal import _schema_generation_shared
+from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
@@ -22,7 +21,7 @@ def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaH
@classmethod
def __get_pydantic_json_schema__(
- cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler
+ cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
field_schema = handler(core_schema)
field_schema.update(type='string', format='scope')