diff --git a/services/backend/api/v1/auth.py b/services/backend/api/v1/auth.py index 354c16a..bcc8870 100644 --- a/services/backend/api/v1/auth.py +++ b/services/backend/api/v1/auth.py @@ -1,5 +1,5 @@ import datetime -from typing import Optional, Any +from typing import Any import jwt from django.conf import settings @@ -10,7 +10,7 @@ from apps.user.models import User class BearerAuth(HttpBearer): - def authenticate(self, request: HttpRequest, token: str) -> Optional[Any]: + def authenticate(self, request: HttpRequest, token: str) -> Any | None: data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) if data["exp"] < datetime.datetime.now().timestamp(): return None @@ -21,7 +21,9 @@ class BearerAuth(HttpBearer): @staticmethod def generate_jwt(user: User) -> str: data = { - "exp": (datetime.datetime.now() + datetime.timedelta(days=365)).timestamp(), - "id": str(user.id) + "exp": ( + datetime.datetime.now() + datetime.timedelta(days=365) + ).timestamp(), + "id": str(user.id), } return jwt.encode(data, settings.SECRET_KEY, algorithm="HS256") diff --git a/services/backend/api/v1/competitions/__init__.py b/services/backend/api/v1/competition/__init__.py similarity index 100% rename from services/backend/api/v1/competitions/__init__.py rename to services/backend/api/v1/competition/__init__.py diff --git a/services/backend/api/v1/competition/schemas.py b/services/backend/api/v1/competition/schemas.py new file mode 100644 index 0000000..efd667a --- /dev/null +++ b/services/backend/api/v1/competition/schemas.py @@ -0,0 +1,30 @@ +from uuid import UUID + +from ninja import ModelSchema + +from apps.competition.models import Competition + + +class CompetitionOut(ModelSchema): + id: UUID + + class Meta: + model = Competition + fields = "__all__" + + +class CompetitionListInstanceOut(ModelSchema): + id: UUID + is_participating: bool + completed: bool + + class Meta: + model = Competition + fields = ( + "id", + "title", + "description", + "start_date", + "end_date", + "image_url", + ) diff --git a/services/backend/api/v1/competition/views.py b/services/backend/api/v1/competition/views.py new file mode 100644 index 0000000..95a70d5 --- /dev/null +++ b/services/backend/api/v1/competition/views.py @@ -0,0 +1,39 @@ +from http import HTTPStatus as status +from uuid import UUID + +from django.http import HttpRequest +from ninja import Router + +import api.v1.schemas as global_schemas +from api.v1.auth import BearerAuth +from api.v1.competition import schemas + +router = Router(tags=["competition"]) + + +@router.get( + "competition/{competition_id}", + response={ + status.OK: schemas.CompetitionOut, + status.BAD_REQUEST: global_schemas.BadRequestError, + status.UNAUTHORIZED: global_schemas.UnauthorizedError, + }, + auth=BearerAuth(), +) +def get_competition( + request: HttpRequest, competition_id: UUID +) -> tuple[status, schemas.CompetitionOut]: ... + + +@router.get( + "competitions", + response={ + status.OK: list[schemas.CompetitionListInstanceOut], + status.BAD_REQUEST: global_schemas.BadRequestError, + status.UNAUTHORIZED: global_schemas.UnauthorizedError, + }, + auth=BearerAuth(), +) +def list_competitions( + request: HttpRequest, is_participating: bool +) -> tuple[status, list[schemas.CompetitionListInstanceOut]]: ... diff --git a/services/backend/api/v1/competitions/schemas.py b/services/backend/api/v1/competitions/schemas.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/backend/api/v1/competitions/views.py b/services/backend/api/v1/competitions/views.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/backend/api/v1/ping/views.py b/services/backend/api/v1/ping/views.py index a6d78d7..378074d 100644 --- a/services/backend/api/v1/ping/views.py +++ b/services/backend/api/v1/ping/views.py @@ -15,4 +15,4 @@ router = Router(tags=["ping"]) }, ) def ping(request: HttpRequest) -> tuple[status, schemas.PingOut]: - return status.OK, schemas.PingOut + return status.OK, schemas.PingOut() diff --git a/services/backend/api/v1/router.py b/services/backend/api/v1/router.py index aa06562..bc544a8 100644 --- a/services/backend/api/v1/router.py +++ b/services/backend/api/v1/router.py @@ -3,14 +3,14 @@ from functools import partial from ninja import NinjaAPI from api.v1 import handlers -from api.v1.auth import BearerAuth +from api.v1.competition.views import router as competition_router from api.v1.ping.views import router as ping_router -from api.v1.users.views import router as users_router +from api.v1.user.views import router as user_router router = NinjaAPI( - title="DataRush API", + title="project_name API", version="1", - description="API docs for DataRush", + description="API docs for project_name", openapi_url="/docs/openapi.json", ) @@ -21,8 +21,11 @@ router.add_router( ) router.add_router( "", - users_router, - auth=BearerAuth(), + user_router, +) +router.add_router( + "", + competition_router, ) diff --git a/services/backend/api/v1/users/__init__.py b/services/backend/api/v1/user/__init__.py similarity index 100% rename from services/backend/api/v1/users/__init__.py rename to services/backend/api/v1/user/__init__.py diff --git a/services/backend/api/v1/users/schemas.py b/services/backend/api/v1/user/schemas.py similarity index 91% rename from services/backend/api/v1/users/schemas.py rename to services/backend/api/v1/user/schemas.py index 067ca85..edde86f 100644 --- a/services/backend/api/v1/users/schemas.py +++ b/services/backend/api/v1/user/schemas.py @@ -1,5 +1,4 @@ -from ninja import Schema, ModelSchema - +from ninja import ModelSchema, Schema from apps.user.models import User diff --git a/services/backend/api/v1/users/views.py b/services/backend/api/v1/user/views.py similarity index 94% rename from services/backend/api/v1/users/views.py rename to services/backend/api/v1/user/views.py index db77bc0..b253db9 100644 --- a/services/backend/api/v1/users/views.py +++ b/services/backend/api/v1/user/views.py @@ -3,7 +3,7 @@ from http import HTTPStatus as status from ninja import Router from ninja.errors import AuthenticationError -from api.v1.users.schemas import LoginSchema, RegisterSchema, TokenSchema, UserSchema +from api.v1.user.schemas import LoginSchema, RegisterSchema, TokenSchema, UserSchema from api.v1.auth import BearerAuth from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError from apps.user.models import User diff --git a/services/backend/apps/competitions/__init__.py b/services/backend/apps/competition/__init__.py similarity index 100% rename from services/backend/apps/competitions/__init__.py rename to services/backend/apps/competition/__init__.py diff --git a/services/backend/apps/competition/apps.py b/services/backend/apps/competition/apps.py new file mode 100644 index 0000000..64e1839 --- /dev/null +++ b/services/backend/apps/competition/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class CompetitionsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.competition" + label = "competition" diff --git a/services/backend/apps/competition/migrations/0001_initial.py b/services/backend/apps/competition/migrations/0001_initial.py new file mode 100644 index 0000000..dd16963 --- /dev/null +++ b/services/backend/apps/competition/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1.6 on 2025-02-28 21:27 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Competition', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=100, verbose_name='Название')), + ('description', models.TextField(verbose_name='Описание')), + ('image_url', models.FileField(blank=True, null=True, upload_to='', verbose_name='Изображение соревнования')), + ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='Дедлайн участия')), + ('start_date', models.DateTimeField(blank=True, null=True, verbose_name='Дедлайн участия')), + ('type', models.CharField(choices=[('solo', 'Solo')], max_length=10, verbose_name='Тип участия')), + ('participation_type', models.CharField(choices=[('edu', 'Edu'), ('competitive', 'Competitive')], max_length=11, verbose_name='Тип соревнования')), + ], + options={ + 'verbose_name': 'соревнование', + 'verbose_name_plural': 'соревнования', + }, + ), + ] diff --git a/services/backend/apps/competitions/migrations/__init__.py b/services/backend/apps/competition/migrations/__init__.py similarity index 100% rename from services/backend/apps/competitions/migrations/__init__.py rename to services/backend/apps/competition/migrations/__init__.py diff --git a/services/backend/apps/competition/models.py b/services/backend/apps/competition/models.py new file mode 100644 index 0000000..952058b --- /dev/null +++ b/services/backend/apps/competition/models.py @@ -0,0 +1,40 @@ +from django.db import models + +from apps.core.models import BaseModel + + +class CompetitionType(models.TextChoices): + SOLO = "solo" + + +class CompetitionParticipationType(models.TextChoices): + EDU = "edu" + COMPETITIVE = "competitive" + + +class Competition(BaseModel): + title = models.CharField(max_length=100, verbose_name="Название") + description = models.TextField(verbose_name="Описание") + image_url = models.FileField( + verbose_name="Изображение соревнования", null=True, blank=True + ) + end_date = models.DateTimeField( + verbose_name="Дедлайн участия", null=True, blank=True + ) + start_date = models.DateTimeField( + verbose_name="Дедлайн участия", null=True, blank=True + ) + type = models.CharField( + max_length=10, + choices=CompetitionType.choices, + verbose_name="Тип участия", + ) + participation_type = models.CharField( + max_length=11, + choices=CompetitionParticipationType.choices, + verbose_name="Тип соревнования", + ) + + class Meta: + verbose_name = "соревнование" + verbose_name_plural = "соревнования" diff --git a/services/backend/apps/competitions/apps.py b/services/backend/apps/competitions/apps.py deleted file mode 100644 index a74f56a..0000000 --- a/services/backend/apps/competitions/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class CompetitionsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.competitions' - label = 'competitions' diff --git a/services/backend/apps/competitions/models.py b/services/backend/apps/competitions/models.py deleted file mode 100644 index 0fa5134..0000000 --- a/services/backend/apps/competitions/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.db import models - -from apps.core.models import BaseModel - - -class CompetitionType(models.TextChoices): - SOLO = "solo" - - -class CompetitionPartipicationType(models.TextChoices): - EDU = "edu" - COMPETITIVE = "competitive" - - -class Competition(BaseModel): - title = models.CharField(max_length=100, verbose_name="Название") - description = models.TextField(verbose_name="Описание") - image_url = models.FileField(verbose_name="Изображение соревнования") - due_to = models.DateTimeField(verbose_name="Дедлайн участия") - - type = models.CharField(max_length=10, choices=CompetitionType.choices, - verbose_name="Тип участия") - participation_type = models.CharField(max_length=10, choices=CompetitionPartipicationType.choices, - verbose_name="Тип соревнования") - - class Meta: - verbose_name = "соревнование" - verbose_name_plural = "соревнования" diff --git a/services/backend/apps/user/apps.py b/services/backend/apps/user/apps.py index 0364d64..2f3daa6 100644 --- a/services/backend/apps/user/apps.py +++ b/services/backend/apps/user/apps.py @@ -2,6 +2,6 @@ from django.apps import AppConfig class UsersConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.user' - label = 'user' + default_auto_field = "django.db.models.BigAutoField" + name = "apps.user" + label = "user" diff --git a/services/backend/config/settings.py b/services/backend/config/settings.py index 73efd2e..c3a64da 100644 --- a/services/backend/config/settings.py +++ b/services/backend/config/settings.py @@ -444,6 +444,7 @@ INSTALLED_APPS = [ # Internal apps "apps.core", "apps.user", + "apps.competition", ] # GUID