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.
- package/README.md +1 -1
- package/dist/cli/commands/ecommerce.d.ts +3 -0
- package/dist/cli/commands/ecommerce.d.ts.map +1 -0
- package/dist/cli/commands/ecommerce.js +164 -0
- package/dist/cli/commands/ecommerce.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/ecommerce/.env.example +10 -0
- package/templates/ecommerce/.github/workflows/ci.yml +102 -0
- package/templates/ecommerce/.github/workflows/deploy.yml +31 -0
- package/templates/ecommerce/.prettierrc +9 -0
- package/templates/ecommerce/Dockerfile +54 -0
- package/templates/ecommerce/README.md +295 -0
- package/templates/ecommerce/apps/api/.env.example +59 -0
- package/templates/ecommerce/apps/api/jest.config.ts +50 -0
- package/templates/ecommerce/apps/api/jest.integration.config.ts +45 -0
- package/templates/ecommerce/apps/api/package.json +59 -0
- package/templates/ecommerce/apps/api/prisma/migrations/20260306000137_init/migration.sql +184 -0
- package/templates/ecommerce/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/templates/ecommerce/apps/api/prisma/schema.prisma +181 -0
- package/templates/ecommerce/apps/api/prisma/seed.ts +159 -0
- package/templates/ecommerce/apps/api/src/__tests__/app.test.ts +39 -0
- package/templates/ecommerce/apps/api/src/__tests__/globalSetup.ts +34 -0
- package/templates/ecommerce/apps/api/src/__tests__/globalTeardown.ts +16 -0
- package/templates/ecommerce/apps/api/src/__tests__/setup.db.ts +18 -0
- package/templates/ecommerce/apps/api/src/__tests__/setup.env.ts +14 -0
- package/templates/ecommerce/apps/api/src/app.ts +133 -0
- package/templates/ecommerce/apps/api/src/application/admin/admin-user.service.ts +24 -0
- package/templates/ecommerce/apps/api/src/application/admin/dashboard.service.ts +102 -0
- package/templates/ecommerce/apps/api/src/application/auth/auth.service.ts +185 -0
- package/templates/ecommerce/apps/api/src/application/cart/cart.service.ts +151 -0
- package/templates/ecommerce/apps/api/src/application/cart/coupon.service.ts +51 -0
- package/templates/ecommerce/apps/api/src/application/catalog/catalog.service.ts +168 -0
- package/templates/ecommerce/apps/api/src/application/checkout/checkout.service.ts +114 -0
- package/templates/ecommerce/apps/api/src/application/orders/order.service.ts +93 -0
- package/templates/ecommerce/apps/api/src/application/ports/email.port.ts +3 -0
- package/templates/ecommerce/apps/api/src/application/ports/payment.port.ts +24 -0
- package/templates/ecommerce/apps/api/src/application/ports/shipping.port.ts +9 -0
- package/templates/ecommerce/apps/api/src/application/ports/storage.port.ts +3 -0
- package/templates/ecommerce/apps/api/src/application/ports/token-blacklist.port.ts +4 -0
- package/templates/ecommerce/apps/api/src/application/ports/token.port.ts +18 -0
- package/templates/ecommerce/apps/api/src/application/profile/profile.service.ts +76 -0
- package/templates/ecommerce/apps/api/src/domain/auth/user.entity.ts +109 -0
- package/templates/ecommerce/apps/api/src/domain/auth/user.repository.ts +11 -0
- package/templates/ecommerce/apps/api/src/domain/cart/cart.entity.ts +136 -0
- package/templates/ecommerce/apps/api/src/domain/cart/cart.repository.ts +8 -0
- package/templates/ecommerce/apps/api/src/domain/cart/coupon.entity.ts +58 -0
- package/templates/ecommerce/apps/api/src/domain/cart/coupon.repository.ts +10 -0
- package/templates/ecommerce/apps/api/src/domain/catalog/category.entity.ts +51 -0
- package/templates/ecommerce/apps/api/src/domain/catalog/category.repository.ts +10 -0
- package/templates/ecommerce/apps/api/src/domain/catalog/product.entity.ts +130 -0
- package/templates/ecommerce/apps/api/src/domain/catalog/product.repository.ts +28 -0
- package/templates/ecommerce/apps/api/src/domain/checkout/order.entity.ts +121 -0
- package/templates/ecommerce/apps/api/src/domain/checkout/order.repository.ts +11 -0
- package/templates/ecommerce/apps/api/src/domain/shared/AppError.ts +12 -0
- package/templates/ecommerce/apps/api/src/infrastructure/cache/redis.ts +16 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/admin.registry.ts +13 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/auth.registry.ts +34 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/cart.registry.ts +49 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/catalog.registry.ts +24 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/checkout.registry.ts +47 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/orders.registry.ts +6 -0
- package/templates/ecommerce/apps/api/src/infrastructure/config/registry/profile.registry.ts +4 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/cart.memory.repository.ts +33 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/category.memory.repository.ts +41 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/coupon.memory.repository.ts +55 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/order.memory.repository.ts +75 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/product.memory.repository.ts +100 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/in-memory/user.memory.repository.ts +54 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/auth/user.prisma.repository.ts +83 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/category.prisma.repository.ts +69 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/catalog/product.prisma.repository.ts +185 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma/checkout/order.prisma.repository.ts +149 -0
- package/templates/ecommerce/apps/api/src/infrastructure/persistence/prisma-client.ts +17 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/email/email.registry.ts +18 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/email/ethereal.email.service.ts +38 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/email/noop.email.service.ts +12 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/email/smtp.email.service.ts +36 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe-webhook.handler.ts +83 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/payment/stripe.adapter.ts +39 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/shipping/mock.shipping.service.ts +17 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/storage/in-memory.storage.service.ts +11 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/storage/local-disk.storage.service.ts +27 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/storage/s3.storage.service.ts +52 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/storage/storage.registry.ts +19 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/token/redis.token.blacklist.ts +23 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.blacklist.ts +30 -0
- package/templates/ecommerce/apps/api/src/infrastructure/services/token/token.service.ts +136 -0
- package/templates/ecommerce/apps/api/src/modules/admin/__tests__/admin.routes.integration.test.ts +250 -0
- package/templates/ecommerce/apps/api/src/modules/admin/admin.controller.ts +116 -0
- package/templates/ecommerce/apps/api/src/modules/admin/admin.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/admin/admin.routes.ts +21 -0
- package/templates/ecommerce/apps/api/src/modules/admin/admin.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/admin/admin.user.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.logout.redis.test.ts +104 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.routes.integration.test.ts +211 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/auth.service.unit.test.ts +260 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/email.service.unit.test.ts +94 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/token.blacklist.redis.test.ts +65 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.entity.unit.test.ts +79 -0
- package/templates/ecommerce/apps/api/src/modules/auth/__tests__/user.prisma.repository.test.ts +138 -0
- package/templates/ecommerce/apps/api/src/modules/auth/auth.controller.ts +148 -0
- package/templates/ecommerce/apps/api/src/modules/auth/auth.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/auth.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/modules/auth/auth.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/redis.token.blacklist.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/token.blacklist.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/auth/token.service.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/auth/user.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/user.prisma.repository.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/auth/user.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.entity.unit.test.ts +144 -0
- package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.routes.integration.test.ts +242 -0
- package/templates/ecommerce/apps/api/src/modules/cart/__tests__/cart.service.unit.test.ts +151 -0
- package/templates/ecommerce/apps/api/src/modules/cart/__tests__/coupon.admin.integration.test.ts +136 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.controller.ts +94 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/modules/cart/cart.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/cart/coupon.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/cart/coupon.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/cart/coupon.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/cart/shipping.service.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.routes.integration.test.ts +275 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/catalog.service.unit.test.ts +223 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.image.integration.test.ts +130 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/__tests__/product.prisma.repository.test.ts +174 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/catalog.controller.ts +176 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/catalog.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/catalog.routes.ts +38 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/catalog.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/category.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/category.prisma.repository.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/category.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/product.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/product.prisma.repository.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/catalog/product.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.routes.integration.test.ts +163 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/checkout.service.unit.test.ts +191 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/__tests__/order.prisma.repository.test.ts +150 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/checkout.controller.ts +59 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/checkout.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/checkout.routes.ts +18 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/checkout.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/order.entity.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/order.prisma.repository.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/order.repository.ts +2 -0
- package/templates/ecommerce/apps/api/src/modules/checkout/tax.service.ts +9 -0
- package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.entity.unit.test.ts +68 -0
- package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.routes.integration.test.ts +254 -0
- package/templates/ecommerce/apps/api/src/modules/orders/__tests__/order.service.email.unit.test.ts +142 -0
- package/templates/ecommerce/apps/api/src/modules/orders/order.controller.ts +96 -0
- package/templates/ecommerce/apps/api/src/modules/orders/order.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/orders/order.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/modules/orders/order.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe-webhook.unit.test.ts +330 -0
- package/templates/ecommerce/apps/api/src/modules/payment/__tests__/stripe.adapter.unit.test.ts +84 -0
- package/templates/ecommerce/apps/api/src/modules/payment/adapters/stripe.adapter.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/payment/payment.port.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/payment/stripe-webhook.handler.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.routes.integration.test.ts +180 -0
- package/templates/ecommerce/apps/api/src/modules/profile/__tests__/profile.service.unit.test.ts +187 -0
- package/templates/ecommerce/apps/api/src/modules/profile/profile.controller.ts +92 -0
- package/templates/ecommerce/apps/api/src/modules/profile/profile.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/modules/profile/profile.routes.ts +14 -0
- package/templates/ecommerce/apps/api/src/modules/profile/profile.service.ts +1 -0
- package/templates/ecommerce/apps/api/src/presentation/middlewares/authenticate.ts +37 -0
- package/templates/ecommerce/apps/api/src/presentation/middlewares/authorize.ts +23 -0
- package/templates/ecommerce/apps/api/src/presentation/middlewares/errorHandler.ts +48 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.controller.ts +116 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/admin/admin.routes.ts +21 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.controller.ts +147 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/auth/auth.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.controller.ts +94 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/cart/cart.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.controller.ts +176 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/catalog/catalog.routes.ts +38 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.controller.ts +59 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/checkout/checkout.routes.ts +18 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.controller.ts +96 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/orders/order.routes.ts +17 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.controller.ts +92 -0
- package/templates/ecommerce/apps/api/src/presentation/modules/profile/profile.routes.ts +14 -0
- package/templates/ecommerce/apps/api/src/presentation/validators/uuidParam.ts +20 -0
- package/templates/ecommerce/apps/api/src/server.ts +47 -0
- package/templates/ecommerce/apps/api/src/shared/__tests__/uuid.validation.test.ts +111 -0
- package/templates/ecommerce/apps/api/src/shared/errors/AppError.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/EtherealEmailService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/IEmailService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/NoopEmailService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/SmtpEmailService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/__tests__/ethereal.email.integration.test.ts +32 -0
- package/templates/ecommerce/apps/api/src/shared/infra/email/email.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/prisma.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/redis.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/IStorageService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/InMemoryStorageService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/LocalDiskStorageService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/S3StorageService.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/__tests__/s3.storage.unit.test.ts +73 -0
- package/templates/ecommerce/apps/api/src/shared/infra/storage/storage.registry.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/middlewares/authenticate.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/middlewares/authorize.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/middlewares/errorHandler.ts +1 -0
- package/templates/ecommerce/apps/api/src/shared/validators/uuidParam.ts +1 -0
- package/templates/ecommerce/apps/api/tsconfig.json +15 -0
- package/templates/ecommerce/apps/web/.env.example +8 -0
- package/templates/ecommerce/apps/web/index.html +19 -0
- package/templates/ecommerce/apps/web/jest.config.ts +45 -0
- package/templates/ecommerce/apps/web/package.json +38 -0
- package/templates/ecommerce/apps/web/src/App.tsx +133 -0
- package/templates/ecommerce/apps/web/src/__mocks__/fileMock.ts +1 -0
- package/templates/ecommerce/apps/web/src/__mocks__/styleMock.ts +1 -0
- package/templates/ecommerce/apps/web/src/index.css +159 -0
- package/templates/ecommerce/apps/web/src/main.tsx +13 -0
- package/templates/ecommerce/apps/web/src/modules/admin/__tests__/CouponsAdminPage.test.tsx +134 -0
- package/templates/ecommerce/apps/web/src/modules/admin/__tests__/DashboardPage.test.tsx +65 -0
- package/templates/ecommerce/apps/web/src/modules/admin/__tests__/OrdersAdminPage.test.tsx +79 -0
- package/templates/ecommerce/apps/web/src/modules/admin/__tests__/ProductsAdminPage.test.tsx +84 -0
- package/templates/ecommerce/apps/web/src/modules/admin/__tests__/UsersAdminPage.test.tsx +85 -0
- package/templates/ecommerce/apps/web/src/modules/admin/pages/CouponsAdminPage.tsx +179 -0
- package/templates/ecommerce/apps/web/src/modules/admin/pages/DashboardPage.tsx +58 -0
- package/templates/ecommerce/apps/web/src/modules/admin/pages/OrdersAdminPage.tsx +178 -0
- package/templates/ecommerce/apps/web/src/modules/admin/pages/ProductsAdminPage.tsx +444 -0
- package/templates/ecommerce/apps/web/src/modules/admin/pages/UsersAdminPage.tsx +87 -0
- package/templates/ecommerce/apps/web/src/modules/auth/LoginForm.tsx +91 -0
- package/templates/ecommerce/apps/web/src/modules/auth/RegisterForm.tsx +109 -0
- package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ForgotPasswordPage.test.tsx +42 -0
- package/templates/ecommerce/apps/web/src/modules/auth/__tests__/LoginForm.test.tsx +76 -0
- package/templates/ecommerce/apps/web/src/modules/auth/__tests__/RegisterForm.test.tsx +62 -0
- package/templates/ecommerce/apps/web/src/modules/auth/__tests__/ResetPasswordPage.test.tsx +66 -0
- package/templates/ecommerce/apps/web/src/modules/auth/pages/ForgotPasswordPage.tsx +100 -0
- package/templates/ecommerce/apps/web/src/modules/auth/pages/LoginPage.tsx +39 -0
- package/templates/ecommerce/apps/web/src/modules/auth/pages/RegisterPage.tsx +39 -0
- package/templates/ecommerce/apps/web/src/modules/auth/pages/ResetPasswordPage.tsx +110 -0
- package/templates/ecommerce/apps/web/src/modules/auth/useAuthStore.ts +141 -0
- package/templates/ecommerce/apps/web/src/modules/cart/__tests__/CartPage.test.tsx +111 -0
- package/templates/ecommerce/apps/web/src/modules/cart/pages/CartPage.tsx +313 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductCard.test.tsx +59 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/__tests__/ProductFilters.test.tsx +56 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductCard.tsx +78 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/components/ProductFilters.tsx +104 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductDetailPage.tsx +179 -0
- package/templates/ecommerce/apps/web/src/modules/catalog/pages/ProductListPage.tsx +100 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/CheckoutPage.test.tsx +159 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/__tests__/StripePaymentForm.test.tsx +79 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/components/StripePaymentForm.tsx +55 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/hooks/useCheckout.ts +56 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutPage.tsx +344 -0
- package/templates/ecommerce/apps/web/src/modules/checkout/pages/CheckoutSuccessPage.tsx +12 -0
- package/templates/ecommerce/apps/web/src/modules/legal/pages/PrivacyPolicyPage.tsx +207 -0
- package/templates/ecommerce/apps/web/src/modules/legal/pages/TermsOfServicePage.tsx +175 -0
- package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderDetailPage.test.tsx +75 -0
- package/templates/ecommerce/apps/web/src/modules/orders/__tests__/OrderHistoryPage.test.tsx +87 -0
- package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderDetailPage.tsx +73 -0
- package/templates/ecommerce/apps/web/src/modules/orders/pages/OrderHistoryPage.tsx +97 -0
- package/templates/ecommerce/apps/web/src/modules/profile/__tests__/ProfilePage.test.tsx +150 -0
- package/templates/ecommerce/apps/web/src/modules/profile/pages/ProfilePage.tsx +275 -0
- package/templates/ecommerce/apps/web/src/setupTests.ts +10 -0
- package/templates/ecommerce/apps/web/src/shared/components/CookieConsent.tsx +108 -0
- package/templates/ecommerce/apps/web/src/shared/components/ErrorBoundary.tsx +112 -0
- package/templates/ecommerce/apps/web/src/shared/components/Layout.tsx +143 -0
- package/templates/ecommerce/apps/web/src/shared/components/ProtectedRoute.tsx +21 -0
- package/templates/ecommerce/apps/web/src/shared/config/siteConfig.ts +57 -0
- package/templates/ecommerce/apps/web/src/shared/hooks/usePageTitle.ts +16 -0
- package/templates/ecommerce/apps/web/src/shared/lib/apiFetch.ts +16 -0
- package/templates/ecommerce/apps/web/src/shared/pages/NotFoundPage.tsx +42 -0
- package/templates/ecommerce/apps/web/src/shared/theme/ThemeProvider.tsx +45 -0
- package/templates/ecommerce/apps/web/src/shared/theme/__tests__/ThemeProvider.test.tsx +78 -0
- package/templates/ecommerce/apps/web/src/shared/theme/createTheme.ts +58 -0
- package/templates/ecommerce/apps/web/src/shared/theme/tokens.ts +81 -0
- package/templates/ecommerce/apps/web/src/vite-env.d.ts +1 -0
- package/templates/ecommerce/apps/web/tsconfig.jest.json +12 -0
- package/templates/ecommerce/apps/web/tsconfig.json +25 -0
- package/templates/ecommerce/apps/web/tsconfig.node.json +11 -0
- package/templates/ecommerce/apps/web/vite.config.ts +30 -0
- package/templates/ecommerce/docker-compose.yml +85 -0
- package/templates/ecommerce/package-lock.json +11255 -0
- package/templates/ecommerce/package.json +27 -0
- package/templates/ecommerce/packages/shared-types/package.json +13 -0
- package/templates/ecommerce/packages/shared-types/src/index.ts +3 -0
- package/templates/ecommerce/packages/shared-types/src/theme.ts +44 -0
- package/templates/ecommerce/packages/shared-types/tsconfig.json +11 -0
- package/templates/ecommerce/scripts/customize.sh +201 -0
- package/templates/ecommerce/tsconfig.json +14 -0
- package/templates/java-spring/clean/.gitignore.hbs +72 -0
- package/templates/java-spring/clean/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/application/usecase/PaymentUseCase.java.hbs +21 -17
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/entity/UserEntity.java.hbs +52 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/persistence/repository/JpaUserRepository.java.hbs +12 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/JwtAuthenticationFilter.java.hbs +64 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/security/SecurityConfig.java.hbs +36 -0
- package/templates/java-spring/clean/src/main/java/{{packagePath}}/infrastructure/stripe/StripeGateway.java.hbs +63 -0
- package/templates/java-spring/clean/src/main/resources/application.properties.hbs +6 -7
- package/templates/java-spring/hexagonal/.gitignore.hbs +72 -0
- package/templates/java-spring/hexagonal/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/JwtFilter.java.hbs +71 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/adapters/outbound/security/SecurityConfig.java.hbs +35 -0
- package/templates/java-spring/hexagonal/src/main/java/{{packagePath}}/core/service/PaymentService.java.hbs +3 -3
- package/templates/java-spring/hexagonal/src/main/resources/application.properties.hbs +4 -4
- package/templates/java-spring/mvc/.gitignore.hbs +72 -0
- package/templates/java-spring/mvc/docker-compose.yml.hbs +6 -3
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/config/SecurityConfig.java.hbs +13 -12
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/AuthController.java.hbs +9 -8
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/controller/PaymentsController.java.hbs +5 -6
- package/templates/java-spring/mvc/src/main/java/{{packagePath}}/service/StripeService.java.hbs +3 -3
- package/templates/java-spring/mvc/src/main/resources/application.yml.hbs +29 -26
- package/templates/nestjs/clean/.gitignore.hbs +42 -0
- package/templates/nestjs/clean/Dockerfile.hbs +6 -3
- package/templates/nestjs/clean/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/clean/src/app.module.ts.hbs +2 -1
- package/templates/nestjs/clean/src/application/payment.service.ts.hbs +72 -72
- package/templates/nestjs/clean/src/domain/entities/user.entity.ts.hbs +2 -2
- package/templates/nestjs/clean/src/domain/repositories/user.repository.ts.hbs +2 -2
- package/templates/nestjs/clean/src/infrastructure/database/repositories/prisma.user.repository.ts.hbs +18 -18
- package/templates/nestjs/clean/src/infrastructure/http/health.controller.ts.hbs +9 -0
- package/templates/nestjs/clean/src/main.ts.hbs +1 -4
- package/templates/nestjs/clean/src/payment.module.ts.hbs +12 -12
- package/templates/nestjs/hexagonal/.gitignore.hbs +42 -0
- package/templates/nestjs/hexagonal/Dockerfile.hbs +6 -3
- package/templates/nestjs/hexagonal/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/hexagonal/src/adapters/inbound/health.controller.ts.hbs +9 -0
- package/templates/nestjs/hexagonal/src/app.module.ts.hbs +2 -1
- package/templates/nestjs/hexagonal/src/core/domain/user.entity.ts.hbs +6 -6
- package/templates/nestjs/hexagonal/src/core/ports/ports.ts.hbs +4 -4
- package/templates/nestjs/hexagonal/src/main.ts.hbs +1 -4
- package/templates/nestjs/mvc/.gitignore.hbs +42 -0
- package/templates/nestjs/mvc/Dockerfile.hbs +6 -3
- package/templates/nestjs/mvc/docker-compose.yml.hbs +1 -11
- package/templates/nestjs/mvc/src/auth/auth.controller.ts.hbs +11 -1
- package/templates/nestjs/mvc/src/auth/auth.service.ts.hbs +3 -1
- package/templates/nestjs/mvc/src/controllers/health.controller.ts.hbs +6 -6
- package/templates/nestjs/mvc/src/main.ts.hbs +1 -4
- package/templates/nestjs/mvc/src/models/create-item.dto.ts.hbs +5 -2
- package/templates/nestjs/mvc/src/prisma/prisma.service.ts.hbs +1 -0
- package/templates/nextjs/mvc/.gitignore.hbs +42 -0
- package/templates/nextjs/mvc/Dockerfile.hbs +23 -8
- package/templates/nextjs/mvc/docker-compose.yml.hbs +1 -1
- package/templates/nodejs-express/clean/.gitignore.hbs +42 -0
- package/templates/nodejs-express/clean/Dockerfile.hbs +6 -1
- package/templates/nodejs-express/clean/docker-compose.yml.hbs +2 -2
- package/templates/nodejs-express/clean/package.json.hbs +69 -69
- package/templates/nodejs-express/clean/src/config.ts.hbs +11 -0
- package/templates/nodejs-express/clean/src/domain/entities/User.ts.hbs +46 -8
- package/templates/nodejs-express/hexagonal/.gitignore.hbs +42 -0
- package/templates/nodejs-express/hexagonal/Dockerfile.hbs +1 -1
- package/templates/nodejs-express/hexagonal/docker-compose.yml.hbs +2 -2
- package/templates/nodejs-express/hexagonal/package.json.hbs +69 -69
- package/templates/nodejs-express/hexagonal/src/adapters/inbound/http/PaymentController.ts.hbs +21 -38
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts.hbs +2 -0
- package/templates/nodejs-express/hexagonal/src/config.ts.hbs +9 -0
- package/templates/nodejs-express/hexagonal/src/core/AuthService.ts.hbs +5 -5
- package/templates/nodejs-express/hexagonal/src/core/PaymentService.ts.hbs +7 -22
- package/templates/nodejs-express/hexagonal/src/core/domain/entities/User.ts.hbs +24 -4
- package/templates/nodejs-express/mvc/.gitignore.hbs +42 -0
- package/templates/nodejs-express/mvc/package.json.hbs +67 -67
- package/templates/python-fastapi/clean/.gitignore.hbs +76 -0
- package/templates/python-fastapi/clean/app/application/services/payment_service.py.hbs +3 -3
- package/templates/python-fastapi/clean/app/config.py.hbs +6 -7
- package/templates/python-fastapi/clean/app/domain/usecases/login_user.py.hbs +15 -0
- package/templates/python-fastapi/clean/app/infrastructure/http/auth_controller.py.hbs +40 -6
- package/templates/python-fastapi/clean/app/infrastructure/http/payment_controller.py.hbs +5 -4
- package/templates/python-fastapi/clean/app/infrastructure/security/jwt.py.hbs +23 -0
- package/templates/python-fastapi/clean/app/main.py.hbs +3 -0
- package/templates/python-fastapi/clean/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/clean/requirements.txt.hbs +3 -0
- package/templates/python-fastapi/hexagonal/.gitignore.hbs +76 -0
- package/templates/python-fastapi/hexagonal/app/adapters/inbound/http_adapter.py.hbs +6 -9
- package/templates/python-fastapi/hexagonal/app/adapters/inbound/payment_http_adapter.py.hbs +4 -3
- package/templates/python-fastapi/hexagonal/app/adapters/outbound/stripe_adapter.py.hbs +30 -19
- package/templates/python-fastapi/hexagonal/app/config.py.hbs +14 -4
- package/templates/python-fastapi/hexagonal/app/core/domain/user.py.hbs +3 -1
- package/templates/python-fastapi/hexagonal/app/core/payment_service.py.hbs +28 -18
- package/templates/python-fastapi/hexagonal/app/core/ports/__init__.py.hbs +3 -0
- package/templates/python-fastapi/hexagonal/app/core/ports/user_repository.py.hbs +15 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/session.py.hbs +7 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/database/user_repository.py.hbs +53 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/__init__.py.hbs +0 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/adapters.py.hbs +23 -0
- package/templates/python-fastapi/hexagonal/app/infrastructure/security/jwt.py.hbs +23 -0
- package/templates/python-fastapi/hexagonal/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/hexagonal/requirements.txt.hbs +4 -0
- package/templates/python-fastapi/mvc/.gitignore.hbs +76 -0
- package/templates/python-fastapi/mvc/app/controllers/payments.py.hbs +3 -17
- package/templates/python-fastapi/mvc/app/middleware/security.py.hbs +24 -3
- package/templates/python-fastapi/mvc/app/schemas/item.py.hbs +3 -1
- package/templates/python-fastapi/mvc/docker-compose.yml.hbs +5 -12
- package/templates/python-fastapi/mvc/requirements.txt.hbs +3 -1
- package/templates/nodejs-express/hexagonal/src/adapters/outbound/persistence/prisma.ts +0 -5
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "postgresql"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Phase 1 — Auth ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
enum Role {
|
|
16
|
+
CUSTOMER
|
|
17
|
+
ADMIN
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
model User {
|
|
21
|
+
id String @id @default(cuid())
|
|
22
|
+
name String
|
|
23
|
+
email String @unique
|
|
24
|
+
passwordHash String
|
|
25
|
+
role Role @default(CUSTOMER)
|
|
26
|
+
createdAt DateTime @default(now())
|
|
27
|
+
updatedAt DateTime @updatedAt
|
|
28
|
+
|
|
29
|
+
passwordResetTokens PasswordResetToken[]
|
|
30
|
+
|
|
31
|
+
@@map("users")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
model PasswordResetToken {
|
|
35
|
+
id String @id @default(cuid())
|
|
36
|
+
token String @unique
|
|
37
|
+
userId String
|
|
38
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
39
|
+
expiresAt DateTime
|
|
40
|
+
usedAt DateTime?
|
|
41
|
+
createdAt DateTime @default(now())
|
|
42
|
+
|
|
43
|
+
@@map("password_reset_tokens")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Note: RefreshToken blacklist is stored in Redis (not Prisma) for performance.
|
|
47
|
+
// Refresh tokens have short TTL and high write volume — Redis is the right tool.
|
|
48
|
+
|
|
49
|
+
// ── Phase 2 — Catalog ─────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
enum ProductStatus {
|
|
52
|
+
ACTIVE
|
|
53
|
+
INACTIVE
|
|
54
|
+
DELETED
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
model Category {
|
|
58
|
+
id String @id @default(cuid())
|
|
59
|
+
name String
|
|
60
|
+
slug String @unique
|
|
61
|
+
description String?
|
|
62
|
+
parentId String?
|
|
63
|
+
parent Category? @relation("CategoryParent", fields: [parentId], references: [id])
|
|
64
|
+
children Category[] @relation("CategoryParent")
|
|
65
|
+
products Product[]
|
|
66
|
+
createdAt DateTime @default(now())
|
|
67
|
+
updatedAt DateTime @updatedAt
|
|
68
|
+
|
|
69
|
+
@@map("categories")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
model Product {
|
|
73
|
+
id String @id @default(cuid())
|
|
74
|
+
name String
|
|
75
|
+
slug String @unique
|
|
76
|
+
description String?
|
|
77
|
+
price Decimal @db.Decimal(10, 2)
|
|
78
|
+
status ProductStatus @default(ACTIVE)
|
|
79
|
+
categoryId String
|
|
80
|
+
category Category @relation(fields: [categoryId], references: [id])
|
|
81
|
+
images String[]
|
|
82
|
+
variants ProductVariant[]
|
|
83
|
+
createdAt DateTime @default(now())
|
|
84
|
+
updatedAt DateTime @updatedAt
|
|
85
|
+
|
|
86
|
+
@@index([categoryId])
|
|
87
|
+
@@index([status])
|
|
88
|
+
@@map("products")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
model ProductVariant {
|
|
92
|
+
id String @id @default(cuid())
|
|
93
|
+
sku String @unique
|
|
94
|
+
size String?
|
|
95
|
+
color String?
|
|
96
|
+
stock Int @default(0)
|
|
97
|
+
price Decimal? @db.Decimal(10, 2)
|
|
98
|
+
productId String
|
|
99
|
+
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
|
100
|
+
createdAt DateTime @default(now())
|
|
101
|
+
updatedAt DateTime @updatedAt
|
|
102
|
+
|
|
103
|
+
@@index([productId])
|
|
104
|
+
@@map("product_variants")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Phase 3 — Cart ───────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
enum DiscountType {
|
|
110
|
+
percent
|
|
111
|
+
fixed
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
model Coupon {
|
|
115
|
+
id String @id @default(cuid())
|
|
116
|
+
code String @unique
|
|
117
|
+
discountType DiscountType
|
|
118
|
+
discountValue Decimal @db.Decimal(10, 2)
|
|
119
|
+
minOrderValue Decimal @default(0) @db.Decimal(10, 2)
|
|
120
|
+
usageLimit Int?
|
|
121
|
+
usageCount Int @default(0)
|
|
122
|
+
expiresAt DateTime?
|
|
123
|
+
createdAt DateTime @default(now())
|
|
124
|
+
|
|
125
|
+
@@map("coupons")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Note: Cart and CartItem are stored in Redis (TTL-based), not Postgres.
|
|
129
|
+
// Only Coupon lives in Postgres for persistence and reporting.
|
|
130
|
+
|
|
131
|
+
// ── Phase 4 — Checkout & Orders ───────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
enum OrderStatus {
|
|
134
|
+
PENDING
|
|
135
|
+
PAID
|
|
136
|
+
FAILED
|
|
137
|
+
SHIPPED
|
|
138
|
+
DELIVERED
|
|
139
|
+
CANCELLED
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
model Order {
|
|
143
|
+
id String @id @default(cuid())
|
|
144
|
+
userId String
|
|
145
|
+
status OrderStatus @default(PENDING)
|
|
146
|
+
subtotal Decimal @db.Decimal(10, 2)
|
|
147
|
+
discount Decimal @default(0) @db.Decimal(10, 2)
|
|
148
|
+
shippingCost Decimal @default(0) @db.Decimal(10, 2)
|
|
149
|
+
tax Decimal @default(0) @db.Decimal(10, 2)
|
|
150
|
+
total Decimal @db.Decimal(10, 2)
|
|
151
|
+
couponCode String?
|
|
152
|
+
paymentIntentId String? @unique
|
|
153
|
+
trackingCode String?
|
|
154
|
+
shippingAddress Json?
|
|
155
|
+
items OrderItem[]
|
|
156
|
+
createdAt DateTime @default(now())
|
|
157
|
+
updatedAt DateTime @updatedAt
|
|
158
|
+
|
|
159
|
+
@@index([userId])
|
|
160
|
+
@@index([status])
|
|
161
|
+
@@map("orders")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
model OrderItem {
|
|
165
|
+
id String @id @default(cuid())
|
|
166
|
+
orderId String
|
|
167
|
+
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
|
168
|
+
variantId String
|
|
169
|
+
productId String
|
|
170
|
+
name String
|
|
171
|
+
sku String
|
|
172
|
+
price Decimal @db.Decimal(10, 2)
|
|
173
|
+
qty Int
|
|
174
|
+
image String?
|
|
175
|
+
|
|
176
|
+
@@index([orderId])
|
|
177
|
+
@@map("order_items")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Phase 5 — (Order status history — no new model, timestamps in Order)
|
|
181
|
+
// Phase 6 — (Admin aggregations — no new models, only queries)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma seed — popula o banco com dados de demonstração.
|
|
3
|
+
* Execute com: npm run db:seed (dentro de apps/api) ou
|
|
4
|
+
* npm run db:seed (na raiz do monorepo)
|
|
5
|
+
*/
|
|
6
|
+
import { PrismaClient, Role, ProductStatus } from '@prisma/client';
|
|
7
|
+
import bcrypt from 'bcryptjs';
|
|
8
|
+
|
|
9
|
+
const prisma = new PrismaClient();
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('🌱 Seeding database…');
|
|
13
|
+
|
|
14
|
+
// ── Admin user ─────────────────────────────────────────────────────────────
|
|
15
|
+
const adminPassword = await bcrypt.hash('Admin@1234', 10);
|
|
16
|
+
const admin = await prisma.user.upsert({
|
|
17
|
+
where: { email: 'admin@minhaloja.com.br' },
|
|
18
|
+
update: {},
|
|
19
|
+
create: {
|
|
20
|
+
name: 'Administrador',
|
|
21
|
+
email: 'admin@minhaloja.com.br',
|
|
22
|
+
passwordHash: adminPassword,
|
|
23
|
+
role: Role.ADMIN,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
console.log(` ✔ Admin: ${admin.email}`);
|
|
27
|
+
|
|
28
|
+
// ── Demo customer ──────────────────────────────────────────────────────────
|
|
29
|
+
const customerPassword = await bcrypt.hash('Cliente@1234', 10);
|
|
30
|
+
const customer = await prisma.user.upsert({
|
|
31
|
+
where: { email: 'cliente@exemplo.com.br' },
|
|
32
|
+
update: {},
|
|
33
|
+
create: {
|
|
34
|
+
name: 'João Silva',
|
|
35
|
+
email: 'cliente@exemplo.com.br',
|
|
36
|
+
passwordHash: customerPassword,
|
|
37
|
+
role: Role.CUSTOMER,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
console.log(` ✔ Customer: ${customer.email}`);
|
|
41
|
+
|
|
42
|
+
// ── Categories ─────────────────────────────────────────────────────────────
|
|
43
|
+
const catCamisetas = await prisma.category.upsert({
|
|
44
|
+
where: { slug: 'camisetas' },
|
|
45
|
+
update: {},
|
|
46
|
+
create: { name: 'Camisetas', slug: 'camisetas', description: 'Camisetas e básicos' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const catCalcas = await prisma.category.upsert({
|
|
50
|
+
where: { slug: 'calcas' },
|
|
51
|
+
update: {},
|
|
52
|
+
create: { name: 'Calças', slug: 'calcas', description: 'Calças e bermudas' },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const catAcessorios = await prisma.category.upsert({
|
|
56
|
+
where: { slug: 'acessorios' },
|
|
57
|
+
update: {},
|
|
58
|
+
create: { name: 'Acessórios', slug: 'acessorios', description: 'Bolsas, cintos, bonés' },
|
|
59
|
+
});
|
|
60
|
+
console.log(` ✔ Categories: ${catCamisetas.name}, ${catCalcas.name}, ${catAcessorios.name}`);
|
|
61
|
+
|
|
62
|
+
// ── Products ───────────────────────────────────────────────────────────────
|
|
63
|
+
const products = [
|
|
64
|
+
{
|
|
65
|
+
name: 'Camiseta Básica Branca',
|
|
66
|
+
slug: 'camiseta-basica-branca',
|
|
67
|
+
description: 'Camiseta 100% algodão, corte regular, disponível em vários tamanhos.',
|
|
68
|
+
price: 49.9,
|
|
69
|
+
categoryId: catCamisetas.id,
|
|
70
|
+
images: ['https://placehold.co/600x600?text=Camiseta+Branca'],
|
|
71
|
+
variants: [
|
|
72
|
+
{ sku: 'CAM-BRA-P', size: 'P', color: 'Branco', stock: 20 },
|
|
73
|
+
{ sku: 'CAM-BRA-M', size: 'M', color: 'Branco', stock: 30 },
|
|
74
|
+
{ sku: 'CAM-BRA-G', size: 'G', color: 'Branco', stock: 25 },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Camiseta Básica Preta',
|
|
79
|
+
slug: 'camiseta-basica-preta',
|
|
80
|
+
description: 'Camiseta 100% algodão, corte regular.',
|
|
81
|
+
price: 49.9,
|
|
82
|
+
categoryId: catCamisetas.id,
|
|
83
|
+
images: ['https://placehold.co/600x600?text=Camiseta+Preta'],
|
|
84
|
+
variants: [
|
|
85
|
+
{ sku: 'CAM-PTO-P', size: 'P', color: 'Preto', stock: 15 },
|
|
86
|
+
{ sku: 'CAM-PTO-M', size: 'M', color: 'Preto', stock: 20 },
|
|
87
|
+
{ sku: 'CAM-PTO-G', size: 'G', color: 'Preto', stock: 18 },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'Calça Slim Jeans',
|
|
92
|
+
slug: 'calca-slim-jeans',
|
|
93
|
+
description: 'Calça slim fit em denim stretch, confortável para o dia a dia.',
|
|
94
|
+
price: 149.9,
|
|
95
|
+
categoryId: catCalcas.id,
|
|
96
|
+
images: ['https://placehold.co/600x600?text=Calça+Jeans'],
|
|
97
|
+
variants: [
|
|
98
|
+
{ sku: 'CAL-JEA-38', size: '38', color: 'Azul', stock: 10 },
|
|
99
|
+
{ sku: 'CAL-JEA-40', size: '40', color: 'Azul', stock: 12 },
|
|
100
|
+
{ sku: 'CAL-JEA-42', size: '42', color: 'Azul', stock: 8 },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'Boné Snapback',
|
|
105
|
+
slug: 'bone-snapback',
|
|
106
|
+
description: 'Boné aba reta com fecho snapback, tamanho único.',
|
|
107
|
+
price: 59.9,
|
|
108
|
+
categoryId: catAcessorios.id,
|
|
109
|
+
images: ['https://placehold.co/600x600?text=Boné'],
|
|
110
|
+
variants: [
|
|
111
|
+
{ sku: 'BON-BLK-UN', color: 'Preto', stock: 40 },
|
|
112
|
+
{ sku: 'BON-WHT-UN', color: 'Branco', stock: 35 },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
for (const p of products) {
|
|
118
|
+
const { variants, ...productData } = p;
|
|
119
|
+
const product = await prisma.product.upsert({
|
|
120
|
+
where: { slug: productData.slug },
|
|
121
|
+
update: {},
|
|
122
|
+
create: {
|
|
123
|
+
...productData,
|
|
124
|
+
price: productData.price,
|
|
125
|
+
status: ProductStatus.ACTIVE,
|
|
126
|
+
variants: {
|
|
127
|
+
createMany: { data: variants, skipDuplicates: true },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
console.log(` ✔ Product: ${product.name}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Coupon ─────────────────────────────────────────────────────────────────
|
|
135
|
+
const coupon = await prisma.coupon.upsert({
|
|
136
|
+
where: { code: 'BEMVINDO10' },
|
|
137
|
+
update: {},
|
|
138
|
+
create: {
|
|
139
|
+
code: 'BEMVINDO10',
|
|
140
|
+
discountType: 'percent',
|
|
141
|
+
discountValue: 10,
|
|
142
|
+
minOrderValue: 50,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
console.log(` ✔ Coupon: ${coupon.code} (${coupon.discountValue}% off)`);
|
|
146
|
+
|
|
147
|
+
console.log('\n✅ Seed concluído!');
|
|
148
|
+
console.log('\nCredenciais para teste:');
|
|
149
|
+
console.log(' Admin → admin@minhaloja.com.br / Admin@1234');
|
|
150
|
+
console.log(' Cliente → cliente@exemplo.com.br / Cliente@1234');
|
|
151
|
+
console.log(' Cupom → BEMVINDO10 (10% off em pedidos acima de R$50)');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main()
|
|
155
|
+
.catch((e) => {
|
|
156
|
+
console.error(e);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
})
|
|
159
|
+
.finally(() => prisma.$disconnect());
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import request from 'supertest';
|
|
2
|
+
import app from '../app';
|
|
3
|
+
|
|
4
|
+
describe('App — Health & Error Handling', () => {
|
|
5
|
+
describe('GET /health', () => {
|
|
6
|
+
it('deve retornar 200 com { status: "ok" }', async () => {
|
|
7
|
+
const res = await request(app).get('/health');
|
|
8
|
+
|
|
9
|
+
expect(res.status).toBe(200);
|
|
10
|
+
expect(res.body).toEqual({ status: 'ok' });
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('Rota inexistente', () => {
|
|
15
|
+
it('deve retornar 404 com shape { error: string }', async () => {
|
|
16
|
+
const res = await request(app).get('/rota-que-nao-existe');
|
|
17
|
+
|
|
18
|
+
expect(res.status).toBe(404);
|
|
19
|
+
expect(res.body).toHaveProperty('error');
|
|
20
|
+
expect(typeof res.body.error).toBe('string');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('Error Handler Global', () => {
|
|
25
|
+
it('deve retornar 500 sem vazar stack trace em producao', async () => {
|
|
26
|
+
const originalEnv = process.env['NODE_ENV'];
|
|
27
|
+
process.env['NODE_ENV'] = 'production';
|
|
28
|
+
|
|
29
|
+
// /test-error lança um Error propositalmente (rota de teste)
|
|
30
|
+
const res = await request(app).get('/test-error');
|
|
31
|
+
|
|
32
|
+
expect(res.status).toBe(500);
|
|
33
|
+
expect(res.body).toHaveProperty('error');
|
|
34
|
+
expect(res.body.stack).toBeUndefined();
|
|
35
|
+
|
|
36
|
+
process.env['NODE_ENV'] = originalEnv;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs before all integration test workers.
|
|
6
|
+
* - Ensures the test database exists
|
|
7
|
+
* - Applies all pending Prisma migrations against TEST_DATABASE_URL
|
|
8
|
+
*/
|
|
9
|
+
export default async function globalSetup(): Promise<void> {
|
|
10
|
+
const testDbUrl =
|
|
11
|
+
process.env['TEST_DATABASE_URL'] ??
|
|
12
|
+
'postgresql://ecommerce:ecommerce@localhost:5435/ecommerce_test';
|
|
13
|
+
|
|
14
|
+
// __dirname = apps/api/src/__tests__ → two levels up = apps/api
|
|
15
|
+
const apiRoot = path.resolve(__dirname, '../..');
|
|
16
|
+
|
|
17
|
+
// Create test database if it doesn't exist (via docker exec)
|
|
18
|
+
try {
|
|
19
|
+
execSync(
|
|
20
|
+
`docker exec ecommerce_postgres createdb -U ecommerce ecommerce_test`,
|
|
21
|
+
{ stdio: 'pipe', timeout: 10_000 },
|
|
22
|
+
);
|
|
23
|
+
} catch {
|
|
24
|
+
// Likely "already exists" — not an error
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Apply migrations to the test database
|
|
28
|
+
execSync('npx prisma migrate deploy', {
|
|
29
|
+
cwd: apiRoot,
|
|
30
|
+
env: { ...process.env, DATABASE_URL: testDbUrl },
|
|
31
|
+
stdio: 'pipe',
|
|
32
|
+
timeout: 60_000,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global teardown for integration tests.
|
|
3
|
+
* Runs once after all test suites complete in the integration config.
|
|
4
|
+
* Closes the Redis connection to allow the process to exit cleanly.
|
|
5
|
+
*/
|
|
6
|
+
import Redis from 'ioredis';
|
|
7
|
+
|
|
8
|
+
export default async function globalTeardown(): Promise<void> {
|
|
9
|
+
const redisUrl = process.env['REDIS_URL'] ?? 'redis://localhost:6379';
|
|
10
|
+
const client = new Redis(redisUrl, { lazyConnect: false, maxRetriesPerRequest: 1 });
|
|
11
|
+
try {
|
|
12
|
+
await client.quit();
|
|
13
|
+
} catch {
|
|
14
|
+
// ignore — redis may already be closed
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs inside each integration test worker process (via `setupFiles`).
|
|
3
|
+
* Sets DATABASE_URL to the test database before any module is imported.
|
|
4
|
+
*/
|
|
5
|
+
process.env['JWT_SECRET'] =
|
|
6
|
+
process.env['JWT_SECRET'] ?? 'test-jwt-secret-min-32-chars-long!';
|
|
7
|
+
process.env['JWT_REFRESH_SECRET'] =
|
|
8
|
+
process.env['JWT_REFRESH_SECRET'] ?? 'test-refresh-secret-min-32-chars-long!';
|
|
9
|
+
process.env['NODE_ENV'] = 'test';
|
|
10
|
+
|
|
11
|
+
// Point PrismaClient to the test database
|
|
12
|
+
const testDbUrl =
|
|
13
|
+
process.env['TEST_DATABASE_URL'] ??
|
|
14
|
+
'postgresql://ecommerce:ecommerce@localhost:5435/ecommerce_test';
|
|
15
|
+
process.env['DATABASE_URL'] = testDbUrl;
|
|
16
|
+
|
|
17
|
+
// Point Redis client to the test Redis instance
|
|
18
|
+
process.env['REDIS_URL'] = process.env['REDIS_URL'] ?? 'redis://localhost:6379';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment setup for API tests.
|
|
3
|
+
* Runs before each test file in the worker process via jest `setupFiles`.
|
|
4
|
+
* This ensures env vars are available before any module (including singletons
|
|
5
|
+
* in auth.registry.ts) is first imported.
|
|
6
|
+
*
|
|
7
|
+
* Forces NODE_ENV=test-inmemory so the registry always uses InMemoryUserRepository
|
|
8
|
+
* and InMemoryTokenBlacklist, regardless of DATABASE_URL or REDIS_URL present in
|
|
9
|
+
* the developer's .env file. DB integration tests use their own setup (setup.db.ts).
|
|
10
|
+
*/
|
|
11
|
+
process.env['JWT_SECRET'] = process.env['JWT_SECRET'] ?? 'test-jwt-secret-min-32-chars-long!';
|
|
12
|
+
process.env['JWT_REFRESH_SECRET'] =
|
|
13
|
+
process.env['JWT_REFRESH_SECRET'] ?? 'test-refresh-secret-min-32-chars-long!';
|
|
14
|
+
process.env['NODE_ENV'] = 'test-inmemory';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import cookieParser from 'cookie-parser';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import rateLimit from 'express-rate-limit';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { errorHandler } from './shared/middlewares/errorHandler';
|
|
8
|
+
import { AppError } from './shared/errors/AppError';
|
|
9
|
+
import authRoutes from './modules/auth/auth.routes';
|
|
10
|
+
import catalogRoutes, { categoryRouter } from './modules/catalog/catalog.routes';
|
|
11
|
+
import cartRoutes from './modules/cart/cart.routes';
|
|
12
|
+
import checkoutRoutes from './modules/checkout/checkout.routes';
|
|
13
|
+
import { orderRoutes, adminOrderRoutes } from './modules/orders/order.routes';
|
|
14
|
+
import { adminRoutes } from './modules/admin/admin.routes';
|
|
15
|
+
import profileRoutes from './modules/profile/profile.routes';
|
|
16
|
+
import { prisma } from './shared/infra/prisma';
|
|
17
|
+
import { redis } from './shared/infra/redis';
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
|
|
21
|
+
// ── Trust proxy (required when running behind a reverse proxy / load balancer)
|
|
22
|
+
// Rate limiting uses the real client IP from X-Forwarded-For instead of the
|
|
23
|
+
// proxy IP. Set to the number of trusted proxy hops (1 = one hop, e.g. nginx).
|
|
24
|
+
if (process.env['NODE_ENV'] === 'production') {
|
|
25
|
+
app.set('trust proxy', 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Stripe webhook MUST receive the raw body for signature verification.
|
|
29
|
+
// Register express.raw() for this path BEFORE express.json() runs globally,
|
|
30
|
+
// otherwise express.json() will parse the body first and the raw bytes are lost.
|
|
31
|
+
app.use('/api/checkout/webhook', express.raw({ type: 'application/json' }));
|
|
32
|
+
|
|
33
|
+
// ── Security headers ─────────────────────────────────────────────────────────
|
|
34
|
+
app.use(helmet());
|
|
35
|
+
|
|
36
|
+
// ── Core middlewares ─────────────────────────────────────────────────────────
|
|
37
|
+
app.use(express.json());
|
|
38
|
+
app.use(express.urlencoded({ extended: true }));
|
|
39
|
+
app.use(
|
|
40
|
+
cors({
|
|
41
|
+
origin: process.env['CORS_ORIGIN'] || 'http://localhost:5173',
|
|
42
|
+
credentials: true,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
app.use(cookieParser());
|
|
46
|
+
|
|
47
|
+
// ── Static file serving (product images) ────────────────────────────────────
|
|
48
|
+
// Serves LocalDiskStorageService uploads from apps/api/public/
|
|
49
|
+
app.use('/public', express.static(path.join(__dirname, '../public')));
|
|
50
|
+
|
|
51
|
+
// ── Rate limiting (disabled in test environment) ────────────────────────────
|
|
52
|
+
if (process.env['NODE_ENV'] !== 'test' && process.env['NODE_ENV'] !== 'test-inmemory') {
|
|
53
|
+
const makeLimiter = (max: number, windowMs = 60_000) =>
|
|
54
|
+
rateLimit({
|
|
55
|
+
windowMs,
|
|
56
|
+
max,
|
|
57
|
+
standardHeaders: true,
|
|
58
|
+
legacyHeaders: false,
|
|
59
|
+
message: { error: 'Too many requests, please try again later.' },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Auth: strict — brute-force protection
|
|
63
|
+
const authLimiter = makeLimiter(20);
|
|
64
|
+
app.use('/api/auth/login', authLimiter);
|
|
65
|
+
app.use('/api/auth/register', authLimiter);
|
|
66
|
+
app.use('/api/auth/forgot-password', authLimiter);
|
|
67
|
+
app.use('/api/auth/reset-password', authLimiter);
|
|
68
|
+
|
|
69
|
+
// Checkout: 10 requests/min — prevents order spam and payment intent abuse
|
|
70
|
+
app.use('/api/checkout', makeLimiter(10));
|
|
71
|
+
|
|
72
|
+
// Orders: 60 requests/min — prevents enumeration scraping
|
|
73
|
+
app.use('/api/orders', makeLimiter(60));
|
|
74
|
+
app.use('/api/admin/orders', makeLimiter(60));
|
|
75
|
+
|
|
76
|
+
// Admin: 120 requests/min — still generous for legitimate use
|
|
77
|
+
app.use('/api/admin', makeLimiter(120));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Health check (liveness) ──────────────────────────────────────────────────
|
|
81
|
+
app.get('/health', (_req: Request, res: Response) => {
|
|
82
|
+
res.status(200).json({ status: 'ok' });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── Readiness probe — checks DB + Redis ─────────────────────────────────────
|
|
86
|
+
app.get('/health/ready', async (_req: Request, res: Response) => {
|
|
87
|
+
const checks: Record<string, string> = {};
|
|
88
|
+
let ok = true;
|
|
89
|
+
try {
|
|
90
|
+
await prisma.$queryRaw`SELECT 1`;
|
|
91
|
+
checks['database'] = 'ok';
|
|
92
|
+
} catch {
|
|
93
|
+
checks['database'] = 'unreachable';
|
|
94
|
+
ok = false;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await redis.ping();
|
|
98
|
+
checks['redis'] = 'ok';
|
|
99
|
+
} catch {
|
|
100
|
+
checks['redis'] = 'unreachable';
|
|
101
|
+
ok = false;
|
|
102
|
+
}
|
|
103
|
+
res.status(ok ? 200 : 503).json({ status: ok ? 'ok' : 'degraded', checks });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── Test-only route (throws intentionally to exercise the error handler) ─────
|
|
107
|
+
// Available in 'test' and 'development' — never in production
|
|
108
|
+
if (process.env['NODE_ENV'] !== 'production') {
|
|
109
|
+
app.get('/test-error', (_req: Request, _res: Response, next: NextFunction) => {
|
|
110
|
+
next(new Error('Intentional test error'));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Feature routes ────────────────────────────────────────────────────────────
|
|
115
|
+
app.use('/api/auth', authRoutes); // Phase 1 ✅
|
|
116
|
+
app.use('/api/products', catalogRoutes); // Phase 2 ✅
|
|
117
|
+
app.use('/api/categories', categoryRouter); // Phase 2 ✅
|
|
118
|
+
app.use('/api/cart', cartRoutes); // Phase 3 ✅
|
|
119
|
+
app.use('/api/checkout', checkoutRoutes); // Phase 4 ✅
|
|
120
|
+
app.use('/api/orders', orderRoutes); // Phase 5 ✅
|
|
121
|
+
app.use('/api/admin/orders', adminOrderRoutes); // Phase 5 ✅
|
|
122
|
+
app.use('/api/admin', adminRoutes); // Phase 6 ✅
|
|
123
|
+
app.use('/api/me', profileRoutes); // Phase 16 ✅
|
|
124
|
+
|
|
125
|
+
// ── 404 handler ──────────────────────────────────────────────────────────────
|
|
126
|
+
app.use((_req: Request, _res: Response, next: NextFunction) => {
|
|
127
|
+
next(new AppError('Route not found', 404));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ── Global error handler ─────────────────────────────────────────────────────
|
|
131
|
+
app.use(errorHandler);
|
|
132
|
+
|
|
133
|
+
export default app;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IUserRepository } from '../../domain/auth/user.repository';
|
|
2
|
+
import type { UserRole } from '../../domain/auth/user.entity';
|
|
3
|
+
import { AppError } from '../../domain/shared/AppError';
|
|
4
|
+
|
|
5
|
+
export class AdminUserService {
|
|
6
|
+
constructor(private readonly userRepo: IUserRepository) {}
|
|
7
|
+
|
|
8
|
+
async listUsers(opts?: { role?: UserRole; cursor?: string; limit?: number }) {
|
|
9
|
+
return this.userRepo.findAll(opts);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async updateUserRole(
|
|
13
|
+
targetId: string,
|
|
14
|
+
newRole: UserRole,
|
|
15
|
+
requesterId: string,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
if (targetId === requesterId) {
|
|
18
|
+
throw new AppError('Não é possível alterar o próprio role', 403);
|
|
19
|
+
}
|
|
20
|
+
const user = await this.userRepo.findById(targetId);
|
|
21
|
+
if (!user) throw new AppError('Usuário não encontrado', 404);
|
|
22
|
+
await this.userRepo.update(user.withRole(newRole));
|
|
23
|
+
}
|
|
24
|
+
}
|