agentic-dev 0.2.9 → 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 -8
  2. package/bin/agentic-dev.mjs +692 -55
  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,154 +0,0 @@
1
- from datetime import datetime, timezone
2
- from functools import lru_cache
3
-
4
- from fastapi import HTTPException, status as http_status
5
-
6
- from contexts.catalog.domain import (
7
- CreateProductCommand,
8
- ProductDetail,
9
- ProductRecord,
10
- ProductStatus,
11
- ProductSummary,
12
- UpdateProductCommand,
13
- UpdateProductStatusCommand,
14
- )
15
- from contexts.catalog.infrastructure import get_catalog_repository
16
- from data.bootstrap_loader import load_bootstrap_json
17
-
18
-
19
- def list_product_summaries(
20
- product_status: ProductStatus | None = None,
21
- category: str | None = None,
22
- search: str | None = None,
23
- ) -> list[ProductSummary]:
24
- prepare_catalog_store()
25
- repository = get_catalog_repository()
26
- products = repository.list_products()
27
-
28
- if product_status is not None:
29
- products = [product for product in products if product.status == product_status]
30
- if category is not None:
31
- normalized_category = category.strip().casefold()
32
- products = [product for product in products if product.category.casefold() == normalized_category]
33
- if search is not None:
34
- normalized_search = search.strip().casefold()
35
- products = [
36
- product
37
- for product in products
38
- if normalized_search
39
- in " ".join([product.name, product.brand, product.category, product.slug, *product.tags]).casefold()
40
- ]
41
-
42
- return [_to_summary(product) for product in products]
43
-
44
-
45
- def get_product_or_404(product_id: str) -> ProductDetail:
46
- prepare_catalog_store()
47
- repository = get_catalog_repository()
48
- product = repository.get_by_id(product_id)
49
- if product is None:
50
- raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Catalog product not found")
51
- return ProductDetail(**product.model_dump())
52
-
53
-
54
- def create_product(command: CreateProductCommand) -> ProductDetail:
55
- prepare_catalog_store()
56
- repository = get_catalog_repository()
57
- if repository.get_by_slug(command.slug) is not None:
58
- raise HTTPException(status_code=http_status.HTTP_409_CONFLICT, detail="Catalog product slug already exists")
59
-
60
- next_id = _next_product_id(repository.list_products())
61
- timestamp = _now_iso()
62
- product = ProductRecord(
63
- id=next_id,
64
- slug=command.slug,
65
- name=command.name,
66
- brand=command.brand,
67
- category=command.category,
68
- status=command.status,
69
- short_description=command.short_description,
70
- description=command.description,
71
- hero_image=command.hero_image,
72
- gallery=command.gallery,
73
- price=command.price,
74
- compare_at_price=command.compare_at_price,
75
- tags=command.tags,
76
- attributes=command.attributes,
77
- variants=command.variants,
78
- created_at=timestamp,
79
- updated_at=timestamp,
80
- )
81
- return ProductDetail(**repository.create_product(product).model_dump())
82
-
83
-
84
- def update_product_or_404(product_id: str, command: UpdateProductCommand) -> ProductDetail:
85
- prepare_catalog_store()
86
- repository = get_catalog_repository()
87
- current = repository.get_by_id(product_id)
88
- if current is None:
89
- raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Catalog product not found")
90
-
91
- updates = command.model_dump(exclude_none=True, exclude_unset=True)
92
- if not updates:
93
- raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail="No catalog product fields provided")
94
-
95
- if "slug" in updates:
96
- existing = repository.get_by_slug(updates["slug"])
97
- if existing is not None and existing.id != product_id:
98
- raise HTTPException(status_code=http_status.HTTP_409_CONFLICT, detail="Catalog product slug already exists")
99
-
100
- updated = current.model_copy(update={**updates, "updated_at": _now_iso()})
101
- return ProductDetail(**repository.update_product(updated).model_dump())
102
-
103
-
104
- def update_product_status_or_404(product_id: str, command: UpdateProductStatusCommand) -> ProductDetail:
105
- prepare_catalog_store()
106
- repository = get_catalog_repository()
107
- current = repository.get_by_id(product_id)
108
- if current is None:
109
- raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Catalog product not found")
110
- updated = current.model_copy(update={"status": command.status, "updated_at": _now_iso()})
111
- return ProductDetail(**repository.update_product(updated).model_dump())
112
-
113
-
114
- def prepare_catalog_store() -> None:
115
- _seed_catalog_store()
116
-
117
-
118
- @lru_cache
119
- def _seed_catalog_store() -> None:
120
- repository = get_catalog_repository()
121
- repository.initialize()
122
- records = [ProductRecord(**entry) for entry in load_bootstrap_json("catalog_products.json")]
123
- repository.seed_products(records)
124
-
125
-
126
- def _to_summary(product: ProductRecord) -> ProductSummary:
127
- return ProductSummary(
128
- id=product.id,
129
- slug=product.slug,
130
- name=product.name,
131
- brand=product.brand,
132
- category=product.category,
133
- status=product.status,
134
- short_description=product.short_description,
135
- hero_image=product.hero_image,
136
- price=product.price,
137
- compare_at_price=product.compare_at_price,
138
- tags=product.tags,
139
- variant_count=len(product.variants),
140
- )
141
-
142
-
143
- def _next_product_id(products: list[ProductRecord]) -> str:
144
- max_sequence = 1000
145
- for product in products:
146
- try:
147
- max_sequence = max(max_sequence, int(product.id.split("-")[-1]))
148
- except ValueError:
149
- continue
150
- return f"prd-{max_sequence + 1}"
151
-
152
-
153
- def _now_iso() -> str:
154
- return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
@@ -1,3 +0,0 @@
1
- from .http.router import router
2
-
3
- __all__ = ["router"]
@@ -1,3 +0,0 @@
1
- from .router import router
2
-
3
- __all__ = ["router"]
@@ -1,60 +0,0 @@
1
- from fastapi import APIRouter, Depends, Query, status
2
-
3
- from contexts.auth.contracts.http.dependencies import require_admin_user
4
- from contexts.catalog.application import (
5
- create_product,
6
- get_product_or_404,
7
- list_product_summaries,
8
- update_product_or_404,
9
- update_product_status_or_404,
10
- )
11
- from contexts.catalog.domain import (
12
- CreateProductCommand,
13
- ProductDetail,
14
- ProductStatus,
15
- ProductSummary,
16
- UpdateProductCommand,
17
- UpdateProductStatusCommand,
18
- )
19
-
20
- router = APIRouter(prefix="/catalog", tags=["catalog"])
21
-
22
-
23
- @router.get("/products", response_model=list[ProductSummary])
24
- def list_products(
25
- product_status: ProductStatus | None = Query(default=None, alias="status"),
26
- category: str | None = Query(default=None),
27
- search: str | None = Query(default=None, alias="q"),
28
- ) -> list[ProductSummary]:
29
- return list_product_summaries(product_status=product_status, category=category, search=search)
30
-
31
-
32
- @router.get("/products/{product_id}", response_model=ProductDetail)
33
- def get_product(product_id: str) -> ProductDetail:
34
- return get_product_or_404(product_id)
35
-
36
-
37
- @router.post("/products", response_model=ProductDetail, status_code=status.HTTP_201_CREATED)
38
- def post_product(
39
- command: CreateProductCommand,
40
- _: object = Depends(require_admin_user),
41
- ) -> ProductDetail:
42
- return create_product(command)
43
-
44
-
45
- @router.put("/products/{product_id}", response_model=ProductDetail)
46
- def put_product(
47
- product_id: str,
48
- command: UpdateProductCommand,
49
- _: object = Depends(require_admin_user),
50
- ) -> ProductDetail:
51
- return update_product_or_404(product_id, command)
52
-
53
-
54
- @router.patch("/products/{product_id}/status", response_model=ProductDetail)
55
- def patch_product_status(
56
- product_id: str,
57
- command: UpdateProductStatusCommand,
58
- _: object = Depends(require_admin_user),
59
- ) -> ProductDetail:
60
- return update_product_status_or_404(product_id, command)
@@ -1,45 +0,0 @@
1
- from .models import (
2
- CatalogMedia,
3
- CatalogMoney,
4
- CatalogProductCreateCommand,
5
- CatalogProductDetail,
6
- CatalogProductRecord,
7
- CatalogProductStatus,
8
- CatalogProductStatusCommand,
9
- CatalogProductSummary,
10
- CatalogProductUpdateCommand,
11
- CreateProductCommand,
12
- MediaAsset,
13
- Money,
14
- ProductAttribute,
15
- ProductDetail,
16
- ProductRecord,
17
- ProductStatus,
18
- ProductSummary,
19
- ProductVariant,
20
- UpdateProductCommand,
21
- UpdateProductStatusCommand,
22
- )
23
-
24
- __all__ = [
25
- "CatalogMedia",
26
- "CatalogMoney",
27
- "CatalogProductCreateCommand",
28
- "CatalogProductDetail",
29
- "CatalogProductRecord",
30
- "CatalogProductStatus",
31
- "CatalogProductStatusCommand",
32
- "CatalogProductSummary",
33
- "CatalogProductUpdateCommand",
34
- "CreateProductCommand",
35
- "MediaAsset",
36
- "Money",
37
- "ProductAttribute",
38
- "ProductDetail",
39
- "ProductRecord",
40
- "ProductStatus",
41
- "ProductSummary",
42
- "ProductVariant",
43
- "UpdateProductCommand",
44
- "UpdateProductStatusCommand",
45
- ]
@@ -1,113 +0,0 @@
1
- from typing import Literal, TypeAlias
2
-
3
- from pydantic import BaseModel, Field
4
-
5
- ProductStatus = Literal["draft", "active", "archived"]
6
- CatalogProductStatus: TypeAlias = ProductStatus
7
-
8
-
9
- class Money(BaseModel):
10
- amount: int = Field(ge=0)
11
- currency: str = Field(min_length=3, max_length=3)
12
-
13
-
14
- class MediaAsset(BaseModel):
15
- url: str = Field(min_length=1)
16
- alt: str = Field(min_length=1)
17
-
18
-
19
- class ProductAttribute(BaseModel):
20
- name: str = Field(min_length=1)
21
- value: str = Field(min_length=1)
22
-
23
-
24
- class ProductVariant(BaseModel):
25
- sku: str = Field(min_length=1)
26
- name: str = Field(min_length=1)
27
- option_values: list[str] = Field(min_length=1)
28
-
29
-
30
- class ProductRecord(BaseModel):
31
- id: str
32
- slug: str
33
- name: str
34
- brand: str
35
- category: str
36
- status: ProductStatus
37
- short_description: str
38
- description: str
39
- hero_image: MediaAsset
40
- gallery: list[MediaAsset] = Field(default_factory=list)
41
- price: Money
42
- compare_at_price: Money | None = None
43
- tags: list[str] = Field(default_factory=list)
44
- attributes: list[ProductAttribute] = Field(default_factory=list)
45
- variants: list[ProductVariant] = Field(min_length=1)
46
- created_at: str
47
- updated_at: str
48
-
49
-
50
- class ProductSummary(BaseModel):
51
- id: str
52
- slug: str
53
- name: str
54
- brand: str
55
- category: str
56
- status: ProductStatus
57
- short_description: str
58
- hero_image: MediaAsset
59
- price: Money
60
- compare_at_price: Money | None = None
61
- tags: list[str] = Field(default_factory=list)
62
- variant_count: int
63
-
64
-
65
- class ProductDetail(ProductRecord):
66
- pass
67
-
68
-
69
- class CreateProductCommand(BaseModel):
70
- slug: str = Field(min_length=1)
71
- name: str = Field(min_length=1)
72
- brand: str = Field(min_length=1)
73
- category: str = Field(min_length=1)
74
- status: ProductStatus = "draft"
75
- short_description: str = Field(min_length=1)
76
- description: str = Field(min_length=1)
77
- hero_image: MediaAsset
78
- gallery: list[MediaAsset] = Field(default_factory=list)
79
- price: Money
80
- compare_at_price: Money | None = None
81
- tags: list[str] = Field(default_factory=list)
82
- attributes: list[ProductAttribute] = Field(default_factory=list)
83
- variants: list[ProductVariant] = Field(min_length=1)
84
-
85
-
86
- class UpdateProductCommand(BaseModel):
87
- slug: str | None = Field(default=None, min_length=1)
88
- name: str | None = Field(default=None, min_length=1)
89
- brand: str | None = Field(default=None, min_length=1)
90
- category: str | None = Field(default=None, min_length=1)
91
- short_description: str | None = Field(default=None, min_length=1)
92
- description: str | None = Field(default=None, min_length=1)
93
- hero_image: MediaAsset | None = None
94
- gallery: list[MediaAsset] | None = None
95
- price: Money | None = None
96
- compare_at_price: Money | None = None
97
- tags: list[str] | None = None
98
- attributes: list[ProductAttribute] | None = None
99
- variants: list[ProductVariant] | None = Field(default=None, min_length=1)
100
-
101
-
102
- class UpdateProductStatusCommand(BaseModel):
103
- status: ProductStatus
104
-
105
-
106
- CatalogMoney = Money
107
- CatalogMedia = MediaAsset
108
- CatalogProductRecord = ProductRecord
109
- CatalogProductSummary = ProductSummary
110
- CatalogProductDetail = ProductDetail
111
- CatalogProductCreateCommand = CreateProductCommand
112
- CatalogProductUpdateCommand = UpdateProductCommand
113
- CatalogProductStatusCommand = UpdateProductStatusCommand
@@ -1,4 +0,0 @@
1
- from .adapters.memory import MemoryCatalogRepository
2
- from .repository import get_catalog_repository
3
-
4
- __all__ = ["MemoryCatalogRepository", "get_catalog_repository"]
@@ -1,62 +0,0 @@
1
- from contexts.catalog.domain import ProductRecord, ProductStatus, ProductSummary
2
-
3
-
4
- class MemoryCatalogRepository:
5
- def __init__(self) -> None:
6
- self._products: dict[str, ProductRecord] = {}
7
-
8
- def initialize(self) -> None:
9
- return None
10
-
11
- def seed_products(self, products: list[ProductRecord]) -> None:
12
- for product in products:
13
- self._products[product.id] = product.model_copy(deep=True)
14
-
15
- def list_products(self) -> list[ProductRecord]:
16
- return [product.model_copy(deep=True) for product in self._products.values()]
17
-
18
- def list_summaries(self) -> list[ProductSummary]:
19
- return [
20
- ProductSummary(
21
- id=product.id,
22
- slug=product.slug,
23
- name=product.name,
24
- brand=product.brand,
25
- category=product.category,
26
- status=product.status,
27
- short_description=product.short_description,
28
- hero_image=product.hero_image,
29
- price=product.price,
30
- compare_at_price=product.compare_at_price,
31
- tags=product.tags,
32
- variant_count=len(product.variants),
33
- )
34
- for product in self._products.values()
35
- ]
36
-
37
- def get_by_id(self, product_id: str) -> ProductRecord | None:
38
- product = self._products.get(product_id)
39
- return product.model_copy(deep=True) if product is not None else None
40
-
41
- def get_by_slug(self, slug: str) -> ProductRecord | None:
42
- normalized_slug = slug.casefold()
43
- for product in self._products.values():
44
- if product.slug.casefold() == normalized_slug:
45
- return product.model_copy(deep=True)
46
- return None
47
-
48
- def create_product(self, product: ProductRecord) -> ProductRecord:
49
- self._products[product.id] = product.model_copy(deep=True)
50
- return product.model_copy(deep=True)
51
-
52
- def update_product(self, product: ProductRecord) -> ProductRecord:
53
- self._products[product.id] = product.model_copy(deep=True)
54
- return product.model_copy(deep=True)
55
-
56
- def update_status(self, product_id: str, status: ProductStatus) -> ProductRecord | None:
57
- product = self._products.get(product_id)
58
- if product is None:
59
- return None
60
- updated = product.model_copy(update={"status": status})
61
- self._products[product_id] = updated
62
- return updated.model_copy(deep=True)
@@ -1,8 +0,0 @@
1
- from functools import lru_cache
2
-
3
- from contexts.catalog.infrastructure.adapters.memory import MemoryCatalogRepository
4
-
5
-
6
- @lru_cache
7
- def get_catalog_repository() -> MemoryCatalogRepository:
8
- return MemoryCatalogRepository()
@@ -1 +0,0 @@
1
- __all__ = ["application", "contracts", "domain", "infrastructure"]
@@ -1,13 +0,0 @@
1
- from .services import (
2
- get_fulfillment_board,
3
- get_fulfillment_overview,
4
- prepare_fulfillment_store,
5
- transition_fulfillment_task_status,
6
- )
7
-
8
- __all__ = [
9
- "get_fulfillment_board",
10
- "get_fulfillment_overview",
11
- "prepare_fulfillment_store",
12
- "transition_fulfillment_task_status",
13
- ]
@@ -1,20 +0,0 @@
1
- from typing import Protocol
2
-
3
- from contexts.fulfillment.domain import (
4
- FulfillmentEvent,
5
- FulfillmentNote,
6
- FulfillmentTaskRecord,
7
- )
8
-
9
-
10
- class FulfillmentRepository(Protocol):
11
- def initialize(self) -> None: ...
12
- def seed_tasks(self, tasks: list[FulfillmentTaskRecord]) -> None: ...
13
- def seed_events(self, events: list[FulfillmentEvent]) -> None: ...
14
- def seed_notes(self, notes: list[FulfillmentNote]) -> None: ...
15
- def list_tasks(self) -> list[FulfillmentTaskRecord]: ...
16
- def list_events(self) -> list[FulfillmentEvent]: ...
17
- def list_notes(self) -> list[FulfillmentNote]: ...
18
- def update_task_status(
19
- self, task_id: str, status: str
20
- ) -> FulfillmentTaskRecord | None: ...
@@ -1,85 +0,0 @@
1
- from collections import Counter
2
-
3
- from contexts.fulfillment.domain import (
4
- FulfillmentBoardItem,
5
- FulfillmentBoardPayload,
6
- FulfillmentOverview,
7
- FulfillmentStageLoad,
8
- FulfillmentStat,
9
- FulfillmentTaskStatusTransition,
10
- FulfillmentTaskStatusTransitionCommand,
11
- )
12
- from contexts.fulfillment.infrastructure import (
13
- list_seed_fulfillment_events,
14
- list_seed_fulfillment_notes,
15
- list_seed_fulfillment_tasks,
16
- transition_seed_fulfillment_task_status,
17
- )
18
-
19
-
20
- def get_fulfillment_overview() -> FulfillmentOverview:
21
- tasks = list_seed_fulfillment_tasks()
22
- stage_counts = Counter(task.stage for task in tasks if task.status != "Completed")
23
- blocked_count = sum(1 for task in tasks if task.status == "Blocked")
24
- active_count = sum(
25
- 1 for task in tasks if task.status in {"Queued", "In progress", "Blocked"}
26
- )
27
- outbound_count = sum(
28
- 1 for task in tasks if task.stage == "Outbound" and task.status != "Completed"
29
- )
30
-
31
- return FulfillmentOverview(
32
- throughput_total=str(sum(task.units for task in tasks)),
33
- stats=[
34
- FulfillmentStat(
35
- label="Open tasks", value=str(active_count), tone="text-[#22314d]"
36
- ),
37
- FulfillmentStat(
38
- label="Blocked",
39
- value=str(blocked_count),
40
- tone="text-[var(--app-danger)]",
41
- ),
42
- FulfillmentStat(
43
- label="Outbound ready",
44
- value=str(outbound_count),
45
- tone="text-[var(--app-accent)]",
46
- ),
47
- ],
48
- timeline=list_seed_fulfillment_events(),
49
- stage_load=[
50
- FulfillmentStageLoad(label=label, value=f"{count}건")
51
- for label, count in stage_counts.items()
52
- ],
53
- )
54
-
55
-
56
- def get_fulfillment_board() -> FulfillmentBoardPayload:
57
- tasks = list_seed_fulfillment_tasks()
58
- return FulfillmentBoardPayload(
59
- tasks=[
60
- FulfillmentBoardItem(
61
- id=task.id,
62
- order_id=task.order_id,
63
- title=task.title,
64
- assignee=task.assignee,
65
- stage=task.stage,
66
- status=task.status,
67
- priority=task.priority,
68
- sla=f"{task.sla_minutes} min",
69
- )
70
- for task in tasks
71
- ],
72
- notes=list_seed_fulfillment_notes(),
73
- )
74
-
75
-
76
- def transition_fulfillment_task_status(
77
- task_id: str, command: FulfillmentTaskStatusTransitionCommand
78
- ) -> FulfillmentTaskStatusTransition:
79
- return transition_seed_fulfillment_task_status(task_id, command)
80
-
81
-
82
- def prepare_fulfillment_store() -> None:
83
- list_seed_fulfillment_tasks()
84
- list_seed_fulfillment_events()
85
- list_seed_fulfillment_notes()
@@ -1,3 +0,0 @@
1
- from .http.router import router
2
-
3
- __all__ = ["router"]
@@ -1,3 +0,0 @@
1
- from .router import router
2
-
3
- __all__ = ["router"]
@@ -1,40 +0,0 @@
1
- from fastapi import APIRouter, Depends, HTTPException
2
-
3
- from contexts.auth.contracts.http.dependencies import require_authenticated_user
4
- from contexts.fulfillment.application import (
5
- get_fulfillment_board,
6
- get_fulfillment_overview,
7
- transition_fulfillment_task_status,
8
- )
9
- from contexts.fulfillment.domain import (
10
- FulfillmentBoardPayload,
11
- FulfillmentOverview,
12
- FulfillmentTaskStatusTransition,
13
- FulfillmentTaskStatusTransitionCommand,
14
- )
15
-
16
- router = APIRouter(
17
- prefix="/fulfillment",
18
- tags=["fulfillment"],
19
- dependencies=[Depends(require_authenticated_user)],
20
- )
21
-
22
-
23
- @router.get("/overview", response_model=FulfillmentOverview)
24
- def fulfillment_overview() -> FulfillmentOverview:
25
- return get_fulfillment_overview()
26
-
27
-
28
- @router.get("/board", response_model=FulfillmentBoardPayload)
29
- def fulfillment_board() -> FulfillmentBoardPayload:
30
- return get_fulfillment_board()
31
-
32
-
33
- @router.patch("/tasks/{task_id}/status", response_model=FulfillmentTaskStatusTransition)
34
- def transition_task_status(
35
- task_id: str, command: FulfillmentTaskStatusTransitionCommand
36
- ) -> FulfillmentTaskStatusTransition:
37
- try:
38
- return transition_fulfillment_task_status(task_id, command)
39
- except LookupError as exc:
40
- raise HTTPException(status_code=404, detail=str(exc)) from exc
@@ -1,25 +0,0 @@
1
- from .models import (
2
- FulfillmentBoardItem,
3
- FulfillmentBoardPayload,
4
- FulfillmentEvent,
5
- FulfillmentNote,
6
- FulfillmentOverview,
7
- FulfillmentStageLoad,
8
- FulfillmentStat,
9
- FulfillmentTaskRecord,
10
- FulfillmentTaskStatusTransition,
11
- FulfillmentTaskStatusTransitionCommand,
12
- )
13
-
14
- __all__ = [
15
- "FulfillmentBoardItem",
16
- "FulfillmentBoardPayload",
17
- "FulfillmentEvent",
18
- "FulfillmentNote",
19
- "FulfillmentOverview",
20
- "FulfillmentStageLoad",
21
- "FulfillmentStat",
22
- "FulfillmentTaskRecord",
23
- "FulfillmentTaskStatusTransition",
24
- "FulfillmentTaskStatusTransitionCommand",
25
- ]