You've already forked RekomenciBackend
fix merge conflict
This commit is contained in:
@@ -344,3 +344,4 @@ workflow:
|
|||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ build:
|
|||||||
[group("Docker")]
|
[group("Docker")]
|
||||||
[doc("Compose start")]
|
[doc("Compose start")]
|
||||||
up:
|
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
|
# > Tests
|
||||||
@@ -29,7 +29,7 @@ tests:
|
|||||||
just up
|
just up
|
||||||
|
|
||||||
docker compose --profile migrations --profile tests up tests --remove-orphans --abort-on-container-exit
|
docker compose --profile migrations --profile tests up tests --remove-orphans --abort-on-container-exit
|
||||||
|
|
||||||
# =========
|
# =========
|
||||||
# > Lints
|
# > Lints
|
||||||
# =========
|
# =========
|
||||||
@@ -44,10 +44,21 @@ lint:
|
|||||||
bandit src tests
|
bandit src tests
|
||||||
|
|
||||||
[no-cd]
|
[no-cd]
|
||||||
[group("Formatters run")]
|
[group("Formatters")]
|
||||||
format:
|
format:
|
||||||
ruff format
|
ruff format
|
||||||
|
|
||||||
|
# =========
|
||||||
|
# > Overall check
|
||||||
|
# =========
|
||||||
|
|
||||||
|
[no-cd]
|
||||||
|
[group("Check")]
|
||||||
|
[doc("Check before commit")]
|
||||||
|
check:
|
||||||
|
just lint
|
||||||
|
just tests
|
||||||
|
|
||||||
# =========
|
# =========
|
||||||
# > Migrations
|
# > Migrations
|
||||||
# =========
|
# =========
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ dependencies = [
|
|||||||
"psycopg[binary]>=3.2.12",
|
"psycopg[binary]>=3.2.12",
|
||||||
"firebase-admin>=7.1.0",
|
"firebase-admin>=7.1.0",
|
||||||
"aioboto3==15.5.0",
|
"aioboto3==15.5.0",
|
||||||
|
"prometheus-fastapi-instrumentator>=7.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from template_project.adapters.data_gateways.tables import notification_device_table
|
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.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):
|
class DefaultNotificationDeviceDataGateway(NotificationDeviceDataGateway):
|
||||||
@@ -18,3 +18,16 @@ class DefaultNotificationDeviceDataGateway(NotificationDeviceDataGateway):
|
|||||||
statement = select(NotificationDevice).where(notification_device_table.c.user_id == user_id)
|
statement = select(NotificationDevice).where(notification_device_table.c.user_id == user_id)
|
||||||
result = await self._session.execute(statement)
|
result = await self._session.execute(statement)
|
||||||
return result.scalar_one_or_none()
|
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()
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from sqlalchemy.orm import registry
|
|||||||
|
|
||||||
from template_project.application.access_token.entity import AccessToken
|
from template_project.application.access_token.entity import AccessToken
|
||||||
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
|
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.entity import User
|
||||||
from template_project.application.user.notification_device.entity import NotificationDevice
|
|
||||||
from template_project.application.user.profile.entity import Profile
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
meta_data = MetaData()
|
meta_data = MetaData()
|
||||||
|
|||||||
+9
-1
@@ -1,11 +1,19 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Protocol
|
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.entity import UserId
|
||||||
from template_project.application.user.notification_device.entity import NotificationDevice
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationDeviceDataGateway(Protocol):
|
class NotificationDeviceDataGateway(Protocol):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def load_by_user_id(self, user_id: UserId) -> NotificationDevice | None:
|
async def load_by_user_id(self, user_id: UserId) -> NotificationDevice | None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def load_by_user_id_and_device_id(
|
||||||
|
self,
|
||||||
|
user_id: UserId,
|
||||||
|
device_id: str,
|
||||||
|
) -> NotificationDevice | None:
|
||||||
|
raise NotImplementedError
|
||||||
@@ -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()
|
||||||
+2
-2
@@ -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.identity_provider import IdentityProvider
|
||||||
from template_project.application.common.interactor import to_interactor
|
from template_project.application.common.interactor import to_interactor
|
||||||
from template_project.application.common.notifications.service import NotificationService
|
from template_project.application.common.notifications.service import NotificationService
|
||||||
from template_project.application.user.notification_device.data_gateway import NotificationDeviceDataGateway
|
from template_project.application.notification_device.data_gateway import NotificationDeviceDataGateway
|
||||||
from template_project.application.user.notification_device.errors import NotificationDeviceNotFoundError
|
from template_project.application.notification_device.errors import NotificationDeviceNotFoundError
|
||||||
|
|
||||||
|
|
||||||
@to_data_structure
|
@to_data_structure
|
||||||
@@ -15,6 +15,7 @@ from dishka.integrations.fastapi import setup_dishka
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from firebase_admin import credentials
|
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.configuration import Configuration, load_configuration
|
||||||
from template_project.web_api.ioc.make import make_ioc
|
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(healthcheck.router)
|
||||||
app.include_router(profile.router)
|
app.include_router(profile.router)
|
||||||
app.include_router(notification.router)
|
app.include_router(notification.router)
|
||||||
|
Instrumentator().instrument(app).expose(app)
|
||||||
|
|
||||||
setup_dishka(container=ioc, app=app)
|
setup_dishka(container=ioc, app=app)
|
||||||
|
|
||||||
|
|||||||
@@ -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_in import SignInInteractor
|
||||||
from template_project.application.auth_identity.interactors.sign_up import SignUpInteractor
|
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.get_profile import GetProfileInteractor
|
||||||
from template_project.application.user.profile.interactors.patch_profile import PatchProfileInteractor
|
from template_project.application.user.profile.interactors.patch_profile import PatchProfileInteractor
|
||||||
|
|
||||||
@@ -16,4 +19,5 @@ class InteractorProvider(Provider):
|
|||||||
GetProfileInteractor,
|
GetProfileInteractor,
|
||||||
PatchProfileInteractor,
|
PatchProfileInteractor,
|
||||||
NotificationInteractor,
|
NotificationInteractor,
|
||||||
|
RegisterNotificationDeviceInteractor,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||||||
from fastapi.security import HTTPBearer
|
from fastapi.security import HTTPBearer
|
||||||
from pydantic import BaseModel
|
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,
|
NotificationInteractor,
|
||||||
SendNotificationRequest,
|
SendNotificationRequest,
|
||||||
)
|
)
|
||||||
from template_project.application.user.notification_device.errors import NotificationDeviceNotFoundError
|
|
||||||
|
|
||||||
security = HTTPBearer()
|
security = HTTPBearer()
|
||||||
router = APIRouter(route_class=DishkaRoute, tags=["Notifications"], dependencies=[Depends(security)])
|
router = APIRouter(route_class=DishkaRoute, tags=["Notifications"], dependencies=[Depends(security)])
|
||||||
@@ -19,6 +23,10 @@ class SendNotificationRequestModel(BaseModel):
|
|||||||
body: str
|
body: str
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterNotificationDeviceRequestModel(BaseModel):
|
||||||
|
device_id: str
|
||||||
|
|
||||||
|
|
||||||
@router.post("/notifications/send")
|
@router.post("/notifications/send")
|
||||||
async def send_notification(
|
async def send_notification(
|
||||||
request: SendNotificationRequestModel,
|
request: SendNotificationRequestModel,
|
||||||
@@ -29,3 +37,12 @@ async def send_notification(
|
|||||||
await interactor.send_notification(notification_request)
|
await interactor.send_notification(notification_request)
|
||||||
except NotificationDeviceNotFoundError as error:
|
except NotificationDeviceNotFoundError as error:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Notification device not found") from 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)
|
||||||
|
|||||||
@@ -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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "proto-plus"
|
name = "proto-plus"
|
||||||
version = "1.26.1"
|
version = "1.26.1"
|
||||||
@@ -1406,6 +1428,7 @@ dependencies = [
|
|||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "firebase-admin" },
|
{ name = "firebase-admin" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
{ name = "prometheus-fastapi-instrumentator" },
|
||||||
{ name = "psycopg", extra = ["binary"] },
|
{ name = "psycopg", extra = ["binary"] },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
{ name = "uuid-utils" },
|
{ name = "uuid-utils" },
|
||||||
@@ -1452,6 +1475,7 @@ requires-dist = [
|
|||||||
{ name = "fastapi", specifier = "==0.119.0" },
|
{ name = "fastapi", specifier = "==0.119.0" },
|
||||||
{ name = "firebase-admin", specifier = ">=7.1.0" },
|
{ name = "firebase-admin", specifier = ">=7.1.0" },
|
||||||
{ name = "httpx", specifier = "==0.28.1" },
|
{ name = "httpx", specifier = "==0.28.1" },
|
||||||
|
{ name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" },
|
||||||
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" },
|
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" },
|
||||||
{ name = "sqlalchemy", specifier = "==2.0.44" },
|
{ name = "sqlalchemy", specifier = "==2.0.44" },
|
||||||
{ name = "uuid-utils", specifier = "==0.11.1" },
|
{ name = "uuid-utils", specifier = "==0.11.1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user