From 3bd43c21faee4cd465a0103d510a9d61baf79c45 Mon Sep 17 00:00:00 2001 From: Spotika Date: Wed, 25 Sep 2024 10:33:56 +0300 Subject: [PATCH 1/9] fix: fix nested ids and tests --- src/db/methods/organizations.py | 2 +- src/routers/test.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/db/methods/organizations.py b/src/db/methods/organizations.py index a4cfba5..3282a0d 100644 --- a/src/db/methods/organizations.py +++ b/src/db/methods/organizations.py @@ -26,7 +26,7 @@ def add_member(organization_id: int, member: types.Member, session: ClientSessio organizations.update_one( {"_id": organization_id}, - {"$push": {"members": member.db_dump()}}, + {"$push": {"members": member.model_dump()}}, session=session, ) diff --git a/src/routers/test.py b/src/routers/test.py index 90e0741..e86ce41 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -77,4 +77,10 @@ async def invite_user_to_organization( message="You are not a member of this organization", ) + if methods.organizations.is_user_in_organization(request.user_id, request.organization_id, session): + raise ErrorResponse( + code=ErrorCodes.USER_ALREADY_MEMBER, + message="User already in organization", + ) + methods.organizations.add_member(request.organization_id, types.Member(id=request.user_id), session) From e4d68bf29e94ab6cb5e732416975bed7e08c7d39 Mon Sep 17 00:00:00 2001 From: Spotika Date: Wed, 25 Sep 2024 22:08:16 +0300 Subject: [PATCH 2/9] refactor: change database arch --- src/config/config_model.py | 6 +- src/db/methods/__init__.py | 12 +--- src/db/methods/collections/__init__.py | 7 +- src/db/methods/collections/collections.py | 10 +-- src/db/methods/domains.py | 18 ----- src/db/methods/methods.py | 83 +++++++++++++++++++++++ src/db/methods/organizations.py | 48 ------------- src/db/methods/users.py | 33 --------- src/db/types/requests.py | 6 +- src/db/types/responses.py | 3 + src/db/types/types.py | 34 ++-------- src/routers/auth.py | 10 +-- src/routers/organizations.py | 11 ++- src/routers/test.py | 49 ++++++------- src/routers/users.py | 4 +- src/utils/auth/auth.py | 6 +- tests/src/helpers/api.ts | 11 +-- tests/src/helpers/requests.ts | 4 ++ tests/src/helpers/responses.ts | 3 + tests/src/helpers/types.ts | 15 +--- tests/src/organizations.test.ts | 32 ++++----- 21 files changed, 168 insertions(+), 237 deletions(-) delete mode 100644 src/db/methods/domains.py create mode 100644 src/db/methods/methods.py delete mode 100644 src/db/methods/organizations.py delete mode 100644 src/db/methods/users.py diff --git a/src/config/config_model.py b/src/config/config_model.py index d77fda4..20d668e 100644 --- a/src/config/config_model.py +++ b/src/config/config_model.py @@ -10,13 +10,9 @@ class MongoDBCollections(BaseModel): users: str = "Users" - domains: str = "Domains" - groups: str = "Groups" organizations: str = "Organizations" - group_roles: str = "GroupRoles" + organization_members: str = "OrganizationMembers" internal_counters: str = "InternalCounters" - group_members: str = "GroupMembers" - contests: str = "Contests" class DatabaseConfig(BaseModel): diff --git a/src/db/methods/__init__.py b/src/db/methods/__init__.py index 4d7740d..3c80016 100644 --- a/src/db/methods/__init__.py +++ b/src/db/methods/__init__.py @@ -1,12 +1,4 @@ -from . import ( - users, - domains, - organizations, -) +from . import methods -__all__ = [ - "users", - "domains", - "organizations", -] +__all__ = ["methods"] diff --git a/src/db/methods/collections/__init__.py b/src/db/methods/collections/__init__.py index 66f605c..3734808 100644 --- a/src/db/methods/collections/__init__.py +++ b/src/db/methods/collections/__init__.py @@ -1,12 +1,9 @@ -from .collections import users, domains, contests, group_roles, group_members, organizations, internal_counters +from .collections import users, organizations, internal_counters, organization_members __all__ = [ "users", - "domains", - "contests", - "group_roles", - "group_members", "organizations", "internal_counters", + "organization_members", ] diff --git a/src/db/methods/collections/collections.py b/src/db/methods/collections/collections.py index 81a76ac..2fbaa55 100644 --- a/src/db/methods/collections/collections.py +++ b/src/db/methods/collections/collections.py @@ -6,13 +6,9 @@ users = db.get_collection(config.database.collections.users) organizations = db.get_collection(config.database.collections.organizations) -groups = db.get_collection(config.database.collections.groups) -domains = db.get_collection(config.database.collections.domains) -contests = db.get_collection(config.database.collections.contests) -group_roles = db.get_collection(config.database.collections.group_roles) -group_members = db.get_collection(config.database.collections.group_members) +organization_members = db.get_collection(config.database.collections.organization_members) internal_counters = db.get_collection(config.database.collections.internal_counters) -users.create_index([("email", 1)], unique=True, name="email") -# domains.create_index([("target_id", 1)], unique=True, name="target_id") +users.create_index([("email", 1)], unique=True) +organization_members.create_index([("target_id", 1), ("object_id", 1)], unique=True) diff --git a/src/db/methods/domains.py b/src/db/methods/domains.py deleted file mode 100644 index 543ffd6..0000000 --- a/src/db/methods/domains.py +++ /dev/null @@ -1,18 +0,0 @@ -from pymongo.client_session import ClientSession - -from db.types import types -from .collections import domains - - -def attach_to_entity( - domain: str, target_id: int, target_type: types.EntityTargetType, session: ClientSession | None = None -) -> None: - domains.find_one_and_update( - {"_id": domain}, {"$set": {"target_id": target_id, "target_type": target_type}}, session=session, upsert=True - ) - - -def resolve_entity(domain: str, session: ClientSession | None = None) -> types.Entity | None: - if (entity := domains.find_one({"_id": domain}, session=session)) is None: - return None - return types.Entity(**entity) diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py new file mode 100644 index 0000000..ac80f3b --- /dev/null +++ b/src/db/methods/methods.py @@ -0,0 +1,83 @@ +from pymongo.client_session import ClientSession +from pymongo.errors import DuplicateKeyError + +from db.types import types +from db.methods.helpers import insert_with_auto_increment_id +from .collections import users, organizations, organization_members + + +# Organizations +def get_organization(organization_id: int, s: ClientSession | None = None) -> types.Organization | None: + if (organization := organizations.find_one({"_id": organization_id}, session=s)) is None: + return None + return types.Organization(**organization) + + +def check_organization_existence(organization_id: int, s: ClientSession | None = None) -> bool: + return organizations.count_documents({"_id": organization_id}, session=s) > 0 + + +def insert_organization(organization: types.OrganizationWithoutID, s: ClientSession | None = None) -> int: + return insert_with_auto_increment_id(organizations, organization.db_dump(), session=s) + + +def insert_member_to_organization(member: types.Member, s: ClientSession | None = None) -> bool: + try: + organization_members.insert_one(member.db_dump(), session=s) + except DuplicateKeyError: + return False + return True + + +def get_organizations_by_user(user_id: int, s: ClientSession | None = None) -> list[types.Organization]: + + pipeline = [ + {"$match": {"object_id": user_id}}, + { + "$lookup": { + "from": organizations.name, + "localField": "target_id", + "foreignField": "_id", + "as": "organizations", + } + }, + {"$unwind": "$organizations"}, + {"$replaceRoot": {"newRoot": "$organizations"}}, + ] + return [types.Organization(**org) for org in organization_members.aggregate(pipeline, session=s)] + + +def is_user_in_organization(user_id: int, organization_id: int, s: ClientSession | None = None) -> bool: + return organization_members.count_documents({"object_id": user_id, "target_id": organization_id}, session=s) > 0 + + +def get_members_of_organization(organization_id: int, s: ClientSession | None = None) -> list[int]: + pipeline = [ + {"$match": {"target_id": organization_id}}, + {"$group": {"_id": None, "result": {"$push": "$object_id"}}}, + ] + return next(organization_members.aggregate(pipeline, session=s)).get("result", None) + + +# Users +def get_user(user_id: int, s: ClientSession | None = None) -> types.User | None: + if (user := users.find_one({"_id": user_id}, session=s)) is None: + return None + return types.User(**user) + + +def get_user_by_email(email: str, s: ClientSession | None = None): + if (user := users.find_one({"email": email}, session=s)) is None: + return None + return types.User(**user) + + +def insert_user(user: types.UserWithoutID, s: ClientSession | None = None) -> int | None: + try: + return insert_with_auto_increment_id(users, user.db_dump(), session=s) + except DuplicateKeyError: + return None + + +def check_user_existence(user_id: int, s: ClientSession | None = None) -> bool: + return users.count_documents({"_id": user_id}, session=s) > 0 diff --git a/src/db/methods/organizations.py b/src/db/methods/organizations.py deleted file mode 100644 index 3282a0d..0000000 --- a/src/db/methods/organizations.py +++ /dev/null @@ -1,48 +0,0 @@ -from pymongo.client_session import ClientSession - -from db.types import types -from db.methods.helpers import insert_with_auto_increment_id -from utils.response import ErrorCodes, ErrorResponse -from .collections import organizations - - -def get(organization_id: int, session: ClientSession | None = None) -> types.Organization | None: - if (organization := organizations.find_one({"_id": organization_id}, session=session)) is None: - return None - return types.Organization(**organization) - - -def check_existence(organization_id: int, session: ClientSession | None = None) -> bool: - return organizations.count_documents({"_id": organization_id}, session=session) > 0 - - -def insert_organization(organization: types.OrganizationWithoutID, session: ClientSession | None = None) -> int: - return insert_with_auto_increment_id(organizations, organization.db_dump(), session=session) - - -def add_member(organization_id: int, member: types.Member, session: ClientSession | None = None) -> None: - if is_user_in_organization(member.id, organization_id): - raise ErrorResponse(code=ErrorCodes.USER_ALREADY_MEMBER) - - organizations.update_one( - {"_id": organization_id}, - {"$push": {"members": member.model_dump()}}, - session=session, - ) - - -def get_organizations_by_user(user_id: int, session: ClientSession | None = None) -> list[types.Organization]: - return [ - types.Organization(**org) - for org in organizations.aggregate([{"$match": {"members": {"$elemMatch": {"id": user_id}}}}], session=session) - ] - - -def is_user_in_organization(user_id: int, organization_id: int, session: ClientSession | None = None) -> bool: - return ( - organizations.count_documents( - {"_id": organization_id, "members": {"$elemMatch": {"id": user_id}}}, - session=session, - ) - > 0 - ) diff --git a/src/db/methods/users.py b/src/db/methods/users.py deleted file mode 100644 index 4073199..0000000 --- a/src/db/methods/users.py +++ /dev/null @@ -1,33 +0,0 @@ -from pymongo.errors import DuplicateKeyError -from pymongo.client_session import ClientSession - -from db.types import types -from .collections import users -from .helpers import insert_with_auto_increment_id - - -def get(user_id: int, session: ClientSession | None = None) -> types.User | None: - if (user := users.find_one({"_id": user_id}, session=session)) is None: - return None - return types.User(**user) - - -def get_by_email(email: str, session: ClientSession | None = None): - if (user := users.find_one({"email": email}, session=session)) is None: - return None - return types.User(**user) - - -def insert_user(user: types.UserWithoutID, session: ClientSession | None = None) -> int | None: - """ - Returns: - Inserted user id or None, if error occurred - """ - try: - return insert_with_auto_increment_id(users, user.db_dump(), session=session) - except DuplicateKeyError: - return None - - -def check_existence(user_id: int, session: ClientSession | None = None) -> bool: - return users.count_documents({"_id": user_id}, session=session) > 0 diff --git a/src/db/types/requests.py b/src/db/types/requests.py index 48effc2..5e1d331 100644 --- a/src/db/types/requests.py +++ b/src/db/types/requests.py @@ -11,20 +11,22 @@ class RQ: class auth: class signin(BaseModel): id: int | None = None - domain: str | None = None email: str | None = None password: str @model_validator(mode="after") def check_only_one_field(self): error_message = "You must provide exactly one of the fields: email, domain, user_id" - assert sum(x is not None for x in (self.id, self.domain, self.email)) == 1, error_message + assert sum(x is not None for x in (self.id, self.email)) == 1, error_message return self class organizations: class get(BaseModel): id: int + class get_members(BaseModel): + id: int + class users: class get_organizations(BaseModel): id: int diff --git a/src/db/types/responses.py b/src/db/types/responses.py index e327eda..e957b16 100644 --- a/src/db/types/responses.py +++ b/src/db/types/responses.py @@ -27,6 +27,9 @@ class get_organizations(BaseModel): class organizations: class get(types.Organization): ... + class get_members(BaseModel): + members: list[int] + class test: class signup(BaseModel): access_token: str diff --git a/src/db/types/types.py b/src/db/types/types.py index bacc2fe..c29d7f7 100644 --- a/src/db/types/types.py +++ b/src/db/types/types.py @@ -1,23 +1,10 @@ -from typing import Literal - from utils.schemas import BaseModel +# Misc class Member(BaseModel): - id: int - roles: list[str] = [] - custom_permissions: int = 0 - - -class Role(BaseModel): - id: str - name: str - permissions: int - - -class _HasMembersWithRoles(BaseModel): - members: list[Member] - roles: list[Role] = [] + object_id: int + target_id: int class JWTPair(BaseModel): @@ -25,20 +12,10 @@ class JWTPair(BaseModel): refresh_token: str -EntityTargetType = Literal["user", "group", "contest"] - - -class Entity(BaseModel): - id: str - target_type: EntityTargetType - target_id: int - - +# Users class _UserBase(BaseModel): - domain: str | None = None first_name: str last_name: str | None = None - groups: list[int] = [] hashed_password: str email: str @@ -50,7 +27,8 @@ class User(_UserBase): id: int -class _OrganizationBase(_HasMembersWithRoles): +# Organizations +class _OrganizationBase(BaseModel): name: str diff --git a/src/routers/auth.py b/src/routers/auth.py index 486c85b..26921b5 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -4,7 +4,7 @@ import utils.auth from utils.auth import get_current_user_by_refresh_token from utils.response import SuccessfulResponse, ErrorCodes, ErrorResponse -from db import methods +from db.methods import methods from db.types import types, RS, RQ @@ -15,13 +15,9 @@ async def signin(request: RQ.auth.signin): user: types.User | None = None if request.id: - user = methods.users.get(request.id) - elif request.domain: - entity = methods.domains.resolve_entity(request.domain) - if entity and entity.target_type == "user": - user = methods.users.get(entity.target_id) + user = methods.get_user(request.id) elif request.email: - user = methods.users.get_by_email(request.email) + user = methods.get_user_by_email(request.email) if user is None: raise ErrorResponse( diff --git a/src/routers/organizations.py b/src/routers/organizations.py index 3b17e59..bc9c2aa 100644 --- a/src/routers/organizations.py +++ b/src/routers/organizations.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends -from db import methods +from db.methods import methods from db.types import types, RQ, RS from utils.auth.auth import get_current_user from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse @@ -11,7 +11,14 @@ @router.get("/get", response_model=SuccessfulResponse[RS.organizations.get]) async def get(request: RQ.organizations.get = Depends(), _current_user: types.User = Depends(get_current_user)): - if (organization := methods.organizations.get(request.id)) is None: + if (organization := methods.get_organization(request.id)) is None: raise ErrorResponse(code=ErrorCodes.NOT_FOUND) return organization + + +@router.get("/get_members", response_model=SuccessfulResponse[RS.organizations.get_members]) +async def get_members( + request: RQ.organizations.get_members = Depends(), _current_user: types.User = Depends(get_current_user) +): + return RS.organizations.get_members(members=methods.get_members_of_organization(request.id)) diff --git a/src/routers/test.py b/src/routers/test.py index e86ce41..320f9de 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -6,7 +6,7 @@ import utils.auth from config import config from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse -from db import methods +from db.methods import methods from db.client import client from db.types import types, RS, RQ @@ -26,8 +26,7 @@ async def cleanup(): @router.post("/signup", response_model=SuccessfulResponse[RS.test.signup]) async def signup(request: RQ.test.signup): hashed_password = utils.auth.hash_password(request.password) - - inserted_user_id = methods.users.insert_user( + inserted_user_id = methods.insert_user( types.UserWithoutID( email=request.email, hashed_password=hashed_password, @@ -51,11 +50,9 @@ async def create_organization( request: RQ.test.organizations.create, current_user: types.User = Depends(utils.auth.get_current_user), ): - inserted_id = methods.organizations.insert_organization( - types.OrganizationWithoutID(name=request.name, members=[types.Member(id=current_user.id)]) - ) - - return RS.test.organizations.create(members=[types.Member(id=current_user.id)], name=request.name, id=inserted_id) + inserted_id = methods.insert_organization(types.OrganizationWithoutID(name=request.name)) + methods.insert_member_to_organization(types.Member(object_id=current_user.id, target_id=inserted_id)) + return types.Organization(id=inserted_id, name=request.name) @router.post("/organizations/invite", response_model=SuccessfulResponse[None]) @@ -63,24 +60,18 @@ async def invite_user_to_organization( request: RQ.test.organizations.invite, current_user: types.User = Depends(utils.auth.get_current_user), ): - with client.start_session() as session: - with session.start_transaction(): - if not methods.organizations.check_existence(request.organization_id, session): - raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="Organization not found") - - if not methods.users.check_existence(request.user_id, session): - raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="User not found") - - if not methods.organizations.is_user_in_organization(current_user.id, request.organization_id, session): - raise ErrorResponse( - code=ErrorCodes.ACCESS_DENIED, - message="You are not a member of this organization", - ) - - if methods.organizations.is_user_in_organization(request.user_id, request.organization_id, session): - raise ErrorResponse( - code=ErrorCodes.USER_ALREADY_MEMBER, - message="User already in organization", - ) - - methods.organizations.add_member(request.organization_id, types.Member(id=request.user_id), session) + if not methods.check_organization_existence(request.organization_id): + raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="Organization not found") + + if not methods.check_user_existence(request.user_id): + raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="User not found") + + if not methods.is_user_in_organization(current_user.id, request.organization_id): + raise ErrorResponse(code=ErrorCodes.ACCESS_DENIED, message="You are not a member of this organization") + + is_member_inserted = methods.insert_member_to_organization( + types.Member(object_id=request.user_id, target_id=request.organization_id) + ) + + if not is_member_inserted: + raise ErrorResponse(code=ErrorCodes.USER_ALREADY_MEMBER, message="User already in organization") diff --git a/src/routers/users.py b/src/routers/users.py index 348d42b..f039c7d 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -2,7 +2,7 @@ from utils.response import SuccessfulResponse from utils.auth import get_current_user -from db import methods +from db.methods import methods from db.types import types, RS, RQ @@ -18,4 +18,4 @@ async def current(current_user: types.User = Depends(get_current_user)): async def get_organizations( request: RQ.users.get_organizations = Depends(), _current_user: types.User = Depends(get_current_user) ): - return RS.users.get_organizations(organizations=methods.organizations.get_organizations_by_user(request.id)) + return RS.users.get_organizations(organizations=methods.get_organizations_by_user(request.id)) diff --git a/src/utils/auth/auth.py b/src/utils/auth/auth.py index 0b3cca9..1b22d1c 100644 --- a/src/utils/auth/auth.py +++ b/src/utils/auth/auth.py @@ -5,7 +5,7 @@ from fastapi import Header from fastapi import status as http_status -from db import methods +from db.methods import methods from db.types import types from utils import response from config import config @@ -121,7 +121,7 @@ def get_current_user(authorization: str = Header()) -> types.User: subject = decode_jwt(token, config.auth.jwt_access_secret_key.get_secret_value())["subject"] - if subject.isnumeric() and (user := methods.users.get(int(subject))) is not None: + if subject.isnumeric() and (user := methods.get_user(int(subject))) is not None: return user raise response.ErrorResponse( @@ -136,7 +136,7 @@ def get_current_user_by_refresh_token(authorization: str = Header()) -> types.Us subject = decode_jwt(token, config.auth.jwt_refresh_secret_key.get_secret_value())["subject"] - if subject.isnumeric() and (user := methods.users.get(int(subject))) is not None: + if subject.isnumeric() and (user := methods.get_user(int(subject))) is not None: return user raise response.ErrorResponse( diff --git a/tests/src/helpers/api.ts b/tests/src/helpers/api.ts index eaeef31..7dc1aed 100644 --- a/tests/src/helpers/api.ts +++ b/tests/src/helpers/api.ts @@ -11,12 +11,15 @@ const api = { refresh: (bearerToken: string) => pactum.spec().get("/api/auth/refresh") .withBearerToken(bearerToken), signin: (request: RQ.auth.signin) => pactum.spec().post("/api/auth/signin") - .withBody(makeRequest(request)) + .withBody(makeRequest(request)), }, organizations: { get: (request: RQ.organizations.get, bearerToken: string) => pactum.spec().get("/api/organizations/get") .withBearerToken(bearerToken) - .withQueryParams(makeRequest(request)) + .withQueryParams(makeRequest(request)), + get_members: (request: RQ.organizations.get_members, bearerToken: string) => pactum.spec().get("/api/organizations/get_members") + .withBearerToken(bearerToken) + .withQueryParams(makeRequest(request)), }, test: { cleanup: () => pactum.spec().post("/api/test/cleanup"), @@ -28,7 +31,7 @@ const api = { .withBody(makeRequest(request)), invite: (request: RQ.test.organizations.invite, bearerToken: string) => pactum.spec().post("/api/test/organizations/invite") .withBearerToken(bearerToken) - .withBody(makeRequest(request)) + .withBody(makeRequest(request)), } }, users: { @@ -36,7 +39,7 @@ const api = { .withBearerToken(bearerToken), get_organizations: (request: RQ.users.get_organizations, bearerToken: string) => pactum.spec().get("/api/users/get_organizations") .withBearerToken(bearerToken) - .withQueryParams(makeRequest(request)) + .withQueryParams(makeRequest(request)), } }; diff --git a/tests/src/helpers/requests.ts b/tests/src/helpers/requests.ts index c2a8ca5..50e4aa2 100644 --- a/tests/src/helpers/requests.ts +++ b/tests/src/helpers/requests.ts @@ -11,6 +11,10 @@ namespace RQ { export interface get { id: number; } + + export interface get_members { + id: number; + } } export namespace users { export interface get_organizations { diff --git a/tests/src/helpers/responses.ts b/tests/src/helpers/responses.ts index 3d13ad7..2e5d087 100644 --- a/tests/src/helpers/responses.ts +++ b/tests/src/helpers/responses.ts @@ -23,6 +23,9 @@ namespace RS { } export namespace organizations { export interface get extends Organization {}; + export interface get_members { + members: number[]; + }; } export namespace test { export interface signup { diff --git a/tests/src/helpers/types.ts b/tests/src/helpers/types.ts index 7321a80..6c2d0c9 100644 --- a/tests/src/helpers/types.ts +++ b/tests/src/helpers/types.ts @@ -3,20 +3,7 @@ export interface JWTPair { refreshToken: string; } -interface HasMembersWithRoles { - members: { - id: number; - customPermissions: number; - roles: string[]; - }[]; - roles: { - id: number; - name: string; - permissions: number; - }[]; -} - -export interface Organization extends HasMembersWithRoles { +export interface Organization { id: number; name: string; } diff --git a/tests/src/organizations.test.ts b/tests/src/organizations.test.ts index 9346aa4..c0f6a3e 100644 --- a/tests/src/organizations.test.ts +++ b/tests/src/organizations.test.ts @@ -18,18 +18,15 @@ describe("Organizations", () => { }); test("Create and get organization", async () => { - const organizationName = "test_org"; - const organization: RS.test.organizations.create = await api.test.organizations.create({name: organizationName}, firstUser.getAccessToken()) + const organization: RS.test.organizations.create = await api.test.organizations.create({name: "test_org"}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); // Ensure that organization have only creator as a member - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) - .expectJsonLike({ok: true}) - .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, name: organizationName, members: [{id: firstUser.id}] - }); + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) + .expectJsonLike({"ok": true}) + .returns(makeResponse) + expect(members.members).toContain(firstUser.id); }); test("Invite user to organization", async () => { @@ -41,12 +38,11 @@ describe("Organizations", () => { .expectJsonLike({ok: true}); // Ensure that the organization members have been updated - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}, {id: secondUser.id}] - }); + expect(members.members).toContain(firstUser.id); + expect(members.members).toContain(secondUser.id); }); test("Invite user to organization from unauthorized user", async () => { @@ -59,12 +55,10 @@ describe("Organizations", () => { .expectJsonLike({ok: false, error: {code: ErrorCodes.ACCESS_DENIED}}); // Ensure that the organization members remain unchanged - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}] - }); + expect(members).toMatchObject({members: [firstUser.id]}) }); test("Invite user already in organization", async () => { @@ -81,12 +75,10 @@ describe("Organizations", () => { .expectJsonLike({ok: false, error: {code: ErrorCodes.USER_ALREADY_MEMBER}}); // Ensure that the organization members remain unchanged - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}, {id: secondUser.id}] - }); + expect(members).toMatchObject({members: [firstUser.id, secondUser.id]}); }); test("Get user organizations", async () => { // Create two different organizations and ensure that after each creation method returns new organization From d8e2ce6adc187c10e6cf53a3a7bc7cc06e71d965 Mon Sep 17 00:00:00 2001 From: Spotika Date: Thu, 26 Sep 2024 14:52:23 +0300 Subject: [PATCH 3/9] refactor: move out the types folder && fix imports --- src/db/__init__.py | 8 ++------ src/db/methods/__init__.py | 5 +---- src/db/methods/methods.py | 4 ++-- src/routers/auth.py | 5 +++-- src/routers/organizations.py | 5 +++-- src/routers/test.py | 5 +++-- src/routers/users.py | 5 +++-- src/{db => }/types/__init__.py | 4 ++-- src/{db => }/types/requests.py | 0 src/{db => }/types/responses.py | 2 +- src/{db => }/types/types.py | 0 src/{db => }/types/validators.py | 0 src/utils/auth/auth.py | 4 ++-- 13 files changed, 22 insertions(+), 25 deletions(-) rename src/{db => }/types/__init__.py (50%) rename src/{db => }/types/requests.py (100%) rename src/{db => }/types/responses.py (96%) rename src/{db => }/types/types.py (100%) rename src/{db => }/types/validators.py (100%) diff --git a/src/db/__init__.py b/src/db/__init__.py index d40a224..4ebe159 100644 --- a/src/db/__init__.py +++ b/src/db/__init__.py @@ -1,8 +1,4 @@ -from . import types, methods, client +from . import methods, client -__all__ = [ - "types", - "methods", - "client", -] +__all__ = ["methods", "client"] diff --git a/src/db/methods/__init__.py b/src/db/methods/__init__.py index 3c80016..ae4bc03 100644 --- a/src/db/methods/__init__.py +++ b/src/db/methods/__init__.py @@ -1,4 +1 @@ -from . import methods - - -__all__ = ["methods"] +from .methods import * diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py index ac80f3b..407b567 100644 --- a/src/db/methods/methods.py +++ b/src/db/methods/methods.py @@ -1,8 +1,8 @@ from pymongo.client_session import ClientSession from pymongo.errors import DuplicateKeyError -from db.types import types -from db.methods.helpers import insert_with_auto_increment_id +from src import types +from .helpers import insert_with_auto_increment_id from .collections import users, organizations, organization_members diff --git a/src/routers/auth.py b/src/routers/auth.py index 26921b5..64ec29e 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -4,8 +4,9 @@ import utils.auth from utils.auth import get_current_user_by_refresh_token from utils.response import SuccessfulResponse, ErrorCodes, ErrorResponse -from db.methods import methods -from db.types import types, RS, RQ +from db import methods +from src import types +from src.types import RQ, RS router = APIRouter() diff --git a/src/routers/organizations.py b/src/routers/organizations.py index bc9c2aa..9bebaa8 100644 --- a/src/routers/organizations.py +++ b/src/routers/organizations.py @@ -1,7 +1,8 @@ from fastapi import APIRouter, Depends -from db.methods import methods -from db.types import types, RQ, RS +from db import methods +from src import types +from src.types import RQ, RS from utils.auth.auth import get_current_user from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse diff --git a/src/routers/test.py b/src/routers/test.py index 320f9de..16af5d0 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -6,9 +6,10 @@ import utils.auth from config import config from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse -from db.methods import methods +from db import methods from db.client import client -from db.types import types, RS, RQ +from src import types +from src.types import RQ, RS router = APIRouter() diff --git a/src/routers/users.py b/src/routers/users.py index f039c7d..0e57347 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -2,8 +2,9 @@ from utils.response import SuccessfulResponse from utils.auth import get_current_user -from db.methods import methods -from db.types import types, RS, RQ +from db import methods +from src import types +from src.types import RS, RQ router = APIRouter() diff --git a/src/db/types/__init__.py b/src/types/__init__.py similarity index 50% rename from src/db/types/__init__.py rename to src/types/__init__.py index 64882cc..2ae5156 100644 --- a/src/db/types/__init__.py +++ b/src/types/__init__.py @@ -1,5 +1,5 @@ -from . import types +from .types import * from .requests import RQ from .responses import RS -__all__ = ["types", "RQ", "RS"] +__all__ = ["RQ", "RS"] diff --git a/src/db/types/requests.py b/src/types/requests.py similarity index 100% rename from src/db/types/requests.py rename to src/types/requests.py diff --git a/src/db/types/responses.py b/src/types/responses.py similarity index 96% rename from src/db/types/responses.py rename to src/types/responses.py index e957b16..5689266 100644 --- a/src/db/types/responses.py +++ b/src/types/responses.py @@ -2,7 +2,7 @@ from __future__ import annotations from utils.schemas import BaseModel -from db.types import types +from . import types class RS: diff --git a/src/db/types/types.py b/src/types/types.py similarity index 100% rename from src/db/types/types.py rename to src/types/types.py diff --git a/src/db/types/validators.py b/src/types/validators.py similarity index 100% rename from src/db/types/validators.py rename to src/types/validators.py diff --git a/src/utils/auth/auth.py b/src/utils/auth/auth.py index 1b22d1c..6617dd5 100644 --- a/src/utils/auth/auth.py +++ b/src/utils/auth/auth.py @@ -5,8 +5,8 @@ from fastapi import Header from fastapi import status as http_status -from db.methods import methods -from db.types import types +from db import methods +from src import types from utils import response from config import config From c97f9d78949f8147c415d9d1b48bd11ccb84c1b0 Mon Sep 17 00:00:00 2001 From: Spotika Date: Thu, 26 Sep 2024 15:19:59 +0300 Subject: [PATCH 4/9] fix --- src/db/methods/methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py index 407b567..f4bdce6 100644 --- a/src/db/methods/methods.py +++ b/src/db/methods/methods.py @@ -56,7 +56,7 @@ def get_members_of_organization(organization_id: int, s: ClientSession | None = {"$match": {"target_id": organization_id}}, {"$group": {"_id": None, "result": {"$push": "$object_id"}}}, ] - return next(organization_members.aggregate(pipeline, session=s)).get("result", None) + return next(organization_members.aggregate(pipeline, session=s)).get("result", []) # Users From e3a79ba00fef2f5be08d898753bfa0a71624eefd Mon Sep 17 00:00:00 2001 From: Spotika Date: Thu, 26 Sep 2024 15:35:08 +0300 Subject: [PATCH 5/9] fix --- src/db/methods/collections/collections.py | 2 ++ src/db/methods/methods.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/db/methods/collections/collections.py b/src/db/methods/collections/collections.py index 2fbaa55..5cb8dbf 100644 --- a/src/db/methods/collections/collections.py +++ b/src/db/methods/collections/collections.py @@ -1,5 +1,7 @@ """Here collection and indexes are defined""" +from typing import Any +from pymongo.collection import Collection from db.client import db from config import config diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py index f4bdce6..ad227be 100644 --- a/src/db/methods/methods.py +++ b/src/db/methods/methods.py @@ -56,7 +56,7 @@ def get_members_of_organization(organization_id: int, s: ClientSession | None = {"$match": {"target_id": organization_id}}, {"$group": {"_id": None, "result": {"$push": "$object_id"}}}, ] - return next(organization_members.aggregate(pipeline, session=s)).get("result", []) + return (organization_members.aggregate(pipeline, session=s).try_next() or {}).get("result", []) # Users From 5f8bae5ab1e666ec723384532c53d799e73d2030 Mon Sep 17 00:00:00 2001 From: Spotika Date: Thu, 26 Sep 2024 15:37:38 +0300 Subject: [PATCH 6/9] fix --- src/db/methods/collections/collections.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/db/methods/collections/collections.py b/src/db/methods/collections/collections.py index 5cb8dbf..2fbaa55 100644 --- a/src/db/methods/collections/collections.py +++ b/src/db/methods/collections/collections.py @@ -1,7 +1,5 @@ """Here collection and indexes are defined""" -from typing import Any -from pymongo.collection import Collection from db.client import db from config import config From 4fe2b52e27af6b899ecea5236365f9274467ae10 Mon Sep 17 00:00:00 2001 From: Spotika Date: Thu, 26 Sep 2024 15:43:16 +0300 Subject: [PATCH 7/9] refactor --- src/db/methods/methods.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py index ad227be..28f8a5a 100644 --- a/src/db/methods/methods.py +++ b/src/db/methods/methods.py @@ -30,7 +30,6 @@ def insert_member_to_organization(member: types.Member, s: ClientSession | None def get_organizations_by_user(user_id: int, s: ClientSession | None = None) -> list[types.Organization]: - pipeline = [ {"$match": {"object_id": user_id}}, { From 69030dfaa465ccbcd8933b121245610344df6346 Mon Sep 17 00:00:00 2001 From: Spotika Date: Fri, 27 Sep 2024 19:36:28 +0300 Subject: [PATCH 8/9] refactor & resolve conversations --- src/db/methods/__init__.py | 3 +- .../methods/{methods.py => organizations.py} | 29 ++---------------- src/db/methods/users.py | 30 +++++++++++++++++++ src/routers/auth.py | 3 +- src/routers/organizations.py | 3 +- src/routers/test.py | 10 ++----- src/routers/users.py | 3 +- src/{types => t}/__init__.py | 4 +-- src/{types => t}/requests.py | 0 src/{types => t}/responses.py | 2 +- src/{types => t}/types.py | 0 src/{types => t}/validators.py | 0 src/utils/auth/auth.py | 2 +- 13 files changed, 44 insertions(+), 45 deletions(-) rename src/db/methods/{methods.py => organizations.py} (69%) create mode 100644 src/db/methods/users.py rename src/{types => t}/__init__.py (50%) rename src/{types => t}/requests.py (100%) rename src/{types => t}/responses.py (97%) rename src/{types => t}/types.py (100%) rename src/{types => t}/validators.py (100%) diff --git a/src/db/methods/__init__.py b/src/db/methods/__init__.py index ae4bc03..46ae102 100644 --- a/src/db/methods/__init__.py +++ b/src/db/methods/__init__.py @@ -1 +1,2 @@ -from .methods import * +from .users import * +from .organizations import * diff --git a/src/db/methods/methods.py b/src/db/methods/organizations.py similarity index 69% rename from src/db/methods/methods.py rename to src/db/methods/organizations.py index 28f8a5a..1223d1b 100644 --- a/src/db/methods/methods.py +++ b/src/db/methods/organizations.py @@ -1,12 +1,11 @@ from pymongo.client_session import ClientSession from pymongo.errors import DuplicateKeyError -from src import types +from t import types from .helpers import insert_with_auto_increment_id -from .collections import users, organizations, organization_members +from .collections import organizations, organization_members -# Organizations def get_organization(organization_id: int, s: ClientSession | None = None) -> types.Organization | None: if (organization := organizations.find_one({"_id": organization_id}, session=s)) is None: return None @@ -56,27 +55,3 @@ def get_members_of_organization(organization_id: int, s: ClientSession | None = {"$group": {"_id": None, "result": {"$push": "$object_id"}}}, ] return (organization_members.aggregate(pipeline, session=s).try_next() or {}).get("result", []) - - -# Users -def get_user(user_id: int, s: ClientSession | None = None) -> types.User | None: - if (user := users.find_one({"_id": user_id}, session=s)) is None: - return None - return types.User(**user) - - -def get_user_by_email(email: str, s: ClientSession | None = None): - if (user := users.find_one({"email": email}, session=s)) is None: - return None - return types.User(**user) - - -def insert_user(user: types.UserWithoutID, s: ClientSession | None = None) -> int | None: - try: - return insert_with_auto_increment_id(users, user.db_dump(), session=s) - except DuplicateKeyError: - return None - - -def check_user_existence(user_id: int, s: ClientSession | None = None) -> bool: - return users.count_documents({"_id": user_id}, session=s) > 0 diff --git a/src/db/methods/users.py b/src/db/methods/users.py new file mode 100644 index 0000000..b03b4ac --- /dev/null +++ b/src/db/methods/users.py @@ -0,0 +1,30 @@ +from pymongo.client_session import ClientSession +from pymongo.errors import DuplicateKeyError + + +from t import types +from .helpers import insert_with_auto_increment_id +from .collections import users + + +def get_user(user_id: int, s: ClientSession | None = None) -> types.User | None: + if (user := users.find_one({"_id": user_id}, session=s)) is None: + return None + return types.User(**user) + + +def get_user_by_email(email: str, s: ClientSession | None = None): + if (user := users.find_one({"email": email}, session=s)) is None: + return None + return types.User(**user) + + +def insert_user(user: types.UserWithoutID, s: ClientSession | None = None) -> int | None: + try: + return insert_with_auto_increment_id(users, user.db_dump(), session=s) + except DuplicateKeyError: + return None + + +def check_user_existence(user_id: int, s: ClientSession | None = None) -> bool: + return users.count_documents({"_id": user_id}, session=s) > 0 diff --git a/src/routers/auth.py b/src/routers/auth.py index 64ec29e..24c4e60 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -5,8 +5,7 @@ from utils.auth import get_current_user_by_refresh_token from utils.response import SuccessfulResponse, ErrorCodes, ErrorResponse from db import methods -from src import types -from src.types import RQ, RS +from t import types, RQ, RS router = APIRouter() diff --git a/src/routers/organizations.py b/src/routers/organizations.py index 9bebaa8..d33d8f4 100644 --- a/src/routers/organizations.py +++ b/src/routers/organizations.py @@ -1,8 +1,7 @@ from fastapi import APIRouter, Depends from db import methods -from src import types -from src.types import RQ, RS +from t import types, RQ, RS from utils.auth.auth import get_current_user from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse diff --git a/src/routers/test.py b/src/routers/test.py index 16af5d0..9aaecf6 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -8,9 +8,7 @@ from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse from db import methods from db.client import client -from src import types -from src.types import RQ, RS - +from t import types, RQ, RS router = APIRouter() @@ -70,9 +68,7 @@ async def invite_user_to_organization( if not methods.is_user_in_organization(current_user.id, request.organization_id): raise ErrorResponse(code=ErrorCodes.ACCESS_DENIED, message="You are not a member of this organization") - is_member_inserted = methods.insert_member_to_organization( + if not methods.insert_member_to_organization( types.Member(object_id=request.user_id, target_id=request.organization_id) - ) - - if not is_member_inserted: + ): raise ErrorResponse(code=ErrorCodes.USER_ALREADY_MEMBER, message="User already in organization") diff --git a/src/routers/users.py b/src/routers/users.py index 0e57347..476af04 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -3,8 +3,7 @@ from utils.response import SuccessfulResponse from utils.auth import get_current_user from db import methods -from src import types -from src.types import RS, RQ +from t import types, RS, RQ router = APIRouter() diff --git a/src/types/__init__.py b/src/t/__init__.py similarity index 50% rename from src/types/__init__.py rename to src/t/__init__.py index 2ae5156..64882cc 100644 --- a/src/types/__init__.py +++ b/src/t/__init__.py @@ -1,5 +1,5 @@ -from .types import * +from . import types from .requests import RQ from .responses import RS -__all__ = ["RQ", "RS"] +__all__ = ["types", "RQ", "RS"] diff --git a/src/types/requests.py b/src/t/requests.py similarity index 100% rename from src/types/requests.py rename to src/t/requests.py diff --git a/src/types/responses.py b/src/t/responses.py similarity index 97% rename from src/types/responses.py rename to src/t/responses.py index 5689266..1c4c55f 100644 --- a/src/types/responses.py +++ b/src/t/responses.py @@ -2,7 +2,7 @@ from __future__ import annotations from utils.schemas import BaseModel -from . import types +from t import types class RS: diff --git a/src/types/types.py b/src/t/types.py similarity index 100% rename from src/types/types.py rename to src/t/types.py diff --git a/src/types/validators.py b/src/t/validators.py similarity index 100% rename from src/types/validators.py rename to src/t/validators.py diff --git a/src/utils/auth/auth.py b/src/utils/auth/auth.py index 6617dd5..52f4210 100644 --- a/src/utils/auth/auth.py +++ b/src/utils/auth/auth.py @@ -5,8 +5,8 @@ from fastapi import Header from fastapi import status as http_status +from t import types from db import methods -from src import types from utils import response from config import config From c43d110ccb7ca9db4503f21c9f887f702acb487a Mon Sep 17 00:00:00 2001 From: Spotika Date: Sun, 6 Oct 2024 11:15:19 +0300 Subject: [PATCH 9/9] refactor(types): rename t -> typings --- src/db/methods/organizations.py | 2 +- src/db/methods/users.py | 2 +- src/routers/auth.py | 2 +- src/routers/organizations.py | 2 +- src/routers/test.py | 2 +- src/routers/users.py | 2 +- src/{t => typings}/__init__.py | 0 src/{t => typings}/requests.py | 0 src/{t => typings}/responses.py | 2 +- src/{t => typings}/types.py | 0 src/{t => typings}/validators.py | 0 src/utils/auth/auth.py | 2 +- 12 files changed, 8 insertions(+), 8 deletions(-) rename src/{t => typings}/__init__.py (100%) rename src/{t => typings}/requests.py (100%) rename src/{t => typings}/responses.py (97%) rename src/{t => typings}/types.py (100%) rename src/{t => typings}/validators.py (100%) diff --git a/src/db/methods/organizations.py b/src/db/methods/organizations.py index 1223d1b..0419041 100644 --- a/src/db/methods/organizations.py +++ b/src/db/methods/organizations.py @@ -1,7 +1,7 @@ from pymongo.client_session import ClientSession from pymongo.errors import DuplicateKeyError -from t import types +from typings import types from .helpers import insert_with_auto_increment_id from .collections import organizations, organization_members diff --git a/src/db/methods/users.py b/src/db/methods/users.py index b03b4ac..bf88c41 100644 --- a/src/db/methods/users.py +++ b/src/db/methods/users.py @@ -2,7 +2,7 @@ from pymongo.errors import DuplicateKeyError -from t import types +from typings import types from .helpers import insert_with_auto_increment_id from .collections import users diff --git a/src/routers/auth.py b/src/routers/auth.py index 24c4e60..b7a366a 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -5,7 +5,7 @@ from utils.auth import get_current_user_by_refresh_token from utils.response import SuccessfulResponse, ErrorCodes, ErrorResponse from db import methods -from t import types, RQ, RS +from typings import types, RQ, RS router = APIRouter() diff --git a/src/routers/organizations.py b/src/routers/organizations.py index d33d8f4..3b3e4b8 100644 --- a/src/routers/organizations.py +++ b/src/routers/organizations.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends from db import methods -from t import types, RQ, RS +from typings import types, RQ, RS from utils.auth.auth import get_current_user from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse diff --git a/src/routers/test.py b/src/routers/test.py index 9aaecf6..52577dc 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -8,7 +8,7 @@ from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse from db import methods from db.client import client -from t import types, RQ, RS +from typings import types, RQ, RS router = APIRouter() diff --git a/src/routers/users.py b/src/routers/users.py index 476af04..cf47005 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -3,7 +3,7 @@ from utils.response import SuccessfulResponse from utils.auth import get_current_user from db import methods -from t import types, RS, RQ +from typings import types, RS, RQ router = APIRouter() diff --git a/src/t/__init__.py b/src/typings/__init__.py similarity index 100% rename from src/t/__init__.py rename to src/typings/__init__.py diff --git a/src/t/requests.py b/src/typings/requests.py similarity index 100% rename from src/t/requests.py rename to src/typings/requests.py diff --git a/src/t/responses.py b/src/typings/responses.py similarity index 97% rename from src/t/responses.py rename to src/typings/responses.py index 1c4c55f..ce63c1d 100644 --- a/src/t/responses.py +++ b/src/typings/responses.py @@ -2,7 +2,7 @@ from __future__ import annotations from utils.schemas import BaseModel -from t import types +from typings import types class RS: diff --git a/src/t/types.py b/src/typings/types.py similarity index 100% rename from src/t/types.py rename to src/typings/types.py diff --git a/src/t/validators.py b/src/typings/validators.py similarity index 100% rename from src/t/validators.py rename to src/typings/validators.py diff --git a/src/utils/auth/auth.py b/src/utils/auth/auth.py index 52f4210..45e0558 100644 --- a/src/utils/auth/auth.py +++ b/src/utils/auth/auth.py @@ -5,7 +5,7 @@ from fastapi import Header from fastapi import status as http_status -from t import types +from typings import types from db import methods from utils import response from config import config