v2
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.sqlite3
|
||||||
|
.env
|
||||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Run migrations, load data and start server
|
||||||
|
CMD ["sh", "-c", "python manage.py migrate && python manage.py load_static_data && python manage.py runserver 0.0.0.0:8000"]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
25
api/migrations/0004_buildofday.py
Normal file
25
api/migrations/0004_buildofday.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-16 18:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0003_aspect'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BuildOfDay',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date', models.DateField(unique=True)),
|
||||||
|
('skill_build', models.JSONField(default=dict)),
|
||||||
|
('aspect', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
|
('hero', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.hero')),
|
||||||
|
('items', models.ManyToManyField(to='api.item')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
api/migrations/__pycache__/0004_buildofday.cpython-312.pyc
Normal file
BIN
api/migrations/__pycache__/0004_buildofday.cpython-312.pyc
Normal file
Binary file not shown.
@@ -35,3 +35,14 @@ class Aspect(models.Model):
|
|||||||
|
|
||||||
def __str__(self) -> str: # pragma: no cover - convenience only
|
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||||
return f"{self.hero.name} - {self.name}"
|
return f"{self.hero.name} - {self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class BuildOfDay(models.Model):
|
||||||
|
date = models.DateField(unique=True)
|
||||||
|
hero = models.ForeignKey(Hero, on_delete=models.CASCADE)
|
||||||
|
items = models.ManyToManyField(Item)
|
||||||
|
skill_build = models.JSONField(default=dict)
|
||||||
|
aspect = models.CharField(max_length=200, blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||||
|
return f"Build of {self.date} - {self.hero.name}"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import HeroesListView, RandomizeBuildView
|
from .views import BuildOfDayView, HeroesListView, RandomizeBuildView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("randomize", RandomizeBuildView.as_view(), name="randomize-build"),
|
path("randomize", RandomizeBuildView.as_view(), name="randomize-build"),
|
||||||
path("heroes", HeroesListView.as_view(), name="heroes-list"),
|
path("heroes", HeroesListView.as_view(), name="heroes-list"),
|
||||||
|
path("build-of-day", BuildOfDayView.as_view(), name="build-of-day"),
|
||||||
]
|
]
|
||||||
|
|||||||
63
api/views.py
63
api/views.py
@@ -1,8 +1,10 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from api.models import Aspect, Hero, Item
|
from api.models import Aspect, BuildOfDay, Hero, Item
|
||||||
from api.serializers import (
|
from api.serializers import (
|
||||||
HeroSerializer,
|
HeroSerializer,
|
||||||
ItemSerializer,
|
ItemSerializer,
|
||||||
@@ -101,3 +103,62 @@ class RandomizeBuildView(APIView):
|
|||||||
else:
|
else:
|
||||||
messages.append(f"{field}: {error}")
|
messages.append(f"{field}: {error}")
|
||||||
return messages[0] if messages else "Invalid request data."
|
return messages[0] if messages else "Invalid request data."
|
||||||
|
|
||||||
|
|
||||||
|
class BuildOfDayView(APIView):
|
||||||
|
"""
|
||||||
|
GET: Return the build of the day. Generates one if it doesn't exist for today.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ITEMS_COUNT = 6
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
today = date.today()
|
||||||
|
|
||||||
|
build = BuildOfDay.objects.filter(date=today).first()
|
||||||
|
if not build:
|
||||||
|
build = self._generate_build_of_day(today)
|
||||||
|
if build is None:
|
||||||
|
return Response(
|
||||||
|
{"message": "Unable to generate build. Load data first."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
"date": build.date.isoformat(),
|
||||||
|
"hero": HeroSerializer(build.hero).data,
|
||||||
|
"items": ItemSerializer(build.items.all(), many=True).data,
|
||||||
|
"skillBuild": build.skill_build,
|
||||||
|
}
|
||||||
|
|
||||||
|
if build.aspect:
|
||||||
|
response_data["aspect"] = build.aspect
|
||||||
|
|
||||||
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def _generate_build_of_day(self, today: date) -> BuildOfDay | None:
|
||||||
|
"""Generate and save a new build of the day."""
|
||||||
|
hero_count = Hero.objects.count()
|
||||||
|
item_count = Item.objects.count()
|
||||||
|
|
||||||
|
if hero_count == 0 or item_count < self.ITEMS_COUNT:
|
||||||
|
return None
|
||||||
|
|
||||||
|
hero_obj = Hero.objects.order_by("?").first()
|
||||||
|
item_objs = list(Item.objects.order_by("?")[: self.ITEMS_COUNT])
|
||||||
|
|
||||||
|
aspect_name = None
|
||||||
|
hero_aspects = Aspect.objects.filter(hero=hero_obj)
|
||||||
|
if hero_aspects.exists():
|
||||||
|
aspect_obj = hero_aspects.order_by("?").first()
|
||||||
|
aspect_name = aspect_obj.name
|
||||||
|
|
||||||
|
build = BuildOfDay.objects.create(
|
||||||
|
date=today,
|
||||||
|
hero=hero_obj,
|
||||||
|
skill_build=generate_skill_build(),
|
||||||
|
aspect=aspect_name,
|
||||||
|
)
|
||||||
|
build.items.set(item_objs)
|
||||||
|
|
||||||
|
return build
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ SECRET_KEY = 'django-insecure-a#7v1um&b88$k)2nm7hvxe__o2c(gz=t5%)1)*oaij6u%+i((=
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@@ -76,10 +76,15 @@ WSGI_APPLICATION = 'config.wsgi.application'
|
|||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
DATA_DIR = BASE_DIR / 'data'
|
||||||
|
DATA_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
'NAME': DATA_DIR / 'db.sqlite3',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +126,4 @@ USE_TZ = True
|
|||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
# CORS settings
|
# CORS settings
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
"http://localhost:5173",
|
|
||||||
"http://127.0.0.1:5173",
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|||||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
django>=6.0,<7.0
|
||||||
|
djangorestframework>=3.16,<4.0
|
||||||
|
django-cors-headers>=4.9,<5.0
|
||||||
Reference in New Issue
Block a user