diff --git a/docs/serverless-split-services.md b/docs/serverless-split-services.md new file mode 100644 index 0000000..2aa6823 --- /dev/null +++ b/docs/serverless-split-services.md @@ -0,0 +1,40 @@ +# Split Serverless services (deploy order) + +This repo is split into multiple Serverless configs so you can deploy smaller CloudFormation stacks instead of one huge stack. + +## Config files + +- `serverless.layers.yml`: Prisma layer stack (deploy once per stage) +- `serverless.host.yml`: Host + PQQ functions (owns the shared HTTP API) +- `serverless.admin.yml`: Minglar Admin functions (attaches routes to Host HTTP API) +- `serverless.user.yml`: User functions (attaches routes to Host HTTP API) +- `serverless.prepopulate.yml`: Prepopulate functions (attaches routes to Host HTTP API) + +## Deploy order (per stage) + +1) Deploy the layer: + +```bash +npx serverless deploy --config serverless.layers.yml --stage dev +``` + +2) Deploy Host (creates the HTTP API + routes for host functions): + +```bash +npx serverless deploy --config serverless.host.yml --stage dev +``` + +3) Deploy remaining services (they reuse Host's HTTP API id): + +```bash +npx serverless deploy --config serverless.admin.yml --stage dev +npx serverless deploy --config serverless.user.yml --stage dev +npx serverless deploy --config serverless.prepopulate.yml --stage dev +``` + +## Deploy a single function + +```bash +npx serverless deploy function --config serverless.host.yml --stage dev -f getHosts +``` + diff --git a/package.json b/package.json index d69d5dc..055e401 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,6 @@ "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.2.5", - "serverless-esbuild": "^1.55.1", "serverless-offline": "^14.4.0", "source-map-support": "^0.5.21", "supertest": "^6.3.4", diff --git a/serverless.admin.yml b/serverless.admin.yml new file mode 100644 index 0000000..ee0b26b --- /dev/null +++ b/serverless.admin.yml @@ -0,0 +1,133 @@ +service: minglar-admin + +useDotenv: true + +params: + dev: + stage: dev + test: + stage: test + uat: + stage: uat + +provider: + name: aws + runtime: nodejs22.x + region: ap-south-1 + stage: ${opt:stage, 'dev'} + versionFunctions: false + memorySize: 512 + layers: + - ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn} + httpApi: + id: ${cf:minglar-host-${sls:stage}.HttpApiId} + apiGateway: + binaryMediaTypes: + - '*/*' + minimumCompressionSize: 1024 + + environment: + DATABASE_URL: ${env:DATABASE_URL} + DB_USERNAME: ${env:DB_USERNAME} + DB_PASSWORD: ${env:DB_PASSWORD} + DB_DATABASE_NAME: ${env:DB_DATABASE_NAME} + DB_HOSTNAME: ${env:DB_HOSTNAME} + DB_PORT: ${env:DB_PORT} + BY_PASS_EMAIL: ${env:BY_PASS_EMAIL} + BYPASS_OTP: ${env:BYPASS_OTP} + BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY} + BREVO_API_BASEURL: ${env:BREVO_API_BASEURL} + BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL} + BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST} + BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT} + BREVO_SMTP_USER: ${env:BREVO_SMTP_USER} + BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS} + REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET} + JWT_SECRET: ${env:JWT_SECRET} + JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES} + JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS} + JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES} + JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES} + SALT_ROUNDS: ${env:SALT_ROUNDS} + NODE_ENV: ${env:NODE_ENV} + S3_BUCKET_NAME: ${env:S3_BUCKET_NAME} + MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME} + MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} + AM_INVITATION_LINK: ${env:AM_INVITATION_LINK} + HOST_LINK: ${env:HOST_LINK} + HOST_LINK_PQ: ${env:HOST_LINK_PQ} + + iam: + role: + statements: + - Effect: Allow + Action: + - s3:PutObject + - s3:GetObject + - s3:DeleteObject + - s3:ListBucket + Resource: + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}' + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*' + +custom: + serverless-offline: + reloadHandler: true + +build: + esbuild: + bundle: true + minify: true + sourcemap: false + target: node22 + platform: node + external: + - '@prisma/client' + - '.prisma/client' + - '.prisma' + - '@prisma/adapter-pg' + - 'pg' + - 'zod' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + exclude: + - 'aws-sdk' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + - '@prisma/adapter-pg' + - '@prisma/client' + - '.prisma' + - '.prisma/client' + - 'pg' + - 'zod' + - 'pg-*' + - 'postgres-*' + - 'pgpass' + - 'split2' + - 'xtend' + +package: + individually: true + excludeDevDependencies: true + patterns: + - '!node_modules/**' + - '!node_modules/@prisma/**' + - '!node_modules/.prisma/**' + - '!**/*.test.js' + - '!**/*.spec.js' + - '!**/test/**' + - '!**/__tests__/**' + - '!package-lock.json' + - '!yarn.lock' + - '!README.md' + - '!*.config.js' + - '!.git/**' + - '!.github/**' + +functions: + - ${file(./serverless/functions/minglaradmin.yml)} + +plugins: + - serverless-offline diff --git a/serverless.host.yml b/serverless.host.yml new file mode 100644 index 0000000..de0e484 --- /dev/null +++ b/serverless.host.yml @@ -0,0 +1,132 @@ +service: minglar-host + +useDotenv: true + +params: + dev: + stage: dev + test: + stage: test + uat: + stage: uat + +provider: + name: aws + runtime: nodejs22.x + region: ap-south-1 + stage: ${opt:stage, 'dev'} + versionFunctions: false + memorySize: 512 + layers: + - ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn} + apiGateway: + binaryMediaTypes: + - '*/*' + minimumCompressionSize: 1024 + + environment: + DATABASE_URL: ${env:DATABASE_URL} + DB_USERNAME: ${env:DB_USERNAME} + DB_PASSWORD: ${env:DB_PASSWORD} + DB_DATABASE_NAME: ${env:DB_DATABASE_NAME} + DB_HOSTNAME: ${env:DB_HOSTNAME} + DB_PORT: ${env:DB_PORT} + BY_PASS_EMAIL: ${env:BY_PASS_EMAIL} + BYPASS_OTP: ${env:BYPASS_OTP} + BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY} + BREVO_API_BASEURL: ${env:BREVO_API_BASEURL} + BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL} + BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST} + BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT} + BREVO_SMTP_USER: ${env:BREVO_SMTP_USER} + BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS} + REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET} + JWT_SECRET: ${env:JWT_SECRET} + JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES} + JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS} + JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES} + JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES} + SALT_ROUNDS: ${env:SALT_ROUNDS} + NODE_ENV: ${env:NODE_ENV} + S3_BUCKET_NAME: ${env:S3_BUCKET_NAME} + MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME} + MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} + AM_INVITATION_LINK: ${env:AM_INVITATION_LINK} + HOST_LINK: ${env:HOST_LINK} + HOST_LINK_PQ: ${env:HOST_LINK_PQ} + + iam: + role: + statements: + - Effect: Allow + Action: + - s3:PutObject + - s3:GetObject + - s3:DeleteObject + - s3:ListBucket + Resource: + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}' + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*' + +custom: + serverless-offline: + reloadHandler: true + +build: + esbuild: + bundle: true + minify: true + sourcemap: false + target: node22 + platform: node + external: + - '@prisma/client' + - '.prisma/client' + - '.prisma' + - '@prisma/adapter-pg' + - 'pg' + - 'zod' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + exclude: + - 'aws-sdk' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + - '@prisma/adapter-pg' + - '@prisma/client' + - '.prisma' + - '.prisma/client' + - 'pg' + - 'zod' + - 'pg-*' + - 'postgres-*' + - 'pgpass' + - 'split2' + - 'xtend' + +package: + individually: true + excludeDevDependencies: true + patterns: + - '!node_modules/**' + - '!node_modules/@prisma/**' + - '!node_modules/.prisma/**' + - '!**/*.test.js' + - '!**/*.spec.js' + - '!**/test/**' + - '!**/__tests__/**' + - '!package-lock.json' + - '!yarn.lock' + - '!README.md' + - '!*.config.js' + - '!.git/**' + - '!.github/**' + +functions: + - ${file(./serverless/functions/host.yml)} + - ${file(./serverless/functions/pqq.yml)} + +plugins: + - serverless-offline diff --git a/serverless.layers.yml b/serverless.layers.yml new file mode 100644 index 0000000..e8e5ddd --- /dev/null +++ b/serverless.layers.yml @@ -0,0 +1,30 @@ +service: minglar-layers + +useDotenv: true + +params: + dev: + stage: dev + test: + stage: test + uat: + stage: uat + +provider: + name: aws + runtime: nodejs22.x + region: ap-south-1 + stage: ${opt:stage, 'dev'} + versionFunctions: false + +# Define layers (deployed once; other stacks reference via cf output) +layers: + prisma: + path: layers/prisma + name: ${self:service}-prisma-layer-${sls:stage} + description: Prisma 7 client with pg driver adapter (no binary engines) + compatibleRuntimes: + - nodejs22.x + retain: false + +plugins: [] diff --git a/serverless.prepopulate.yml b/serverless.prepopulate.yml new file mode 100644 index 0000000..1681000 --- /dev/null +++ b/serverless.prepopulate.yml @@ -0,0 +1,133 @@ +service: minglar-prepopulate + +useDotenv: true + +params: + dev: + stage: dev + test: + stage: test + uat: + stage: uat + +provider: + name: aws + runtime: nodejs22.x + region: ap-south-1 + stage: ${opt:stage, 'dev'} + versionFunctions: false + memorySize: 512 + layers: + - ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn} + httpApi: + id: ${cf:minglar-host-${sls:stage}.HttpApiId} + apiGateway: + binaryMediaTypes: + - '*/*' + minimumCompressionSize: 1024 + + environment: + DATABASE_URL: ${env:DATABASE_URL} + DB_USERNAME: ${env:DB_USERNAME} + DB_PASSWORD: ${env:DB_PASSWORD} + DB_DATABASE_NAME: ${env:DB_DATABASE_NAME} + DB_HOSTNAME: ${env:DB_HOSTNAME} + DB_PORT: ${env:DB_PORT} + BY_PASS_EMAIL: ${env:BY_PASS_EMAIL} + BYPASS_OTP: ${env:BYPASS_OTP} + BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY} + BREVO_API_BASEURL: ${env:BREVO_API_BASEURL} + BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL} + BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST} + BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT} + BREVO_SMTP_USER: ${env:BREVO_SMTP_USER} + BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS} + REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET} + JWT_SECRET: ${env:JWT_SECRET} + JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES} + JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS} + JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES} + JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES} + SALT_ROUNDS: ${env:SALT_ROUNDS} + NODE_ENV: ${env:NODE_ENV} + S3_BUCKET_NAME: ${env:S3_BUCKET_NAME} + MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME} + MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} + AM_INVITATION_LINK: ${env:AM_INVITATION_LINK} + HOST_LINK: ${env:HOST_LINK} + HOST_LINK_PQ: ${env:HOST_LINK_PQ} + + iam: + role: + statements: + - Effect: Allow + Action: + - s3:PutObject + - s3:GetObject + - s3:DeleteObject + - s3:ListBucket + Resource: + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}' + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*' + +custom: + serverless-offline: + reloadHandler: true + +build: + esbuild: + bundle: true + minify: true + sourcemap: false + target: node22 + platform: node + external: + - '@prisma/client' + - '.prisma/client' + - '.prisma' + - '@prisma/adapter-pg' + - 'pg' + - 'zod' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + exclude: + - 'aws-sdk' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + - '@prisma/adapter-pg' + - '@prisma/client' + - '.prisma' + - '.prisma/client' + - 'pg' + - 'zod' + - 'pg-*' + - 'postgres-*' + - 'pgpass' + - 'split2' + - 'xtend' + +package: + individually: true + excludeDevDependencies: true + patterns: + - '!node_modules/**' + - '!node_modules/@prisma/**' + - '!node_modules/.prisma/**' + - '!**/*.test.js' + - '!**/*.spec.js' + - '!**/test/**' + - '!**/__tests__/**' + - '!package-lock.json' + - '!yarn.lock' + - '!README.md' + - '!*.config.js' + - '!.git/**' + - '!.github/**' + +functions: + - ${file(./serverless/functions/prepopulate.yml)} + +plugins: + - serverless-offline diff --git a/serverless.user.yml b/serverless.user.yml new file mode 100644 index 0000000..ce6a890 --- /dev/null +++ b/serverless.user.yml @@ -0,0 +1,133 @@ +service: minglar-user + +useDotenv: true + +params: + dev: + stage: dev + test: + stage: test + uat: + stage: uat + +provider: + name: aws + runtime: nodejs22.x + region: ap-south-1 + stage: ${opt:stage, 'dev'} + versionFunctions: false + memorySize: 512 + layers: + - ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn} + httpApi: + id: ${cf:minglar-host-${sls:stage}.HttpApiId} + apiGateway: + binaryMediaTypes: + - '*/*' + minimumCompressionSize: 1024 + + environment: + DATABASE_URL: ${env:DATABASE_URL} + DB_USERNAME: ${env:DB_USERNAME} + DB_PASSWORD: ${env:DB_PASSWORD} + DB_DATABASE_NAME: ${env:DB_DATABASE_NAME} + DB_HOSTNAME: ${env:DB_HOSTNAME} + DB_PORT: ${env:DB_PORT} + BY_PASS_EMAIL: ${env:BY_PASS_EMAIL} + BYPASS_OTP: ${env:BYPASS_OTP} + BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY} + BREVO_API_BASEURL: ${env:BREVO_API_BASEURL} + BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL} + BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST} + BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT} + BREVO_SMTP_USER: ${env:BREVO_SMTP_USER} + BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS} + REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET} + JWT_SECRET: ${env:JWT_SECRET} + JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES} + JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS} + JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES} + JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES} + SALT_ROUNDS: ${env:SALT_ROUNDS} + NODE_ENV: ${env:NODE_ENV} + S3_BUCKET_NAME: ${env:S3_BUCKET_NAME} + MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME} + MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} + AM_INVITATION_LINK: ${env:AM_INVITATION_LINK} + HOST_LINK: ${env:HOST_LINK} + HOST_LINK_PQ: ${env:HOST_LINK_PQ} + + iam: + role: + statements: + - Effect: Allow + Action: + - s3:PutObject + - s3:GetObject + - s3:DeleteObject + - s3:ListBucket + Resource: + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}' + - 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*' + +custom: + serverless-offline: + reloadHandler: true + +build: + esbuild: + bundle: true + minify: true + sourcemap: false + target: node22 + platform: node + external: + - '@prisma/client' + - '.prisma/client' + - '.prisma' + - '@prisma/adapter-pg' + - 'pg' + - 'zod' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + exclude: + - 'aws-sdk' + - '@aws-sdk/*' + - '@smithy/*' + - '@aws-crypto/*' + - '@prisma/adapter-pg' + - '@prisma/client' + - '.prisma' + - '.prisma/client' + - 'pg' + - 'zod' + - 'pg-*' + - 'postgres-*' + - 'pgpass' + - 'split2' + - 'xtend' + +package: + individually: true + excludeDevDependencies: true + patterns: + - '!node_modules/**' + - '!node_modules/@prisma/**' + - '!node_modules/.prisma/**' + - '!**/*.test.js' + - '!**/*.spec.js' + - '!**/test/**' + - '!**/__tests__/**' + - '!package-lock.json' + - '!yarn.lock' + - '!README.md' + - '!*.config.js' + - '!.git/**' + - '!.github/**' + +functions: + - ${file(./serverless/functions/user.yml)} + +plugins: + - serverless-offline diff --git a/serverless.yml b/serverless.yml index 49909c9..21ea24a 100644 --- a/serverless.yml +++ b/serverless.yml @@ -1,4 +1,4 @@ -service: minglarDev +service: minglar useDotenv: true @@ -23,7 +23,7 @@ provider: layers: # Use the exported stack output so deploy function works (expects a string ARN) # For offline/local, fall back to an empty string so the CF lookup is optional. - - ${cf:${self:service}-${sls:stage}.PrismaLambdaLayerQualifiedArn, ''} + - ${cf:${self:service}-${sls:stage}.PrismaLambdaLayerQualifiedArn} apiGateway: binaryMediaTypes: - '*/*' @@ -76,13 +76,13 @@ provider: custom: serverless-offline: reloadHandler: true - + build: esbuild: bundle: true minify: true sourcemap: false - target: node20 + target: node22 platform: node # Mark as external so they're not bundled into the JS external: @@ -125,6 +125,7 @@ layers: package: individually: true + excludeDevDependencies: true patterns: - '!node_modules/**' - '!node_modules/@prisma/**' @@ -147,6 +148,6 @@ functions: - ${file(./serverless/functions/prepopulate.yml)} - ${file(./serverless/functions/pqq.yml)} - ${file(./serverless/functions/user.yml)} - + plugins: - serverless-offline \ No newline at end of file