BTHLABS-61: Service layer refactoring

A journey to fix `ValidationError` in Pocket imports turned service
layer refactoring :D
This commit is contained in:
2025-10-12 18:37:32 +00:00
parent ac7a8dd90e
commit 8b86145519
45 changed files with 1023 additions and 337 deletions

View File

@@ -5,6 +5,7 @@ import datetime
import logging
import uuid
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.timezone import now
@@ -15,6 +16,10 @@ from hotpocket_soa.dto.associations import (
AssociationsQuery,
AssociationUpdateIn,
)
from hotpocket_soa.exceptions.backend import (
Invalid as InvalidError,
NotFound as NotFoundError,
)
from .saves import SavesService
@@ -25,7 +30,10 @@ class AssociationsService:
class AssociationsServiceError(Exception):
pass
class AssociationNotFound(AssociationsServiceError):
class Invalid(InvalidError, AssociationsServiceError):
pass
class NotFound(NotFoundError, AssociationsServiceError):
pass
@property
@@ -46,30 +54,33 @@ class AssociationsService:
pk: uuid.UUID | None = None,
created_at: datetime.datetime | None = None,
) -> Association:
save = SavesService().get(pk=save_uuid)
try:
save = SavesService().get(pk=save_uuid)
defaults = dict(
account_uuid=account_uuid,
target=save,
)
defaults = dict(
account_uuid=account_uuid,
target=save,
)
if pk is not None:
defaults['id'] = pk
if pk is not None:
defaults['id'] = pk
result, created = Association.objects.get_or_create(
account_uuid=account_uuid,
deleted_at__isnull=True,
target=save,
archived_at__isnull=True,
defaults=defaults,
)
result, created = Association.objects.get_or_create(
account_uuid=account_uuid,
deleted_at__isnull=True,
target=save,
archived_at__isnull=True,
defaults=defaults,
)
if created is True:
if created_at is not None:
result.created_at = created_at
result.save()
if created is True:
if created_at is not None:
result.created_at = created_at
result.save()
return result
return result
except ValidationError as exception:
raise self.Invalid.from_django_validation_error(exception)
def get(self,
*,
@@ -87,7 +98,7 @@ class AssociationsService:
return query_set.get(pk=pk)
except Association.DoesNotExist as exception:
raise self.AssociationNotFound(
raise self.NotFound(
f'Association not found: pk=`{pk}`',
) from exception
@@ -112,21 +123,24 @@ class AssociationsService:
pk: uuid.UUID,
update: AssociationUpdateIn,
) -> Association:
association = self.get(pk=pk)
association.target_title = update.target_title
association.target_description = update.target_description
try:
association = self.get(pk=pk)
association.target_title = update.target_title
association.target_description = update.target_description
next_target_meta = {
**(association.target_meta or {}),
}
next_target_meta = {
**(association.target_meta or {}),
}
next_target_meta.pop('title', None)
next_target_meta.pop('description', None)
association.target_meta = next_target_meta
next_target_meta.pop('title', None)
next_target_meta.pop('description', None)
association.target_meta = next_target_meta
association.save()
association.save()
return association
return association
except ValidationError as exception:
raise self.Invalid.from_django_validation_error(exception)
def archive(self, *, pk: uuid.UUID) -> bool:
association = self.get(pk=pk)

View File

@@ -5,19 +5,27 @@ import hashlib
import typing
import uuid
from django.core.exceptions import ValidationError
from django.db import models
from hotpocket_backend.apps.core.services import get_adapter
from hotpocket_backend.apps.saves.models import Save
from hotpocket_backend.apps.saves.types import PSaveAdapter
from hotpocket_soa.dto.saves import ImportedSaveIn, SaveIn, SavesQuery
from hotpocket_soa.exceptions.backend import (
Invalid as InvalidError,
NotFound as NotFoundError,
)
class SavesService:
class SavesServiceError(Exception):
pass
class SaveNotFound(SavesServiceError):
class Invalid(InvalidError, SavesServiceError):
pass
class NotFound(NotFoundError, SavesServiceError):
pass
@property
@@ -36,35 +44,38 @@ class SavesService:
account_uuid: uuid.UUID,
save: SaveIn | ImportedSaveIn,
) -> Save:
key = hashlib.sha256(save.url.encode('utf-8')).hexdigest()
try:
key = hashlib.sha256(save.url.encode('utf-8')).hexdigest()
defaults = dict(
account_uuid=account_uuid,
key=key,
url=save.url,
)
defaults = dict(
account_uuid=account_uuid,
key=key,
url=save.url,
)
save_object, created = Save.objects.get_or_create(
key=key,
deleted_at__isnull=True,
defaults=defaults,
)
save_object, created = Save.objects.get_or_create(
key=key,
deleted_at__isnull=True,
defaults=defaults,
)
if created is True:
save_object.is_netloc_banned = save.is_netloc_banned
if created is True:
save_object.is_netloc_banned = save.is_netloc_banned
if isinstance(save, ImportedSaveIn) is True:
save_object.title = save.title # type: ignore[union-attr]
if isinstance(save, ImportedSaveIn) is True:
save_object.title = save.title # type: ignore[union-attr]
save_object.save()
save_object.save()
return save_object
return save_object
except ValidationError as exception:
raise self.Invalid.from_django_validation_error(exception)
def get(self, *, pk: uuid.UUID) -> Save:
try:
return Save.active_objects.get(pk=pk)
except Save.DoesNotExist as exception:
raise self.SaveNotFound(
raise self.NotFound(
f'Save not found: pk=`{pk}`',
) from exception