Skip to content

Commit

Permalink
Fix: use UUID
Browse files Browse the repository at this point in the history
  • Loading branch information
Rotheem committed Jan 7, 2025
1 parent 0b3b543 commit d4904ed
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 47 deletions.
4 changes: 2 additions & 2 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ def initialize_schools(
hyperion_error_logger.info("Startup: Adding new groups to the database")
with Session(sync_engine) as db:
for school in SchoolType:
exists = initialization.get_school_by_id_sync(school_id=school, db=db)
exists = initialization.get_school_by_id_sync(school_id=school.value, db=db)
# We don't want to recreate the groups if they already exist
if not exists:
db_school = models_core.CoreSchool(
id=school,
id=school.value,
name=school.name,
email_regex=".*",
)
Expand Down
3 changes: 2 additions & 1 deletion app/core/models_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Common model files for all core in order to avoid circular import due to bidirectional relationship"""

from datetime import date, datetime
from uuid import UUID

from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand Down Expand Up @@ -29,7 +30,7 @@ class CoreUser(Base):
index=True,
) # Use UUID later
email: Mapped[str] = mapped_column(unique=True, index=True)
school_id: Mapped[str] = mapped_column(ForeignKey("core_school.id"))
school_id: Mapped[UUID] = mapped_column(ForeignKey("core_school.id"))
password_hash: Mapped[str]
# Depending on the account type, the user may have different rights and access to different features
# External users may exist for:
Expand Down
4 changes: 2 additions & 2 deletions app/core/schemas_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class CoreUserSimple(CoreUserBase):

id: str
account_type: AccountType
school_id: str
school_id: UUID

model_config = ConfigDict(from_attributes=True)

Expand Down Expand Up @@ -123,7 +123,7 @@ class CoreUserFusionRequest(BaseModel):

class CoreUserUpdateAdmin(BaseModel):
email: str | None = None
school_id: str | None = None
school_id: UUID | None = None
account_type: AccountType | None = None
name: str | None = None
firstname: str | None = None
Expand Down
12 changes: 9 additions & 3 deletions app/core/schools/endpoints_schools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
School management is part of the core of Hyperion. These endpoints allow managing schools.
"""

import logging
import re
import uuid

Expand All @@ -23,6 +24,8 @@

router = APIRouter(tags=["Schools"])

hyperion_error_logger = logging.getLogger("hyperion.error")


@router.get(
"/schools/",
Expand Down Expand Up @@ -94,7 +97,7 @@ async def create_school(
await cruds_schools.create_school(school=db_school, db=db)
users = await cruds_users.get_users(
db=db,
schools_ids=[SchoolType.no_school],
schools_ids=[SchoolType.no_school.value],
)
for db_user in users:
if re.match(db_school.email_regex, db_user.email):
Expand Down Expand Up @@ -158,7 +161,10 @@ async def update_school(
and school_update.email_regex != school.email_regex
):
await cruds_users.remove_users_from_school(db, school_id=school_id)
users = await cruds_users.get_users(db, schools_ids=[SchoolType.no_school])
users = await cruds_users.get_users(
db,
schools_ids=[SchoolType.no_school.value],
)
for db_user in users:
if re.match(school_update.email_regex, db_user.email):
await cruds_users.update_user(
Expand Down Expand Up @@ -194,7 +200,7 @@ async def delete_school(
**This endpoint is only usable by administrators**
"""

if school_id in (item.value for item in SchoolType):
if school_id in (SchoolType.list()):
raise HTTPException(
status_code=400,
detail="SchoolTypes schools can not be deleted",
Expand Down
11 changes: 8 additions & 3 deletions app/core/schools/schools_type.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from enum import Enum
from uuid import UUID


class SchoolType(str, Enum):
class SchoolType(Enum):
"""
In Hyperion, each user must have a school. Belonging to a school gives access to a set of specific endpoints.
Expand All @@ -10,10 +11,14 @@ class SchoolType(str, Enum):
"""

# Account types
no_school = "dce19aa2-8863-4c93-861e-fb7be8f610ed"
centrale_lyon = "d9772da7-1142-4002-8b86-b694b431dfed"
no_school = UUID("dce19aa2-8863-4c93-861e-fb7be8f610ed")
centrale_lyon = UUID("d9772da7-1142-4002-8b86-b694b431dfed")

# Auth related groups

def __str__(self):
return f"{self.name}<{self.value}>"

@staticmethod
def list():
return [school.value for school in SchoolType]
22 changes: 13 additions & 9 deletions app/core/users/cruds_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ async def get_users(
excluded_account_types: list[AccountType] | None = None,
included_groups: list[str] | None = None,
excluded_groups: list[str] | None = None,
schools_ids: list[str] | None = None,
schools_ids: list[UUID] | None = None,
) -> Sequence[models_core.CoreUser]:
"""
Return all users from database.
Parameters `excluded_account_types` and `excluded_groups` can be used to filter results.
"""
included_account_types = included_account_types or list(AccountType)
included_account_types = included_account_types or None
excluded_account_types = excluded_account_types or []
included_groups = included_groups or []
excluded_groups = excluded_groups or []
Expand All @@ -48,12 +48,16 @@ async def get_users(
)
for group_id in included_groups
]
included_account_type_condition = or_(
False,
*[
models_core.CoreUser.account_type == account_type
for account_type in included_account_types
],
included_account_type_condition = (
or_(
False,
*[
models_core.CoreUser.account_type == account_type
for account_type in included_account_types
],
)
if included_account_types
else and_(True)
)
# We want, for each group that should not be included
# check that the following condition is false :
Expand Down Expand Up @@ -303,7 +307,7 @@ async def remove_users_from_school(
models_core.CoreUser.school_id == school_id,
)
.values(
school_id=SchoolType.no_school,
school_id=SchoolType.no_school.value,
account_type=AccountType.external,
),
)
Expand Down
18 changes: 1 addition & 17 deletions app/core/users/endpoints_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,23 +364,7 @@ async def activate_user(
detail=f"The account with the email {unconfirmed_user.email} is already confirmed",
)

# Check the account type

# For staff and student
# ^[\w\-.]*@((etu(-enise)?|enise)\.)?ec-lyon\.fr$
# For staff
# ^[\w\-.]*@(enise\.)?ec-lyon\.fr$
# For student
# ^[\w\-.]*@etu(-enise)?\.ec-lyon\.fr$

# For former students
# ^[\w\-.]*@centraliens-lyon\.net$

# All accepted emails
# ^[\w\-.]*@(((etu(-enise)?|enise)\.)?ec-lyon\.fr|centraliens-lyon\.net)$

# By default we mark the user as external
# but if it has an ECL email address, we will mark it as member
# Get the account type and school_id from the email
account_type, school_id = await get_account_type_and_school_id_from_email(
email=unconfirmed_user.email,
db=db,
Expand Down
10 changes: 4 additions & 6 deletions app/core/users/tools_users.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

Expand All @@ -14,7 +15,7 @@
async def get_account_type_and_school_id_from_email(
email: str,
db: AsyncSession,
) -> tuple[AccountType, str]:
) -> tuple[AccountType, UUID]:
"""Return the account type from the email"""
if re.match(ECL_STAFF_REGEX, email):
return AccountType.staff, SchoolType.centrale_lyon.value
Expand All @@ -23,11 +24,8 @@ async def get_account_type_and_school_id_from_email(
if re.match(ECL_FORMER_STUDENT_REGEX, email):
return AccountType.former_student, SchoolType.centrale_lyon.value
schools = await cruds_schools.get_schools(db)
schools = [
school
for school in schools
if school.id not in (SchoolType.centrale_lyon, SchoolType.no_school)
]

schools = [school for school in schools if school.id not in SchoolType.list()]
school = next(
(school for school in schools if re.match(school.email_regex, email)),
None,
Expand Down
3 changes: 2 additions & 1 deletion tests/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def change_redis_client_status(activated: bool) -> None:
async def create_user_with_groups(
groups: list[GroupType],
account_type: AccountType = AccountType.student,
school_id: SchoolType | str = SchoolType.centrale_lyon,
school_id: SchoolType | uuid.UUID = SchoolType.centrale_lyon,
user_id: str | None = None,
email: str | None = None,
password: str | None = None,
Expand All @@ -129,6 +129,7 @@ async def create_user_with_groups(

user_id = user_id or str(uuid.uuid4())
password_hash = security.get_password_hash(password or get_random_string())
school_id = school_id.value if isinstance(school_id, SchoolType) else school_id

user = models_core.CoreUser(
id=user_id,
Expand Down
8 changes: 5 additions & 3 deletions tests/test_schools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from uuid import UUID

import pytest_asyncio
from fastapi.testclient import TestClient
from pytest_mock import MockerFixture
Expand All @@ -17,7 +19,7 @@

UNIQUE_TOKEN = "my_unique_token"

id_test_ens = "4d133de7-24c4-4dbc-be73-4705a2ddd315"
id_test_ens = UUID("4d133de7-24c4-4dbc-be73-4705a2ddd315")


@pytest_asyncio.fixture(scope="module", autouse=True)
Expand Down Expand Up @@ -177,7 +179,7 @@ def test_create_user_corresponding_to_school(
assert response.status_code == 201

users = client.get(
"/users/",
"/users",
headers={"Authorization": f"Bearer {token}"},
)
user = next(
Expand Down Expand Up @@ -225,4 +227,4 @@ def test_delete_school(client: TestClient) -> None:
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 200
assert response.json()["school_id"] == SchoolType.no_school
assert response.json()["school_id"] == str(SchoolType.no_school.value)

0 comments on commit d4904ed

Please sign in to comment.