Release v1.0.0
Some checks failed
CI / Checks (push) Failing after 13m2s

This commit is contained in:
2025-08-20 21:00:50 +02:00
commit b4338e2769
401 changed files with 23576 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
from .associations import AssociationsService # noqa: F401
from .save_processor import SaveProcessorService # noqa: F401
from .saves import SavesService # noqa: F401

View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import datetime
import logging
import uuid
from django.db import models
from django.utils.timezone import now
from hotpocket_backend.apps.core.services import get_adapter
from hotpocket_backend.apps.saves.models import Association, Save
from hotpocket_backend.apps.saves.types import PAssociationAdapter
from hotpocket_soa.dto.associations import (
AssociationsQuery,
AssociationUpdateIn,
)
from .saves import SavesService
LOGGER = logging.getLogger(__name__)
class AssociationsService:
class AssociationsServiceError(Exception):
pass
class AssociationNotFound(AssociationsServiceError):
pass
@property
def adapter(self) -> PAssociationAdapter:
if hasattr(self, '_adapter') is False:
adapter_klass = get_adapter(
'SAVES_ASSOCIATION_ADAPTER',
'hotpocket_backend.apps.saves.adapters.basic:BasicAssociationAdapter',
)
self._adapter = adapter_klass()
return self._adapter
def create(self,
*,
account_uuid: uuid.UUID,
save_uuid: uuid.UUID,
pk: uuid.UUID | None = None,
created_at: datetime.datetime | None = None,
) -> Association:
save = SavesService().get(pk=save_uuid)
defaults = dict(
account_uuid=account_uuid,
target=save,
)
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,
)
if created is True:
if created_at is not None:
result.created_at = created_at
result.save()
return result
def get(self,
*,
pk: uuid.UUID,
with_target: bool = False,
) -> Association:
try:
query_set = Association.active_objects.\
filter(
target__deleted_at__isnull=True,
)
if with_target is True:
query_set = query_set.select_related('target')
return query_set.get(pk=pk)
except Association.DoesNotExist as exception:
raise self.AssociationNotFound(
f'Association not found: pk=`{pk}`',
) from exception
def search(self,
*,
query: AssociationsQuery,
offset: int = 0,
limit: int = 10,
order_by: str = '-pk',
) -> models.QuerySet[Save]:
filters = self.adapter.get_search_filters(query=query)
result = Association.active_objects.\
select_related('target').\
filter(*filters).\
order_by(order_by)
return result[offset:offset + limit]
def update(self,
*,
pk: uuid.UUID,
update: AssociationUpdateIn,
) -> Association:
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.pop('title', None)
next_target_meta.pop('description', None)
association.target_meta = next_target_meta
association.save()
return association
def archive(self, *, pk: uuid.UUID) -> bool:
association = self.get(pk=pk)
association.archived_at = now()
association.save()
return True
def star(self, *, pk: uuid.UUID) -> Association:
association = self.get(pk=pk)
if association.starred_at is None:
association.starred_at = now()
association.save()
return association
def unstar(self, *, pk: uuid.UUID) -> Association:
association = self.get(pk=pk)
if association.starred_at is not None:
association.starred_at = None
association.save()
return association
def delete(self, *, pk: uuid.UUID) -> bool:
association = self.get(pk=pk)
association.soft_delete()
return True

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
import uuid
from celery.result import AsyncResult
from django.utils.timezone import now
from hotpocket_backend.apps.bot.services import BotService
from hotpocket_backend.apps.saves.tasks import process_save
from .saves import SavesService
LOGGER = logging.getLogger(__name__)
class SaveProcessorService:
class SaveProcessorServiceError(Exception):
pass
class SkipReprocessing(SaveProcessorServiceError):
pass
def process(self, *, pk: uuid.UUID) -> bool:
result = True
try:
save = SavesService().get_for_processing(pk=pk)
assert save is not None, ('Could not fetch the save.')
title: str = save.url
description: str | None = None
is_netloc_banned = False
try:
bot_result = BotService().handle(url=save.url)
if bot_result.title is not None:
title = bot_result.title
description = bot_result.description
is_netloc_banned = bot_result.is_netloc_banned
except Exception as exception:
LOGGER.error(
'Unhandled exception when fetching save content: %s',
exception,
exc_info=exception,
)
if save.last_processed_at is not None:
raise self.SkipReprocessing(
'Not re-processing the save fetch error!',
) from exception
save.title = title
save.description = description
save.is_netloc_banned = is_netloc_banned
save.last_processed_at = now()
save.save()
except Exception as exception:
LOGGER.error(
'Unhandled exception: pk=`%s`: %s',
pk,
exception,
exc_info=exception,
)
result = False
return result
def schedule_process_save(self, *, pk: uuid.UUID) -> AsyncResult:
return process_save.apply_async(
kwargs={
'pk': pk,
},
)

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import hashlib
import typing
import uuid
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
class SavesService:
class SavesServiceError(Exception):
pass
class SaveNotFound(SavesServiceError):
pass
@property
def adapter(self) -> PSaveAdapter:
if hasattr(self, '_adapter') is False:
adapter_klass = get_adapter(
'SAVES_SAVE_ADAPTER',
'hotpocket_backend.apps.saves.adapters.basic:BasicSaveAdapter',
)
self._adapter = adapter_klass()
return self._adapter
def create(self,
*,
account_uuid: uuid.UUID,
save: SaveIn | ImportedSaveIn,
) -> Save:
key = hashlib.sha256(save.url.encode('utf-8')).hexdigest()
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,
)
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]
save_object.save()
return save_object
def get(self, *, pk: uuid.UUID) -> Save:
try:
return Save.active_objects.get(pk=pk)
except Save.DoesNotExist as exception:
raise self.SaveNotFound(
f'Save not found: pk=`{pk}`',
) from exception
def search(self,
*,
filters: typing.Any,
offset: int = 0,
limit: int = 10,
order: str = '-created_at',
) -> models.QuerySet[Save]:
raise NotImplementedError('TODO')
def update(self, *, pk: uuid.UUID, save: SaveIn) -> Save:
raise NotImplementedError('TODO')
def delete(self, *, pk: uuid.UUID) -> bool:
raise NotImplementedError('TODO')
def search_associated_to_account(self,
*,
account_uuid: uuid.UUID,
query: SavesQuery,
offset: int = 0,
limit: int = 10,
order_by: str = '-associations__created_at',
) -> models.QuerySet[Save]:
result = Save.active_objects.\
filter(
associations__account_uuid=account_uuid,
associations__archived_at__isnull=True,
).\
order_by(order_by)
return result
def get_for_processing(self, *, pk: uuid.UUID) -> Save:
return self.adapter.get_for_processing(pk=pk)