@digilogiclabs/create-saas-app 2.0.0 → 2.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/CHANGELOG.md +7 -0
- package/README.md +975 -891
- package/dist/.tsbuildinfo +1 -1
- package/dist/generators/template-generator.d.ts +11 -0
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +389 -18
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/index.js +1837 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/infrastructure/kubernetes/base/template/README.md +253 -0
- package/dist/templates/infrastructure/kubernetes/base/template/configmap.yaml +12 -0
- package/dist/templates/infrastructure/kubernetes/base/template/deployment.yaml +123 -0
- package/dist/templates/infrastructure/kubernetes/base/template/hpa.yaml +45 -0
- package/dist/templates/infrastructure/kubernetes/base/template/ingress.yaml +31 -0
- package/dist/templates/infrastructure/kubernetes/base/template/kustomization.yaml +25 -0
- package/dist/templates/infrastructure/kubernetes/base/template/namespace.yaml +8 -0
- package/dist/templates/infrastructure/kubernetes/base/template/networkpolicy.yaml +48 -0
- package/dist/templates/infrastructure/kubernetes/base/template/pdb.yaml +14 -0
- package/dist/templates/infrastructure/kubernetes/base/template/secret.yaml +17 -0
- package/dist/templates/infrastructure/kubernetes/base/template/service.yaml +19 -0
- package/dist/templates/infrastructure/kubernetes/base/template/serviceaccount.yaml +9 -0
- package/dist/templates/infrastructure/kubernetes/production/template/kustomization.yaml +92 -0
- package/dist/templates/infrastructure/terraform/aws/template/README.md +156 -0
- package/dist/templates/infrastructure/terraform/aws/template/main.tf +343 -0
- package/dist/templates/infrastructure/terraform/aws/template/outputs.tf +66 -0
- package/dist/templates/infrastructure/terraform/aws/template/terraform.tfvars.example +28 -0
- package/dist/templates/infrastructure/terraform/aws/template/variables.tf +110 -0
- package/dist/templates/infrastructure/terraform/gcp/template/README.md +165 -0
- package/dist/templates/infrastructure/terraform/gcp/template/main.tf +397 -0
- package/dist/templates/infrastructure/terraform/gcp/template/outputs.tf +51 -0
- package/dist/templates/infrastructure/terraform/gcp/template/terraform.tfvars.example +29 -0
- package/dist/templates/infrastructure/terraform/gcp/template/variables.tf +115 -0
- package/dist/templates/shared/admin/web/app/admin/layout.tsx +34 -0
- package/dist/templates/shared/admin/web/components/admin-nav.tsx +48 -0
- package/dist/templates/shared/audit/web/lib/audit.ts +24 -0
- package/dist/templates/shared/auth/keycloak/web/app/api/auth/federated-logout/route.ts +173 -0
- package/dist/templates/shared/auth/keycloak/web/auth.config.ts +84 -0
- package/dist/templates/shared/auth/keycloak/web/auth.ts +26 -0
- package/dist/templates/shared/beta/web/app/api/beta-settings/route.ts +25 -0
- package/dist/templates/shared/beta/web/app/api/validate-beta-code/route.ts +67 -0
- package/dist/templates/shared/beta/web/lib/beta/settings.ts +31 -0
- package/dist/templates/shared/cache/web/lib/cache.ts +44 -0
- package/dist/templates/shared/config/web/lib/config.ts +112 -0
- package/dist/templates/shared/config/web/next.config.mjs +62 -0
- package/dist/templates/shared/contact/web/app/api/contact/route.ts +113 -0
- package/dist/templates/shared/contact/web/app/contact/page.tsx +195 -0
- package/dist/templates/shared/cookie-consent/web/components/cookie-consent.tsx +54 -0
- package/dist/templates/shared/database/postgresql/web/drizzle.config.ts +16 -0
- package/dist/templates/shared/database/postgresql/web/lib/db/drizzle.ts +39 -0
- package/dist/templates/shared/database/postgresql/web/lib/db/schema.ts +33 -0
- package/dist/templates/shared/database/supabase/web/lib/supabase/client.ts +12 -0
- package/dist/templates/shared/database/supabase/web/lib/supabase/server.ts +31 -0
- package/dist/templates/shared/database/supabase/web/lib/supabase/service.ts +15 -0
- package/dist/templates/shared/email/web/lib/email/branding.ts +18 -0
- package/dist/templates/shared/email/web/lib/email/client.ts +96 -0
- package/dist/templates/shared/error-pages/web/app/error.tsx +70 -0
- package/dist/templates/shared/error-pages/web/app/global-error.tsx +102 -0
- package/dist/templates/shared/error-pages/web/app/not-found.tsx +39 -0
- package/dist/templates/shared/health/web/app/api/health/route.ts +68 -0
- package/dist/templates/shared/legal/web/app/(legal)/privacy/page.tsx +205 -0
- package/dist/templates/shared/legal/web/app/(legal)/terms/page.tsx +154 -0
- package/dist/templates/shared/legal/web/lib/legal-config.ts +50 -0
- package/dist/templates/shared/loading/web/app/loading.tsx +5 -0
- package/dist/templates/shared/loading/web/components/skeleton.tsx +95 -0
- package/dist/templates/shared/middleware/web/middleware.ts +68 -0
- package/dist/templates/shared/observability/web/lib/observability.ts +135 -0
- package/dist/templates/shared/payments/web/app/api/webhooks/stripe/route.ts +109 -0
- package/dist/templates/shared/platform/web/lib/platform.ts +37 -0
- package/dist/templates/shared/redis/web/lib/rate-limit-store.ts +18 -0
- package/dist/templates/shared/redis/web/lib/redis.ts +48 -0
- package/dist/templates/shared/security/web/lib/api-security.ts +318 -0
- package/dist/templates/shared/seo/web/app/api/og/route.tsx +97 -0
- package/dist/templates/shared/seo/web/app/robots.ts +53 -0
- package/dist/templates/shared/seo/web/app/sitemap.ts +53 -0
- package/dist/templates/shared/utils/web/lib/api-response.ts +71 -0
- package/dist/templates/shared/utils/web/lib/utils.ts +85 -0
- package/dist/templates/web/ai-platform/template/.env.example +16 -0
- package/dist/templates/web/ai-platform/template/README.md +84 -0
- package/dist/templates/web/ai-platform/template/middleware.ts +55 -0
- package/dist/templates/web/ai-platform/template/next.config.js +14 -0
- package/dist/templates/web/ai-platform/template/package.json +55 -0
- package/dist/templates/web/ai-platform/template/src/app/api/chat/route.ts +54 -0
- package/dist/templates/web/ai-platform/template/src/app/chat/page.tsx +235 -0
- package/dist/templates/web/ai-platform/template/src/app/dashboard/page.tsx +142 -0
- package/dist/templates/web/ai-platform/template/src/app/globals.css +34 -0
- package/dist/templates/web/ai-platform/template/src/app/layout.tsx +27 -0
- package/dist/templates/web/ai-platform/template/src/app/page.tsx +203 -0
- package/dist/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +27 -0
- package/dist/templates/web/ai-platform/template/src/lib/auth-server.ts +33 -0
- package/dist/templates/web/ai-platform/template/src/lib/supabase/client.ts +8 -0
- package/dist/templates/web/ai-platform/template/src/lib/supabase/server.ts +27 -0
- package/dist/templates/web/ai-platform/template/src/lib/utils.ts +6 -0
- package/dist/templates/web/ai-platform/template/tsconfig.json +27 -0
- package/dist/templates/web/iot-dashboard/template/.env.example +12 -0
- package/dist/templates/web/iot-dashboard/template/README.md +101 -0
- package/dist/templates/web/iot-dashboard/template/middleware.ts +56 -0
- package/dist/templates/web/iot-dashboard/template/next.config.js +14 -0
- package/dist/templates/web/iot-dashboard/template/package.json +49 -0
- package/dist/templates/web/iot-dashboard/template/src/app/dashboard/page.tsx +229 -0
- package/dist/templates/web/iot-dashboard/template/src/app/globals.css +20 -0
- package/dist/templates/web/iot-dashboard/template/src/app/layout.tsx +27 -0
- package/dist/templates/web/iot-dashboard/template/src/app/page.tsx +191 -0
- package/dist/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +24 -0
- package/dist/templates/web/iot-dashboard/template/src/lib/auth-server.ts +33 -0
- package/dist/templates/web/iot-dashboard/template/src/lib/supabase/client.ts +8 -0
- package/dist/templates/web/iot-dashboard/template/src/lib/supabase/server.ts +27 -0
- package/dist/templates/web/iot-dashboard/template/src/lib/utils.ts +25 -0
- package/dist/templates/web/iot-dashboard/template/tsconfig.json +27 -0
- package/dist/templates/web/marketplace/template/.env.example +12 -0
- package/dist/templates/web/marketplace/template/README.md +66 -0
- package/dist/templates/web/marketplace/template/middleware.ts +56 -0
- package/dist/templates/web/marketplace/template/next.config.js +14 -0
- package/dist/templates/web/marketplace/template/package.json +51 -0
- package/dist/templates/web/marketplace/template/src/app/cart/page.tsx +147 -0
- package/dist/templates/web/marketplace/template/src/app/dashboard/page.tsx +149 -0
- package/dist/templates/web/marketplace/template/src/app/globals.css +20 -0
- package/dist/templates/web/marketplace/template/src/app/layout.tsx +27 -0
- package/dist/templates/web/marketplace/template/src/app/page.tsx +167 -0
- package/dist/templates/web/marketplace/template/src/app/products/page.tsx +129 -0
- package/dist/templates/web/marketplace/template/src/components/providers/app-providers.tsx +27 -0
- package/dist/templates/web/marketplace/template/src/lib/auth-server.ts +33 -0
- package/dist/templates/web/marketplace/template/src/lib/supabase/client.ts +8 -0
- package/dist/templates/web/marketplace/template/src/lib/supabase/server.ts +27 -0
- package/dist/templates/web/marketplace/template/src/lib/utils.ts +19 -0
- package/dist/templates/web/marketplace/template/tsconfig.json +27 -0
- package/dist/templates/web/micro-saas/template/.env.example +10 -0
- package/dist/templates/web/micro-saas/template/README.md +63 -0
- package/dist/templates/web/micro-saas/template/middleware.ts +53 -0
- package/dist/templates/web/micro-saas/template/next.config.js +14 -0
- package/dist/templates/web/micro-saas/template/package.json +41 -0
- package/dist/templates/web/micro-saas/template/src/app/dashboard/page.tsx +117 -0
- package/dist/templates/web/micro-saas/template/src/app/globals.css +20 -0
- package/dist/templates/web/micro-saas/template/src/app/layout.tsx +27 -0
- package/dist/templates/web/micro-saas/template/src/app/login/page.tsx +87 -0
- package/dist/templates/web/micro-saas/template/src/app/page.tsx +137 -0
- package/dist/templates/web/micro-saas/template/src/app/signup/page.tsx +108 -0
- package/dist/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +24 -0
- package/dist/templates/web/micro-saas/template/src/lib/auth-server.ts +33 -0
- package/dist/templates/web/micro-saas/template/src/lib/supabase/client.ts +8 -0
- package/dist/templates/web/micro-saas/template/src/lib/supabase/server.ts +29 -0
- package/dist/templates/web/micro-saas/template/src/lib/utils.ts +6 -0
- package/dist/templates/web/micro-saas/template/tsconfig.json +27 -0
- package/package.json +5 -4
- package/src/templates/infrastructure/kubernetes/base/template/README.md +253 -0
- package/src/templates/infrastructure/kubernetes/base/template/configmap.yaml +12 -0
- package/src/templates/infrastructure/kubernetes/base/template/deployment.yaml +123 -0
- package/src/templates/infrastructure/kubernetes/base/template/hpa.yaml +45 -0
- package/src/templates/infrastructure/kubernetes/base/template/ingress.yaml +31 -0
- package/src/templates/infrastructure/kubernetes/base/template/kustomization.yaml +25 -0
- package/src/templates/infrastructure/kubernetes/base/template/namespace.yaml +8 -0
- package/src/templates/infrastructure/kubernetes/base/template/networkpolicy.yaml +48 -0
- package/src/templates/infrastructure/kubernetes/base/template/pdb.yaml +14 -0
- package/src/templates/infrastructure/kubernetes/base/template/secret.yaml +17 -0
- package/src/templates/infrastructure/kubernetes/base/template/service.yaml +19 -0
- package/src/templates/infrastructure/kubernetes/base/template/serviceaccount.yaml +9 -0
- package/src/templates/infrastructure/kubernetes/production/template/kustomization.yaml +92 -0
- package/src/templates/infrastructure/terraform/aws/template/README.md +156 -0
- package/src/templates/infrastructure/terraform/aws/template/main.tf +343 -0
- package/src/templates/infrastructure/terraform/aws/template/outputs.tf +66 -0
- package/src/templates/infrastructure/terraform/aws/template/terraform.tfvars.example +28 -0
- package/src/templates/infrastructure/terraform/aws/template/variables.tf +110 -0
- package/src/templates/infrastructure/terraform/gcp/template/README.md +165 -0
- package/src/templates/infrastructure/terraform/gcp/template/main.tf +397 -0
- package/src/templates/infrastructure/terraform/gcp/template/outputs.tf +51 -0
- package/src/templates/infrastructure/terraform/gcp/template/terraform.tfvars.example +29 -0
- package/src/templates/infrastructure/terraform/gcp/template/variables.tf +115 -0
- package/src/templates/shared/admin/web/app/admin/layout.tsx +34 -0
- package/src/templates/shared/admin/web/components/admin-nav.tsx +48 -0
- package/src/templates/shared/audit/web/lib/audit.ts +24 -0
- package/src/templates/shared/auth/keycloak/web/app/api/auth/federated-logout/route.ts +173 -0
- package/src/templates/shared/auth/keycloak/web/auth.config.ts +84 -0
- package/src/templates/shared/auth/keycloak/web/auth.ts +26 -0
- package/src/templates/shared/beta/web/app/api/beta-settings/route.ts +25 -0
- package/src/templates/shared/beta/web/app/api/validate-beta-code/route.ts +67 -0
- package/src/templates/shared/beta/web/lib/beta/settings.ts +31 -0
- package/src/templates/shared/cache/web/lib/cache.ts +44 -0
- package/src/templates/shared/config/web/lib/config.ts +112 -0
- package/src/templates/shared/config/web/next.config.mjs +62 -0
- package/src/templates/shared/contact/web/app/api/contact/route.ts +113 -0
- package/src/templates/shared/contact/web/app/contact/page.tsx +195 -0
- package/src/templates/shared/cookie-consent/web/components/cookie-consent.tsx +54 -0
- package/src/templates/shared/database/postgresql/web/drizzle.config.ts +16 -0
- package/src/templates/shared/database/postgresql/web/lib/db/drizzle.ts +39 -0
- package/src/templates/shared/database/postgresql/web/lib/db/schema.ts +33 -0
- package/src/templates/shared/database/supabase/web/lib/supabase/client.ts +12 -0
- package/src/templates/shared/database/supabase/web/lib/supabase/server.ts +31 -0
- package/src/templates/shared/database/supabase/web/lib/supabase/service.ts +15 -0
- package/src/templates/shared/email/web/lib/email/branding.ts +18 -0
- package/src/templates/shared/email/web/lib/email/client.ts +96 -0
- package/src/templates/shared/error-pages/web/app/error.tsx +70 -0
- package/src/templates/shared/error-pages/web/app/global-error.tsx +102 -0
- package/src/templates/shared/error-pages/web/app/not-found.tsx +39 -0
- package/src/templates/shared/health/web/app/api/health/route.ts +68 -0
- package/src/templates/shared/legal/web/app/(legal)/privacy/page.tsx +205 -0
- package/src/templates/shared/legal/web/app/(legal)/terms/page.tsx +154 -0
- package/src/templates/shared/legal/web/lib/legal-config.ts +50 -0
- package/src/templates/shared/loading/web/app/loading.tsx +5 -0
- package/src/templates/shared/loading/web/components/skeleton.tsx +95 -0
- package/src/templates/shared/middleware/web/middleware.ts +68 -0
- package/src/templates/shared/observability/web/lib/observability.ts +135 -0
- package/src/templates/shared/payments/web/app/api/webhooks/stripe/route.ts +109 -0
- package/src/templates/shared/platform/web/lib/platform.ts +37 -0
- package/src/templates/shared/redis/web/lib/rate-limit-store.ts +18 -0
- package/src/templates/shared/redis/web/lib/redis.ts +48 -0
- package/src/templates/shared/security/web/lib/api-security.ts +318 -0
- package/src/templates/shared/seo/web/app/api/og/route.tsx +97 -0
- package/src/templates/shared/seo/web/app/robots.ts +53 -0
- package/src/templates/shared/seo/web/app/sitemap.ts +53 -0
- package/src/templates/shared/utils/web/lib/api-response.ts +71 -0
- package/src/templates/shared/utils/web/lib/utils.ts +85 -0
- package/src/templates/web/ai-platform/template/.env.example +16 -0
- package/src/templates/web/ai-platform/template/README.md +84 -0
- package/src/templates/web/ai-platform/template/middleware.ts +55 -0
- package/src/templates/web/ai-platform/template/next.config.js +14 -0
- package/src/templates/web/ai-platform/template/package.json +55 -0
- package/src/templates/web/ai-platform/template/src/app/api/chat/route.ts +54 -0
- package/src/templates/web/ai-platform/template/src/app/chat/page.tsx +235 -0
- package/src/templates/web/ai-platform/template/src/app/dashboard/page.tsx +142 -0
- package/src/templates/web/ai-platform/template/src/app/globals.css +34 -0
- package/src/templates/web/ai-platform/template/src/app/layout.tsx +27 -0
- package/src/templates/web/ai-platform/template/src/app/page.tsx +203 -0
- package/src/templates/web/ai-platform/template/src/components/providers/app-providers.tsx +27 -0
- package/src/templates/web/ai-platform/template/src/lib/auth-server.ts +33 -0
- package/src/templates/web/ai-platform/template/src/lib/supabase/client.ts +8 -0
- package/src/templates/web/ai-platform/template/src/lib/supabase/server.ts +27 -0
- package/src/templates/web/ai-platform/template/src/lib/utils.ts +6 -0
- package/src/templates/web/ai-platform/template/tsconfig.json +27 -0
- package/src/templates/web/iot-dashboard/template/.env.example +12 -0
- package/src/templates/web/iot-dashboard/template/README.md +101 -0
- package/src/templates/web/iot-dashboard/template/middleware.ts +56 -0
- package/src/templates/web/iot-dashboard/template/next.config.js +14 -0
- package/src/templates/web/iot-dashboard/template/package.json +49 -0
- package/src/templates/web/iot-dashboard/template/src/app/dashboard/page.tsx +229 -0
- package/src/templates/web/iot-dashboard/template/src/app/globals.css +20 -0
- package/src/templates/web/iot-dashboard/template/src/app/layout.tsx +27 -0
- package/src/templates/web/iot-dashboard/template/src/app/page.tsx +191 -0
- package/src/templates/web/iot-dashboard/template/src/components/providers/app-providers.tsx +24 -0
- package/src/templates/web/iot-dashboard/template/src/lib/auth-server.ts +33 -0
- package/src/templates/web/iot-dashboard/template/src/lib/supabase/client.ts +8 -0
- package/src/templates/web/iot-dashboard/template/src/lib/supabase/server.ts +27 -0
- package/src/templates/web/iot-dashboard/template/src/lib/utils.ts +25 -0
- package/src/templates/web/iot-dashboard/template/tsconfig.json +27 -0
- package/src/templates/web/marketplace/template/.env.example +12 -0
- package/src/templates/web/marketplace/template/README.md +66 -0
- package/src/templates/web/marketplace/template/middleware.ts +56 -0
- package/src/templates/web/marketplace/template/next.config.js +14 -0
- package/src/templates/web/marketplace/template/package.json +51 -0
- package/src/templates/web/marketplace/template/src/app/cart/page.tsx +147 -0
- package/src/templates/web/marketplace/template/src/app/dashboard/page.tsx +149 -0
- package/src/templates/web/marketplace/template/src/app/globals.css +20 -0
- package/src/templates/web/marketplace/template/src/app/layout.tsx +27 -0
- package/src/templates/web/marketplace/template/src/app/page.tsx +167 -0
- package/src/templates/web/marketplace/template/src/app/products/page.tsx +129 -0
- package/src/templates/web/marketplace/template/src/components/providers/app-providers.tsx +27 -0
- package/src/templates/web/marketplace/template/src/lib/auth-server.ts +33 -0
- package/src/templates/web/marketplace/template/src/lib/supabase/client.ts +8 -0
- package/src/templates/web/marketplace/template/src/lib/supabase/server.ts +27 -0
- package/src/templates/web/marketplace/template/src/lib/utils.ts +19 -0
- package/src/templates/web/marketplace/template/tsconfig.json +27 -0
- package/src/templates/web/micro-saas/template/.env.example +10 -0
- package/src/templates/web/micro-saas/template/README.md +63 -0
- package/src/templates/web/micro-saas/template/middleware.ts +53 -0
- package/src/templates/web/micro-saas/template/next.config.js +14 -0
- package/src/templates/web/micro-saas/template/package.json +41 -0
- package/src/templates/web/micro-saas/template/src/app/dashboard/page.tsx +117 -0
- package/src/templates/web/micro-saas/template/src/app/globals.css +20 -0
- package/src/templates/web/micro-saas/template/src/app/layout.tsx +27 -0
- package/src/templates/web/micro-saas/template/src/app/login/page.tsx +87 -0
- package/src/templates/web/micro-saas/template/src/app/page.tsx +137 -0
- package/src/templates/web/micro-saas/template/src/app/signup/page.tsx +108 -0
- package/src/templates/web/micro-saas/template/src/components/providers/app-providers.tsx +24 -0
- package/src/templates/web/micro-saas/template/src/lib/auth-server.ts +33 -0
- package/src/templates/web/micro-saas/template/src/lib/supabase/client.ts +8 -0
- package/src/templates/web/micro-saas/template/src/lib/supabase/server.ts +29 -0
- package/src/templates/web/micro-saas/template/src/lib/utils.ts +6 -0
- package/src/templates/web/micro-saas/template/tsconfig.json +27 -0
- package/dist/cli/commands/add.d.ts +0 -6
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -39
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/create.d.ts +0 -45
- package/dist/cli/commands/create.d.ts.map +0 -1
- package/dist/cli/commands/create.js +0 -175
- package/dist/cli/commands/create.js.map +0 -1
- package/dist/cli/commands/index.d.ts +0 -4
- package/dist/cli/commands/index.d.ts.map +0 -1
- package/dist/cli/commands/index.js +0 -20
- package/dist/cli/commands/index.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -6
- package/dist/cli/commands/update.d.ts.map +0 -1
- package/dist/cli/commands/update.js +0 -68
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -61
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/prompts/index.d.ts +0 -2
- package/dist/cli/prompts/index.d.ts.map +0 -1
- package/dist/cli/prompts/index.js +0 -18
- package/dist/cli/prompts/index.js.map +0 -1
- package/dist/cli/prompts/project-setup.d.ts +0 -5
- package/dist/cli/prompts/project-setup.d.ts.map +0 -1
- package/dist/cli/prompts/project-setup.js +0 -316
- package/dist/cli/prompts/project-setup.js.map +0 -1
- package/dist/cli/utils/git.d.ts +0 -9
- package/dist/cli/utils/git.d.ts.map +0 -1
- package/dist/cli/utils/git.js +0 -77
- package/dist/cli/utils/git.js.map +0 -1
- package/dist/cli/utils/index.d.ts +0 -5
- package/dist/cli/utils/index.d.ts.map +0 -1
- package/dist/cli/utils/index.js +0 -21
- package/dist/cli/utils/index.js.map +0 -1
- package/dist/cli/utils/logger.d.ts +0 -16
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/cli/utils/logger.js +0 -55
- package/dist/cli/utils/logger.js.map +0 -1
- package/dist/cli/utils/package-manager.d.ts +0 -8
- package/dist/cli/utils/package-manager.d.ts.map +0 -1
- package/dist/cli/utils/package-manager.js +0 -92
- package/dist/cli/utils/package-manager.js.map +0 -1
- package/dist/cli/utils/spinner.d.ts +0 -7
- package/dist/cli/utils/spinner.d.ts.map +0 -1
- package/dist/cli/utils/spinner.js +0 -48
- package/dist/cli/utils/spinner.js.map +0 -1
- package/dist/cli/validators/dependencies.d.ts +0 -15
- package/dist/cli/validators/dependencies.d.ts.map +0 -1
- package/dist/cli/validators/dependencies.js +0 -108
- package/dist/cli/validators/dependencies.js.map +0 -1
- package/dist/cli/validators/index.d.ts +0 -3
- package/dist/cli/validators/index.d.ts.map +0 -1
- package/dist/cli/validators/index.js +0 -19
- package/dist/cli/validators/index.js.map +0 -1
- package/dist/cli/validators/project-name.d.ts +0 -5
- package/dist/cli/validators/project-name.d.ts.map +0 -1
- package/dist/cli/validators/project-name.js +0 -151
- package/dist/cli/validators/project-name.js.map +0 -1
- package/dist/generators/file-processor.d.ts +0 -28
- package/dist/generators/file-processor.d.ts.map +0 -1
- package/dist/generators/file-processor.js +0 -224
- package/dist/generators/file-processor.js.map +0 -1
- package/dist/generators/index.d.ts +0 -4
- package/dist/generators/index.d.ts.map +0 -1
- package/dist/generators/index.js +0 -20
- package/dist/generators/index.js.map +0 -1
- package/dist/generators/package-installer.d.ts +0 -29
- package/dist/generators/package-installer.d.ts.map +0 -1
- package/dist/generators/package-installer.js +0 -177
- package/dist/generators/package-installer.js.map +0 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { getClientIp } from '@digilogiclabs/platform-core/auth';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Observability helpers — audit logging, error reporting, metrics.
|
|
5
|
+
*
|
|
6
|
+
* All functions are fire-and-forget (no await needed) and fail silently.
|
|
7
|
+
* Uses platform-core adapters when available, falls back to console.
|
|
8
|
+
*
|
|
9
|
+
* Customize the SERVICE_NAME and add app-specific audit actions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SERVICE_NAME = 'my-app';
|
|
13
|
+
|
|
14
|
+
// ─── Lazy Singletons ────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
let auditLogPromise: Promise<AuditLogger> | null = null;
|
|
17
|
+
|
|
18
|
+
interface AuditLogger {
|
|
19
|
+
log(event: AuditEvent): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AuditEvent {
|
|
23
|
+
action: string;
|
|
24
|
+
actor: { id: string; type: string; email?: string };
|
|
25
|
+
resource?: { type: string; id?: string };
|
|
26
|
+
outcome: 'success' | 'failure' | 'blocked';
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
category?: string;
|
|
29
|
+
ip?: string;
|
|
30
|
+
userAgent?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getAuditLog(): Promise<AuditLogger> {
|
|
34
|
+
if (!auditLogPromise) {
|
|
35
|
+
auditLogPromise = (async () => {
|
|
36
|
+
try {
|
|
37
|
+
const { getPlatform } = await import('@/lib/platform');
|
|
38
|
+
const platform = await getPlatform();
|
|
39
|
+
const { DatabaseAuditLog } = await import('@digilogiclabs/platform-core');
|
|
40
|
+
return new DatabaseAuditLog(platform.db, { serviceName: SERVICE_NAME });
|
|
41
|
+
} catch {
|
|
42
|
+
return {
|
|
43
|
+
log: async (event: AuditEvent) => {
|
|
44
|
+
console.log(`[Audit] ${event.action}`, JSON.stringify(event));
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
}
|
|
50
|
+
return auditLogPromise;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Public Helpers ──────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Get user agent string from request */
|
|
56
|
+
export function getUserAgent(request: Request): string {
|
|
57
|
+
return request.headers.get('user-agent') || 'unknown';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Re-export getClientIp from platform-core
|
|
61
|
+
export { getClientIp } from '@digilogiclabs/platform-core/auth';
|
|
62
|
+
|
|
63
|
+
/** Log an audit event (fire-and-forget) */
|
|
64
|
+
export function auditAction(params: {
|
|
65
|
+
action: string;
|
|
66
|
+
actorId: string;
|
|
67
|
+
actorEmail?: string;
|
|
68
|
+
resourceType?: string;
|
|
69
|
+
resourceId?: string;
|
|
70
|
+
outcome?: 'success' | 'failure' | 'blocked';
|
|
71
|
+
metadata?: Record<string, unknown>;
|
|
72
|
+
request?: Request;
|
|
73
|
+
}): void {
|
|
74
|
+
const category = inferCategory(params.action);
|
|
75
|
+
getAuditLog()
|
|
76
|
+
.then((log) =>
|
|
77
|
+
log.log({
|
|
78
|
+
action: params.action,
|
|
79
|
+
actor: {
|
|
80
|
+
id: params.actorId,
|
|
81
|
+
type: params.actorId === 'system' ? 'system' : 'user',
|
|
82
|
+
email: params.actorEmail,
|
|
83
|
+
},
|
|
84
|
+
resource: params.resourceType
|
|
85
|
+
? { type: params.resourceType, id: params.resourceId }
|
|
86
|
+
: undefined,
|
|
87
|
+
outcome: params.outcome || 'success',
|
|
88
|
+
metadata: params.metadata,
|
|
89
|
+
category,
|
|
90
|
+
ip: params.request ? getClientIp(params.request) : undefined,
|
|
91
|
+
userAgent: params.request ? getUserAgent(params.request) : undefined,
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
.catch(() => {});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Log an admin action */
|
|
98
|
+
export function auditAdminAction(
|
|
99
|
+
params: Omit<Parameters<typeof auditAction>[0], 'action'> & { action: string }
|
|
100
|
+
): void {
|
|
101
|
+
auditAction({ ...params, action: `admin.${params.action}` });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Log a cron job action */
|
|
105
|
+
export function auditCronAction(
|
|
106
|
+
params: Omit<Parameters<typeof auditAction>[0], 'actorId'> & { action: string }
|
|
107
|
+
): void {
|
|
108
|
+
auditAction({ ...params, actorId: 'system', action: `cron.${params.action}` });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Capture an error (fire-and-forget) */
|
|
112
|
+
export function captureError(error: unknown, context?: Record<string, unknown>): void {
|
|
113
|
+
try {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
116
|
+
console.error(`[${SERVICE_NAME}] Error:`, message, context || '');
|
|
117
|
+
if (stack && process.env.NODE_ENV === 'development') {
|
|
118
|
+
console.error(stack);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// Silent fail — observability must never crash the app
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Helpers ─────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function inferCategory(action: string): string {
|
|
128
|
+
if (action.startsWith('admin.')) return 'admin';
|
|
129
|
+
if (action.startsWith('payment.')) return 'billing';
|
|
130
|
+
if (action.startsWith('account.')) return 'data_mutation';
|
|
131
|
+
if (action.startsWith('auth.')) return 'authentication';
|
|
132
|
+
if (action.startsWith('cron.')) return 'system';
|
|
133
|
+
if (action.startsWith('security.')) return 'security';
|
|
134
|
+
return 'general';
|
|
135
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stripe Webhook Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles Stripe webhook events for subscription lifecycle management.
|
|
5
|
+
* Uses HMAC signature verification and Redis-based idempotency.
|
|
6
|
+
*
|
|
7
|
+
* Required env vars:
|
|
8
|
+
* STRIPE_SECRET_KEY - Stripe API key
|
|
9
|
+
* STRIPE_WEBHOOK_SECRET - Webhook signing secret (whsec_...)
|
|
10
|
+
*/
|
|
11
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
12
|
+
import Stripe from 'stripe';
|
|
13
|
+
import { getRedisClient } from '@/lib/redis';
|
|
14
|
+
import { enforceRateLimit, AppRateLimits } from '@/lib/api-security';
|
|
15
|
+
|
|
16
|
+
export const dynamic = 'force-dynamic';
|
|
17
|
+
|
|
18
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
19
|
+
apiVersion: '2024-12-18.acacia',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if this webhook event has already been processed (idempotency).
|
|
24
|
+
* Uses Redis SET NX + EX pattern — atomic check-and-set with 24h TTL.
|
|
25
|
+
* Returns true if already processed, false if new.
|
|
26
|
+
*/
|
|
27
|
+
async function isEventProcessed(eventId: string): Promise<boolean> {
|
|
28
|
+
const redis = getRedisClient();
|
|
29
|
+
if (!redis) return false; // No Redis — allow processing (best-effort)
|
|
30
|
+
try {
|
|
31
|
+
const result = await redis.set(`webhook:${eventId}`, '1', 'EX', 86400, 'NX');
|
|
32
|
+
return result === null; // null = key already existed = already processed
|
|
33
|
+
} catch {
|
|
34
|
+
return false; // Redis error — allow processing
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function POST(request: NextRequest) {
|
|
39
|
+
// Rate limit webhook calls (generous — Stripe retries on failure)
|
|
40
|
+
const rateLimited = await enforceRateLimit(request, 'stripe-webhook', AppRateLimits.webhook);
|
|
41
|
+
if (rateLimited) return rateLimited;
|
|
42
|
+
|
|
43
|
+
const body = await request.text();
|
|
44
|
+
const signature = request.headers.get('stripe-signature');
|
|
45
|
+
|
|
46
|
+
if (!signature) {
|
|
47
|
+
return NextResponse.json({ error: 'Missing signature' }, { status: 400 });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let event: Stripe.Event;
|
|
51
|
+
try {
|
|
52
|
+
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('[Stripe Webhook] Signature verification failed:', err);
|
|
55
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Idempotency check — prevent duplicate processing
|
|
59
|
+
if (await isEventProcessed(event.id)) {
|
|
60
|
+
return NextResponse.json({ received: true, duplicate: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
switch (event.type) {
|
|
65
|
+
case 'checkout.session.completed': {
|
|
66
|
+
const session = event.data.object as Stripe.Checkout.Session;
|
|
67
|
+
console.log('[Stripe Webhook] Checkout completed:', session.id);
|
|
68
|
+
// TODO: Activate subscription, provision access, send welcome email
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'invoice.paid': {
|
|
73
|
+
const invoice = event.data.object as Stripe.Invoice;
|
|
74
|
+
console.log('[Stripe Webhook] Invoice paid:', invoice.id);
|
|
75
|
+
// TODO: Record payment, extend subscription
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'invoice.payment_failed': {
|
|
80
|
+
const invoice = event.data.object as Stripe.Invoice;
|
|
81
|
+
console.log('[Stripe Webhook] Payment failed:', invoice.id);
|
|
82
|
+
// TODO: Notify user, start grace period
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'customer.subscription.updated': {
|
|
87
|
+
const subscription = event.data.object as Stripe.Subscription;
|
|
88
|
+
console.log('[Stripe Webhook] Subscription updated:', subscription.id);
|
|
89
|
+
// TODO: Handle plan changes, cancellation scheduling
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'customer.subscription.deleted': {
|
|
94
|
+
const subscription = event.data.object as Stripe.Subscription;
|
|
95
|
+
console.log('[Stripe Webhook] Subscription deleted:', subscription.id);
|
|
96
|
+
// TODO: Revoke access, send cancellation email
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
default:
|
|
101
|
+
console.log(`[Stripe Webhook] Unhandled event type: ${event.type}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({ received: true });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('[Stripe Webhook] Handler error:', error);
|
|
107
|
+
return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-Core Singleton
|
|
3
|
+
*
|
|
4
|
+
* Lazy-initialized platform instance with adapter auto-selection from env vars.
|
|
5
|
+
* Uses a promise-based lock to prevent race conditions during initialization.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { getPlatform } from '@/lib/platform';
|
|
9
|
+
* const platform = await getPlatform();
|
|
10
|
+
* await platform.cache.set('key', value, 3600);
|
|
11
|
+
*/
|
|
12
|
+
import 'server-only';
|
|
13
|
+
import { createPlatformAsync, type IPlatform } from '@digilogiclabs/platform-core';
|
|
14
|
+
|
|
15
|
+
let _platform: IPlatform | null = null;
|
|
16
|
+
let _initPromise: Promise<IPlatform> | null = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get or initialize the platform singleton.
|
|
20
|
+
* Adapters are selected automatically based on environment variables:
|
|
21
|
+
* - DATABASE_URL → PostgreSQL adapter
|
|
22
|
+
* - REDIS_URL → Redis cache adapter
|
|
23
|
+
* - RESEND_API_KEY → Resend email adapter
|
|
24
|
+
* Falls back to memory adapters when env vars are not set.
|
|
25
|
+
*/
|
|
26
|
+
export async function getPlatform(): Promise<IPlatform> {
|
|
27
|
+
if (_platform) return _platform;
|
|
28
|
+
|
|
29
|
+
if (!_initPromise) {
|
|
30
|
+
_initPromise = createPlatformAsync().then((p) => {
|
|
31
|
+
_platform = p;
|
|
32
|
+
return p;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return _initPromise;
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { createRedisRateLimitStore } from '@digilogiclabs/platform-core/auth';
|
|
3
|
+
import type { RateLimitStore } from '@digilogiclabs/platform-core/auth';
|
|
4
|
+
import { getRedisClient } from './redis';
|
|
5
|
+
|
|
6
|
+
let _store: RateLimitStore | null = null;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns a Redis-backed rate limit store, or undefined to fall back to in-memory.
|
|
10
|
+
* The store is created lazily on first call and reused thereafter.
|
|
11
|
+
*/
|
|
12
|
+
export function getRateLimitStore(): RateLimitStore | undefined {
|
|
13
|
+
if (_store) return _store;
|
|
14
|
+
const redis = getRedisClient();
|
|
15
|
+
if (!redis) return undefined; // falls back to in-memory in enforceRateLimit
|
|
16
|
+
_store = createRedisRateLimitStore(redis, { keyPrefix: 'rl:' });
|
|
17
|
+
return _store;
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal Redis client interface matching ioredis.
|
|
5
|
+
* Avoids importing ioredis at type-check time (may not be installed yet).
|
|
6
|
+
*/
|
|
7
|
+
interface RedisClient {
|
|
8
|
+
zremrangebyscore(key: string, min: number | string, max: number | string): Promise<number>;
|
|
9
|
+
zadd(key: string, score: number, member: string): Promise<number>;
|
|
10
|
+
zcard(key: string): Promise<number>;
|
|
11
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
12
|
+
get(key: string): Promise<string | null>;
|
|
13
|
+
set(key: string, value: string, ...args: unknown[]): Promise<string | null>;
|
|
14
|
+
ttl(key: string): Promise<number>;
|
|
15
|
+
setex(key: string, seconds: number, value: string): Promise<string>;
|
|
16
|
+
del(...keys: string[]): Promise<number>;
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
disconnect(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let _client: RedisClient | null = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Lazy Redis client singleton.
|
|
25
|
+
* Returns null if REDIS_URL is not set or ioredis is not installed.
|
|
26
|
+
* Graceful degradation — rate limiting falls back to in-memory.
|
|
27
|
+
*/
|
|
28
|
+
export function getRedisClient(): RedisClient | null {
|
|
29
|
+
if (_client) return _client;
|
|
30
|
+
const url = process.env.REDIS_URL;
|
|
31
|
+
if (!url) return null;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Dynamic require — only loads if ioredis is installed
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const Redis = require('ioredis');
|
|
37
|
+
_client = new Redis(url, {
|
|
38
|
+
keyPrefix: process.env.REDIS_KEY_PREFIX || 'app:',
|
|
39
|
+
maxRetriesPerRequest: 3,
|
|
40
|
+
lazyConnect: true,
|
|
41
|
+
}) as RedisClient;
|
|
42
|
+
_client.connect().catch(() => {});
|
|
43
|
+
return _client;
|
|
44
|
+
} catch {
|
|
45
|
+
// ioredis not installed — graceful fallback
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared API security utilities.
|
|
3
|
+
*
|
|
4
|
+
* Built on platform-core/auth building blocks — no local reimplementations.
|
|
5
|
+
* Uses constantTimeEqual, classifyError, rate limiting, and audit from the package.
|
|
6
|
+
*
|
|
7
|
+
* Two usage patterns:
|
|
8
|
+
* 1. Wrappers: withPublicApi / withAuthenticatedApi / withAdminApi (recommended)
|
|
9
|
+
* 2. Primitives: enforceRateLimit, isAdminRequest, errorResponse (manual composition)
|
|
10
|
+
*/
|
|
11
|
+
import 'server-only';
|
|
12
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
13
|
+
import { randomUUID } from 'crypto';
|
|
14
|
+
import {
|
|
15
|
+
// Security primitives
|
|
16
|
+
constantTimeEqual,
|
|
17
|
+
// Rate limiting
|
|
18
|
+
CommonRateLimits,
|
|
19
|
+
type RateLimitRule,
|
|
20
|
+
type RateLimitOptions,
|
|
21
|
+
// Next.js API helpers
|
|
22
|
+
enforceRateLimit as _enforceRateLimit,
|
|
23
|
+
errorResponse,
|
|
24
|
+
zodErrorResponse,
|
|
25
|
+
classifyError,
|
|
26
|
+
} from '@digilogiclabs/platform-core/auth';
|
|
27
|
+
|
|
28
|
+
// Trigger env validation on first import (fail-fast in production)
|
|
29
|
+
import { config } from '@/lib/config';
|
|
30
|
+
|
|
31
|
+
// Re-export for convenience in routes
|
|
32
|
+
export { escapeHtml } from '@digilogiclabs/platform-core/auth';
|
|
33
|
+
export { errorResponse, zodErrorResponse };
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Request ID / Correlation ID
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** Generate or extract a request ID for correlation. */
|
|
40
|
+
export function getRequestId(request: NextRequest): string {
|
|
41
|
+
return request.headers.get('x-request-id') || randomUUID();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Rate-limit a request using Redis-backed store (if REDIS_URL is set)
|
|
46
|
+
* or in-memory fallback. Wraps platform-core's enforceRateLimit to
|
|
47
|
+
* automatically inject the store.
|
|
48
|
+
*/
|
|
49
|
+
export async function enforceRateLimit(
|
|
50
|
+
request: { headers: { get(name: string): string | null } },
|
|
51
|
+
operation: string,
|
|
52
|
+
rule: RateLimitRule,
|
|
53
|
+
options?: {
|
|
54
|
+
identifier?: string;
|
|
55
|
+
userId?: string;
|
|
56
|
+
rateLimitOptions?: RateLimitOptions;
|
|
57
|
+
}
|
|
58
|
+
): Promise<Response | null> {
|
|
59
|
+
const { getRateLimitStore } = await import('@/lib/rate-limit-store');
|
|
60
|
+
const store = getRateLimitStore();
|
|
61
|
+
return _enforceRateLimit(request, operation, rule, {
|
|
62
|
+
...options,
|
|
63
|
+
rateLimitOptions: { ...options?.rateLimitOptions, ...(store ? { store } : {}) },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Admin / Cron auth helpers
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/** Extract bearer token from Authorization header. */
|
|
72
|
+
function extractBearerToken(request: NextRequest): string | null {
|
|
73
|
+
const header = request.headers.get('authorization');
|
|
74
|
+
if (!header?.startsWith('Bearer ')) return null;
|
|
75
|
+
return header.slice(7);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Check if request has a valid admin bearer token (ADMIN_SECRET). Timing-safe. */
|
|
79
|
+
export function isAdminRequest(request: NextRequest): boolean {
|
|
80
|
+
const secret = config.adminSecret;
|
|
81
|
+
if (!secret) return false;
|
|
82
|
+
const token = extractBearerToken(request);
|
|
83
|
+
if (!token) return false;
|
|
84
|
+
return constantTimeEqual(token, secret);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Check if request has a valid cron bearer token (CRON_SECRET). Timing-safe. */
|
|
88
|
+
export function isCronRequest(request: NextRequest): boolean {
|
|
89
|
+
const secret = config.cronSecret;
|
|
90
|
+
if (!secret) return false;
|
|
91
|
+
const token = extractBearerToken(request);
|
|
92
|
+
if (!token) return false;
|
|
93
|
+
return constantTimeEqual(token, secret);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Rate limiting presets — tune for your app's traffic patterns
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/** App-specific rate limit presets. Extend or override as needed. */
|
|
101
|
+
export const AppRateLimits = {
|
|
102
|
+
/** Public read endpoints */
|
|
103
|
+
publicRead: { limit: 60, windowSeconds: 60 } satisfies RateLimitRule,
|
|
104
|
+
/** Authenticated mutations */
|
|
105
|
+
authMutation: { limit: 30, windowSeconds: 60 } satisfies RateLimitRule,
|
|
106
|
+
/** Admin endpoints */
|
|
107
|
+
admin: CommonRateLimits.adminAction,
|
|
108
|
+
/** Beta code validation */
|
|
109
|
+
betaValidation: CommonRateLimits.betaValidation,
|
|
110
|
+
/** Webhook endpoints (generous — Stripe retries) */
|
|
111
|
+
webhook: { limit: 100, windowSeconds: 60 } satisfies RateLimitRule,
|
|
112
|
+
} as const;
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// API Wrapper Types
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
interface ApiWrapperConfig {
|
|
119
|
+
/** Rate limit rule (defaults to preset based on wrapper type) */
|
|
120
|
+
rateLimit?: RateLimitRule;
|
|
121
|
+
/** Operation name for rate limiting and logging */
|
|
122
|
+
operation?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface AuthenticatedApiContext {
|
|
126
|
+
/** The authenticated session */
|
|
127
|
+
session: { user: { id: string; email: string; roles?: string[] } };
|
|
128
|
+
/** Request correlation ID */
|
|
129
|
+
requestId: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface PublicApiContext {
|
|
133
|
+
/** Request correlation ID */
|
|
134
|
+
requestId: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type PublicApiHandler = (request: NextRequest, context: PublicApiContext) => Promise<NextResponse>;
|
|
138
|
+
|
|
139
|
+
type AuthenticatedApiHandler = (
|
|
140
|
+
request: NextRequest,
|
|
141
|
+
context: AuthenticatedApiContext
|
|
142
|
+
) => Promise<NextResponse>;
|
|
143
|
+
|
|
144
|
+
type AdminApiHandler = (request: NextRequest, context: PublicApiContext) => Promise<NextResponse>;
|
|
145
|
+
|
|
146
|
+
type CronApiHandler = (request: NextRequest, context: PublicApiContext) => Promise<NextResponse>;
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// API Wrappers — compose auth, rate limiting, error handling automatically
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Wrap a public API route with rate limiting and error handling.
|
|
154
|
+
* No authentication required.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* export const GET = withPublicApi({ operation: 'list-items' }, async (req, ctx) => {
|
|
158
|
+
* return NextResponse.json({ items: [] });
|
|
159
|
+
* });
|
|
160
|
+
*/
|
|
161
|
+
export function withPublicApi(
|
|
162
|
+
handlerConfig: ApiWrapperConfig,
|
|
163
|
+
handler: PublicApiHandler
|
|
164
|
+
): (request: NextRequest) => Promise<NextResponse> {
|
|
165
|
+
return async (request: NextRequest) => {
|
|
166
|
+
const requestId = getRequestId(request);
|
|
167
|
+
const operation = handlerConfig.operation || 'public';
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Rate limiting
|
|
171
|
+
const rateLimited = await enforceRateLimit(
|
|
172
|
+
request,
|
|
173
|
+
operation,
|
|
174
|
+
handlerConfig.rateLimit || AppRateLimits.publicRead
|
|
175
|
+
);
|
|
176
|
+
if (rateLimited) return rateLimited as NextResponse;
|
|
177
|
+
|
|
178
|
+
const response = await handler(request, { requestId });
|
|
179
|
+
response.headers.set('x-request-id', requestId);
|
|
180
|
+
return response;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const { status, body } = classifyError(error, process.env.NODE_ENV === 'development');
|
|
183
|
+
return NextResponse.json({ ...body, requestId }, { status });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Wrap an authenticated API route with session validation, rate limiting, and error handling.
|
|
190
|
+
* Requires a valid Auth.js session.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* export const POST = withAuthenticatedApi({ operation: 'create-item' }, async (req, ctx) => {
|
|
194
|
+
* const { session, requestId } = ctx;
|
|
195
|
+
* return NextResponse.json({ userId: session.user.id });
|
|
196
|
+
* });
|
|
197
|
+
*/
|
|
198
|
+
export function withAuthenticatedApi(
|
|
199
|
+
handlerConfig: ApiWrapperConfig,
|
|
200
|
+
handler: AuthenticatedApiHandler
|
|
201
|
+
): (request: NextRequest) => Promise<NextResponse> {
|
|
202
|
+
return async (request: NextRequest) => {
|
|
203
|
+
const requestId = getRequestId(request);
|
|
204
|
+
const operation = handlerConfig.operation || 'authenticated';
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Dynamic import to avoid Edge runtime issues
|
|
208
|
+
const { auth } = await import('@/auth');
|
|
209
|
+
const session = await auth();
|
|
210
|
+
|
|
211
|
+
if (!session?.user?.id) {
|
|
212
|
+
return NextResponse.json({ error: 'Unauthorized', requestId }, { status: 401 });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Rate limiting (per-user)
|
|
216
|
+
const rateLimited = await enforceRateLimit(
|
|
217
|
+
request,
|
|
218
|
+
operation,
|
|
219
|
+
handlerConfig.rateLimit || AppRateLimits.authMutation,
|
|
220
|
+
{ userId: session.user.id }
|
|
221
|
+
);
|
|
222
|
+
if (rateLimited) return rateLimited as NextResponse;
|
|
223
|
+
|
|
224
|
+
const response = await handler(request, {
|
|
225
|
+
session: session as AuthenticatedApiContext['session'],
|
|
226
|
+
requestId,
|
|
227
|
+
});
|
|
228
|
+
response.headers.set('x-request-id', requestId);
|
|
229
|
+
return response;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const { status, body } = classifyError(error, process.env.NODE_ENV === 'development');
|
|
232
|
+
return NextResponse.json({ ...body, requestId }, { status });
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Wrap an admin API route with Bearer token auth, rate limiting, and error handling.
|
|
239
|
+
* Requires ADMIN_SECRET Bearer token.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* export const POST = withAdminApi({ operation: 'admin-action' }, async (req, ctx) => {
|
|
243
|
+
* return NextResponse.json({ success: true });
|
|
244
|
+
* });
|
|
245
|
+
*/
|
|
246
|
+
export function withAdminApi(
|
|
247
|
+
handlerConfig: ApiWrapperConfig,
|
|
248
|
+
handler: AdminApiHandler
|
|
249
|
+
): (request: NextRequest) => Promise<NextResponse> {
|
|
250
|
+
return async (request: NextRequest) => {
|
|
251
|
+
const requestId = getRequestId(request);
|
|
252
|
+
const operation = handlerConfig.operation || 'admin';
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
if (!isAdminRequest(request)) {
|
|
256
|
+
return NextResponse.json({ error: 'Forbidden', requestId }, { status: 403 });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Rate limiting
|
|
260
|
+
const rateLimited = await enforceRateLimit(
|
|
261
|
+
request,
|
|
262
|
+
operation,
|
|
263
|
+
handlerConfig.rateLimit || AppRateLimits.admin
|
|
264
|
+
);
|
|
265
|
+
if (rateLimited) return rateLimited as NextResponse;
|
|
266
|
+
|
|
267
|
+
const response = await handler(request, { requestId });
|
|
268
|
+
response.headers.set('x-request-id', requestId);
|
|
269
|
+
return response;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const { status, body } = classifyError(error, process.env.NODE_ENV === 'development');
|
|
272
|
+
return NextResponse.json({ ...body, requestId }, { status });
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Wrap a cron/scheduled API route with CRON_SECRET Bearer token auth,
|
|
279
|
+
* falling back to admin session check. Uses generous rate limits since
|
|
280
|
+
* cron jobs are server-to-server.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* export const POST = withCronApi({ operation: 'daily-digest' }, async (req, ctx) => {
|
|
284
|
+
* // Run scheduled task...
|
|
285
|
+
* return NextResponse.json({ processed: 42 });
|
|
286
|
+
* });
|
|
287
|
+
*/
|
|
288
|
+
export function withCronApi(
|
|
289
|
+
handlerConfig: ApiWrapperConfig,
|
|
290
|
+
handler: CronApiHandler
|
|
291
|
+
): (request: NextRequest) => Promise<NextResponse> {
|
|
292
|
+
return async (request: NextRequest) => {
|
|
293
|
+
const requestId = getRequestId(request);
|
|
294
|
+
const operation = handlerConfig.operation || 'cron';
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// Accept CRON_SECRET Bearer token or fall back to ADMIN_SECRET
|
|
298
|
+
if (!isCronRequest(request) && !isAdminRequest(request)) {
|
|
299
|
+
return NextResponse.json({ error: 'Forbidden', requestId }, { status: 403 });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Rate limiting (generous — cron jobs are server-to-server)
|
|
303
|
+
const rateLimited = await enforceRateLimit(
|
|
304
|
+
request,
|
|
305
|
+
operation,
|
|
306
|
+
handlerConfig.rateLimit || AppRateLimits.webhook
|
|
307
|
+
);
|
|
308
|
+
if (rateLimited) return rateLimited as NextResponse;
|
|
309
|
+
|
|
310
|
+
const response = await handler(request, { requestId });
|
|
311
|
+
response.headers.set('x-request-id', requestId);
|
|
312
|
+
return response;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const { status, body } = classifyError(error, process.env.NODE_ENV === 'development');
|
|
315
|
+
return NextResponse.json({ ...body, requestId }, { status });
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|