kybernus 3.0.1 → 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 (392) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/ecommerce.d.ts +3 -0
  3. package/dist/cli/commands/ecommerce.d.ts.map +1 -0
  4. package/dist/cli/commands/ecommerce.js +164 -0
  5. package/dist/cli/commands/ecommerce.js.map +1 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/templates/ecommerce/.env.example +10 -0
  10. package/templates/ecommerce/.github/workflows/ci.yml +102 -0
  11. package/templates/ecommerce/.github/workflows/deploy.yml +31 -0
  12. package/templates/ecommerce/.prettierrc +9 -0
  13. package/templates/ecommerce/Dockerfile +54 -0
  14. package/templates/ecommerce/README.md +295 -0
  15. package/templates/ecommerce/apps/api/.env.example +59 -0
  16. package/templates/ecommerce/apps/api/jest.config.ts +50 -0
  17. package/templates/ecommerce/apps/api/jest.integration.config.ts +45 -0
  18. package/templates/ecommerce/apps/api/package.json +59 -0
  19. package/templates/ecommerce/apps/api/prisma/migrations/20260306000137_init/migration.sql +184 -0
  20. package/templates/ecommerce/apps/api/prisma/migrations/migration_lock.toml +3 -0
  21. package/templates/ecommerce/apps/api/prisma/schema.prisma +181 -0
  22. package/templates/ecommerce/apps/api/prisma/seed.ts +159 -0
  23. package/templates/ecommerce/apps/api/src/__tests__/app.test.ts +39 -0
  24. package/templates/ecommerce/apps/api/src/__tests__/globalSetup.ts +34 -0
  25. package/templates/ecommerce/apps/api/src/__tests__/globalTeardown.ts +16 -0
  26. package/templates/ecommerce/apps/api/src/__tests__/setup.db.ts +18 -0
  27. package/templates/ecommerce/apps/api/src/__tests__/setup.env.ts +14 -0
  28. package/templates/ecommerce/apps/api/src/app.ts +133 -0
  29. package/templates/ecommerce/apps/api/src/application/admin/admin-user.service.ts +24 -0
  30. package/templates/ecommerce/apps/api/src/application/admin/dashboard.service.ts +102 -0
  31. package/templates/ecommerce/apps/api/src/application/auth/auth.service.ts +185 -0
  32. package/templates/ecommerce/apps/api/src/application/cart/cart.service.ts +151 -0
  33. package/templates/ecommerce/apps/api/src/application/cart/coupon.service.ts +51 -0
  34. package/templates/ecommerce/apps/api/src/application/catalog/catalog.service.ts +168 -0
  35. package/templates/ecommerce/apps/api/src/application/checkout/checkout.service.ts +114 -0
  36. package/templates/ecommerce/apps/api/src/application/orders/order.service.ts +93 -0
  37. package/templates/ecommerce/apps/api/src/application/ports/email.port.ts +3 -0
  38. package/templates/ecommerce/apps/api/src/application/ports/payment.port.ts +24 -0
  39. package/templates/ecommerce/apps/api/src/application/ports/shipping.port.ts +9 -0
  40. package/templates/ecommerce/apps/api/src/application/ports/storage.port.ts +3 -0
  41. package/templates/ecommerce/apps/api/src/application/ports/token-blacklist.port.ts +4 -0
  42. package/templates/ecommerce/apps/api/src/application/ports/token.port.ts +18 -0
  43. package/templates/ecommerce/apps/api/src/application/profile/profile.service.ts +76 -0
  44. package/templates/ecommerce/apps/api/src/domain/auth/user.entity.ts +109 -0
  45. package/templates/ecommerce/apps/api/src/domain/auth/user.repository.ts +11 -0
  46. package/templates/ecommerce/apps/api/src/domain/cart/cart.entity.ts +136 -0
  47. package/templates/ecommerce/apps/api/src/domain/cart/cart.repository.ts +8 -0
  48. package/templates/ecommerce/apps/api/src/domain/cart/coupon.entity.ts +58 -0
  49. package/templates/ecommerce/apps/api/src/domain/cart/coupon.repository.ts +10 -0
  50. package/templates/ecommerce/apps/api/src/domain/catalog/category.entity.ts +51 -0
  51. package/templates/ecommerce/apps/api/src/domain/catalog/category.repository.ts +10 -0
  52. package/templates/ecommerce/apps/api/src/domain/catalog/product.entity.ts +130 -0
  53. package/templates/ecommerce/apps/api/src/domain/catalog/product.repository.ts +28 -0
  54. package/templates/ecommerce/apps/api/src/domain/checkout/order.entity.ts +121 -0
  55. package/templates/ecommerce/apps/api/src/domain/checkout/order.repository.ts +11 -0
  56. package/templates/ecommerce/apps/api/src/domain/shared/AppError.ts +12 -0
  57. package/templates/ecommerce/apps/api/src/infrastructure/cache/redis.ts +16 -0
  58. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/admin.registry.ts +13 -0
  59. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/auth.registry.ts +34 -0
  60. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/cart.registry.ts +49 -0
  61. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/catalog.registry.ts +24 -0
  62. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/checkout.registry.ts +47 -0
  63. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/orders.registry.ts +6 -0
  64. package/templates/ecommerce/apps/api/src/infrastructure/config/registry/profile.registry.ts +4 -0
  65. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/cart.memory.repository.ts +33 -0
  66. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/category.memory.repository.ts +41 -0
  67. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/coupon.memory.repository.ts +55 -0
  68. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/order.memory.repository.ts +75 -0
  69. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/product.memory.repository.ts +100 -0
  70. package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/user.memory.repository.ts +54 -0
  71. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/auth/user.prisma.repository.ts +83 -0
  72. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/category.prisma.repository.ts +69 -0
  73. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/product.prisma.repository.ts +185 -0
  74. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/checkout/order.prisma.repository.ts +149 -0
  75. package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma-client.ts +17 -0
  76. package/templates/ecommerce/apps/api/src/infrastructure/services/email/email.registry.ts +18 -0
  77. package/templates/ecommerce/apps/api/src/infrastructure/services/email/ethereal.email.service.ts +38 -0
  78. package/templates/ecommerce/apps/api/src/infrastructure/services/email/noop.email.service.ts +12 -0
  79. package/templates/ecommerce/apps/api/src/infrastructure/services/email/smtp.email.service.ts +36 -0
  80. package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe-webhook.handler.ts +83 -0
  81. package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe.adapter.ts +39 -0
  82. package/templates/ecommerce/apps/api/src/infrastructure/services/shipping/mock.shipping.service.ts +17 -0
  83. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/in-memory.storage.service.ts +11 -0
  84. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/local-disk.storage.service.ts +27 -0
  85. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/s3.storage.service.ts +52 -0
  86. package/templates/ecommerce/apps/api/src/infrastructure/services/storage/storage.registry.ts +19 -0
  87. package/templates/ecommerce/apps/api/src/infrastructure/services/token/redis.token.blacklist.ts +23 -0
  88. package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.blacklist.ts +30 -0
  89. package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.service.ts +136 -0
  90. package/templates/ecommerce/apps/api/src/modules/admin/__tests__/admin.routes.integration.test.ts +250 -0
  91. package/templates/ecommerce/apps/api/src/modules/admin/admin.controller.ts +116 -0
  92. package/templates/ecommerce/apps/api/src/modules/admin/admin.registry.ts +1 -0
  93. package/templates/ecommerce/apps/api/src/modules/admin/admin.routes.ts +21 -0
  94. package/templates/ecommerce/apps/api/src/modules/admin/admin.service.ts +1 -0
  95. package/templates/ecommerce/apps/api/src/modules/admin/admin.user.service.ts +1 -0
  96. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.logout.redis.test.ts +104 -0
  97. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.routes.integration.test.ts +211 -0
  98. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.service.unit.test.ts +260 -0
  99. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/email.service.unit.test.ts +94 -0
  100. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/token.blacklist.redis.test.ts +65 -0
  101. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.entity.unit.test.ts +79 -0
  102. package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.prisma.repository.test.ts +138 -0
  103. package/templates/ecommerce/apps/api/src/modules/auth/auth.controller.ts +148 -0
  104. package/templates/ecommerce/apps/api/src/modules/auth/auth.registry.ts +1 -0
  105. package/templates/ecommerce/apps/api/src/modules/auth/auth.routes.ts +17 -0
  106. package/templates/ecommerce/apps/api/src/modules/auth/auth.service.ts +1 -0
  107. package/templates/ecommerce/apps/api/src/modules/auth/redis.token.blacklist.ts +1 -0
  108. package/templates/ecommerce/apps/api/src/modules/auth/token.blacklist.ts +2 -0
  109. package/templates/ecommerce/apps/api/src/modules/auth/token.service.ts +2 -0
  110. package/templates/ecommerce/apps/api/src/modules/auth/user.entity.ts +1 -0
  111. package/templates/ecommerce/apps/api/src/modules/auth/user.prisma.repository.ts +1 -0
  112. package/templates/ecommerce/apps/api/src/modules/auth/user.repository.ts +2 -0
  113. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.entity.unit.test.ts +144 -0
  114. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.routes.integration.test.ts +242 -0
  115. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.service.unit.test.ts +151 -0
  116. package/templates/ecommerce/apps/api/src/modules/cart/__tests__/coupon.admin.integration.test.ts +136 -0
  117. package/templates/ecommerce/apps/api/src/modules/cart/cart.controller.ts +94 -0
  118. package/templates/ecommerce/apps/api/src/modules/cart/cart.entity.ts +1 -0
  119. package/templates/ecommerce/apps/api/src/modules/cart/cart.registry.ts +1 -0
  120. package/templates/ecommerce/apps/api/src/modules/cart/cart.repository.ts +2 -0
  121. package/templates/ecommerce/apps/api/src/modules/cart/cart.routes.ts +17 -0
  122. package/templates/ecommerce/apps/api/src/modules/cart/cart.service.ts +1 -0
  123. package/templates/ecommerce/apps/api/src/modules/cart/coupon.entity.ts +1 -0
  124. package/templates/ecommerce/apps/api/src/modules/cart/coupon.repository.ts +2 -0
  125. package/templates/ecommerce/apps/api/src/modules/cart/coupon.service.ts +1 -0
  126. package/templates/ecommerce/apps/api/src/modules/cart/shipping.service.ts +2 -0
  127. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.routes.integration.test.ts +275 -0
  128. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.service.unit.test.ts +223 -0
  129. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.image.integration.test.ts +130 -0
  130. package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.prisma.repository.test.ts +174 -0
  131. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.controller.ts +176 -0
  132. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.registry.ts +1 -0
  133. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.routes.ts +38 -0
  134. package/templates/ecommerce/apps/api/src/modules/catalog/catalog.service.ts +1 -0
  135. package/templates/ecommerce/apps/api/src/modules/catalog/category.entity.ts +1 -0
  136. package/templates/ecommerce/apps/api/src/modules/catalog/category.prisma.repository.ts +1 -0
  137. package/templates/ecommerce/apps/api/src/modules/catalog/category.repository.ts +2 -0
  138. package/templates/ecommerce/apps/api/src/modules/catalog/product.entity.ts +1 -0
  139. package/templates/ecommerce/apps/api/src/modules/catalog/product.prisma.repository.ts +1 -0
  140. package/templates/ecommerce/apps/api/src/modules/catalog/product.repository.ts +2 -0
  141. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.routes.integration.test.ts +163 -0
  142. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.service.unit.test.ts +191 -0
  143. package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/order.prisma.repository.test.ts +150 -0
  144. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.controller.ts +59 -0
  145. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.registry.ts +1 -0
  146. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.routes.ts +18 -0
  147. package/templates/ecommerce/apps/api/src/modules/checkout/checkout.service.ts +1 -0
  148. package/templates/ecommerce/apps/api/src/modules/checkout/order.entity.ts +1 -0
  149. package/templates/ecommerce/apps/api/src/modules/checkout/order.prisma.repository.ts +1 -0
  150. package/templates/ecommerce/apps/api/src/modules/checkout/order.repository.ts +2 -0
  151. package/templates/ecommerce/apps/api/src/modules/checkout/tax.service.ts +9 -0
  152. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.entity.unit.test.ts +68 -0
  153. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.routes.integration.test.ts +254 -0
  154. package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.service.email.unit.test.ts +142 -0
  155. package/templates/ecommerce/apps/api/src/modules/orders/order.controller.ts +96 -0
  156. package/templates/ecommerce/apps/api/src/modules/orders/order.registry.ts +1 -0
  157. package/templates/ecommerce/apps/api/src/modules/orders/order.routes.ts +17 -0
  158. package/templates/ecommerce/apps/api/src/modules/orders/order.service.ts +1 -0
  159. package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe-webhook.unit.test.ts +330 -0
  160. package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe.adapter.unit.test.ts +84 -0
  161. package/templates/ecommerce/apps/api/src/modules/payment/adapters/stripe.adapter.ts +1 -0
  162. package/templates/ecommerce/apps/api/src/modules/payment/payment.port.ts +1 -0
  163. package/templates/ecommerce/apps/api/src/modules/payment/stripe-webhook.handler.ts +1 -0
  164. package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.routes.integration.test.ts +180 -0
  165. package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.service.unit.test.ts +187 -0
  166. package/templates/ecommerce/apps/api/src/modules/profile/profile.controller.ts +92 -0
  167. package/templates/ecommerce/apps/api/src/modules/profile/profile.registry.ts +1 -0
  168. package/templates/ecommerce/apps/api/src/modules/profile/profile.routes.ts +14 -0
  169. package/templates/ecommerce/apps/api/src/modules/profile/profile.service.ts +1 -0
  170. package/templates/ecommerce/apps/api/src/presentation/middlewares/authenticate.ts +37 -0
  171. package/templates/ecommerce/apps/api/src/presentation/middlewares/authorize.ts +23 -0
  172. package/templates/ecommerce/apps/api/src/presentation/middlewares/errorHandler.ts +48 -0
  173. package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.controller.ts +116 -0
  174. package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.routes.ts +21 -0
  175. package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.controller.ts +147 -0
  176. package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.routes.ts +17 -0
  177. package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.controller.ts +94 -0
  178. package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.routes.ts +17 -0
  179. package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.controller.ts +176 -0
  180. package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.routes.ts +38 -0
  181. package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.controller.ts +59 -0
  182. package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.routes.ts +18 -0
  183. package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.controller.ts +96 -0
  184. package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.routes.ts +17 -0
  185. package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.controller.ts +92 -0
  186. package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.routes.ts +14 -0
  187. package/templates/ecommerce/apps/api/src/presentation/validators/uuidParam.ts +20 -0
  188. package/templates/ecommerce/apps/api/src/server.ts +47 -0
  189. package/templates/ecommerce/apps/api/src/shared/__tests__/uuid.validation.test.ts +111 -0
  190. package/templates/ecommerce/apps/api/src/shared/errors/AppError.ts +1 -0
  191. package/templates/ecommerce/apps/api/src/shared/infra/email/EtherealEmailService.ts +1 -0
  192. package/templates/ecommerce/apps/api/src/shared/infra/email/IEmailService.ts +1 -0
  193. package/templates/ecommerce/apps/api/src/shared/infra/email/NoopEmailService.ts +1 -0
  194. package/templates/ecommerce/apps/api/src/shared/infra/email/SmtpEmailService.ts +1 -0
  195. package/templates/ecommerce/apps/api/src/shared/infra/email/__tests__/ethereal.email.integration.test.ts +32 -0
  196. package/templates/ecommerce/apps/api/src/shared/infra/email/email.registry.ts +1 -0
  197. package/templates/ecommerce/apps/api/src/shared/infra/prisma.ts +1 -0
  198. package/templates/ecommerce/apps/api/src/shared/infra/redis.ts +1 -0
  199. package/templates/ecommerce/apps/api/src/shared/infra/storage/IStorageService.ts +1 -0
  200. package/templates/ecommerce/apps/api/src/shared/infra/storage/InMemoryStorageService.ts +1 -0
  201. package/templates/ecommerce/apps/api/src/shared/infra/storage/LocalDiskStorageService.ts +1 -0
  202. package/templates/ecommerce/apps/api/src/shared/infra/storage/S3StorageService.ts +1 -0
  203. package/templates/ecommerce/apps/api/src/shared/infra/storage/__tests__/s3.storage.unit.test.ts +73 -0
  204. package/templates/ecommerce/apps/api/src/shared/infra/storage/storage.registry.ts +1 -0
  205. package/templates/ecommerce/apps/api/src/shared/middlewares/authenticate.ts +1 -0
  206. package/templates/ecommerce/apps/api/src/shared/middlewares/authorize.ts +1 -0
  207. package/templates/ecommerce/apps/api/src/shared/middlewares/errorHandler.ts +1 -0
  208. package/templates/ecommerce/apps/api/src/shared/validators/uuidParam.ts +1 -0
  209. package/templates/ecommerce/apps/api/tsconfig.json +15 -0
  210. package/templates/ecommerce/apps/web/.env.example +8 -0
  211. package/templates/ecommerce/apps/web/index.html +19 -0
  212. package/templates/ecommerce/apps/web/jest.config.ts +45 -0
  213. package/templates/ecommerce/apps/web/package.json +38 -0
  214. package/templates/ecommerce/apps/web/src/App.tsx +133 -0
  215. package/templates/ecommerce/apps/web/src/__mocks__/fileMock.ts +1 -0
  216. package/templates/ecommerce/apps/web/src/__mocks__/styleMock.ts +1 -0
  217. package/templates/ecommerce/apps/web/src/index.css +159 -0
  218. package/templates/ecommerce/apps/web/src/main.tsx +13 -0
  219. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/CouponsAdminPage.test.tsx +134 -0
  220. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/DashboardPage.test.tsx +65 -0
  221. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/OrdersAdminPage.test.tsx +79 -0
  222. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/ProductsAdminPage.test.tsx +84 -0
  223. package/templates/ecommerce/apps/web/src/modules/admin/__tests__/UsersAdminPage.test.tsx +85 -0
  224. package/templates/ecommerce/apps/web/src/modules/admin/pages/CouponsAdminPage.tsx +179 -0
  225. package/templates/ecommerce/apps/web/src/modules/admin/pages/DashboardPage.tsx +58 -0
  226. package/templates/ecommerce/apps/web/src/modules/admin/pages/OrdersAdminPage.tsx +178 -0
  227. package/templates/ecommerce/apps/web/src/modules/admin/pages/ProductsAdminPage.tsx +444 -0
  228. package/templates/ecommerce/apps/web/src/modules/admin/pages/UsersAdminPage.tsx +87 -0
  229. package/templates/ecommerce/apps/web/src/modules/auth/LoginForm.tsx +91 -0
  230. package/templates/ecommerce/apps/web/src/modules/auth/RegisterForm.tsx +109 -0
  231. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ForgotPasswordPage.test.tsx +42 -0
  232. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/LoginForm.test.tsx +76 -0
  233. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/RegisterForm.test.tsx +62 -0
  234. package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ResetPasswordPage.test.tsx +66 -0
  235. package/templates/ecommerce/apps/web/src/modules/auth/pages/ForgotPasswordPage.tsx +100 -0
  236. package/templates/ecommerce/apps/web/src/modules/auth/pages/LoginPage.tsx +39 -0
  237. package/templates/ecommerce/apps/web/src/modules/auth/pages/RegisterPage.tsx +39 -0
  238. package/templates/ecommerce/apps/web/src/modules/auth/pages/ResetPasswordPage.tsx +110 -0
  239. package/templates/ecommerce/apps/web/src/modules/auth/useAuthStore.ts +141 -0
  240. package/templates/ecommerce/apps/web/src/modules/cart/__tests__/CartPage.test.tsx +111 -0
  241. package/templates/ecommerce/apps/web/src/modules/cart/pages/CartPage.tsx +313 -0
  242. package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductCard.test.tsx +59 -0
  243. package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductFilters.test.tsx +56 -0
  244. package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductCard.tsx +78 -0
  245. package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductFilters.tsx +104 -0
  246. package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductDetailPage.tsx +179 -0
  247. package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductListPage.tsx +100 -0
  248. package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/CheckoutPage.test.tsx +159 -0
  249. package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/StripePaymentForm.test.tsx +79 -0
  250. package/templates/ecommerce/apps/web/src/modules/checkout/components/StripePaymentForm.tsx +55 -0
  251. package/templates/ecommerce/apps/web/src/modules/checkout/hooks/useCheckout.ts +56 -0
  252. package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutPage.tsx +344 -0
  253. package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutSuccessPage.tsx +12 -0
  254. package/templates/ecommerce/apps/web/src/modules/legal/pages/PrivacyPolicyPage.tsx +207 -0
  255. package/templates/ecommerce/apps/web/src/modules/legal/pages/TermsOfServicePage.tsx +175 -0
  256. package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderDetailPage.test.tsx +75 -0
  257. package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderHistoryPage.test.tsx +87 -0
  258. package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderDetailPage.tsx +73 -0
  259. package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderHistoryPage.tsx +97 -0
  260. package/templates/ecommerce/apps/web/src/modules/profile/__tests__/ProfilePage.test.tsx +150 -0
  261. package/templates/ecommerce/apps/web/src/modules/profile/pages/ProfilePage.tsx +275 -0
  262. package/templates/ecommerce/apps/web/src/setupTests.ts +10 -0
  263. package/templates/ecommerce/apps/web/src/shared/components/CookieConsent.tsx +108 -0
  264. package/templates/ecommerce/apps/web/src/shared/components/ErrorBoundary.tsx +112 -0
  265. package/templates/ecommerce/apps/web/src/shared/components/Layout.tsx +143 -0
  266. package/templates/ecommerce/apps/web/src/shared/components/ProtectedRoute.tsx +21 -0
  267. package/templates/ecommerce/apps/web/src/shared/config/siteConfig.ts +57 -0
  268. package/templates/ecommerce/apps/web/src/shared/hooks/usePageTitle.ts +16 -0
  269. package/templates/ecommerce/apps/web/src/shared/lib/apiFetch.ts +16 -0
  270. package/templates/ecommerce/apps/web/src/shared/pages/NotFoundPage.tsx +42 -0
  271. package/templates/ecommerce/apps/web/src/shared/theme/ThemeProvider.tsx +45 -0
  272. package/templates/ecommerce/apps/web/src/shared/theme/__tests__/ThemeProvider.test.tsx +78 -0
  273. package/templates/ecommerce/apps/web/src/shared/theme/createTheme.ts +58 -0
  274. package/templates/ecommerce/apps/web/src/shared/theme/tokens.ts +81 -0
  275. package/templates/ecommerce/apps/web/src/vite-env.d.ts +1 -0
  276. package/templates/ecommerce/apps/web/tsconfig.jest.json +12 -0
  277. package/templates/ecommerce/apps/web/tsconfig.json +25 -0
  278. package/templates/ecommerce/apps/web/tsconfig.node.json +11 -0
  279. package/templates/ecommerce/apps/web/vite.config.ts +30 -0
  280. package/templates/ecommerce/docker-compose.yml +85 -0
  281. package/templates/ecommerce/package-lock.json +11255 -0
  282. package/templates/ecommerce/package.json +27 -0
  283. package/templates/ecommerce/packages/shared-types/package.json +13 -0
  284. package/templates/ecommerce/packages/shared-types/src/index.ts +3 -0
  285. package/templates/ecommerce/packages/shared-types/src/theme.ts +44 -0
  286. package/templates/ecommerce/packages/shared-types/tsconfig.json +11 -0
  287. package/templates/ecommerce/scripts/customize.sh +201 -0
  288. package/templates/ecommerce/tsconfig.json +14 -0
  289. package/templates/java-spring/clean/.gitignore.hbs +72 -0
  290. package/templates/java-spring/clean/docker-compose.yml.hbs +6 -3
  291. package/templates/java-spring/clean/src/main/java/{{packagePath}}/application/usecase/PaymentUseCase.java.hbs +21 -17
  292. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/entity/UserEntity.java.hbs +52 -0
  293. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/repository/JpaUserRepository.java.hbs +12 -0
  294. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/JwtAuthenticationFilter.java.hbs +64 -0
  295. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/SecurityConfig.java.hbs +36 -0
  296. package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/stripe/StripeGateway.java.hbs +63 -0
  297. package/templates/java-spring/clean/src/main/resources/application.properties.hbs +6 -7
  298. package/templates/java-spring/hexagonal/.gitignore.hbs +72 -0
  299. package/templates/java-spring/hexagonal/docker-compose.yml.hbs +6 -3
  300. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/JwtFilter.java.hbs +71 -0
  301. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/SecurityConfig.java.hbs +35 -0
  302. package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/core/service/PaymentService.java.hbs +3 -3
  303. package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +4 -4
  304. package/templates/java-spring/mvc/.gitignore.hbs +72 -0
  305. package/templates/java-spring/mvc/docker-compose.yml.hbs +6 -3
  306. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/config/SecurityConfig.java.hbs +13 -12
  307. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +9 -8
  308. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +5 -6
  309. package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +3 -3
  310. package/templates/java-spring/mvc/src/main/resources/application.yml.hbs +29 -26
  311. package/templates/nestjs/clean/.gitignore.hbs +42 -0
  312. package/templates/nestjs/clean/Dockerfile.hbs +6 -3
  313. package/templates/nestjs/clean/docker-compose.yml.hbs +1 -11
  314. package/templates/nestjs/clean/src/app.module.ts.hbs +2 -1
  315. package/templates/nestjs/clean/src/application/payment.service.ts.hbs +72 -72
  316. package/templates/nestjs/clean/src/domain/entities/user.entity.ts.hbs +2 -2
  317. package/templates/nestjs/clean/src/domain/repositories/user.repository.ts.hbs +2 -2
  318. package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +18 -18
  319. package/templates/nestjs/clean/src/infrastructure/http/health.controller.ts.hbs +9 -0
  320. package/templates/nestjs/clean/src/main.ts.hbs +1 -4
  321. package/templates/nestjs/clean/src/payment.module.ts.hbs +12 -12
  322. package/templates/nestjs/hexagonal/.gitignore.hbs +42 -0
  323. package/templates/nestjs/hexagonal/Dockerfile.hbs +6 -3
  324. package/templates/nestjs/hexagonal/docker-compose.yml.hbs +1 -11
  325. package/templates/nestjs/hexagonal/src/adapters/inbound/health.controller.ts.hbs +9 -0
  326. package/templates/nestjs/hexagonal/src/app.module.ts.hbs +2 -1
  327. package/templates/nestjs/hexagonal/src/core/domain/user.entity.ts.hbs +6 -6
  328. package/templates/nestjs/hexagonal/src/core/ports/ports.ts.hbs +4 -4
  329. package/templates/nestjs/hexagonal/src/main.ts.hbs +1 -4
  330. package/templates/nestjs/mvc/.gitignore.hbs +42 -0
  331. package/templates/nestjs/mvc/Dockerfile.hbs +6 -3
  332. package/templates/nestjs/mvc/docker-compose.yml.hbs +1 -11
  333. package/templates/nestjs/mvc/src/auth/auth.controller.ts.hbs +11 -1
  334. package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +3 -1
  335. package/templates/nestjs/mvc/src/controllers/health.controller.ts.hbs +6 -6
  336. package/templates/nestjs/mvc/src/main.ts.hbs +1 -4
  337. package/templates/nestjs/mvc/src/models/create-item.dto.ts.hbs +5 -2
  338. package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +1 -0
  339. package/templates/nextjs/mvc/.gitignore.hbs +42 -0
  340. package/templates/nextjs/mvc/Dockerfile.hbs +23 -8
  341. package/templates/nextjs/mvc/docker-compose.yml.hbs +1 -1
  342. package/templates/nodejs-express/clean/.gitignore.hbs +42 -0
  343. package/templates/nodejs-express/clean/Dockerfile.hbs +6 -1
  344. package/templates/nodejs-express/clean/docker-compose.yml.hbs +2 -2
  345. package/templates/nodejs-express/clean/package.json.hbs +69 -69
  346. package/templates/nodejs-express/clean/src/config.ts.hbs +11 -0
  347. package/templates/nodejs-express/clean/src/domain/entities/User.ts.hbs +46 -8
  348. package/templates/nodejs-express/hexagonal/.gitignore.hbs +42 -0
  349. package/templates/nodejs-express/hexagonal/Dockerfile.hbs +1 -1
  350. package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +2 -2
  351. package/templates/nodejs-express/hexagonal/package.json.hbs +69 -69
  352. package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/PaymentController.ts.hbs +21 -38
  353. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts.hbs +2 -0
  354. package/templates/nodejs-express/hexagonal/src/config.ts.hbs +9 -0
  355. package/templates/nodejs-express/hexagonal/src/core/AuthService.ts.hbs +5 -5
  356. package/templates/nodejs-express/hexagonal/src/core/PaymentService.ts.hbs +7 -22
  357. package/templates/nodejs-express/hexagonal/src/core/domain/entities/User.ts.hbs +24 -4
  358. package/templates/nodejs-express/mvc/.gitignore.hbs +42 -0
  359. package/templates/nodejs-express/mvc/package.json.hbs +67 -67
  360. package/templates/python-fastapi/clean/.gitignore.hbs +76 -0
  361. package/templates/python-fastapi/clean/app/application/services/payment_service.py.hbs +3 -3
  362. package/templates/python-fastapi/clean/app/config.py.hbs +6 -7
  363. package/templates/python-fastapi/clean/app/domain/usecases/login_user.py.hbs +15 -0
  364. package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +40 -6
  365. package/templates/python-fastapi/clean/app/infrastructure/http/payment_controller.py.hbs +5 -4
  366. package/templates/python-fastapi/clean/app/infrastructure/security/jwt.py.hbs +23 -0
  367. package/templates/python-fastapi/clean/app/main.py.hbs +3 -0
  368. package/templates/python-fastapi/clean/docker-compose.yml.hbs +5 -12
  369. package/templates/python-fastapi/clean/requirements.txt.hbs +3 -0
  370. package/templates/python-fastapi/hexagonal/.gitignore.hbs +76 -0
  371. package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +6 -9
  372. package/templates/python-fastapi/hexagonal/app/adapters/inbound/payment_http_adapter.py.hbs +4 -3
  373. package/templates/python-fastapi/hexagonal/app/adapters/outbound/stripe_adapter.py.hbs +30 -19
  374. package/templates/python-fastapi/hexagonal/app/config.py.hbs +14 -4
  375. package/templates/python-fastapi/hexagonal/app/core/domain/user.py.hbs +3 -1
  376. package/templates/python-fastapi/hexagonal/app/core/payment_service.py.hbs +28 -18
  377. package/templates/python-fastapi/hexagonal/app/core/ports/__init__.py.hbs +3 -0
  378. package/templates/python-fastapi/hexagonal/app/core/ports/user_repository.py.hbs +15 -0
  379. package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +7 -0
  380. package/templates/python-fastapi/hexagonal/app/infrastructure/database/user_repository.py.hbs +53 -0
  381. package/templates/python-fastapi/hexagonal/app/infrastructure/security/__init__.py.hbs +0 -0
  382. package/templates/python-fastapi/hexagonal/app/infrastructure/security/adapters.py.hbs +23 -0
  383. package/templates/python-fastapi/hexagonal/app/infrastructure/security/jwt.py.hbs +23 -0
  384. package/templates/python-fastapi/hexagonal/docker-compose.yml.hbs +5 -12
  385. package/templates/python-fastapi/hexagonal/requirements.txt.hbs +4 -0
  386. package/templates/python-fastapi/mvc/.gitignore.hbs +76 -0
  387. package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +3 -17
  388. package/templates/python-fastapi/mvc/app/middleware/security.py.hbs +24 -3
  389. package/templates/python-fastapi/mvc/app/schemas/item.py.hbs +3 -1
  390. package/templates/python-fastapi/mvc/docker-compose.yml.hbs +5 -12
  391. package/templates/python-fastapi/mvc/requirements.txt.hbs +3 -1
  392. package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +0 -5
@@ -2,25 +2,48 @@ from fastapi import APIRouter, HTTPException, Depends
2
2
  from pydantic import BaseModel, EmailStr
3
3
  from sqlalchemy.ext.asyncio import AsyncSession
4
4
  from app.domain.usecases.register_user import RegisterUserUseCase
5
+ from app.domain.usecases.login_user import LoginUserUseCase
5
6
  from app.infrastructure.database.postgres_repository import PostgresUserRepository
6
7
  from app.infrastructure.security.adapters import BcryptHasher, JwtTokenGenerator
7
8
  from app.infrastructure.database.session import get_db
8
9
 
9
10
  router = APIRouter()
10
11
 
11
- # Dependency Injection Factory
12
+
13
+ class RegisterRequest(BaseModel):
14
+ email: EmailStr
15
+ name: str
16
+ password: str
17
+
18
+ class LoginRequest(BaseModel):
19
+ email: EmailStr
20
+ password: str
21
+
22
+ class UserResponse(BaseModel):
23
+ id: str
24
+ email: str
25
+ name: str
26
+
27
+ class AuthResponse(BaseModel):
28
+ user: UserResponse
29
+ token: str
30
+
31
+
32
+ # Dependency Injection Factories
12
33
  def get_register_usecase(db: AsyncSession = Depends(get_db)) -> RegisterUserUseCase:
13
34
  repo = PostgresUserRepository(db)
14
35
  hasher = BcryptHasher()
15
36
  token_gen = JwtTokenGenerator()
16
37
  return RegisterUserUseCase(repo, hasher, token_gen)
17
38
 
18
- class RegisterRequest(BaseModel):
19
- email: EmailStr
20
- name: str
21
- password: str
39
+ def get_login_usecase(db: AsyncSession = Depends(get_db)) -> LoginUserUseCase:
40
+ repo = PostgresUserRepository(db)
41
+ hasher = BcryptHasher()
42
+ token_gen = JwtTokenGenerator()
43
+ return LoginUserUseCase(repo, hasher, token_gen)
22
44
 
23
- @router.post("/register")
45
+
46
+ @router.post("/register", response_model=AuthResponse)
24
47
  async def register(
25
48
  req: RegisterRequest,
26
49
  usecase: RegisterUserUseCase = Depends(get_register_usecase)
@@ -30,3 +53,14 @@ async def register(
30
53
  return result
31
54
  except ValueError as e:
32
55
  raise HTTPException(status_code=400, detail=str(e))
56
+
57
+ @router.post("/login", response_model=AuthResponse)
58
+ async def login(
59
+ req: LoginRequest,
60
+ usecase: LoginUserUseCase = Depends(get_login_usecase)
61
+ ):
62
+ try:
63
+ result = await usecase.execute(req.email, req.password)
64
+ return result
65
+ except ValueError as e:
66
+ raise HTTPException(status_code=401, detail=str(e))
@@ -1,16 +1,17 @@
1
1
  from fastapi import APIRouter, HTTPException, Header, Request, Depends
2
2
  from pydantic import BaseModel
3
+ from sqlalchemy.ext.asyncio import AsyncSession
3
4
  from app.application.services.payment_service import PaymentService
4
- from app.infrastructure.database.session import AsyncSessionLocal
5
- from app.infrastructure.database.user_repository import SQLAlchemyUserRepository
5
+ from app.infrastructure.database.session import get_db
6
+ from app.infrastructure.database.postgres_repository import PostgresUserRepository
6
7
  from app.infrastructure.stripe_provider import StripeProvider
7
8
  from app.infrastructure.security.jwt import get_current_user_id
8
9
 
9
10
  router = APIRouter()
10
11
 
11
12
 
12
- def get_payment_service() -> PaymentService:
13
- repo = SQLAlchemyUserRepository(AsyncSessionLocal())
13
+ def get_payment_service(db: AsyncSession = Depends(get_db)) -> PaymentService:
14
+ repo = PostgresUserRepository(db)
14
15
  provider = StripeProvider()
15
16
  return PaymentService(user_repository=repo, stripe_provider=provider)
16
17
 
@@ -0,0 +1,23 @@
1
+ from fastapi import HTTPException, Security
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from jose import jwt, JWTError
4
+ import os
5
+
6
+ security = HTTPBearer()
7
+
8
+ SECRET_KEY = os.getenv("JWT_SECRET", "secret")
9
+ ALGORITHM = "HS256"
10
+
11
+
12
+ def get_current_user_id(
13
+ credentials: HTTPAuthorizationCredentials = Security(security),
14
+ ) -> str:
15
+ try:
16
+ token = credentials.credentials
17
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
18
+ user_id: str = payload.get("sub")
19
+ if user_id is None:
20
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
21
+ return user_id
22
+ except JWTError:
23
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
@@ -1,3 +1,6 @@
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
1
4
  from contextlib import asynccontextmanager
2
5
  from fastapi import FastAPI
3
6
  from app.infrastructure.http import auth_controller, payment_controller
@@ -1,16 +1,4 @@
1
- version: '3.8'
2
-
3
1
  services:
4
- app:
5
- build: .
6
- container_name: {{kebabCase projectName}}-app
7
- ports:
8
- - "8000:8000"
9
- environment:
10
- - DATABASE_URL=postgresql://postgres:postgres@db:5432/{{snakeCase projectName}}
11
- depends_on:
12
- - db
13
-
14
2
  db:
15
3
  image: postgres:15-alpine
16
4
  container_name: {{kebabCase projectName}}-db
@@ -23,6 +11,11 @@ services:
23
11
  volumes:
24
12
  - postgres_data:/var/lib/postgresql/data
25
13
  restart: unless-stopped
14
+ healthcheck:
15
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
16
+ interval: 10s
17
+ timeout: 5s
18
+ retries: 5
26
19
 
27
20
  volumes:
28
21
  postgres_data:
@@ -1,10 +1,13 @@
1
1
  fastapi>=0.109.0
2
2
  uvicorn[standard]>=0.27.0
3
3
  pydantic>=2.5.0
4
+ pydantic[email]>=2.5.0
4
5
  pydantic-settings>=2.1.0
5
6
  python-dotenv>=1.0.0
6
7
  python-jose[cryptography]>=3.3.0
7
8
  passlib[bcrypt]>=1.7.4
9
+ bcrypt>=3.0.0,<4.0.0
10
+ greenlet>=3.0.0
8
11
  stripe>=8.0.0
9
12
  sqlalchemy>=2.0.0
10
13
  alembic>=1.13.0
@@ -0,0 +1,76 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.pyo
7
+
8
+ # Distribution / packaging
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Virtual environments
27
+ .venv/
28
+ venv/
29
+ env/
30
+ ENV/
31
+ env.bak/
32
+ venv.bak/
33
+ .python-version
34
+
35
+ # Environment variables
36
+ .env
37
+ .env.local
38
+ .env.*.local
39
+ !.env.example
40
+
41
+ # Pytest
42
+ .pytest_cache/
43
+ pytest-cache/
44
+ .cache/
45
+
46
+ # Coverage
47
+ htmlcov/
48
+ .tox/
49
+ .coverage
50
+ .coverage.*
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+
55
+ # MyPy / Pyright
56
+ .mypy_cache/
57
+ .dmypy.json
58
+ dmypy.json
59
+ .pyright/
60
+ pyrightconfig.json
61
+
62
+ # Alembic — keep migrations, ignore autogenerated caches
63
+ # alembic/versions/ is intentionally tracked
64
+
65
+ # Jupyter
66
+ .ipynb_checkpoints
67
+
68
+ # OS
69
+ .DS_Store
70
+ Thumbs.db
71
+
72
+ # Editor
73
+ .vscode/
74
+ .idea/
75
+ *.swp
76
+ *.swo
@@ -5,7 +5,7 @@ from app.core.ports import IAuthPort
5
5
  from app.core.domain.user import User as DomainUser
6
6
  from app.infrastructure.database.session import get_db
7
7
  from app.infrastructure.database.models import UserModel
8
- from app.adapters.outbound.postgres_user_repository import PostgresUserRepository
8
+ from app.adapters.outbound.postgres_user_repository import PostgresUserRepository
9
9
  from app.core.service import AuthService
10
10
  from app.infrastructure.security.adapters import BcryptHasher, JwtTokenGenerator
11
11
 
@@ -16,6 +16,10 @@ class RegisterRequest(BaseModel):
16
16
  name: str
17
17
  password: str
18
18
 
19
+ class LoginRequest(BaseModel):
20
+ email: EmailStr
21
+ password: str
22
+
19
23
  # Dependency Injection Factory
20
24
  def get_auth_service(db: AsyncSession = Depends(get_db)) -> IAuthPort:
21
25
  repo = PostgresUserRepository(db)
@@ -36,7 +40,7 @@ async def register(
36
40
 
37
41
  @router.post("/login")
38
42
  async def login(
39
- req: RegisterRequest,
43
+ req: LoginRequest,
40
44
  service: IAuthPort = Depends(get_auth_service)
41
45
  ):
42
46
  try:
@@ -44,10 +48,3 @@ async def login(
44
48
  return result
45
49
  except ValueError as e:
46
50
  raise HTTPException(status_code=401, detail=str(e))
47
-
48
- # Setup function for backward compatibility or direct usage
49
- # though router is preferred
50
- def setup_auth_routes(auth_service: IAuthPort):
51
- # This pattern is tricky with FastAPIs dependency injection system
52
- # It's better to rely on Depends() as implemented above
53
- return router
@@ -1,16 +1,17 @@
1
1
  from fastapi import APIRouter, HTTPException, Header, Request, Depends
2
2
  from pydantic import BaseModel
3
+ from sqlalchemy.ext.asyncio import AsyncSession
3
4
  from app.core.payment_service import PaymentService
4
5
  from app.adapters.outbound.stripe_adapter import StripeAdapter
5
- from app.infrastructure.database.session import AsyncSessionLocal
6
+ from app.infrastructure.database.session import get_db
6
7
  from app.infrastructure.database.user_repository import SQLAlchemyUserRepository
7
8
  from app.infrastructure.security.jwt import get_current_user_id
8
9
 
9
10
  router = APIRouter()
10
11
 
11
12
 
12
- def get_payment_service() -> PaymentService:
13
- repo = SQLAlchemyUserRepository(AsyncSessionLocal())
13
+ def get_payment_service(db: AsyncSession = Depends(get_db)) -> PaymentService:
14
+ repo = SQLAlchemyUserRepository(db)
14
15
  adapter = StripeAdapter()
15
16
  return PaymentService(user_repository=repo, stripe_adapter=adapter)
16
17
 
@@ -1,19 +1,24 @@
1
1
  import stripe
2
2
  import os
3
3
 
4
- stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
5
4
  stripe.api_version = "2026-02-25.clover"
6
5
 
7
6
 
8
7
  class StripeAdapter:
9
8
  """Outbound adapter: wraps the Stripe SDK for use by the core domain."""
10
9
 
10
+ def __init__(self):
11
+ stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
12
+
11
13
  def create_customer(self, email: str, name: str | None = None, user_id: str | None = None):
12
- return stripe.Customer.create(
13
- email=email,
14
- name=name,
15
- metadata={"userId": user_id} if user_id else {},
16
- )
14
+ try:
15
+ return stripe.Customer.create(
16
+ email=email,
17
+ name=name,
18
+ metadata={"userId": user_id} if user_id else {},
19
+ )
20
+ except stripe.StripeError as e:
21
+ raise ValueError(f"Stripe error creating customer: {e.user_message or str(e)}")
17
22
 
18
23
  def create_checkout_session(
19
24
  self,
@@ -23,21 +28,27 @@ class StripeAdapter:
23
28
  success_url: str,
24
29
  cancel_url: str,
25
30
  ):
26
- return stripe.checkout.Session.create(
27
- mode="subscription",
28
- payment_method_types=["card"],
29
- line_items=[{"price": price_id, "quantity": 1}],
30
- customer=customer_id,
31
- success_url=success_url,
32
- cancel_url=cancel_url,
33
- client_reference_id=str(user_id),
34
- )
31
+ try:
32
+ return stripe.checkout.Session.create(
33
+ mode="subscription",
34
+ payment_method_types=["card"],
35
+ line_items=[{"price": price_id, "quantity": 1}],
36
+ customer=customer_id,
37
+ success_url=success_url,
38
+ cancel_url=cancel_url,
39
+ client_reference_id=str(user_id),
40
+ )
41
+ except stripe.StripeError as e:
42
+ raise ValueError(f"Stripe error creating checkout session: {e.user_message or str(e)}")
35
43
 
36
44
  def create_portal_session(self, customer_id: str, return_url: str):
37
- return stripe.billing_portal.Session.create(
38
- customer=customer_id,
39
- return_url=return_url,
40
- )
45
+ try:
46
+ return stripe.billing_portal.Session.create(
47
+ customer=customer_id,
48
+ return_url=return_url,
49
+ )
50
+ except stripe.StripeError as e:
51
+ raise ValueError(f"Stripe error creating portal session: {e.user_message or str(e)}")
41
52
 
42
53
  def construct_event(self, payload: bytes, sig_header: str):
43
54
  webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET", "")
@@ -1,19 +1,29 @@
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
1
4
  from pydantic_settings import BaseSettings, SettingsConfigDict
2
5
  from functools import lru_cache
3
6
 
4
7
  class Settings(BaseSettings):
5
8
  PROJECT_NAME: str = "{{projectName}}"
6
9
  API_V1_STR: str = "/api/v1"
7
-
10
+
8
11
  # Database
9
- DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/{{projectName}}_db"
10
-
12
+ DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/{{projectName}}"
13
+
11
14
  # Security
12
15
  SECRET_KEY: str = "change_this_to_a_secure_random_key"
13
16
  ALGORITHM: str = "HS256"
14
17
  ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
15
18
 
16
- model_config = SettingsConfigDict(env_file=".env", case_sensitive=True)
19
+ # Stripe
20
+ STRIPE_SECRET_KEY: str = "sk_test_placeholder"
21
+ STRIPE_WEBHOOK_SECRET: str = "whsec_placeholder"
22
+
23
+ # Frontend
24
+ FRONTEND_URL: str = "http://localhost:3000"
25
+
26
+ model_config = SettingsConfigDict(env_file=".env", case_sensitive=True, extra="ignore")
17
27
 
18
28
  @lru_cache
19
29
  def get_settings():
@@ -1,4 +1,5 @@
1
1
  from dataclasses import dataclass
2
+ from datetime import datetime
2
3
  from typing import Optional
3
4
 
4
5
  @dataclass(frozen=True)
@@ -9,7 +10,8 @@ class User:
9
10
  password: str
10
11
  id: Optional[str] = None
11
12
  stripe_customer_id: Optional[str] = None
12
-
13
+ created_at: Optional[datetime] = None
14
+
13
15
  def __post_init__(self):
14
16
  if "@" not in self.email:
15
17
  raise ValueError("Invalid email")
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import dataclasses
2
3
  from app.core.ports.user_repository import UserRepository
3
4
  from app.adapters.outbound.stripe_adapter import StripeAdapter
4
5
 
@@ -18,22 +19,28 @@ class PaymentService:
18
19
  customer_id = user.stripe_customer_id
19
20
 
20
21
  if not customer_id:
21
- customer = self.stripe_adapter.create_customer(
22
- email=user.email,
23
- name=getattr(user, "name", None),
24
- user_id=str(user.id),
25
- )
22
+ try:
23
+ customer = self.stripe_adapter.create_customer(
24
+ email=user.email,
25
+ name=getattr(user, "name", None),
26
+ user_id=str(user.id),
27
+ )
28
+ except ValueError as e:
29
+ raise ValueError(f"Failed to create Stripe customer: {e}")
26
30
  customer_id = customer.id
27
- user.stripe_customer_id = customer_id
31
+ user = dataclasses.replace(user, stripe_customer_id=customer_id)
28
32
  await self.user_repository.save(user)
29
33
 
30
- session = self.stripe_adapter.create_checkout_session(
31
- customer_id=customer_id,
32
- price_id=price_id,
33
- user_id=str(user_id),
34
- success_url=f"{os.getenv('FRONTEND_URL')}/success?session_id={{CHECKOUT_SESSION_ID}}",
35
- cancel_url=f"{os.getenv('FRONTEND_URL')}/cancel",
36
- )
34
+ try:
35
+ session = self.stripe_adapter.create_checkout_session(
36
+ customer_id=customer_id,
37
+ price_id=price_id,
38
+ user_id=str(user_id),
39
+ success_url=f"{os.getenv('FRONTEND_URL')}/success?session_id={'{CHECKOUT_SESSION_ID}'}",
40
+ cancel_url=f"{os.getenv('FRONTEND_URL')}/cancel",
41
+ )
42
+ except ValueError as e:
43
+ raise ValueError(f"Failed to create checkout session: {e}")
37
44
  return session.url
38
45
 
39
46
  async def create_portal_session(self, user_id: str) -> str:
@@ -41,10 +48,13 @@ class PaymentService:
41
48
  if not user or not user.stripe_customer_id:
42
49
  raise ValueError("No Stripe customer found for this user")
43
50
 
44
- session = self.stripe_adapter.create_portal_session(
45
- customer_id=user.stripe_customer_id,
46
- return_url=f"{os.getenv('FRONTEND_URL')}/dashboard",
47
- )
51
+ try:
52
+ session = self.stripe_adapter.create_portal_session(
53
+ customer_id=user.stripe_customer_id,
54
+ return_url=f"{os.getenv('FRONTEND_URL')}/dashboard",
55
+ )
56
+ except ValueError as e:
57
+ raise ValueError(f"Failed to create portal session: {e}")
48
58
  return session.url
49
59
 
50
60
  async def handle_webhook(self, payload: bytes, sig_header: str) -> dict:
@@ -62,7 +72,7 @@ class PaymentService:
62
72
  if user_id and customer_id:
63
73
  user = await self.user_repository.find_by_id(user_id)
64
74
  if user:
65
- user.stripe_customer_id = customer_id
75
+ user = dataclasses.replace(user, stripe_customer_id=customer_id)
66
76
  await self.user_repository.save(user)
67
77
  print(f"Checkout completed for user: {user_id}")
68
78
 
@@ -0,0 +1,3 @@
1
+ from app.core.ports.ports import IAuthPort, IUserRepositoryPort
2
+
3
+ __all__ = ["IAuthPort", "IUserRepositoryPort"]
@@ -0,0 +1,15 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+ from app.core.domain.user import User
4
+
5
+
6
+ class UserRepository(ABC):
7
+ """Repository port for payment-related user lookups."""
8
+
9
+ @abstractmethod
10
+ async def find_by_id(self, user_id: str) -> Optional[User]:
11
+ pass
12
+
13
+ @abstractmethod
14
+ async def save(self, user: User) -> User:
15
+ pass
@@ -18,3 +18,10 @@ AsyncSessionLocal = async_sessionmaker(
18
18
 
19
19
  class Base(DeclarativeBase):
20
20
  pass
21
+
22
+ async def get_db():
23
+ async with AsyncSessionLocal() as session:
24
+ try:
25
+ yield session
26
+ finally:
27
+ await session.close()
@@ -0,0 +1,53 @@
1
+ from typing import Optional
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlalchemy import select
4
+ from app.core.ports.user_repository import UserRepository
5
+ from app.core.domain.user import User
6
+ from app.infrastructure.database.models import UserModel
7
+
8
+
9
+ class SQLAlchemyUserRepository(UserRepository):
10
+ """Async SQLAlchemy implementation of UserRepository (payment context)."""
11
+
12
+ def __init__(self, session: AsyncSession):
13
+ self.session = session
14
+
15
+ def _to_entity(self, model: UserModel) -> User:
16
+ return User(
17
+ id=model.id,
18
+ email=model.email,
19
+ name=model.name,
20
+ password=model.password,
21
+ stripe_customer_id=model.stripe_customer_id,
22
+ created_at=model.created_at,
23
+ )
24
+
25
+ async def find_by_id(self, user_id: str) -> Optional[User]:
26
+ result = await self.session.execute(
27
+ select(UserModel).where(UserModel.id == user_id)
28
+ )
29
+ model = result.scalars().first()
30
+ return self._to_entity(model) if model else None
31
+
32
+ async def save(self, user: User) -> User:
33
+ result = await self.session.execute(
34
+ select(UserModel).where(UserModel.id == user.id)
35
+ )
36
+ model = result.scalars().first()
37
+
38
+ if model:
39
+ model.stripe_customer_id = user.stripe_customer_id
40
+ else:
41
+ model = UserModel(
42
+ id=user.id,
43
+ email=user.email,
44
+ name=user.name,
45
+ password=user.password,
46
+ stripe_customer_id=user.stripe_customer_id,
47
+ created_at=user.created_at,
48
+ )
49
+ self.session.add(model)
50
+
51
+ await self.session.commit()
52
+ await self.session.refresh(model)
53
+ return self._to_entity(model)
@@ -0,0 +1,23 @@
1
+ from passlib.context import CryptContext
2
+ from jose import jwt
3
+ from datetime import datetime, timedelta
4
+ import os
5
+
6
+ SECRET_KEY = os.getenv("JWT_SECRET", "secret")
7
+ ALGORITHM = "HS256"
8
+
9
+ class BcryptHasher:
10
+ def __init__(self):
11
+ self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
12
+
13
+ def hash(self, password: str) -> str:
14
+ return self.pwd_context.hash(password)
15
+
16
+ def verify(self, password: str, hashed: str) -> bool:
17
+ return self.pwd_context.verify(password, hashed)
18
+
19
+ class JwtTokenGenerator:
20
+ def generate(self, user_id: str, email: str) -> str:
21
+ expire = datetime.utcnow() + timedelta(days=7)
22
+ to_encode = {"exp": expire, "sub": user_id, "email": email}
23
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@@ -0,0 +1,23 @@
1
+ from fastapi import HTTPException, Security
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from jose import jwt, JWTError
4
+ import os
5
+
6
+ security = HTTPBearer()
7
+
8
+ SECRET_KEY = os.getenv("JWT_SECRET", "secret")
9
+ ALGORITHM = "HS256"
10
+
11
+
12
+ def get_current_user_id(
13
+ credentials: HTTPAuthorizationCredentials = Security(security),
14
+ ) -> str:
15
+ try:
16
+ token = credentials.credentials
17
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
18
+ user_id: str = payload.get("sub")
19
+ if user_id is None:
20
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
21
+ return user_id
22
+ except JWTError:
23
+ raise HTTPException(status_code=401, detail="Invalid authentication credentials")
@@ -1,16 +1,4 @@
1
- version: '3.8'
2
-
3
1
  services:
4
- app:
5
- build: .
6
- container_name: {{kebabCase projectName}}-app
7
- ports:
8
- - "8000:8000"
9
- environment:
10
- - DATABASE_URL=postgresql://postgres:postgres@db:5432/{{snakeCase projectName}}
11
- depends_on:
12
- - db
13
-
14
2
  db:
15
3
  image: postgres:15-alpine
16
4
  container_name: {{kebabCase projectName}}-db
@@ -23,6 +11,11 @@ services:
23
11
  volumes:
24
12
  - postgres_data:/var/lib/postgresql/data
25
13
  restart: unless-stopped
14
+ healthcheck:
15
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
16
+ interval: 10s
17
+ timeout: 5s
18
+ retries: 5
26
19
 
27
20
  volumes:
28
21
  postgres_data: