Add production Docker setup and update backend/frontend configs

Introduces .dockerignore, production Dockerfile and nginx config for frontend, and refactors docker-compose.yml for multi-service deployment. Updates backend and frontend code to support public API tagging, improves refund handling, adds test email endpoint, and migrates Orval config to TypeScript. Removes unused frontend Dockerfile and updates dependencies for React Query and Orval.
This commit is contained in:
David Bruno Vontor
2025-12-05 18:22:35 +01:00
parent d94ad93222
commit 4cbebff43b
20 changed files with 3752 additions and 144 deletions

View File

@@ -8,7 +8,8 @@ RUN apt update && apt install -y \
pango1.0-tools \
libpango-1.0-0 \
libgobject-2.0-0 \
ffmpeg
ffmpeg \
ca-certificates
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

View File

@@ -38,7 +38,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView
# Custom Token obtaining view
@extend_schema(
tags=["Authentication"],
tags=["Authentication", "public"],
summary="Obtain JWT access and refresh tokens (cookie-based)",
description="Authenticate user and obtain JWT access and refresh tokens. You can use either email or username.",
request=CustomTokenObtainPairSerializer,
@@ -107,7 +107,7 @@ class CookieTokenObtainPairView(TokenObtainPairView):
return super().validate(attrs)
@extend_schema(
tags=["Authentication"],
tags=["Authentication", "public"],
summary="Refresh JWT token using cookie",
description="Refresh JWT access and refresh tokens using the refresh token stored in cookie.",
responses={
@@ -163,7 +163,7 @@ class CookieTokenRefreshView(APIView):
#---------------------------------------------LOGOUT------------------------------------------------
@extend_schema(
tags=["Authentication"],
tags=["Authentication", "public"],
summary="Logout user (delete access and refresh token cookies)",
description="Logs out the user by deleting access and refresh token cookies.",
responses={
@@ -186,6 +186,7 @@ class LogoutView(APIView):
@extend_schema(
tags=["User"],
get=extend_schema(tags=["public"]),
summary="List, retrieve, update, and delete users.",
description="Displays all users with filtering and ordering options. Requires authentication and appropriate role.",
responses={

View File

@@ -10,11 +10,13 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from weasyprint import HTML
import os
from backend.commerce.tasks import notify_order_sended
from configuration.models import ShopConfiguration
from thirdparty.zasilkovna.models import ZasilkovnaPacket
from thirdparty.stripe.models import StripeModel
from .tasks import notify_refund_accepted, notify_order_sended
#FIXME: přidat soft delete pro všchny modely !!!!
class Category(models.Model):
@@ -477,6 +479,7 @@ class Refund(models.Model):
WRONG_ITEM = "wrong_item", "cz#Špatná položka"
OTHER = "other", "cz#Jiný důvod"
reason_choice = models.CharField(max_length=30, choices=Reason.choices)
reason_text = models.TextField(blank=True)
verified = models.BooleanField(default=False)
@@ -507,12 +510,26 @@ class Refund(models.Model):
# super().save(*args, **kwargs)
def save(self, *args, **kwargs):
# Automaticky aktualizovat stav objednávky na "vráceno"
if self.pk is None:
if self.order.status != Order.Status.REFUNDING:
self.order.save(update_fields=["status", "updated_at"])
super().save(*args, **kwargs)
def refund_completed(self):
# Aktualizovat stav objednávky na "vráceno"
self.order.payment.stripe.refund()
if self.order.payment and self.order.payment.payment_method == Payment.PAYMENT.STRIPE:
self.order.payment.stripe.refund() # Vrácení pěnez přes stripe
self.order.status = Order.Status.REFUNDED
self.order.save(update_fields=["status", "updated_at"])
notify_refund_accepted.delay(order=self.order, user=self.order.user)
def generate_refund_pdf_for_customer(self):
"""Vygeneruje PDF formulář k vrácení zboží pro zákazníka.

View File

@@ -1,10 +1,13 @@
from .models import Order
from account.models import User
from account.tasks import send_email_with_context
from celery import shared_task
from django.apps import apps
from django.utils import timezone
Order = apps.get_model('commerce', 'Order')
def delete_expired_orders():
expired_orders = Order.objects.filter(status=Order.STATUS_CHOICES.CANCELLED, created_at__lt=timezone.now() - timezone.timedelta(hours=24))
count = expired_orders.count()

View File

View File

@@ -1,3 +1,5 @@
from django.shortcuts import render
# Create your views here.
#TODO: dej public tag pro view, aby to mohl react číst pomocí public axiosu

View File

@@ -48,7 +48,7 @@ class Downloader(APIView):
authentication_classes = []
@extend_schema(
tags=["downloader"],
tags=["downloader", "public"],
summary="Get video info from URL",
parameters=[
inline_serializer(
@@ -129,7 +129,7 @@ class Downloader(APIView):
@extend_schema(
tags=["downloader"],
tags=["downloader", "public"],
summary="Download video from URL",
request=inline_serializer(
name="DownloadRequest",
@@ -277,7 +277,7 @@ class DownloaderStats(APIView):
authentication_classes = []
permission_classes = [AllowAny]
@extend_schema(
tags=["downloader"],
tags=["downloader", "public"],
summary="Get aggregated downloader statistics",
responses={200: DownloaderStatsSerializer},
)

View File

@@ -44,9 +44,10 @@ class ZasilkovnaPacket(models.Model):
state = models.CharField(max_length=20, choices=STATE.choices, default=STATE.PENDING)
# ------- API -------
class BUISSNESS_ADDRESS_ID(models.IntegerChoices):
SHOP = 1, "address of buissnes"
addressId = models.IntegerField(help_text="ID adresy, v API rozhraní", choices=BUISSNESS_ADDRESS_ID.choices, default=BUISSNESS_ADDRESS_ID.SHOP)
#TODO: změnit na nastavení adresy eshopu/obchodu z modelu konfigurace
# https://client.packeta.com/cs/senders (admin rozhraní)
addressId = models.IntegerField(help_text="ID adresy, v Widgetu zásilkovny který si vybere uživatel.")
packet_id = models.IntegerField(help_text="Číslo zásilky v Packetě (api)")
barcode = models.CharField(max_length=64, help_text="Čárový kód zásilky v Packetě")

View File

@@ -5,7 +5,11 @@ from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.permissions import AllowAny
from account.tasks import send_email_test_task
@extend_schema(
tags=["public"],
description="Vrátí všechny možné hodnoty pro ChoiceField s podporou vícejazyčných labelů. "
"Umožňuje načíst více modelů a polí najednou.",
parameters=[
@@ -83,3 +87,24 @@ def choices(request):
result[f"{model_name}.{field_name}"] = choices_data
return Response(result)
@extend_schema(
tags=["Testing"],
description="Testovací endpoint pro odeslání testovacího emailu.",
parameters=[
OpenApiParameter(
name="email",
description="Emailová adresa příjemce testovacího emailu.",
required=True,
type=str,
),
],
)
@api_view(["GET"])
def test_email(request):
email = request.query_params.get("email")
send_email_test_task.delay(email)
return Response({"status": "Test task (celery) email sent, await delivery."})