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]:
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 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.users.views import router as users_router
from api.v1.user.views import router as user_router
router = NinjaAPI(
title="project_name API",
@@ -20,7 +21,11 @@ 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.users.models import User
from apps.user.models import User
class TokenSchema(Schema):
@@ -1,11 +1,14 @@
from ninja import Router
from api.v1.users.schemas import LoginSchema, RegisterSchema, TokenSchema, UserSchema
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=["users"])
router = Router(tags=["user"])
@router.post(
@@ -13,10 +16,9 @@ router = Router(tags=["users"])
response={
201: TokenSchema,
400: BadRequestError,
}
},
)
def sign_up(data: RegisterSchema):
...
def sign_up(data: RegisterSchema): ...
@router.post(
@@ -25,10 +27,9 @@ def sign_up(data: RegisterSchema):
200: TokenSchema,
400: BadRequestError,
403: ForbiddenError,
}
},
)
def sign_in(data: LoginSchema):
...
def sign_in(data: LoginSchema): ...
@router.get(
@@ -37,7 +38,6 @@ def sign_in(data: LoginSchema):
200: UserSchema,
400: BadRequestError,
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",
# Internal apps
"apps.core",
"apps.users",
"apps.user",
"apps.competition",
]
# GUID