BTHLABS-50: Safari Web extension

Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
This commit is contained in:
2025-09-08 18:11:36 +00:00
committed by Tomek Wójcik
parent ffecf780ee
commit b6d02dbe78
184 changed files with 7536 additions and 163 deletions

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import urllib.parse
import uuid
import pydantic
from hotpocket_common.constants import AccessTokenOriginApp
from .base import ModelOut, Query
class AccessTokenOut(ModelOut):
key: str
origin: str
meta: dict
def get_parsed_origin(self) -> urllib.parse.SplitResult:
return urllib.parse.urlsplit(self.origin)
def get_origin_app(self) -> AccessTokenOriginApp | None:
parsed_origin = self.get_parsed_origin()
match parsed_origin.scheme:
case 'safari-web-extension':
return AccessTokenOriginApp.SAFARI_WEB_EXTENSION
case _:
return None
def get_origin_app_id(self) -> str:
return self.get_parsed_origin().netloc
class AccessTokensQuery(Query):
account_uuid: uuid.UUID
before: uuid.UUID | None = pydantic.Field(default=None)

View File

@@ -19,6 +19,9 @@ class ModelOut(pydantic.BaseModel):
def pk(self) -> uuid.UUID:
return self.id
def to_rpc(self) -> dict:
return self.dict()
class Query(pydantic.BaseModel):
pass

View File

@@ -1,3 +1,4 @@
from .access_tokens import AccessTokensService # noqa: F401
from .associations import AssociationsService # noqa: F401
from .bot import BotService # noqa: F401
from .save_processor import SaveProcessorService # noqa: F401

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import uuid
from hotpocket_backend.apps.accounts.services import (
AccessTokensService as BackendAccessTokensService,
)
from hotpocket_soa.dto.accounts import AccessTokenOut, AccessTokensQuery
from .base import ProxyService, SOAError
class AccessTokensService(ProxyService):
class AccessTokensServiceError(SOAError):
pass
class AccessTokenNotFound(AccessTokensServiceError):
pass
class AccessTokenAccessDenied(AccessTokensServiceError):
pass
def __init__(self):
super().__init__()
self.backend_access_tokens_service = BackendAccessTokensService()
def wrap_exception(self, exception: Exception) -> Exception:
new_exception_args = []
if len(exception.args) > 0:
new_exception_args = [exception.args[0]]
return self.AccessTokensServiceError(*new_exception_args)
def create(self,
*,
account_uuid: uuid.UUID,
origin: str,
meta: dict,
) -> AccessTokenOut:
return AccessTokenOut.model_validate(
self.call(
self.backend_access_tokens_service,
'create',
account_uuid=account_uuid,
origin=origin,
meta=meta,
),
from_attributes=True,
)
def get(self,
*,
account_uuid: uuid.UUID,
pk: uuid.UUID,
) -> AccessTokenOut:
try:
result = AccessTokenOut.model_validate(
self.call(
self.backend_access_tokens_service,
'get',
pk=pk,
),
from_attributes=True,
)
if result.account_uuid != account_uuid:
raise self.AccessTokenAccessDenied(
f'account_uuid=`{account_uuid}` pk=`{pk}`',
)
return result
except SOAError as exception:
if isinstance(exception.__cause__, BackendAccessTokensService.AccessTokenNotFound) is True:
raise self.AccessTokenNotFound(f'account_uuid=`{account_uuid}` pk=`{pk}`') from exception
else:
raise
def search(self,
*,
query: AccessTokensQuery,
limit: int,
) -> list[AccessTokenOut]:
return [
AccessTokenOut.model_validate(row, from_attributes=True)
for row
in self.call(
self.backend_access_tokens_service,
'search',
query=query,
limit=limit,
)
]
def delete(self, *, access_token: AccessTokenOut) -> bool:
return self.call(
self.backend_access_tokens_service,
'delete',
pk=access_token.pk,
)