kybernus 3.1.0 → 3.2.0

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 (287) hide show
  1. package/dist/cli/commands/ecommerce.d.ts +3 -0
  2. package/dist/cli/commands/ecommerce.d.ts.map +1 -0
  3. package/dist/cli/commands/ecommerce.js +164 -0
  4. package/dist/cli/commands/ecommerce.js.map +1 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/templates/ecommerce/.env.example +10 -0
  9. package/templates/ecommerce/.github/workflows/ci.yml +102 -0
  10. package/templates/ecommerce/.github/workflows/deploy.yml +31 -0
  11. package/templates/ecommerce/.prettierrc +9 -0
  12. package/templates/ecommerce/Dockerfile +54 -0
  13. package/templates/ecommerce/README.md +295 -0
  14. package/templates/ecommerce/apps/api/.env.example +59 -0
  15. package/templates/ecommerce/apps/api/jest.config.ts +50 -0
  16. package/templates/ecommerce/apps/api/jest.integration.config.ts +45 -0
  17. package/templates/ecommerce/apps/api/package.json +59 -0
  18. package/templates/ecommerce/apps/api/prisma/migrations/20260306000137_init/migration.sql +184 -0
  19. package/templates/ecommerce/apps/api/prisma/migrations/migration_lock.toml +3 -0
  20. package/templates/ecommerce/apps/api/prisma/schema.prisma +181 -0
  21. package/templates/ecommerce/apps/api/prisma/seed.ts +159 -0
  22. package/templates/ecommerce/apps/api/src/__tests__/app.test.ts +39 -0
  23. package/templates/ecommerce/apps/api/src/__tests__/globalSetup.ts +34 -0
  24. package/templates/ecommerce/apps/api/src/__tests__/globalTeardown.ts +16 -0
  25. package/templates/ecommerce/apps/api/src/__tests__/setup.db.ts +18 -0
  26. package/templates/ecommerce/apps/api/src/__tests__/setup.env.ts +14 -0
  27. package/templates/ecommerce/apps/api/src/app.ts +133 -0
  28. package/templates/ecommerce/apps/api/src/application/admin/admin-user.service.ts +24 -0
  29. package/templates/ecommerce/apps/api/src/application/admin/dashboard.service.ts +102 -0
  30. package/templates/ecommerce/apps/api/src/application/auth/auth.service.ts +185 -0
  31. package/templates/ecommerce/apps/api/src/application/cart/cart.service.ts +151 -0
  32. package/templates/ecommerce/apps/api/src/application/cart/coupon.service.ts +51 -0
  33. package/templates/ecommerce/apps/api/src/application/catalog/catalog.service.ts +168 -0
  34. package/templates/ecommerce/apps/api/src/application/checkout/checkout.service.ts +114 -0
  35. package/templates/ecommerce/apps/api/src/application/orders/order.service.ts +93 -0
  36. package/templates/ecommerce/apps/api/src/application/ports/email.port.ts +3 -0
  37. package/templates/ecommerce/apps/api/src/application/ports/payment.port.ts +24 -0
  38. package/templates/ecommerce/apps/api/src/application/ports/shipping.port.ts +9 -0
  39. package/templates/ecommerce/apps/api/src/application/ports/storage.port.ts +3 -0
  40. package/templates/ecommerce/apps/api/src/application/ports/token-blacklist.port.ts +4 -0
  41. package/templates/ecommerce/apps/api/src/application/ports/token.port.ts +18 -0
  42. package/templates/ecommerce/apps/api/src/application/profile/profile.service.ts +76 -0
  43. package/templates/ecommerce/apps/api/src/domain/auth/user.entity.ts +109 -0
  44. package/templates/ecommerce/apps/api/src/domain/auth/user.repository.ts +11 -0
  45. package/templates/ecommerce/apps/api/src/domain/cart/cart.entity.ts +136 -0
  46. package/templates/ecommerce/apps/api/src/domain/cart/cart.repository.ts +8 -0
  47. package/templates/ecommerce/apps/api/src/domain/cart/coupon.entity.ts +58 -0
  48. package/templates/ecommerce/apps/api/src/domain/cart/coupon.repository.ts +10 -0
  49. package/templates/ecommerce/apps/api/src/domain/catalog/category.entity.ts +51 -0
  50. package/templates/ecommerce/apps/api/src/domain/catalog/category.repository.ts +10 -0
  51. package/templates/ecommerce/apps/api/src/domain/catalog/product.entity.ts +130 -0
  52. package/templates/ecommerce/apps/api/src/domain/catalog/product.repository.ts +28 -0
  53. package/templates/ecommerce/apps/api/src/domain/checkout/order.entity.ts +121 -0
  54. package/templates/ecommerce/apps/api/src/domain/checkout/order.repository.ts +11 -0
  55. package/templates/ecommerce/apps/api/src/domain/shared/AppError.ts +12 -0
  56. package/templates/ecommerce/apps/api/src/infrastructure/cache/redis.ts +16 -0
  57. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/admin.registry.ts +13 -0
  58. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/auth.registry.ts +34 -0
  59. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/cart.registry.ts +49 -0
  60. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/catalog.registry.ts +24 -0
  61. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/checkout.registry.ts +47 -0
  62. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/orders.registry.ts +6 -0
  63. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/profile.registry.ts +4 -0
  64. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/cart.memory.repository.ts +33 -0
  65. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/category.memory.repository.ts +41 -0
  66. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/coupon.memory.repository.ts +55 -0
  67. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/order.memory.repository.ts +75 -0
  68. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/product.memory.repository.ts +100 -0
  69. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/user.memory.repository.ts +54 -0
  70. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/auth/user.prisma.repository.ts +83 -0
  71. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/category.prisma.repository.ts +69 -0
  72. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/product.prisma.repository.ts +185 -0
  73. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/checkout/order.prisma.repository.ts +149 -0
  74. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma-client.ts +17 -0
  75. package/templates/ecommerce/apps/api/src/infrastructure/services/email/email.registry.ts +18 -0
  76. package/templates/ecommerce/apps/api/src/infrastructure/services/email/ethereal.email.service.ts +38 -0
  77. package/templates/ecommerce/apps/api/src/infrastructure/services/email/noop.email.service.ts +12 -0
  78. package/templates/ecommerce/apps/api/src/infrastructure/services/email/smtp.email.service.ts +36 -0
  79. package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe-webhook.handler.ts +83 -0
  80. package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe.adapter.ts +39 -0
  81. package/templates/ecommerce/apps/api/src/infrastructure/services/shipping/mock.shipping.service.ts +17 -0
  82. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/in-memory.storage.service.ts +11 -0
  83. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/local-disk.storage.service.ts +27 -0
  84. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/s3.storage.service.ts +52 -0
  85. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/storage.registry.ts +19 -0
  86. package/templates/ecommerce/apps/api/src/infrastructure/services/token/redis.token.blacklist.ts +23 -0
  87. package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.blacklist.ts +30 -0
  88. package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.service.ts +136 -0
  89. package/templates/ecommerce/apps/api/src/modules/admin/__tests__/admin.routes.integration.test.ts +250 -0
  90. package/templates/ecommerce/apps/api/src/modules/admin/admin.controller.ts +116 -0
  91. package/templates/ecommerce/apps/api/src/modules/admin/admin.registry.ts +1 -0
  92. package/templates/ecommerce/apps/api/src/modules/admin/admin.routes.ts +21 -0
  93. package/templates/ecommerce/apps/api/src/modules/admin/admin.service.ts +1 -0
  94. package/templates/ecommerce/apps/api/src/modules/admin/admin.user.service.ts +1 -0
  95. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.logout.redis.test.ts +104 -0
  96. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.routes.integration.test.ts +211 -0
  97. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.service.unit.test.ts +260 -0
  98. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/email.service.unit.test.ts +94 -0
  99. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/token.blacklist.redis.test.ts +65 -0
  100. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.entity.unit.test.ts +79 -0
  101. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.prisma.repository.test.ts +138 -0
  102. package/templates/ecommerce/apps/api/src/modules/auth/auth.controller.ts +148 -0
  103. package/templates/ecommerce/apps/api/src/modules/auth/auth.registry.ts +1 -0
  104. package/templates/ecommerce/apps/api/src/modules/auth/auth.routes.ts +17 -0
  105. package/templates/ecommerce/apps/api/src/modules/auth/auth.service.ts +1 -0
  106. package/templates/ecommerce/apps/api/src/modules/auth/redis.token.blacklist.ts +1 -0
  107. package/templates/ecommerce/apps/api/src/modules/auth/token.blacklist.ts +2 -0
  108. package/templates/ecommerce/apps/api/src/modules/auth/token.service.ts +2 -0
  109. package/templates/ecommerce/apps/api/src/modules/auth/user.entity.ts +1 -0
  110. package/templates/ecommerce/apps/api/src/modules/auth/user.prisma.repository.ts +1 -0
  111. package/templates/ecommerce/apps/api/src/modules/auth/user.repository.ts +2 -0
  112. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.entity.unit.test.ts +144 -0
  113. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.routes.integration.test.ts +242 -0
  114. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.service.unit.test.ts +151 -0
  115. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/coupon.admin.integration.test.ts +136 -0
  116. package/templates/ecommerce/apps/api/src/modules/cart/cart.controller.ts +94 -0
  117. package/templates/ecommerce/apps/api/src/modules/cart/cart.entity.ts +1 -0
  118. package/templates/ecommerce/apps/api/src/modules/cart/cart.registry.ts +1 -0
  119. package/templates/ecommerce/apps/api/src/modules/cart/cart.repository.ts +2 -0
  120. package/templates/ecommerce/apps/api/src/modules/cart/cart.routes.ts +17 -0
  121. package/templates/ecommerce/apps/api/src/modules/cart/cart.service.ts +1 -0
  122. package/templates/ecommerce/apps/api/src/modules/cart/coupon.entity.ts +1 -0
  123. package/templates/ecommerce/apps/api/src/modules/cart/coupon.repository.ts +2 -0
  124. package/templates/ecommerce/apps/api/src/modules/cart/coupon.service.ts +1 -0
  125. package/templates/ecommerce/apps/api/src/modules/cart/shipping.service.ts +2 -0
  126. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.routes.integration.test.ts +275 -0
  127. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.service.unit.test.ts +223 -0
  128. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.image.integration.test.ts +130 -0
  129. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.prisma.repository.test.ts +174 -0
  130. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.controller.ts +176 -0
  131. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.registry.ts +1 -0
  132. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.routes.ts +38 -0
  133. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.service.ts +1 -0
  134. package/templates/ecommerce/apps/api/src/modules/catalog/category.entity.ts +1 -0
  135. package/templates/ecommerce/apps/api/src/modules/catalog/category.prisma.repository.ts +1 -0
  136. package/templates/ecommerce/apps/api/src/modules/catalog/category.repository.ts +2 -0
  137. package/templates/ecommerce/apps/api/src/modules/catalog/product.entity.ts +1 -0
  138. package/templates/ecommerce/apps/api/src/modules/catalog/product.prisma.repository.ts +1 -0
  139. package/templates/ecommerce/apps/api/src/modules/catalog/product.repository.ts +2 -0
  140. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.routes.integration.test.ts +163 -0
  141. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.service.unit.test.ts +191 -0
  142. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/order.prisma.repository.test.ts +150 -0
  143. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.controller.ts +59 -0
  144. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.registry.ts +1 -0
  145. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.routes.ts +18 -0
  146. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.service.ts +1 -0
  147. package/templates/ecommerce/apps/api/src/modules/checkout/order.entity.ts +1 -0
  148. package/templates/ecommerce/apps/api/src/modules/checkout/order.prisma.repository.ts +1 -0
  149. package/templates/ecommerce/apps/api/src/modules/checkout/order.repository.ts +2 -0
  150. package/templates/ecommerce/apps/api/src/modules/checkout/tax.service.ts +9 -0
  151. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.entity.unit.test.ts +68 -0
  152. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.routes.integration.test.ts +254 -0
  153. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.service.email.unit.test.ts +142 -0
  154. package/templates/ecommerce/apps/api/src/modules/orders/order.controller.ts +96 -0
  155. package/templates/ecommerce/apps/api/src/modules/orders/order.registry.ts +1 -0
  156. package/templates/ecommerce/apps/api/src/modules/orders/order.routes.ts +17 -0
  157. package/templates/ecommerce/apps/api/src/modules/orders/order.service.ts +1 -0
  158. package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe-webhook.unit.test.ts +330 -0
  159. package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe.adapter.unit.test.ts +84 -0
  160. package/templates/ecommerce/apps/api/src/modules/payment/adapters/stripe.adapter.ts +1 -0
  161. package/templates/ecommerce/apps/api/src/modules/payment/payment.port.ts +1 -0
  162. package/templates/ecommerce/apps/api/src/modules/payment/stripe-webhook.handler.ts +1 -0
  163. package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.routes.integration.test.ts +180 -0
  164. package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.service.unit.test.ts +187 -0
  165. package/templates/ecommerce/apps/api/src/modules/profile/profile.controller.ts +92 -0
  166. package/templates/ecommerce/apps/api/src/modules/profile/profile.registry.ts +1 -0
  167. package/templates/ecommerce/apps/api/src/modules/profile/profile.routes.ts +14 -0
  168. package/templates/ecommerce/apps/api/src/modules/profile/profile.service.ts +1 -0
  169. package/templates/ecommerce/apps/api/src/presentation/middlewares/authenticate.ts +37 -0
  170. package/templates/ecommerce/apps/api/src/presentation/middlewares/authorize.ts +23 -0
  171. package/templates/ecommerce/apps/api/src/presentation/middlewares/errorHandler.ts +48 -0
  172. package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.controller.ts +116 -0
  173. package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.routes.ts +21 -0
  174. package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.controller.ts +147 -0
  175. package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.routes.ts +17 -0
  176. package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.controller.ts +94 -0
  177. package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.routes.ts +17 -0
  178. package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.controller.ts +176 -0
  179. package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.routes.ts +38 -0
  180. package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.controller.ts +59 -0
  181. package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.routes.ts +18 -0
  182. package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.controller.ts +96 -0
  183. package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.routes.ts +17 -0
  184. package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.controller.ts +92 -0
  185. package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.routes.ts +14 -0
  186. package/templates/ecommerce/apps/api/src/presentation/validators/uuidParam.ts +20 -0
  187. package/templates/ecommerce/apps/api/src/server.ts +47 -0
  188. package/templates/ecommerce/apps/api/src/shared/__tests__/uuid.validation.test.ts +111 -0
  189. package/templates/ecommerce/apps/api/src/shared/errors/AppError.ts +1 -0
  190. package/templates/ecommerce/apps/api/src/shared/infra/email/EtherealEmailService.ts +1 -0
  191. package/templates/ecommerce/apps/api/src/shared/infra/email/IEmailService.ts +1 -0
  192. package/templates/ecommerce/apps/api/src/shared/infra/email/NoopEmailService.ts +1 -0
  193. package/templates/ecommerce/apps/api/src/shared/infra/email/SmtpEmailService.ts +1 -0
  194. package/templates/ecommerce/apps/api/src/shared/infra/email/__tests__/ethereal.email.integration.test.ts +32 -0
  195. package/templates/ecommerce/apps/api/src/shared/infra/email/email.registry.ts +1 -0
  196. package/templates/ecommerce/apps/api/src/shared/infra/prisma.ts +1 -0
  197. package/templates/ecommerce/apps/api/src/shared/infra/redis.ts +1 -0
  198. package/templates/ecommerce/apps/api/src/shared/infra/storage/IStorageService.ts +1 -0
  199. package/templates/ecommerce/apps/api/src/shared/infra/storage/InMemoryStorageService.ts +1 -0
  200. package/templates/ecommerce/apps/api/src/shared/infra/storage/LocalDiskStorageService.ts +1 -0
  201. package/templates/ecommerce/apps/api/src/shared/infra/storage/S3StorageService.ts +1 -0
  202. package/templates/ecommerce/apps/api/src/shared/infra/storage/__tests__/s3.storage.unit.test.ts +73 -0
  203. package/templates/ecommerce/apps/api/src/shared/infra/storage/storage.registry.ts +1 -0
  204. package/templates/ecommerce/apps/api/src/shared/middlewares/authenticate.ts +1 -0
  205. package/templates/ecommerce/apps/api/src/shared/middlewares/authorize.ts +1 -0
  206. package/templates/ecommerce/apps/api/src/shared/middlewares/errorHandler.ts +1 -0
  207. package/templates/ecommerce/apps/api/src/shared/validators/uuidParam.ts +1 -0
  208. package/templates/ecommerce/apps/api/tsconfig.json +15 -0
  209. package/templates/ecommerce/apps/web/.env.example +8 -0
  210. package/templates/ecommerce/apps/web/index.html +19 -0
  211. package/templates/ecommerce/apps/web/jest.config.ts +45 -0
  212. package/templates/ecommerce/apps/web/package.json +38 -0
  213. package/templates/ecommerce/apps/web/src/App.tsx +133 -0
  214. package/templates/ecommerce/apps/web/src/__mocks__/fileMock.ts +1 -0
  215. package/templates/ecommerce/apps/web/src/__mocks__/styleMock.ts +1 -0
  216. package/templates/ecommerce/apps/web/src/index.css +159 -0
  217. package/templates/ecommerce/apps/web/src/main.tsx +13 -0
  218. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/CouponsAdminPage.test.tsx +134 -0
  219. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/DashboardPage.test.tsx +65 -0
  220. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/OrdersAdminPage.test.tsx +79 -0
  221. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/ProductsAdminPage.test.tsx +84 -0
  222. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/UsersAdminPage.test.tsx +85 -0
  223. package/templates/ecommerce/apps/web/src/modules/admin/pages/CouponsAdminPage.tsx +179 -0
  224. package/templates/ecommerce/apps/web/src/modules/admin/pages/DashboardPage.tsx +58 -0
  225. package/templates/ecommerce/apps/web/src/modules/admin/pages/OrdersAdminPage.tsx +178 -0
  226. package/templates/ecommerce/apps/web/src/modules/admin/pages/ProductsAdminPage.tsx +444 -0
  227. package/templates/ecommerce/apps/web/src/modules/admin/pages/UsersAdminPage.tsx +87 -0
  228. package/templates/ecommerce/apps/web/src/modules/auth/LoginForm.tsx +91 -0
  229. package/templates/ecommerce/apps/web/src/modules/auth/RegisterForm.tsx +109 -0
  230. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ForgotPasswordPage.test.tsx +42 -0
  231. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/LoginForm.test.tsx +76 -0
  232. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/RegisterForm.test.tsx +62 -0
  233. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ResetPasswordPage.test.tsx +66 -0
  234. package/templates/ecommerce/apps/web/src/modules/auth/pages/ForgotPasswordPage.tsx +100 -0
  235. package/templates/ecommerce/apps/web/src/modules/auth/pages/LoginPage.tsx +39 -0
  236. package/templates/ecommerce/apps/web/src/modules/auth/pages/RegisterPage.tsx +39 -0
  237. package/templates/ecommerce/apps/web/src/modules/auth/pages/ResetPasswordPage.tsx +110 -0
  238. package/templates/ecommerce/apps/web/src/modules/auth/useAuthStore.ts +141 -0
  239. package/templates/ecommerce/apps/web/src/modules/cart/__tests__/CartPage.test.tsx +111 -0
  240. package/templates/ecommerce/apps/web/src/modules/cart/pages/CartPage.tsx +313 -0
  241. package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductCard.test.tsx +59 -0
  242. package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductFilters.test.tsx +56 -0
  243. package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductCard.tsx +78 -0
  244. package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductFilters.tsx +104 -0
  245. package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductDetailPage.tsx +179 -0
  246. package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductListPage.tsx +100 -0
  247. package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/CheckoutPage.test.tsx +159 -0
  248. package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/StripePaymentForm.test.tsx +79 -0
  249. package/templates/ecommerce/apps/web/src/modules/checkout/components/StripePaymentForm.tsx +55 -0
  250. package/templates/ecommerce/apps/web/src/modules/checkout/hooks/useCheckout.ts +56 -0
  251. package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutPage.tsx +344 -0
  252. package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutSuccessPage.tsx +12 -0
  253. package/templates/ecommerce/apps/web/src/modules/legal/pages/PrivacyPolicyPage.tsx +207 -0
  254. package/templates/ecommerce/apps/web/src/modules/legal/pages/TermsOfServicePage.tsx +175 -0
  255. package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderDetailPage.test.tsx +75 -0
  256. package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderHistoryPage.test.tsx +87 -0
  257. package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderDetailPage.tsx +73 -0
  258. package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderHistoryPage.tsx +97 -0
  259. package/templates/ecommerce/apps/web/src/modules/profile/__tests__/ProfilePage.test.tsx +150 -0
  260. package/templates/ecommerce/apps/web/src/modules/profile/pages/ProfilePage.tsx +275 -0
  261. package/templates/ecommerce/apps/web/src/setupTests.ts +10 -0
  262. package/templates/ecommerce/apps/web/src/shared/components/CookieConsent.tsx +108 -0
  263. package/templates/ecommerce/apps/web/src/shared/components/ErrorBoundary.tsx +112 -0
  264. package/templates/ecommerce/apps/web/src/shared/components/Layout.tsx +143 -0
  265. package/templates/ecommerce/apps/web/src/shared/components/ProtectedRoute.tsx +21 -0
  266. package/templates/ecommerce/apps/web/src/shared/config/siteConfig.ts +57 -0
  267. package/templates/ecommerce/apps/web/src/shared/hooks/usePageTitle.ts +16 -0
  268. package/templates/ecommerce/apps/web/src/shared/lib/apiFetch.ts +16 -0
  269. package/templates/ecommerce/apps/web/src/shared/pages/NotFoundPage.tsx +42 -0
  270. package/templates/ecommerce/apps/web/src/shared/theme/ThemeProvider.tsx +45 -0
  271. package/templates/ecommerce/apps/web/src/shared/theme/__tests__/ThemeProvider.test.tsx +78 -0
  272. package/templates/ecommerce/apps/web/src/shared/theme/createTheme.ts +58 -0
  273. package/templates/ecommerce/apps/web/src/shared/theme/tokens.ts +81 -0
  274. package/templates/ecommerce/apps/web/src/vite-env.d.ts +1 -0
  275. package/templates/ecommerce/apps/web/tsconfig.jest.json +12 -0
  276. package/templates/ecommerce/apps/web/tsconfig.json +25 -0
  277. package/templates/ecommerce/apps/web/tsconfig.node.json +11 -0
  278. package/templates/ecommerce/apps/web/vite.config.ts +30 -0
  279. package/templates/ecommerce/docker-compose.yml +85 -0
  280. package/templates/ecommerce/package-lock.json +11255 -0
  281. package/templates/ecommerce/package.json +27 -0
  282. package/templates/ecommerce/packages/shared-types/package.json +13 -0
  283. package/templates/ecommerce/packages/shared-types/src/index.ts +3 -0
  284. package/templates/ecommerce/packages/shared-types/src/theme.ts +44 -0
  285. package/templates/ecommerce/packages/shared-types/tsconfig.json +11 -0
  286. package/templates/ecommerce/scripts/customize.sh +201 -0
  287. package/templates/ecommerce/tsconfig.json +14 -0
@@ -0,0 +1,10 @@
1
+ import { CouponEntity } from './coupon.entity';
2
+
3
+ export interface ICouponRepository {
4
+ findByCode(code: string): Promise<CouponEntity | null>;
5
+ findById(id: string): Promise<CouponEntity | null>;
6
+ findAll(): Promise<CouponEntity[]>;
7
+ save(coupon: CouponEntity): Promise<CouponEntity>;
8
+ deleteById(id: string): Promise<void>;
9
+ incrementUsage(code: string): Promise<void>;
10
+ }
@@ -0,0 +1,51 @@
1
+ import { randomUUID } from 'crypto';
2
+
3
+ export interface CategoryProps {
4
+ id: string;
5
+ name: string;
6
+ slug: string;
7
+ description: string | null;
8
+ parentId: string | null;
9
+ createdAt: Date;
10
+ updatedAt: Date;
11
+ }
12
+
13
+ export interface CreateCategoryInput {
14
+ name: string;
15
+ slug: string;
16
+ description?: string | null;
17
+ parentId?: string | null;
18
+ }
19
+
20
+ export class CategoryEntity {
21
+ private constructor(private readonly props: CategoryProps) {}
22
+
23
+ static create(input: CreateCategoryInput): CategoryEntity {
24
+ if (!input.name.trim()) throw new Error('Category name is required');
25
+ return new CategoryEntity({
26
+ id: randomUUID(),
27
+ name: input.name.trim(),
28
+ slug: input.slug.toLowerCase(),
29
+ description: input.description ?? null,
30
+ parentId: input.parentId ?? null,
31
+ createdAt: new Date(),
32
+ updatedAt: new Date(),
33
+ });
34
+ }
35
+
36
+ static reconstitute(props: CategoryProps): CategoryEntity {
37
+ return new CategoryEntity(props);
38
+ }
39
+
40
+ get id(): string { return this.props.id; }
41
+ get name(): string { return this.props.name; }
42
+ get slug(): string { return this.props.slug; }
43
+ get description(): string | null { return this.props.description; }
44
+ get parentId(): string | null { return this.props.parentId; }
45
+ get createdAt(): Date { return this.props.createdAt; }
46
+ get updatedAt(): Date { return this.props.updatedAt; }
47
+
48
+ toRecord(): CategoryProps {
49
+ return { ...this.props };
50
+ }
51
+ }
@@ -0,0 +1,10 @@
1
+ import { CategoryEntity } from './category.entity';
2
+
3
+ export interface ICategoryRepository {
4
+ findById(id: string): Promise<CategoryEntity | null>;
5
+ findBySlug(slug: string): Promise<CategoryEntity | null>;
6
+ findAll(): Promise<CategoryEntity[]>;
7
+ create(category: CategoryEntity): Promise<CategoryEntity>;
8
+ update(category: CategoryEntity): Promise<CategoryEntity>;
9
+ delete(id: string): Promise<void>;
10
+ }
@@ -0,0 +1,130 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { AppError } from '../shared/AppError';
3
+
4
+ export enum ProductStatus {
5
+ ACTIVE = 'ACTIVE',
6
+ INACTIVE = 'INACTIVE',
7
+ DELETED = 'DELETED',
8
+ }
9
+
10
+ export interface ProductVariantProps {
11
+ id: string;
12
+ sku: string;
13
+ size: string | null;
14
+ color: string | null;
15
+ stock: number;
16
+ price: number | null;
17
+ productId: string;
18
+ }
19
+
20
+ export interface ProductProps {
21
+ id: string;
22
+ name: string;
23
+ slug: string;
24
+ description: string | null;
25
+ price: number;
26
+ categoryId: string;
27
+ status: ProductStatus;
28
+ images: string[];
29
+ createdAt: Date;
30
+ updatedAt: Date;
31
+ variants: ProductVariantProps[];
32
+ }
33
+
34
+ export interface CreateProductInput {
35
+ name: string;
36
+ slug: string;
37
+ description?: string | null;
38
+ price: number;
39
+ categoryId: string;
40
+ images?: string[];
41
+ variants: Array<{
42
+ sku: string;
43
+ size?: string | null;
44
+ color?: string | null;
45
+ stock: number;
46
+ price?: number | null;
47
+ }>;
48
+ }
49
+
50
+ export class ProductEntity {
51
+ private constructor(private readonly props: ProductProps) {}
52
+
53
+ static create(input: CreateProductInput): ProductEntity {
54
+ if (!input.name.trim()) {
55
+ throw new AppError('Nome do produto é obrigatório', 422);
56
+ }
57
+ if (input.price < 0) {
58
+ throw new AppError('Preço não pode ser negativo', 422);
59
+ }
60
+
61
+ const id = randomUUID();
62
+ return new ProductEntity({
63
+ id,
64
+ name: input.name.trim(),
65
+ slug: input.slug,
66
+ description: input.description ?? null,
67
+ price: input.price,
68
+ categoryId: input.categoryId,
69
+ status: ProductStatus.ACTIVE,
70
+ images: input.images ?? [],
71
+ createdAt: new Date(),
72
+ updatedAt: new Date(),
73
+ variants: input.variants.map((v) => ({
74
+ id: randomUUID(),
75
+ sku: v.sku,
76
+ size: v.size ?? null,
77
+ color: v.color ?? null,
78
+ stock: v.stock,
79
+ price: v.price ?? null,
80
+ productId: id,
81
+ })),
82
+ });
83
+ }
84
+
85
+ static reconstitute(props: ProductProps): ProductEntity {
86
+ return new ProductEntity(props);
87
+ }
88
+
89
+ get id(): string { return this.props.id; }
90
+ get name(): string { return this.props.name; }
91
+ get slug(): string { return this.props.slug; }
92
+ get description(): string | null { return this.props.description; }
93
+ get price(): number { return this.props.price; }
94
+ get categoryId(): string { return this.props.categoryId; }
95
+ get status(): ProductStatus { return this.props.status; }
96
+ get images(): string[] { return this.props.images; }
97
+ get createdAt(): Date { return this.props.createdAt; }
98
+ get updatedAt(): Date { return this.props.updatedAt; }
99
+ get variants(): ProductVariantProps[] { return this.props.variants; }
100
+
101
+ isDeleted(): boolean {
102
+ return this.props.status === ProductStatus.DELETED;
103
+ }
104
+
105
+ withUpdates(updates: Partial<Pick<ProductProps, 'name' | 'description' | 'price' | 'images' | 'status'>>): ProductEntity {
106
+ return new ProductEntity({
107
+ ...this.props,
108
+ ...updates,
109
+ updatedAt: new Date(),
110
+ });
111
+ }
112
+
113
+ withDeletedStatus(): ProductEntity {
114
+ return this.withUpdates({ status: ProductStatus.DELETED });
115
+ }
116
+
117
+ withVariantStockUpdate(variantId: string, newStock: number): ProductEntity {
118
+ return new ProductEntity({
119
+ ...this.props,
120
+ updatedAt: new Date(),
121
+ variants: this.props.variants.map((v) =>
122
+ v.id === variantId ? { ...v, stock: newStock } : v,
123
+ ),
124
+ });
125
+ }
126
+
127
+ toRecord(): ProductProps {
128
+ return { ...this.props, variants: this.props.variants.map((v) => ({ ...v })) };
129
+ }
130
+ }
@@ -0,0 +1,28 @@
1
+ import { ProductEntity } from './product.entity';
2
+
3
+ export interface SearchParams {
4
+ q?: string;
5
+ categoryId?: string;
6
+ minPrice?: number;
7
+ maxPrice?: number;
8
+ sortBy?: 'price_asc' | 'price_desc' | 'newest';
9
+ cursor?: string;
10
+ limit?: number;
11
+ }
12
+
13
+ export interface SearchResult {
14
+ items: ProductEntity[];
15
+ nextCursor: string | null;
16
+ }
17
+
18
+ export interface IProductRepository {
19
+ findById(id: string): Promise<ProductEntity | null>;
20
+ findBySlug(slug: string): Promise<ProductEntity | null>;
21
+ findAll(): Promise<ProductEntity[]>;
22
+ search(params: SearchParams): Promise<SearchResult>;
23
+ create(product: ProductEntity): Promise<ProductEntity>;
24
+ update(product: ProductEntity): Promise<ProductEntity>;
25
+ softDelete(id: string): Promise<undefined>;
26
+ updateVariantStock(productId: string, variantId: string, newStock: number): Promise<undefined>;
27
+ slugExists(slug: string): Promise<boolean>;
28
+ }
@@ -0,0 +1,121 @@
1
+ import { AppError } from '../shared/AppError';
2
+
3
+ // ── Types ─────────────────────────────────────────────────────────────────────
4
+ export type OrderStatus = 'PENDING' | 'PAID' | 'FAILED' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED';
5
+
6
+ export interface OrderItemProps {
7
+ id: string;
8
+ variantId: string;
9
+ productId: string;
10
+ name: string;
11
+ sku: string;
12
+ price: number;
13
+ qty: number;
14
+ image: string | null;
15
+ }
16
+
17
+ export interface OrderProps {
18
+ id: string;
19
+ userId: string;
20
+ status: OrderStatus;
21
+ items: OrderItemProps[];
22
+ subtotal: number;
23
+ discount: number;
24
+ shippingCost: number;
25
+ tax: number;
26
+ total: number;
27
+ couponCode: string | null;
28
+ paymentIntentId: string | null;
29
+ trackingCode: string | null;
30
+ shippingAddress: Record<string, string> | null;
31
+ createdAt: Date;
32
+ updatedAt: Date;
33
+ }
34
+
35
+ export type CreateOrderInput = Pick<
36
+ OrderProps,
37
+ 'userId' | 'items' | 'subtotal' | 'discount' | 'shippingCost' | 'tax' | 'total' | 'couponCode' | 'paymentIntentId' | 'shippingAddress'
38
+ >;
39
+
40
+ // ── Valid status transitions ───────────────────────────────────────────────────
41
+ const ALLOWED_TRANSITIONS: Partial<Record<OrderStatus, OrderStatus[]>> = {
42
+ PENDING: ['PAID', 'FAILED', 'CANCELLED'],
43
+ PAID: ['SHIPPED', 'CANCELLED'],
44
+ SHIPPED: ['DELIVERED'],
45
+ FAILED: [],
46
+ DELIVERED: [],
47
+ CANCELLED: [],
48
+ };
49
+
50
+ // ── OrderEntity ───────────────────────────────────────────────────────────────
51
+ export class OrderEntity {
52
+ private constructor(private readonly props: OrderProps) {}
53
+
54
+ // ── Factory ───────────────────────────────────────────────────────────────
55
+ static create(input: CreateOrderInput): OrderEntity {
56
+ return new OrderEntity({
57
+ id: crypto.randomUUID(),
58
+ status: 'PENDING',
59
+ trackingCode: null,
60
+ ...input,
61
+ createdAt: new Date(),
62
+ updatedAt: new Date(),
63
+ });
64
+ }
65
+
66
+ static reconstitute(props: OrderProps): OrderEntity {
67
+ return new OrderEntity({ ...props });
68
+ }
69
+
70
+ // ── Getters ───────────────────────────────────────────────────────────────
71
+ get id(): string { return this.props.id; }
72
+ get userId(): string { return this.props.userId; }
73
+ get status(): OrderStatus { return this.props.status; }
74
+ get items(): ReadonlyArray<OrderItemProps> { return this.props.items; }
75
+ get subtotal(): number { return this.props.subtotal; }
76
+ get discount(): number { return this.props.discount; }
77
+ get shippingCost(): number { return this.props.shippingCost; }
78
+ get tax(): number { return this.props.tax; }
79
+ get total(): number { return this.props.total; }
80
+ get couponCode(): string | null { return this.props.couponCode; }
81
+ get paymentIntentId(): string | null { return this.props.paymentIntentId; }
82
+ get trackingCode(): string | null { return this.props.trackingCode; }
83
+ get shippingAddress(): Record<string, string> | null { return this.props.shippingAddress; }
84
+ get createdAt(): Date { return this.props.createdAt; }
85
+ get updatedAt(): Date { return this.props.updatedAt; }
86
+
87
+ // ── State transitions (immutable) ─────────────────────────────────────────
88
+ private transition(to: OrderStatus): OrderEntity {
89
+ const allowed = ALLOWED_TRANSITIONS[this.props.status] ?? [];
90
+ if (!allowed.includes(to)) {
91
+ throw new AppError(
92
+ `Transição inválida: ${this.props.status} → ${to}`,
93
+ 400,
94
+ );
95
+ }
96
+ return new OrderEntity({ ...this.props, status: to, updatedAt: new Date() });
97
+ }
98
+
99
+ pay(): OrderEntity { return this.transition('PAID'); }
100
+ fail(): OrderEntity { return this.transition('FAILED'); }
101
+ ship(trackingCode?: string): OrderEntity {
102
+ const next = this.transition('SHIPPED');
103
+ if (trackingCode) return new OrderEntity({ ...next.props, trackingCode, updatedAt: new Date() });
104
+ return next;
105
+ }
106
+ deliver(): OrderEntity { return this.transition('DELIVERED'); }
107
+ cancel(): OrderEntity { return this.transition('CANCELLED'); }
108
+
109
+ withPaymentIntentId(id: string): OrderEntity {
110
+ return new OrderEntity({ ...this.props, paymentIntentId: id, updatedAt: new Date() });
111
+ }
112
+
113
+ withTrackingCode(code: string): OrderEntity {
114
+ return new OrderEntity({ ...this.props, trackingCode: code, updatedAt: new Date() });
115
+ }
116
+
117
+ // ── Serialization ─────────────────────────────────────────────────────────
118
+ toRecord(): OrderProps {
119
+ return { ...this.props, items: [...this.props.items] };
120
+ }
121
+ }
@@ -0,0 +1,11 @@
1
+ import { OrderEntity } from './order.entity';
2
+ import type { OrderStatus } from './order.entity';
3
+
4
+ export interface IOrderRepository {
5
+ findById(id: string): Promise<OrderEntity | null>;
6
+ findByPaymentIntentId(paymentIntentId: string): Promise<OrderEntity | null>;
7
+ findByUserId(userId: string, opts?: { cursor?: string; limit?: number }): Promise<{ items: OrderEntity[]; nextCursor: string | null }>;
8
+ findAll(opts?: { status?: OrderStatus; cursor?: string; limit?: number }): Promise<{ items: OrderEntity[]; nextCursor: string | null }>;
9
+ create(order: OrderEntity): Promise<OrderEntity>;
10
+ update(order: OrderEntity): Promise<OrderEntity>;
11
+ }
@@ -0,0 +1,12 @@
1
+ export class AppError extends Error {
2
+ public readonly statusCode: number;
3
+ public readonly isOperational: boolean;
4
+
5
+ constructor(message: string, statusCode: number = 500, isOperational: boolean = true) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.isOperational = isOperational;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ Error.captureStackTrace(this, this.constructor);
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ import Redis from 'ioredis';
2
+
3
+ // Prevent multiple Redis clients in development hot-reload
4
+ const globalForRedis = globalThis as unknown as { redis?: Redis };
5
+
6
+ export const redis =
7
+ globalForRedis.redis ??
8
+ new Redis(process.env['REDIS_URL'] ?? 'redis://localhost:6379', {
9
+ lazyConnect: true,
10
+ maxRetriesPerRequest: 3,
11
+ enableReadyCheck: false,
12
+ });
13
+
14
+ if (process.env['NODE_ENV'] !== 'production') {
15
+ globalForRedis.redis = redis;
16
+ }
@@ -0,0 +1,13 @@
1
+ import { DashboardService } from '../../../application/admin/dashboard.service';
2
+ import { AdminUserService } from '../../../application/admin/admin-user.service';
3
+ import { AdminController } from '../../../presentation/modules/admin/admin.controller';
4
+ import { orderRepository } from './checkout.registry';
5
+ import { userRepository } from './auth.registry';
6
+ import { couponRepository } from './cart.registry';
7
+ import { CouponAdminService } from '../../../application/cart/coupon.service';
8
+
9
+ const dashboardService = new DashboardService(orderRepository);
10
+ const adminUserService = new AdminUserService(userRepository);
11
+ const couponAdminService = new CouponAdminService(couponRepository);
12
+
13
+ export const adminController = new AdminController(dashboardService, adminUserService, couponAdminService);
@@ -0,0 +1,34 @@
1
+ import { InMemoryUserRepository } from '../../persistence/in-memory/user.memory.repository';
2
+ import { PrismaUserRepository } from '../../persistence/prisma/auth/user.prisma.repository';
3
+ import { TokenService } from '../../services/token/token.service';
4
+ import { AuthService } from '../../../application/auth/auth.service';
5
+ import { prisma } from '../../persistence/prisma-client';
6
+ import { InMemoryTokenBlacklist } from '../../services/token/token.blacklist';
7
+ import { RedisTokenBlacklist } from '../../services/token/redis.token.blacklist';
8
+ import { redis } from '../../cache/redis';
9
+ import { emailService } from '../../services/email/email.registry';
10
+
11
+ /**
12
+ * Module-level singletons.
13
+ * Uses PrismaUserRepository when DATABASE_URL is set (production / integration
14
+ * tests). Falls back to InMemoryUserRepository when NODE_ENV is 'test' without
15
+ * a DATABASE_URL, or when NODE_ENV is 'test-inmemory'.
16
+ */
17
+ const useDb =
18
+ Boolean(process.env['DATABASE_URL']) &&
19
+ process.env['NODE_ENV'] !== 'test-inmemory';
20
+
21
+ export const userRepository = useDb
22
+ ? new PrismaUserRepository(prisma)
23
+ : new InMemoryUserRepository();
24
+
25
+ const useRedis =
26
+ Boolean(process.env['REDIS_URL']) &&
27
+ process.env['NODE_ENV'] !== 'test-inmemory';
28
+
29
+ const blacklist = useRedis
30
+ ? new RedisTokenBlacklist(redis)
31
+ : new InMemoryTokenBlacklist();
32
+
33
+ export const tokenService = new TokenService(blacklist, useDb ? prisma : undefined);
34
+ export const authService = new AuthService(userRepository, tokenService, emailService);
@@ -0,0 +1,49 @@
1
+ import { InMemoryCartRepository } from '../../persistence/in-memory/cart.memory.repository';
2
+ import { InMemoryCouponRepository } from '../../persistence/in-memory/coupon.memory.repository';
3
+ import { MockShippingService } from '../../services/shipping/mock.shipping.service';
4
+ import { CouponEntity } from '../../../domain/cart/coupon.entity';
5
+ import { CartService } from '../../../application/cart/cart.service';
6
+ import { productRepository } from './catalog.registry';
7
+
8
+ // ── Singletons ────────────────────────────────────────────────────────────────
9
+ export const cartRepository = new InMemoryCartRepository();
10
+ export const couponRepository = new InMemoryCouponRepository();
11
+ export const shippingService = new MockShippingService();
12
+ export const cartService = new CartService(
13
+ cartRepository,
14
+ couponRepository,
15
+ shippingService,
16
+ productRepository,
17
+ );
18
+
19
+ // ── Seed test coupons ─────────────────────────────────────────────────────────
20
+ couponRepository.seed(
21
+ CouponEntity.reconstitute({
22
+ id: 'seed-coup-1',
23
+ code: 'PROMO10',
24
+ discountType: 'percent',
25
+ discountValue: 10,
26
+ minOrderValue: 0,
27
+ usageLimit: null,
28
+ usageCount: 0,
29
+ expiresAt: null,
30
+ createdAt: new Date(),
31
+ }),
32
+ );
33
+
34
+ couponRepository.seed(
35
+ CouponEntity.reconstitute({
36
+ id: 'seed-coup-2',
37
+ code: 'EXPIRED',
38
+ discountType: 'percent',
39
+ discountValue: 5,
40
+ minOrderValue: 0,
41
+ usageLimit: null,
42
+ usageCount: 0,
43
+ expiresAt: new Date('2020-01-01'),
44
+ createdAt: new Date(),
45
+ }),
46
+ );
47
+
48
+ // ── Named export for test setup ───────────────────────────────────────────────
49
+ export const cartRegistry = { cartRepository, couponRepository, shippingService, cartService };
@@ -0,0 +1,24 @@
1
+ import { InMemoryCategoryRepository } from '../../persistence/in-memory/category.memory.repository';
2
+ import { InMemoryProductRepository } from '../../persistence/in-memory/product.memory.repository';
3
+ import { PrismaCategoryRepository } from '../../persistence/prisma/catalog/category.prisma.repository';
4
+ import { PrismaProductRepository } from '../../persistence/prisma/catalog/product.prisma.repository';
5
+ import { CatalogService } from '../../../application/catalog/catalog.service';
6
+ import { prisma } from '../../persistence/prisma-client';
7
+ import { storageService } from '../../services/storage/storage.registry';
8
+
9
+ const useDb =
10
+ Boolean(process.env['DATABASE_URL']) &&
11
+ process.env['NODE_ENV'] !== 'test-inmemory';
12
+
13
+ export const categoryRepository = useDb
14
+ ? new PrismaCategoryRepository(prisma)
15
+ : new InMemoryCategoryRepository();
16
+
17
+ export const productRepository = useDb
18
+ ? new PrismaProductRepository(prisma)
19
+ : new InMemoryProductRepository();
20
+
21
+ export const catalogService = new CatalogService(categoryRepository, productRepository, storageService);
22
+
23
+ // Named export for test setup access
24
+ export const catalogRegistry = { categoryRepository, productRepository, catalogService };
@@ -0,0 +1,47 @@
1
+ import { InMemoryOrderRepository } from '../../persistence/in-memory/order.memory.repository';
2
+ import { PrismaOrderRepository } from '../../persistence/prisma/checkout/order.prisma.repository';
3
+ import { StripeAdapter } from '../../services/payment/stripe.adapter';
4
+ import { StripeWebhookHandler } from '../../services/payment/stripe-webhook.handler';
5
+ import { CheckoutService } from '../../../application/checkout/checkout.service';
6
+ import { cartRepository, couponRepository } from './cart.registry';
7
+ import { productRepository } from './catalog.registry';
8
+ import { prisma } from '../../persistence/prisma-client';
9
+ import { emailService } from '../../services/email/email.registry';
10
+ import { userRepository } from './auth.registry';
11
+
12
+ const useDb =
13
+ Boolean(process.env['DATABASE_URL']) &&
14
+ process.env['NODE_ENV'] !== 'test-inmemory';
15
+
16
+ // ── Singletons ────────────────────────────────────────────────────────────────
17
+ export const orderRepository = useDb
18
+ ? new PrismaOrderRepository(prisma)
19
+ : new InMemoryOrderRepository();
20
+
21
+ export const paymentAdapter = new StripeAdapter(
22
+ process.env['STRIPE_SECRET_KEY'] ?? 'sk_test_dummy',
23
+ );
24
+
25
+ export const webhookHandler = new StripeWebhookHandler(
26
+ process.env['STRIPE_SECRET_KEY'] ?? 'sk_test_dummy',
27
+ process.env['STRIPE_WEBHOOK_SECRET'] ?? 'whsec_dummy',
28
+ orderRepository,
29
+ couponRepository,
30
+ emailService,
31
+ userRepository,
32
+ );
33
+
34
+ export const checkoutService = new CheckoutService(
35
+ orderRepository,
36
+ paymentAdapter,
37
+ cartRepository,
38
+ productRepository,
39
+ );
40
+
41
+ // ── Named export for test overrides ──────────────────────────────────────────
42
+ export const checkoutRegistry = {
43
+ orderRepository,
44
+ paymentAdapter,
45
+ webhookHandler,
46
+ checkoutService,
47
+ };
@@ -0,0 +1,6 @@
1
+ import { OrderService } from '../../../application/orders/order.service';
2
+ import { orderRepository } from './checkout.registry';
3
+ import { emailService } from '../../services/email/email.registry';
4
+ import { userRepository } from './auth.registry';
5
+
6
+ export const orderService = new OrderService(orderRepository, emailService, userRepository);
@@ -0,0 +1,4 @@
1
+ import { ProfileService } from '../../../application/profile/profile.service';
2
+ import { userRepository } from './auth.registry';
3
+
4
+ export const profileService = new ProfileService(userRepository);
@@ -0,0 +1,33 @@
1
+ import { CartEntity } from '../../../domain/cart/cart.entity';
2
+ import type { ICartRepository } from '../../../domain/cart/cart.repository';
3
+
4
+ // Simulates Redis TTL-based storage using two Maps (by userId and by sessionId).
5
+ export class InMemoryCartRepository implements ICartRepository {
6
+ private byUser = new Map<string, CartEntity>();
7
+ private bySession = new Map<string, CartEntity>();
8
+
9
+ async findByUserId(userId: string): Promise<CartEntity | null> {
10
+ return this.byUser.get(userId) ?? null;
11
+ }
12
+
13
+ async findBySessionId(sessionId: string): Promise<CartEntity | null> {
14
+ return this.bySession.get(sessionId) ?? null;
15
+ }
16
+
17
+ async save(cart: CartEntity): Promise<CartEntity> {
18
+ if (cart.userId) this.byUser.set(cart.userId, cart);
19
+ if (cart.sessionId) this.bySession.set(cart.sessionId, cart);
20
+ return cart;
21
+ }
22
+
23
+ async delete(key: { userId?: string; sessionId?: string }): Promise<void> {
24
+ if (key.userId) this.byUser.delete(key.userId);
25
+ if (key.sessionId) this.bySession.delete(key.sessionId);
26
+ }
27
+
28
+ /** Test helper — wipe all entries */
29
+ clear(): void {
30
+ this.byUser.clear();
31
+ this.bySession.clear();
32
+ }
33
+ }
@@ -0,0 +1,41 @@
1
+ import { CategoryEntity, CategoryProps } from '../../../domain/catalog/category.entity';
2
+ import type { ICategoryRepository } from '../../../domain/catalog/category.repository';
3
+
4
+ export class InMemoryCategoryRepository implements ICategoryRepository {
5
+ private store = new Map<string, CategoryProps>();
6
+
7
+ async findById(id: string): Promise<CategoryEntity | null> {
8
+ const props = this.store.get(id);
9
+ return props ? CategoryEntity.reconstitute(props) : null;
10
+ }
11
+
12
+ async findBySlug(slug: string): Promise<CategoryEntity | null> {
13
+ for (const props of this.store.values()) {
14
+ if (props.slug === slug) return CategoryEntity.reconstitute(props);
15
+ }
16
+ return null;
17
+ }
18
+
19
+ async findAll(): Promise<CategoryEntity[]> {
20
+ return Array.from(this.store.values()).map(CategoryEntity.reconstitute);
21
+ }
22
+
23
+ async create(category: CategoryEntity): Promise<CategoryEntity> {
24
+ this.store.set(category.id, category.toRecord());
25
+ return category;
26
+ }
27
+
28
+ async update(category: CategoryEntity): Promise<CategoryEntity> {
29
+ this.store.set(category.id, category.toRecord());
30
+ return category;
31
+ }
32
+
33
+ async delete(id: string): Promise<undefined> {
34
+ this.store.delete(id);
35
+ return undefined;
36
+ }
37
+
38
+ clear(): void {
39
+ this.store.clear();
40
+ }
41
+ }