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.
Files changed (291) hide show
  1. package/README.md +23 -9
  2. package/bin/agentic-dev.mjs +656 -124
  3. package/lib/scaffold.mjs +109 -6
  4. package/package.json +1 -1
  5. package/client/admin/.dockerignore +0 -3
  6. package/client/admin/.env.example +0 -1
  7. package/client/admin/Dockerfile +0 -16
  8. package/client/admin/Dockerfile.dev +0 -18
  9. package/client/admin/README.md +0 -20
  10. package/client/admin/index.html +0 -12
  11. package/client/admin/package.json +0 -41
  12. package/client/admin/postcss.config.js +0 -6
  13. package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
  14. package/client/admin/src/api/alerts.ts +0 -33
  15. package/client/admin/src/api/client.ts +0 -71
  16. package/client/admin/src/api/orders.ts +0 -33
  17. package/client/admin/src/api/support.ts +0 -11
  18. package/client/admin/src/app/App.tsx +0 -23
  19. package/client/admin/src/auth/AuthProvider.tsx +0 -122
  20. package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
  21. package/client/admin/src/auth/auth-client.ts +0 -38
  22. package/client/admin/src/auth/types.ts +0 -18
  23. package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
  24. package/client/admin/src/components/AdminShell.tsx +0 -76
  25. package/client/admin/src/components/ui/button.tsx +0 -34
  26. package/client/admin/src/components/ui/input.tsx +0 -21
  27. package/client/admin/src/lib/cn.ts +0 -6
  28. package/client/admin/src/lib/specRouteCatalog.json +0 -30
  29. package/client/admin/src/lib/specScreens.json +0 -22
  30. package/client/admin/src/main.tsx +0 -17
  31. package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
  32. package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
  33. package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
  34. package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
  35. package/client/admin/src/styles/globals.css +0 -17
  36. package/client/admin/src/theme-vars.ts +0 -18
  37. package/client/admin/src/theme.ts +0 -25
  38. package/client/admin/src/vite-env.d.ts +0 -1
  39. package/client/admin/tailwind.config.js +0 -8
  40. package/client/admin/tsconfig.json +0 -25
  41. package/client/admin/vite.config.ts +0 -12
  42. package/client/landing/.dockerignore +0 -3
  43. package/client/landing/.env.example +0 -1
  44. package/client/landing/Dockerfile +0 -16
  45. package/client/landing/Dockerfile.dev +0 -18
  46. package/client/landing/README.md +0 -18
  47. package/client/landing/index.html +0 -12
  48. package/client/landing/package.json +0 -41
  49. package/client/landing/postcss.config.js +0 -6
  50. package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
  51. package/client/landing/src/App.tsx +0 -21
  52. package/client/landing/src/api/catalog.ts +0 -30
  53. package/client/landing/src/api/client.ts +0 -30
  54. package/client/landing/src/auth/AuthProvider.tsx +0 -122
  55. package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
  56. package/client/landing/src/auth/auth-client.ts +0 -38
  57. package/client/landing/src/auth/types.ts +0 -18
  58. package/client/landing/src/components/LandingShell.tsx +0 -34
  59. package/client/landing/src/lib/specRouteCatalog.json +0 -23
  60. package/client/landing/src/lib/specScreens.json +0 -17
  61. package/client/landing/src/main.tsx +0 -17
  62. package/client/landing/src/pages/LandingHomePage.tsx +0 -215
  63. package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
  64. package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
  65. package/client/landing/src/styles/globals.css +0 -17
  66. package/client/landing/src/theme-vars.ts +0 -16
  67. package/client/landing/src/theme.ts +0 -21
  68. package/client/landing/src/vite-env.d.ts +0 -1
  69. package/client/landing/tailwind.config.js +0 -8
  70. package/client/landing/tsconfig.json +0 -25
  71. package/client/landing/vite.config.ts +0 -12
  72. package/client/mobile/.dockerignore +0 -2
  73. package/client/mobile/.env.example +0 -1
  74. package/client/mobile/Dockerfile +0 -16
  75. package/client/mobile/Dockerfile.dev +0 -18
  76. package/client/mobile/README.md +0 -19
  77. package/client/mobile/index.html +0 -12
  78. package/client/mobile/package.json +0 -42
  79. package/client/mobile/postcss.config.js +0 -6
  80. package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
  81. package/client/mobile/src/App.tsx +0 -1
  82. package/client/mobile/src/api/client.ts +0 -62
  83. package/client/mobile/src/api/fulfillment.ts +0 -55
  84. package/client/mobile/src/api/shipping.ts +0 -56
  85. package/client/mobile/src/app/App.tsx +0 -23
  86. package/client/mobile/src/auth/AuthProvider.tsx +0 -122
  87. package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
  88. package/client/mobile/src/auth/auth-client.ts +0 -38
  89. package/client/mobile/src/auth/types.ts +0 -18
  90. package/client/mobile/src/components/InShell.tsx +0 -74
  91. package/client/mobile/src/components/ui/button.tsx +0 -35
  92. package/client/mobile/src/components/ui/card.tsx +0 -15
  93. package/client/mobile/src/components/ui/input.tsx +0 -21
  94. package/client/mobile/src/lib/cn.ts +0 -6
  95. package/client/mobile/src/lib/specRouteCatalog.json +0 -26
  96. package/client/mobile/src/lib/specScreens.json +0 -22
  97. package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
  98. package/client/mobile/src/main.tsx +0 -17
  99. package/client/mobile/src/pages/DashboardPage.tsx +0 -172
  100. package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
  101. package/client/mobile/src/pages/LoginPage.tsx +0 -74
  102. package/client/mobile/src/pages/ShippingPage.tsx +0 -338
  103. package/client/mobile/src/styles/globals.css +0 -23
  104. package/client/mobile/src/theme-vars.ts +0 -16
  105. package/client/mobile/src/theme.ts +0 -21
  106. package/client/mobile/src/vite-env.d.ts +0 -1
  107. package/client/mobile/tailwind.config.js +0 -8
  108. package/client/mobile/tsconfig.json +0 -25
  109. package/client/mobile/vite.config.ts +0 -12
  110. package/client/web/.dockerignore +0 -3
  111. package/client/web/.env.example +0 -1
  112. package/client/web/Dockerfile +0 -16
  113. package/client/web/Dockerfile.dev +0 -18
  114. package/client/web/README.md +0 -47
  115. package/client/web/index.html +0 -12
  116. package/client/web/package.json +0 -42
  117. package/client/web/postcss.config.js +0 -6
  118. package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
  119. package/client/web/src/api/client.ts +0 -30
  120. package/client/web/src/api/orders.ts +0 -42
  121. package/client/web/src/app/App.tsx +0 -21
  122. package/client/web/src/auth/AuthProvider.tsx +0 -122
  123. package/client/web/src/auth/ProtectedRoute.tsx +0 -22
  124. package/client/web/src/auth/auth-client.ts +0 -38
  125. package/client/web/src/auth/types.ts +0 -18
  126. package/client/web/src/components/AppShell.tsx +0 -59
  127. package/client/web/src/components/ui/button.tsx +0 -35
  128. package/client/web/src/components/ui/card.tsx +0 -7
  129. package/client/web/src/components/ui/input.tsx +0 -21
  130. package/client/web/src/lib/cn.ts +0 -6
  131. package/client/web/src/lib/specRouteCatalog.json +0 -23
  132. package/client/web/src/lib/specScreens.json +0 -17
  133. package/client/web/src/main.tsx +0 -17
  134. package/client/web/src/pages/DashboardPage.tsx +0 -158
  135. package/client/web/src/pages/LoginPage.tsx +0 -72
  136. package/client/web/src/pages/OrdersPage.tsx +0 -123
  137. package/client/web/src/styles/globals.css +0 -17
  138. package/client/web/src/theme-vars.ts +0 -18
  139. package/client/web/src/theme.ts +0 -25
  140. package/client/web/src/vite-env.d.ts +0 -1
  141. package/client/web/tailwind.config.js +0 -8
  142. package/client/web/tsconfig.json +0 -25
  143. package/client/web/vite.config.ts +0 -12
  144. package/server/.dockerignore +0 -4
  145. package/server/.env.example +0 -19
  146. package/server/Dockerfile +0 -22
  147. package/server/Dockerfile.dev +0 -19
  148. package/server/README.md +0 -33
  149. package/server/__init__.py +0 -0
  150. package/server/api/__init__.py +0 -1
  151. package/server/api/http/__init__.py +0 -4
  152. package/server/api/http/app.py +0 -53
  153. package/server/api/http/router.py +0 -24
  154. package/server/config.py +0 -52
  155. package/server/contexts/__init__.py +0 -12
  156. package/server/contexts/alerts/__init__.py +0 -1
  157. package/server/contexts/alerts/application/__init__.py +0 -13
  158. package/server/contexts/alerts/application/services.py +0 -41
  159. package/server/contexts/alerts/contracts/__init__.py +0 -3
  160. package/server/contexts/alerts/contracts/http/__init__.py +0 -3
  161. package/server/contexts/alerts/contracts/http/router.py +0 -37
  162. package/server/contexts/alerts/domain/__init__.py +0 -15
  163. package/server/contexts/alerts/domain/models.py +0 -29
  164. package/server/contexts/alerts/infrastructure/__init__.py +0 -11
  165. package/server/contexts/alerts/infrastructure/repository.py +0 -41
  166. package/server/contexts/auth/__init__.py +0 -1
  167. package/server/contexts/auth/application/__init__.py +0 -3
  168. package/server/contexts/auth/application/ports.py +0 -10
  169. package/server/contexts/auth/application/services.py +0 -64
  170. package/server/contexts/auth/contracts/__init__.py +0 -4
  171. package/server/contexts/auth/contracts/http/__init__.py +0 -4
  172. package/server/contexts/auth/contracts/http/dependencies.py +0 -37
  173. package/server/contexts/auth/contracts/http/router.py +0 -19
  174. package/server/contexts/auth/domain/__init__.py +0 -3
  175. package/server/contexts/auth/domain/models.py +0 -24
  176. package/server/contexts/auth/infrastructure/__init__.py +0 -4
  177. package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
  178. package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
  179. package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
  180. package/server/contexts/auth/infrastructure/repository.py +0 -28
  181. package/server/contexts/catalog/__init__.py +0 -1
  182. package/server/contexts/catalog/application/__init__.py +0 -28
  183. package/server/contexts/catalog/application/ports.py +0 -15
  184. package/server/contexts/catalog/application/services.py +0 -154
  185. package/server/contexts/catalog/contracts/__init__.py +0 -3
  186. package/server/contexts/catalog/contracts/http/__init__.py +0 -3
  187. package/server/contexts/catalog/contracts/http/router.py +0 -60
  188. package/server/contexts/catalog/domain/__init__.py +0 -45
  189. package/server/contexts/catalog/domain/models.py +0 -113
  190. package/server/contexts/catalog/infrastructure/__init__.py +0 -4
  191. package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
  192. package/server/contexts/catalog/infrastructure/repository.py +0 -8
  193. package/server/contexts/fulfillment/__init__.py +0 -1
  194. package/server/contexts/fulfillment/application/__init__.py +0 -13
  195. package/server/contexts/fulfillment/application/ports.py +0 -20
  196. package/server/contexts/fulfillment/application/services.py +0 -85
  197. package/server/contexts/fulfillment/contracts/__init__.py +0 -3
  198. package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
  199. package/server/contexts/fulfillment/contracts/http/router.py +0 -40
  200. package/server/contexts/fulfillment/domain/__init__.py +0 -25
  201. package/server/contexts/fulfillment/domain/models.py +0 -73
  202. package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
  203. package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
  204. package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
  205. package/server/contexts/health/__init__.py +0 -1
  206. package/server/contexts/health/application/__init__.py +0 -3
  207. package/server/contexts/health/application/services.py +0 -2
  208. package/server/contexts/health/contracts/__init__.py +0 -3
  209. package/server/contexts/health/contracts/http/__init__.py +0 -3
  210. package/server/contexts/health/contracts/http/router.py +0 -10
  211. package/server/contexts/inventory/__init__.py +0 -1
  212. package/server/contexts/inventory/application/__init__.py +0 -28
  213. package/server/contexts/inventory/application/ports.py +0 -11
  214. package/server/contexts/inventory/application/services.py +0 -214
  215. package/server/contexts/inventory/contracts/__init__.py +0 -3
  216. package/server/contexts/inventory/contracts/http/__init__.py +0 -3
  217. package/server/contexts/inventory/contracts/http/router.py +0 -82
  218. package/server/contexts/inventory/domain/__init__.py +0 -33
  219. package/server/contexts/inventory/domain/models.py +0 -93
  220. package/server/contexts/inventory/infrastructure/__init__.py +0 -4
  221. package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
  222. package/server/contexts/inventory/infrastructure/repository.py +0 -8
  223. package/server/contexts/orders/__init__.py +0 -1
  224. package/server/contexts/orders/application/__init__.py +0 -19
  225. package/server/contexts/orders/application/services.py +0 -127
  226. package/server/contexts/orders/contracts/__init__.py +0 -3
  227. package/server/contexts/orders/contracts/http/__init__.py +0 -3
  228. package/server/contexts/orders/contracts/http/router.py +0 -82
  229. package/server/contexts/orders/domain/__init__.py +0 -29
  230. package/server/contexts/orders/domain/models.py +0 -95
  231. package/server/contexts/orders/infrastructure/__init__.py +0 -7
  232. package/server/contexts/orders/infrastructure/repository.py +0 -104
  233. package/server/contexts/shipping/__init__.py +0 -1
  234. package/server/contexts/shipping/application/__init__.py +0 -13
  235. package/server/contexts/shipping/application/services.py +0 -92
  236. package/server/contexts/shipping/contracts/__init__.py +0 -3
  237. package/server/contexts/shipping/contracts/http/__init__.py +0 -3
  238. package/server/contexts/shipping/contracts/http/router.py +0 -40
  239. package/server/contexts/shipping/domain/__init__.py +0 -19
  240. package/server/contexts/shipping/domain/models.py +0 -48
  241. package/server/contexts/shipping/infrastructure/__init__.py +0 -9
  242. package/server/contexts/shipping/infrastructure/repository.py +0 -50
  243. package/server/contexts/support/__init__.py +0 -1
  244. package/server/contexts/support/application/__init__.py +0 -13
  245. package/server/contexts/support/application/services.py +0 -29
  246. package/server/contexts/support/contracts/__init__.py +0 -3
  247. package/server/contexts/support/contracts/http/__init__.py +0 -3
  248. package/server/contexts/support/contracts/http/router.py +0 -40
  249. package/server/contexts/support/domain/__init__.py +0 -13
  250. package/server/contexts/support/domain/models.py +0 -27
  251. package/server/contexts/support/infrastructure/__init__.py +0 -11
  252. package/server/contexts/support/infrastructure/repository.py +0 -70
  253. package/server/contexts/user/__init__.py +0 -1
  254. package/server/contexts/user/application/__init__.py +0 -3
  255. package/server/contexts/user/application/ports.py +0 -11
  256. package/server/contexts/user/application/services.py +0 -44
  257. package/server/contexts/user/contracts/__init__.py +0 -3
  258. package/server/contexts/user/contracts/http/__init__.py +0 -3
  259. package/server/contexts/user/contracts/http/router.py +0 -26
  260. package/server/contexts/user/domain/__init__.py +0 -3
  261. package/server/contexts/user/domain/models.py +0 -22
  262. package/server/contexts/user/infrastructure/__init__.py +0 -3
  263. package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
  264. package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
  265. package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
  266. package/server/contexts/user/infrastructure/factory.py +0 -28
  267. package/server/data/README.md +0 -24
  268. package/server/data/bootstrap/alerts.json +0 -38
  269. package/server/data/bootstrap/auth_accounts.json +0 -18
  270. package/server/data/bootstrap/catalog_products.json +0 -179
  271. package/server/data/bootstrap/fulfillment_events.json +0 -5
  272. package/server/data/bootstrap/fulfillment_notes.json +0 -5
  273. package/server/data/bootstrap/fulfillment_tasks.json +0 -50
  274. package/server/data/bootstrap/inventory_levels.json +0 -80
  275. package/server/data/bootstrap/orders.json +0 -62
  276. package/server/data/bootstrap/shipping_shipments.json +0 -50
  277. package/server/data/bootstrap/support_faqs.json +0 -26
  278. package/server/data/bootstrap/users.json +0 -20
  279. package/server/data/bootstrap_loader.py +0 -15
  280. package/server/docker-entrypoint.sh +0 -56
  281. package/server/main.py +0 -3
  282. package/server/pyproject.toml +0 -36
  283. package/server/shared/__init__.py +0 -1
  284. package/server/shared/application/__init__.py +0 -3
  285. package/server/shared/application/health.py +0 -2
  286. package/server/shared/infrastructure/__init__.py +0 -10
  287. package/server/shared/infrastructure/runtime.py +0 -6
  288. package/server/shared/infrastructure/security.py +0 -33
  289. package/server/tests/e2e/test_domain_feature_flows.py +0 -483
  290. package/server/tests/test_health.py +0 -49
  291. 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,15 +0,0 @@
1
- from .models import (
2
- AlertItem,
3
- AlertReadResult,
4
- AlertRecord,
5
- AlertsPayload,
6
- AlertsReadAllResult,
7
- )
8
-
9
- __all__ = [
10
- "AlertItem",
11
- "AlertReadResult",
12
- "AlertRecord",
13
- "AlertsPayload",
14
- "AlertsReadAllResult",
15
- ]
@@ -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,11 +0,0 @@
1
- from .repository import (
2
- list_seed_alerts,
3
- mark_all_seed_alerts_read,
4
- mark_seed_alert_read,
5
- )
6
-
7
- __all__ = [
8
- "list_seed_alerts",
9
- "mark_all_seed_alerts_read",
10
- "mark_seed_alert_read",
11
- ]
@@ -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,3 +0,0 @@
1
- from .services import authenticate_user, prepare_auth_store, resolve_current_user
2
-
3
- __all__ = ["authenticate_user", "resolve_current_user", "prepare_auth_store"]
@@ -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,4 +0,0 @@
1
- from .http.dependencies import require_admin_user, require_authenticated_user
2
- from .http.router import router
3
-
4
- __all__ = ["require_admin_user", "require_authenticated_user", "router"]
@@ -1,4 +0,0 @@
1
- from .dependencies import require_admin_user, require_authenticated_user
2
- from .router import router
3
-
4
- __all__ = ["require_admin_user", "require_authenticated_user", "router"]
@@ -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,3 +0,0 @@
1
- from .models import AuthAccountRecord, AuthenticatedUser, AuthToken, LoginCommand
2
-
3
- __all__ = ["AuthToken", "LoginCommand", "AuthenticatedUser", "AuthAccountRecord"]
@@ -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,4 +0,0 @@
1
- from .repository import get_auth_repository
2
- from shared.infrastructure import decode_access_token, issue_access_token, verify_password
3
-
4
- __all__ = ["decode_access_token", "get_auth_repository", "issue_access_token", "verify_password"]
@@ -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: ...