Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database: change db arch #67

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Spotika marked this conversation as resolved.
Show resolved Hide resolved


# 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
Spotika marked this conversation as resolved.
Show resolved Hide resolved
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
Loading