added competition endpoints

This commit is contained in:
Андрей Сумин
2025-03-01 01:10:08 +03:00
parent 65c86d2a9c
commit 7791a57b88
25 changed files with 209 additions and 64 deletions
+29
View File
@@ -0,0 +1,29 @@
import datetime
from typing import Any
import jwt
from django.conf import settings
from django.http import HttpRequest
from ninja.security import HttpBearer
from apps.user.models import User
class BearerAuth(HttpBearer):
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
user = User.objects.get(id=data["id"])
return user
@staticmethod
def generate_jwt(user: User) -> str:
data = {
"exp": (
datetime.datetime.now() + datetime.timedelta(days=365)
).timestamp(),
"id": str(user.id),
}
return jwt.encode(data, settings.SECRET_KEY, algorithm="HS256")
@@ -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",
)
@@ -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]]: ...
+1 -1
View File
@@ -15,4 +15,4 @@ router = Router(tags=["ping"])
}, },
) )
def ping(request: HttpRequest) -> tuple[status, schemas.PingOut]: def ping(request: HttpRequest) -> tuple[status, schemas.PingOut]:
return status.OK, schemas.PingOut return status.OK, schemas.PingOut()
+7 -2
View File
@@ -3,8 +3,9 @@ from functools import partial
from ninja import NinjaAPI from ninja import NinjaAPI
from api.v1 import handlers from api.v1 import handlers
from api.v1.competition.views import router as competition_router
from api.v1.ping.views import router as ping_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( router = NinjaAPI(
title="project_name API", title="project_name API",
@@ -20,7 +21,11 @@ router.add_router(
) )
router.add_router( router.add_router(
"", "",
users_router, user_router,
)
router.add_router(
"",
competition_router,
) )
@@ -1,7 +1,6 @@
from ninja import Schema, ModelSchema from ninja import ModelSchema, Schema
from apps.user.models import User
from apps.users.models import User
class TokenSchema(Schema): class TokenSchema(Schema):
@@ -1,11 +1,14 @@
from ninja import Router from ninja import Router
from api.v1.users.schemas import LoginSchema, RegisterSchema, TokenSchema, UserSchema
from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError
from apps.users.models import User from api.v1.user.schemas import (
LoginSchema,
RegisterSchema,
TokenSchema,
UserSchema,
)
router = Router(tags=["user"])
router = Router(tags=["users"])
@router.post( @router.post(
@@ -13,10 +16,9 @@ router = Router(tags=["users"])
response={ response={
201: TokenSchema, 201: TokenSchema,
400: BadRequestError, 400: BadRequestError,
} },
) )
def sign_up(data: RegisterSchema): def sign_up(data: RegisterSchema): ...
...
@router.post( @router.post(
@@ -25,10 +27,9 @@ def sign_up(data: RegisterSchema):
200: TokenSchema, 200: TokenSchema,
400: BadRequestError, 400: BadRequestError,
403: ForbiddenError, 403: ForbiddenError,
} },
) )
def sign_in(data: LoginSchema): def sign_in(data: LoginSchema): ...
...
@router.get( @router.get(
@@ -37,7 +38,6 @@ def sign_in(data: LoginSchema):
200: UserSchema, 200: UserSchema,
400: BadRequestError, 400: BadRequestError,
404: NotFoundError, 404: NotFoundError,
} },
) )
def get_user(user_id: str): def get_user(user_id: str): ...
...
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class CompetitionsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.competition"
label = "competition"
@@ -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': 'соревнования',
},
),
]
@@ -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 = "соревнования"
@@ -1,7 +0,0 @@
from django.apps import AppConfig
class CompetitionsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.competitions'
label = 'competitions'
@@ -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 = "соревнования"
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.user"
label = "user"
-9
View File
@@ -1,9 +0,0 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'
label = 'users'
+2 -1
View File
@@ -443,7 +443,8 @@ INSTALLED_APPS = [
"minio_storage", "minio_storage",
# Internal apps # Internal apps
"apps.core", "apps.core",
"apps.users", "apps.user",
"apps.competition",
] ]
# GUID # GUID