mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-23 05:07:10 +00:00
Merge branch 'master' of https://gitlab.prodcontest.ru/team-15/project
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import datetime
|
||||
from typing import Optional, Any
|
||||
from typing import Any
|
||||
|
||||
import jwt
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
from ninja.security import HttpBearer
|
||||
|
||||
from apps.users.models import User
|
||||
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")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from ninja import ModelSchema, Schema
|
||||
|
||||
from apps.competition.models import Competition, State
|
||||
|
||||
|
||||
class CompetitionOut(ModelSchema):
|
||||
id: UUID
|
||||
|
||||
class Meta:
|
||||
model = Competition
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class StateOut(ModelSchema):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = ("state",)
|
||||
|
||||
|
||||
class StateIn(Schema):
|
||||
state: Literal["started", "not_started", "finished"]
|
||||
|
||||
|
||||
class CompetitionListInstanceOut(ModelSchema):
|
||||
id: UUID
|
||||
is_participating: bool
|
||||
completed: bool
|
||||
|
||||
@staticmethod
|
||||
def resolve_is_participating(self, context):
|
||||
user = context["request"].auth
|
||||
return self.participants.filter(id=user.id).exists()
|
||||
|
||||
@staticmethod
|
||||
def resolve_completed(self, context):
|
||||
user = context["request"].auth
|
||||
return State.objects.filter(
|
||||
competition=self, user=user, state="finished"
|
||||
).exists()
|
||||
|
||||
class Meta:
|
||||
model = Competition
|
||||
fields = (
|
||||
"id",
|
||||
"title",
|
||||
"description",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"image_url",
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
from http import HTTPStatus as status
|
||||
from uuid import UUID
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja import Router
|
||||
|
||||
import api.v1.schemas as global_schemas
|
||||
from api.v1.competition import schemas
|
||||
from apps.competition.models import Competition, State
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
def get_competition(
|
||||
request: HttpRequest, competition_id: UUID
|
||||
) -> tuple[status, schemas.CompetitionOut]:
|
||||
competition = get_object_or_404(Competition, id=competition_id)
|
||||
return status.OK, competition
|
||||
|
||||
|
||||
@router.get(
|
||||
"competitions",
|
||||
response={
|
||||
status.OK: list[schemas.CompetitionListInstanceOut],
|
||||
status.BAD_REQUEST: global_schemas.BadRequestError,
|
||||
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
|
||||
},
|
||||
)
|
||||
def list_competitions(
|
||||
request: HttpRequest, is_participating: bool
|
||||
) -> tuple[status, list[schemas.CompetitionListInstanceOut]]:
|
||||
user = request.auth
|
||||
if is_participating:
|
||||
competitions = Competition.objects.filter(participants=user)
|
||||
else:
|
||||
competitions = Competition.objects.exclude(participants=user)
|
||||
return status.OK, competitions
|
||||
|
||||
|
||||
@router.post(
|
||||
"competitions/{competition_id}/state",
|
||||
response={
|
||||
status.OK: schemas.StateOut,
|
||||
status.BAD_REQUEST: global_schemas.BadRequestError,
|
||||
status.UNAUTHORIZED: global_schemas.UnauthorizedError,
|
||||
},
|
||||
)
|
||||
def change_competition_state(
|
||||
request: HttpRequest,
|
||||
competition_id: UUID,
|
||||
state: schemas.StateIn,
|
||||
) -> tuple[status, schemas.StateOut]:
|
||||
user = request.auth
|
||||
competition = get_object_or_404(Competition, id=competition_id)
|
||||
|
||||
state_obj, _ = State.objects.update_or_create(
|
||||
user=user, competition=competition, state=state.state
|
||||
)
|
||||
return status.OK, schemas.StateOut.from_orm(state_obj)
|
||||
@@ -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()
|
||||
|
||||
@@ -3,14 +3,17 @@ 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="project_name API",
|
||||
title="DataRush API",
|
||||
version="1",
|
||||
description="API docs for project_name",
|
||||
description="API docs for DataRush",
|
||||
openapi_url="/docs/openapi.json",
|
||||
auth=BearerAuth(),
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +23,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,20 +1,29 @@
|
||||
from http import HTTPStatus as status
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja import Router
|
||||
from ninja.errors import AuthenticationError
|
||||
|
||||
from api.v1.users.schemas import LoginSchema, RegisterSchema, TokenSchema, UserSchema
|
||||
from api.v1.auth import BearerAuth
|
||||
from api.v1.schemas import BadRequestError, ForbiddenError, NotFoundError
|
||||
from apps.users.models import User
|
||||
from api.v1.user.schemas import (
|
||||
LoginSchema,
|
||||
RegisterSchema,
|
||||
TokenSchema,
|
||||
UserSchema,
|
||||
)
|
||||
from apps.user.models import User
|
||||
|
||||
router = Router(tags=["users"])
|
||||
router = Router(tags=["user"])
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/sign-up",
|
||||
response={
|
||||
201: TokenSchema,
|
||||
400: BadRequestError,
|
||||
}
|
||||
status.CREATED: TokenSchema,
|
||||
status.BAD_REQUEST: BadRequestError,
|
||||
},
|
||||
auth=None,
|
||||
)
|
||||
def sign_up(request, data: RegisterSchema):
|
||||
user = User(**data.dict())
|
||||
@@ -22,16 +31,17 @@ def sign_up(request, data: RegisterSchema):
|
||||
user.save()
|
||||
|
||||
token = BearerAuth.generate_jwt(user)
|
||||
return 201, TokenSchema(token=token)
|
||||
return status.CREATED, TokenSchema(token=token)
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/sign-in",
|
||||
response={
|
||||
200: TokenSchema,
|
||||
400: BadRequestError,
|
||||
401: ForbiddenError,
|
||||
}
|
||||
status.OK: TokenSchema,
|
||||
status.BAD_REQUEST: BadRequestError,
|
||||
status.UNAUTHORIZED: ForbiddenError,
|
||||
},
|
||||
auth=None,
|
||||
)
|
||||
def sign_in(request, data: LoginSchema):
|
||||
user = User.objects.filter(email=data.email).first()
|
||||
@@ -41,16 +51,17 @@ def sign_in(request, data: LoginSchema):
|
||||
raise AuthenticationError
|
||||
|
||||
token = BearerAuth.generate_jwt(user)
|
||||
return 200, TokenSchema(token=token)
|
||||
return status.OK, TokenSchema(token=token)
|
||||
|
||||
|
||||
@router.get(
|
||||
path="/user/{user_id}",
|
||||
response={
|
||||
200: UserSchema,
|
||||
400: BadRequestError,
|
||||
404: NotFoundError,
|
||||
}
|
||||
status.OK: UserSchema,
|
||||
status.BAD_REQUEST: BadRequestError,
|
||||
status.NOT_FOUND: NotFoundError,
|
||||
},
|
||||
)
|
||||
def get_user(request, user_id: str):
|
||||
...
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
return status.OK, user
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CompetitionsConfig(AppConfig):
|
||||
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,19 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-28 22:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0001_initial'),
|
||||
('user', '0002_user_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='competition',
|
||||
name='participants',
|
||||
field=models.ManyToManyField(related_name='participants', to='user.user'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-28 23:26
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('competition', '0002_competition_participants'),
|
||||
('user', '0003_alter_user_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='State',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('state', models.CharField(choices=[('not_started', 'Not Started'), ('started', 'Started'), ('finished', 'Finished')], max_length=11)),
|
||||
('competition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='competition.competition')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.user')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
from django.db import models
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
from apps.user.models import User
|
||||
|
||||
|
||||
class Competition(BaseModel):
|
||||
class CompetitionType(models.TextChoices):
|
||||
SOLO = "solo"
|
||||
|
||||
class CompetitionParticipationType(models.TextChoices):
|
||||
EDU = "edu"
|
||||
COMPETITIVE = "competitive"
|
||||
|
||||
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="Тип соревнования",
|
||||
)
|
||||
participants = models.ManyToManyField(User, related_name="participants")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "соревнование"
|
||||
verbose_name_plural = "соревнования"
|
||||
|
||||
|
||||
class State(BaseModel):
|
||||
class StateChoices(models.TextChoices):
|
||||
NOT_STARTED = "not_started"
|
||||
STARTED = "started"
|
||||
FINISHED = "finished"
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
|
||||
state = models.CharField(choices=StateChoices.choices, max_length=11)
|
||||
@@ -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 = "соревнования"
|
||||
@@ -1,13 +1,6 @@
|
||||
import contextlib
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
name = "apps.core"
|
||||
label = "core"
|
||||
|
||||
def ready(self) -> None:
|
||||
with contextlib.suppress(Exception):
|
||||
cache.add("current_date", 0, timeout=None)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CompetitionsConfig(AppConfig):
|
||||
name = "apps.task"
|
||||
label = "task"
|
||||
@@ -0,0 +1,34 @@
|
||||
from uuid import uuid4
|
||||
|
||||
from competition.models import Competition
|
||||
from core.models import BaseModel
|
||||
from django.db import models
|
||||
|
||||
from apps.task.validators import ContestTaskCriteriesValidator
|
||||
|
||||
|
||||
class CompetitionTask(BaseModel):
|
||||
class CompetitionTaskType(models.TextChoices):
|
||||
INPUT = "input"
|
||||
CHECKER = "checker"
|
||||
REVIEW = "review"
|
||||
|
||||
def answer_file_upload_to(instance, filename) -> str:
|
||||
return f"/tasks/{instance.id}/answer/{uuid4}"
|
||||
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE)
|
||||
title = models.TextField(verbose_name="заголовок", max_length=50)
|
||||
description = models.TextField(verbose_name="описание", max_length=300)
|
||||
type = models.CharField(choices=CompetitionTaskType)
|
||||
|
||||
# only when "input" or "checker" type
|
||||
correct_answer_file = models.FileField(upload_to=answer_file_upload_to)
|
||||
|
||||
# only when "checker" type
|
||||
answer_file_path = models.TextField()
|
||||
|
||||
# only when "review" type
|
||||
criteries = models.JSONField(blank=True, null=True)
|
||||
|
||||
def clean(self):
|
||||
ContestTaskCriteriesValidator()(self)
|
||||
@@ -0,0 +1,24 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from pydantic import BaseModel
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
|
||||
|
||||
class Criteria(BaseModel):
|
||||
name: str
|
||||
slug: str
|
||||
max_value: int
|
||||
min_value: int
|
||||
|
||||
|
||||
class ContestTaskCriteriesValidator:
|
||||
def __call__(self, instance):
|
||||
if instance.criterties and not isinstance(instance.criterties, list):
|
||||
err = "criteries must be a valid dictionary"
|
||||
raise ValidationError(err)
|
||||
|
||||
try:
|
||||
for criteria in instance.criterties:
|
||||
Criteria(**criteria)
|
||||
except PydanticValidationError:
|
||||
err = "invalid criteries data"
|
||||
raise ValidationError(err)
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.user"
|
||||
label = "user"
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-28 22:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-28 22:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0002_user_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('student', 'Student'), ('metodist', 'Metodist')], default='student', max_length=10),
|
||||
),
|
||||
]
|
||||
@@ -3,12 +3,21 @@ from django.db import models
|
||||
from apps.core.models import BaseModel
|
||||
|
||||
|
||||
class UserRole(models.Choices):
|
||||
STUDENT = "student"
|
||||
METODIST = "metodist"
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
email = models.EmailField(unique=True, verbose_name="Почта")
|
||||
username = models.SlugField(unique=True, verbose_name="Юзернейм")
|
||||
password = models.TextField(verbose_name="Пароль")
|
||||
|
||||
def __str__(self):
|
||||
status = models.CharField(
|
||||
max_length=10, choices=UserRole, default="student"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.username
|
||||
|
||||
class Meta:
|
||||
@@ -0,0 +1,32 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
from apps.user.models import User
|
||||
|
||||
|
||||
class TestSignUp(TestCase):
|
||||
def test_correct_signup(self):
|
||||
user = User(
|
||||
email="123123@timka.su",
|
||||
password="1321312",
|
||||
username="123123",
|
||||
)
|
||||
user.full_clean()
|
||||
user.save()
|
||||
|
||||
def test_incorrect_mail(self):
|
||||
user = User(
|
||||
email="123123",
|
||||
password="1321312",
|
||||
username="123123123",
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
user.full_clean()
|
||||
|
||||
def test_missing_params(self):
|
||||
user = User(
|
||||
password="123123",
|
||||
username="132131232131"
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
user.full_clean()
|
||||
@@ -1,9 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.users'
|
||||
label = 'users'
|
||||
|
||||
|
||||
@@ -443,7 +443,8 @@ INSTALLED_APPS = [
|
||||
"minio_storage",
|
||||
# Internal apps
|
||||
"apps.core",
|
||||
"apps.users",
|
||||
"apps.user",
|
||||
"apps.competition",
|
||||
]
|
||||
|
||||
# GUID
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Stage 1: Build the Vite app using Bun
|
||||
FROM oven/bun:latest AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN bun run build
|
||||
|
||||
# Stage 2: Serve the built files with Nginx
|
||||
FROM nginx:latest
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -5,8 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . && tsc -b",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user