From 0a35951c62652434e49bca3255932d58f9ffa8b7 Mon Sep 17 00:00:00 2001 From: ITQ Date: Fri, 7 Mar 2025 19:32:09 +0300 Subject: [PATCH] chore: restructured project --- .gitlab-ci.yml | 52 --- README.md | 313 +++++++++++++++++- {solution/assets => assets}/gifs/ad_image.gif | Bin .../gifs/ad_text-generation.gif | Bin {solution/assets => assets}/gifs/grafana.gif | Bin .../assets => assets}/gifs/moderation.gif | Bin {solution/assets => assets}/gifs/telegram.gif | Bin .../images/backend-coverage.png | Bin .../assets => assets}/images/er-diagram.png | Bin .../assets => assets}/images/healthcheck.png | Bin .../assets => assets}/images/pgadmin.png | Bin solution/compose.yaml => compose.yaml | 0 .../.gitignore | 0 .../backend/.env.template | 0 .../grafana/grafana.ini | 0 .../dashboards/AdNova/statistics.json | 0 .../provisioning/dashboards/providers.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../provisioning/plugins/plugings.yaml | 0 .../grafana/scripts/entrypoint.sh | 0 .../minio/.env.template | 0 .../pgadmin/.env.template | 0 .../pgadmin/servers.json | 0 .../postgres/.env.template | 0 .../postgres/postgresql.conf | 0 .../redis/.env.template | 0 .../redis/redis.conf | 0 .../telegram_bot/.env.template | 0 .../backend/.dockerignore | 0 .../backend/.env.template | 0 .../services => services}/backend/.gitignore | 0 .../services => services}/backend/Dockerfile | 0 .../backend/Dockerfile.staticfiles | 0 .../services => services}/backend/README.md | 0 .../backend/api/__init__.py | 0 .../services => services}/backend/api/urls.py | 0 .../backend/api/v1/__init__.py | 0 .../backend/api/v1/ads/__init__.py | 0 .../backend/api/v1/ads/schemas.py | 0 .../backend/api/v1/ads/views.py | 0 .../backend/api/v1/advertisers/__init__.py | 0 .../backend/api/v1/advertisers/schemas.py | 0 .../backend/api/v1/advertisers/tests.py | 0 .../backend/api/v1/advertisers/views.py | 0 .../backend/api/v1/campaigns/__init__.py | 0 .../backend/api/v1/campaigns/schemas.py | 0 .../backend/api/v1/campaigns/utils.py | 0 .../backend/api/v1/campaigns/views.py | 0 .../backend/api/v1/clients/__init__.py | 0 .../backend/api/v1/clients/schemas.py | 0 .../backend/api/v1/clients/tests.py | 0 .../backend/api/v1/clients/views.py | 0 .../backend/api/v1/generate/__init__.py | 0 .../backend/api/v1/generate/schemas.py | 0 .../backend/api/v1/generate/views.py | 0 .../backend/api/v1/handlers.py | 0 .../backend/api/v1/report/__init__.py | 0 .../backend/api/v1/report/schemas.py | 0 .../backend/api/v1/report/views.py | 0 .../backend/api/v1/router.py | 0 .../backend/api/v1/schemas.py | 0 .../backend/api/v1/stats/__init__.py | 0 .../backend/api/v1/stats/schemas.py | 0 .../backend/api/v1/stats/tests.py | 0 .../backend/api/v1/stats/views.py | 0 .../backend/api/v1/time/__init__.py | 0 .../backend/api/v1/time/schemas.py | 0 .../backend/api/v1/time/tests.py | 0 .../backend/api/v1/time/views.py | 0 .../backend/apps/__init__.py | 0 .../backend/apps/advertiser/__init__.py | 0 .../backend/apps/advertiser/admin.py | 0 .../backend/apps/advertiser/apps.py | 0 .../advertiser/migrations/0001_initial.py | 0 .../apps/advertiser/migrations/__init__.py | 0 .../backend/apps/advertiser/models.py | 0 .../backend/apps/advertiser/tests/__init__.py | 0 .../advertiser/tests/test_advertiser_model.py | 0 .../tests/test_advertiser_statistics.py | 0 .../backend/apps/campaign/__init__.py | 0 .../backend/apps/campaign/admin.py | 0 .../backend/apps/campaign/apps.py | 0 .../backend/apps/campaign/forms.py | 0 .../apps/campaign/management/__init__.py | 0 .../campaign/management/commands/__init__.py | 0 .../management/commands/init_cache.py | 0 .../apps/campaign/migrations/0001_initial.py | 0 .../apps/campaign/migrations/__init__.py | 0 .../backend/apps/campaign/models.py | 0 .../backend/apps/campaign/tasks.py | 0 .../backend/apps/campaign/tests/__init__.py | 0 .../tests/test_campaign_click_model.py | 0 .../tests/test_campaign_impression_model.py | 0 .../campaign/tests/test_campaign_model.py | 0 .../tests/test_campaign_report_model.py | 0 .../tests/test_campaign_statistics.py | 0 .../backend/apps/campaign/validators.py | 0 .../backend/apps/client/__init__.py | 0 .../backend/apps/client/admin.py | 0 .../backend/apps/client/apps.py | 0 .../apps/client/migrations/0001_initial.py | 0 .../apps/client/migrations/__init__.py | 0 .../backend/apps/client/models.py | 0 .../backend/apps/client/tests.py | 0 .../backend/apps/core/__init__.py | 0 .../backend/apps/core/apps.py | 0 .../backend/apps/core/models.py | 0 .../backend/apps/mlscore/__init__.py | 0 .../backend/apps/mlscore/admin.py | 0 .../backend/apps/mlscore/apps.py | 0 .../apps/mlscore/migrations/0001_initial.py | 0 .../apps/mlscore/migrations/__init__.py | 0 .../backend/apps/mlscore/models.py | 0 .../backend/apps/mlscore/tests.py | 0 .../backend/config/__init__.py | 0 .../backend/config/asgi.py | 0 .../backend/config/celery.py | 0 .../backend/config/errors.py | 0 .../backend/config/handlers.py | 0 .../backend/config/settings.py | 0 .../backend/config/urls.py | 0 .../backend/config/wsgi.py | 0 .../backend/integrations/__init__.py | 0 .../backend/integrations/yandexai/__init__.py | 0 .../yandexai/generators/__init__.py | 0 .../yandexai/generators/ad_text.py | 0 .../integrations/yandexai/healthcheck.py | 0 .../integrations/yandexai/moderation.py | 0 .../services => services}/backend/manage.py | 0 .../backend/pyproject.toml | 0 .../backend/scripts/check | 0 .../backend/scripts/initdb | 0 .../telegram_bot/.dockerignore | 0 .../telegram_bot/.env.template | 0 .../telegram_bot/.gitignore | 0 .../telegram_bot/Dockerfile | 0 .../telegram_bot/README.md | 0 .../telegram_bot/api/__init__.py | 0 .../telegram_bot/api/client.py | 0 .../telegram_bot/api/errors.py | 0 .../telegram_bot/api/schemas.py | 0 .../telegram_bot/commands/__init__.py | 0 .../telegram_bot/commands/campaigns.py | 0 .../telegram_bot/commands/help.py | 0 .../telegram_bot/commands/logout.py | 0 .../telegram_bot/commands/start.py | 0 .../telegram_bot/commands/stats.py | 0 .../telegram_bot/config.py | 0 .../telegram_bot/dialogs/__init__.py | 0 .../telegram_bot/dialogs/campaigns.py | 0 .../telegram_bot/dialogs/start.py | 0 .../telegram_bot/dialogs/utils.py | 0 .../telegram_bot/filters/__init__.py | 0 .../telegram_bot/filters/auth.py | 0 .../telegram_bot/main.py | 0 .../telegram_bot/middlewares/__init__.py | 0 .../telegram_bot/middlewares/auth.py | 0 .../telegram_bot/middlewares/throttling.py | 0 .../telegram_bot/pyproject.toml | 0 .../telegram_bot/scripts/check | 0 .../telegram_bot/states/__init__.py | 0 .../telegram_bot/states/auth.py | 0 .../telegram_bot/states/campaigns.py | 0 .../telegram_bot/states/start.py | 0 solution/README.md | 313 ------------------ solution/infrastructure/pgadmin/password | 1 - solution/infrastructure/postgres/password | 1 - {solution/tests => tests}/README.md | 0 {solution/tests => tests}/e2e/.env.template | 0 {solution/tests => tests}/e2e/.gitignore | 0 {solution/tests => tests}/e2e/README.md | 0 {solution/tests => tests}/e2e/conftest.py | 0 {solution/tests => tests}/e2e/pyproject.toml | 0 {solution/tests => tests}/e2e/pytest.ini | 0 {solution/tests => tests}/e2e/scripts/check | 0 .../tests => tests}/e2e/tests/__init__.py | 0 .../e2e/tests/test_ad_text_generation.py | 0 .../e2e/tests/test_backend_health.py | 0 178 files changed, 304 insertions(+), 376 deletions(-) delete mode 100644 .gitlab-ci.yml rename {solution/assets => assets}/gifs/ad_image.gif (100%) rename {solution/assets => assets}/gifs/ad_text-generation.gif (100%) rename {solution/assets => assets}/gifs/grafana.gif (100%) rename {solution/assets => assets}/gifs/moderation.gif (100%) rename {solution/assets => assets}/gifs/telegram.gif (100%) rename {solution/assets => assets}/images/backend-coverage.png (100%) rename {solution/assets => assets}/images/er-diagram.png (100%) rename {solution/assets => assets}/images/healthcheck.png (100%) rename {solution/assets => assets}/images/pgadmin.png (100%) rename solution/compose.yaml => compose.yaml (100%) rename {solution/infrastructure => infrastructure}/.gitignore (100%) rename {solution/infrastructure => infrastructure}/backend/.env.template (100%) rename {solution/infrastructure => infrastructure}/grafana/grafana.ini (100%) rename {solution/infrastructure => infrastructure}/grafana/provisioning/dashboards/AdNova/statistics.json (100%) rename {solution/infrastructure => infrastructure}/grafana/provisioning/dashboards/providers.yaml (100%) rename {solution/infrastructure => infrastructure}/grafana/provisioning/datasources/datasources.yaml (100%) rename {solution/infrastructure => infrastructure}/grafana/provisioning/plugins/plugings.yaml (100%) rename {solution/infrastructure => infrastructure}/grafana/scripts/entrypoint.sh (100%) rename {solution/infrastructure => infrastructure}/minio/.env.template (100%) rename {solution/infrastructure => infrastructure}/pgadmin/.env.template (100%) rename {solution/infrastructure => infrastructure}/pgadmin/servers.json (100%) rename {solution/infrastructure => infrastructure}/postgres/.env.template (100%) rename {solution/infrastructure => infrastructure}/postgres/postgresql.conf (100%) rename {solution/infrastructure => infrastructure}/redis/.env.template (100%) rename {solution/infrastructure => infrastructure}/redis/redis.conf (100%) rename {solution/infrastructure => infrastructure}/telegram_bot/.env.template (100%) rename {solution/services => services}/backend/.dockerignore (100%) rename {solution/services => services}/backend/.env.template (100%) rename {solution/services => services}/backend/.gitignore (100%) rename {solution/services => services}/backend/Dockerfile (100%) rename {solution/services => services}/backend/Dockerfile.staticfiles (100%) rename {solution/services => services}/backend/README.md (100%) rename {solution/services => services}/backend/api/__init__.py (100%) rename {solution/services => services}/backend/api/urls.py (100%) rename {solution/services => services}/backend/api/v1/__init__.py (100%) rename {solution/services => services}/backend/api/v1/ads/__init__.py (100%) rename {solution/services => services}/backend/api/v1/ads/schemas.py (100%) rename {solution/services => services}/backend/api/v1/ads/views.py (100%) rename {solution/services => services}/backend/api/v1/advertisers/__init__.py (100%) rename {solution/services => services}/backend/api/v1/advertisers/schemas.py (100%) rename {solution/services => services}/backend/api/v1/advertisers/tests.py (100%) rename {solution/services => services}/backend/api/v1/advertisers/views.py (100%) rename {solution/services => services}/backend/api/v1/campaigns/__init__.py (100%) rename {solution/services => services}/backend/api/v1/campaigns/schemas.py (100%) rename {solution/services => services}/backend/api/v1/campaigns/utils.py (100%) rename {solution/services => services}/backend/api/v1/campaigns/views.py (100%) rename {solution/services => services}/backend/api/v1/clients/__init__.py (100%) rename {solution/services => services}/backend/api/v1/clients/schemas.py (100%) rename {solution/services => services}/backend/api/v1/clients/tests.py (100%) rename {solution/services => services}/backend/api/v1/clients/views.py (100%) rename {solution/services => services}/backend/api/v1/generate/__init__.py (100%) rename {solution/services => services}/backend/api/v1/generate/schemas.py (100%) rename {solution/services => services}/backend/api/v1/generate/views.py (100%) rename {solution/services => services}/backend/api/v1/handlers.py (100%) rename {solution/services => services}/backend/api/v1/report/__init__.py (100%) rename {solution/services => services}/backend/api/v1/report/schemas.py (100%) rename {solution/services => services}/backend/api/v1/report/views.py (100%) rename {solution/services => services}/backend/api/v1/router.py (100%) rename {solution/services => services}/backend/api/v1/schemas.py (100%) rename {solution/services => services}/backend/api/v1/stats/__init__.py (100%) rename {solution/services => services}/backend/api/v1/stats/schemas.py (100%) rename {solution/services => services}/backend/api/v1/stats/tests.py (100%) rename {solution/services => services}/backend/api/v1/stats/views.py (100%) rename {solution/services => services}/backend/api/v1/time/__init__.py (100%) rename {solution/services => services}/backend/api/v1/time/schemas.py (100%) rename {solution/services => services}/backend/api/v1/time/tests.py (100%) rename {solution/services => services}/backend/api/v1/time/views.py (100%) rename {solution/services => services}/backend/apps/__init__.py (100%) rename {solution/services => services}/backend/apps/advertiser/__init__.py (100%) rename {solution/services => services}/backend/apps/advertiser/admin.py (100%) rename {solution/services => services}/backend/apps/advertiser/apps.py (100%) rename {solution/services => services}/backend/apps/advertiser/migrations/0001_initial.py (100%) rename {solution/services => services}/backend/apps/advertiser/migrations/__init__.py (100%) rename {solution/services => services}/backend/apps/advertiser/models.py (100%) rename {solution/services => services}/backend/apps/advertiser/tests/__init__.py (100%) rename {solution/services => services}/backend/apps/advertiser/tests/test_advertiser_model.py (100%) rename {solution/services => services}/backend/apps/advertiser/tests/test_advertiser_statistics.py (100%) rename {solution/services => services}/backend/apps/campaign/__init__.py (100%) rename {solution/services => services}/backend/apps/campaign/admin.py (100%) rename {solution/services => services}/backend/apps/campaign/apps.py (100%) rename {solution/services => services}/backend/apps/campaign/forms.py (100%) rename {solution/services => services}/backend/apps/campaign/management/__init__.py (100%) rename {solution/services => services}/backend/apps/campaign/management/commands/__init__.py (100%) rename {solution/services => services}/backend/apps/campaign/management/commands/init_cache.py (100%) rename {solution/services => services}/backend/apps/campaign/migrations/0001_initial.py (100%) rename {solution/services => services}/backend/apps/campaign/migrations/__init__.py (100%) rename {solution/services => services}/backend/apps/campaign/models.py (100%) rename {solution/services => services}/backend/apps/campaign/tasks.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/__init__.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/test_campaign_click_model.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/test_campaign_impression_model.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/test_campaign_model.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/test_campaign_report_model.py (100%) rename {solution/services => services}/backend/apps/campaign/tests/test_campaign_statistics.py (100%) rename {solution/services => services}/backend/apps/campaign/validators.py (100%) rename {solution/services => services}/backend/apps/client/__init__.py (100%) rename {solution/services => services}/backend/apps/client/admin.py (100%) rename {solution/services => services}/backend/apps/client/apps.py (100%) rename {solution/services => services}/backend/apps/client/migrations/0001_initial.py (100%) rename {solution/services => services}/backend/apps/client/migrations/__init__.py (100%) rename {solution/services => services}/backend/apps/client/models.py (100%) rename {solution/services => services}/backend/apps/client/tests.py (100%) rename {solution/services => services}/backend/apps/core/__init__.py (100%) rename {solution/services => services}/backend/apps/core/apps.py (100%) rename {solution/services => services}/backend/apps/core/models.py (100%) rename {solution/services => services}/backend/apps/mlscore/__init__.py (100%) rename {solution/services => services}/backend/apps/mlscore/admin.py (100%) rename {solution/services => services}/backend/apps/mlscore/apps.py (100%) rename {solution/services => services}/backend/apps/mlscore/migrations/0001_initial.py (100%) rename {solution/services => services}/backend/apps/mlscore/migrations/__init__.py (100%) rename {solution/services => services}/backend/apps/mlscore/models.py (100%) rename {solution/services => services}/backend/apps/mlscore/tests.py (100%) rename {solution/services => services}/backend/config/__init__.py (100%) rename {solution/services => services}/backend/config/asgi.py (100%) rename {solution/services => services}/backend/config/celery.py (100%) rename {solution/services => services}/backend/config/errors.py (100%) rename {solution/services => services}/backend/config/handlers.py (100%) rename {solution/services => services}/backend/config/settings.py (100%) rename {solution/services => services}/backend/config/urls.py (100%) rename {solution/services => services}/backend/config/wsgi.py (100%) rename {solution/services => services}/backend/integrations/__init__.py (100%) rename {solution/services => services}/backend/integrations/yandexai/__init__.py (100%) rename {solution/services => services}/backend/integrations/yandexai/generators/__init__.py (100%) rename {solution/services => services}/backend/integrations/yandexai/generators/ad_text.py (100%) rename {solution/services => services}/backend/integrations/yandexai/healthcheck.py (100%) rename {solution/services => services}/backend/integrations/yandexai/moderation.py (100%) rename {solution/services => services}/backend/manage.py (100%) rename {solution/services => services}/backend/pyproject.toml (100%) rename {solution/services => services}/backend/scripts/check (100%) rename {solution/services => services}/backend/scripts/initdb (100%) rename {solution/services => services}/telegram_bot/.dockerignore (100%) rename {solution/services => services}/telegram_bot/.env.template (100%) rename {solution/services => services}/telegram_bot/.gitignore (100%) rename {solution/services => services}/telegram_bot/Dockerfile (100%) rename {solution/services => services}/telegram_bot/README.md (100%) rename {solution/services => services}/telegram_bot/api/__init__.py (100%) rename {solution/services => services}/telegram_bot/api/client.py (100%) rename {solution/services => services}/telegram_bot/api/errors.py (100%) rename {solution/services => services}/telegram_bot/api/schemas.py (100%) rename {solution/services => services}/telegram_bot/commands/__init__.py (100%) rename {solution/services => services}/telegram_bot/commands/campaigns.py (100%) rename {solution/services => services}/telegram_bot/commands/help.py (100%) rename {solution/services => services}/telegram_bot/commands/logout.py (100%) rename {solution/services => services}/telegram_bot/commands/start.py (100%) rename {solution/services => services}/telegram_bot/commands/stats.py (100%) rename {solution/services => services}/telegram_bot/config.py (100%) rename {solution/services => services}/telegram_bot/dialogs/__init__.py (100%) rename {solution/services => services}/telegram_bot/dialogs/campaigns.py (100%) rename {solution/services => services}/telegram_bot/dialogs/start.py (100%) rename {solution/services => services}/telegram_bot/dialogs/utils.py (100%) rename {solution/services => services}/telegram_bot/filters/__init__.py (100%) rename {solution/services => services}/telegram_bot/filters/auth.py (100%) rename {solution/services => services}/telegram_bot/main.py (100%) rename {solution/services => services}/telegram_bot/middlewares/__init__.py (100%) rename {solution/services => services}/telegram_bot/middlewares/auth.py (100%) rename {solution/services => services}/telegram_bot/middlewares/throttling.py (100%) rename {solution/services => services}/telegram_bot/pyproject.toml (100%) rename {solution/services => services}/telegram_bot/scripts/check (100%) rename {solution/services => services}/telegram_bot/states/__init__.py (100%) rename {solution/services => services}/telegram_bot/states/auth.py (100%) rename {solution/services => services}/telegram_bot/states/campaigns.py (100%) rename {solution/services => services}/telegram_bot/states/start.py (100%) delete mode 100644 solution/README.md delete mode 100644 solution/infrastructure/pgadmin/password delete mode 100644 solution/infrastructure/postgres/password rename {solution/tests => tests}/README.md (100%) rename {solution/tests => tests}/e2e/.env.template (100%) rename {solution/tests => tests}/e2e/.gitignore (100%) rename {solution/tests => tests}/e2e/README.md (100%) rename {solution/tests => tests}/e2e/conftest.py (100%) rename {solution/tests => tests}/e2e/pyproject.toml (100%) rename {solution/tests => tests}/e2e/pytest.ini (100%) rename {solution/tests => tests}/e2e/scripts/check (100%) rename {solution/tests => tests}/e2e/tests/__init__.py (100%) rename {solution/tests => tests}/e2e/tests/test_ad_text_generation.py (100%) rename {solution/tests => tests}/e2e/tests/test_backend_health.py (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 2d70bad..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -stages: - - test - -variables: - DOCKER_TLS_CERTDIR: "/certs" - DOCKER_HOST: "tcp://docker:2376" - DOCKER_TLS_VERIFY: "1" - DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" - DOCKER_DRIVER: overlay2 - -image: docker:27.5.1 - -services: - - name: docker:27.5.1-dind - command: [ - "--registry-mirror=http://gitlab.prodcontest.ru:5015", - "--registry-mirror=https://dockerhub.timeweb.cloud" - ] - -test: - stage: test - tags: - - backend - only: - - master - before_script: - - for try in {1..10}; do sleep 1; docker info && break ; done - - echo "$DOCKER_DEPLOY_KEY" | docker login -u "backend" --password-stdin $CI_REGISTRY - - docker compose version - - | - find solution -type f -name "Dockerfile*" ! -name "*.*" -print0 | while IFS= read -r -d '' file; do - echo "Обрабатываем файл: $file" - if ! grep -q "^ENV http_proxy" "$file"; then - sed -i '/^FROM.*alpine/ a RUN apk --update --no-cache add openssl wget' "$file" - sed -i '/^FROM/ a ENV http_proxy=http:\/\/84.201.181.188:15000\nENV https_proxy=http:\/\/84.201.181.188:15000\nENV no_proxy=localhost' "$file" - echo -e "\nENV http_proxy=\nENV https_proxy=\nENV no_proxy=" >> "$file" - echo "Добавлены прокси-переменные в $file" - else - echo "Прокси-переменные уже присутствуют в $file" - fi - done - - cd solution && docker compose up -d - script: - - docker run -v $CI_PROJECT_DIR:/app --network host gitlab.prodcontest.ru:5050/2025-final-indiv-repos/backend-checker:v2.5 - after_script: - - cd solution && docker compose logs > ../compose.log && docker compose down - artifacts: - when: always - access: 'developer' - paths: - - events.csv - - compose.log diff --git a/README.md b/README.md index 1b186ca..87f5326 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,313 @@ -# Backend +# AdNova -Используйте данный репозиторий для работы над задачей. +[![wakatime](https://wakatime.com/badge/user/cb406c1c-8eb4-4829-b9f9-816a0d284d7e/project/2b690440-fe32-49f5-87ea-b1def19de612.svg)](https://wakatime.com/badge/user/cb406c1c-8eb4-4829-b9f9-816a0d284d7e/project/2b690440-fe32-49f5-87ea-b1def19de612) -Помните! Все изменения должны быть строго в директории solution. Любые другие файлы за пределами директории solution изменять запрещено. +Service for advertisers to provide their ads and get profit! +## 🗺️ ER diagram -## Проверка на плагиат +![ER diagram](./assets/images/er-diagram.png) -Двойная система проверки включает:  +Not all tables inlcuded in this diagram (some django utility tables which makes no sense to business logic). On the left side there is default django models and on the right side there is developed models. -Собственное решение организаторов: мы используем алгоритмы для анализа вашего кода, проверяя, что все работы являются оригинальными. +Tables that are not mentioned in task (btw, you can see them on diagram): -Затем работы участников олимпиады будут проверяться на неправомерные заимствования через сервис Codechecker, продукт компании [«Антиплагиат»](https://antiplagiat.ru/). +Table Report: -Рекомендации по использованию стороннего кода: если вы решите использовать код, найденный в открытых источниках, очень важно указать источник этого кода. Это позволит легко проверить и подтвердить подлинность вашей работы, а также развеять любые возможные сомнения.  +- campaign_id - relation to Campaign +- client_id - relation to Client +- state - could be `s` (Sent), `r` (Under review), `t` (Took action), `f` (Skipped) +- message - optional text, helps moderator better understand report context +- flagged_by_llm - boolean, if true, then llm thinks that campaign is unacceptable, if false, then campaign is acceptable, if null, then llm has not provided response yet -Мы призываем всех участников придерживаться принципов академической честности и подходить к соревнованиям с открытым и честным настроем. Помните, что цель олимпиады — не только показать ваши навыки, но и развиваться как надежные и честные специалисты в будущем. \ No newline at end of file +## 📋 Instructions + +### Dedicated services setup + +[Backend](./services/backend/README.md) + +[Telegram bot](./services/telegram_bot/README.md) + +### Setup with docker compose + +#### Warning + +Plese note that containers will use ports from 13241 to 13245 and 8080, so there is must be no listeners on this ports range. + +#### Configure + +There is an [infrastructure](./infrastructure/) folder where stored all configs for services inside docker compose, so feel free to change them. In folders where you see `.env.template` you can create `.env` file to override defaults. + +#### Pull images + +```bash +docker compose pull +``` + +#### Build images + +```bash +docker compose build +``` + +#### Start services + +```bash +docker compose up -d +``` + +#### Notes + +You can just use this command to do all stuff listed above: + +```bash +docker compose up -d --build +``` + +#### Structure + +- **backend**: [127.0.0.1:8080](http://127.0.0.1:8080) -> `8080` + - Depends on: `postgres`, `redis`, `minio`, `backend-initdb` +- **backend-initdb** + - Depends on: `postgres`, `redis`, `minio` +- **backend-staticfiles**: [127.0.0.1:13241](http://127.0.0.1:13241) -> `80` +- **backend-celery-worker** + - Depends on: `redis` +- **telegram_bot** + - Depends on: `backend`, `redis` +- **postgres** + - Volume: `postgres_data` +- **redis** + - Volume: `redis_data` +- **pgadmin**: [127.0.0.1:13242](http://127.0.0.1:13242) -> `80` + - Depends on: `postgres` + - Volume: `pgadmin_data` +- **grafana**: [127.0.0.1:13243](http://127.0.0.1:13243) -> `3000` + - Volume: `grafana_data` +- **minio** + - API: [127.0.0.1:13244](http://127.0.0.1:13244) -> `9000` + - Console: [127.0.0.1:13245](http://127.0.0.1:13245) -> `9001` + - Volume: `minio_data` + +## ⚙️ Technologies + +### [Python](https://www.python.org/) + +Python is a high-level programming language renowned for its readability and simplicity. Its extensive standard library and active community make it an ideal choice for rapid prototyping and development. It not so fast as Go or Java, but development speed is rather faster them and systax is very simple and intuitive. Also there is a lot of python developers available on market. Used and trusted by many companies around the world. In project used for `backend` and `telegram_bot` services. + +### [Django](https://www.djangoproject.com/) + +Django is a powerful web framework for Python that provides rapid development capabilities through built-in tools such as ORM, authentication, and administration systems. It promotes the creation of secure and scalable web appwill write here somethinglications. Used and trusted by many companies around the world. Lets write less boilerplate and more actual logic!). In project used for `backend` service. + +### [Django Ninja](https://django-ninja.dev/) + +Django Ninja is a web framework that integrates seamlessly with Django, offering high performance (compared to DRF) and ease of use. It is designed to build APIs with minimal code and includes features like data validation and serialization with Pydantic. Not so popular, but much less complex than DRF. I prefer this to FastAPI actually). In project used for `backend` service. + +### [aiogram](https://aiogram.dev/) + +aiogram is a modern and fully asynchronous framework for Telegram bot development in Python. It allows efficient handling of multiple requests and provides a straightforward interface for building complex bots. Very popular and has big community for today. Known for its speed. In project used for `telegram_bot` service. + +### [Redis](https://redis.io/) + +Redis is an in-memory data structure store often used as a database, cache, and message broker. It supports various data structures and offers high performance for read and write operations, making it suitable for caching and real-time analytics. Very popular and has big community for today. In project used as fsm for aiogram (to avoid data loss on restart), caches (current_date, mlscores, clicks, views) for backend and as broker for Celery. + +### [Postgres](https://www.postgresql.org/) + +PostgreSQL is a powerful, open-source relational database system known for its robustness and feature richness. It supports advanced data types and performance optimization features, making it a reliable choice for handling complex queries and large datasets. And the one quote i really as main database for storing data.like: "The best database is that that you know". In project used as main database for storing data. + +### [Grafana](https://grafana.com/) + +Grafana is an open-source analytics and monitoring platform that allows for the visualization of metrics from various data sources. It provides customizable dashboards and alerting features, aiding in real-time monitoring and analysis. No-dealer winner in the dashboards solution and i really like it. In project used for advertisers' analytics dashboards. + +### [MinIO](https://min.io/) + +MinIO is a high-performance, S3-compatible object storage system. It is designed for large-scale data infrastructures and is suitable for storing unstructured data such as photos, videos, log files, backups, and container images. Best open-source alternative to AWS S3 for now. In project used to store advertisments' images. Allows us to scale easily (very native replication feature) and soon we can store other things such as user avatars (if we countinue developing project), etc. + +### [Celery](https://github.com/celery/celery) + +The most popular distributed task queue for Python. Task queues are used as a mechanism to distribute work across threads or machines. In project used for AI features like text generation, moderation, so client gets response on such endpoints immediately and spreads less resources of server (after of course he can get task result by uuid). For now only ad_text generation and moderation add-on uses celery, but soon we can add more features to do with tasks and scale easily!)) + +### [Docker & docker compose](https://www.docker.com/) + +Forced by organizators :) + +### Notes + +You may say: "For what we need a lot of complex technologies for now". I have an answer. More complex solutions (at first glance) will really help us to scale in the future and have less pain on refactoring system. + +## ➡️ Entrypoints + +### Restful API + +API Base endpoint when deployed with default docker compose: [127.0.0.1:8080](http://127.0.0.1:8080), also see [docs](#openapi-docs). + +### Admin panel + +Django admin panel, used for moderation, see [this](#moderation). + +### Telegram Bot + +Link: [t.me/adnova_bot](https://t.me/adnova_bot) + +Basic commands: + +/start - Start the bot and authenticate as advertiser + +/help - Get list of all commands + +/campaigns - Manage advertiser campaigns (only after authentication) + +/statistics - See advertiser overall statistics (only after authentication) + +/logout - Logout of current advertiser account (only after authentication) + +See [this](#telegram-bot-1). + +### Grafana + +When deployed with default docker compose: [127.0.0.1:13243](http://127.0.0.1:13243). See more details about this [here](#grafana-dashboard). + +## ✨ Features + +### Notes about basic features + +I cache every mlscore in redis (btw, on startup of docker compose i upload each cache in db for stability) and also i cache clicks and impressions count for campaigns. This increases perfomance of suggesting algorithm and increases clients satisfaction!_) + +### Clever suggesting algotithm + +Here is how suggesting algotitm looks like: + +1. Filter all campaigns and left only that currently active and matches user targeting. +2. Filter all campaigns with exceeded impressions, but to make more money i let exceed limit by 10% with chance 25% +3. Creating metrics for each campaign + 1. Profit: cost_per_impression (=0 if already viewed), cost_per_click (=0 if already clicked) + 2. Mlscores: from cache + 3. Capacity: 1 - ((impressions_limit - actual_impressions) / impressions_limit) +4. Normalization + 1. Normalizing profit from 0 to 1 + 2. Normalizing mlscores from 0 to 1 + 3. Capacity already normalized +5. Scoring each campaign: `0.8 * normalized_profit + 0.4 * normalized_ml + 0.05 * capacity` +6. Finding campaign with max score and returning it to user + +### Telegram bot + +With this bot you can easily manage your campaigns and see statistics. + +Demonstration: + +![telegram](./assets/gifs/telegram.gif) + +### Campaign image upload + +Advertisers can upload images to campaigns and delete with endpoints: `POST /advertisers/{advertiser_id}/campaigns/{campaign_id}/ad_image` and `DELETE /advertisers/{advertiser_id}/campaigns/{campaign_id}/ad_image`, for more details see [docs](#openapi-docs). + +![ad_image](./assets/gifs/ad_image.gif) + +### Campaign text generation + +Advertisers can generate text for their campaigns with their name and campaign title with `/generate/ad_text` endpoint, but it just returns a promise with `task_id` and client can get generation result at `/generate/ad_text/{task_id}/result`, for detailed info see [docs](#openapi-docs). + +Prompt used to generate: + +```text +Сгенерируй креативный рекламный текст для рекламодателя: "{advertiser_name}", +который проводит рекламную кампанию с названием: "{ad_title}" + +Требования: +1. Текст должен быть максимально привлекательным и продающим +2. Использовать современные маркетинговые приемы +3. Включить призыв к действию +4. Соблюдать структуру: заголовок - основной текст - заключение +5. Длина: 3-6 коротких предложений +6. Ответ должен содержать только текст рекламы без дополнительных комментариев + +Пример хорошего текста: +Запустите свой бизнес в космос с {{advertiser_name}}! Кампания "{{ad_title}}" предлагает +уникальные решения для цифрового продвижения. Присоединяйтесь к лидерам рынка - получите +персональную консультацию сегодня! +``` + +Demonstration: + +![ad_image generation](./assets/gifs/ad_text-generation.gif) + +### Moderation + +Moderation implemented via report system. Client goes to `/report` ([see OpenAPI docs](#openapi-docs)) and submits a report, then llm ([YandexGPT-lite](https://yandex.cloud/en/services/yandexgpt)) in task mode (does stuff on the background with Celery) checks for potential violation and sets the `flagged_by_llm` to True or False and this just a little help to moderators to make more accurate decisions. Btw, moderators can do anything they think they should do, for example make some changes, censor some content or even delete campaign. +Also admin user (whoose creditionals specified lower) can add new staff members and even create a specified group for them (this is built-in django capabilities). +Report has four states: Sent, Under review, Took action and Skipped. Admin panel has filtration by states and by flagged by llm status. + +Admin panel when deployed with docker compose (by default): [localhost:8080/admin/](http://localhost:8080/admin/) + +Reports list when deployed with docker compose (requires authentication): [localhost:8080/admin/campaign/campaignreport/](http://localhost:8080/admin/campaign/campaignreport/) + +Default username: `admin` + +Default password: `admin` + +Prompt used for moderation: + +```text +Ты — строгий AI-модератор контента. Анализируй текст ПО ВСЕМ указанным критериям. +Если ЛЮБОЙ из критериев нарушен — верни true. Только если ВСЕ критерии соблюдены — верни false. + +Критерии нарушений (true): +1. Нецензурная лексика: мат, эвфемизмы, оскорбительные выражения +2. Угрозы: прямые/косвенные угрозы жизни, шантаж, буллинг +3. Дискриминация: расизм, сексизм, ксенофобия, гомофобия +``` + +Btw, if llm returns `В интернете есть много сайтов с информацией на эту тему. [Посмотрите, что нашлось в поиске](https://ya.ru)` it also means that text is unacceptable) + +Demonstration: + +![moderation](./assets/gifs/moderation.gif) + +### Grafana dashboard + +When deployed with default docker compose: [localhost:13243](http://localhost:13243/) + +Default login: `admin` + +Default password: `proooooood` + +Analytics dashboard when deployed with default docker compose: [localhost:13243/d/adnova-statistics/statistics](http://localhost:13243/d/adnova-statisticss/statistics). You can enter advertiser id and get detailed advertiser statistics and also detailed statistics for each advertiser's campaign. + +Demonstration: + +![grafana](./assets/gifs/grafana.gif) + +### OpenAPI docs + +When deployed with default docker compose: [localhost:8080/docs](http://localhost:8080/docs) + +### Healthcheck endpoint + +When deployed with default docker compose: [localhost:8080/health](http://localhost:8080/health) + +Lets developers easily understand and identify problem and users check services health. + +![Healthcheck endpoint](./assets/images/healthcheck.png) + +### PgAdmin + +When deployed with default docker compose: [localhost:13242](http://localhost:13242) + +Default email: `admin@mail.com` + +Default password: `password` + +Default password for existing postgres server: `postgres` + +Not enough of basic django admin? Here is pgadmin, which is the most powerfull instrument to manage and administrate your postgres instance. (btw, text is not gpt-generated as it could be seen at first glance). + +![PgAdmin](./assets/images/pgadmin.png) + +## Testing + +You can find out about project tests [here](./tests/README.md). + +## Code quality + +All Python code linted and formatted with [Ruff](https://astral.sh/ruff) and i tried my best to keep code and architecture clean and simple :) diff --git a/solution/assets/gifs/ad_image.gif b/assets/gifs/ad_image.gif similarity index 100% rename from solution/assets/gifs/ad_image.gif rename to assets/gifs/ad_image.gif diff --git a/solution/assets/gifs/ad_text-generation.gif b/assets/gifs/ad_text-generation.gif similarity index 100% rename from solution/assets/gifs/ad_text-generation.gif rename to assets/gifs/ad_text-generation.gif diff --git a/solution/assets/gifs/grafana.gif b/assets/gifs/grafana.gif similarity index 100% rename from solution/assets/gifs/grafana.gif rename to assets/gifs/grafana.gif diff --git a/solution/assets/gifs/moderation.gif b/assets/gifs/moderation.gif similarity index 100% rename from solution/assets/gifs/moderation.gif rename to assets/gifs/moderation.gif diff --git a/solution/assets/gifs/telegram.gif b/assets/gifs/telegram.gif similarity index 100% rename from solution/assets/gifs/telegram.gif rename to assets/gifs/telegram.gif diff --git a/solution/assets/images/backend-coverage.png b/assets/images/backend-coverage.png similarity index 100% rename from solution/assets/images/backend-coverage.png rename to assets/images/backend-coverage.png diff --git a/solution/assets/images/er-diagram.png b/assets/images/er-diagram.png similarity index 100% rename from solution/assets/images/er-diagram.png rename to assets/images/er-diagram.png diff --git a/solution/assets/images/healthcheck.png b/assets/images/healthcheck.png similarity index 100% rename from solution/assets/images/healthcheck.png rename to assets/images/healthcheck.png diff --git a/solution/assets/images/pgadmin.png b/assets/images/pgadmin.png similarity index 100% rename from solution/assets/images/pgadmin.png rename to assets/images/pgadmin.png diff --git a/solution/compose.yaml b/compose.yaml similarity index 100% rename from solution/compose.yaml rename to compose.yaml diff --git a/solution/infrastructure/.gitignore b/infrastructure/.gitignore similarity index 100% rename from solution/infrastructure/.gitignore rename to infrastructure/.gitignore diff --git a/solution/infrastructure/backend/.env.template b/infrastructure/backend/.env.template similarity index 100% rename from solution/infrastructure/backend/.env.template rename to infrastructure/backend/.env.template diff --git a/solution/infrastructure/grafana/grafana.ini b/infrastructure/grafana/grafana.ini similarity index 100% rename from solution/infrastructure/grafana/grafana.ini rename to infrastructure/grafana/grafana.ini diff --git a/solution/infrastructure/grafana/provisioning/dashboards/AdNova/statistics.json b/infrastructure/grafana/provisioning/dashboards/AdNova/statistics.json similarity index 100% rename from solution/infrastructure/grafana/provisioning/dashboards/AdNova/statistics.json rename to infrastructure/grafana/provisioning/dashboards/AdNova/statistics.json diff --git a/solution/infrastructure/grafana/provisioning/dashboards/providers.yaml b/infrastructure/grafana/provisioning/dashboards/providers.yaml similarity index 100% rename from solution/infrastructure/grafana/provisioning/dashboards/providers.yaml rename to infrastructure/grafana/provisioning/dashboards/providers.yaml diff --git a/solution/infrastructure/grafana/provisioning/datasources/datasources.yaml b/infrastructure/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from solution/infrastructure/grafana/provisioning/datasources/datasources.yaml rename to infrastructure/grafana/provisioning/datasources/datasources.yaml diff --git a/solution/infrastructure/grafana/provisioning/plugins/plugings.yaml b/infrastructure/grafana/provisioning/plugins/plugings.yaml similarity index 100% rename from solution/infrastructure/grafana/provisioning/plugins/plugings.yaml rename to infrastructure/grafana/provisioning/plugins/plugings.yaml diff --git a/solution/infrastructure/grafana/scripts/entrypoint.sh b/infrastructure/grafana/scripts/entrypoint.sh similarity index 100% rename from solution/infrastructure/grafana/scripts/entrypoint.sh rename to infrastructure/grafana/scripts/entrypoint.sh diff --git a/solution/infrastructure/minio/.env.template b/infrastructure/minio/.env.template similarity index 100% rename from solution/infrastructure/minio/.env.template rename to infrastructure/minio/.env.template diff --git a/solution/infrastructure/pgadmin/.env.template b/infrastructure/pgadmin/.env.template similarity index 100% rename from solution/infrastructure/pgadmin/.env.template rename to infrastructure/pgadmin/.env.template diff --git a/solution/infrastructure/pgadmin/servers.json b/infrastructure/pgadmin/servers.json similarity index 100% rename from solution/infrastructure/pgadmin/servers.json rename to infrastructure/pgadmin/servers.json diff --git a/solution/infrastructure/postgres/.env.template b/infrastructure/postgres/.env.template similarity index 100% rename from solution/infrastructure/postgres/.env.template rename to infrastructure/postgres/.env.template diff --git a/solution/infrastructure/postgres/postgresql.conf b/infrastructure/postgres/postgresql.conf similarity index 100% rename from solution/infrastructure/postgres/postgresql.conf rename to infrastructure/postgres/postgresql.conf diff --git a/solution/infrastructure/redis/.env.template b/infrastructure/redis/.env.template similarity index 100% rename from solution/infrastructure/redis/.env.template rename to infrastructure/redis/.env.template diff --git a/solution/infrastructure/redis/redis.conf b/infrastructure/redis/redis.conf similarity index 100% rename from solution/infrastructure/redis/redis.conf rename to infrastructure/redis/redis.conf diff --git a/solution/infrastructure/telegram_bot/.env.template b/infrastructure/telegram_bot/.env.template similarity index 100% rename from solution/infrastructure/telegram_bot/.env.template rename to infrastructure/telegram_bot/.env.template diff --git a/solution/services/backend/.dockerignore b/services/backend/.dockerignore similarity index 100% rename from solution/services/backend/.dockerignore rename to services/backend/.dockerignore diff --git a/solution/services/backend/.env.template b/services/backend/.env.template similarity index 100% rename from solution/services/backend/.env.template rename to services/backend/.env.template diff --git a/solution/services/backend/.gitignore b/services/backend/.gitignore similarity index 100% rename from solution/services/backend/.gitignore rename to services/backend/.gitignore diff --git a/solution/services/backend/Dockerfile b/services/backend/Dockerfile similarity index 100% rename from solution/services/backend/Dockerfile rename to services/backend/Dockerfile diff --git a/solution/services/backend/Dockerfile.staticfiles b/services/backend/Dockerfile.staticfiles similarity index 100% rename from solution/services/backend/Dockerfile.staticfiles rename to services/backend/Dockerfile.staticfiles diff --git a/solution/services/backend/README.md b/services/backend/README.md similarity index 100% rename from solution/services/backend/README.md rename to services/backend/README.md diff --git a/solution/services/backend/api/__init__.py b/services/backend/api/__init__.py similarity index 100% rename from solution/services/backend/api/__init__.py rename to services/backend/api/__init__.py diff --git a/solution/services/backend/api/urls.py b/services/backend/api/urls.py similarity index 100% rename from solution/services/backend/api/urls.py rename to services/backend/api/urls.py diff --git a/solution/services/backend/api/v1/__init__.py b/services/backend/api/v1/__init__.py similarity index 100% rename from solution/services/backend/api/v1/__init__.py rename to services/backend/api/v1/__init__.py diff --git a/solution/services/backend/api/v1/ads/__init__.py b/services/backend/api/v1/ads/__init__.py similarity index 100% rename from solution/services/backend/api/v1/ads/__init__.py rename to services/backend/api/v1/ads/__init__.py diff --git a/solution/services/backend/api/v1/ads/schemas.py b/services/backend/api/v1/ads/schemas.py similarity index 100% rename from solution/services/backend/api/v1/ads/schemas.py rename to services/backend/api/v1/ads/schemas.py diff --git a/solution/services/backend/api/v1/ads/views.py b/services/backend/api/v1/ads/views.py similarity index 100% rename from solution/services/backend/api/v1/ads/views.py rename to services/backend/api/v1/ads/views.py diff --git a/solution/services/backend/api/v1/advertisers/__init__.py b/services/backend/api/v1/advertisers/__init__.py similarity index 100% rename from solution/services/backend/api/v1/advertisers/__init__.py rename to services/backend/api/v1/advertisers/__init__.py diff --git a/solution/services/backend/api/v1/advertisers/schemas.py b/services/backend/api/v1/advertisers/schemas.py similarity index 100% rename from solution/services/backend/api/v1/advertisers/schemas.py rename to services/backend/api/v1/advertisers/schemas.py diff --git a/solution/services/backend/api/v1/advertisers/tests.py b/services/backend/api/v1/advertisers/tests.py similarity index 100% rename from solution/services/backend/api/v1/advertisers/tests.py rename to services/backend/api/v1/advertisers/tests.py diff --git a/solution/services/backend/api/v1/advertisers/views.py b/services/backend/api/v1/advertisers/views.py similarity index 100% rename from solution/services/backend/api/v1/advertisers/views.py rename to services/backend/api/v1/advertisers/views.py diff --git a/solution/services/backend/api/v1/campaigns/__init__.py b/services/backend/api/v1/campaigns/__init__.py similarity index 100% rename from solution/services/backend/api/v1/campaigns/__init__.py rename to services/backend/api/v1/campaigns/__init__.py diff --git a/solution/services/backend/api/v1/campaigns/schemas.py b/services/backend/api/v1/campaigns/schemas.py similarity index 100% rename from solution/services/backend/api/v1/campaigns/schemas.py rename to services/backend/api/v1/campaigns/schemas.py diff --git a/solution/services/backend/api/v1/campaigns/utils.py b/services/backend/api/v1/campaigns/utils.py similarity index 100% rename from solution/services/backend/api/v1/campaigns/utils.py rename to services/backend/api/v1/campaigns/utils.py diff --git a/solution/services/backend/api/v1/campaigns/views.py b/services/backend/api/v1/campaigns/views.py similarity index 100% rename from solution/services/backend/api/v1/campaigns/views.py rename to services/backend/api/v1/campaigns/views.py diff --git a/solution/services/backend/api/v1/clients/__init__.py b/services/backend/api/v1/clients/__init__.py similarity index 100% rename from solution/services/backend/api/v1/clients/__init__.py rename to services/backend/api/v1/clients/__init__.py diff --git a/solution/services/backend/api/v1/clients/schemas.py b/services/backend/api/v1/clients/schemas.py similarity index 100% rename from solution/services/backend/api/v1/clients/schemas.py rename to services/backend/api/v1/clients/schemas.py diff --git a/solution/services/backend/api/v1/clients/tests.py b/services/backend/api/v1/clients/tests.py similarity index 100% rename from solution/services/backend/api/v1/clients/tests.py rename to services/backend/api/v1/clients/tests.py diff --git a/solution/services/backend/api/v1/clients/views.py b/services/backend/api/v1/clients/views.py similarity index 100% rename from solution/services/backend/api/v1/clients/views.py rename to services/backend/api/v1/clients/views.py diff --git a/solution/services/backend/api/v1/generate/__init__.py b/services/backend/api/v1/generate/__init__.py similarity index 100% rename from solution/services/backend/api/v1/generate/__init__.py rename to services/backend/api/v1/generate/__init__.py diff --git a/solution/services/backend/api/v1/generate/schemas.py b/services/backend/api/v1/generate/schemas.py similarity index 100% rename from solution/services/backend/api/v1/generate/schemas.py rename to services/backend/api/v1/generate/schemas.py diff --git a/solution/services/backend/api/v1/generate/views.py b/services/backend/api/v1/generate/views.py similarity index 100% rename from solution/services/backend/api/v1/generate/views.py rename to services/backend/api/v1/generate/views.py diff --git a/solution/services/backend/api/v1/handlers.py b/services/backend/api/v1/handlers.py similarity index 100% rename from solution/services/backend/api/v1/handlers.py rename to services/backend/api/v1/handlers.py diff --git a/solution/services/backend/api/v1/report/__init__.py b/services/backend/api/v1/report/__init__.py similarity index 100% rename from solution/services/backend/api/v1/report/__init__.py rename to services/backend/api/v1/report/__init__.py diff --git a/solution/services/backend/api/v1/report/schemas.py b/services/backend/api/v1/report/schemas.py similarity index 100% rename from solution/services/backend/api/v1/report/schemas.py rename to services/backend/api/v1/report/schemas.py diff --git a/solution/services/backend/api/v1/report/views.py b/services/backend/api/v1/report/views.py similarity index 100% rename from solution/services/backend/api/v1/report/views.py rename to services/backend/api/v1/report/views.py diff --git a/solution/services/backend/api/v1/router.py b/services/backend/api/v1/router.py similarity index 100% rename from solution/services/backend/api/v1/router.py rename to services/backend/api/v1/router.py diff --git a/solution/services/backend/api/v1/schemas.py b/services/backend/api/v1/schemas.py similarity index 100% rename from solution/services/backend/api/v1/schemas.py rename to services/backend/api/v1/schemas.py diff --git a/solution/services/backend/api/v1/stats/__init__.py b/services/backend/api/v1/stats/__init__.py similarity index 100% rename from solution/services/backend/api/v1/stats/__init__.py rename to services/backend/api/v1/stats/__init__.py diff --git a/solution/services/backend/api/v1/stats/schemas.py b/services/backend/api/v1/stats/schemas.py similarity index 100% rename from solution/services/backend/api/v1/stats/schemas.py rename to services/backend/api/v1/stats/schemas.py diff --git a/solution/services/backend/api/v1/stats/tests.py b/services/backend/api/v1/stats/tests.py similarity index 100% rename from solution/services/backend/api/v1/stats/tests.py rename to services/backend/api/v1/stats/tests.py diff --git a/solution/services/backend/api/v1/stats/views.py b/services/backend/api/v1/stats/views.py similarity index 100% rename from solution/services/backend/api/v1/stats/views.py rename to services/backend/api/v1/stats/views.py diff --git a/solution/services/backend/api/v1/time/__init__.py b/services/backend/api/v1/time/__init__.py similarity index 100% rename from solution/services/backend/api/v1/time/__init__.py rename to services/backend/api/v1/time/__init__.py diff --git a/solution/services/backend/api/v1/time/schemas.py b/services/backend/api/v1/time/schemas.py similarity index 100% rename from solution/services/backend/api/v1/time/schemas.py rename to services/backend/api/v1/time/schemas.py diff --git a/solution/services/backend/api/v1/time/tests.py b/services/backend/api/v1/time/tests.py similarity index 100% rename from solution/services/backend/api/v1/time/tests.py rename to services/backend/api/v1/time/tests.py diff --git a/solution/services/backend/api/v1/time/views.py b/services/backend/api/v1/time/views.py similarity index 100% rename from solution/services/backend/api/v1/time/views.py rename to services/backend/api/v1/time/views.py diff --git a/solution/services/backend/apps/__init__.py b/services/backend/apps/__init__.py similarity index 100% rename from solution/services/backend/apps/__init__.py rename to services/backend/apps/__init__.py diff --git a/solution/services/backend/apps/advertiser/__init__.py b/services/backend/apps/advertiser/__init__.py similarity index 100% rename from solution/services/backend/apps/advertiser/__init__.py rename to services/backend/apps/advertiser/__init__.py diff --git a/solution/services/backend/apps/advertiser/admin.py b/services/backend/apps/advertiser/admin.py similarity index 100% rename from solution/services/backend/apps/advertiser/admin.py rename to services/backend/apps/advertiser/admin.py diff --git a/solution/services/backend/apps/advertiser/apps.py b/services/backend/apps/advertiser/apps.py similarity index 100% rename from solution/services/backend/apps/advertiser/apps.py rename to services/backend/apps/advertiser/apps.py diff --git a/solution/services/backend/apps/advertiser/migrations/0001_initial.py b/services/backend/apps/advertiser/migrations/0001_initial.py similarity index 100% rename from solution/services/backend/apps/advertiser/migrations/0001_initial.py rename to services/backend/apps/advertiser/migrations/0001_initial.py diff --git a/solution/services/backend/apps/advertiser/migrations/__init__.py b/services/backend/apps/advertiser/migrations/__init__.py similarity index 100% rename from solution/services/backend/apps/advertiser/migrations/__init__.py rename to services/backend/apps/advertiser/migrations/__init__.py diff --git a/solution/services/backend/apps/advertiser/models.py b/services/backend/apps/advertiser/models.py similarity index 100% rename from solution/services/backend/apps/advertiser/models.py rename to services/backend/apps/advertiser/models.py diff --git a/solution/services/backend/apps/advertiser/tests/__init__.py b/services/backend/apps/advertiser/tests/__init__.py similarity index 100% rename from solution/services/backend/apps/advertiser/tests/__init__.py rename to services/backend/apps/advertiser/tests/__init__.py diff --git a/solution/services/backend/apps/advertiser/tests/test_advertiser_model.py b/services/backend/apps/advertiser/tests/test_advertiser_model.py similarity index 100% rename from solution/services/backend/apps/advertiser/tests/test_advertiser_model.py rename to services/backend/apps/advertiser/tests/test_advertiser_model.py diff --git a/solution/services/backend/apps/advertiser/tests/test_advertiser_statistics.py b/services/backend/apps/advertiser/tests/test_advertiser_statistics.py similarity index 100% rename from solution/services/backend/apps/advertiser/tests/test_advertiser_statistics.py rename to services/backend/apps/advertiser/tests/test_advertiser_statistics.py diff --git a/solution/services/backend/apps/campaign/__init__.py b/services/backend/apps/campaign/__init__.py similarity index 100% rename from solution/services/backend/apps/campaign/__init__.py rename to services/backend/apps/campaign/__init__.py diff --git a/solution/services/backend/apps/campaign/admin.py b/services/backend/apps/campaign/admin.py similarity index 100% rename from solution/services/backend/apps/campaign/admin.py rename to services/backend/apps/campaign/admin.py diff --git a/solution/services/backend/apps/campaign/apps.py b/services/backend/apps/campaign/apps.py similarity index 100% rename from solution/services/backend/apps/campaign/apps.py rename to services/backend/apps/campaign/apps.py diff --git a/solution/services/backend/apps/campaign/forms.py b/services/backend/apps/campaign/forms.py similarity index 100% rename from solution/services/backend/apps/campaign/forms.py rename to services/backend/apps/campaign/forms.py diff --git a/solution/services/backend/apps/campaign/management/__init__.py b/services/backend/apps/campaign/management/__init__.py similarity index 100% rename from solution/services/backend/apps/campaign/management/__init__.py rename to services/backend/apps/campaign/management/__init__.py diff --git a/solution/services/backend/apps/campaign/management/commands/__init__.py b/services/backend/apps/campaign/management/commands/__init__.py similarity index 100% rename from solution/services/backend/apps/campaign/management/commands/__init__.py rename to services/backend/apps/campaign/management/commands/__init__.py diff --git a/solution/services/backend/apps/campaign/management/commands/init_cache.py b/services/backend/apps/campaign/management/commands/init_cache.py similarity index 100% rename from solution/services/backend/apps/campaign/management/commands/init_cache.py rename to services/backend/apps/campaign/management/commands/init_cache.py diff --git a/solution/services/backend/apps/campaign/migrations/0001_initial.py b/services/backend/apps/campaign/migrations/0001_initial.py similarity index 100% rename from solution/services/backend/apps/campaign/migrations/0001_initial.py rename to services/backend/apps/campaign/migrations/0001_initial.py diff --git a/solution/services/backend/apps/campaign/migrations/__init__.py b/services/backend/apps/campaign/migrations/__init__.py similarity index 100% rename from solution/services/backend/apps/campaign/migrations/__init__.py rename to services/backend/apps/campaign/migrations/__init__.py diff --git a/solution/services/backend/apps/campaign/models.py b/services/backend/apps/campaign/models.py similarity index 100% rename from solution/services/backend/apps/campaign/models.py rename to services/backend/apps/campaign/models.py diff --git a/solution/services/backend/apps/campaign/tasks.py b/services/backend/apps/campaign/tasks.py similarity index 100% rename from solution/services/backend/apps/campaign/tasks.py rename to services/backend/apps/campaign/tasks.py diff --git a/solution/services/backend/apps/campaign/tests/__init__.py b/services/backend/apps/campaign/tests/__init__.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/__init__.py rename to services/backend/apps/campaign/tests/__init__.py diff --git a/solution/services/backend/apps/campaign/tests/test_campaign_click_model.py b/services/backend/apps/campaign/tests/test_campaign_click_model.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/test_campaign_click_model.py rename to services/backend/apps/campaign/tests/test_campaign_click_model.py diff --git a/solution/services/backend/apps/campaign/tests/test_campaign_impression_model.py b/services/backend/apps/campaign/tests/test_campaign_impression_model.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/test_campaign_impression_model.py rename to services/backend/apps/campaign/tests/test_campaign_impression_model.py diff --git a/solution/services/backend/apps/campaign/tests/test_campaign_model.py b/services/backend/apps/campaign/tests/test_campaign_model.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/test_campaign_model.py rename to services/backend/apps/campaign/tests/test_campaign_model.py diff --git a/solution/services/backend/apps/campaign/tests/test_campaign_report_model.py b/services/backend/apps/campaign/tests/test_campaign_report_model.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/test_campaign_report_model.py rename to services/backend/apps/campaign/tests/test_campaign_report_model.py diff --git a/solution/services/backend/apps/campaign/tests/test_campaign_statistics.py b/services/backend/apps/campaign/tests/test_campaign_statistics.py similarity index 100% rename from solution/services/backend/apps/campaign/tests/test_campaign_statistics.py rename to services/backend/apps/campaign/tests/test_campaign_statistics.py diff --git a/solution/services/backend/apps/campaign/validators.py b/services/backend/apps/campaign/validators.py similarity index 100% rename from solution/services/backend/apps/campaign/validators.py rename to services/backend/apps/campaign/validators.py diff --git a/solution/services/backend/apps/client/__init__.py b/services/backend/apps/client/__init__.py similarity index 100% rename from solution/services/backend/apps/client/__init__.py rename to services/backend/apps/client/__init__.py diff --git a/solution/services/backend/apps/client/admin.py b/services/backend/apps/client/admin.py similarity index 100% rename from solution/services/backend/apps/client/admin.py rename to services/backend/apps/client/admin.py diff --git a/solution/services/backend/apps/client/apps.py b/services/backend/apps/client/apps.py similarity index 100% rename from solution/services/backend/apps/client/apps.py rename to services/backend/apps/client/apps.py diff --git a/solution/services/backend/apps/client/migrations/0001_initial.py b/services/backend/apps/client/migrations/0001_initial.py similarity index 100% rename from solution/services/backend/apps/client/migrations/0001_initial.py rename to services/backend/apps/client/migrations/0001_initial.py diff --git a/solution/services/backend/apps/client/migrations/__init__.py b/services/backend/apps/client/migrations/__init__.py similarity index 100% rename from solution/services/backend/apps/client/migrations/__init__.py rename to services/backend/apps/client/migrations/__init__.py diff --git a/solution/services/backend/apps/client/models.py b/services/backend/apps/client/models.py similarity index 100% rename from solution/services/backend/apps/client/models.py rename to services/backend/apps/client/models.py diff --git a/solution/services/backend/apps/client/tests.py b/services/backend/apps/client/tests.py similarity index 100% rename from solution/services/backend/apps/client/tests.py rename to services/backend/apps/client/tests.py diff --git a/solution/services/backend/apps/core/__init__.py b/services/backend/apps/core/__init__.py similarity index 100% rename from solution/services/backend/apps/core/__init__.py rename to services/backend/apps/core/__init__.py diff --git a/solution/services/backend/apps/core/apps.py b/services/backend/apps/core/apps.py similarity index 100% rename from solution/services/backend/apps/core/apps.py rename to services/backend/apps/core/apps.py diff --git a/solution/services/backend/apps/core/models.py b/services/backend/apps/core/models.py similarity index 100% rename from solution/services/backend/apps/core/models.py rename to services/backend/apps/core/models.py diff --git a/solution/services/backend/apps/mlscore/__init__.py b/services/backend/apps/mlscore/__init__.py similarity index 100% rename from solution/services/backend/apps/mlscore/__init__.py rename to services/backend/apps/mlscore/__init__.py diff --git a/solution/services/backend/apps/mlscore/admin.py b/services/backend/apps/mlscore/admin.py similarity index 100% rename from solution/services/backend/apps/mlscore/admin.py rename to services/backend/apps/mlscore/admin.py diff --git a/solution/services/backend/apps/mlscore/apps.py b/services/backend/apps/mlscore/apps.py similarity index 100% rename from solution/services/backend/apps/mlscore/apps.py rename to services/backend/apps/mlscore/apps.py diff --git a/solution/services/backend/apps/mlscore/migrations/0001_initial.py b/services/backend/apps/mlscore/migrations/0001_initial.py similarity index 100% rename from solution/services/backend/apps/mlscore/migrations/0001_initial.py rename to services/backend/apps/mlscore/migrations/0001_initial.py diff --git a/solution/services/backend/apps/mlscore/migrations/__init__.py b/services/backend/apps/mlscore/migrations/__init__.py similarity index 100% rename from solution/services/backend/apps/mlscore/migrations/__init__.py rename to services/backend/apps/mlscore/migrations/__init__.py diff --git a/solution/services/backend/apps/mlscore/models.py b/services/backend/apps/mlscore/models.py similarity index 100% rename from solution/services/backend/apps/mlscore/models.py rename to services/backend/apps/mlscore/models.py diff --git a/solution/services/backend/apps/mlscore/tests.py b/services/backend/apps/mlscore/tests.py similarity index 100% rename from solution/services/backend/apps/mlscore/tests.py rename to services/backend/apps/mlscore/tests.py diff --git a/solution/services/backend/config/__init__.py b/services/backend/config/__init__.py similarity index 100% rename from solution/services/backend/config/__init__.py rename to services/backend/config/__init__.py diff --git a/solution/services/backend/config/asgi.py b/services/backend/config/asgi.py similarity index 100% rename from solution/services/backend/config/asgi.py rename to services/backend/config/asgi.py diff --git a/solution/services/backend/config/celery.py b/services/backend/config/celery.py similarity index 100% rename from solution/services/backend/config/celery.py rename to services/backend/config/celery.py diff --git a/solution/services/backend/config/errors.py b/services/backend/config/errors.py similarity index 100% rename from solution/services/backend/config/errors.py rename to services/backend/config/errors.py diff --git a/solution/services/backend/config/handlers.py b/services/backend/config/handlers.py similarity index 100% rename from solution/services/backend/config/handlers.py rename to services/backend/config/handlers.py diff --git a/solution/services/backend/config/settings.py b/services/backend/config/settings.py similarity index 100% rename from solution/services/backend/config/settings.py rename to services/backend/config/settings.py diff --git a/solution/services/backend/config/urls.py b/services/backend/config/urls.py similarity index 100% rename from solution/services/backend/config/urls.py rename to services/backend/config/urls.py diff --git a/solution/services/backend/config/wsgi.py b/services/backend/config/wsgi.py similarity index 100% rename from solution/services/backend/config/wsgi.py rename to services/backend/config/wsgi.py diff --git a/solution/services/backend/integrations/__init__.py b/services/backend/integrations/__init__.py similarity index 100% rename from solution/services/backend/integrations/__init__.py rename to services/backend/integrations/__init__.py diff --git a/solution/services/backend/integrations/yandexai/__init__.py b/services/backend/integrations/yandexai/__init__.py similarity index 100% rename from solution/services/backend/integrations/yandexai/__init__.py rename to services/backend/integrations/yandexai/__init__.py diff --git a/solution/services/backend/integrations/yandexai/generators/__init__.py b/services/backend/integrations/yandexai/generators/__init__.py similarity index 100% rename from solution/services/backend/integrations/yandexai/generators/__init__.py rename to services/backend/integrations/yandexai/generators/__init__.py diff --git a/solution/services/backend/integrations/yandexai/generators/ad_text.py b/services/backend/integrations/yandexai/generators/ad_text.py similarity index 100% rename from solution/services/backend/integrations/yandexai/generators/ad_text.py rename to services/backend/integrations/yandexai/generators/ad_text.py diff --git a/solution/services/backend/integrations/yandexai/healthcheck.py b/services/backend/integrations/yandexai/healthcheck.py similarity index 100% rename from solution/services/backend/integrations/yandexai/healthcheck.py rename to services/backend/integrations/yandexai/healthcheck.py diff --git a/solution/services/backend/integrations/yandexai/moderation.py b/services/backend/integrations/yandexai/moderation.py similarity index 100% rename from solution/services/backend/integrations/yandexai/moderation.py rename to services/backend/integrations/yandexai/moderation.py diff --git a/solution/services/backend/manage.py b/services/backend/manage.py similarity index 100% rename from solution/services/backend/manage.py rename to services/backend/manage.py diff --git a/solution/services/backend/pyproject.toml b/services/backend/pyproject.toml similarity index 100% rename from solution/services/backend/pyproject.toml rename to services/backend/pyproject.toml diff --git a/solution/services/backend/scripts/check b/services/backend/scripts/check similarity index 100% rename from solution/services/backend/scripts/check rename to services/backend/scripts/check diff --git a/solution/services/backend/scripts/initdb b/services/backend/scripts/initdb similarity index 100% rename from solution/services/backend/scripts/initdb rename to services/backend/scripts/initdb diff --git a/solution/services/telegram_bot/.dockerignore b/services/telegram_bot/.dockerignore similarity index 100% rename from solution/services/telegram_bot/.dockerignore rename to services/telegram_bot/.dockerignore diff --git a/solution/services/telegram_bot/.env.template b/services/telegram_bot/.env.template similarity index 100% rename from solution/services/telegram_bot/.env.template rename to services/telegram_bot/.env.template diff --git a/solution/services/telegram_bot/.gitignore b/services/telegram_bot/.gitignore similarity index 100% rename from solution/services/telegram_bot/.gitignore rename to services/telegram_bot/.gitignore diff --git a/solution/services/telegram_bot/Dockerfile b/services/telegram_bot/Dockerfile similarity index 100% rename from solution/services/telegram_bot/Dockerfile rename to services/telegram_bot/Dockerfile diff --git a/solution/services/telegram_bot/README.md b/services/telegram_bot/README.md similarity index 100% rename from solution/services/telegram_bot/README.md rename to services/telegram_bot/README.md diff --git a/solution/services/telegram_bot/api/__init__.py b/services/telegram_bot/api/__init__.py similarity index 100% rename from solution/services/telegram_bot/api/__init__.py rename to services/telegram_bot/api/__init__.py diff --git a/solution/services/telegram_bot/api/client.py b/services/telegram_bot/api/client.py similarity index 100% rename from solution/services/telegram_bot/api/client.py rename to services/telegram_bot/api/client.py diff --git a/solution/services/telegram_bot/api/errors.py b/services/telegram_bot/api/errors.py similarity index 100% rename from solution/services/telegram_bot/api/errors.py rename to services/telegram_bot/api/errors.py diff --git a/solution/services/telegram_bot/api/schemas.py b/services/telegram_bot/api/schemas.py similarity index 100% rename from solution/services/telegram_bot/api/schemas.py rename to services/telegram_bot/api/schemas.py diff --git a/solution/services/telegram_bot/commands/__init__.py b/services/telegram_bot/commands/__init__.py similarity index 100% rename from solution/services/telegram_bot/commands/__init__.py rename to services/telegram_bot/commands/__init__.py diff --git a/solution/services/telegram_bot/commands/campaigns.py b/services/telegram_bot/commands/campaigns.py similarity index 100% rename from solution/services/telegram_bot/commands/campaigns.py rename to services/telegram_bot/commands/campaigns.py diff --git a/solution/services/telegram_bot/commands/help.py b/services/telegram_bot/commands/help.py similarity index 100% rename from solution/services/telegram_bot/commands/help.py rename to services/telegram_bot/commands/help.py diff --git a/solution/services/telegram_bot/commands/logout.py b/services/telegram_bot/commands/logout.py similarity index 100% rename from solution/services/telegram_bot/commands/logout.py rename to services/telegram_bot/commands/logout.py diff --git a/solution/services/telegram_bot/commands/start.py b/services/telegram_bot/commands/start.py similarity index 100% rename from solution/services/telegram_bot/commands/start.py rename to services/telegram_bot/commands/start.py diff --git a/solution/services/telegram_bot/commands/stats.py b/services/telegram_bot/commands/stats.py similarity index 100% rename from solution/services/telegram_bot/commands/stats.py rename to services/telegram_bot/commands/stats.py diff --git a/solution/services/telegram_bot/config.py b/services/telegram_bot/config.py similarity index 100% rename from solution/services/telegram_bot/config.py rename to services/telegram_bot/config.py diff --git a/solution/services/telegram_bot/dialogs/__init__.py b/services/telegram_bot/dialogs/__init__.py similarity index 100% rename from solution/services/telegram_bot/dialogs/__init__.py rename to services/telegram_bot/dialogs/__init__.py diff --git a/solution/services/telegram_bot/dialogs/campaigns.py b/services/telegram_bot/dialogs/campaigns.py similarity index 100% rename from solution/services/telegram_bot/dialogs/campaigns.py rename to services/telegram_bot/dialogs/campaigns.py diff --git a/solution/services/telegram_bot/dialogs/start.py b/services/telegram_bot/dialogs/start.py similarity index 100% rename from solution/services/telegram_bot/dialogs/start.py rename to services/telegram_bot/dialogs/start.py diff --git a/solution/services/telegram_bot/dialogs/utils.py b/services/telegram_bot/dialogs/utils.py similarity index 100% rename from solution/services/telegram_bot/dialogs/utils.py rename to services/telegram_bot/dialogs/utils.py diff --git a/solution/services/telegram_bot/filters/__init__.py b/services/telegram_bot/filters/__init__.py similarity index 100% rename from solution/services/telegram_bot/filters/__init__.py rename to services/telegram_bot/filters/__init__.py diff --git a/solution/services/telegram_bot/filters/auth.py b/services/telegram_bot/filters/auth.py similarity index 100% rename from solution/services/telegram_bot/filters/auth.py rename to services/telegram_bot/filters/auth.py diff --git a/solution/services/telegram_bot/main.py b/services/telegram_bot/main.py similarity index 100% rename from solution/services/telegram_bot/main.py rename to services/telegram_bot/main.py diff --git a/solution/services/telegram_bot/middlewares/__init__.py b/services/telegram_bot/middlewares/__init__.py similarity index 100% rename from solution/services/telegram_bot/middlewares/__init__.py rename to services/telegram_bot/middlewares/__init__.py diff --git a/solution/services/telegram_bot/middlewares/auth.py b/services/telegram_bot/middlewares/auth.py similarity index 100% rename from solution/services/telegram_bot/middlewares/auth.py rename to services/telegram_bot/middlewares/auth.py diff --git a/solution/services/telegram_bot/middlewares/throttling.py b/services/telegram_bot/middlewares/throttling.py similarity index 100% rename from solution/services/telegram_bot/middlewares/throttling.py rename to services/telegram_bot/middlewares/throttling.py diff --git a/solution/services/telegram_bot/pyproject.toml b/services/telegram_bot/pyproject.toml similarity index 100% rename from solution/services/telegram_bot/pyproject.toml rename to services/telegram_bot/pyproject.toml diff --git a/solution/services/telegram_bot/scripts/check b/services/telegram_bot/scripts/check similarity index 100% rename from solution/services/telegram_bot/scripts/check rename to services/telegram_bot/scripts/check diff --git a/solution/services/telegram_bot/states/__init__.py b/services/telegram_bot/states/__init__.py similarity index 100% rename from solution/services/telegram_bot/states/__init__.py rename to services/telegram_bot/states/__init__.py diff --git a/solution/services/telegram_bot/states/auth.py b/services/telegram_bot/states/auth.py similarity index 100% rename from solution/services/telegram_bot/states/auth.py rename to services/telegram_bot/states/auth.py diff --git a/solution/services/telegram_bot/states/campaigns.py b/services/telegram_bot/states/campaigns.py similarity index 100% rename from solution/services/telegram_bot/states/campaigns.py rename to services/telegram_bot/states/campaigns.py diff --git a/solution/services/telegram_bot/states/start.py b/services/telegram_bot/states/start.py similarity index 100% rename from solution/services/telegram_bot/states/start.py rename to services/telegram_bot/states/start.py diff --git a/solution/README.md b/solution/README.md deleted file mode 100644 index 87f5326..0000000 --- a/solution/README.md +++ /dev/null @@ -1,313 +0,0 @@ -# AdNova - -[![wakatime](https://wakatime.com/badge/user/cb406c1c-8eb4-4829-b9f9-816a0d284d7e/project/2b690440-fe32-49f5-87ea-b1def19de612.svg)](https://wakatime.com/badge/user/cb406c1c-8eb4-4829-b9f9-816a0d284d7e/project/2b690440-fe32-49f5-87ea-b1def19de612) - -Service for advertisers to provide their ads and get profit! - -## 🗺️ ER diagram - -![ER diagram](./assets/images/er-diagram.png) - -Not all tables inlcuded in this diagram (some django utility tables which makes no sense to business logic). On the left side there is default django models and on the right side there is developed models. - -Tables that are not mentioned in task (btw, you can see them on diagram): - -Table Report: - -- campaign_id - relation to Campaign -- client_id - relation to Client -- state - could be `s` (Sent), `r` (Under review), `t` (Took action), `f` (Skipped) -- message - optional text, helps moderator better understand report context -- flagged_by_llm - boolean, if true, then llm thinks that campaign is unacceptable, if false, then campaign is acceptable, if null, then llm has not provided response yet - -## 📋 Instructions - -### Dedicated services setup - -[Backend](./services/backend/README.md) - -[Telegram bot](./services/telegram_bot/README.md) - -### Setup with docker compose - -#### Warning - -Plese note that containers will use ports from 13241 to 13245 and 8080, so there is must be no listeners on this ports range. - -#### Configure - -There is an [infrastructure](./infrastructure/) folder where stored all configs for services inside docker compose, so feel free to change them. In folders where you see `.env.template` you can create `.env` file to override defaults. - -#### Pull images - -```bash -docker compose pull -``` - -#### Build images - -```bash -docker compose build -``` - -#### Start services - -```bash -docker compose up -d -``` - -#### Notes - -You can just use this command to do all stuff listed above: - -```bash -docker compose up -d --build -``` - -#### Structure - -- **backend**: [127.0.0.1:8080](http://127.0.0.1:8080) -> `8080` - - Depends on: `postgres`, `redis`, `minio`, `backend-initdb` -- **backend-initdb** - - Depends on: `postgres`, `redis`, `minio` -- **backend-staticfiles**: [127.0.0.1:13241](http://127.0.0.1:13241) -> `80` -- **backend-celery-worker** - - Depends on: `redis` -- **telegram_bot** - - Depends on: `backend`, `redis` -- **postgres** - - Volume: `postgres_data` -- **redis** - - Volume: `redis_data` -- **pgadmin**: [127.0.0.1:13242](http://127.0.0.1:13242) -> `80` - - Depends on: `postgres` - - Volume: `pgadmin_data` -- **grafana**: [127.0.0.1:13243](http://127.0.0.1:13243) -> `3000` - - Volume: `grafana_data` -- **minio** - - API: [127.0.0.1:13244](http://127.0.0.1:13244) -> `9000` - - Console: [127.0.0.1:13245](http://127.0.0.1:13245) -> `9001` - - Volume: `minio_data` - -## ⚙️ Technologies - -### [Python](https://www.python.org/) - -Python is a high-level programming language renowned for its readability and simplicity. Its extensive standard library and active community make it an ideal choice for rapid prototyping and development. It not so fast as Go or Java, but development speed is rather faster them and systax is very simple and intuitive. Also there is a lot of python developers available on market. Used and trusted by many companies around the world. In project used for `backend` and `telegram_bot` services. - -### [Django](https://www.djangoproject.com/) - -Django is a powerful web framework for Python that provides rapid development capabilities through built-in tools such as ORM, authentication, and administration systems. It promotes the creation of secure and scalable web appwill write here somethinglications. Used and trusted by many companies around the world. Lets write less boilerplate and more actual logic!). In project used for `backend` service. - -### [Django Ninja](https://django-ninja.dev/) - -Django Ninja is a web framework that integrates seamlessly with Django, offering high performance (compared to DRF) and ease of use. It is designed to build APIs with minimal code and includes features like data validation and serialization with Pydantic. Not so popular, but much less complex than DRF. I prefer this to FastAPI actually). In project used for `backend` service. - -### [aiogram](https://aiogram.dev/) - -aiogram is a modern and fully asynchronous framework for Telegram bot development in Python. It allows efficient handling of multiple requests and provides a straightforward interface for building complex bots. Very popular and has big community for today. Known for its speed. In project used for `telegram_bot` service. - -### [Redis](https://redis.io/) - -Redis is an in-memory data structure store often used as a database, cache, and message broker. It supports various data structures and offers high performance for read and write operations, making it suitable for caching and real-time analytics. Very popular and has big community for today. In project used as fsm for aiogram (to avoid data loss on restart), caches (current_date, mlscores, clicks, views) for backend and as broker for Celery. - -### [Postgres](https://www.postgresql.org/) - -PostgreSQL is a powerful, open-source relational database system known for its robustness and feature richness. It supports advanced data types and performance optimization features, making it a reliable choice for handling complex queries and large datasets. And the one quote i really as main database for storing data.like: "The best database is that that you know". In project used as main database for storing data. - -### [Grafana](https://grafana.com/) - -Grafana is an open-source analytics and monitoring platform that allows for the visualization of metrics from various data sources. It provides customizable dashboards and alerting features, aiding in real-time monitoring and analysis. No-dealer winner in the dashboards solution and i really like it. In project used for advertisers' analytics dashboards. - -### [MinIO](https://min.io/) - -MinIO is a high-performance, S3-compatible object storage system. It is designed for large-scale data infrastructures and is suitable for storing unstructured data such as photos, videos, log files, backups, and container images. Best open-source alternative to AWS S3 for now. In project used to store advertisments' images. Allows us to scale easily (very native replication feature) and soon we can store other things such as user avatars (if we countinue developing project), etc. - -### [Celery](https://github.com/celery/celery) - -The most popular distributed task queue for Python. Task queues are used as a mechanism to distribute work across threads or machines. In project used for AI features like text generation, moderation, so client gets response on such endpoints immediately and spreads less resources of server (after of course he can get task result by uuid). For now only ad_text generation and moderation add-on uses celery, but soon we can add more features to do with tasks and scale easily!)) - -### [Docker & docker compose](https://www.docker.com/) - -Forced by organizators :) - -### Notes - -You may say: "For what we need a lot of complex technologies for now". I have an answer. More complex solutions (at first glance) will really help us to scale in the future and have less pain on refactoring system. - -## ➡️ Entrypoints - -### Restful API - -API Base endpoint when deployed with default docker compose: [127.0.0.1:8080](http://127.0.0.1:8080), also see [docs](#openapi-docs). - -### Admin panel - -Django admin panel, used for moderation, see [this](#moderation). - -### Telegram Bot - -Link: [t.me/adnova_bot](https://t.me/adnova_bot) - -Basic commands: - -/start - Start the bot and authenticate as advertiser - -/help - Get list of all commands - -/campaigns - Manage advertiser campaigns (only after authentication) - -/statistics - See advertiser overall statistics (only after authentication) - -/logout - Logout of current advertiser account (only after authentication) - -See [this](#telegram-bot-1). - -### Grafana - -When deployed with default docker compose: [127.0.0.1:13243](http://127.0.0.1:13243). See more details about this [here](#grafana-dashboard). - -## ✨ Features - -### Notes about basic features - -I cache every mlscore in redis (btw, on startup of docker compose i upload each cache in db for stability) and also i cache clicks and impressions count for campaigns. This increases perfomance of suggesting algorithm and increases clients satisfaction!_) - -### Clever suggesting algotithm - -Here is how suggesting algotitm looks like: - -1. Filter all campaigns and left only that currently active and matches user targeting. -2. Filter all campaigns with exceeded impressions, but to make more money i let exceed limit by 10% with chance 25% -3. Creating metrics for each campaign - 1. Profit: cost_per_impression (=0 if already viewed), cost_per_click (=0 if already clicked) - 2. Mlscores: from cache - 3. Capacity: 1 - ((impressions_limit - actual_impressions) / impressions_limit) -4. Normalization - 1. Normalizing profit from 0 to 1 - 2. Normalizing mlscores from 0 to 1 - 3. Capacity already normalized -5. Scoring each campaign: `0.8 * normalized_profit + 0.4 * normalized_ml + 0.05 * capacity` -6. Finding campaign with max score and returning it to user - -### Telegram bot - -With this bot you can easily manage your campaigns and see statistics. - -Demonstration: - -![telegram](./assets/gifs/telegram.gif) - -### Campaign image upload - -Advertisers can upload images to campaigns and delete with endpoints: `POST /advertisers/{advertiser_id}/campaigns/{campaign_id}/ad_image` and `DELETE /advertisers/{advertiser_id}/campaigns/{campaign_id}/ad_image`, for more details see [docs](#openapi-docs). - -![ad_image](./assets/gifs/ad_image.gif) - -### Campaign text generation - -Advertisers can generate text for their campaigns with their name and campaign title with `/generate/ad_text` endpoint, but it just returns a promise with `task_id` and client can get generation result at `/generate/ad_text/{task_id}/result`, for detailed info see [docs](#openapi-docs). - -Prompt used to generate: - -```text -Сгенерируй креативный рекламный текст для рекламодателя: "{advertiser_name}", -который проводит рекламную кампанию с названием: "{ad_title}" - -Требования: -1. Текст должен быть максимально привлекательным и продающим -2. Использовать современные маркетинговые приемы -3. Включить призыв к действию -4. Соблюдать структуру: заголовок - основной текст - заключение -5. Длина: 3-6 коротких предложений -6. Ответ должен содержать только текст рекламы без дополнительных комментариев - -Пример хорошего текста: -Запустите свой бизнес в космос с {{advertiser_name}}! Кампания "{{ad_title}}" предлагает -уникальные решения для цифрового продвижения. Присоединяйтесь к лидерам рынка - получите -персональную консультацию сегодня! -``` - -Demonstration: - -![ad_image generation](./assets/gifs/ad_text-generation.gif) - -### Moderation - -Moderation implemented via report system. Client goes to `/report` ([see OpenAPI docs](#openapi-docs)) and submits a report, then llm ([YandexGPT-lite](https://yandex.cloud/en/services/yandexgpt)) in task mode (does stuff on the background with Celery) checks for potential violation and sets the `flagged_by_llm` to True or False and this just a little help to moderators to make more accurate decisions. Btw, moderators can do anything they think they should do, for example make some changes, censor some content or even delete campaign. -Also admin user (whoose creditionals specified lower) can add new staff members and even create a specified group for them (this is built-in django capabilities). -Report has four states: Sent, Under review, Took action and Skipped. Admin panel has filtration by states and by flagged by llm status. - -Admin panel when deployed with docker compose (by default): [localhost:8080/admin/](http://localhost:8080/admin/) - -Reports list when deployed with docker compose (requires authentication): [localhost:8080/admin/campaign/campaignreport/](http://localhost:8080/admin/campaign/campaignreport/) - -Default username: `admin` - -Default password: `admin` - -Prompt used for moderation: - -```text -Ты — строгий AI-модератор контента. Анализируй текст ПО ВСЕМ указанным критериям. -Если ЛЮБОЙ из критериев нарушен — верни true. Только если ВСЕ критерии соблюдены — верни false. - -Критерии нарушений (true): -1. Нецензурная лексика: мат, эвфемизмы, оскорбительные выражения -2. Угрозы: прямые/косвенные угрозы жизни, шантаж, буллинг -3. Дискриминация: расизм, сексизм, ксенофобия, гомофобия -``` - -Btw, if llm returns `В интернете есть много сайтов с информацией на эту тему. [Посмотрите, что нашлось в поиске](https://ya.ru)` it also means that text is unacceptable) - -Demonstration: - -![moderation](./assets/gifs/moderation.gif) - -### Grafana dashboard - -When deployed with default docker compose: [localhost:13243](http://localhost:13243/) - -Default login: `admin` - -Default password: `proooooood` - -Analytics dashboard when deployed with default docker compose: [localhost:13243/d/adnova-statistics/statistics](http://localhost:13243/d/adnova-statisticss/statistics). You can enter advertiser id and get detailed advertiser statistics and also detailed statistics for each advertiser's campaign. - -Demonstration: - -![grafana](./assets/gifs/grafana.gif) - -### OpenAPI docs - -When deployed with default docker compose: [localhost:8080/docs](http://localhost:8080/docs) - -### Healthcheck endpoint - -When deployed with default docker compose: [localhost:8080/health](http://localhost:8080/health) - -Lets developers easily understand and identify problem and users check services health. - -![Healthcheck endpoint](./assets/images/healthcheck.png) - -### PgAdmin - -When deployed with default docker compose: [localhost:13242](http://localhost:13242) - -Default email: `admin@mail.com` - -Default password: `password` - -Default password for existing postgres server: `postgres` - -Not enough of basic django admin? Here is pgadmin, which is the most powerfull instrument to manage and administrate your postgres instance. (btw, text is not gpt-generated as it could be seen at first glance). - -![PgAdmin](./assets/images/pgadmin.png) - -## Testing - -You can find out about project tests [here](./tests/README.md). - -## Code quality - -All Python code linted and formatted with [Ruff](https://astral.sh/ruff) and i tried my best to keep code and architecture clean and simple :) diff --git a/solution/infrastructure/pgadmin/password b/solution/infrastructure/pgadmin/password deleted file mode 100644 index f77b004..0000000 --- a/solution/infrastructure/pgadmin/password +++ /dev/null @@ -1 +0,0 @@ -admin \ No newline at end of file diff --git a/solution/infrastructure/postgres/password b/solution/infrastructure/postgres/password deleted file mode 100644 index 7d72bd7..0000000 --- a/solution/infrastructure/postgres/password +++ /dev/null @@ -1 +0,0 @@ -postgres \ No newline at end of file diff --git a/solution/tests/README.md b/tests/README.md similarity index 100% rename from solution/tests/README.md rename to tests/README.md diff --git a/solution/tests/e2e/.env.template b/tests/e2e/.env.template similarity index 100% rename from solution/tests/e2e/.env.template rename to tests/e2e/.env.template diff --git a/solution/tests/e2e/.gitignore b/tests/e2e/.gitignore similarity index 100% rename from solution/tests/e2e/.gitignore rename to tests/e2e/.gitignore diff --git a/solution/tests/e2e/README.md b/tests/e2e/README.md similarity index 100% rename from solution/tests/e2e/README.md rename to tests/e2e/README.md diff --git a/solution/tests/e2e/conftest.py b/tests/e2e/conftest.py similarity index 100% rename from solution/tests/e2e/conftest.py rename to tests/e2e/conftest.py diff --git a/solution/tests/e2e/pyproject.toml b/tests/e2e/pyproject.toml similarity index 100% rename from solution/tests/e2e/pyproject.toml rename to tests/e2e/pyproject.toml diff --git a/solution/tests/e2e/pytest.ini b/tests/e2e/pytest.ini similarity index 100% rename from solution/tests/e2e/pytest.ini rename to tests/e2e/pytest.ini diff --git a/solution/tests/e2e/scripts/check b/tests/e2e/scripts/check similarity index 100% rename from solution/tests/e2e/scripts/check rename to tests/e2e/scripts/check diff --git a/solution/tests/e2e/tests/__init__.py b/tests/e2e/tests/__init__.py similarity index 100% rename from solution/tests/e2e/tests/__init__.py rename to tests/e2e/tests/__init__.py diff --git a/solution/tests/e2e/tests/test_ad_text_generation.py b/tests/e2e/tests/test_ad_text_generation.py similarity index 100% rename from solution/tests/e2e/tests/test_ad_text_generation.py rename to tests/e2e/tests/test_ad_text_generation.py diff --git a/solution/tests/e2e/tests/test_backend_health.py b/tests/e2e/tests/test_backend_health.py similarity index 100% rename from solution/tests/e2e/tests/test_backend_health.py rename to tests/e2e/tests/test_backend_health.py