From 2675a642dad0039dc4fb3a46137af6416b36b6f9 Mon Sep 17 00:00:00 2001 From: ITQ Date: Thu, 20 Nov 2025 20:32:05 +0300 Subject: [PATCH 1/5] feat: added dangerous tagging after all validation steps pass --- .gitlab-ci.yml | 125 ++++++++++++++++++++++++++++++++++------------ compose.prod.yaml | 6 +-- 2 files changed, 95 insertions(+), 36 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 55ba470..23b1bb7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - build - test - security + - tag - deploy variables: @@ -102,15 +103,22 @@ cache: when: never .webhook-template: &webhook-config - stage: deploy image: curlimages/curl:latest script: - | - curl -s -X POST \ + response=$(curl -s -w "\n%{http_code}" -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $WEBHOOK_SECRET_TOKEN" \ -H "Webhook-Identifier: $WEBHOOK_BYPASS_TOKEN" \ - "$WEBHOOK_URL" + "$WEBHOOK_URL") + + body=$(echo "$response" | sed '$d') + status=$(echo "$response" | tail -n1) + + echo "$body" + + [ $? -ne 0 ] && echo "curl failed" && exit 1 + [ "$status" -lt 200 ] || [ "$status" -ge 300 ] && echo "HTTP $status" && exit 1 rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: on_success @@ -125,8 +133,6 @@ cache: - | docker buildx create --use docker buildx build . \ - -t $IMAGE_NAME:latest \ - -t $IMAGE_NAME:$CI_COMMIT_REF_SLUG \ -t $IMAGE_NAME:$CI_COMMIT_SHA \ -f $CONTAINERFILE --target $BUILDTARGET --push \ --cache-from type=registry,ref=$IMAGE_NAME-cache \ @@ -141,6 +147,42 @@ cache: when: manual allow_failure: true +.tag-template: &tag-config + <<: *docker-job + stage: tag + script: + - | + set -euo pipefail + SOURCE_IMAGE="$IMAGE_NAME:$CI_COMMIT_SHA" + docker pull "$SOURCE_IMAGE" + DANGEROUS_TAGS="" + if [ -n "$CI_COMMIT_TAG" ]; then + DANGEROUS_TAGS="$DANGEROUS_TAGS $CI_COMMIT_TAG" + fi + if [ -n "$CI_COMMIT_BRANCH" ]; then + if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then + DANGEROUS_TAGS="$DANGEROUS_TAGS latest $CI_COMMIT_REF_SLUG" + else + DANGEROUS_TAGS="$DANGEROUS_TAGS $CI_COMMIT_REF_SLUG" + fi + fi + if [ -z "$DANGEROUS_TAGS" ]; then + echo "No tags to publish." + exit 0 + fi + for TAG in $DANGEROUS_TAGS; do + [ -z "$TAG" ] && continue + TARGET_IMAGE="$IMAGE_NAME:$TAG" + docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE" + docker push "$TARGET_IMAGE" + done + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + when: manual + allow_failure: true + .uv-job: &uv-job image: debian:trixie-slim cache: @@ -156,33 +198,6 @@ cache: - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" -sast-filesystem: - <<: *trivy-fs-scan - -sast-image-runtime: - <<: *trivy-image-scan - variables: - IMAGE_NAME: $BASE_IMAGE_NAME/backend - IMAGE_TYPE: runtime - dependencies: - - build-runtime - -sast-image-tests: - <<: *trivy-image-scan - variables: - IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests - IMAGE_TYPE: tests - dependencies: - - build-tests - -sast-image-migrations: - <<: *trivy-image-scan - variables: - IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations - IMAGE_TYPE: migrations - dependencies: - - build-migrations - build-runtime: <<: *build-config variables: @@ -236,7 +251,7 @@ test: ) | tee -a compose.log & - LOGS_PID=$! - | - REGISTRY_PREFIX=$CI_REGISTRY_IMAGE \ + REGISTRY_PREFIX=$CI_REGISTRY_IMAGE IMAGE_TAG=$CI_COMMIT_SHA \ docker compose -f compose.yaml -f compose.prod.yaml \ $PROFILES up -d --quiet-pull --quiet-build 2>&1 | tee compose.log - | @@ -272,8 +287,51 @@ test: - build-tests - build-migrations +sast-filesystem: + <<: *trivy-fs-scan + +sast-image-runtime: + <<: *trivy-image-scan + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend + IMAGE_TYPE: runtime + dependencies: + - build-runtime + +sast-image-tests: + <<: *trivy-image-scan + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests + IMAGE_TYPE: tests + dependencies: + - build-tests + +sast-image-migrations: + <<: *trivy-image-scan + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations + IMAGE_TYPE: migrations + dependencies: + - build-migrations + +tag-runtime: + <<: *tag-config + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend + +tag-tests: + <<: *tag-config + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests + +tag-migrations: + <<: *tag-config + variables: + IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations + webhook-migrations-deploy: <<: *webhook-config + stage: deploy variables: WEBHOOK_URL: $WEBHOOK_URL_MIGRATIONS resource_group: staging @@ -283,6 +341,7 @@ webhook-migrations-deploy: webhook-backend-deploy: <<: *webhook-config + stage: deploy variables: WEBHOOK_URL: $WEBHOOK_URL_BACKEND environment: diff --git a/compose.prod.yaml b/compose.prod.yaml index c4ec125..f53322a 100644 --- a/compose.prod.yaml +++ b/compose.prod.yaml @@ -1,9 +1,9 @@ services: backend: - image: "${REGISTRY_PREFIX}/backend" + image: "${REGISTRY_PREFIX}/backend:${IMAGE_TAG}" tests: - image: "${REGISTRY_PREFIX}/backend-tests" + image: "${REGISTRY_PREFIX}/backend-tests:${IMAGE_TAG}" migrations: - image: "${REGISTRY_PREFIX}/backend-migrations" + image: "${REGISTRY_PREFIX}/backend-migrations:${IMAGE_TAG}" From 2e073146bf42905d030f717a9d5bd775a27450bf Mon Sep 17 00:00:00 2001 From: gitgernit Date: Fri, 21 Nov 2025 00:18:55 +0300 Subject: [PATCH 2/5] feat(): register device route --- .../data_gateways/notification_device.py | 17 ++++++++- .../adapters/data_gateways/tables.py | 2 +- .../__init__.py | 0 .../notification_device/data_gateway.py | 10 ++++- .../{user => }/notification_device/entity.py | 0 .../{user => }/notification_device/errors.py | 0 .../interactors/register_device.py | 37 +++++++++++++++++++ .../interactors/send_notification.py | 4 +- .../user/notification_device/__init__.py | 0 .../web_api/ioc/interactor.py | 6 ++- .../web_api/routes/notification.py | 21 ++++++++++- 11 files changed, 88 insertions(+), 9 deletions(-) rename src/template_project/application/{user/notification/interactors => notification_device}/__init__.py (100%) rename src/template_project/application/{user => }/notification_device/data_gateway.py (51%) rename src/template_project/application/{user => }/notification_device/entity.py (100%) rename src/template_project/application/{user => }/notification_device/errors.py (100%) create mode 100644 src/template_project/application/notification_device/interactors/register_device.py rename src/template_project/application/{user/notification => notification_device}/interactors/send_notification.py (84%) delete mode 100644 src/template_project/application/user/notification_device/__init__.py diff --git a/src/template_project/adapters/data_gateways/notification_device.py b/src/template_project/adapters/data_gateways/notification_device.py index 5c0dbc2..76b6457 100644 --- a/src/template_project/adapters/data_gateways/notification_device.py +++ b/src/template_project/adapters/data_gateways/notification_device.py @@ -4,9 +4,9 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from template_project.adapters.data_gateways.tables import notification_device_table +from template_project.application.notification_device.data_gateway import NotificationDeviceDataGateway +from template_project.application.notification_device.entity import NotificationDevice from template_project.application.user.entity import UserId -from template_project.application.user.notification_device.data_gateway import NotificationDeviceDataGateway -from template_project.application.user.notification_device.entity import NotificationDevice class DefaultNotificationDeviceDataGateway(NotificationDeviceDataGateway): @@ -18,3 +18,16 @@ class DefaultNotificationDeviceDataGateway(NotificationDeviceDataGateway): statement = select(NotificationDevice).where(notification_device_table.c.user_id == user_id) result = await self._session.execute(statement) return result.scalar_one_or_none() + + @override + async def load_by_user_id_and_device_id( + self, + user_id: UserId, + device_id: str, + ) -> NotificationDevice | None: + statement = select(NotificationDevice).where( + notification_device_table.c.user_id == user_id, + notification_device_table.c.device_id == device_id, + ) + result = await self._session.execute(statement) + return result.scalar_one_or_none() diff --git a/src/template_project/adapters/data_gateways/tables.py b/src/template_project/adapters/data_gateways/tables.py index 411130e..ffa4e3e 100644 --- a/src/template_project/adapters/data_gateways/tables.py +++ b/src/template_project/adapters/data_gateways/tables.py @@ -14,8 +14,8 @@ from sqlalchemy.orm import registry from template_project.application.access_token.entity import AccessToken from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod +from template_project.application.notification_device.entity import NotificationDevice from template_project.application.user.entity import User -from template_project.application.user.notification_device.entity import NotificationDevice from template_project.application.user.profile.entity import Profile meta_data = MetaData() diff --git a/src/template_project/application/user/notification/interactors/__init__.py b/src/template_project/application/notification_device/__init__.py similarity index 100% rename from src/template_project/application/user/notification/interactors/__init__.py rename to src/template_project/application/notification_device/__init__.py diff --git a/src/template_project/application/user/notification_device/data_gateway.py b/src/template_project/application/notification_device/data_gateway.py similarity index 51% rename from src/template_project/application/user/notification_device/data_gateway.py rename to src/template_project/application/notification_device/data_gateway.py index 0225fcd..2805924 100644 --- a/src/template_project/application/user/notification_device/data_gateway.py +++ b/src/template_project/application/notification_device/data_gateway.py @@ -1,11 +1,19 @@ from abc import abstractmethod from typing import Protocol +from template_project.application.notification_device.entity import NotificationDevice from template_project.application.user.entity import UserId -from template_project.application.user.notification_device.entity import NotificationDevice class NotificationDeviceDataGateway(Protocol): @abstractmethod async def load_by_user_id(self, user_id: UserId) -> NotificationDevice | None: raise NotImplementedError + + @abstractmethod + async def load_by_user_id_and_device_id( + self, + user_id: UserId, + device_id: str, + ) -> NotificationDevice | None: + raise NotImplementedError diff --git a/src/template_project/application/user/notification_device/entity.py b/src/template_project/application/notification_device/entity.py similarity index 100% rename from src/template_project/application/user/notification_device/entity.py rename to src/template_project/application/notification_device/entity.py diff --git a/src/template_project/application/user/notification_device/errors.py b/src/template_project/application/notification_device/errors.py similarity index 100% rename from src/template_project/application/user/notification_device/errors.py rename to src/template_project/application/notification_device/errors.py diff --git a/src/template_project/application/notification_device/interactors/register_device.py b/src/template_project/application/notification_device/interactors/register_device.py new file mode 100644 index 0000000..2e64fa8 --- /dev/null +++ b/src/template_project/application/notification_device/interactors/register_device.py @@ -0,0 +1,37 @@ +from template_project.application.common.data_structure import to_data_structure +from template_project.application.common.identity_provider import IdentityProvider +from template_project.application.common.interactor import to_interactor +from template_project.application.common.unit_of_work import UnitOfWork +from template_project.application.notification_device.data_gateway import NotificationDeviceDataGateway +from template_project.application.notification_device.entity import NotificationDevice + + +@to_data_structure +class RegisterNotificationDeviceRequest: + device_id: str + + +@to_interactor +class RegisterNotificationDeviceInteractor: + identity_provider: IdentityProvider + notification_device_data_gateway: NotificationDeviceDataGateway + unit_of_work: UnitOfWork + + async def execute(self, request: RegisterNotificationDeviceRequest) -> None: + current_user = await self.identity_provider.get_current_user() + + existing_device = await self.notification_device_data_gateway.load_by_user_id_and_device_id( + user_id=current_user.id, + device_id=request.device_id, + ) + + if existing_device: + return + + notification_device = NotificationDevice.factory( + user_id=current_user.id, + device_id=request.device_id, + ) + + await self.unit_of_work.add(notification_device) + await self.unit_of_work.commit() diff --git a/src/template_project/application/user/notification/interactors/send_notification.py b/src/template_project/application/notification_device/interactors/send_notification.py similarity index 84% rename from src/template_project/application/user/notification/interactors/send_notification.py rename to src/template_project/application/notification_device/interactors/send_notification.py index 7ebc6d8..0eeefe5 100644 --- a/src/template_project/application/user/notification/interactors/send_notification.py +++ b/src/template_project/application/notification_device/interactors/send_notification.py @@ -2,8 +2,8 @@ from template_project.application.common.data_structure import to_data_structure from template_project.application.common.identity_provider import IdentityProvider from template_project.application.common.interactor import to_interactor from template_project.application.common.notifications.service import NotificationService -from template_project.application.user.notification_device.data_gateway import NotificationDeviceDataGateway -from template_project.application.user.notification_device.errors import NotificationDeviceNotFoundError +from template_project.application.notification_device.data_gateway import NotificationDeviceDataGateway +from template_project.application.notification_device.errors import NotificationDeviceNotFoundError @to_data_structure diff --git a/src/template_project/application/user/notification_device/__init__.py b/src/template_project/application/user/notification_device/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/template_project/web_api/ioc/interactor.py b/src/template_project/web_api/ioc/interactor.py index 264d56b..ce8fac4 100644 --- a/src/template_project/web_api/ioc/interactor.py +++ b/src/template_project/web_api/ioc/interactor.py @@ -2,7 +2,10 @@ from dishka import BaseScope, Provider, Scope, provide_all from template_project.application.auth_identity.interactors.sign_in import SignInInteractor from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor -from template_project.application.user.notification.interactors.send_notification import NotificationInteractor +from template_project.application.notification_device.interactors.register_device import ( + RegisterNotificationDeviceInteractor, +) +from template_project.application.notification_device.interactors.send_notification import NotificationInteractor from template_project.application.user.profile.interactors.get_profile import GetProfileInteractor from template_project.application.user.profile.interactors.patch_profile import PatchProfileInteractor @@ -16,4 +19,5 @@ class InteractorProvider(Provider): GetProfileInteractor, PatchProfileInteractor, NotificationInteractor, + RegisterNotificationDeviceInteractor, ) diff --git a/src/template_project/web_api/routes/notification.py b/src/template_project/web_api/routes/notification.py index 2345465..c258c66 100644 --- a/src/template_project/web_api/routes/notification.py +++ b/src/template_project/web_api/routes/notification.py @@ -4,11 +4,15 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBearer from pydantic import BaseModel -from template_project.application.user.notification.interactors.send_notification import ( +from template_project.application.notification_device.errors import NotificationDeviceNotFoundError +from template_project.application.notification_device.interactors.register_device import ( + RegisterNotificationDeviceInteractor, + RegisterNotificationDeviceRequest, +) +from template_project.application.notification_device.interactors.send_notification import ( NotificationInteractor, SendNotificationRequest, ) -from template_project.application.user.notification_device.errors import NotificationDeviceNotFoundError security = HTTPBearer() router = APIRouter(route_class=DishkaRoute, tags=["Notifications"], dependencies=[Depends(security)]) @@ -19,6 +23,10 @@ class SendNotificationRequestModel(BaseModel): body: str +class RegisterNotificationDeviceRequestModel(BaseModel): + device_id: str + + @router.post("/notifications/send") async def send_notification( request: SendNotificationRequestModel, @@ -29,3 +37,12 @@ async def send_notification( await interactor.send_notification(notification_request) except NotificationDeviceNotFoundError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Notification device not found") from error + + +@router.post("/notifications/register-device") +async def register_notification_device( + request: RegisterNotificationDeviceRequestModel, + interactor: FromDishka[RegisterNotificationDeviceInteractor], +) -> None: + register_request = RegisterNotificationDeviceRequest(device_id=request.device_id) + await interactor.execute(register_request) From 2e0470a833c9b62da6e8e1deb743ebca6b300fdf Mon Sep 17 00:00:00 2001 From: gitgernit Date: Fri, 21 Nov 2025 09:43:18 +0300 Subject: [PATCH 3/5] fix(): revert gitlab ci --- .gitlab-ci.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5a5b0d9..d0ce4ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,15 +110,7 @@ cache: -H "Content-Type: application/json" \ -H "Authorization: Bearer $WEBHOOK_SECRET_TOKEN" \ -H "Webhook-Identifier: $WEBHOOK_BYPASS_TOKEN" \ - "$WEBHOOK_URL") - - body=$(echo "$response" | sed '$d') - status=$(echo "$response" | tail -n1) - - echo "$body" - - [ $? -ne 0 ] && echo "curl failed" && exit 1 - [ "$status" -lt 200 ] || [ "$status" -ge 300 ] && echo "HTTP $status" && exit 1 + "$WEBHOOK_URL" rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: on_success @@ -352,3 +344,4 @@ workflow: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_TAG + From f23c4cbe6d05331a8e0367f00bda855904178a3c Mon Sep 17 00:00:00 2001 From: ITQ Date: Fri, 21 Nov 2025 09:44:35 +0300 Subject: [PATCH 4/5] feat: instrumented backend with prometheus endpoint --- pyproject.toml | 1 + src/template_project/web_api/entry_point.py | 2 ++ uv.lock | 24 +++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4551f4a..9553522 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "httpx==0.28.1", "psycopg[binary]>=3.2.12", "firebase-admin>=7.1.0", + "prometheus-fastapi-instrumentator>=7.1.0", ] [dependency-groups] diff --git a/src/template_project/web_api/entry_point.py b/src/template_project/web_api/entry_point.py index 45b0a25..55874b8 100644 --- a/src/template_project/web_api/entry_point.py +++ b/src/template_project/web_api/entry_point.py @@ -15,6 +15,7 @@ from dishka.integrations.fastapi import setup_dishka from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from firebase_admin import credentials +from prometheus_fastapi_instrumentator import Instrumentator from template_project.web_api.configuration import Configuration, load_configuration from template_project.web_api.ioc.make import make_ioc @@ -72,6 +73,7 @@ def make_asgi_application( app.include_router(healthcheck.router) app.include_router(profile.router) app.include_router(notification.router) + Instrumentator().instrument(app).expose(app) setup_dishka(container=ioc, app=app) diff --git a/uv.lock b/uv.lock index c7c6c33..c3f2e03 100644 --- a/uv.lock +++ b/uv.lock @@ -975,6 +975,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prometheus-fastapi-instrumentator" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prometheus-client" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/6d/24d53033cf93826aa7857699a4450c1c67e5b9c710e925b1ed2b320c04df/prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e", size = 20220, upload-time = "2025-03-19T19:35:05.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/72/0824c18f3bc75810f55dacc2dd933f6ec829771180245ae3cc976195dec0/prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9", size = 19296, upload-time = "2025-03-19T19:35:04.323Z" }, +] + [[package]] name = "proto-plus" version = "1.26.1" @@ -1406,6 +1428,7 @@ dependencies = [ { name = "fastapi" }, { name = "firebase-admin" }, { name = "httpx" }, + { name = "prometheus-fastapi-instrumentator" }, { name = "psycopg", extra = ["binary"] }, { name = "sqlalchemy" }, { name = "uuid-utils" }, @@ -1452,6 +1475,7 @@ requires-dist = [ { name = "fastapi", specifier = "==0.119.0" }, { name = "firebase-admin", specifier = ">=7.1.0" }, { name = "httpx", specifier = "==0.28.1" }, + { name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" }, { name = "sqlalchemy", specifier = "==2.0.44" }, { name = "uuid-utils", specifier = "==0.11.1" }, From 262ac5cc38be020ce85ef9087d122af38679baca Mon Sep 17 00:00:00 2001 From: ITQ Date: Fri, 21 Nov 2025 09:44:44 +0300 Subject: [PATCH 5/5] chore: improved Justfile --- .justfile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.justfile b/.justfile index ab6643e..738ad14 100644 --- a/.justfile +++ b/.justfile @@ -16,7 +16,7 @@ build: [group("Docker")] [doc("Compose start")] up: - docker compose --profile migrations --profile observability up -d --remove-orphans --quiet-pull + docker compose --profile migrations --profile observability up -d --remove-orphans --quiet-pull --force-recreate # ========= # > Tests @@ -29,7 +29,7 @@ tests: just up docker compose --profile migrations --profile tests up tests --remove-orphans --abort-on-container-exit - + # ========= # > Lints # ========= @@ -44,10 +44,21 @@ lint: bandit src tests [no-cd] -[group("Formatters run")] +[group("Formatters")] format: ruff format +# ========= +# > Overall check +# ========= + +[no-cd] +[group("Check")] +[doc("Check before commit")] +check: + just lint + just tests + # ========= # > Migrations # =========