diff --git a/README.md b/README.md index cdef7b7..a22dac5 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,6 @@ Backlog — сервис «списка хотелок» (backlog) для фил > Добавляя ссылку на просмотр можно легко и быстро выбрать то, что подходит по настроению и перейти к просмотру. Если > ссылка стала недоступна, то можно быстро найти по названию фильм и отредактировать. -## Текущий статус (TODO) - -(Отметки соответствуют состоянию на момент создания README — обновляй чек-лист по мере прогресса.) - -- [X] Поправить чтобы рейтинг был флоатом -- [X] Возможно, добавить ссылку на ресурс где можно посмотреть -- [X] Каждый фильм должен быть подписан кем был добавлен -- [X] Вывод фильмов, которые добавил текущий пользователь -- [X] Описать проект на GitHub -- [X] Написать тесты -- [X] Подготовить проект к выкладке на прод -- [X] Переделать Original Link на IMDB ID -- [X] Настроить логгинг - - -## Задумки на будущее -- [ ] Выпуск на публику: подтверждение email при регистрации -- [ ] Система уведомлений: пользователь может подписаться на уведомление когда выйдет новая серия сериала/фильма, администратор может добавлять эти уведомления -- [ ] Интеграция с API IMDB/KP - ## Технологии ### Backend @@ -50,7 +30,7 @@ Backlog — сервис «списка хотелок» (backlog) для фил Контейнеризация: Docker (+ docker-compose) -## Установка (локально) — с uv (UV package manager) +## Локальная развёртка 1. Клонируйте репозиторий: @@ -65,8 +45,6 @@ cd backend uv install ``` -(Команда установит зависимости, указанные в конфигурации проекта — `pyproject.toml`.) - 3. Настройте переменные окружения: - Скопируйте шаблон и заполните значения: @@ -77,7 +55,7 @@ cp .env.template .env - Пропишите параметры БД, секреты, настройки SMTP и т.д. -4. Примените миграции (через uv-run, если вы используете uv для запуска команд, иначе вызывайте alembic напрямую): +4. Примените миграции: ```bash alembic upgrade head @@ -97,16 +75,12 @@ npm install npm run dev ``` -## Быстрый запуск с Docker - -Пример (при наличии docker-compose.yml и Dockerfile): +## Запуск Docker контейнеров ```bash -docker compose -f docker-compose.dev.yaml up --build -d +docker compose up --build -d ``` -После сборки приложение доступно по адресу: http://localhost:8000 - ## API (общая информация) - Базовый путь: `/api` (может быть `/`) @@ -140,12 +114,14 @@ curl -X POST "http://localhost:8000/api/movies" \ - Защищённые эндпоинты требуют заголовок: `Authorization: Bearer ` - ## Брокер сообщений + - Запуск: + ```bash taskiq worker backlog_app.taskiq_broker:broker --fs-discover -tp "**/tasks" --no-configure-logging ``` + ## Тестирование - Запуск тестов: diff --git a/backend/.dockerignore b/backend/.dockerignore index 0a53e44..373898f 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -72,6 +72,7 @@ target/ # Virtual environment .env +config.local.yaml .venv/ venv/ diff --git a/backend/.env.template b/backend/.env.template new file mode 100644 index 0000000..7220d36 --- /dev/null +++ b/backend/.env.template @@ -0,0 +1,16 @@ +BACKLOG__DB__CONNECTION__HOST=pg +BACKLOG__DB__CONNECTION__PORT=5432 +BACKLOG__DB__CONNECTION__USERNAME=postgres +BACKLOG__DB__CONNECTION__PASSWORD=postgres +BACKLOG__DB__CONNECTION__NAME=backlog + +BACKLOG__TASKIQ__RBMQ_HOST=rabbitmq +BACKLOG__TASKIQ__RBMQ_PORT=5672 +BACKLOG__TASKIQ__RBMQ_USERNAME=guest +BACKLOG__TASKIQ__RBMQ_PASSWORD=guest + +BACKLOG__ACCESS_TOKEN_DB__RESET_PASSWORD_TOKEN_SECRET= +BACKLOG__ACCESS_TOKEN_DB__VERIFICATION_TOKEN_SECRET= + +BACKLOG__SUPERUSER__EMAIL=admin@site.com +BACKLOG__SUPERUSER__PASSWORD=admin diff --git a/backend/Dockerfile b/backend/Dockerfile index b3fe3e2..48d78d6 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.13-slim +FROM python:3.13 ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 @@ -9,9 +9,10 @@ WORKDIR /app COPY ./pyproject.toml ./ COPY ./uv.lock ./ +COPY config.default.yaml ./ COPY ./backlog_app ./backlog_app -RUN uv sync +RUN uv sync --locked --no-install-project --no-dev EXPOSE 8000 CMD ["sh", "-c", "cd backlog_app && uv run alembic upgrade head && cd .. && uv run fastapi run backlog_app/main.py --port 8000"] diff --git a/backend/backlog_app/config.py b/backend/backlog_app/config.py index 1cbf46a..9b8c5f3 100644 --- a/backend/backlog_app/config.py +++ b/backend/backlog_app/config.py @@ -11,6 +11,7 @@ ) BASE_DIR = Path(__file__).resolve().parent +ROOT_DIR = BASE_DIR.parent class LoggingConfig(BaseModel): @@ -56,21 +57,28 @@ class DataBase(BaseModel): class TaskiqConfig(BaseModel): - url: AmqpDsn = "amqp://guest:guest@localhost:5672//" + rbmq_host: str + rbmq_port: int + rbmq_username: str + rbmq_password: str + + @property + def url(self) -> str: + return f"amqp://{self.rbmq_username}:{self.rbmq_password}@{self.rbmq_host}:{self.rbmq_port}//" class Settings(BaseSettings): model_config = SettingsConfigDict( case_sensitive=False, env_file=( - BASE_DIR / ".env.template", - BASE_DIR / ".env", + ROOT_DIR / ".env.template", + ROOT_DIR / ".env", ), env_prefix="BACKLOG__", env_nested_delimiter="__", yaml_file=( - BASE_DIR / "config.default.yaml", - BASE_DIR / "config.local.yaml", + ROOT_DIR / "config.default.yaml", + ROOT_DIR / "config.local.yaml", ), yaml_config_section="backlog", ) @@ -106,7 +114,7 @@ def settings_customise_sources( ) db: DataBase - taskiq: TaskiqConfig = TaskiqConfig() + taskiq: TaskiqConfig logging: LoggingConfig = LoggingConfig() access_token_db: AccessToken superuser: SuperUser diff --git a/backend/backlog_app/config.default.yaml b/backend/config.default.yaml similarity index 100% rename from backend/backlog_app/config.default.yaml rename to backend/config.default.yaml diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 5234588..9b1ad12 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ "taskiq>=0.12.1", "taskiq-aio-pika>=0.5.0", "taskiq-fastapi>=0.4.0", - "taskiq-redis>=1.2.1", "uvicorn>=0.40.0", ] diff --git a/backend/uv.lock b/backend/uv.lock index a023510..f67f6cf 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -283,7 +283,6 @@ dependencies = [ { name = "taskiq" }, { name = "taskiq-aio-pika" }, { name = "taskiq-fastapi" }, - { name = "taskiq-redis" }, { name = "uvicorn" }, ] @@ -312,7 +311,6 @@ requires-dist = [ { name = "taskiq", specifier = ">=0.12.1" }, { name = "taskiq-aio-pika", specifier = ">=0.5.0" }, { name = "taskiq-fastapi", specifier = ">=0.4.0" }, - { name = "taskiq-redis", specifier = ">=1.2.1" }, { name = "uvicorn", specifier = ">=0.40.0" }, ] @@ -1642,15 +1640,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] -[[package]] -name = "redis" -version = "7.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, -] - [[package]] name = "rich" version = "14.3.1" @@ -1858,19 +1847,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/58/11eacc48d615703618f93ff44812409dca94cb465ab5cb173a8a90e079b1/taskiq_fastapi-0.4.0-py3-none-any.whl", hash = "sha256:9e7e57fa337b4fad2641c8761cafc7b54a63bf7bf726423a28fc2b15c55fd0bc", size = 5369, upload-time = "2025-11-29T15:23:00.185Z" }, ] -[[package]] -name = "taskiq-redis" -version = "1.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "redis" }, - { name = "taskiq" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/35/58ec34c74c888235ecea3dba3b0e74a3902a6b70f0cd56fffa0012f03a61/taskiq_redis-1.2.1.tar.gz", hash = "sha256:8a6b7d3187798e00f27fca6d4a9d73efc559ed88e3c3c571647c9163add7af47", size = 14376, upload-time = "2025-12-19T17:36:03.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/1e/d4202cdb98bf2df087dd303df35d0faf2528f985d62aa685cce994584c4f/taskiq_redis-1.2.1-py3-none-any.whl", hash = "sha256:caf91241a23cd77c4304e455a775caa08c5005b48b62fc613b4dd5b44a515488", size = 20570, upload-time = "2025-12-19T17:36:02.142Z" }, -] - [[package]] name = "typer" version = "0.21.1" diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml deleted file mode 100644 index 192bc58..0000000 --- a/docker-compose.dev.yaml +++ /dev/null @@ -1,54 +0,0 @@ -services: - - pg: - image: postgres:18 - env_file: - - .env - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - PGDATA: /var/lib/postgresql/18/docker - ports: - - "5432:5432" - volumes: - - pgdata:/var/lib/postgresql/18/docker - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] - interval: 10s - timeout: 5s - retries: 5 - restart: unless-stopped - - maildev: - image: maildev/maildev - environment: - - TZ=Europe/Moscow - - MAILDEV_WEB_PORT=1080 - - MAILDEV_SMTP_PORT=1025 - ports: - - "8080:1080" - - "1025:1025" - logging: - driver: "json-file" - options: - max-size: "1m" - - rabbitmq: - image: rabbitmq:4.2-management-alpine - hostname: rabbitmq - container_name: rabbitmq - ports: - - "5672:5672" - - "15672:15672" - env_file: - - .env - environment: - RABBITMQ_DEFAULT_USER: ${RABBIT_USER} - RABBITMQ_DEFAULT_PASS: ${RABBIT_PSWD} - volumes: - - rabbitmq-data:/var/lib/rabbitmq - -volumes: - pgdata: - rabbitmq-data: diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml new file mode 100644 index 0000000..7304b54 --- /dev/null +++ b/docker-compose.prod.yaml @@ -0,0 +1,112 @@ +services: + + backend: + &app + build: + context: backend + dockerfile: Dockerfile + env_file: + - ./backend/.env + expose: + - "8000" + depends_on: + pg: + condition: service_healthy + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8000" ] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - app-network + + worker: + <<: *app + command: > + uv run taskiq worker backlog_app.taskiq_broker:broker --no-configure-logging + depends_on: + rabbitmq: + condition: service_started + restart: unless-stopped + networks: + - app-network + + frontend: + container_name: frontend + build: + context: frontend + dockerfile: Dockerfile + args: + VITE_API_URL: /api + expose: + - "80" + depends_on: + backend: + condition: service_healthy + restart: unless-stopped + networks: + - app-network + + pg: + image: postgres:18 + env_file: + - .env + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATA: /var/lib/postgresql/18/docker + expose: + - "5432" + volumes: + - pgdata:/var/lib/postgresql/18/docker + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - app-network + + nginx: + image: nginx:alpine + container_name: nginx + ports: + - "80:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + backend: + condition: service_healthy + frontend: + condition: service_started + restart: unless-stopped + networks: + - app-network + + rabbitmq: + image: rabbitmq:4.2-alpine + hostname: rabbitmq + container_name: rabbitmq + expose: + - "5672" + env_file: + - .env + environment: + RABBITMQ_DEFAULT_USER: ${RABBIT_USER} + RABBITMQ_DEFAULT_PASS: ${RABBIT_PSWD} + volumes: + - rabbitmq-data:/var/lib/rabbitmq + restart: unless-stopped + networks: + - app-network + +volumes: + pgdata: + rabbitmq-data: + +networks: + app-network: + driver: bridge diff --git a/docker-compose.yaml b/docker-compose.yaml index 8ed6684..192bc58 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,37 +1,5 @@ services: - backend: - container_name: fastapi_app - build: - context: backend - dockerfile: Dockerfile - env_file: - - ./backend/backlog_app/.env - expose: - - "8000" - depends_on: - pg: - condition: service_healthy - restart: unless-stopped - networks: - - app-network - - frontend: - container_name: vite_vue_frontend - build: - context: frontend - dockerfile: Dockerfile - args: - VITE_API_URL: /api - expose: - - "80" - depends_on: - backend: - condition: service_started - restart: unless-stopped - networks: - - app-network - pg: image: postgres:18 env_file: @@ -41,49 +9,46 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} PGDATA: /var/lib/postgresql/18/docker - expose: - - "5432" + ports: + - "5432:5432" volumes: - pgdata:/var/lib/postgresql/18/docker healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped - networks: - - app-network - nginx: - image: nginx:alpine - container_name: nginx_proxy + maildev: + image: maildev/maildev + environment: + - TZ=Europe/Moscow + - MAILDEV_WEB_PORT=1080 + - MAILDEV_SMTP_PORT=1025 ports: - - "80:80" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - depends_on: - - backend - - frontend - restart: unless-stopped - networks: - - app-network + - "8080:1080" + - "1025:1025" + logging: + driver: "json-file" + options: + max-size: "1m" - dockhand: - image: fnsys/dockhand:latest - container_name: dockhand - restart: unless-stopped - expose: - - "3000" + rabbitmq: + image: rabbitmq:4.2-management-alpine + hostname: rabbitmq + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + env_file: + - .env + environment: + RABBITMQ_DEFAULT_USER: ${RABBIT_USER} + RABBITMQ_DEFAULT_PASS: ${RABBIT_PSWD} volumes: - - /var/run/docker.sock:/var/run/docker.sock - - dockhand_data:/app/data - networks: - - app-network + - rabbitmq-data:/var/lib/rabbitmq volumes: pgdata: - dockhand_data: - -networks: - app-network: - driver: bridge + rabbitmq-data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6877a3d..a3d87ee 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -8,9 +8,6 @@ RUN npm ci COPY . . -ARG VITE_API_URL -ENV VITE_API_URL=$VITE_API_URL - RUN npm run build # Stage 2: Production diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 1bba7fd..6ca84e9 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -11,10 +11,6 @@ http { server frontend:80; } - upstream dockhand { - server dockhand:3000; - } - server { listen 80; server_name _; @@ -39,15 +35,5 @@ http { } - location /dockhand/ { - proxy_pass http://dockhand; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; -} } }