agentic-dev 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/bin/agentic-dev.mjs +656 -124
- package/lib/scaffold.mjs +109 -6
- package/package.json +1 -1
- package/client/admin/.dockerignore +0 -3
- package/client/admin/.env.example +0 -1
- package/client/admin/Dockerfile +0 -16
- package/client/admin/Dockerfile.dev +0 -18
- package/client/admin/README.md +0 -20
- package/client/admin/index.html +0 -12
- package/client/admin/package.json +0 -41
- package/client/admin/postcss.config.js +0 -6
- package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
- package/client/admin/src/api/alerts.ts +0 -33
- package/client/admin/src/api/client.ts +0 -71
- package/client/admin/src/api/orders.ts +0 -33
- package/client/admin/src/api/support.ts +0 -11
- package/client/admin/src/app/App.tsx +0 -23
- package/client/admin/src/auth/AuthProvider.tsx +0 -122
- package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
- package/client/admin/src/auth/auth-client.ts +0 -38
- package/client/admin/src/auth/types.ts +0 -18
- package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
- package/client/admin/src/components/AdminShell.tsx +0 -76
- package/client/admin/src/components/ui/button.tsx +0 -34
- package/client/admin/src/components/ui/input.tsx +0 -21
- package/client/admin/src/lib/cn.ts +0 -6
- package/client/admin/src/lib/specRouteCatalog.json +0 -30
- package/client/admin/src/lib/specScreens.json +0 -22
- package/client/admin/src/main.tsx +0 -17
- package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
- package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
- package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
- package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
- package/client/admin/src/styles/globals.css +0 -17
- package/client/admin/src/theme-vars.ts +0 -18
- package/client/admin/src/theme.ts +0 -25
- package/client/admin/src/vite-env.d.ts +0 -1
- package/client/admin/tailwind.config.js +0 -8
- package/client/admin/tsconfig.json +0 -25
- package/client/admin/vite.config.ts +0 -12
- package/client/landing/.dockerignore +0 -3
- package/client/landing/.env.example +0 -1
- package/client/landing/Dockerfile +0 -16
- package/client/landing/Dockerfile.dev +0 -18
- package/client/landing/README.md +0 -18
- package/client/landing/index.html +0 -12
- package/client/landing/package.json +0 -41
- package/client/landing/postcss.config.js +0 -6
- package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
- package/client/landing/src/App.tsx +0 -21
- package/client/landing/src/api/catalog.ts +0 -30
- package/client/landing/src/api/client.ts +0 -30
- package/client/landing/src/auth/AuthProvider.tsx +0 -122
- package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
- package/client/landing/src/auth/auth-client.ts +0 -38
- package/client/landing/src/auth/types.ts +0 -18
- package/client/landing/src/components/LandingShell.tsx +0 -34
- package/client/landing/src/lib/specRouteCatalog.json +0 -23
- package/client/landing/src/lib/specScreens.json +0 -17
- package/client/landing/src/main.tsx +0 -17
- package/client/landing/src/pages/LandingHomePage.tsx +0 -215
- package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
- package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
- package/client/landing/src/styles/globals.css +0 -17
- package/client/landing/src/theme-vars.ts +0 -16
- package/client/landing/src/theme.ts +0 -21
- package/client/landing/src/vite-env.d.ts +0 -1
- package/client/landing/tailwind.config.js +0 -8
- package/client/landing/tsconfig.json +0 -25
- package/client/landing/vite.config.ts +0 -12
- package/client/mobile/.dockerignore +0 -2
- package/client/mobile/.env.example +0 -1
- package/client/mobile/Dockerfile +0 -16
- package/client/mobile/Dockerfile.dev +0 -18
- package/client/mobile/README.md +0 -19
- package/client/mobile/index.html +0 -12
- package/client/mobile/package.json +0 -42
- package/client/mobile/postcss.config.js +0 -6
- package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
- package/client/mobile/src/App.tsx +0 -1
- package/client/mobile/src/api/client.ts +0 -62
- package/client/mobile/src/api/fulfillment.ts +0 -55
- package/client/mobile/src/api/shipping.ts +0 -56
- package/client/mobile/src/app/App.tsx +0 -23
- package/client/mobile/src/auth/AuthProvider.tsx +0 -122
- package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
- package/client/mobile/src/auth/auth-client.ts +0 -38
- package/client/mobile/src/auth/types.ts +0 -18
- package/client/mobile/src/components/InShell.tsx +0 -74
- package/client/mobile/src/components/ui/button.tsx +0 -35
- package/client/mobile/src/components/ui/card.tsx +0 -15
- package/client/mobile/src/components/ui/input.tsx +0 -21
- package/client/mobile/src/lib/cn.ts +0 -6
- package/client/mobile/src/lib/specRouteCatalog.json +0 -26
- package/client/mobile/src/lib/specScreens.json +0 -22
- package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
- package/client/mobile/src/main.tsx +0 -17
- package/client/mobile/src/pages/DashboardPage.tsx +0 -172
- package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
- package/client/mobile/src/pages/LoginPage.tsx +0 -74
- package/client/mobile/src/pages/ShippingPage.tsx +0 -338
- package/client/mobile/src/styles/globals.css +0 -23
- package/client/mobile/src/theme-vars.ts +0 -16
- package/client/mobile/src/theme.ts +0 -21
- package/client/mobile/src/vite-env.d.ts +0 -1
- package/client/mobile/tailwind.config.js +0 -8
- package/client/mobile/tsconfig.json +0 -25
- package/client/mobile/vite.config.ts +0 -12
- package/client/web/.dockerignore +0 -3
- package/client/web/.env.example +0 -1
- package/client/web/Dockerfile +0 -16
- package/client/web/Dockerfile.dev +0 -18
- package/client/web/README.md +0 -47
- package/client/web/index.html +0 -12
- package/client/web/package.json +0 -42
- package/client/web/postcss.config.js +0 -6
- package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
- package/client/web/src/api/client.ts +0 -30
- package/client/web/src/api/orders.ts +0 -42
- package/client/web/src/app/App.tsx +0 -21
- package/client/web/src/auth/AuthProvider.tsx +0 -122
- package/client/web/src/auth/ProtectedRoute.tsx +0 -22
- package/client/web/src/auth/auth-client.ts +0 -38
- package/client/web/src/auth/types.ts +0 -18
- package/client/web/src/components/AppShell.tsx +0 -59
- package/client/web/src/components/ui/button.tsx +0 -35
- package/client/web/src/components/ui/card.tsx +0 -7
- package/client/web/src/components/ui/input.tsx +0 -21
- package/client/web/src/lib/cn.ts +0 -6
- package/client/web/src/lib/specRouteCatalog.json +0 -23
- package/client/web/src/lib/specScreens.json +0 -17
- package/client/web/src/main.tsx +0 -17
- package/client/web/src/pages/DashboardPage.tsx +0 -158
- package/client/web/src/pages/LoginPage.tsx +0 -72
- package/client/web/src/pages/OrdersPage.tsx +0 -123
- package/client/web/src/styles/globals.css +0 -17
- package/client/web/src/theme-vars.ts +0 -18
- package/client/web/src/theme.ts +0 -25
- package/client/web/src/vite-env.d.ts +0 -1
- package/client/web/tailwind.config.js +0 -8
- package/client/web/tsconfig.json +0 -25
- package/client/web/vite.config.ts +0 -12
- package/server/.dockerignore +0 -4
- package/server/.env.example +0 -19
- package/server/Dockerfile +0 -22
- package/server/Dockerfile.dev +0 -19
- package/server/README.md +0 -33
- package/server/__init__.py +0 -0
- package/server/api/__init__.py +0 -1
- package/server/api/http/__init__.py +0 -4
- package/server/api/http/app.py +0 -53
- package/server/api/http/router.py +0 -24
- package/server/config.py +0 -52
- package/server/contexts/__init__.py +0 -12
- package/server/contexts/alerts/__init__.py +0 -1
- package/server/contexts/alerts/application/__init__.py +0 -13
- package/server/contexts/alerts/application/services.py +0 -41
- package/server/contexts/alerts/contracts/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/router.py +0 -37
- package/server/contexts/alerts/domain/__init__.py +0 -15
- package/server/contexts/alerts/domain/models.py +0 -29
- package/server/contexts/alerts/infrastructure/__init__.py +0 -11
- package/server/contexts/alerts/infrastructure/repository.py +0 -41
- package/server/contexts/auth/__init__.py +0 -1
- package/server/contexts/auth/application/__init__.py +0 -3
- package/server/contexts/auth/application/ports.py +0 -10
- package/server/contexts/auth/application/services.py +0 -64
- package/server/contexts/auth/contracts/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/dependencies.py +0 -37
- package/server/contexts/auth/contracts/http/router.py +0 -19
- package/server/contexts/auth/domain/__init__.py +0 -3
- package/server/contexts/auth/domain/models.py +0 -24
- package/server/contexts/auth/infrastructure/__init__.py +0 -4
- package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
- package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
- package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
- package/server/contexts/auth/infrastructure/repository.py +0 -28
- package/server/contexts/catalog/__init__.py +0 -1
- package/server/contexts/catalog/application/__init__.py +0 -28
- package/server/contexts/catalog/application/ports.py +0 -15
- package/server/contexts/catalog/application/services.py +0 -154
- package/server/contexts/catalog/contracts/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/router.py +0 -60
- package/server/contexts/catalog/domain/__init__.py +0 -45
- package/server/contexts/catalog/domain/models.py +0 -113
- package/server/contexts/catalog/infrastructure/__init__.py +0 -4
- package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
- package/server/contexts/catalog/infrastructure/repository.py +0 -8
- package/server/contexts/fulfillment/__init__.py +0 -1
- package/server/contexts/fulfillment/application/__init__.py +0 -13
- package/server/contexts/fulfillment/application/ports.py +0 -20
- package/server/contexts/fulfillment/application/services.py +0 -85
- package/server/contexts/fulfillment/contracts/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/router.py +0 -40
- package/server/contexts/fulfillment/domain/__init__.py +0 -25
- package/server/contexts/fulfillment/domain/models.py +0 -73
- package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
- package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
- package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
- package/server/contexts/health/__init__.py +0 -1
- package/server/contexts/health/application/__init__.py +0 -3
- package/server/contexts/health/application/services.py +0 -2
- package/server/contexts/health/contracts/__init__.py +0 -3
- package/server/contexts/health/contracts/http/__init__.py +0 -3
- package/server/contexts/health/contracts/http/router.py +0 -10
- package/server/contexts/inventory/__init__.py +0 -1
- package/server/contexts/inventory/application/__init__.py +0 -28
- package/server/contexts/inventory/application/ports.py +0 -11
- package/server/contexts/inventory/application/services.py +0 -214
- package/server/contexts/inventory/contracts/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/router.py +0 -82
- package/server/contexts/inventory/domain/__init__.py +0 -33
- package/server/contexts/inventory/domain/models.py +0 -93
- package/server/contexts/inventory/infrastructure/__init__.py +0 -4
- package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
- package/server/contexts/inventory/infrastructure/repository.py +0 -8
- package/server/contexts/orders/__init__.py +0 -1
- package/server/contexts/orders/application/__init__.py +0 -19
- package/server/contexts/orders/application/services.py +0 -127
- package/server/contexts/orders/contracts/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/router.py +0 -82
- package/server/contexts/orders/domain/__init__.py +0 -29
- package/server/contexts/orders/domain/models.py +0 -95
- package/server/contexts/orders/infrastructure/__init__.py +0 -7
- package/server/contexts/orders/infrastructure/repository.py +0 -104
- package/server/contexts/shipping/__init__.py +0 -1
- package/server/contexts/shipping/application/__init__.py +0 -13
- package/server/contexts/shipping/application/services.py +0 -92
- package/server/contexts/shipping/contracts/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/router.py +0 -40
- package/server/contexts/shipping/domain/__init__.py +0 -19
- package/server/contexts/shipping/domain/models.py +0 -48
- package/server/contexts/shipping/infrastructure/__init__.py +0 -9
- package/server/contexts/shipping/infrastructure/repository.py +0 -50
- package/server/contexts/support/__init__.py +0 -1
- package/server/contexts/support/application/__init__.py +0 -13
- package/server/contexts/support/application/services.py +0 -29
- package/server/contexts/support/contracts/__init__.py +0 -3
- package/server/contexts/support/contracts/http/__init__.py +0 -3
- package/server/contexts/support/contracts/http/router.py +0 -40
- package/server/contexts/support/domain/__init__.py +0 -13
- package/server/contexts/support/domain/models.py +0 -27
- package/server/contexts/support/infrastructure/__init__.py +0 -11
- package/server/contexts/support/infrastructure/repository.py +0 -70
- package/server/contexts/user/__init__.py +0 -1
- package/server/contexts/user/application/__init__.py +0 -3
- package/server/contexts/user/application/ports.py +0 -11
- package/server/contexts/user/application/services.py +0 -44
- package/server/contexts/user/contracts/__init__.py +0 -3
- package/server/contexts/user/contracts/http/__init__.py +0 -3
- package/server/contexts/user/contracts/http/router.py +0 -26
- package/server/contexts/user/domain/__init__.py +0 -3
- package/server/contexts/user/domain/models.py +0 -22
- package/server/contexts/user/infrastructure/__init__.py +0 -3
- package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
- package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
- package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
- package/server/contexts/user/infrastructure/factory.py +0 -28
- package/server/data/README.md +0 -24
- package/server/data/bootstrap/alerts.json +0 -38
- package/server/data/bootstrap/auth_accounts.json +0 -18
- package/server/data/bootstrap/catalog_products.json +0 -179
- package/server/data/bootstrap/fulfillment_events.json +0 -5
- package/server/data/bootstrap/fulfillment_notes.json +0 -5
- package/server/data/bootstrap/fulfillment_tasks.json +0 -50
- package/server/data/bootstrap/inventory_levels.json +0 -80
- package/server/data/bootstrap/orders.json +0 -62
- package/server/data/bootstrap/shipping_shipments.json +0 -50
- package/server/data/bootstrap/support_faqs.json +0 -26
- package/server/data/bootstrap/users.json +0 -20
- package/server/data/bootstrap_loader.py +0 -15
- package/server/docker-entrypoint.sh +0 -56
- package/server/main.py +0 -3
- package/server/pyproject.toml +0 -36
- package/server/shared/__init__.py +0 -1
- package/server/shared/application/__init__.py +0 -3
- package/server/shared/application/health.py +0 -2
- package/server/shared/infrastructure/__init__.py +0 -10
- package/server/shared/infrastructure/runtime.py +0 -6
- package/server/shared/infrastructure/security.py +0 -33
- package/server/tests/e2e/test_domain_feature_flows.py +0 -483
- package/server/tests/test_health.py +0 -49
- package/server/uv.lock +0 -1169
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
2
|
-
|
|
3
|
-
from contexts.alerts.application import (
|
|
4
|
-
get_alerts,
|
|
5
|
-
mark_alert_read,
|
|
6
|
-
mark_all_alerts_read,
|
|
7
|
-
)
|
|
8
|
-
from contexts.alerts.domain import (
|
|
9
|
-
AlertReadResult,
|
|
10
|
-
AlertsPayload,
|
|
11
|
-
AlertsReadAllResult,
|
|
12
|
-
)
|
|
13
|
-
from contexts.auth.contracts.http.dependencies import require_admin_user
|
|
14
|
-
|
|
15
|
-
router = APIRouter(
|
|
16
|
-
prefix="/alerts",
|
|
17
|
-
tags=["alerts"],
|
|
18
|
-
dependencies=[Depends(require_admin_user)],
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@router.get("", response_model=AlertsPayload)
|
|
23
|
-
def list_alerts() -> AlertsPayload:
|
|
24
|
-
return get_alerts()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@router.post("/read-all", response_model=AlertsReadAllResult)
|
|
28
|
-
def read_all_alerts() -> AlertsReadAllResult:
|
|
29
|
-
return mark_all_alerts_read()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@router.post("/{alert_id}/read", response_model=AlertReadResult)
|
|
33
|
-
def read_alert(alert_id: str) -> AlertReadResult:
|
|
34
|
-
try:
|
|
35
|
-
return mark_alert_read(alert_id)
|
|
36
|
-
except LookupError as exc:
|
|
37
|
-
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class AlertItem(BaseModel):
|
|
5
|
-
id: str
|
|
6
|
-
source: str
|
|
7
|
-
title: str
|
|
8
|
-
message: str
|
|
9
|
-
tone: str
|
|
10
|
-
created_at: str
|
|
11
|
-
read: bool
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class AlertRecord(AlertItem):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class AlertReadResult(AlertItem):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class AlertsPayload(BaseModel):
|
|
23
|
-
unread_count: int
|
|
24
|
-
items: list[AlertItem]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class AlertsReadAllResult(BaseModel):
|
|
28
|
-
updated_count: int
|
|
29
|
-
unread_count: int
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from contexts.alerts.domain import AlertRecord
|
|
2
|
-
from data.bootstrap_loader import load_bootstrap_json
|
|
3
|
-
|
|
4
|
-
_alert_store: list[AlertRecord] | None = None
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _get_alert_store() -> list[AlertRecord]:
|
|
8
|
-
global _alert_store
|
|
9
|
-
if _alert_store is None:
|
|
10
|
-
_alert_store = [
|
|
11
|
-
AlertRecord(**entry) for entry in load_bootstrap_json("alerts.json")
|
|
12
|
-
]
|
|
13
|
-
return _alert_store
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def list_seed_alerts() -> list[AlertRecord]:
|
|
17
|
-
return [record.model_copy(deep=True) for record in _get_alert_store()]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def mark_seed_alert_read(alert_id: str) -> AlertRecord:
|
|
21
|
-
records = _get_alert_store()
|
|
22
|
-
for index, record in enumerate(records):
|
|
23
|
-
if record.id != alert_id:
|
|
24
|
-
continue
|
|
25
|
-
|
|
26
|
-
updated_record = record.model_copy(update={"read": True})
|
|
27
|
-
records[index] = updated_record
|
|
28
|
-
return updated_record.model_copy(deep=True)
|
|
29
|
-
|
|
30
|
-
raise LookupError(f"Alert {alert_id} not found")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def mark_all_seed_alerts_read() -> int:
|
|
34
|
-
records = _get_alert_store()
|
|
35
|
-
updated_count = 0
|
|
36
|
-
for index, record in enumerate(records):
|
|
37
|
-
if record.read:
|
|
38
|
-
continue
|
|
39
|
-
records[index] = record.model_copy(update={"read": True})
|
|
40
|
-
updated_count += 1
|
|
41
|
-
return updated_count
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["contracts", "application", "domain", "infrastructure"]
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
|
|
3
|
-
from contexts.auth.domain import AuthAccountRecord
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class AuthAccountRepository(Protocol):
|
|
7
|
-
def initialize(self) -> None: ...
|
|
8
|
-
def seed_accounts(self, accounts: list[AuthAccountRecord]) -> None: ...
|
|
9
|
-
def get_by_email(self, email: str) -> AuthAccountRecord | None: ...
|
|
10
|
-
def get_by_id(self, user_id: str) -> AuthAccountRecord | None: ...
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from functools import lru_cache
|
|
2
|
-
|
|
3
|
-
from fastapi import HTTPException, status
|
|
4
|
-
|
|
5
|
-
from config import get_settings
|
|
6
|
-
from contexts.auth.domain import AuthAccountRecord, AuthenticatedUser, AuthToken, LoginCommand
|
|
7
|
-
from contexts.auth.infrastructure import (
|
|
8
|
-
decode_access_token,
|
|
9
|
-
get_auth_repository,
|
|
10
|
-
issue_access_token,
|
|
11
|
-
verify_password,
|
|
12
|
-
)
|
|
13
|
-
from data.bootstrap_loader import load_bootstrap_json
|
|
14
|
-
from shared.infrastructure import hash_password
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def authenticate_user(command: LoginCommand) -> AuthToken:
|
|
18
|
-
prepare_auth_store()
|
|
19
|
-
repository = get_auth_repository()
|
|
20
|
-
account = repository.get_by_email(str(command.email))
|
|
21
|
-
if account is None or not verify_password(command.password, account.password_hash):
|
|
22
|
-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
|
|
23
|
-
|
|
24
|
-
return AuthToken(access_token=issue_access_token(account.id), user_id=account.id)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def resolve_current_user(bearer_token: str) -> AuthenticatedUser:
|
|
28
|
-
prepare_auth_store()
|
|
29
|
-
subject = decode_access_token(bearer_token)
|
|
30
|
-
if subject is None:
|
|
31
|
-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
|
32
|
-
|
|
33
|
-
repository = get_auth_repository()
|
|
34
|
-
account = repository.get_by_id(subject)
|
|
35
|
-
if account is None:
|
|
36
|
-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
|
37
|
-
return AuthenticatedUser(**account.model_dump(exclude={"password_hash"}))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def prepare_auth_store() -> None:
|
|
41
|
-
_seed_auth_store()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@lru_cache
|
|
45
|
-
def _seed_auth_store() -> None:
|
|
46
|
-
settings = get_settings()
|
|
47
|
-
repository = get_auth_repository()
|
|
48
|
-
repository.initialize()
|
|
49
|
-
password_lookup = {
|
|
50
|
-
"bootstrap_admin_password": settings.bootstrap_admin_password,
|
|
51
|
-
"bootstrap_operator_password": settings.bootstrap_operator_password,
|
|
52
|
-
}
|
|
53
|
-
accounts = [
|
|
54
|
-
AuthAccountRecord(
|
|
55
|
-
id=entry["id"],
|
|
56
|
-
name=entry["name"],
|
|
57
|
-
email=entry["email"],
|
|
58
|
-
role=entry["role"],
|
|
59
|
-
status=entry["status"],
|
|
60
|
-
password_hash=hash_password(password_lookup[entry["password_source"]]),
|
|
61
|
-
)
|
|
62
|
-
for entry in load_bootstrap_json("auth_accounts.json")
|
|
63
|
-
]
|
|
64
|
-
repository.seed_accounts(accounts)
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from fastapi import Depends, Header, HTTPException, status
|
|
2
|
-
|
|
3
|
-
from contexts.auth.application import resolve_current_user
|
|
4
|
-
from contexts.auth.domain import AuthenticatedUser
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _extract_bearer_token(authorization: str | None) -> str:
|
|
8
|
-
if authorization is None:
|
|
9
|
-
raise HTTPException(
|
|
10
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
11
|
-
detail="Missing authorization header",
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
scheme, _, token = authorization.partition(" ")
|
|
15
|
-
if scheme.casefold() != "bearer" or not token.strip():
|
|
16
|
-
raise HTTPException(
|
|
17
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
18
|
-
detail="Invalid authorization header",
|
|
19
|
-
)
|
|
20
|
-
return token.strip()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def require_authenticated_user(
|
|
24
|
-
authorization: str | None = Header(default=None),
|
|
25
|
-
) -> AuthenticatedUser:
|
|
26
|
-
return resolve_current_user(_extract_bearer_token(authorization))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def require_admin_user(
|
|
30
|
-
current_user: AuthenticatedUser = Depends(require_authenticated_user),
|
|
31
|
-
) -> AuthenticatedUser:
|
|
32
|
-
if current_user.role != "admin":
|
|
33
|
-
raise HTTPException(
|
|
34
|
-
status_code=status.HTTP_403_FORBIDDEN,
|
|
35
|
-
detail="Admin access required",
|
|
36
|
-
)
|
|
37
|
-
return current_user
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from fastapi import APIRouter, Depends
|
|
2
|
-
|
|
3
|
-
from contexts.auth.application import authenticate_user
|
|
4
|
-
from contexts.auth.contracts.http.dependencies import require_authenticated_user
|
|
5
|
-
from contexts.auth.domain import AuthToken, AuthenticatedUser, LoginCommand
|
|
6
|
-
|
|
7
|
-
router = APIRouter(prefix="/auth", tags=["auth"])
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@router.post("/login", response_model=AuthToken)
|
|
11
|
-
def login(command: LoginCommand) -> AuthToken:
|
|
12
|
-
return authenticate_user(command)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@router.get("/me", response_model=AuthenticatedUser)
|
|
16
|
-
def me(
|
|
17
|
-
current_user: AuthenticatedUser = Depends(require_authenticated_user),
|
|
18
|
-
) -> AuthenticatedUser:
|
|
19
|
-
return current_user
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, EmailStr
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class LoginCommand(BaseModel):
|
|
5
|
-
email: EmailStr
|
|
6
|
-
password: str
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class AuthToken(BaseModel):
|
|
10
|
-
access_token: str
|
|
11
|
-
token_type: str = "bearer"
|
|
12
|
-
user_id: str
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class AuthenticatedUser(BaseModel):
|
|
16
|
-
id: str
|
|
17
|
-
name: str
|
|
18
|
-
email: EmailStr
|
|
19
|
-
role: str
|
|
20
|
-
status: str
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class AuthAccountRecord(AuthenticatedUser):
|
|
24
|
-
password_hash: str
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from contexts.auth.domain import AuthAccountRecord
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class MemoryAuthAccountRepository:
|
|
5
|
-
def __init__(self) -> None:
|
|
6
|
-
self._accounts: dict[str, AuthAccountRecord] = {}
|
|
7
|
-
|
|
8
|
-
def initialize(self) -> None:
|
|
9
|
-
return None
|
|
10
|
-
|
|
11
|
-
def seed_accounts(self, accounts: list[AuthAccountRecord]) -> None:
|
|
12
|
-
for account in accounts:
|
|
13
|
-
self._accounts[account.id] = account
|
|
14
|
-
|
|
15
|
-
def get_by_email(self, email: str) -> AuthAccountRecord | None:
|
|
16
|
-
return next((account for account in self._accounts.values() if account.email == email), None)
|
|
17
|
-
|
|
18
|
-
def get_by_id(self, user_id: str) -> AuthAccountRecord | None:
|
|
19
|
-
return self._accounts.get(user_id)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from pymongo import ASCENDING, MongoClient
|
|
2
|
-
|
|
3
|
-
from contexts.auth.domain import AuthAccountRecord
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class MongoAuthAccountRepository:
|
|
7
|
-
def __init__(self, mongodb_url: str, database_name: str) -> None:
|
|
8
|
-
self.client = MongoClient(mongodb_url)
|
|
9
|
-
self.collection = self.client[database_name]["auth_accounts"]
|
|
10
|
-
|
|
11
|
-
def initialize(self) -> None:
|
|
12
|
-
self.collection.create_index([("email", ASCENDING)], unique=True)
|
|
13
|
-
|
|
14
|
-
def seed_accounts(self, accounts: list[AuthAccountRecord]) -> None:
|
|
15
|
-
for account in accounts:
|
|
16
|
-
self.collection.update_one({"id": account.id}, {"$set": account.model_dump()}, upsert=True)
|
|
17
|
-
|
|
18
|
-
def get_by_email(self, email: str) -> AuthAccountRecord | None:
|
|
19
|
-
record = self.collection.find_one({"email": email}, {"_id": 0})
|
|
20
|
-
return AuthAccountRecord(**record) if record else None
|
|
21
|
-
|
|
22
|
-
def get_by_id(self, user_id: str) -> AuthAccountRecord | None:
|
|
23
|
-
record = self.collection.find_one({"id": user_id}, {"_id": 0})
|
|
24
|
-
return AuthAccountRecord(**record) if record else None
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
from sqlalchemy import String, create_engine, select
|
|
2
|
-
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
|
|
3
|
-
|
|
4
|
-
from contexts.auth.domain import AuthAccountRecord
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Base(DeclarativeBase):
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class AuthAccountTable(Base):
|
|
12
|
-
__tablename__ = "auth_accounts"
|
|
13
|
-
|
|
14
|
-
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
15
|
-
name: Mapped[str] = mapped_column(String(128))
|
|
16
|
-
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
|
17
|
-
role: Mapped[str] = mapped_column(String(64))
|
|
18
|
-
status: Mapped[str] = mapped_column(String(32))
|
|
19
|
-
password_hash: Mapped[str] = mapped_column(String(255))
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class SqlAlchemyAuthAccountRepository:
|
|
23
|
-
def __init__(self, database_url: str) -> None:
|
|
24
|
-
self.engine = create_engine(database_url, future=True)
|
|
25
|
-
self.session_factory = sessionmaker(bind=self.engine, expire_on_commit=False)
|
|
26
|
-
|
|
27
|
-
def initialize(self) -> None:
|
|
28
|
-
Base.metadata.create_all(self.engine)
|
|
29
|
-
|
|
30
|
-
def seed_accounts(self, accounts: list[AuthAccountRecord]) -> None:
|
|
31
|
-
with self.session_factory() as session:
|
|
32
|
-
for account in accounts:
|
|
33
|
-
existing = session.get(AuthAccountTable, account.id)
|
|
34
|
-
if existing is None:
|
|
35
|
-
session.add(AuthAccountTable(**account.model_dump()))
|
|
36
|
-
continue
|
|
37
|
-
for field, value in account.model_dump().items():
|
|
38
|
-
setattr(existing, field, value)
|
|
39
|
-
session.commit()
|
|
40
|
-
|
|
41
|
-
def get_by_email(self, email: str) -> AuthAccountRecord | None:
|
|
42
|
-
with self.session_factory() as session:
|
|
43
|
-
record = session.scalar(select(AuthAccountTable).where(AuthAccountTable.email == email))
|
|
44
|
-
return self._to_record(record)
|
|
45
|
-
|
|
46
|
-
def get_by_id(self, user_id: str) -> AuthAccountRecord | None:
|
|
47
|
-
with self.session_factory() as session:
|
|
48
|
-
record = session.get(AuthAccountTable, user_id)
|
|
49
|
-
return self._to_record(record)
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def _to_record(record: AuthAccountTable | None) -> AuthAccountRecord | None:
|
|
53
|
-
if record is None:
|
|
54
|
-
return None
|
|
55
|
-
return AuthAccountRecord(
|
|
56
|
-
id=record.id,
|
|
57
|
-
name=record.name,
|
|
58
|
-
email=record.email,
|
|
59
|
-
role=record.role,
|
|
60
|
-
status=record.status,
|
|
61
|
-
password_hash=record.password_hash,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class PostgresAuthAccountRepository(SqlAlchemyAuthAccountRepository):
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class MySqlAuthAccountRepository(SqlAlchemyAuthAccountRepository):
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class MariaDbAuthAccountRepository(SqlAlchemyAuthAccountRepository):
|
|
74
|
-
pass
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from functools import lru_cache
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
|
-
|
|
4
|
-
from config import get_settings
|
|
5
|
-
from contexts.auth.infrastructure.adapters.memory import MemoryAuthAccountRepository
|
|
6
|
-
from contexts.auth.infrastructure.adapters.mongodb import MongoAuthAccountRepository
|
|
7
|
-
from contexts.auth.infrastructure.adapters.sqlalchemy import (
|
|
8
|
-
MariaDbAuthAccountRepository,
|
|
9
|
-
MySqlAuthAccountRepository,
|
|
10
|
-
PostgresAuthAccountRepository,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from contexts.auth.application.ports import AuthAccountRepository
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@lru_cache
|
|
18
|
-
def get_auth_repository() -> "AuthAccountRepository":
|
|
19
|
-
settings = get_settings()
|
|
20
|
-
if settings.database_backend == "postgres":
|
|
21
|
-
return PostgresAuthAccountRepository(settings.postgres_url)
|
|
22
|
-
if settings.database_backend == "mysql":
|
|
23
|
-
return MySqlAuthAccountRepository(settings.mysql_url)
|
|
24
|
-
if settings.database_backend == "mariadb":
|
|
25
|
-
return MariaDbAuthAccountRepository(settings.mariadb_url)
|
|
26
|
-
if settings.database_backend == "mongodb":
|
|
27
|
-
return MongoAuthAccountRepository(settings.mongodb_url, settings.mongodb_database)
|
|
28
|
-
return MemoryAuthAccountRepository()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["application", "contracts", "domain", "infrastructure"]
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from .services import (
|
|
2
|
-
create_product,
|
|
3
|
-
get_product_or_404,
|
|
4
|
-
list_product_summaries,
|
|
5
|
-
prepare_catalog_store,
|
|
6
|
-
update_product_or_404,
|
|
7
|
-
update_product_status_or_404,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
create_catalog_product = create_product
|
|
11
|
-
get_catalog_product_or_404 = get_product_or_404
|
|
12
|
-
list_catalog_products = list_product_summaries
|
|
13
|
-
update_catalog_product = update_product_or_404
|
|
14
|
-
update_catalog_product_status = update_product_status_or_404
|
|
15
|
-
|
|
16
|
-
__all__ = [
|
|
17
|
-
"create_catalog_product",
|
|
18
|
-
"create_product",
|
|
19
|
-
"get_catalog_product_or_404",
|
|
20
|
-
"get_product_or_404",
|
|
21
|
-
"list_catalog_products",
|
|
22
|
-
"list_product_summaries",
|
|
23
|
-
"prepare_catalog_store",
|
|
24
|
-
"update_catalog_product",
|
|
25
|
-
"update_catalog_product_status",
|
|
26
|
-
"update_product_or_404",
|
|
27
|
-
"update_product_status_or_404",
|
|
28
|
-
]
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
|
|
3
|
-
from contexts.catalog.domain import ProductRecord, ProductStatus, ProductSummary
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class CatalogRepository(Protocol):
|
|
7
|
-
def initialize(self) -> None: ...
|
|
8
|
-
def seed_products(self, products: list[ProductRecord]) -> None: ...
|
|
9
|
-
def list_products(self) -> list[ProductRecord]: ...
|
|
10
|
-
def list_summaries(self) -> list[ProductSummary]: ...
|
|
11
|
-
def get_by_id(self, product_id: str) -> ProductRecord | None: ...
|
|
12
|
-
def get_by_slug(self, slug: str) -> ProductRecord | None: ...
|
|
13
|
-
def create_product(self, product: ProductRecord) -> ProductRecord: ...
|
|
14
|
-
def update_product(self, product: ProductRecord) -> ProductRecord: ...
|
|
15
|
-
def update_status(self, product_id: str, status: ProductStatus) -> ProductRecord | None: ...
|