Skip to content

Commit

Permalink
refactor: change database arch
Browse files Browse the repository at this point in the history
  • Loading branch information
Spotika committed Sep 25, 2024
1 parent 3bd43c2 commit e4d68bf
Show file tree
Hide file tree
Showing 21 changed files with 168 additions and 237 deletions.
6 changes: 1 addition & 5 deletions src/config/config_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 2 additions & 10 deletions src/db/methods/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
from . import (
users,
domains,
organizations,
)
from . import methods


__all__ = [
"users",
"domains",
"organizations",
]
__all__ = ["methods"]
7 changes: 2 additions & 5 deletions src/db/methods/collections/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
10 changes: 3 additions & 7 deletions src/db/methods/collections/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 0 additions & 18 deletions src/db/methods/domains.py

This file was deleted.

83 changes: 83 additions & 0 deletions src/db/methods/methods.py
Original file line number Diff line number Diff line change
@@ -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
48 changes: 0 additions & 48 deletions src/db/methods/organizations.py

This file was deleted.

33 changes: 0 additions & 33 deletions src/db/methods/users.py

This file was deleted.

6 changes: 4 additions & 2 deletions src/db/types/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/db/types/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 6 additions & 28 deletions src/db/types/types.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,21 @@
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):
access_token: str
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

Expand All @@ -50,7 +27,8 @@ class User(_UserBase):
id: int


class _OrganizationBase(_HasMembersWithRoles):
# Organizations
class _OrganizationBase(BaseModel):
name: str


Expand Down
10 changes: 3 additions & 7 deletions src/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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(
Expand Down
11 changes: 9 additions & 2 deletions src/routers/organizations.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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))
Loading

0 comments on commit e4d68bf

Please sign in to comment.