You've already forked hotpocket
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29c732faa0 | |||
| dcebccf947 | |||
| 67138c7035 |
@@ -66,7 +66,7 @@ $ docker run --rm -it \
|
||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
|
||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
|
||||
-p 8000:8000 \
|
||||
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.8-01
|
||||
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.12-01
|
||||
```
|
||||
|
||||
The command above will set up and start the application. The SQLite file will
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
backend:
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.8-01"
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.9.12-01"
|
||||
environment:
|
||||
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
|
||||
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"
|
||||
|
||||
@@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
|
||||
|
||||
services:
|
||||
webapp:
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.8-01"
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
|
||||
environment:
|
||||
<<: *x-backend-environment
|
||||
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
|
||||
@@ -21,7 +21,7 @@ services:
|
||||
restart: "unless-stopped"
|
||||
|
||||
admin:
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.8-01"
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
|
||||
environment:
|
||||
<<: *x-backend-environment
|
||||
HOTPOCKET_BACKEND_APP: "admin"
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
restart: "unless-stopped"
|
||||
|
||||
celery-worker:
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.8-01"
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
|
||||
command:
|
||||
- "/srv/venv/bin/celery"
|
||||
- "-A"
|
||||
@@ -57,7 +57,7 @@ services:
|
||||
restart: "unless-stopped"
|
||||
|
||||
celery-beat:
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.8-01"
|
||||
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.9.12-01"
|
||||
command:
|
||||
- "/srv/venv/bin/celery"
|
||||
- "-A"
|
||||
|
||||
@@ -462,7 +462,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||
@@ -474,12 +474,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS.Extension;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -492,7 +492,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||
@@ -504,12 +504,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS.Extension;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -525,31 +525,36 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -561,31 +566,36 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.iOS;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
@@ -598,7 +608,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -612,12 +622,12 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS.Extension;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -629,9 +639,9 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -645,12 +655,12 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS.Extension;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.Extension;
|
||||
PRODUCT_NAME = "HotPocket Extension";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -666,12 +676,13 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -679,14 +690,14 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = macosx;
|
||||
@@ -700,14 +711,15 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2025091201;
|
||||
DEVELOPMENT_TEAM = 648728X64K;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HotPocket;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-present BTHLabs";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -715,14 +727,14 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 25.9.8;
|
||||
MARKETING_VERSION = 25.9.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
"-framework",
|
||||
WebKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.macOS;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket;
|
||||
PRODUCT_NAME = HotPocket;
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = macosx;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
version = '25.9.8'
|
||||
version = '25.9.12'
|
||||
|
||||
@@ -11,7 +11,10 @@ import uuid6
|
||||
|
||||
from hotpocket_backend.apps.accounts.models import AccessToken
|
||||
from hotpocket_backend.apps.core.conf import settings
|
||||
from hotpocket_soa.dto.accounts import AccessTokensQuery
|
||||
from hotpocket_soa.dto.accounts import (
|
||||
AccessTokenMetaUpdateIn,
|
||||
AccessTokensQuery,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,6 +57,16 @@ class AccessTokensService:
|
||||
f'Access Token not found: pk=`{pk}`',
|
||||
) from exception
|
||||
|
||||
def get_by_key(self, *, key: str) -> AccessToken:
|
||||
try:
|
||||
query_set = AccessToken.active_objects
|
||||
|
||||
return query_set.get(key=key)
|
||||
except AccessToken.DoesNotExist as exception:
|
||||
raise self.AccessTokenNotFound(
|
||||
f'Access Token not found: key=`{key}`',
|
||||
) from exception
|
||||
|
||||
def search(self,
|
||||
*,
|
||||
query: AccessTokensQuery,
|
||||
@@ -79,3 +92,27 @@ class AccessTokensService:
|
||||
access_token.soft_delete()
|
||||
|
||||
return True
|
||||
|
||||
def update_meta(self,
|
||||
*,
|
||||
pk: uuid.UUID,
|
||||
update: AccessTokenMetaUpdateIn,
|
||||
) -> AccessToken:
|
||||
access_token = AccessToken.active_objects.get(pk=pk)
|
||||
|
||||
next_meta = {
|
||||
**(access_token.meta or {}),
|
||||
}
|
||||
|
||||
if update.version is not None:
|
||||
next_meta['version'] = update.version
|
||||
|
||||
if update.platform is not None:
|
||||
next_meta['platform'] = update.platform
|
||||
|
||||
access_token.meta = next_meta
|
||||
access_token.save()
|
||||
|
||||
access_token.refresh_from_db()
|
||||
|
||||
return access_token
|
||||
|
||||
@@ -1,10 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from bthlabs_jsonrpc_core import register_method
|
||||
from django import db
|
||||
from django.http import HttpRequest
|
||||
|
||||
from hotpocket_soa.dto.accounts import AccessTokenMetaUpdateIn
|
||||
from hotpocket_soa.services import AccessTokensService
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register_method('accounts.auth.check')
|
||||
def check(request: HttpRequest) -> bool:
|
||||
return request.user.is_anonymous is False
|
||||
|
||||
|
||||
@register_method('accounts.auth.check_access_token')
|
||||
def check_access_token(request: HttpRequest,
|
||||
access_token: str,
|
||||
meta: dict | None = None,
|
||||
) -> bool:
|
||||
result = True
|
||||
|
||||
try:
|
||||
access_tokens_service = AccessTokensService()
|
||||
|
||||
with db.transaction.atomic():
|
||||
access_token_object = access_tokens_service.get_by_key(
|
||||
account_uuid=request.user.pk,
|
||||
key=access_token,
|
||||
)
|
||||
|
||||
meta_update = AccessTokenMetaUpdateIn.model_validate(
|
||||
(meta or {}),
|
||||
)
|
||||
|
||||
_ = access_tokens_service.update_meta(
|
||||
access_token=access_token_object,
|
||||
update=meta_update,
|
||||
)
|
||||
except AccessTokensService.AccessTokenNotFound as exception:
|
||||
LOGGER.error(
|
||||
'Access Token not found: account_uuid=`%s` key=`%s`',
|
||||
request.user.pk,
|
||||
access_token,
|
||||
exc_info=exception,
|
||||
)
|
||||
result = False
|
||||
except AccessTokensService.AccessTokenAccessDenied as exception:
|
||||
LOGGER.error(
|
||||
'Access Token access denied: account_uuid=`%s` key=`%s`',
|
||||
request.user.pk,
|
||||
access_token,
|
||||
exc_info=exception,
|
||||
)
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
@@ -12,24 +12,32 @@ def manifest_json(request: HttpRequest) -> JsonResponse:
|
||||
result = {
|
||||
'name': settings.SITE_TITLE,
|
||||
'short_name': settings.SITE_SHORT_TITLE,
|
||||
'start_url': reverse('ui.associations.browse'),
|
||||
'start_url': request.build_absolute_uri(
|
||||
reverse('ui.associations.browse'),
|
||||
),
|
||||
'display': 'standalone',
|
||||
'background_color': '#212529',
|
||||
'theme_color': '#2b3035',
|
||||
'icons': [
|
||||
{
|
||||
'src': static('ui/img/icon-192.png'),
|
||||
'src': request.build_absolute_uri(
|
||||
static('ui/img/icon-192.png'),
|
||||
),
|
||||
'sizes': '192x192',
|
||||
'type': 'image/png',
|
||||
},
|
||||
{
|
||||
'src': static('ui/img/icon-512.png'),
|
||||
'src': request.build_absolute_uri(
|
||||
static('ui/img/icon-512.png'),
|
||||
),
|
||||
'sizes': '512x512',
|
||||
'type': 'image/png',
|
||||
},
|
||||
],
|
||||
'share_target': {
|
||||
'action': reverse('ui.integrations.android.share_sheet'),
|
||||
'action': request.build_absolute_uri(
|
||||
reverse('ui.integrations.android.share_sheet'),
|
||||
),
|
||||
'method': 'POST',
|
||||
'enctype': 'multipart/form-data',
|
||||
'params': {
|
||||
|
||||
@@ -13,7 +13,7 @@ cat <<EOF
|
||||
|_|
|
||||
production
|
||||
|
||||
HotPocket v25.9.8 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
||||
HotPocket v25.9.12 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
||||
Copyright 2025-present by BTHLabs. All rights reserved. (https://bthlabs.pl/)
|
||||
Licensed under Apache-2.0
|
||||
EOF
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hotpocket-backend",
|
||||
"version": "25.9.8",
|
||||
"version": "25.9.12",
|
||||
"description": "HotPocket Backend",
|
||||
"main": "hotpocket_backend/apps/frontend/src/index.js",
|
||||
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "hotpocket-backend"
|
||||
version = "25.9.8"
|
||||
version = "25.9.12"
|
||||
description = "HotPocket Backend"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -11,7 +11,7 @@ from hotpocket_backend.apps.accounts.models import AccessToken
|
||||
def AccessTokenMetaFactory() -> dict:
|
||||
return {
|
||||
'platform': 'MacIntel',
|
||||
'version': '1987.10.03',
|
||||
'version': '1985.12.12',
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class AccessTokenFactory(factory.django.DjangoModelFactory):
|
||||
account_uuid = None
|
||||
key = factory.LazyFunction(lambda: str(uuid.uuid4()))
|
||||
origin = factory.LazyFunction(
|
||||
lambda: f'safari-web-extension//{uuid.uuid4()}',
|
||||
lambda: f'safari-web-extension://{uuid.uuid4()}',
|
||||
)
|
||||
meta = factory.LazyFunction(AccessTokenMetaFactory)
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from .access_token import * # noqa: F401,F403
|
||||
from .account import * # noqa: F401,F403
|
||||
from .apps import * # noqa: F401,F403
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def safari_extension_origin():
|
||||
return f'safari-web-extension://{uuid.uuid4()}'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def safari_extension_meta():
|
||||
return {
|
||||
'platform': 'MacIntel',
|
||||
'version': '1987.10.03',
|
||||
}
|
||||
@@ -24,15 +24,28 @@ class AccessTokensTestingService:
|
||||
assert access_token.updated_at is not None
|
||||
|
||||
def assert_deleted(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = AccessToken.objects.get(pk=pk)
|
||||
assert association.deleted_at is not None
|
||||
access_token = AccessToken.objects.get(pk=pk)
|
||||
assert access_token.deleted_at is not None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at > reference.updated_at
|
||||
assert access_token.updated_at > reference.updated_at
|
||||
|
||||
def assert_not_deleted(self, *, pk: uuid.UUID, reference: typing.Any = None):
|
||||
association = AccessToken.objects.get(pk=pk)
|
||||
assert association.deleted_at is None
|
||||
access_token = AccessToken.objects.get(pk=pk)
|
||||
assert access_token.deleted_at is None
|
||||
|
||||
if reference is not None:
|
||||
assert association.updated_at == reference.updated_at
|
||||
assert access_token.updated_at == reference.updated_at
|
||||
|
||||
def assert_meta_updated(self, *, pk: uuid.UUID, meta_update: dict, reference: typing.Any = None):
|
||||
access_token = AccessToken.objects.get(pk=pk)
|
||||
|
||||
if len(meta_update) > 0 and reference is not None:
|
||||
expected_meta = {
|
||||
**reference.meta,
|
||||
**meta_update,
|
||||
}
|
||||
assert access_token.meta == expected_meta
|
||||
|
||||
if reference is not None:
|
||||
assert access_token.updated_at > reference.updated_at
|
||||
|
||||
0
services/backend/tests/ui/views/meta/__init__.py
Normal file
0
services/backend/tests/ui/views/meta/__init__.py
Normal file
24
services/backend/tests/ui/views/meta/test_manifest_json.py
Normal file
24
services/backend/tests/ui/views/meta/test_manifest_json.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
import http
|
||||
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def test_ok(client: Client, settings):
|
||||
# When
|
||||
result = client.get(reverse('ui.meta.manifest_json'))
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
payload = result.json()
|
||||
assert payload['name'] == settings.SITE_TITLE
|
||||
assert payload['short_name'] == settings.SITE_SHORT_TITLE
|
||||
assert payload['start_url'] == f"http://testserver{reverse('ui.associations.browse')}"
|
||||
assert payload['share_target']['action'] == (
|
||||
f"http://testserver{reverse('ui.integrations.android.share_sheet')}"
|
||||
)
|
||||
@@ -14,29 +14,16 @@ from hotpocket_backend_testing.services.accounts import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def origin():
|
||||
return f'safari-web-extension://{uuid.uuid4()}'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth_key():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def meta():
|
||||
return {
|
||||
'platform': 'MacIntel',
|
||||
'version': '1987.10.03',
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call(rpc_call_factory, auth_key, meta):
|
||||
def call(rpc_call_factory, auth_key, safari_extension_meta):
|
||||
return rpc_call_factory(
|
||||
'accounts.access_tokens.create',
|
||||
[auth_key, meta],
|
||||
[auth_key, safari_extension_meta],
|
||||
)
|
||||
|
||||
|
||||
@@ -44,9 +31,9 @@ def call(rpc_call_factory, auth_key, meta):
|
||||
def test_ok(authenticated_client: Client,
|
||||
auth_key,
|
||||
call,
|
||||
origin,
|
||||
safari_extension_origin,
|
||||
account,
|
||||
meta,
|
||||
safari_extension_meta,
|
||||
):
|
||||
# Given
|
||||
session = authenticated_client.session
|
||||
@@ -59,7 +46,7 @@ def test_ok(authenticated_client: Client,
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
headers={
|
||||
'Origin': origin,
|
||||
'Origin': safari_extension_origin,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -72,8 +59,8 @@ def test_ok(authenticated_client: Client,
|
||||
AccessTokensTestingService().assert_created(
|
||||
key=call_result['result'],
|
||||
account_uuid=account.pk,
|
||||
origin=origin,
|
||||
meta=meta,
|
||||
origin=safari_extension_origin,
|
||||
meta=safari_extension_meta,
|
||||
)
|
||||
|
||||
assert 'extension_auth_key' not in authenticated_client.session
|
||||
@@ -82,7 +69,7 @@ def test_ok(authenticated_client: Client,
|
||||
@pytest.mark.django_db
|
||||
def test_auth_key_missing(authenticated_client: Client,
|
||||
call,
|
||||
origin,
|
||||
safari_extension_origin,
|
||||
):
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
@@ -90,7 +77,7 @@ def test_auth_key_missing(authenticated_client: Client,
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
headers={
|
||||
'Origin': origin,
|
||||
'Origin': safari_extension_origin,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -105,7 +92,7 @@ def test_auth_key_missing(authenticated_client: Client,
|
||||
@pytest.mark.django_db
|
||||
def test_auth_key_mismatch(authenticated_client: Client,
|
||||
call,
|
||||
origin,
|
||||
safari_extension_origin,
|
||||
):
|
||||
# Given
|
||||
session = authenticated_client.session
|
||||
@@ -118,7 +105,7 @@ def test_auth_key_mismatch(authenticated_client: Client,
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
headers={
|
||||
'Origin': origin,
|
||||
'Origin': safari_extension_origin,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# type: ignore
|
||||
from __future__ import annotations
|
||||
|
||||
import http
|
||||
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
import pytest
|
||||
|
||||
from hotpocket_backend_testing.services.accounts import (
|
||||
AccessTokensTestingService,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call_factory(request: pytest.FixtureRequest, rpc_call_factory):
|
||||
default_access_token = request.getfixturevalue('access_token_out')
|
||||
default_meta_update = request.getfixturevalue('safari_extension_meta')
|
||||
|
||||
def factory(access_token=None, meta_update=None):
|
||||
return rpc_call_factory(
|
||||
'accounts.auth.check_access_token',
|
||||
[
|
||||
(
|
||||
access_token.key
|
||||
if access_token is not None
|
||||
else default_access_token.key
|
||||
),
|
||||
(
|
||||
meta_update
|
||||
if meta_update is not None
|
||||
else default_meta_update
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
return factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def call(call_factory):
|
||||
return call_factory()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ok(authenticated_client: Client,
|
||||
call,
|
||||
access_token_out,
|
||||
safari_extension_meta,
|
||||
):
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
call_result = result.json()
|
||||
assert 'error' not in call_result
|
||||
assert call_result['result'] is True
|
||||
|
||||
AccessTokensTestingService().assert_meta_updated(
|
||||
pk=access_token_out.pk,
|
||||
meta_update=safari_extension_meta,
|
||||
reference=access_token_out,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'meta_keys_to_pop',
|
||||
[
|
||||
('platform',),
|
||||
('version',),
|
||||
('platform', 'version'),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_ok_with_partial_meta_update(meta_keys_to_pop,
|
||||
safari_extension_meta,
|
||||
authenticated_client: Client,
|
||||
call_factory,
|
||||
access_token_out,
|
||||
):
|
||||
# Given
|
||||
meta_update = {**safari_extension_meta}
|
||||
for meta_key_to_pop in meta_keys_to_pop:
|
||||
meta_update.pop(meta_key_to_pop)
|
||||
|
||||
call = call_factory(meta_update=meta_update)
|
||||
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
call_result = result.json()
|
||||
assert 'error' not in call_result
|
||||
assert call_result['result'] is True
|
||||
|
||||
AccessTokensTestingService().assert_meta_updated(
|
||||
pk=access_token_out.pk,
|
||||
meta_update=meta_update,
|
||||
reference=access_token_out,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invalid_access_token(authenticated_client: Client,
|
||||
call,
|
||||
):
|
||||
# Given
|
||||
call['params'][0] = 'thisisntright'
|
||||
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
call_result = result.json()
|
||||
assert 'error' not in call_result
|
||||
assert call_result['result'] is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_deleted_access_token(call_factory,
|
||||
deleted_access_token_out,
|
||||
authenticated_client: Client,
|
||||
):
|
||||
# Given
|
||||
call = call_factory(access_token=deleted_access_token_out)
|
||||
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
call_result = result.json()
|
||||
assert 'error' not in call_result
|
||||
assert call_result['result'] is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_other_account_access_token(call_factory,
|
||||
other_account_access_token_out,
|
||||
authenticated_client: Client,
|
||||
):
|
||||
# Given
|
||||
call = call_factory(access_token=other_account_access_token_out)
|
||||
|
||||
# When
|
||||
result = authenticated_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.OK
|
||||
|
||||
call_result = result.json()
|
||||
assert 'error' not in call_result
|
||||
assert call_result['result'] is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_inactive_account(inactive_account_client: Client, call):
|
||||
# When
|
||||
result = inactive_account_client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.FORBIDDEN
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_anonymous(client: Client, call):
|
||||
# When
|
||||
result = client.post(
|
||||
reverse('ui.rpc'),
|
||||
data=call,
|
||||
)
|
||||
|
||||
# Then
|
||||
assert result.status_code == http.HTTPStatus.FORBIDDEN
|
||||
12
services/dotcom/webroot/privacy.html
Normal file
12
services/dotcom/webroot/privacy.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>hotpocket.app</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
</head>
|
||||
<body>
|
||||
<h1>SOON</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hotpocket-extension",
|
||||
"version": "25.9.8",
|
||||
"version": "25.9.12",
|
||||
"description": "HotPocket Extension",
|
||||
"main": "src/index.js",
|
||||
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "hotpocket-extension"
|
||||
version = "25.9.8"
|
||||
version = "25.9.12"
|
||||
description = "HotPocket Extension"
|
||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -66,6 +66,10 @@ const manifestJsonOutputPlugin = () => {
|
||||
|
||||
result.version = packageJSON.version;
|
||||
|
||||
if (IS_PRODUCTION === false) {
|
||||
result.name = 'HotPocket Development';
|
||||
}
|
||||
|
||||
return JSON.stringify(result, null, 2);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,9 +29,12 @@ const executeJSONRPCCall = async (url, call, {accessToken}) => {
|
||||
let result = null;
|
||||
let error = null;
|
||||
|
||||
const effectiveURL = new URL(url);
|
||||
effectiveURL.searchParams.append('method', call.method);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
effectiveURL.toString(),
|
||||
{
|
||||
body: JSON.stringify(call),
|
||||
credentials: 'include',
|
||||
@@ -78,6 +81,13 @@ const executeJSONRPCCall = async (url, call, {accessToken}) => {
|
||||
return [result, error];
|
||||
};
|
||||
|
||||
const getAccessTokenMeta = () => {
|
||||
return {
|
||||
platform: navigator.platform,
|
||||
version: HotPocketExtension.version,
|
||||
};
|
||||
};
|
||||
|
||||
const doSave = async (accessToken, tab) => {
|
||||
const call = makeJSONRPCCall('saves.create', [tab.url]);
|
||||
const [result, error] = await executeJSONRPCCall(RPC_URL, call, {accessToken});
|
||||
@@ -97,10 +107,7 @@ const doCreateAndStoreAccessToken = async (authKey) => {
|
||||
'accounts.access_tokens.create',
|
||||
[
|
||||
authKey,
|
||||
{
|
||||
platform: navigator.platform,
|
||||
version: HotPocketExtension.version,
|
||||
},
|
||||
getAccessTokenMeta(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -163,7 +170,10 @@ const doCheckAuth = async (accessToken) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const call = makeJSONRPCCall('accounts.auth.check');
|
||||
const call = makeJSONRPCCall(
|
||||
'accounts.auth.check_access_token',
|
||||
[accessToken, getAccessTokenMeta()],
|
||||
);
|
||||
|
||||
const [result, error] = await executeJSONRPCCall(RPC_URL, call, {
|
||||
accessToken,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"default_locale": "en",
|
||||
"name": "__MSG_extension_name__",
|
||||
"description": "__MSG_extension_description__",
|
||||
"version": "25.9.8",
|
||||
"version": "25.9.12",
|
||||
"icons": {
|
||||
"48": "images/icon-48.png",
|
||||
"64": "images/icon-64.png",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"scripts": [
|
||||
"background-bundle.js"
|
||||
],
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"persistent": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,3 +36,8 @@ class AccessTokenOut(ModelOut):
|
||||
class AccessTokensQuery(Query):
|
||||
account_uuid: uuid.UUID
|
||||
before: uuid.UUID | None = pydantic.Field(default=None)
|
||||
|
||||
|
||||
class AccessTokenMetaUpdateIn(pydantic.BaseModel):
|
||||
version: str | None = None
|
||||
platform: str | None = None
|
||||
|
||||
@@ -6,7 +6,11 @@ import uuid
|
||||
from hotpocket_backend.apps.accounts.services import (
|
||||
AccessTokensService as BackendAccessTokensService,
|
||||
)
|
||||
from hotpocket_soa.dto.accounts import AccessTokenOut, AccessTokensQuery
|
||||
from hotpocket_soa.dto.accounts import (
|
||||
AccessTokenMetaUpdateIn,
|
||||
AccessTokenOut,
|
||||
AccessTokensQuery,
|
||||
)
|
||||
|
||||
from .base import ProxyService, SOAError
|
||||
|
||||
@@ -76,6 +80,33 @@ class AccessTokensService(ProxyService):
|
||||
else:
|
||||
raise
|
||||
|
||||
def get_by_key(self,
|
||||
*,
|
||||
account_uuid: uuid.UUID,
|
||||
key: str,
|
||||
) -> AccessTokenOut:
|
||||
try:
|
||||
result = AccessTokenOut.model_validate(
|
||||
self.call(
|
||||
self.backend_access_tokens_service,
|
||||
'get_by_key',
|
||||
key=key,
|
||||
),
|
||||
from_attributes=True,
|
||||
)
|
||||
|
||||
if result.account_uuid != account_uuid:
|
||||
raise self.AccessTokenAccessDenied(
|
||||
f'account_uuid=`{account_uuid}` key=`{key}`',
|
||||
)
|
||||
|
||||
return result
|
||||
except SOAError as exception:
|
||||
if isinstance(exception.__cause__, BackendAccessTokensService.AccessTokenNotFound) is True:
|
||||
raise self.AccessTokenNotFound(f'account_uuid=`{account_uuid}` pk=`{key}`') from exception
|
||||
else:
|
||||
raise
|
||||
|
||||
def search(self,
|
||||
*,
|
||||
query: AccessTokensQuery,
|
||||
@@ -98,3 +129,18 @@ class AccessTokensService(ProxyService):
|
||||
'delete',
|
||||
pk=access_token.pk,
|
||||
)
|
||||
|
||||
def update_meta(self,
|
||||
*,
|
||||
access_token: AccessTokenOut,
|
||||
update: AccessTokenMetaUpdateIn,
|
||||
) -> AccessTokenOut:
|
||||
return AccessTokenOut.model_validate(
|
||||
self.call(
|
||||
self.backend_access_tokens_service,
|
||||
'update_meta',
|
||||
pk=access_token.pk,
|
||||
update=update,
|
||||
),
|
||||
from_attributes=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user