Compare commits
284 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a906dc5635 | |||
| 8ec8cf4854 | |||
| fab7642302 | |||
| 4d3796c5f3 | |||
| 2f3c531c56 | |||
| a2907929d4 | |||
|
|
ef2b23ef83 | ||
| 2767d29d79 | |||
| 46daec00ce | |||
| 43e494780d | |||
| 0da18b18f7 | |||
| b5304b3c26 | |||
| 82340c2918 | |||
| b8f5f92c98 | |||
| 3652d851f7 | |||
|
|
6166075967 | ||
|
|
b6cb5831c2 | ||
|
|
a39cc1c3c8 | ||
|
|
ae76618f7a | ||
| e957fc5c50 | |||
|
|
856db687c3 | ||
| a020a28993 | |||
| 1a520ae9e1 | |||
| d5d6951e64 | |||
| bbd55562af | |||
| 9abadba8f5 | |||
| 747566497c | |||
| 6c3e5ccd60 | |||
| ca9ba601ad | |||
| eab6565e12 | |||
| 6a84876518 | |||
| d8fb4b242d | |||
| 51b053310f | |||
| 06010ef6e8 | |||
| 7b9763c668 | |||
| 4c1a04d043 | |||
|
|
ecf45c3e7c | ||
|
|
20a931ab27 | ||
| 6a84fbc0c3 | |||
| 9fc8336bd9 | |||
| b049146664 | |||
| 963f84681c | |||
| ab9e02972e | |||
| 33b330a15b | |||
| d898dcd8ff | |||
|
|
ff18fcbf9f | ||
|
|
a872ed89e4 | ||
| 759eeb298c | |||
| 1b72e65a71 | |||
| 4a7e5fbb1e | |||
| ca5936d0db | |||
| c0d607a321 | |||
| 1d684b7de6 | |||
| 16b16ac7ca | |||
| 0e0c63e31a | |||
| 822b425c1d | |||
| 42e2d7a579 | |||
|
|
78f49b35dd | ||
|
|
930ae708a1 | ||
|
|
8fb3f16d18 | ||
| 3d6226ddac | |||
| 38c616a7af | |||
| c6ab3e57c0 | |||
|
|
776c03911e | ||
|
|
781058c443 | ||
| 3b723e5d1f | |||
|
|
56ebf44d37 | ||
| 4f8274adb9 | |||
| 39f182b8e9 | |||
| d9f7cd9a0f | |||
| 4772c320ba | |||
|
|
f19f5e46c4 | ||
|
|
156aed2429 | ||
|
|
fb77111e34 | ||
| 6b673a173d | |||
| 3c4b0db39f | |||
| e72c260b18 | |||
|
|
9a777eb3f9 | ||
|
|
c3f0a1d82a | ||
| 3f921febe0 | |||
|
|
4bc5eb8d4d | ||
| f3f0b2a81b | |||
|
|
6f0cdb4e0a | ||
| ce9c8174d8 | |||
| c72e757bf3 | |||
|
|
6aaf49bf72 | ||
|
|
1b31ca4a83 | ||
| 140f70615c | |||
| 32f4c7ce42 | |||
|
|
067d1a1f1b | ||
|
|
470298a3fb | ||
| 1d7d0749b3 | |||
|
|
d3fb1c85fb | ||
| e723e680ab | |||
|
|
0e50b8b187 | ||
|
|
bbe725dd9e | ||
| e5861654e9 | |||
|
|
b9c90b488f | ||
| d1eef782a2 | |||
| c16a7bb95e | |||
|
|
626ea34a63 | ||
|
|
31e0b3ff2d | ||
| 07f6f0159c | |||
| b1a70acfa8 | |||
| 5ad17869be | |||
| 8961e49dac | |||
| 8d8702dfb6 | |||
|
|
576c749b34 | ||
|
|
649fccb81b | ||
| 15b8afd9b2 | |||
| 84c4b1f2b9 | |||
|
|
933addd60f | ||
|
|
18ff254e8d | ||
| ae7b453abd | |||
| 7bec950096 | |||
| 6b2b211990 | |||
| 2d36ca5dd7 | |||
| f75993f738 | |||
| 264f2fa29c | |||
| e0b841b437 | |||
| 54613534db | |||
| 6cac8fb163 | |||
| 6147b0f476 | |||
| 71e3f2a933 | |||
| 174f13300c | |||
| 3f324bc1fc | |||
|
|
27e19bd921 | ||
|
|
82cbaddce1 | ||
| 80bd926e16 | |||
| 15c1458f02 | |||
| 0cedcec109 | |||
| 9e47d861a2 | |||
| 4f3adff517 | |||
| 8ccbdc8f32 | |||
| 75077c00da | |||
| abe512b0c2 | |||
| 326ea2f96b | |||
| b0bae33b6e | |||
| 030ec9225d | |||
|
|
83c3a39cc9 | ||
|
|
6b0884b070 | ||
|
|
9e0f12117f | ||
| 67da5b57e6 | |||
|
|
dcc0fbbcf6 | ||
| fb392369a5 | |||
|
|
870aaca557 | ||
| 62637ecc9a | |||
| f421a1e88a | |||
| 7beebc16b3 | |||
|
|
6fa96c3fb0 | ||
| e63e906f62 | |||
| a01a93680a | |||
|
|
7be03b6c27 | ||
|
|
abae9d9ac2 | ||
| a0ed3b9faa | |||
| 14ddc94765 | |||
| 498c4cbe46 | |||
|
|
d14948b344 | ||
|
|
c9b4269e9a | ||
| 930605e22e | |||
|
|
263d06949e | ||
| 8e25c5c4a2 | |||
| 917a1f1ee9 | |||
|
|
089d022267 | ||
|
|
d65f7f5368 | ||
| 7056f32e24 | |||
| c4e470be05 | |||
|
|
fca4991577 | ||
| 2ab046fbd2 | |||
|
|
12420f6b51 | ||
|
|
fbd3b12937 | ||
|
|
1c83cc5910 | ||
| d21dcacd7b | |||
| 38d3b4ca6a | |||
| d0fd8e6691 | |||
| 6d48eeb25b | |||
| b1623343c8 | |||
|
|
3fb6961e7c | ||
| 5e0ba4c8e8 | |||
| 1377e0ba9a | |||
|
|
d0b2de3f18 | ||
| 3b1aac921f | |||
| e7c94a1b19 | |||
| d351a632bf | |||
| 0d858f5411 | |||
|
|
15c85686c6 | ||
| 976c9fc686 | |||
| 4665cb7ca4 | |||
| 1fea4f18f6 | |||
| 29cc335a07 | |||
|
|
2ffb9ca242 | ||
| 577471e7f1 | |||
| e21ffd08f1 | |||
|
|
76e2bd542a | ||
| 926ea67e41 | |||
| ee9c0027de | |||
| 8f25bb87e6 | |||
| e366bb4a70 | |||
| 2c3b1af754 | |||
|
|
4c17e1b269 | ||
| 78592dafec | |||
| 5dc0445a61 | |||
| 2ba9b7778b | |||
|
|
3e12419985 | ||
| 041c28ea99 | |||
| 5f37455290 | |||
|
|
00dd080ee0 | ||
|
|
f61287a11d | ||
|
|
1a0fe5768c | ||
| e7234c29ad | |||
| 6bbcb36b10 | |||
|
|
3a29e70b2b | ||
|
|
a3ac533d96 | ||
|
|
5a9d0d6155 | ||
| 13ffee5f7e | |||
| 99cbe55a70 | |||
|
|
5e061f33a1 | ||
|
|
803a11cdda | ||
| 58053e061d | |||
| 4ea9aa8afa | |||
| 60e3a0e72e | |||
| c544c2ac06 | |||
|
|
d65fc684db | ||
| 5fac8b0156 | |||
| 4664eae896 | |||
| 844ebb4672 | |||
| 74b1420b8a | |||
|
|
b1068e7106 | ||
| ee38391d1e | |||
| 05f97d33e7 | |||
| b2bfed756d | |||
| 5506b5d5cc | |||
|
|
94143bd4d0 | ||
| e42a98f723 | |||
|
|
c402c76096 | ||
| db65abf693 | |||
|
|
a54f61e8c8 | ||
| 7ed225a3f0 | |||
|
|
d24fe70046 | ||
|
|
cb70b078e5 | ||
|
|
508487c3a3 | ||
|
|
399c520985 | ||
| 008bcf7c79 | |||
| ce4af2ea3b | |||
| 4ee0819051 | |||
|
|
54c024fc4f | ||
| e9496d3868 | |||
|
|
d0e6629466 | ||
|
|
50878e6374 | ||
| 5d7ce4455e | |||
| 8e91fee8ee | |||
|
|
7e0f5b3162 | ||
|
|
d2045ae0b8 | ||
| b2298e0fb1 | |||
|
|
f23ff39f2e | ||
|
|
d8f08bf564 | ||
|
|
f8a405204f | ||
| 8dda773b90 | |||
| 3e64e50385 | |||
|
|
ecb866c836 | ||
| 0353982ff4 | |||
| 8c48db854e | |||
|
|
73a1f0b375 | ||
| 03c2e729a0 | |||
| 43a2a1c2b1 | |||
|
|
096613f1e2 | ||
| 6b3d2ae7cf | |||
| fb9a92984b | |||
|
|
224e62ef45 | ||
| 6f75d912eb | |||
| fb044a4535 | |||
|
|
ac494455b5 | ||
| 8d1955f357 | |||
| 8587016ad5 | |||
| 8f12151ef1 | |||
| 477f8d229d | |||
| 3ac0a591df | |||
|
|
2ed5435a15 | ||
|
|
ceacdbddcb | ||
|
|
fb72feb20e | ||
| 72f9e26ca6 | |||
|
|
a14f1388f6 | ||
| 8e19bb566d | |||
| c0e58fe1ce |
44
.env.example
Normal file
44
.env.example
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Environment Configuration Template
|
||||||
|
# Copy this file to .env.dev, .env.test, or .env.uat and fill in the values
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=
|
||||||
|
DB_USERNAME=
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_DATABASE_NAME=
|
||||||
|
DB_HOSTNAME=
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# Email Bypass (set to true for dev/test, false for production-like environments)
|
||||||
|
BY_PASS_EMAIL=
|
||||||
|
BYPASS_OTP=
|
||||||
|
|
||||||
|
# Brevo Email Configuration
|
||||||
|
BREVO_EMAIL_API_KEY=
|
||||||
|
BREVO_API_BASEURL=https://api.brevo.com
|
||||||
|
BREVO_FROM_EMAIL=
|
||||||
|
BREVO_SMTP_HOST=smtp-relay.brevo.com
|
||||||
|
BREVO_SMTP_PORT=587
|
||||||
|
BREVO_SMTP_USER=
|
||||||
|
BREVO_SMTP_PASS=
|
||||||
|
|
||||||
|
# JWT Configuration
|
||||||
|
REFRESH_TOKEN_SECRET=
|
||||||
|
JWT_SECRET=
|
||||||
|
JWT_ACCESS_EXPIRATION_MINUTES=30
|
||||||
|
JWT_REFRESH_EXPIRATION_DAYS=7
|
||||||
|
JWT_RESET_PASSWORD_EXPIRATION_MINUTES=15
|
||||||
|
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES=15
|
||||||
|
|
||||||
|
# Security
|
||||||
|
SALT_ROUNDS=
|
||||||
|
NODE_ENV=
|
||||||
|
|
||||||
|
# AWS S3
|
||||||
|
S3_BUCKET_NAME=
|
||||||
|
|
||||||
|
# Admin Configuration
|
||||||
|
MINGLAR_ADMIN_NAME=
|
||||||
|
MINGLAR_ADMIN_EMAIL=
|
||||||
|
AM_INVITATION_LINK=
|
||||||
|
HOST_LINK=
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -40,10 +40,14 @@ lerna-debug.log*
|
|||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
.env.local
|
||||||
|
.env.dev
|
||||||
|
.env.test
|
||||||
|
.env.uat
|
||||||
|
|
||||||
# temp
|
# temp
|
||||||
.tmp
|
.tmp
|
||||||
.temp
|
.temp
|
||||||
|
undefined/
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
@@ -86,6 +90,9 @@ web_modules/
|
|||||||
# Optional npm cache directory
|
# Optional npm cache directory
|
||||||
.npm
|
.npm
|
||||||
|
|
||||||
|
#package.lock.json
|
||||||
|
.package-lock.json
|
||||||
|
|
||||||
# Optional eslint cache
|
# Optional eslint cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
|
|||||||
115
CURL_EXAMPLE.md
Normal file
115
CURL_EXAMPLE.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# CURL Command for Testing addCompanyDetails Lambda
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
1. Replace `YOUR_API_URL` with your actual API Gateway URL
|
||||||
|
2. Replace `YOUR_AUTH_TOKEN` with a valid JWT token
|
||||||
|
3. Replace file paths with actual document files on your system
|
||||||
|
|
||||||
|
## Form Data Structure
|
||||||
|
|
||||||
|
The endpoint expects:
|
||||||
|
- `companyDetails`: JSON string containing company information
|
||||||
|
- `documents`: JSON string containing array of document metadata
|
||||||
|
- File fields: One file field per document (e.g., `panFile`, `gstFile`, etc.)
|
||||||
|
|
||||||
|
## CURL Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "YOUR_API_URL/minglaradmin/add-company-details" \
|
||||||
|
-H "x-auth-token: YOUR_AUTH_TOKEN" \
|
||||||
|
-F "companyDetails={\"companyName\":\"Test Company\",\"hostRefNumber\":\"HOST001\",\"address1\":\"123 Main St\",\"address2\":\"Suite 100\",\"cityXid\":1,\"stateXid\":1,\"countryXid\":1,\"pinCode\":\"12345\",\"isSubsidairy\":false,\"registrationNumber\":\"REG123456\",\"panNumber\":\"ABCDE1234F\",\"gstNumber\":\"27ABCDE1234F1Z5\",\"formationDate\":\"2020-01-01\",\"companyType\":\"Private Limited\",\"currencyXid\":1}" \
|
||||||
|
-F "documents=[{\"documentTypeXid\":1,\"documentName\":\"pan.pdf\",\"fieldName\":\"panFile\"},{\"documentTypeXid\":2,\"documentName\":\"gst.pdf\",\"fieldName\":\"gstFile\"},{\"documentTypeXid\":3,\"documentName\":\"registration.pdf\",\"fieldName\":\"registrationFile\"},{\"documentTypeXid\":4,\"documentName\":\"aadhaar.pdf\",\"fieldName\":\"aadhaarFile\"}]" \
|
||||||
|
-F "panFile=@/path/to/pan.pdf" \
|
||||||
|
-F "gstFile=@/path/to/gst.pdf" \
|
||||||
|
-F "registrationFile=@/path/to/registration.pdf" \
|
||||||
|
-F "aadhaarFile=@/path/to/aadhaar.pdf"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example with Real Values
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://abc123.execute-api.ap-south-1.amazonaws.com/minglaradmin/add-company-details" \
|
||||||
|
-H "x-auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
|
||||||
|
-F "companyDetails={\"companyName\":\"Acme Corp\",\"hostRefNumber\":\"HOST001\",\"address1\":\"123 Business Park\",\"address2\":\"Building A\",\"cityXid\":1,\"stateXid\":1,\"countryXid\":1,\"pinCode\":\"400001\",\"isSubsidairy\":false,\"registrationNumber\":\"U12345MH2020PTC123456\",\"panNumber\":\"ABCDE1234F\",\"gstNumber\":\"27ABCDE1234F1Z5\",\"formationDate\":\"2020-01-15\",\"companyType\":\"Private Limited\",\"websiteUrl\":\"https://acme.com\",\"currencyXid\":1}" \
|
||||||
|
-F "documents=[{\"documentTypeXid\":1,\"documentName\":\"pan-certificate.pdf\",\"fieldName\":\"panFile\"},{\"documentTypeXid\":2,\"documentName\":\"gst-certificate.pdf\",\"fieldName\":\"gstFile\"},{\"documentTypeXid\":3,\"documentName\":\"registration-certificate.pdf\",\"fieldName\":\"registrationFile\"},{\"documentTypeXid\":4,\"documentName\":\"aadhaar.pdf\",\"fieldName\":\"aadhaarFile\"}]" \
|
||||||
|
-F "panFile=@./documents/pan.pdf" \
|
||||||
|
-F "gstFile=@./documents/gst.pdf" \
|
||||||
|
-F "registrationFile=@./documents/registration.pdf" \
|
||||||
|
-F "aadhaarFile=@./documents/aadhaar.pdf"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Postman Setup
|
||||||
|
|
||||||
|
1. **Method**: POST
|
||||||
|
2. **URL**: `YOUR_API_URL/minglaradmin/add-company-details`
|
||||||
|
3. **Headers**:
|
||||||
|
- `x-auth-token`: `YOUR_AUTH_TOKEN`
|
||||||
|
4. **Body**: Select `form-data`
|
||||||
|
5. **Add Fields**:
|
||||||
|
- `companyDetails` (Text): JSON string with company details
|
||||||
|
- `documents` (Text): JSON array string with document metadata
|
||||||
|
- `panFile` (File): Select PDF file
|
||||||
|
- `gstFile` (File): Select PDF file
|
||||||
|
- `registrationFile` (File): Select PDF file
|
||||||
|
- `aadhaarFile` (File): Select PDF file
|
||||||
|
|
||||||
|
## Required Document Types
|
||||||
|
|
||||||
|
Based on `REQUIRED_DOC_TYPES`:
|
||||||
|
- `documentTypeXid: 1` - PAN
|
||||||
|
- `documentTypeXid: 2` - GST
|
||||||
|
- `documentTypeXid: 3` - REGISTRATION
|
||||||
|
- `documentTypeXid: 4` - AADHAAR
|
||||||
|
|
||||||
|
## Company Details JSON Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"companyName": "string (required)",
|
||||||
|
"hostRefNumber": "string (required)",
|
||||||
|
"address1": "string (required)",
|
||||||
|
"address2": "string (optional)",
|
||||||
|
"cityXid": "number (required)",
|
||||||
|
"stateXid": "number (required)",
|
||||||
|
"countryXid": "number (required)",
|
||||||
|
"pinCode": "string (required, min 4 chars)",
|
||||||
|
"logoPath": "string (optional)",
|
||||||
|
"isSubsidairy": "boolean (required)",
|
||||||
|
"registrationNumber": "string (required)",
|
||||||
|
"panNumber": "string (required)",
|
||||||
|
"gstNumber": "string (optional)",
|
||||||
|
"formationDate": "string (required, ISO date)",
|
||||||
|
"companyType": "string (required)",
|
||||||
|
"websiteUrl": "string (optional, must be valid URL)",
|
||||||
|
"instagramUrl": "string (optional, must be valid URL)",
|
||||||
|
"facebookUrl": "string (optional, must be valid URL)",
|
||||||
|
"linkedinUrl": "string (optional, must be valid URL)",
|
||||||
|
"twitterUrl": "string (optional, must be valid URL)",
|
||||||
|
"currencyXid": "number (required)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documents JSON Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"documentTypeXid": 1,
|
||||||
|
"documentName": "pan.pdf",
|
||||||
|
"fieldName": "panFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"documentTypeXid": 2,
|
||||||
|
"documentName": "gst.pdf",
|
||||||
|
"fieldName": "gstFile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All 4 required document types (PAN, GST, REGISTRATION, AADHAAR) must be provided
|
||||||
|
- File field names in the `documents` array must match the form field names
|
||||||
|
- Files can be PDF, images, or any other document type
|
||||||
|
- The endpoint supports both `multipart/form-data` and JSON (for backward compatibility)
|
||||||
|
|
||||||
490
LAMBDA_OPTIMIZATION_GUIDE.md
Normal file
490
LAMBDA_OPTIMIZATION_GUIDE.md
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
# AWS Lambda Bundle Size Optimization Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide documents how to optimize AWS Lambda function bundle sizes when using:
|
||||||
|
- **Serverless Framework v4** (with built-in esbuild)
|
||||||
|
- **Prisma ORM** (with driver adapters)
|
||||||
|
- **NestJS** or any Node.js framework
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Lambda functions can become bloated (25+ MB) due to:
|
||||||
|
1. **Prisma binary engines** (~50MB uncompressed)
|
||||||
|
2. **AWS SDK v3** being bundled (~5-10MB)
|
||||||
|
3. **Dependencies copied to node_modules** instead of being bundled
|
||||||
|
|
||||||
|
## Solution Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Lambda Function │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Your Code (bundled by esbuild) ~50-500 KB │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Prisma Layer (shared) ~15 MB │ │
|
||||||
|
│ │ - @prisma/client │ │
|
||||||
|
│ │ - @prisma/adapter-pg │ │
|
||||||
|
│ │ - .prisma/client (generated) │ │
|
||||||
|
│ │ - pg driver │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ AWS Lambda Runtime │ │
|
||||||
|
│ │ - AWS SDK v3 (built-in for Node.js 18+) │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step-by-Step Setup
|
||||||
|
|
||||||
|
### 1. Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
your-project/
|
||||||
|
├── serverless.yml
|
||||||
|
├── package.json
|
||||||
|
├── prisma/
|
||||||
|
│ └── schema.prisma
|
||||||
|
├── layers/
|
||||||
|
│ └── prisma/
|
||||||
|
│ └── nodejs/
|
||||||
|
│ └── package.json
|
||||||
|
├── src/
|
||||||
|
│ └── ... your code
|
||||||
|
└── build-prisma-layer.ps1 (or .sh for Linux/Mac)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Prisma Schema Configuration
|
||||||
|
|
||||||
|
**`prisma/schema.prisma`**
|
||||||
|
```prisma
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
// For Prisma 7+ with driver adapters, no binary targets needed
|
||||||
|
// The WASM-based query engine is used automatically
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: Prisma 7+ uses WASM-based query compiler instead of binary engines when using driver adapters, which is much smaller.
|
||||||
|
|
||||||
|
### 3. Layer Package.json
|
||||||
|
|
||||||
|
**`layers/prisma/nodejs/package.json`**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "prisma-layer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Lambda layer for Prisma with pg driver adapter",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^7.0.0",
|
||||||
|
"@prisma/adapter-pg": "^7.0.0",
|
||||||
|
"pg": "^8.13.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Serverless Configuration
|
||||||
|
|
||||||
|
**`serverless.yml`**
|
||||||
|
```yaml
|
||||||
|
service: your-service-name
|
||||||
|
|
||||||
|
provider:
|
||||||
|
name: aws
|
||||||
|
runtime: nodejs22.x
|
||||||
|
region: your-region
|
||||||
|
memorySize: 512
|
||||||
|
# Apply Prisma layer to ALL functions
|
||||||
|
layers:
|
||||||
|
- !Ref PrismaLambdaLayer
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: ${env:DATABASE_URL}
|
||||||
|
# ... other env vars
|
||||||
|
|
||||||
|
# esbuild configuration (Serverless v4 built-in)
|
||||||
|
build:
|
||||||
|
esbuild:
|
||||||
|
bundle: true
|
||||||
|
minify: true
|
||||||
|
sourcemap: false
|
||||||
|
target: node20
|
||||||
|
platform: node
|
||||||
|
# Mark packages as external (not bundled into JS)
|
||||||
|
external:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- '.prisma/client'
|
||||||
|
- '.prisma'
|
||||||
|
- 'pg'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
- '@smithy/*'
|
||||||
|
- '@aws-crypto/*'
|
||||||
|
# Exclude from npm install in zip (CRITICAL!)
|
||||||
|
exclude:
|
||||||
|
- 'aws-sdk'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
- '@smithy/*'
|
||||||
|
- '@aws-crypto/*'
|
||||||
|
- '@prisma/client'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- '.prisma'
|
||||||
|
- '.prisma/client'
|
||||||
|
- 'pg'
|
||||||
|
- 'pg-*'
|
||||||
|
- 'postgres-*'
|
||||||
|
- 'pgpass'
|
||||||
|
- 'split2'
|
||||||
|
- 'xtend'
|
||||||
|
|
||||||
|
# Define the Prisma layer
|
||||||
|
layers:
|
||||||
|
prisma:
|
||||||
|
path: layers/prisma
|
||||||
|
name: ${self:service}-prisma-layer-${sls:stage}
|
||||||
|
description: Prisma client with pg driver adapter
|
||||||
|
compatibleRuntimes:
|
||||||
|
- nodejs22.x
|
||||||
|
retain: false
|
||||||
|
|
||||||
|
# Package configuration
|
||||||
|
package:
|
||||||
|
individually: true
|
||||||
|
patterns:
|
||||||
|
- '!node_modules/**'
|
||||||
|
- '!node_modules/@prisma/**'
|
||||||
|
- '!node_modules/.prisma/**'
|
||||||
|
- '!**/*.test.js'
|
||||||
|
- '!**/*.spec.js'
|
||||||
|
- '!**/test/**'
|
||||||
|
- '!**/__tests__/**'
|
||||||
|
- '!package-lock.json'
|
||||||
|
- '!yarn.lock'
|
||||||
|
- '!README.md'
|
||||||
|
- '!.git/**'
|
||||||
|
|
||||||
|
functions:
|
||||||
|
myFunction:
|
||||||
|
handler: src/handlers/myHandler.handler
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /my-endpoint
|
||||||
|
method: get
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- serverless-offline
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Build Script for Prisma Layer
|
||||||
|
|
||||||
|
**Windows (PowerShell) - `build-prisma-layer.ps1`**
|
||||||
|
```powershell
|
||||||
|
# Build Prisma Lambda Layer
|
||||||
|
$layerPath = "layers\prisma\nodejs"
|
||||||
|
|
||||||
|
Write-Host "Building Prisma Lambda Layer..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 1. Clean existing node_modules in layer
|
||||||
|
Write-Host "Cleaning layer node_modules..."
|
||||||
|
if (Test-Path "$layerPath\node_modules") {
|
||||||
|
Remove-Item -Recurse -Force "$layerPath\node_modules"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Install dependencies in layer
|
||||||
|
Write-Host "Installing layer dependencies..."
|
||||||
|
Push-Location $layerPath
|
||||||
|
npm install --omit=dev
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
# 3. Generate Prisma client
|
||||||
|
Write-Host "Generating Prisma client..."
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
# 4. Copy .prisma/client to layer
|
||||||
|
Write-Host "Copying generated Prisma client to layer..."
|
||||||
|
$sourcePrisma = "node_modules\.prisma"
|
||||||
|
$destPrisma = "$layerPath\node_modules\.prisma"
|
||||||
|
|
||||||
|
if (Test-Path $sourcePrisma) {
|
||||||
|
if (Test-Path $destPrisma) {
|
||||||
|
Remove-Item -Recurse -Force $destPrisma
|
||||||
|
}
|
||||||
|
Copy-Item -Recurse $sourcePrisma $destPrisma
|
||||||
|
Write-Host "Copied .prisma/client successfully!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "ERROR: .prisma folder not found. Run 'npx prisma generate' first." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. Show layer size
|
||||||
|
$layerSize = (Get-ChildItem "$layerPath\node_modules" -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB
|
||||||
|
Write-Host "`nTotal layer size: $([math]::Round($layerSize, 2)) MB" -ForegroundColor Yellow
|
||||||
|
Write-Host "Prisma layer built successfully!" -ForegroundColor Green
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac (Bash) - `build-prisma-layer.sh`**
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
LAYER_PATH="layers/prisma/nodejs"
|
||||||
|
|
||||||
|
echo "Building Prisma Lambda Layer..."
|
||||||
|
|
||||||
|
# 1. Clean existing node_modules in layer
|
||||||
|
echo "Cleaning layer node_modules..."
|
||||||
|
rm -rf "$LAYER_PATH/node_modules"
|
||||||
|
|
||||||
|
# 2. Install dependencies in layer
|
||||||
|
echo "Installing layer dependencies..."
|
||||||
|
cd "$LAYER_PATH"
|
||||||
|
npm install --omit=dev
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# 3. Generate Prisma client
|
||||||
|
echo "Generating Prisma client..."
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
# 4. Copy .prisma/client to layer
|
||||||
|
echo "Copying generated Prisma client to layer..."
|
||||||
|
if [ -d "node_modules/.prisma" ]; then
|
||||||
|
rm -rf "$LAYER_PATH/node_modules/.prisma"
|
||||||
|
cp -r "node_modules/.prisma" "$LAYER_PATH/node_modules/.prisma"
|
||||||
|
echo "Copied .prisma/client successfully!"
|
||||||
|
else
|
||||||
|
echo "ERROR: .prisma folder not found. Run 'npx prisma generate' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Show layer size
|
||||||
|
LAYER_SIZE=$(du -sm "$LAYER_PATH/node_modules" | cut -f1)
|
||||||
|
echo "Total layer size: ${LAYER_SIZE} MB"
|
||||||
|
echo "Prisma layer built successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Prisma Client Usage
|
||||||
|
|
||||||
|
**`src/common/database/prisma.client.ts`**
|
||||||
|
```typescript
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
import { Pool } from 'pg';
|
||||||
|
|
||||||
|
// Connection pool for serverless
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
max: 5, // Limit connections in Lambda
|
||||||
|
});
|
||||||
|
|
||||||
|
const adapter = new PrismaPg(pool);
|
||||||
|
|
||||||
|
// Single instance for Lambda warm starts
|
||||||
|
let prisma: PrismaClient;
|
||||||
|
|
||||||
|
export function getPrismaClient(): PrismaClient {
|
||||||
|
if (!prisma) {
|
||||||
|
prisma = new PrismaClient({
|
||||||
|
adapter,
|
||||||
|
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prisma;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { prisma };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Build the Prisma layer (run after schema changes)
|
||||||
|
./build-prisma-layer.ps1 # Windows
|
||||||
|
# or
|
||||||
|
./build-prisma-layer.sh # Linux/Mac
|
||||||
|
|
||||||
|
# 2. Deploy
|
||||||
|
npx serverless deploy --stage=dev
|
||||||
|
|
||||||
|
# 3. Deploy single function (faster, uses existing layer)
|
||||||
|
npx serverless deploy function -f myFunction --stage=dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Configuration Explained
|
||||||
|
|
||||||
|
### esbuild `external` vs `exclude`
|
||||||
|
|
||||||
|
| Property | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `external` | Tells esbuild NOT to bundle these into the JS file. They become `require()` calls. |
|
||||||
|
| `exclude` | Tells Serverless NOT to `npm install` these packages into the function zip. |
|
||||||
|
|
||||||
|
**Both are required!**
|
||||||
|
- `external` alone = esbuild doesn't bundle, but Serverless still installs to node_modules
|
||||||
|
- `exclude` alone = Serverless doesn't install, but esbuild bundles the code
|
||||||
|
|
||||||
|
### Layer Reference
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# This creates a CloudFormation reference to the layer defined in the same stack
|
||||||
|
layers:
|
||||||
|
- !Ref PrismaLambdaLayer
|
||||||
|
```
|
||||||
|
|
||||||
|
The `PrismaLambdaLayer` name comes from the layer key (`prisma`) converted to PascalCase + `LambdaLayer`.
|
||||||
|
|
||||||
|
### Why Exclude pg-* packages?
|
||||||
|
|
||||||
|
When `pg` is external, its dependencies still get installed:
|
||||||
|
- `pg-connection-string`
|
||||||
|
- `pg-pool`
|
||||||
|
- `pg-protocol`
|
||||||
|
- `pg-types`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
These must all be in the `exclude` list to prevent duplication.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
| Function Type | Before | After |
|
||||||
|
|---------------|--------|-------|
|
||||||
|
| Simple handlers | 25+ MB | **50-100 kB** |
|
||||||
|
| With validation (zod/yup) | 25+ MB | **300-500 kB** |
|
||||||
|
| With S3 uploads | 30+ MB | **1-2 MB** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### 1. "Cannot find module '@prisma/client'"
|
||||||
|
|
||||||
|
**Cause**: Layer doesn't have the generated `.prisma/client`
|
||||||
|
|
||||||
|
**Fix**: Run `build-prisma-layer.ps1` to regenerate the layer
|
||||||
|
|
||||||
|
### 2. Function size still large
|
||||||
|
|
||||||
|
**Debug**: Extract and inspect the zip:
|
||||||
|
```powershell
|
||||||
|
Expand-Archive ".serverless\build\your-function.zip" -DestinationPath "extracted"
|
||||||
|
Get-ChildItem "extracted\node_modules" -Directory
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see `@prisma` or `pg` folders, the `exclude` config isn't working.
|
||||||
|
|
||||||
|
### 3. "Cannot resolve CloudFormation reference"
|
||||||
|
|
||||||
|
**Cause**: Using `${cf:...}` reference before first deploy
|
||||||
|
|
||||||
|
**Fix**: Use `!Ref PrismaLambdaLayer` instead (works on first deploy)
|
||||||
|
|
||||||
|
### 4. Cold starts still slow
|
||||||
|
|
||||||
|
Consider:
|
||||||
|
- **Provisioned Concurrency**: Pre-warm instances
|
||||||
|
- **Reduce memory**: Sometimes lower memory = same speed, lower cost
|
||||||
|
- **Connection pooling**: Use tools like PgBouncer for RDS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Optimizations
|
||||||
|
|
||||||
|
### 1. Remove duplicate validation libraries
|
||||||
|
|
||||||
|
Pick ONE of: `zod`, `yup`, or `class-validator`. Don't use all three.
|
||||||
|
|
||||||
|
### 2. Tree-shake NestJS
|
||||||
|
|
||||||
|
If not using full NestJS, import only what you need:
|
||||||
|
```typescript
|
||||||
|
// Instead of
|
||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
|
||||||
|
// For Lambda handlers, you might not need NestJS at all
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use AWS SDK v3 selectively
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
external:
|
||||||
|
- '@aws-sdk/*' # Exclude all
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in code:
|
||||||
|
```typescript
|
||||||
|
// AWS SDK v3 is available in Lambda runtime (Node.js 18+)
|
||||||
|
import { S3Client } from '@aws-sdk/client-s3';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Minimal serverless.yml for Prisma + Lambda optimization
|
||||||
|
build:
|
||||||
|
esbuild:
|
||||||
|
bundle: true
|
||||||
|
minify: true
|
||||||
|
external:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- '.prisma/client'
|
||||||
|
- '.prisma'
|
||||||
|
- 'pg'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
exclude:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- '.prisma'
|
||||||
|
- '.prisma/client'
|
||||||
|
- 'pg'
|
||||||
|
- 'pg-*'
|
||||||
|
- 'postgres-*'
|
||||||
|
- 'pgpass'
|
||||||
|
- 'split2'
|
||||||
|
- 'xtend'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
|
||||||
|
layers:
|
||||||
|
prisma:
|
||||||
|
path: layers/prisma
|
||||||
|
name: ${self:service}-prisma-${sls:stage}
|
||||||
|
compatibleRuntimes:
|
||||||
|
- nodejs22.x
|
||||||
|
|
||||||
|
provider:
|
||||||
|
layers:
|
||||||
|
- !Ref PrismaLambdaLayer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Compatibility
|
||||||
|
|
||||||
|
| Tool | Tested Version |
|
||||||
|
|------|----------------|
|
||||||
|
| Serverless Framework | v4.x |
|
||||||
|
| Prisma | v7.x |
|
||||||
|
| Node.js | 20.x, 22.x |
|
||||||
|
| AWS Lambda Runtime | nodejs20.x, nodejs22.x |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: December 2025*
|
||||||
51
build-prisma-layer.ps1
Normal file
51
build-prisma-layer.ps1
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Build Prisma Lambda Layer
|
||||||
|
# Run this script before deploying to ensure the layer has the generated client
|
||||||
|
|
||||||
|
$layerPath = "layers\prisma\nodejs"
|
||||||
|
|
||||||
|
Write-Host "Building Prisma Lambda Layer..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 1. Clean existing node_modules in layer
|
||||||
|
Write-Host "Cleaning layer node_modules..."
|
||||||
|
if (Test-Path "$layerPath\node_modules") {
|
||||||
|
Remove-Item -Recurse -Force "$layerPath\node_modules"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Install dependencies in layer
|
||||||
|
Write-Host "Installing layer dependencies..."
|
||||||
|
Push-Location $layerPath
|
||||||
|
npm install --omit=dev
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
# 3. Generate Prisma client into the layer
|
||||||
|
Write-Host "Generating Prisma client into layer..."
|
||||||
|
# Set the output directory for Prisma client
|
||||||
|
$env:PRISMA_GENERATE_DATAPROXY = "false"
|
||||||
|
|
||||||
|
# Generate client - this creates .prisma/client
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
# 4. Copy .prisma/client to layer
|
||||||
|
Write-Host "Copying generated Prisma client to layer..."
|
||||||
|
$sourcePrisma = "node_modules\.prisma"
|
||||||
|
$destPrisma = "$layerPath\node_modules\.prisma"
|
||||||
|
|
||||||
|
if (Test-Path $sourcePrisma) {
|
||||||
|
if (Test-Path $destPrisma) {
|
||||||
|
Remove-Item -Recurse -Force $destPrisma
|
||||||
|
}
|
||||||
|
Copy-Item -Recurse $sourcePrisma $destPrisma
|
||||||
|
Write-Host "Copied .prisma/client successfully!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "ERROR: .prisma folder not found. Run 'npx prisma generate' first." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. Show layer size
|
||||||
|
Write-Host "`nLayer contents:"
|
||||||
|
Get-ChildItem "$layerPath\node_modules" -Directory | Select-Object Name
|
||||||
|
$layerSize = (Get-ChildItem "$layerPath\node_modules" -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB
|
||||||
|
Write-Host "`nTotal layer size: $([math]::Round($layerSize, 2)) MB" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
Write-Host "`nPrisma layer built successfully!" -ForegroundColor Green
|
||||||
|
Write-Host "Run 'serverless deploy' to deploy with the updated layer."
|
||||||
238
layers/prisma/nodejs/package-lock.json
generated
Normal file
238
layers/prisma/nodejs/package-lock.json
generated
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-layer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "prisma-layer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/adapter-pg": "^7.0.1",
|
||||||
|
"@prisma/client": "^7.0.1",
|
||||||
|
"pg": "^8.13.0",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/adapter-pg": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-01GpPPhLMoDMF4ipgfZz0L87fla/TV/PBQcmHy+9vV1ml6gUoqF8dUIRNI5Yf2YKpOwzQg9sn8C7dYD1Yio9Ug==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/driver-adapter-utils": "7.0.1",
|
||||||
|
"pg": "^8.16.3",
|
||||||
|
"postgres-array": "3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client-runtime-utils": "7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19 || ^22.12 || >=24.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*",
|
||||||
|
"typescript": ">=5.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client-runtime-utils": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/driver-adapter-utils": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-sBbxm/yysHLLF2iMAB+qcX/nn3WFgsiC4DQNz0uM6BwGSIs8lIvgo0u8nR9nxe5gvFgKiIH8f4z2fgOEMeXc8w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "7.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.16.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.9.1",
|
||||||
|
"pg-pool": "^3.10.1",
|
||||||
|
"pg-protocol": "^1.10.3",
|
||||||
|
"pg-types": "2.2.0",
|
||||||
|
"pgpass": "1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.2.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.10.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
||||||
|
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-types/node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
|
||||||
|
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
layers/prisma/nodejs/package.json
Normal file
11
layers/prisma/nodejs/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-layer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Lambda layer for Prisma 7 with pg driver adapter and zod",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^7.0.1",
|
||||||
|
"@prisma/adapter-pg": "^7.0.1",
|
||||||
|
"pg": "^8.13.0",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
8294
package-lock.json
generated
8294
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -22,9 +22,16 @@
|
|||||||
"prisma:push": "prisma db push",
|
"prisma:push": "prisma db push",
|
||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
"prisma:studio": "prisma studio",
|
"prisma:studio": "prisma studio",
|
||||||
"prisma:seed": "ts-node prisma/seed.ts"
|
"prisma:seed": "ts-node prisma/seed.ts",
|
||||||
|
"seeder": "tsx prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-crypto/crc32c": "^5.2.0",
|
||||||
|
"@aws-crypto/sha256-browser": "^5.2.0",
|
||||||
|
"@aws-crypto/sha256-js": "^5.2.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.928.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.310.0",
|
||||||
|
"@aws/lambda-invoke-store": "^0.2.1",
|
||||||
"@nestjs/common": "^10.3.0",
|
"@nestjs/common": "^10.3.0",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.3.0",
|
"@nestjs/core": "^10.3.0",
|
||||||
@@ -33,23 +40,35 @@
|
|||||||
"@nestjs/platform-express": "^10.3.0",
|
"@nestjs/platform-express": "^10.3.0",
|
||||||
"@nestjs/swagger": "^7.1.17",
|
"@nestjs/swagger": "^7.1.17",
|
||||||
"@nestjs/throttler": "^5.1.1",
|
"@nestjs/throttler": "^5.1.1",
|
||||||
"@prisma/client": "^5.8.1",
|
"@prisma/adapter-pg": "^7.0.1",
|
||||||
|
"@prisma/client": "^7.0.1",
|
||||||
|
"@smithy/middleware-stack": "^4.2.5",
|
||||||
|
"@smithy/protocol-http": "^5.3.5",
|
||||||
|
"@smithy/types": "^4.9.0",
|
||||||
"@types/http-status": "^1.1.2",
|
"@types/http-status": "^1.1.2",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"aws-lambda": "^1.0.7",
|
"aws-lambda": "^1.0.7",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"fast-xml-parser": "^5.3.1",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"http-status": "^2.1.0",
|
"http-status": "^2.1.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"prisma": "^7.0.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"serverless": "4.17.0",
|
"serverless": "4.24.0",
|
||||||
"swagger-ui-express": "^5.0.0",
|
"swagger-ui-express": "^5.0.0",
|
||||||
"yup": "^1.7.1"
|
"tslib": "^2.8.1",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
|
"yup": "^1.7.1",
|
||||||
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.3.0",
|
"@nestjs/cli": "^10.3.0",
|
||||||
@@ -69,14 +88,15 @@
|
|||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prisma": "^5.8.1",
|
|
||||||
"serverless-esbuild": "^1.55.1",
|
"serverless-esbuild": "^1.55.1",
|
||||||
|
"serverless-offline": "^14.4.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"ts-jest": "^29.1.2",
|
"ts-jest": "^29.1.2",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"tsx": "^4.20.6",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
|||||||
12
prisma.config.ts
Normal file
12
prisma.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import { defineConfig, env } from 'prisma/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
})
|
||||||
1801
prisma/migrations/20251120120035_varcharaddition/migration.sql
Normal file
1801
prisma/migrations/20251120120035_varcharaddition/migration.sql
Normal file
File diff suppressed because it is too large
Load Diff
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
||||||
10
prisma/prisma.ts
Normal file
10
prisma/prisma.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// prisma.ts
|
||||||
|
// Re-export from the main singleton for consistency
|
||||||
|
import { prisma } from '../src/common/database/prisma.client';
|
||||||
|
|
||||||
|
export { prisma };
|
||||||
|
|
||||||
|
process.on('SIGINT', async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
1314
prisma/seed.ts
Normal file
1314
prisma/seed.ts
Normal file
File diff suppressed because it is too large
Load Diff
153
serverless.yml
153
serverless.yml
@@ -1,12 +1,39 @@
|
|||||||
service: minglarDev
|
service: minglar
|
||||||
|
|
||||||
|
useDotenv: true
|
||||||
|
|
||||||
|
params:
|
||||||
|
dev:
|
||||||
|
stage: dev
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
uat:
|
||||||
|
stage: uat
|
||||||
|
|
||||||
provider:
|
provider:
|
||||||
name: aws
|
name: aws
|
||||||
runtime: nodejs20.x
|
runtime: nodejs22.x
|
||||||
region: ap-south-1
|
region: ap-south-1
|
||||||
|
stage: ${opt:stage, 'dev'}
|
||||||
versionFunctions: false
|
versionFunctions: false
|
||||||
|
memorySize: 512
|
||||||
|
# Apply Prisma layer to all functions
|
||||||
|
# Reference the layer defined in this stack using CloudFormation Ref
|
||||||
|
layers:
|
||||||
|
# Use the exported stack output so deploy function works (expects a string ARN)
|
||||||
|
- ${cf:${self:service}-${sls:stage}.PrismaLambdaLayerQualifiedArn}
|
||||||
|
apiGateway:
|
||||||
|
binaryMediaTypes:
|
||||||
|
- '*/*'
|
||||||
|
minimumCompressionSize: 1024
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: ${env:DATABASE_URL}
|
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}
|
BY_PASS_EMAIL: ${env:BY_PASS_EMAIL}
|
||||||
BYPASS_OTP: ${env:BYPASS_OTP}
|
BYPASS_OTP: ${env:BYPASS_OTP}
|
||||||
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
|
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
|
||||||
@@ -18,56 +45,104 @@ provider:
|
|||||||
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
|
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
|
||||||
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
|
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
|
||||||
JWT_SECRET: ${env:JWT_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}
|
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}
|
||||||
|
|
||||||
httpApi:
|
iam:
|
||||||
cors:
|
role:
|
||||||
allowedOrigins: ['*']
|
statements:
|
||||||
allowedHeaders:
|
- Effect: Allow
|
||||||
- Content-Type
|
Action:
|
||||||
- X-Amz-Date
|
- s3:PutObject
|
||||||
- Authorization
|
- s3:GetObject
|
||||||
- X-Api-Key
|
- s3:DeleteObject
|
||||||
- X-Auth-Token
|
- s3:ListBucket
|
||||||
allowCredentials: false
|
Resource:
|
||||||
|
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
|
||||||
|
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
|
||||||
|
|
||||||
custom:
|
custom:
|
||||||
|
serverless-offline:
|
||||||
|
reloadHandler: true
|
||||||
|
|
||||||
|
build:
|
||||||
esbuild:
|
esbuild:
|
||||||
bundle: true
|
bundle: true
|
||||||
minify: false
|
minify: true
|
||||||
sourcemap: false
|
sourcemap: false
|
||||||
exclude: ['aws-sdk']
|
|
||||||
target: node20
|
target: node20
|
||||||
platform: node
|
platform: node
|
||||||
concurrency: 10
|
# Mark as external so they're not bundled into the JS
|
||||||
outdir: dist
|
external:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '.prisma/client'
|
||||||
|
- '.prisma'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- 'pg'
|
||||||
|
- 'zod'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
- '@smithy/*'
|
||||||
|
- '@aws-crypto/*'
|
||||||
|
# Exclude prevents npm install of these packages in the zip
|
||||||
|
exclude:
|
||||||
|
- 'aws-sdk'
|
||||||
|
- '@aws-sdk/*'
|
||||||
|
- '@smithy/*'
|
||||||
|
- '@aws-crypto/*'
|
||||||
|
- '@prisma/adapter-pg'
|
||||||
|
- '@prisma/client'
|
||||||
|
- '.prisma'
|
||||||
|
- '.prisma/client'
|
||||||
|
- 'pg'
|
||||||
|
- 'zod'
|
||||||
|
- 'pg-*'
|
||||||
|
- 'postgres-*'
|
||||||
|
- 'pgpass'
|
||||||
|
- 'split2'
|
||||||
|
- 'xtend'
|
||||||
|
|
||||||
|
# Define layers
|
||||||
|
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
|
||||||
|
|
||||||
package:
|
package:
|
||||||
individually: true
|
individually: true
|
||||||
patterns:
|
patterns:
|
||||||
- '!node_modules/**' # exclude all node_modules first
|
- '!node_modules/**'
|
||||||
- '!**/*.spec.ts'
|
- '!node_modules/@prisma/**'
|
||||||
- '!**/*.test.ts'
|
- '!node_modules/.prisma/**'
|
||||||
- '!**/*.log'
|
- '!**/*.test.js'
|
||||||
- 'src/**' # include all source files
|
- '!**/*.spec.js'
|
||||||
- 'common/**' # include common modules
|
- '!**/test/**'
|
||||||
- 'node_modules/@prisma/client/**'
|
- '!**/__tests__/**'
|
||||||
- 'node_modules/.prisma/client/**'
|
- '!package-lock.json'
|
||||||
- 'prisma/schema.prisma'
|
- '!yarn.lock'
|
||||||
|
- '!README.md'
|
||||||
|
- '!*.config.js'
|
||||||
|
- '!.git/**'
|
||||||
|
- '!.github/**'
|
||||||
|
|
||||||
|
# Import function definitions from separate files organized by module
|
||||||
functions:
|
functions:
|
||||||
# 👇 Example Lambda for Host Module
|
- ${file(./serverless/functions/host.yml)}
|
||||||
|
- ${file(./serverless/functions/minglaradmin.yml)}
|
||||||
|
- ${file(./serverless/functions/prepopulate.yml)}
|
||||||
|
- ${file(./serverless/functions/pqq.yml)}
|
||||||
|
|
||||||
getHosts:
|
plugins:
|
||||||
handler: src/modules/host/handlers/host.handler
|
- serverless-offline
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/**'
|
|
||||||
- 'common/**'
|
|
||||||
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
|
||||||
- 'node_modules/@prisma/client/**'
|
|
||||||
- 'prisma/schema.prisma'
|
|
||||||
events:
|
|
||||||
- httpApi:
|
|
||||||
path: /host
|
|
||||||
method: get
|
|
||||||
764
serverless.yml.backup
Normal file
764
serverless.yml.backup
Normal file
@@ -0,0 +1,764 @@
|
|||||||
|
service: minglarDev
|
||||||
|
|
||||||
|
provider:
|
||||||
|
name: aws
|
||||||
|
runtime: nodejs22.x
|
||||||
|
region: ap-south-1
|
||||||
|
versionFunctions: false
|
||||||
|
memorySize: 512 # Default memory for all functions (can be overridden per function)
|
||||||
|
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}
|
||||||
|
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}
|
||||||
|
|
||||||
|
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:
|
||||||
|
esbuild:
|
||||||
|
bundle: true
|
||||||
|
minify: true
|
||||||
|
sourcemap: false
|
||||||
|
target: node20
|
||||||
|
platform: node
|
||||||
|
concurrency: 5
|
||||||
|
external:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '.prisma'
|
||||||
|
exclude:
|
||||||
|
- 'aws-sdk'
|
||||||
|
|
||||||
|
package:
|
||||||
|
individually: true
|
||||||
|
patterns:
|
||||||
|
- '!node_modules/**'
|
||||||
|
- '!**/*.test.js'
|
||||||
|
- '!**/*.spec.js'
|
||||||
|
- '!**/test/**'
|
||||||
|
- '!**/__tests__/**'
|
||||||
|
- '!package-lock.json'
|
||||||
|
- '!yarn.lock'
|
||||||
|
- '!README.md'
|
||||||
|
- '!*.config.js'
|
||||||
|
- '!.git/**'
|
||||||
|
- '!.github/**'
|
||||||
|
|
||||||
|
functions:
|
||||||
|
getHosts:
|
||||||
|
handler: src/modules/host/handlers/host.handler
|
||||||
|
memorySize: 384 # Lower memory for simple GET operations
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/host.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host
|
||||||
|
method: get
|
||||||
|
|
||||||
|
verifyOtp:
|
||||||
|
handler: src/modules/host/handlers/verifyOtp.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/verifyOtp.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/verify-otp
|
||||||
|
method: post
|
||||||
|
|
||||||
|
loginForHost:
|
||||||
|
handler: src/modules/host/handlers/loginForHost.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/loginForHost.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/login
|
||||||
|
method: post
|
||||||
|
|
||||||
|
registrationOfHost:
|
||||||
|
handler: src/modules/host/handlers/registration.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/registration.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/registration
|
||||||
|
method: post
|
||||||
|
|
||||||
|
createPasswordForHost:
|
||||||
|
handler: src/modules/host/handlers/createPassword.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/createPassword.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/create-password
|
||||||
|
method: post
|
||||||
|
|
||||||
|
addPaymentDetailsForHost:
|
||||||
|
handler: src/modules/host/handlers/addPaymentDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addPaymentDetails.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/add-payment-details
|
||||||
|
method: post
|
||||||
|
|
||||||
|
addActivity:
|
||||||
|
handler: src/modules/host/handlers/addActivity.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addActivity.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/add-activity
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getHostById:
|
||||||
|
handler: src/modules/host/handlers/getbyidhandler.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getbyidhandler.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/getById
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getPQQQuestionDetailsById:
|
||||||
|
handler: src/modules/host/handlers/getByIdPQQ.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getByIdPQQ.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/get-pqq-question-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getLatestPQQQuestionDetails:
|
||||||
|
handler: src/modules/host/handlers/getLatestQuestionDetailsPQQ.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getLatestQuestionDetailsPQQ.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/get-latest-pqq-question-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getActivityTypes:
|
||||||
|
handler: src/modules/host/handlers/getActivity.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getActivity.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/get-activity
|
||||||
|
method: get
|
||||||
|
|
||||||
|
acceptMinglarAgreement:
|
||||||
|
handler: src/modules/host/handlers/acceptAgreement.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/acceptAgreement.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/accept-agreement
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
getStepperInfo:
|
||||||
|
handler: src/modules/host/handlers/getStepper.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getStepper.*'
|
||||||
|
- 'src/common/utils/handlers/safeHandler.*'
|
||||||
|
- 'src/common/database/**'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /stepper
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getSuggestion:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-suggestion
|
||||||
|
method: get
|
||||||
|
|
||||||
|
minglarRegistration:
|
||||||
|
handler: src/modules/minglaradmin/handlers/registration.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/registration
|
||||||
|
method: post
|
||||||
|
|
||||||
|
minglarLoginForAdmin:
|
||||||
|
handler: src/modules/minglaradmin/handlers/loginForMinglar.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/login
|
||||||
|
method: post
|
||||||
|
|
||||||
|
minglarCreatePassword:
|
||||||
|
handler: src/modules/minglaradmin/handlers/createPassword.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/create-password
|
||||||
|
method: post
|
||||||
|
|
||||||
|
# Functions using AWS SDK - KEEP AS IS with higher memory
|
||||||
|
updateMinglarProfile:
|
||||||
|
handler: src/modules/minglaradmin/handlers/updateProfile.handler
|
||||||
|
memorySize: 512 # Higher memory for AWS SDK operations
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/updateProfile.*'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
- 'node_modules/@aws-sdk/**'
|
||||||
|
- 'node_modules/@smithy/**'
|
||||||
|
- 'node_modules/tslib/**'
|
||||||
|
- 'node_modules/fast-xml-parser/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/update-profile
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
prepopulateTeammate:
|
||||||
|
handler: src/modules/minglaradmin/handlers/prepopulateTeammate.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/prepopulate-Roles
|
||||||
|
method: get
|
||||||
|
|
||||||
|
inviteTeammate:
|
||||||
|
handler: src/modules/minglaradmin/handlers/inviteTeammate.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/invite-teammate
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAllHostApplication.handler
|
||||||
|
memorySize: 512 # Higher memory for data-intensive operations
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-host-applications-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllOnboardingHostApplications:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAllOnboardingHosts.handler
|
||||||
|
memorySize: 512 # Higher memory for data-intensive operations
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-host-applications-admin
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllOnboardingHostApplications_New:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getOnboardingNewApplications.handler
|
||||||
|
memorySize: 512 # Higher memory for data-intensive operations
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-host-applications-admin-new
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllInvitationDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAllInvitationDetails.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-invitation-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
addSuggestion:
|
||||||
|
handler: src/modules/minglaradmin/handlers/addSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/add-suggestion
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllCoadminAndAMDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAllCoadminAndAM.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-coadmin-and-am-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllInvitedCoadminAndAMDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAllInvitedCoadminAndAM.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-invited-coadmin-and-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllBankAndCurrencyDetails:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllBankDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-bank-currency-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getCityByState:
|
||||||
|
handler: src/modules/prepopulate/handlers/getCityByState.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/handlers/getCityByState.*'
|
||||||
|
- 'src/modules/prepopulate/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-city-by-state
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getBranchByBankXid:
|
||||||
|
handler: src/modules/prepopulate/handlers/getBranchByBank.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/handlers/getBranchByBank.*'
|
||||||
|
- 'src/modules/prepopulate/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-branch-by-bank
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllDocumentCountryStateCityDetails:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllDocTypeWithCountryState.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-doc-country
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllPqqQuesAns:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllPQQQuesWithAns.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-pqq-ques-ans
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getFrequenciesOfActivity:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllFrequencies.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-Frequencies
|
||||||
|
method: get
|
||||||
|
|
||||||
|
assignAMToHost:
|
||||||
|
handler: src/modules/minglaradmin/handlers/assignAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/assign-am-to-host
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
editAgreementDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/editAgreementDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/edit-agreement-details
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
acceptHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/acceptHostApplication.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/accept-host-application
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
acceptHostApplicationMinglar:
|
||||||
|
handler: src/modules/minglaradmin/handlers/acceptHostAppMinglar.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/accept-host-application-minglar
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
rejectHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/rejectHostApplication.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/reject-host-application
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
rejectHostApplicationAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/rejectHostApplicationAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/reject-host-application-am
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
# Functions using AWS SDK and S3 - KEEP AS IS with higher memory
|
||||||
|
addCompanyDetails:
|
||||||
|
handler: src/modules/host/handlers/addCompanyDetails.handler
|
||||||
|
memorySize: 512
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addCompanyDetails.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
# Only include specific AWS SDK modules needed for S3
|
||||||
|
- 'node_modules/@aws-sdk/client-s3/**'
|
||||||
|
- 'node_modules/@aws-sdk/s3-request-presigner/**'
|
||||||
|
- 'node_modules/@aws-sdk/types/**'
|
||||||
|
- 'node_modules/@aws-sdk/middleware-logger/**'
|
||||||
|
- 'node_modules/@aws-sdk/util-utf8-node/**'
|
||||||
|
- 'node_modules/@aws-sdk/util-utf8-browser/**'
|
||||||
|
- 'node_modules/@smithy/**'
|
||||||
|
- 'node_modules/tslib/**'
|
||||||
|
# Remove these large/unnecessary packages:
|
||||||
|
- 'node_modules/fast-xml-parser/**' # Remove if not used
|
||||||
|
- 'node_modules/lambda-multipart-parser/**' # You're using busboy directly
|
||||||
|
- 'node_modules/busboy/**'
|
||||||
|
# Remove these AWS utility packages (included in main SDK):
|
||||||
|
- 'node_modules/@aws-crypto/**'
|
||||||
|
# - 'node_modules/uuid/**' # AWS SDK includes its own
|
||||||
|
# - 'node_modules/@aws/util-uri-escape/**'
|
||||||
|
# - 'node_modules/@aws/util-middleware/**'
|
||||||
|
- 'node_modules/@aws/smithy-client/**'
|
||||||
|
# - 'node_modules/@aws/lambda-invoke-store/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/add-company-details
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
submitPqqAnswer:
|
||||||
|
handler: src/modules/host/handlers/submitPqqAns.handler
|
||||||
|
memorySize: 512
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/submitPqqAns.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
- 'node_modules/@aws-sdk/**'
|
||||||
|
- 'node_modules/@smithy/**'
|
||||||
|
- 'node_modules/tslib/**'
|
||||||
|
- 'node_modules/fast-xml-parser/**'
|
||||||
|
- 'node_modules/lambda-multipart-parser/**'
|
||||||
|
- 'node_modules/busboy/**'
|
||||||
|
- 'node_modules/@aws-crypto/**'
|
||||||
|
- 'node_modules/uuid/**'
|
||||||
|
- 'node_modules/@aws/util-uri-escape/**'
|
||||||
|
- 'node_modules/@aws/util-middleware/**'
|
||||||
|
- 'node_modules/@aws/smithy-client/**'
|
||||||
|
- 'node_modules/@aws/lambda-invoke-store/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/submit-pqq-ans
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
submitFinalPqqAnswer:
|
||||||
|
handler: src/modules/host/handlers/getPQQScore.handler
|
||||||
|
memorySize: 512
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getPQQScore.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
- 'node_modules/@aws-sdk/**'
|
||||||
|
- 'node_modules/@smithy/**'
|
||||||
|
- 'node_modules/tslib/**'
|
||||||
|
- 'node_modules/fast-xml-parser/**'
|
||||||
|
- 'node_modules/lambda-multipart-parser/**'
|
||||||
|
- 'node_modules/busboy/**'
|
||||||
|
- 'node_modules/@aws-crypto/**'
|
||||||
|
- 'node_modules/uuid/**'
|
||||||
|
- 'node_modules/@aws/util-uri-escape/**'
|
||||||
|
- 'node_modules/@aws/util-middleware/**'
|
||||||
|
- 'node_modules/@aws/smithy-client/**'
|
||||||
|
- 'node_modules/@aws/lambda-invoke-store/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/submit-final-pqq-ans
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
addPQQSuggestion:
|
||||||
|
handler: src/modules/minglar/handlers/addPQQSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/addPQQSuggestion.*'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
- 'node_modules/@prisma/client/**'
|
||||||
|
- 'node_modules/.prisma/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglar/add-Pqq-suggestion
|
||||||
|
method: post
|
||||||
353
serverless/functions/host.yml
Normal file
353
serverless/functions/host.yml
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
# Host Module Functions
|
||||||
|
# All authentication and host management endpoints
|
||||||
|
|
||||||
|
getHosts:
|
||||||
|
handler: src/modules/host/handlers/host.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/host.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host
|
||||||
|
method: get
|
||||||
|
|
||||||
|
verifyOTP:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/verifyOTP.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/verifyOtp.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/verify-otp
|
||||||
|
method: post
|
||||||
|
|
||||||
|
login:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/login.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/loginForHost.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/login
|
||||||
|
method: post
|
||||||
|
|
||||||
|
signUp:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/signUp.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/registration.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/registration
|
||||||
|
method: post
|
||||||
|
|
||||||
|
createPassword:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/createPassword.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/createPassword.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/create-password
|
||||||
|
method: post
|
||||||
|
|
||||||
|
updateBankDetails:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/updateBankDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addPaymentDetails.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/add-payment-details
|
||||||
|
method: post
|
||||||
|
|
||||||
|
saveActivity_ForPQQ:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/saveActivity_ForPQQ.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addActivity.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/add-activity
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getHostById:
|
||||||
|
handler: src/modules/host/handlers/getbyidhandler.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getbyidhandler.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/getById
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getPQQ_ByQuestionId:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getPQQ_ByQuestionId.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getByIdPQQ.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/get-pqq-question-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getPQQ_LastUpdatedQuestion:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getPQQ_LastUpdatedQuestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getLatestQuestionDetailsPQQ.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
prePopulateNewActivity:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllActivityType.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getActivityType.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
|
||||||
|
method: get
|
||||||
|
|
||||||
|
showSuggestion:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/showSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Host_Admin/onboarding/showSuggestion.handler.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/get-suggestion
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllHostActivity:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllHostActivity.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/getAllHostActivity.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/get-all-host-activity
|
||||||
|
method: get
|
||||||
|
|
||||||
|
acceptAggrement:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/acceptAggrement.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/acceptAgreement.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/accept-agreement
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
getStepperInfo:
|
||||||
|
handler: src/modules/host/handlers/getStepper.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/getStepper.*'
|
||||||
|
- 'src/common/utils/handlers/safeHandler.*'
|
||||||
|
- 'src/common/database/**'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /stepper
|
||||||
|
method: get
|
||||||
|
|
||||||
|
# Functions with S3/AWS SDK dependencies
|
||||||
|
submitCompanyDetails:
|
||||||
|
handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler
|
||||||
|
memorySize: 1024
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/addCompanyDetails.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- 'src/common/**'
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Host_Admin/onboarding/add-company-details
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
submitPQQ_Answer:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQ_Answer.handler
|
||||||
|
memorySize: 1024
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/submitPqqAns.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/submit-pqq-answer
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
updatePQQ_LastAnswer:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getPQQScore.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/submitPqqAns.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
|
||||||
|
method: post
|
||||||
|
|
||||||
|
|
||||||
|
submitPQQForReview:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.*'
|
||||||
|
- 'src/modules/host/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/submit-pqq-for-review
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
getAllPQQwithSubmittedAns:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
|
||||||
|
method: get
|
||||||
|
|
||||||
|
updateSuggestionAsReviewed:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/updateSuggestionAsReviewed.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/update-suggestion-reviewed
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
resendOTPmail:
|
||||||
|
handler: src/modules/host/handlers/resendOtp.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/resendOtp/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /resend-otp
|
||||||
|
method: post
|
||||||
425
serverless/functions/minglaradmin.yml
Normal file
425
serverless/functions/minglaradmin.yml
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
# Minglar Admin Module Functions
|
||||||
|
# Admin dashboard and management endpoints
|
||||||
|
|
||||||
|
minglarRegistration:
|
||||||
|
handler: src/modules/minglaradmin/handlers/registration.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/registration
|
||||||
|
method: post
|
||||||
|
|
||||||
|
minglarLoginForAdmin:
|
||||||
|
handler: src/modules/minglaradmin/handlers/loginForMinglar.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/login
|
||||||
|
method: post
|
||||||
|
|
||||||
|
minglarCreatePassword:
|
||||||
|
handler: src/modules/minglaradmin/handlers/createPassword.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/create-password
|
||||||
|
method: post
|
||||||
|
|
||||||
|
updateMinglarProfile:
|
||||||
|
handler: src/modules/minglaradmin/handlers/updateProfile.handler
|
||||||
|
memorySize: 1024
|
||||||
|
timeout: 30
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/updateProfile.*'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/update-profile
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
prepopulateRole:
|
||||||
|
handler: src/modules/minglaradmin/handlers/prepopulateRole.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/prepopulate-Roles
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getHostDetailsById:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/getByIdHostDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/get-host-details/{host_xid}
|
||||||
|
method: get
|
||||||
|
|
||||||
|
inviteTeammate:
|
||||||
|
handler: src/modules/minglaradmin/handlers/settings/teammates/inviteTeammate.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/settings/teammates/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/settings/teammates/invite-teammate
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/getAllHostApplicationForAM.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/get-all-host-applications-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllHostActivityForAdmin:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/getAllActivityOfHost.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/getAllActivityOfHost.handler.*'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/get-all-activity-of-host/{id}
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllOnboardingHostApplications:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/getAllOnboardingHosts.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllOnboardingHostApplications_New:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/getOnboardingNewApplications.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin-new
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllInvitationDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/settings/teammates/getAllInvitationDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/settings/teammates**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/settings/teammates/get-all-invitation-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
addSuggestion:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/add-suggestion
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllCoadminAndAMDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/settings/teammates/getAllCoadminAndAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/settings/teammates/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/settings/teammates/get-all-coadmin-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllInvitedCoadminAndAMDetails:
|
||||||
|
handler: src/modules/minglaradmin/handlers/settings/teammates/getAllInvitedCoadminAndAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/settings/teammates/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/settings/teammates/get-all-invited-coadmin-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAmDetailsbyId:
|
||||||
|
handler: src/modules/minglaradmin/handlers/getAmDetail_ById.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/settings/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/settings/teammates/get-am-details-by-id/{amXid}
|
||||||
|
method: get
|
||||||
|
|
||||||
|
assignAMToHost:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/assignAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/assign-am
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
editAgreementDetailsAndAccept:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/editAgreementDetailsAndAccept.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
getAllPqqQuesAnsForAM:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
||||||
|
method: get
|
||||||
|
|
||||||
|
acceptHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptHostApplication.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/accept-host-application
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
RejectPQQByAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectPQQbyAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/reject-pq-by-am
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
acceptPQByAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/accept-pq-by-am
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
rejectHostApplication:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/rejectHostApplication.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/reject-host-application
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
rejectHostApplicationAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectHostApplicationAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/reject-host-application-am
|
||||||
|
method: patch
|
||||||
|
|
||||||
|
addPQQSuggestion:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllPQPDetailsForAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
|
||||||
|
method: get
|
||||||
|
|
||||||
|
|
||||||
|
getSuggestionsForAM:
|
||||||
|
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/showSuggestionToAM.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/showSuggestionToAM**'
|
||||||
|
- 'src/modules/minglaradmin/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /minglaradmin/hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
||||||
|
method: get
|
||||||
29
serverless/functions/pqq.yml
Normal file
29
serverless/functions/pqq.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
createActivityAndAllQuestionsEntry:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/create-activity
|
||||||
|
method: post
|
||||||
|
|
||||||
|
submitPQAnswer:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/submit-pq-answer
|
||||||
|
method: patch
|
||||||
109
serverless/functions/prepopulate.yml
Normal file
109
serverless/functions/prepopulate.yml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Prepopulate Module Functions
|
||||||
|
# Reference data and lookup endpoints
|
||||||
|
|
||||||
|
getAllBankAndCurrencyDetails:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllBankDetails.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-bank-currency-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getCityByState:
|
||||||
|
handler: src/modules/prepopulate/handlers/getCityByState.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/handlers/getCityByState.*'
|
||||||
|
- 'src/modules/prepopulate/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-city-by-state
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getBranchByBankXid:
|
||||||
|
handler: src/modules/prepopulate/handlers/getBranchByBank.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/handlers/getBranchByBank.*'
|
||||||
|
- 'src/modules/prepopulate/services/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-branch-by-bank
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllDocumentCountryStateCityDetails:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllDocTypeWithCountryState.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-doc-country
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAllPqqQuesAns:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllPQQQuesWithAns.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-pqq-ques-ans
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getFrequenciesOfActivity:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAllFrequencies.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-all-Frequencies
|
||||||
|
method: get
|
||||||
|
|
||||||
|
getAddActivityPrePopulate:
|
||||||
|
handler: src/modules/prepopulate/handlers/getAddActivityPrePopulate.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/prepopulate/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /prepopulate/get-add-activity-prepopulate
|
||||||
|
method: get
|
||||||
18
serverless/patterns/aws-s3.yml
Normal file
18
serverless/patterns/aws-s3.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# AWS S3 SDK packaging patterns (merged from aws-s3.yml and aws-s3-full.yml)
|
||||||
|
# Comprehensive list of AWS S3 dependencies for Lambda packaging
|
||||||
|
pattern1: 'node_modules/@aws-sdk/client-s3/**'
|
||||||
|
pattern2: 'node_modules/@aws-sdk/s3-request-presigner/**'
|
||||||
|
pattern3: 'node_modules/@aws-sdk/types/**'
|
||||||
|
pattern4: 'node_modules/@aws-sdk/middleware-logger/**'
|
||||||
|
pattern5: 'node_modules/@aws-sdk/**'
|
||||||
|
pattern6: 'node_modules/@smithy/**'
|
||||||
|
pattern7: 'node_modules/tslib/**'
|
||||||
|
pattern8: 'node_modules/uuid/**'
|
||||||
|
pattern9: 'node_modules/@aws-crypto/**'
|
||||||
|
pattern10: 'node_modules/@aws/smithy-client/**'
|
||||||
|
pattern11: 'node_modules/@aws/util-uri-escape/**'
|
||||||
|
pattern12: 'node_modules/@aws/util-middleware/**'
|
||||||
|
pattern13: 'node_modules/@aws/lambda-invoke-store/**'
|
||||||
|
pattern14: 'node_modules/busboy/**'
|
||||||
|
pattern15: 'node_modules/lambda-multipart-parser/**'
|
||||||
|
pattern16: 'node_modules/fast-xml-parser/**'
|
||||||
9
serverless/patterns/base.yml
Normal file
9
serverless/patterns/base.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Base packaging patterns shared across all functions
|
||||||
|
pattern1: 'src/common/**'
|
||||||
|
pattern2: 'common/**'
|
||||||
|
# REMOVED: Prisma is now provided by Lambda layer - DO NOT include in function packages
|
||||||
|
# pattern3: 'node_modules/@prisma/client/**'
|
||||||
|
# pattern4: 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node'
|
||||||
|
pattern3: '!node_modules/@prisma/**'
|
||||||
|
pattern4: '!node_modules/.prisma/**'
|
||||||
|
pattern5: '!node_modules/.prisma/client/libquery_engine*'
|
||||||
29
src/common/database/prisma.client.ts
Normal file
29
src/common/database/prisma.client.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
|
|
||||||
|
// Singleton pattern for Prisma client - prevents "Too many database connections" error
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createPrismaClient() {
|
||||||
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||||
|
|
||||||
|
return new PrismaClient({
|
||||||
|
adapter,
|
||||||
|
log: process.env.NODE_ENV === 'dev' ? ['query', 'info', 'warn', 'error'] : ['error'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
globalForPrisma.prisma = prisma;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For serverless environments, always cache the client
|
||||||
|
if (process.env.IS_OFFLINE || process.env.AWS_LAMBDA_FUNCTION_NAME) {
|
||||||
|
globalForPrisma.prisma = prisma;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
5
src/common/database/prisma.lambda.service.ts
Normal file
5
src/common/database/prisma.lambda.service.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Re-export the singleton prisma client for Lambda handlers
|
||||||
|
// This ensures all Lambda functions use the same cached connection
|
||||||
|
import { prisma } from './prisma.client';
|
||||||
|
|
||||||
|
export const prismaClient = prisma;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.lambda.service';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import { Injectable, OnModuleInit, OnModuleDestroy, INestApplication } from '@nestjs/common';
|
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { prisma } from './prisma.client';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super();
|
||||||
datasources: {
|
// Use the singleton instance
|
||||||
db: {
|
Object.assign(this, prisma);
|
||||||
url: process.env.DATABASE_URL,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
log: process.env.NODE_ENV === 'dev' ? ['query', 'info', 'warn', 'error'] : ['error'],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
@@ -21,11 +17,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul
|
|||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
await this.$disconnect();
|
await this.$disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableShutdownHooks(app: INestApplication) {
|
|
||||||
process.on('beforeExit', async () => {
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
src/common/email/brevoApi.ts
Normal file
46
src/common/email/brevoApi.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import config from '../../config/config';
|
||||||
|
|
||||||
|
interface EmailRecipient {
|
||||||
|
email: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmailOptions {
|
||||||
|
recipients: EmailRecipient[];
|
||||||
|
subject: string;
|
||||||
|
htmlContent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrevoService {
|
||||||
|
private readonly instance: AxiosInstance;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.instance = axios.create({
|
||||||
|
baseURL: config.email.BrevobaseURL,
|
||||||
|
headers: {
|
||||||
|
'api-key': config.email.api_key,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEmail(options: EmailOptions): Promise<{ messageId: string }> {
|
||||||
|
const response = await this.instance.post('/smtp/email', {
|
||||||
|
sender: {
|
||||||
|
name: 'Minglar',
|
||||||
|
email: 'minglar.admin@minglargroup.com',
|
||||||
|
},
|
||||||
|
to: options.recipients,
|
||||||
|
subject: options.subject,
|
||||||
|
htmlContent: options.htmlContent,
|
||||||
|
replyTo: {
|
||||||
|
email: 'minglar.admin@minglargroup.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const brevoService = new BrevoService();
|
||||||
18
src/common/middlewares/aws/getPreSignedUrl.ts
Normal file
18
src/common/middlewares/aws/getPreSignedUrl.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// common/utils/awsPresign.ts
|
||||||
|
import config from "@/config/config";
|
||||||
|
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: config.aws.region, // e.g. ap-south-1
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getPresignedUrl = async (bucket: string, key: string) => {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
// URL valid for 1 hour
|
||||||
|
return await getSignedUrl(s3, command, { expiresIn: 3600 });
|
||||||
|
};
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import httpStatus from 'http-status';
|
import httpStatus from 'http-status';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import ApiError from '../../utils/helper/ApiError';
|
import ApiError from '../../utils/helper/ApiError';
|
||||||
import config from '../../../config/config';
|
import config from '../../../config/config';
|
||||||
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
||||||
const prisma = new PrismaClient();
|
import { prisma } from '../../database/prisma.client';
|
||||||
|
|
||||||
interface DecodedToken {
|
interface DecodedToken {
|
||||||
id: number;
|
id?: number;
|
||||||
|
sub?: string | number;
|
||||||
role?: string;
|
role?: string;
|
||||||
iat: number;
|
iat: number;
|
||||||
exp: number;
|
exp: number;
|
||||||
@@ -26,7 +26,70 @@ declare module 'express-serve-static-core' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies JWT and validates Host user (role_xid = 3)
|
* Core authentication function - verifies JWT and validates Host user
|
||||||
|
* Can be used by both Express middleware and Lambda handlers
|
||||||
|
*/
|
||||||
|
export async function verifyHostToken(token: string): Promise<{ id: number; role?: string }> {
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||||
|
|
||||||
|
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fetch user from Prisma (Host user only)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: { role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestToken = await prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
userXid: userId
|
||||||
|
},
|
||||||
|
orderBy: { id: 'desc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (latestToken.isBlackListed == true) {
|
||||||
|
throw new ApiError(401, "This session is expired. Please login.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check if user is active
|
||||||
|
if (user.isActive === false) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check Host role (role_xid = 4)
|
||||||
|
if (user.roleXid !== ROLE.HOST) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: user.id, role: user.role?.roleName };
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jwt.TokenExpiredError) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies JWT and validates Host user (role_xid = 4)
|
||||||
*/
|
*/
|
||||||
const verifyCallback = async (
|
const verifyCallback = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -35,69 +98,29 @@ const verifyCallback = async (
|
|||||||
) => {
|
) => {
|
||||||
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
if (!decoded?.id) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Fetch user from Prisma (Host user only)
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { id: decoded.id },
|
|
||||||
include: { role: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check if user is active
|
|
||||||
if (!user.isActive) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check Host role (role_xid = 3)
|
|
||||||
if (user.roleXid !== 3) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach user to request
|
// Attach user to request
|
||||||
req.user = { id: user.id.toString(), role: user.role?.roleName };
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof jwt.TokenExpiredError) {
|
return reject(error as Error);
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Express middleware — use as `auth()` in routes
|
* Express middleware — use as `auth()` in routes
|
||||||
*/
|
*/
|
||||||
const auth =
|
const authForHost =
|
||||||
() =>
|
() =>
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
verifyCallback(req, resolve, reject);
|
verifyCallback(req, resolve, reject);
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch((err) => next(err));
|
.catch((err) => next(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default auth;
|
export default authForHost;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import httpStatus from 'http-status';
|
import httpStatus from 'http-status';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import ApiError from '../../utils/helper/ApiError';
|
import ApiError from '../../utils/helper/ApiError';
|
||||||
import config from '../../../config/config';
|
import config from '../../../config/config';
|
||||||
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
||||||
const prisma = new PrismaClient();
|
import { prisma } from '../../database/prisma.client';
|
||||||
|
|
||||||
interface DecodedToken {
|
interface DecodedToken {
|
||||||
id: number;
|
id?: number;
|
||||||
|
sub?: string | number;
|
||||||
role?: string;
|
role?: string;
|
||||||
iat: number;
|
iat: number;
|
||||||
exp: number;
|
exp: number;
|
||||||
@@ -26,7 +26,71 @@ declare module 'express-serve-static-core' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies JWT and validates Host user (role_xid = 3)
|
* Core authentication function - verifies JWT and validates Host user
|
||||||
|
* Can be used by both Express middleware and Lambda handlers
|
||||||
|
*/
|
||||||
|
export async function verifyMinglarAdminToken(token: string): Promise<{ id: number; role?: string }> {
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||||
|
|
||||||
|
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fetch user from Prisma (Host user only)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: { role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestToken = await prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
userXid: userId
|
||||||
|
},
|
||||||
|
orderBy: { id: 'desc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (latestToken.isBlackListed == true) {
|
||||||
|
throw new ApiError(401, "This session is expired. Please login.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check if user is active
|
||||||
|
if (user.isActive === false) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check Minglar Roles (role_xid = 1, 2, 3)
|
||||||
|
if (![ROLE.MINGLAR_ADMIN, ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER].includes(user.roleXid)) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { id: user.id, role: user.role?.roleName };
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jwt.TokenExpiredError) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies JWT and validates Host user (role_xid = 1)
|
||||||
*/
|
*/
|
||||||
const verifyCallback = async (
|
const verifyCallback = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -35,69 +99,29 @@ const verifyCallback = async (
|
|||||||
) => {
|
) => {
|
||||||
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
if (!decoded?.id) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Fetch user from Prisma (Host user only)
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { id: decoded.id },
|
|
||||||
include: { role: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check if user is active
|
|
||||||
if (!user.isActive) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check Admin role (role_xid = 2)
|
|
||||||
if (user.roleXid !== 2) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach user to request
|
// Attach user to request
|
||||||
req.user = { id: user.id.toString(), role: user.role?.roleName };
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof jwt.TokenExpiredError) {
|
return reject(error as Error);
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Express middleware — use as `auth()` in routes
|
* Express middleware — use as `auth()` in routes
|
||||||
*/
|
*/
|
||||||
const auth =
|
const authForHost =
|
||||||
() =>
|
() =>
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
verifyCallback(req, resolve, reject);
|
verifyCallback(req, resolve, reject);
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch((err) => next(err));
|
.catch((err) => next(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default auth;
|
export default authForHost;
|
||||||
|
|||||||
129
src/common/middlewares/jwt/authForMinglarAdminHost.ts
Normal file
129
src/common/middlewares/jwt/authForMinglarAdminHost.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import httpStatus from 'http-status';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import ApiError from '../../utils/helper/ApiError';
|
||||||
|
import config from '../../../config/config';
|
||||||
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
||||||
|
import { prisma } from '../../database/prisma.client';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface DecodedToken {
|
||||||
|
id?: number;
|
||||||
|
sub?: string | number;
|
||||||
|
role?: string;
|
||||||
|
iat: number;
|
||||||
|
exp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserPayload {
|
||||||
|
id: string;
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'express-serve-static-core' {
|
||||||
|
interface Request {
|
||||||
|
user?: UserPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core authentication function - verifies JWT and validates Host user
|
||||||
|
* Can be used by both Express middleware and Lambda handlers
|
||||||
|
*/
|
||||||
|
export async function verifyMinglarAdminHostToken(token: string): Promise<{ id: number; role?: string }> {
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||||
|
|
||||||
|
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fetch user from Prisma (Host user only)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: { role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestToken = await prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
userXid: userId
|
||||||
|
},
|
||||||
|
orderBy: { id: 'desc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (latestToken.isBlackListed == true) {
|
||||||
|
throw new ApiError(401, "This session is expired. Please login.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check if user is active
|
||||||
|
if (user.isActive === false) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check Minglar Roles (role_xid = 1, 2, 3)
|
||||||
|
if (![ROLE.MINGLAR_ADMIN, ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER, ROLE.HOST].includes(user.roleXid)) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { id: user.id, role: user.role?.roleName };
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jwt.TokenExpiredError) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies JWT and validates Host user (role_xid = 1)
|
||||||
|
*/
|
||||||
|
const verifyCallback = async (
|
||||||
|
req: Request,
|
||||||
|
resolve: (value?: unknown) => void,
|
||||||
|
reject: (reason?: Error) => void
|
||||||
|
) => {
|
||||||
|
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userInfo = await verifyMinglarAdminHostToken(token);
|
||||||
|
|
||||||
|
// Attach user to request
|
||||||
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express middleware — use as `auth()` in routes
|
||||||
|
*/
|
||||||
|
const authForHost =
|
||||||
|
() =>
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
verifyCallback(req, resolve, reject);
|
||||||
|
})
|
||||||
|
.then(() => next())
|
||||||
|
.catch((err) => next(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authForHost;
|
||||||
127
src/common/middlewares/jwt/authForOnlyMinglarAdmin.ts
Normal file
127
src/common/middlewares/jwt/authForOnlyMinglarAdmin.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import httpStatus from 'http-status';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import ApiError from '../../utils/helper/ApiError';
|
||||||
|
import config from '../../../config/config';
|
||||||
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
||||||
|
import { prisma } from '../../database/prisma.client';
|
||||||
|
|
||||||
|
interface DecodedToken {
|
||||||
|
id?: number;
|
||||||
|
sub?: string | number;
|
||||||
|
role?: string;
|
||||||
|
iat: number;
|
||||||
|
exp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserPayload {
|
||||||
|
id: string;
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'express-serve-static-core' {
|
||||||
|
interface Request {
|
||||||
|
user?: UserPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core authentication function - verifies JWT and validates Host user
|
||||||
|
* Can be used by both Express middleware and Lambda handlers
|
||||||
|
*/
|
||||||
|
export async function verifyOnlyMinglarAdminToken(token: string): Promise<{ id: number; role?: string }> {
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||||
|
|
||||||
|
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fetch user from Prisma (Host user only)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: { role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestToken = await prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
userXid: userId
|
||||||
|
},
|
||||||
|
orderBy: { id: 'desc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (latestToken.isBlackListed == true) {
|
||||||
|
throw new ApiError(401, "This session is expired. Please login.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check if user is active
|
||||||
|
if (user.isActive === false) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check Minglar Roles (role_xid = 1)
|
||||||
|
if (user.roleXid !== ROLE.MINGLAR_ADMIN) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { id: user.id, role: user.role?.roleName };
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jwt.TokenExpiredError) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies JWT and validates Host user (role_xid = 1)
|
||||||
|
*/
|
||||||
|
const verifyCallback = async (
|
||||||
|
req: Request,
|
||||||
|
resolve: (value?: unknown) => void,
|
||||||
|
reject: (reason?: Error) => void
|
||||||
|
) => {
|
||||||
|
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Attach user to request
|
||||||
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
return reject(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express middleware — use as `auth()` in routes
|
||||||
|
*/
|
||||||
|
const authForHost =
|
||||||
|
() =>
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
verifyCallback(req, resolve, reject);
|
||||||
|
})
|
||||||
|
.then(() => next())
|
||||||
|
.catch((err) => next(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authForHost;
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import httpStatus from 'http-status';
|
import httpStatus from 'http-status';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import ApiError from '../../utils/helper/ApiError';
|
import ApiError from '../../utils/helper/ApiError';
|
||||||
import config from '../../../config/config';
|
import config from '../../../config/config';
|
||||||
|
import { ROLE } from '@/common/utils/constants/common.constant';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
import { prisma } from '../../database/prisma.client';
|
||||||
|
|
||||||
interface DecodedToken {
|
interface DecodedToken {
|
||||||
id: number;
|
id?: number;
|
||||||
|
sub?: string | number;
|
||||||
role?: string;
|
role?: string;
|
||||||
iat: number;
|
iat: number;
|
||||||
exp: number;
|
exp: number;
|
||||||
@@ -26,7 +27,70 @@ declare module 'express-serve-static-core' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies JWT and validates Host user (role_xid = 3)
|
* Core authentication function - verifies JWT and validates Host user
|
||||||
|
* Can be used by both Express middleware and Lambda handlers
|
||||||
|
*/
|
||||||
|
export async function verifyUserToken(token: string): Promise<{ id: number; role?: string }> {
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
||||||
|
|
||||||
|
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Fetch user from Prisma (Host user only)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: { role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestToken = await prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
userXid: userId
|
||||||
|
},
|
||||||
|
orderBy: { id: 'desc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (latestToken.isBlackListed == true) {
|
||||||
|
throw new ApiError(401, "This session is expired. Please login.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check if user is active
|
||||||
|
if (user.isActive === false) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Check Host role (role_xid = 6)
|
||||||
|
if (user.roleXid !== ROLE.USER) {
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: user.id, role: user.role?.roleName };
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof jwt.TokenExpiredError) {
|
||||||
|
throw new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies JWT and validates Host user (role_xid = 4)
|
||||||
*/
|
*/
|
||||||
const verifyCallback = async (
|
const verifyCallback = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -35,69 +99,29 @@ const verifyCallback = async (
|
|||||||
) => {
|
) => {
|
||||||
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
const token = req.header('x-auth-token') || req.cookies?.accessToken;
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
|
const userInfo = await verifyUserToken(token);
|
||||||
|
|
||||||
if (!decoded?.id) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Fetch user from Prisma (Host user only)
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { id: decoded.id },
|
|
||||||
include: { role: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check if user is active
|
|
||||||
if (!user.isActive) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Check User role (role_xid = 1)
|
|
||||||
if (user.roleXid !== 1) {
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach user to request
|
// Attach user to request
|
||||||
req.user = { id: user.id.toString(), role: user.role?.roleName };
|
req.user = { id: userInfo.id.toString(), role: userInfo.role };
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof jwt.TokenExpiredError) {
|
return reject(error as Error);
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(
|
|
||||||
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Express middleware — use as `auth()` in routes
|
* Express middleware — use as `auth()` in routes
|
||||||
*/
|
*/
|
||||||
const auth =
|
const authForHost =
|
||||||
() =>
|
() =>
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
verifyCallback(req, resolve, reject);
|
verifyCallback(req, resolve, reject);
|
||||||
})
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch((err) => next(err));
|
.catch((err) => next(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default auth;
|
export default authForHost;
|
||||||
|
|||||||
24
src/common/utils/constants/common.constant.ts
Normal file
24
src/common/utils/constants/common.constant.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export const ROLE = {
|
||||||
|
MINGLAR_ADMIN: 1,
|
||||||
|
CO_ADMIN: 2,
|
||||||
|
ACCOUNT_MANAGER: 3,
|
||||||
|
HOST: 4,
|
||||||
|
OPERATOR: 5,
|
||||||
|
USER: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_NAME = {
|
||||||
|
MINGLAR_ADMIN: 'Minglar Admin',
|
||||||
|
CO_ADMIN: 'Co-admin',
|
||||||
|
ACCOUNT_MANAGER: 'Account manager',
|
||||||
|
HOST: 'Host',
|
||||||
|
OPERATOR: 'Operator',
|
||||||
|
USER: 'User'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const USER_STATUS = {
|
||||||
|
INVITED: "Invited",
|
||||||
|
ACTIVE: "Active",
|
||||||
|
DE_ACTIVATED: "De-activated",
|
||||||
|
REJECTED: "Rejected"
|
||||||
|
}
|
||||||
73
src/common/utils/constants/host.constant.ts
Normal file
73
src/common/utils/constants/host.constant.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export const HOST_STATUS_INTERNAL = {
|
||||||
|
HOST_SUBMITTED: "Host Submitted",
|
||||||
|
HOST_TO_UPDATE: "Host To Update",
|
||||||
|
REJECTED: "Rejected",
|
||||||
|
APPROVED: "Approved",
|
||||||
|
DRAFT: "Draft",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HOST_STATUS_DISPLAY = {
|
||||||
|
DRAFT: "Draft",
|
||||||
|
UNDER_REVIEW: "Under Review",
|
||||||
|
ENHANCING: "Enhancing",
|
||||||
|
REJECTED: "Rejected",
|
||||||
|
APPROVED: "Approved",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STEPPER = {
|
||||||
|
NOT_SUBMITTED: 1,
|
||||||
|
UNDER_REVIEW: 2,
|
||||||
|
COMPANY_DETAILS_APPROVED: 3,
|
||||||
|
BANK_DETAILS_UPDATED: 4,
|
||||||
|
AGREEMENT_ACCEPTED: 5,
|
||||||
|
REJECTED: 6
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVITY_INTERNAL_STATUS = {
|
||||||
|
DRAFT_PQ: 'Draft - PQ',
|
||||||
|
APPROVED: 'Approved',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
DRAFT: 'Draft',
|
||||||
|
UNDER_REVIEW: 'Under-Review',
|
||||||
|
PQ_FAILED: 'PQ Failed',
|
||||||
|
PQ_TO_UPDATE: 'PQ To Update',
|
||||||
|
PQ_SUBMITTED: 'PQ Submitted',
|
||||||
|
PQ_APPROVED: 'PQ Approved'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVITY_DISPLAY_STATUS = {
|
||||||
|
DRAFT_PQ: 'Draft - PQ',
|
||||||
|
APPROVED: 'Approved',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
DRAFT: 'Draft',
|
||||||
|
UNDER_REVIEW: 'Under-Review',
|
||||||
|
PQ_FAILED: 'PQ Failed',
|
||||||
|
ENHANCING: 'Enchancing',
|
||||||
|
PQ_IN_REVIEW: 'PQ In Review',
|
||||||
|
PQ_APPROVED: 'PQ Approved'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVITY_AM_INTERNAL_STATUS = {
|
||||||
|
DRAFT_PQ: 'Draft - PQ',
|
||||||
|
APPROVED: 'Approved',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
DRAFT: 'Draft',
|
||||||
|
UNDER_REVIEW: 'Under-Review',
|
||||||
|
PQ_FAILED: 'PQ Failed',
|
||||||
|
PQ_REJECTED: 'PQ Rejected',
|
||||||
|
PQ_TO_REVIEW: 'PQ To Review',
|
||||||
|
PQ_APPROVED: 'PQ Approved'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVITY_AM_DISPLAY_STATUS = {
|
||||||
|
DRAFT_PQ: 'Draft - PQ',
|
||||||
|
APPROVED: 'Approved',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
DRAFT: 'Draft',
|
||||||
|
UNDER_REVIEW: 'Under-Review',
|
||||||
|
PQ_FAILED: 'PQ Failed',
|
||||||
|
ENHANCING: 'Enchancing',
|
||||||
|
NEW: 'New',
|
||||||
|
PQ_APPROVED: 'PQ Approved',
|
||||||
|
REVISED: 'Revised'
|
||||||
|
}
|
||||||
59
src/common/utils/constants/minglar.constant.ts
Normal file
59
src/common/utils/constants/minglar.constant.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
export const MINGLAR_STATUS_INTERNAL = {
|
||||||
|
ADMIN_TO_REVIEW: 'Admin To Review',
|
||||||
|
ADMIN_REJECTED: 'Admin Rejected',
|
||||||
|
AM_NOT_ASSIGNED: 'AM Not Assigned',
|
||||||
|
AM_TO_REVIEW: 'AM To Review',
|
||||||
|
AM_REJECTED: 'AM Rejected',
|
||||||
|
AM_APPROVED: 'AM Approved',
|
||||||
|
DRAFT: 'Draft',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MINGLAR_STATUS_DISPLAY = {
|
||||||
|
NEW: 'New',
|
||||||
|
AM_NOT_ASSIGNED: 'AM Not Assigned',
|
||||||
|
TO_REVIEW: 'To Review',
|
||||||
|
ENHANCING: 'Enhancing',
|
||||||
|
APPROVED: 'Approved',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
DRAFT: 'Draft'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MINGLAR_INVITATION_STATUS = {
|
||||||
|
PENDING: 'Pending',
|
||||||
|
ACCEPTED: 'Accepted',
|
||||||
|
REJECTED: 'Rejected',
|
||||||
|
INVITED: 'Invited',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACTIVITY_TRACK_TYPE = {
|
||||||
|
PQ: 'PQ',
|
||||||
|
ACTIVITY: 'Activity'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVITY_TRACK_STATUS = {
|
||||||
|
REJECTED_BY_AM: 'Rejected By AM',
|
||||||
|
ACCEPTED_BY_AM: 'Accepted By AM',
|
||||||
|
ENHANCING: 'Enhancing',
|
||||||
|
PQ_SUBMITTED: 'PQ Submitted'
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const HOST_SUGGESTION_TITLES = {
|
||||||
|
// COMPANY_DETAILS: 'Complete Details',
|
||||||
|
// COMPANY_DOCUMENTATION: 'Company documentataion',
|
||||||
|
// COMPANY_SOCIAL_PROOF: 'Social Proof',
|
||||||
|
// ACTIVITY_INFORMATION: 'Activity Information',
|
||||||
|
// ACTIVITY_LOCATION: 'Activity Location',
|
||||||
|
// PICKUP_DROP_LOCATION: 'Pickup-Drop Location',
|
||||||
|
// NUMBER_OF_PEOPLE: 'Number of People',
|
||||||
|
// INCLUSION: 'Inclusion',
|
||||||
|
// TAX_SETUP: 'Tax Setup',
|
||||||
|
// ENERGY_LEVEL: 'Energy Level',
|
||||||
|
// ELIGIBILITY_CRITERIA: 'Eligibility Criteria',
|
||||||
|
// AMENITIES: 'Amenities',
|
||||||
|
// EXLUSIVE_NOTES: 'Exclusive Notes',
|
||||||
|
// CANCELLATION_POLICY: 'Cancellation Policy',
|
||||||
|
// DOs_AND_DONTs: 'Do’s and Dont’s',
|
||||||
|
// TIPS_FOR_USERS: 'Tips for Users',
|
||||||
|
// SUSTAINABILITY: 'Sustainability',
|
||||||
|
// TERMS_AND_CONDITION_FOR_USER: 'Terms and Conditions for User'
|
||||||
|
// };
|
||||||
@@ -2,22 +2,68 @@
|
|||||||
import { APIGatewayProxyEvent, Context, APIGatewayProxyResult } from 'aws-lambda';
|
import { APIGatewayProxyEvent, Context, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
import ApiError from '../helper/ApiError';
|
import ApiError from '../helper/ApiError';
|
||||||
|
|
||||||
const stage = process.env.STAGE ?? 'dev';
|
|
||||||
|
|
||||||
export const safeHandler = (
|
export const safeHandler = (
|
||||||
handler: (event: APIGatewayProxyEvent, context?: Context) => Promise<APIGatewayProxyResult | undefined>
|
handler: (event: APIGatewayProxyEvent, context?: Context) => Promise<APIGatewayProxyResult | any>
|
||||||
): ((event: APIGatewayProxyEvent, context: Context) => Promise<APIGatewayProxyResult>) => {
|
): ((event: APIGatewayProxyEvent, context: Context) => Promise<APIGatewayProxyResult>) => {
|
||||||
return async (event, context) => {
|
return async (event, context) => {
|
||||||
try {
|
try {
|
||||||
const result = await handler(event, context);
|
const result = await handler(event, context);
|
||||||
return (
|
|
||||||
result ?? {
|
// If handler returned null/undefined → return 204 response
|
||||||
|
if (!result) {
|
||||||
|
return {
|
||||||
statusCode: 204,
|
statusCode: 204,
|
||||||
body: '',
|
body: JSON.stringify({
|
||||||
}
|
success: true,
|
||||||
);
|
statusCode: 204,
|
||||||
|
message: 'No content',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If handler returned a structured Lambda response
|
||||||
|
if (result.statusCode && result.body) {
|
||||||
|
return {
|
||||||
|
statusCode: result.statusCode,
|
||||||
|
headers: result.headers || {},
|
||||||
|
body: injectStatusCodeIntoBody(result.body, result.statusCode),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If handler returned plain data (not wrapped)
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'OK',
|
||||||
|
statusCode: 200,
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error occurred:', error);
|
console.error('❌ Error occurred:', error);
|
||||||
|
|
||||||
|
// Convert Prisma errors to ApiError automatically
|
||||||
|
if (ApiError.isPrismaError(error)) {
|
||||||
|
const apiError = ApiError.fromPrismaError(error);
|
||||||
|
return {
|
||||||
|
statusCode: apiError.statusCode,
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
message: apiError.message,
|
||||||
|
statusCode: apiError.statusCode,
|
||||||
|
data: null,
|
||||||
|
error: {
|
||||||
|
code: apiError.code || apiError.statusCode,
|
||||||
|
description: apiError.message,
|
||||||
|
statusCode: apiError.statusCode,
|
||||||
|
...(apiError.meta && { meta: apiError.meta }),
|
||||||
|
...(process.env.STAGE !== 'prod' && { debug: apiError.stack }),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof ApiError) {
|
if (error instanceof ApiError) {
|
||||||
return {
|
return {
|
||||||
@@ -25,31 +71,53 @@ export const safeHandler = (
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
statusCode: error.statusCode,
|
||||||
data: null,
|
data: null,
|
||||||
error: {
|
error: {
|
||||||
code: error.statusCode,
|
code: error.code || error.statusCode,
|
||||||
description: error.message,
|
description: error.message,
|
||||||
statusCode: error.statusCode,
|
statusCode: error.statusCode,
|
||||||
...(process.env.STAGE !== 'prod' && { debug: error.stack ?? error.message }),
|
...(error.meta && { meta: error.meta }),
|
||||||
|
...(process.env.STAGE !== 'prod' && { debug: error.stack }),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal Server Error fallback
|
||||||
return {
|
return {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Internal server error',
|
message: 'Internal server error',
|
||||||
|
statusCode: 500,
|
||||||
data: null,
|
data: null,
|
||||||
error: {
|
error: {
|
||||||
code: 500,
|
code: 500,
|
||||||
description: 'Internal server error',
|
description: 'Internal server error',
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
...(process.env.STAGE !== 'prod' && { debug: error.stack ?? error.message }),
|
...(process.env.STAGE !== 'prod' && { debug: error.stack }),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Utility: safely inject statusCode into the JSON response body
|
||||||
|
function injectStatusCodeIntoBody(body: string, statusCode: number): string {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(body);
|
||||||
|
json.statusCode = statusCode;
|
||||||
|
return JSON.stringify(json);
|
||||||
|
} catch {
|
||||||
|
// If body is not JSON, wrap it
|
||||||
|
return JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
statusCode,
|
||||||
|
message: body,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,75 @@
|
|||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma error code mappings to user-friendly messages and HTTP status codes
|
||||||
|
*/
|
||||||
|
const PRISMA_ERROR_CODES: Record<string, { statusCode: number; message: string }> = {
|
||||||
|
// Common errors (P1xxx)
|
||||||
|
P1000: { statusCode: 500, message: 'Authentication failed against database server' },
|
||||||
|
P1001: { statusCode: 503, message: 'Database server is not reachable' },
|
||||||
|
P1002: { statusCode: 504, message: 'Database server timed out' },
|
||||||
|
P1003: { statusCode: 500, message: 'Database does not exist' },
|
||||||
|
P1008: { statusCode: 504, message: 'Database operation timed out' },
|
||||||
|
P1009: { statusCode: 409, message: 'Database already exists' },
|
||||||
|
P1010: { statusCode: 403, message: 'User was denied access to the database' },
|
||||||
|
P1011: { statusCode: 500, message: 'Error opening a TLS connection' },
|
||||||
|
P1012: { statusCode: 500, message: 'Schema validation error' },
|
||||||
|
P1013: { statusCode: 400, message: 'Invalid database connection string' },
|
||||||
|
P1014: { statusCode: 500, message: 'Underlying model does not exist' },
|
||||||
|
P1015: { statusCode: 500, message: 'Database schema uses unsupported features' },
|
||||||
|
P1016: { statusCode: 400, message: 'Raw query has incorrect number of parameters' },
|
||||||
|
P1017: { statusCode: 503, message: 'Database server has closed the connection' },
|
||||||
|
|
||||||
|
// Prisma Client Query Engine errors (P2xxx)
|
||||||
|
P2000: { statusCode: 400, message: 'Value too long for column' },
|
||||||
|
P2001: { statusCode: 404, message: 'Record not found' },
|
||||||
|
P2002: { statusCode: 409, message: 'Unique constraint violation' },
|
||||||
|
P2003: { statusCode: 409, message: 'Foreign key constraint violation' },
|
||||||
|
P2004: { statusCode: 400, message: 'Database constraint violation' },
|
||||||
|
P2005: { statusCode: 400, message: 'Invalid value stored in database' },
|
||||||
|
P2006: { statusCode: 400, message: 'Invalid value provided' },
|
||||||
|
P2007: { statusCode: 400, message: 'Data validation error' },
|
||||||
|
P2008: { statusCode: 400, message: 'Failed to parse the query' },
|
||||||
|
P2009: { statusCode: 400, message: 'Failed to validate the query' },
|
||||||
|
P2010: { statusCode: 500, message: 'Raw query failed' },
|
||||||
|
P2011: { statusCode: 400, message: 'Null constraint violation' },
|
||||||
|
P2012: { statusCode: 400, message: 'Missing required value' },
|
||||||
|
P2013: { statusCode: 400, message: 'Missing required argument' },
|
||||||
|
P2014: { statusCode: 409, message: 'Required relation violation' },
|
||||||
|
P2015: { statusCode: 404, message: 'Related record not found' },
|
||||||
|
P2016: { statusCode: 400, message: 'Query interpretation error' },
|
||||||
|
P2017: { statusCode: 400, message: 'Records for relation not connected' },
|
||||||
|
P2018: { statusCode: 404, message: 'Required connected records not found' },
|
||||||
|
P2019: { statusCode: 400, message: 'Input error' },
|
||||||
|
P2020: { statusCode: 400, message: 'Value out of range' },
|
||||||
|
P2021: { statusCode: 500, message: 'Table does not exist' },
|
||||||
|
P2022: { statusCode: 500, message: 'Column does not exist' },
|
||||||
|
P2023: { statusCode: 500, message: 'Inconsistent column data' },
|
||||||
|
P2024: { statusCode: 503, message: 'Connection pool timeout' },
|
||||||
|
P2025: { statusCode: 404, message: 'Record not found' },
|
||||||
|
P2026: { statusCode: 400, message: 'Unsupported database feature used in query' },
|
||||||
|
P2027: { statusCode: 500, message: 'Multiple database errors occurred' },
|
||||||
|
P2028: { statusCode: 500, message: 'Transaction API error' },
|
||||||
|
P2029: { statusCode: 400, message: 'Query parameter limit exceeded' },
|
||||||
|
P2030: { statusCode: 400, message: 'Fulltext index not found' },
|
||||||
|
P2031: { statusCode: 500, message: 'MongoDB requires replica set' },
|
||||||
|
P2033: { statusCode: 400, message: 'Number does not fit in 64 bit signed integer' },
|
||||||
|
P2034: { statusCode: 409, message: 'Transaction failed due to write conflict or deadlock' },
|
||||||
|
P2035: { statusCode: 500, message: 'Database assertion violation' },
|
||||||
|
P2036: { statusCode: 500, message: 'External connector error' },
|
||||||
|
P2037: { statusCode: 503, message: 'Too many database connections opened' },
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PrismaErrorMeta {
|
||||||
|
target?: string[];
|
||||||
|
field_name?: string;
|
||||||
|
model_name?: string;
|
||||||
|
argument_name?: string;
|
||||||
|
constraint?: string;
|
||||||
|
cause?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
class ApiError<T = unknown> extends Error {
|
class ApiError<T = unknown> extends Error {
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
data: T | null;
|
data: T | null;
|
||||||
@@ -6,13 +78,17 @@ class ApiError<T = unknown> extends Error {
|
|||||||
errors: Array<Error>;
|
errors: Array<Error>;
|
||||||
isOperational: boolean;
|
isOperational: boolean;
|
||||||
stack?: string;
|
stack?: string;
|
||||||
|
code?: string;
|
||||||
|
meta?: PrismaErrorMeta;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
message: string = 'Something went wrong',
|
message: string = 'Something went wrong',
|
||||||
errors: Array<Error> = [],
|
errors: Array<Error> = [],
|
||||||
isOperational: boolean = true,
|
isOperational: boolean = true,
|
||||||
stack?: string
|
stack?: string,
|
||||||
|
code?: string,
|
||||||
|
meta?: PrismaErrorMeta
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.statusCode = statusCode;
|
this.statusCode = statusCode;
|
||||||
@@ -21,6 +97,8 @@ class ApiError<T = unknown> extends Error {
|
|||||||
this.success = false;
|
this.success = false;
|
||||||
this.errors = errors;
|
this.errors = errors;
|
||||||
this.isOperational = isOperational;
|
this.isOperational = isOperational;
|
||||||
|
this.code = code;
|
||||||
|
this.meta = meta;
|
||||||
|
|
||||||
if (stack) {
|
if (stack) {
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
@@ -28,6 +106,159 @@ class ApiError<T = unknown> extends Error {
|
|||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ApiError from a Prisma error
|
||||||
|
* Handles all Prisma error types: PrismaClientKnownRequestError, PrismaClientUnknownRequestError,
|
||||||
|
* PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientValidationError
|
||||||
|
*/
|
||||||
|
static fromPrismaError(error: unknown): ApiError {
|
||||||
|
// Handle PrismaClientKnownRequestError
|
||||||
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
|
const errorInfo = PRISMA_ERROR_CODES[error.code] || {
|
||||||
|
statusCode: 500,
|
||||||
|
message: 'Database operation failed',
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = errorInfo.message;
|
||||||
|
const meta = error.meta as PrismaErrorMeta | undefined;
|
||||||
|
|
||||||
|
// Provide more specific messages based on error code and meta
|
||||||
|
switch (error.code) {
|
||||||
|
case 'P2002': {
|
||||||
|
const target = meta?.target;
|
||||||
|
if (target && Array.isArray(target)) {
|
||||||
|
message = `Unique constraint violation on field(s): ${target.join(', ')}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P2003': {
|
||||||
|
const fieldName = meta?.field_name;
|
||||||
|
if (fieldName) {
|
||||||
|
message = `Foreign key constraint failed on field: ${fieldName}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P2025': {
|
||||||
|
const cause = meta?.cause;
|
||||||
|
if (cause) {
|
||||||
|
message = `Record not found: ${cause}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P2011': {
|
||||||
|
const constraint = meta?.constraint;
|
||||||
|
if (constraint) {
|
||||||
|
message = `Null constraint violation on: ${constraint}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P2014': {
|
||||||
|
const modelName = meta?.model_name;
|
||||||
|
if (modelName) {
|
||||||
|
message = `Required relation violation on model: ${modelName}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ApiError(
|
||||||
|
errorInfo.statusCode,
|
||||||
|
message,
|
||||||
|
[error],
|
||||||
|
true,
|
||||||
|
error.stack,
|
||||||
|
error.code,
|
||||||
|
meta
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PrismaClientUnknownRequestError
|
||||||
|
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
|
||||||
|
return new ApiError(
|
||||||
|
500,
|
||||||
|
'An unknown database error occurred',
|
||||||
|
[error],
|
||||||
|
true,
|
||||||
|
error.stack,
|
||||||
|
'UNKNOWN_REQUEST_ERROR'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PrismaClientRustPanicError
|
||||||
|
if (error instanceof Prisma.PrismaClientRustPanicError) {
|
||||||
|
return new ApiError(
|
||||||
|
500,
|
||||||
|
'A critical database error occurred. Please try again later.',
|
||||||
|
[error],
|
||||||
|
false,
|
||||||
|
error.stack,
|
||||||
|
'RUST_PANIC_ERROR'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PrismaClientInitializationError
|
||||||
|
if (error instanceof Prisma.PrismaClientInitializationError) {
|
||||||
|
const errorInfo = error.errorCode
|
||||||
|
? PRISMA_ERROR_CODES[error.errorCode] || { statusCode: 500, message: 'Database initialization failed' }
|
||||||
|
: { statusCode: 500, message: 'Database initialization failed' };
|
||||||
|
|
||||||
|
return new ApiError(
|
||||||
|
errorInfo.statusCode,
|
||||||
|
errorInfo.message,
|
||||||
|
[error],
|
||||||
|
false,
|
||||||
|
error.stack,
|
||||||
|
error.errorCode || 'INITIALIZATION_ERROR'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PrismaClientValidationError
|
||||||
|
if (error instanceof Prisma.PrismaClientValidationError) {
|
||||||
|
return new ApiError(
|
||||||
|
400,
|
||||||
|
'Invalid data provided for database operation',
|
||||||
|
[error],
|
||||||
|
true,
|
||||||
|
error.stack,
|
||||||
|
'VALIDATION_ERROR'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for any other error
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return new ApiError(500, error.message, [error], true, error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ApiError(500, 'An unexpected error occurred');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error is a Prisma error
|
||||||
|
*/
|
||||||
|
static isPrismaError(error: unknown): boolean {
|
||||||
|
return (
|
||||||
|
error instanceof Prisma.PrismaClientKnownRequestError ||
|
||||||
|
error instanceof Prisma.PrismaClientUnknownRequestError ||
|
||||||
|
error instanceof Prisma.PrismaClientRustPanicError ||
|
||||||
|
error instanceof Prisma.PrismaClientInitializationError ||
|
||||||
|
error instanceof Prisma.PrismaClientValidationError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user-friendly message for a Prisma error code
|
||||||
|
*/
|
||||||
|
static getPrismaErrorMessage(code: string): string {
|
||||||
|
return PRISMA_ERROR_CODES[code]?.message || 'Database operation failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTTP status code for a Prisma error code
|
||||||
|
*/
|
||||||
|
static getPrismaErrorStatusCode(code: string): number {
|
||||||
|
return PRISMA_ERROR_CODES[code]?.statusCode || 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApiError;
|
export default ApiError;
|
||||||
|
|||||||
37
src/common/utils/helper/CodeGenerator.ts
Normal file
37
src/common/utils/helper/CodeGenerator.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
const algorithm = 'aes-256-cbc';
|
||||||
|
const secretKey = crypto.scryptSync('your-secret-password', 'salt', 32);
|
||||||
|
const ivLength = 16;
|
||||||
|
|
||||||
|
// Encrypt function
|
||||||
|
export function encryptUserId(id: string): string {
|
||||||
|
const iv = crypto.randomBytes(ivLength);
|
||||||
|
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
|
||||||
|
let encrypted = cipher.update(id, 'utf8', 'hex');
|
||||||
|
encrypted += cipher.final('hex');
|
||||||
|
return `${iv.toString('hex')}:${encrypted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt function
|
||||||
|
export function decryptUserId(encryptedId: string): string | null {
|
||||||
|
try {
|
||||||
|
const parts = encryptedId.split(':');
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
console.error('Invalid encryptedId format:', encryptedId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iv = Buffer.from(parts[0], 'hex');
|
||||||
|
const encryptedText = Buffer.from(parts[1], 'hex');
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
|
||||||
|
let decrypted = decipher.update(encryptedText);
|
||||||
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||||
|
|
||||||
|
return decrypted.toString('utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Decryption failed:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/common/utils/helper/OtpGenerator.ts
Normal file
18
src/common/utils/helper/OtpGenerator.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import config from '../../../config/config';
|
||||||
|
|
||||||
|
export class OtpGenerator {
|
||||||
|
static generateOtp(): string {
|
||||||
|
if (config.byPassOTP) {
|
||||||
|
return '1234';
|
||||||
|
}
|
||||||
|
return Math.floor(1000 + Math.random() * 9000).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OtpGeneratorSixDigit {
|
||||||
|
static generateOtp(): string {
|
||||||
|
if (config.byPassOTP) {
|
||||||
|
return '123456';
|
||||||
|
}
|
||||||
|
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/common/utils/helper/parseMultipartFormData.ts
Normal file
126
src/common/utils/helper/parseMultipartFormData.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import ApiError from './ApiError';
|
||||||
|
|
||||||
|
interface ParsedFormData {
|
||||||
|
fields: Record<string, string>;
|
||||||
|
files: Array<{
|
||||||
|
fieldName: string;
|
||||||
|
fileName: string;
|
||||||
|
contentType: string;
|
||||||
|
data: Buffer;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse multipart/form-data from Lambda event
|
||||||
|
* Supports both base64 encoded and binary body
|
||||||
|
*/
|
||||||
|
export function parseMultipartFormData(
|
||||||
|
eventBody: string | null,
|
||||||
|
contentType: string | undefined,
|
||||||
|
isBase64Encoded: boolean = false
|
||||||
|
): ParsedFormData {
|
||||||
|
if (!eventBody) {
|
||||||
|
throw new ApiError(400, 'Request body is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentType || !contentType.includes('multipart/form-data')) {
|
||||||
|
throw new ApiError(400, 'Content-Type must be multipart/form-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract boundary from Content-Type header
|
||||||
|
const boundaryMatch = contentType.match(/boundary=([^;]+)/);
|
||||||
|
if (!boundaryMatch) {
|
||||||
|
throw new ApiError(400, 'Invalid multipart boundary');
|
||||||
|
}
|
||||||
|
const boundary = boundaryMatch[1].trim();
|
||||||
|
|
||||||
|
// Decode base64 body if needed (API Gateway sends base64 encoded for binary media types)
|
||||||
|
let bodyBuffer: Buffer;
|
||||||
|
try {
|
||||||
|
if (isBase64Encoded) {
|
||||||
|
bodyBuffer = Buffer.from(eventBody, 'base64');
|
||||||
|
} else {
|
||||||
|
// Try to detect if it's base64
|
||||||
|
if (eventBody.match(/^[A-Za-z0-9+/=]+$/)) {
|
||||||
|
bodyBuffer = Buffer.from(eventBody, 'base64');
|
||||||
|
} else {
|
||||||
|
bodyBuffer = Buffer.from(eventBody, 'binary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid request body encoding');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by boundary
|
||||||
|
const parts = bodyBuffer.toString('binary').split(`--${boundary}`);
|
||||||
|
|
||||||
|
const fields: Record<string, string> = {};
|
||||||
|
const files: ParsedFormData['files'] = [];
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!part || part.trim() === '' || part.trim() === '--') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split headers and body
|
||||||
|
const [headers, ...bodyParts] = part.split('\r\n\r\n');
|
||||||
|
if (!headers || bodyParts.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = bodyParts.join('\r\n\r\n').trim();
|
||||||
|
if (!body) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Content-Disposition header
|
||||||
|
const contentDispositionMatch = headers.match(/Content-Disposition:\s*form-data;\s*name="([^"]+)"/);
|
||||||
|
if (!contentDispositionMatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldName = contentDispositionMatch[1];
|
||||||
|
|
||||||
|
// Check if it's a file
|
||||||
|
const filenameMatch = headers.match(/filename="([^"]+)"/);
|
||||||
|
const contentTypeMatch = headers.match(/Content-Type:\s*([^\r\n]+)/);
|
||||||
|
|
||||||
|
if (filenameMatch) {
|
||||||
|
// It's a file
|
||||||
|
const fileName = filenameMatch[1];
|
||||||
|
const fileContentType = contentTypeMatch ? contentTypeMatch[1].trim() : 'application/octet-stream';
|
||||||
|
|
||||||
|
// Convert body to buffer (remove trailing boundary markers)
|
||||||
|
const fileData = Buffer.from(body.replace(/\r\n--$/, ''), 'binary');
|
||||||
|
|
||||||
|
files.push({
|
||||||
|
fieldName,
|
||||||
|
fileName,
|
||||||
|
contentType: fileContentType,
|
||||||
|
data: fileData,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// It's a regular field
|
||||||
|
fields[fieldName] = body.replace(/\r\n--$/, '').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fields, files };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JSON field from form data
|
||||||
|
*/
|
||||||
|
export function parseJsonField(fields: Record<string, string>, fieldName: string): any {
|
||||||
|
const value = fields[fieldName];
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, `Invalid JSON in field: ${fieldName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
62
src/common/utils/helper/resendOtpHelper.ts
Normal file
62
src/common/utils/helper/resendOtpHelper.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as bcrypt from "bcryptjs";
|
||||||
|
import { OtpGenerator, OtpGeneratorSixDigit } from "./OtpGenerator";
|
||||||
|
import { encryptUserId } from "./CodeGenerator";
|
||||||
|
|
||||||
|
export interface OtpResult {
|
||||||
|
otp: string;
|
||||||
|
hashedOtp: string;
|
||||||
|
expiry: Date;
|
||||||
|
encryptedId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resendOtpHelper(
|
||||||
|
prisma: any,
|
||||||
|
userId: number,
|
||||||
|
emailPurpose: "Register" | "Login" | "ForgotPassword",
|
||||||
|
otpLength: 4 | 6 = 4,
|
||||||
|
expiryMinutes: number = 5
|
||||||
|
): Promise<OtpResult> {
|
||||||
|
|
||||||
|
// 1️⃣ Deactivate previous OTPs
|
||||||
|
await prisma.userOtp.updateMany({
|
||||||
|
where: {
|
||||||
|
userXid: userId,
|
||||||
|
otpType: emailPurpose,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
isActive: false,
|
||||||
|
isVerified: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2️⃣ Generate new OTP
|
||||||
|
const otp =
|
||||||
|
otpLength === 6
|
||||||
|
? OtpGeneratorSixDigit.generateOtp()
|
||||||
|
: OtpGenerator.generateOtp();
|
||||||
|
|
||||||
|
const hashedOtp = await bcrypt.hash(otp, 10);
|
||||||
|
const expiry = new Date(Date.now() + expiryMinutes * 60000);
|
||||||
|
const encryptedId = encryptUserId(userId.toString());
|
||||||
|
|
||||||
|
// 3️⃣ Insert new OTP into table
|
||||||
|
await prisma.userOtp.create({
|
||||||
|
data: {
|
||||||
|
userXid: userId,
|
||||||
|
otpType: emailPurpose,
|
||||||
|
otpCode: hashedOtp,
|
||||||
|
expiresOn: expiry,
|
||||||
|
isVerified: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4️⃣ Return new OTP (email will use this)
|
||||||
|
return {
|
||||||
|
otp,
|
||||||
|
hashedOtp,
|
||||||
|
expiry,
|
||||||
|
encryptedId,
|
||||||
|
};
|
||||||
|
}
|
||||||
191
src/common/utils/helper/s3Upload.ts
Normal file
191
src/common/utils/helper/s3Upload.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
dotenv.config({ path: path.join(__dirname, '../../.env') });
|
||||||
|
|
||||||
|
const envVarsSchema = yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
NODE_ENV: yup
|
||||||
|
.string()
|
||||||
|
.oneOf(['production', 'development', 'test'])
|
||||||
|
.required(),
|
||||||
|
PORT: yup.number().default(3000),
|
||||||
|
// FRONTEND_URL: yup.string().required('Frontend URL is required'),
|
||||||
|
//JWT
|
||||||
|
JWT_SECRET: yup.string().required('JWT secret key is required'),
|
||||||
|
JWT_ACCESS_EXPIRATION_MINUTES: yup
|
||||||
|
.number()
|
||||||
|
.default(30)
|
||||||
|
.required('minutes after which access tokens expire'),
|
||||||
|
JWT_REFRESH_EXPIRATION_DAYS: yup
|
||||||
|
.number()
|
||||||
|
.default(30)
|
||||||
|
.required('days after which refresh tokens expire'),
|
||||||
|
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: yup
|
||||||
|
.number()
|
||||||
|
.default(10)
|
||||||
|
.required('minutes after which reset password token expires'),
|
||||||
|
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: yup
|
||||||
|
.number()
|
||||||
|
.default(10)
|
||||||
|
.required('minutes after which verify email token expires'),
|
||||||
|
AWS_REGION: yup.string().required('AWS region is required'),
|
||||||
|
S3_BUCKET_NAME: yup.string().required('S3 bucket name is required'),
|
||||||
|
//SMTP and BREVO
|
||||||
|
// BREVO_SMTP_HOST: yup
|
||||||
|
// .string()
|
||||||
|
// .nullable()
|
||||||
|
// .required('server that will send the emails'),
|
||||||
|
// BREVO_SMTP_PORT: yup
|
||||||
|
// .number()
|
||||||
|
// .nullable()
|
||||||
|
// .required('port to connect to the email server'),
|
||||||
|
// BREVO_SMTP_USER: yup
|
||||||
|
// .string()
|
||||||
|
// .nullable()
|
||||||
|
// .required('username for email server'),
|
||||||
|
// BREVO_SMTP_PASS: yup
|
||||||
|
// .string()
|
||||||
|
// .nullable()
|
||||||
|
// .required('password for email server'),
|
||||||
|
// BREVO_FROM_EMAIL: yup
|
||||||
|
// .string()
|
||||||
|
// .nullable()
|
||||||
|
// .required('the from field in the emails sent by the app'),
|
||||||
|
// BREVO_EMAIL_API_KEY: yup
|
||||||
|
// .string()
|
||||||
|
// .nullable()
|
||||||
|
// .required('the from field in the emails sent by the app api key'),
|
||||||
|
// BREVO_API_BASEURL: yup.string().required('Brevo base URL is required'),
|
||||||
|
// //one signal
|
||||||
|
// ONESIGNAL_APPID: yup.string().required('One signal app id is required'),
|
||||||
|
// ONESIGNAL_REST_APIKEY: yup
|
||||||
|
// .string()
|
||||||
|
// .required('One signal api key is required'),
|
||||||
|
//branch IO
|
||||||
|
// BRANCH_IO_KEY: yup.string().required('Branch IO key is required'),
|
||||||
|
|
||||||
|
// DataBase
|
||||||
|
DB_USERNAME: yup.string().required('DB Username is required'),
|
||||||
|
DB_PASSWORD: yup.string().required('DB Password is required'),
|
||||||
|
DB_DATABASE_NAME: yup.string().required('Database name is required'),
|
||||||
|
DB_HOSTNAME: yup
|
||||||
|
.string()
|
||||||
|
.default('127.0.0.1')
|
||||||
|
.required('DB Hostname is required'),
|
||||||
|
DB_PORT: yup.number().default(3306).required('DB Port is required'),
|
||||||
|
//OTP Bypass
|
||||||
|
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
|
||||||
|
})
|
||||||
|
.noUnknown(true);
|
||||||
|
|
||||||
|
// Validate and prepare the configuration
|
||||||
|
function getConfig() {
|
||||||
|
try {
|
||||||
|
// Validate the environment variables
|
||||||
|
const envVars = envVarsSchema.validateSync(process.env, {
|
||||||
|
abortEarly: false, // Validate all fields before throwing errors
|
||||||
|
stripUnknown: true, // Remove fields not in the schema
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the validated configuration
|
||||||
|
return {
|
||||||
|
env: envVars.NODE_ENV,
|
||||||
|
port: envVars.PORT,
|
||||||
|
jwt: {
|
||||||
|
secret: envVars.JWT_SECRET,
|
||||||
|
accessExpirationMinutes: envVars.JWT_ACCESS_EXPIRATION_MINUTES,
|
||||||
|
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
|
||||||
|
resetPasswordExpirationMinutes:
|
||||||
|
envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
|
||||||
|
verifyEmailExpirationMinutes:
|
||||||
|
envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
development: {
|
||||||
|
host: envVars.DB_HOSTNAME,
|
||||||
|
port: envVars.DB_PORT,
|
||||||
|
username: envVars.DB_USERNAME,
|
||||||
|
password: envVars.DB_PASSWORD,
|
||||||
|
database: envVars.DB_DATABASE_NAME,
|
||||||
|
logging: false,
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
host: envVars.DB_HOSTNAME,
|
||||||
|
port: envVars.DB_PORT,
|
||||||
|
username: envVars.DB_USERNAME,
|
||||||
|
password: envVars.DB_PASSWORD,
|
||||||
|
database: envVars.DB_DATABASE_NAME,
|
||||||
|
logging: false,
|
||||||
|
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
host: envVars.DB_HOSTNAME,
|
||||||
|
port: envVars.DB_PORT,
|
||||||
|
username: envVars.DB_USERNAME,
|
||||||
|
password: envVars.DB_PASSWORD,
|
||||||
|
database: envVars.DB_DATABASE_NAME,
|
||||||
|
logging: false,
|
||||||
|
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aws: {
|
||||||
|
region: envVars.AWS_REGION,
|
||||||
|
bucketName: envVars.S3_BUCKET_NAME,
|
||||||
|
},
|
||||||
|
byPassOTP: envVars.BYPASS_OTP,
|
||||||
|
// BaseURL: envVars.BASEURL,
|
||||||
|
// FRONTEND_URL: envVars.FRONTEND_URL,
|
||||||
|
// email: {
|
||||||
|
// smtp: {
|
||||||
|
// host: envVars?.BREVO_SMTP_HOST,
|
||||||
|
// port: envVars?.BREVO_SMTP_PORT,
|
||||||
|
// secure: envVars?.BREVO_SMTP_PORT == 465, // true for 465, false for other ports
|
||||||
|
// auth: {
|
||||||
|
// user: envVars?.BREVO_SMTP_USER,
|
||||||
|
// pass: envVars?.BREVO_SMTP_PASS,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// from: envVars?.BREVO_FROM_EMAIL,
|
||||||
|
// api_key: envVars?.BREVO_EMAIL_API_KEY,
|
||||||
|
// BrevobaseURL: envVars?.BREVO_API_BASEURL,
|
||||||
|
// },
|
||||||
|
// oneSignal: {
|
||||||
|
// appID: envVars.ONESIGNAL_APPID,
|
||||||
|
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
||||||
|
// },
|
||||||
|
// branchIO: {
|
||||||
|
// branchIOKey: envVars.BRANCH_IO_KEY,
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof yup.ValidationError) {
|
||||||
|
console.error('Validation Errors:', error.errors.join(', '));
|
||||||
|
} else {
|
||||||
|
console.error('Unexpected error during configuration validation:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
'Server shut down due to incomplete environment variable configuration.'
|
||||||
|
);
|
||||||
|
process.exit(1); // Exit with error code 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created By : Angad Chauhan
|
||||||
|
* Created at : 31/1/25
|
||||||
|
* Use : For google login .env file global variable
|
||||||
|
*/
|
||||||
|
// export const googleConfig = {
|
||||||
|
// clientID: process.env.GOOGLE_CLIENT_ID!,
|
||||||
|
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
|
// callbackURL: process.env.GOOGLE_CALLBACK_URL!,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Validate and export configuration only if validation succeeds
|
||||||
|
const config = getConfig();
|
||||||
|
export default config;
|
||||||
|
|
||||||
62
src/common/utils/helper/sendOtp.ts
Normal file
62
src/common/utils/helper/sendOtp.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as bcrypt from "bcryptjs";
|
||||||
|
import { OtpGenerator, OtpGeneratorSixDigit } from "./OtpGenerator";
|
||||||
|
import { encryptUserId } from "./CodeGenerator";
|
||||||
|
|
||||||
|
export interface OtpResult {
|
||||||
|
otp: string;
|
||||||
|
hashedOtp: string;
|
||||||
|
expiry: Date;
|
||||||
|
encryptedId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate OTP using Prisma instance passed from Lambda
|
||||||
|
*/
|
||||||
|
export async function generateOtpHelper(
|
||||||
|
prisma: any, // ⭐ Inject prisma
|
||||||
|
userId: number,
|
||||||
|
email: string,
|
||||||
|
emailPurpose: "Register" | "Login" | "ForgotPassword",
|
||||||
|
otpLength: 4 | 6 = 4,
|
||||||
|
expiryMinutes: number = 5
|
||||||
|
): Promise<OtpResult> {
|
||||||
|
|
||||||
|
const otp =
|
||||||
|
otpLength === 6
|
||||||
|
? OtpGeneratorSixDigit.generateOtp()
|
||||||
|
: OtpGenerator.generateOtp();
|
||||||
|
|
||||||
|
const hashedOtp = await bcrypt.hash(otp, 10);
|
||||||
|
|
||||||
|
const expiry = new Date(Date.now() + expiryMinutes * 60000);
|
||||||
|
|
||||||
|
const encryptedId = encryptUserId(userId.toString());
|
||||||
|
|
||||||
|
// Delete previous active OTPs
|
||||||
|
await prisma.userOtp.deleteMany({
|
||||||
|
where: {
|
||||||
|
userXid: userId,
|
||||||
|
otpType: emailPurpose,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create new OTP entry
|
||||||
|
await prisma.userOtp.create({
|
||||||
|
data: {
|
||||||
|
userXid: userId,
|
||||||
|
otpType: emailPurpose,
|
||||||
|
otpCode: hashedOtp,
|
||||||
|
expiresOn: expiry,
|
||||||
|
isVerified: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
otp,
|
||||||
|
hashedOtp,
|
||||||
|
expiry,
|
||||||
|
encryptedId,
|
||||||
|
};
|
||||||
|
}
|
||||||
66
src/common/utils/pagination/pagination.service.ts
Normal file
66
src/common/utils/pagination/pagination.service.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// common/utils/pagination/pagination.service.ts
|
||||||
|
import { PaginationOptions, PaginationParams, PaginatedResponse } from './pagination.types';
|
||||||
|
|
||||||
|
export class PaginationService {
|
||||||
|
private readonly DEFAULT_PAGE = 1;
|
||||||
|
private readonly DEFAULT_LIMIT = 10;
|
||||||
|
private readonly MAX_LIMIT = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate pagination parameters
|
||||||
|
*/
|
||||||
|
parsePaginationParams(params: PaginationParams): PaginationOptions {
|
||||||
|
let page = Number(params.page) || this.DEFAULT_PAGE;
|
||||||
|
let limit = Number(params.limit) || this.DEFAULT_LIMIT;
|
||||||
|
|
||||||
|
// Validate and constrain values
|
||||||
|
page = Math.max(1, page);
|
||||||
|
limit = Math.max(1, Math.min(limit, this.MAX_LIMIT));
|
||||||
|
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
skip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create paginated response
|
||||||
|
*/
|
||||||
|
createPaginatedResponse<T>(
|
||||||
|
data: T[],
|
||||||
|
totalCount: number,
|
||||||
|
paginationOptions: PaginationOptions,
|
||||||
|
): PaginatedResponse<T> {
|
||||||
|
const { page, limit } = paginationOptions;
|
||||||
|
const totalPages = Math.ceil(totalCount / limit);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
pagination: {
|
||||||
|
currentPage: page,
|
||||||
|
pageSize: limit,
|
||||||
|
totalCount,
|
||||||
|
totalPages,
|
||||||
|
hasNext: page < totalPages,
|
||||||
|
hasPrevious: page > 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract pagination params from API Gateway event
|
||||||
|
*/
|
||||||
|
getPaginationFromEvent(event: any): PaginationParams {
|
||||||
|
const queryParams = event.queryStringParameters || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
page: queryParams.page,
|
||||||
|
limit: queryParams.limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paginationService = new PaginationService();
|
||||||
23
src/common/utils/pagination/pagination.types.ts
Normal file
23
src/common/utils/pagination/pagination.types.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// common/utils/pagination/pagination.types.ts
|
||||||
|
export interface PaginationOptions {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
skip: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
data: T[];
|
||||||
|
pagination: {
|
||||||
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalCount: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrevious: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationParams {
|
||||||
|
page?: string | number;
|
||||||
|
limit?: string | number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// validations/hostBankDetails.validation.ts
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const hostBankDetailsSchema = z.object({
|
||||||
|
accountNumber: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Account number is required"),
|
||||||
|
|
||||||
|
accountHolderName: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Account holder name is required")
|
||||||
|
.min(2, { message: "Account holder name must be at least 2 characters" }),
|
||||||
|
|
||||||
|
bankXid: z
|
||||||
|
.number()
|
||||||
|
.int({ message: "Bank ID must be an integer" })
|
||||||
|
.positive({ message: "Bank ID must be a positive number" }),
|
||||||
|
|
||||||
|
hostXid: z
|
||||||
|
.number()
|
||||||
|
.int({ message: "Host ID must be an integer" })
|
||||||
|
.positive({ message: "Host ID must be a positive number" }),
|
||||||
|
|
||||||
|
bankBranchXid: z
|
||||||
|
.number()
|
||||||
|
.int({ message: "Bank branch ID must be an integer" })
|
||||||
|
.positive({ message: "Bank branch ID must be a positive number" }),
|
||||||
|
|
||||||
|
currencyXid: z
|
||||||
|
.number()
|
||||||
|
.int({ message: "Currency ID must be an integer" })
|
||||||
|
.positive({ message: "Currency ID must be a positive number" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type HostBankDetailsSchema = z.infer<typeof hostBankDetailsSchema>;
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const parentCompanySchema = z.object({
|
||||||
|
companyName: z.string()
|
||||||
|
.max(100, "Parent company name cannot exceed 100 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
address1: z.string()
|
||||||
|
.max(150, "Address1 cannot exceed 150 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
address2: z.string()
|
||||||
|
.max(150, "Address2 cannot exceed 150 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
cityXid: z.number().optional(),
|
||||||
|
stateXid: z.number().optional(),
|
||||||
|
countryXid: z.number().optional(),
|
||||||
|
|
||||||
|
pinCode: z.string()
|
||||||
|
.max(30, "Pincode cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
logoPath: z.string()
|
||||||
|
.max(400, "Logo path cannot exceed 400 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
registrationNumber: z.string()
|
||||||
|
.max(30, "Registration number cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
panNumber: z.string()
|
||||||
|
.max(30, "PAN number cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
gstNumber: z.string()
|
||||||
|
.max(30, "GST number cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
formationDate: z.string()
|
||||||
|
.optional()
|
||||||
|
.refine((val) => !val || !isNaN(Date.parse(val)), {
|
||||||
|
message: "Formation date must be a valid date",
|
||||||
|
}),
|
||||||
|
|
||||||
|
companyTypeXid: z.number()
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
websiteUrl: z.string().nullable().optional(),
|
||||||
|
instagramUrl: z.string().nullable().optional(),
|
||||||
|
facebookUrl: z.string().nullable().optional(),
|
||||||
|
linkedinUrl: z.string().nullable().optional(),
|
||||||
|
twitterUrl: z.string().nullable().optional(),
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================
|
||||||
|
// HOST COMPANY DETAILS (Main Company)
|
||||||
|
// =======================================================
|
||||||
|
|
||||||
|
export const hostCompanyDetailsSchema = z.object({
|
||||||
|
companyName: z.string()
|
||||||
|
.min(1, "Company name is required")
|
||||||
|
.max(100, "Company name cannot exceed 100 characters"),
|
||||||
|
|
||||||
|
address1: z.string()
|
||||||
|
.min(1, "Address1 is required")
|
||||||
|
.max(150, "Address1 cannot exceed 150 characters"),
|
||||||
|
|
||||||
|
address2: z.string()
|
||||||
|
.max(150, "Address2 cannot exceed 150 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
cityXid: z.number().min(1, "City is required"),
|
||||||
|
stateXid: z.number().min(1, "State is required"),
|
||||||
|
countryXid: z.number().min(1, "Country is required"),
|
||||||
|
|
||||||
|
pinCode: z.string()
|
||||||
|
.min(4, "Pincode/Zipcode is required")
|
||||||
|
.max(30, "Pincode cannot exceed 30 characters"),
|
||||||
|
|
||||||
|
logoPath: z.string()
|
||||||
|
.max(400, "Logo path cannot exceed 400 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
isSubsidairy: z.boolean(),
|
||||||
|
|
||||||
|
registrationNumber: z.string()
|
||||||
|
.max(30, "Registration number cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
panNumber: z.string()
|
||||||
|
.min(1, "PAN number is required")
|
||||||
|
.max(30, "PAN number cannot exceed 30 characters"),
|
||||||
|
|
||||||
|
gstNumber: z.string()
|
||||||
|
.max(30, "GST number cannot exceed 30 characters")
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
formationDate: z.string()
|
||||||
|
.optional()
|
||||||
|
.refine((val) => !val || !isNaN(Date.parse(val)), {
|
||||||
|
message: "Formation date must be a valid date",
|
||||||
|
}),
|
||||||
|
|
||||||
|
companyTypeXid: z.number()
|
||||||
|
.int("Company type must be a valid integer")
|
||||||
|
.min(1, "Company type is required"),
|
||||||
|
|
||||||
|
|
||||||
|
referencedBy: z.string()
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
websiteUrl: z.string().nullable().optional(),
|
||||||
|
instagramUrl: z.string().nullable().optional(),
|
||||||
|
facebookUrl: z.string().nullable().optional(),
|
||||||
|
linkedinUrl: z.string().nullable().optional(),
|
||||||
|
twitterUrl: z.string().nullable().optional(),
|
||||||
|
|
||||||
|
|
||||||
|
parentCompany: parentCompanySchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================
|
||||||
|
// DOCUMENTS VALIDATION
|
||||||
|
// =======================================================
|
||||||
|
|
||||||
|
export const hostDocumentsSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
documentTypeXid: z.number(),
|
||||||
|
documentName: z.string().max(50, "Document name cannot exceed 50 characters"), // per parent max 50 / host max 20 — safe limit 50
|
||||||
|
fieldName: z.string(),
|
||||||
|
owner: z.enum(['host', 'parent']).optional(),
|
||||||
|
isOptional: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
);
|
||||||
20
src/common/utils/validation/host/login.validation.ts
Normal file
20
src/common/utils/validation/host/login.validation.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// validations/hostBankDetails.validation.ts
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const loginForHostSchema = z.object({
|
||||||
|
|
||||||
|
|
||||||
|
emailAddress : z
|
||||||
|
.string()
|
||||||
|
.nonempty("Email is required"),
|
||||||
|
|
||||||
|
userPassword : z
|
||||||
|
.string()
|
||||||
|
.nonempty("Password is required")
|
||||||
|
.min(8, { message: "Password must be at least 8 characters" }),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export type loginForHostSchema = z.infer<typeof loginForHostSchema>;
|
||||||
@@ -12,13 +12,12 @@ const envVarsSchema = yup
|
|||||||
.oneOf(['production', 'development', 'test'])
|
.oneOf(['production', 'development', 'test'])
|
||||||
.required(),
|
.required(),
|
||||||
PORT: yup.number().default(3000),
|
PORT: yup.number().default(3000),
|
||||||
BASEURL: yup.string().required('Base URL is required'),
|
|
||||||
// FRONTEND_URL: yup.string().required('Frontend URL is required'),
|
// FRONTEND_URL: yup.string().required('Frontend URL is required'),
|
||||||
//JWT
|
//JWT
|
||||||
JWT_SECRET: yup.string().required('JWT secret key is required'),
|
JWT_SECRET: yup.string().required('JWT secret key is required'),
|
||||||
JWT_ACCESS_EXPIRATION_MINUTES: yup
|
JWT_ACCESS_EXPIRATION_MINUTES: yup
|
||||||
.number()
|
.number()
|
||||||
.default(30)
|
.default(1440)
|
||||||
.required('minutes after which access tokens expire'),
|
.required('minutes after which access tokens expire'),
|
||||||
JWT_REFRESH_EXPIRATION_DAYS: yup
|
JWT_REFRESH_EXPIRATION_DAYS: yup
|
||||||
.number()
|
.number()
|
||||||
@@ -32,32 +31,37 @@ const envVarsSchema = yup
|
|||||||
.number()
|
.number()
|
||||||
.default(10)
|
.default(10)
|
||||||
.required('minutes after which verify email token expires'),
|
.required('minutes after which verify email token expires'),
|
||||||
|
AWS_REGION: yup.string().required('AWS region is required'),
|
||||||
|
S3_BUCKET_NAME: yup.string().required('S3 bucket name is required'),
|
||||||
//SMTP and BREVO
|
//SMTP and BREVO
|
||||||
// BREVO_SMTP_HOST: yup
|
BREVO_SMTP_HOST: yup
|
||||||
// .string()
|
.string()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('server that will send the emails'),
|
.required('server that will send the emails'),
|
||||||
// BREVO_SMTP_PORT: yup
|
BREVO_SMTP_PORT: yup
|
||||||
// .number()
|
.number()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('port to connect to the email server'),
|
.required('port to connect to the email server'),
|
||||||
// BREVO_SMTP_USER: yup
|
BREVO_SMTP_USER: yup
|
||||||
// .string()
|
.string()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('username for email server'),
|
.required('username for email server'),
|
||||||
// BREVO_SMTP_PASS: yup
|
BREVO_SMTP_PASS: yup
|
||||||
// .string()
|
.string()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('password for email server'),
|
.required('password for email server'),
|
||||||
// BREVO_FROM_EMAIL: yup
|
BREVO_FROM_EMAIL: yup
|
||||||
// .string()
|
.string()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('the from field in the emails sent by the app'),
|
.required('the from field in the emails sent by the app'),
|
||||||
// BREVO_EMAIL_API_KEY: yup
|
BREVO_EMAIL_API_KEY: yup
|
||||||
// .string()
|
.string()
|
||||||
// .nullable()
|
.nullable()
|
||||||
// .required('the from field in the emails sent by the app api key'),
|
.required('the from field in the emails sent by the app api key'),
|
||||||
// BREVO_API_BASEURL: yup.string().required('Brevo base URL is required'),
|
BREVO_API_BASEURL: yup.string().required('Brevo base URL is required'),
|
||||||
|
// Minglar Admin
|
||||||
|
MINGLAR_ADMIN_EMAIL: yup.string().required('Minglar admin email address is required.'),
|
||||||
|
MINGLAR_ADMIN_NAME: yup.string().required('Minglar admin name is required.'),
|
||||||
// //one signal
|
// //one signal
|
||||||
// ONESIGNAL_APPID: yup.string().required('One signal app id is required'),
|
// ONESIGNAL_APPID: yup.string().required('One signal app id is required'),
|
||||||
// ONESIGNAL_REST_APIKEY: yup
|
// ONESIGNAL_REST_APIKEY: yup
|
||||||
@@ -77,6 +81,9 @@ const envVarsSchema = yup
|
|||||||
DB_PORT: yup.number().default(3306).required('DB Port is required'),
|
DB_PORT: yup.number().default(3306).required('DB Port is required'),
|
||||||
//OTP Bypass
|
//OTP Bypass
|
||||||
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
|
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
|
||||||
|
// Email links
|
||||||
|
AM_INVITATION_LINK: yup.string().required('Link to send in AM invitation mail is required'),
|
||||||
|
HOST_LINK: yup.string().required('Link to host panel is required')
|
||||||
})
|
})
|
||||||
.noUnknown(true);
|
.noUnknown(true);
|
||||||
|
|
||||||
@@ -130,23 +137,32 @@ function getConfig() {
|
|||||||
socketPath: '/var/run/mysqld/mysqld.sock',
|
socketPath: '/var/run/mysqld/mysqld.sock',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
aws: {
|
||||||
|
region: envVars.AWS_REGION,
|
||||||
|
bucketName: envVars.S3_BUCKET_NAME,
|
||||||
|
},
|
||||||
byPassOTP: envVars.BYPASS_OTP,
|
byPassOTP: envVars.BYPASS_OTP,
|
||||||
// BaseURL: envVars.BASEURL,
|
// BaseURL: envVars.BASEURL,
|
||||||
// FRONTEND_URL: envVars.FRONTEND_URL,
|
// FRONTEND_URL: envVars.FRONTEND_URL,
|
||||||
// email: {
|
email: {
|
||||||
// smtp: {
|
smtp: {
|
||||||
// host: envVars?.BREVO_SMTP_HOST,
|
host: envVars?.BREVO_SMTP_HOST,
|
||||||
// port: envVars?.BREVO_SMTP_PORT,
|
port: envVars?.BREVO_SMTP_PORT,
|
||||||
// secure: envVars?.BREVO_SMTP_PORT == 465, // true for 465, false for other ports
|
secure: envVars?.BREVO_SMTP_PORT == 465, // true for 465, false for other ports
|
||||||
// auth: {
|
auth: {
|
||||||
// user: envVars?.BREVO_SMTP_USER,
|
user: envVars?.BREVO_SMTP_USER,
|
||||||
// pass: envVars?.BREVO_SMTP_PASS,
|
pass: envVars?.BREVO_SMTP_PASS,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// from: envVars?.BREVO_FROM_EMAIL,
|
from: envVars?.BREVO_FROM_EMAIL,
|
||||||
// api_key: envVars?.BREVO_EMAIL_API_KEY,
|
api_key: envVars?.BREVO_EMAIL_API_KEY,
|
||||||
// BrevobaseURL: envVars?.BREVO_API_BASEURL,
|
BrevobaseURL: envVars?.BREVO_API_BASEURL,
|
||||||
// },
|
},
|
||||||
|
//Minglar admin
|
||||||
|
MinglarAdminEmail: envVars.MINGLAR_ADMIN_EMAIL,
|
||||||
|
MinglarAdminName: envVars.MINGLAR_ADMIN_NAME,
|
||||||
|
AM_INVITATION_LINK: envVars.AM_INVITATION_LINK,
|
||||||
|
HOST_LINK: envVars.HOST_LINK,
|
||||||
// oneSignal: {
|
// oneSignal: {
|
||||||
// appID: envVars.ONESIGNAL_APPID,
|
// appID: envVars.ONESIGNAL_APPID,
|
||||||
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ async function bootstrap() {
|
|||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: [
|
origin: [
|
||||||
'http://localhost:3000', // Local Swagger UI
|
'http://localhost:3000', // Local Swagger UI
|
||||||
'http://localhost:3006', // Local Frontend
|
'http://localhost:5173', // Local Frontend
|
||||||
'https://editor.swagger.io', // Swagger Editor
|
'https://editor.swagger.io', // Swagger Editor
|
||||||
'https://klc-admin.wdiprojects.com',
|
'https://klc-admin.wdiprojects.com',
|
||||||
'https://admin-uat.klc.betadelivery.com',
|
'https://admin-uat.klc.betadelivery.com',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class CreateHostDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
userPasscode?: string;
|
userPassword?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@@ -49,3 +49,47 @@ export class UpdateHostDto {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GetHostLoginResponseDTO {
|
||||||
|
id: number;
|
||||||
|
firstName: string | null;
|
||||||
|
lastName: string | null;
|
||||||
|
emailAddress: string;
|
||||||
|
mobileNumber: string | null;
|
||||||
|
isActive: boolean;
|
||||||
|
roleXid: number;
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
|
||||||
|
constructor(user: any, accessToken: string, refreshToken: string) {
|
||||||
|
this.id = user.id;
|
||||||
|
this.firstName = user.firstName;
|
||||||
|
this.lastName = user.lastName;
|
||||||
|
this.emailAddress = user.emailAddress;
|
||||||
|
this.mobileNumber = user.mobileNumber;
|
||||||
|
this.isActive = user.isActive;
|
||||||
|
this.roleXid = user.roleXid;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddPaymentDetailsDTO {
|
||||||
|
bankXid: number;
|
||||||
|
bankBranchXid: number;
|
||||||
|
accountNumber: string;
|
||||||
|
accountHolderName: string;
|
||||||
|
ifscCode: string;
|
||||||
|
hostXid: number;
|
||||||
|
currencyXid: number;
|
||||||
|
|
||||||
|
constructor(bankXid: number, bankBranchXid: number, accountNumber: string, accountHolderName: string, ifscCode: string, hostXid: number, currencyXid: number) {
|
||||||
|
this.bankXid = bankXid;
|
||||||
|
this.bankBranchXid = bankBranchXid;
|
||||||
|
this.accountNumber = accountNumber;
|
||||||
|
this.accountHolderName = accountHolderName;
|
||||||
|
this.ifscCode = ifscCode;
|
||||||
|
this.hostXid = hostXid;
|
||||||
|
this.currencyXid = currencyXid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token =
|
||||||
|
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
401,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
let body: any = {};
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityTypeXid, frequenciesXid } = body;
|
||||||
|
|
||||||
|
if (!activityTypeXid || !frequenciesXid) {
|
||||||
|
throw new ApiError(400, 'activityType and frequency ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all host applications from service based on user role
|
||||||
|
const createdData = await hostService.createActivityAndAllQuestionsEntry(userInfo.id, activityTypeXid, frequenciesXid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Activity created successfully',
|
||||||
|
data: createdData
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
const prePopulateService = new PrePopulateService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
|
||||||
|
// Read optional search query (supports ?search= or ?q=)
|
||||||
|
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
|
||||||
|
|
||||||
|
const data = await hostService.getAllActivityTypesWithInterest(search);
|
||||||
|
const frequencies = await prePopulateService.getAllFrequencies();
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
data,
|
||||||
|
frequencies
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
|
||||||
|
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// Get pagination params from event
|
||||||
|
const paginationParams = paginationService.getPaginationFromEvent(event);
|
||||||
|
const paginationOptions = paginationService.parsePaginationParams(paginationParams);
|
||||||
|
|
||||||
|
// Read optional search query (supports ?search= or ?q=)
|
||||||
|
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
|
||||||
|
|
||||||
|
const result = await hostService.getAllHostActivity(
|
||||||
|
search ? String(search) : undefined,
|
||||||
|
Number(userInfo.id),
|
||||||
|
paginationOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
...result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate user using the shared authForHost function
|
||||||
|
await verifyMinglarAdminHostToken(token);
|
||||||
|
|
||||||
|
const activity_xid = event.queryStringParameters?.activity_xid
|
||||||
|
? Number(event.queryStringParameters.activity_xid)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!activity_xid) {
|
||||||
|
throw new ApiError(409, "Activity ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await hostService.getAllPQQQuesAndSubmittedAns(Number(activity_xid));
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
204
src/modules/host/handlers/Activity_Hub/OnBoarding/getPQQScore.ts
Normal file
204
src/modules/host/handlers/Activity_Hub/OnBoarding/getPQQScore.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import config from '../../../../../config/config';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
|
import AWS from 'aws-sdk';
|
||||||
|
import Busboy from 'busboy';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
const s3 = new AWS.S3({ region: config.aws.region });
|
||||||
|
|
||||||
|
// Extract S3 key from URL
|
||||||
|
function getS3KeyFromUrl(url: string): string {
|
||||||
|
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
|
return url.replace(bucketBaseUrl, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file from S3
|
||||||
|
async function deleteFromS3(s3Key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await s3.deleteObject({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
}).promise();
|
||||||
|
console.log(`✅ Deleted from S3: ${s3Key}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`❌ Failed to delete from S3: ${s3Key}`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload new file
|
||||||
|
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string): Promise<string> {
|
||||||
|
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
|
||||||
|
const s3Key = `${prefix}/${uniqueKey}`;
|
||||||
|
|
||||||
|
await s3.upload({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: mimeType,
|
||||||
|
ACL: 'private'
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
console.log(`✅ Uploaded to S3: ${s3Key}`);
|
||||||
|
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
try {
|
||||||
|
// AUTH
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'Missing token');
|
||||||
|
|
||||||
|
const user = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// Content-Type
|
||||||
|
const contentType = event.headers["content-type"] || event.headers["Content-Type"];
|
||||||
|
if (!contentType?.startsWith("multipart/form-data"))
|
||||||
|
throw new ApiError(400, "Content-Type must be multipart/form-data");
|
||||||
|
|
||||||
|
if (!event.isBase64Encoded) throw new ApiError(400, "Body must be base64 encoded");
|
||||||
|
|
||||||
|
const bodyBuffer = Buffer.from(event.body!, "base64");
|
||||||
|
|
||||||
|
const fields: any = {};
|
||||||
|
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||||
|
|
||||||
|
// Parse multipart
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||||
|
|
||||||
|
bb.on('file', (fieldname, file, info) => {
|
||||||
|
const { filename, mimeType } = info;
|
||||||
|
if (!filename) return file.resume();
|
||||||
|
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let size = 0;
|
||||||
|
|
||||||
|
file.on('data', chunk => {
|
||||||
|
size += chunk.length;
|
||||||
|
if (size > 5 * 1024 * 1024)
|
||||||
|
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit`));
|
||||||
|
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on('end', () => {
|
||||||
|
if (chunks.length > 0) {
|
||||||
|
files.push({
|
||||||
|
buffer: Buffer.concat(chunks),
|
||||||
|
mimeType,
|
||||||
|
fileName: filename,
|
||||||
|
fieldName: fieldname
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('field', (fieldname, val) => {
|
||||||
|
try { fields[fieldname] = JSON.parse(val); }
|
||||||
|
catch { fields[fieldname] = val; }
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('close', resolve);
|
||||||
|
bb.on('error', err => reject(new ApiError(400, err.message)));
|
||||||
|
bb.end(bodyBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Required fields
|
||||||
|
const activityXid = Number(fields.activityXid);
|
||||||
|
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||||
|
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||||
|
const comments = fields.comments || null;
|
||||||
|
|
||||||
|
if (!activityXid || !pqqQuestionXid || !pqqAnswerXid)
|
||||||
|
throw new ApiError(400, "Missing required fields");
|
||||||
|
|
||||||
|
// UPSERT header
|
||||||
|
const existingHeader = await hostService.findHeaderByCompositeKey(activityXid, pqqQuestionXid);
|
||||||
|
let header;
|
||||||
|
|
||||||
|
if (existingHeader) {
|
||||||
|
header = await hostService.updateHeader(existingHeader.id, pqqAnswerXid, comments);
|
||||||
|
} else {
|
||||||
|
header = await hostService.createHeader(activityXid, pqqQuestionXid, pqqAnswerXid, comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCORE
|
||||||
|
const score = await hostService.calculatePqqScoreForUser(activityXid);
|
||||||
|
|
||||||
|
// Existing supporting files
|
||||||
|
const existingSupportingFiles = await hostService.getSupportingFilesByHeaderId(header.id);
|
||||||
|
|
||||||
|
// Read deletedFiles from frontend
|
||||||
|
const deletedFiles = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
|
||||||
|
|
||||||
|
const deleteResults = [];
|
||||||
|
const addResults = [];
|
||||||
|
|
||||||
|
// DELETE explicitly requested files (Case 3)
|
||||||
|
if (deletedFiles.length > 0) {
|
||||||
|
for (const del of deletedFiles) {
|
||||||
|
const id = Number(del.id);
|
||||||
|
const record = existingSupportingFiles.find(f => f.id === id);
|
||||||
|
if (!record) continue;
|
||||||
|
|
||||||
|
// Delete from S3
|
||||||
|
if (record.mediaFileName) {
|
||||||
|
const key = getS3KeyFromUrl(record.mediaFileName);
|
||||||
|
await deleteFromS3(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete from DB
|
||||||
|
await hostService.deleteSupportingFile(record.id);
|
||||||
|
deleteResults.push({ id: record.id, deleted: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD new uploaded files (Case 1 + Case 3 new files)
|
||||||
|
if (files.length > 0) {
|
||||||
|
for (const file of files) {
|
||||||
|
const url = await uploadToS3(
|
||||||
|
file.buffer,
|
||||||
|
file.mimeType,
|
||||||
|
file.fileName,
|
||||||
|
`ActivityOnboarding/supportings/${activityXid}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const newRec = await hostService.addSupportingFile(header.id, file.mimeType, url);
|
||||||
|
addResults.push(newRec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
|
||||||
|
|
||||||
|
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully",
|
||||||
|
data: {
|
||||||
|
headerId: header.id,
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments,
|
||||||
|
score,
|
||||||
|
getAllUpdatedQuestionResponse
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("❌ Error:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminHostToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
const question_xid = Number(event.queryStringParameters?.question_xid);
|
||||||
|
const activity_xid = Number(event.queryStringParameters?.activity_xid);
|
||||||
|
|
||||||
|
if (!question_xid || !activity_xid) {
|
||||||
|
throw new ApiError(400, "Question and activity xid are required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user with their HostHeader stepper info
|
||||||
|
const pqqQuestionDetails = await hostService.getPQQQuestionDetail(question_xid, activity_xid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Stepper information retrieved successfully',
|
||||||
|
data: pqqQuestionDetails,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
const activity_xid = event.queryStringParameters?.activity_xid
|
||||||
|
? Number(event.queryStringParameters.activity_xid)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Validate it
|
||||||
|
if (!activity_xid || isNaN(activity_xid)) {
|
||||||
|
throw new ApiError(400, "Activity id is required and must be a number.");
|
||||||
|
}
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
// Fetch user with their HostHeader stepper info
|
||||||
|
const pqqQuestionDetails = await hostService.getLatestQuestionDetailsPQQ(activity_xid);
|
||||||
|
|
||||||
|
if (pqqQuestionDetails) {
|
||||||
|
result = {
|
||||||
|
pqqQuestionXid: pqqQuestionDetails.pqqQuestionXid,
|
||||||
|
pqqAnswerXid: pqqQuestionDetails.pqqAnswerXid || null,
|
||||||
|
pqqSubCategoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategoryXid || null,
|
||||||
|
categoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategories.categoryXid || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Latest information retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
let body: any = {};
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityTypeXid, frequenciesXid } = body;
|
||||||
|
|
||||||
|
if (!activityTypeXid) {
|
||||||
|
throw new ApiError(400, 'activityTypeXid is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hostService.createActivity(
|
||||||
|
userInfo.id,
|
||||||
|
Number(activityTypeXid),
|
||||||
|
frequenciesXid ? Number(frequenciesXid) : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 201,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Activity created successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import config from '../../../../../config/config';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
|
import AWS from 'aws-sdk';
|
||||||
|
import Busboy from 'busboy';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
const s3 = new AWS.S3({ region: config.aws.region });
|
||||||
|
|
||||||
|
// Function to extract S3 key from URL
|
||||||
|
function getS3KeyFromUrl(url: string): string {
|
||||||
|
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
|
return url.replace(bucketBaseUrl, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to delete file from S3
|
||||||
|
async function deleteFromS3(s3Key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await s3.deleteObject({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
}).promise();
|
||||||
|
console.log(`✅ File deleted from S3: ${s3Key}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error deleting file from S3: ${s3Key}`, error);
|
||||||
|
// continue — we don't want S3 deletion failure to crash the whole request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> {
|
||||||
|
// We intentionally do NOT reuse old key. If existingUrl is provided we delete old file and create a new random key.
|
||||||
|
if (existingUrl) {
|
||||||
|
try {
|
||||||
|
const oldKey = getS3KeyFromUrl(existingUrl);
|
||||||
|
await deleteFromS3(oldKey);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Warning deleting existingUrl before upload', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
|
||||||
|
const s3Key = `${prefix}/${uniqueKey}`;
|
||||||
|
|
||||||
|
await s3.upload({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: mimeType,
|
||||||
|
ACL: 'private'
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
console.log(`✅ File uploaded to S3: ${s3Key}`);
|
||||||
|
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
try {
|
||||||
|
// 1) Auth
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'Missing token.');
|
||||||
|
const user = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// 2) Content-Type check
|
||||||
|
const contentType = event.headers["content-type"] || event.headers["Content-Type"];
|
||||||
|
if (!contentType?.includes("multipart/form-data"))
|
||||||
|
throw new ApiError(400, "Content-Type must be multipart/form-data");
|
||||||
|
|
||||||
|
// 3) Body decoding
|
||||||
|
const bodyBuffer = event.isBase64Encoded
|
||||||
|
? Buffer.from(event.body!, "base64")
|
||||||
|
: Buffer.from(event.body!, "binary");
|
||||||
|
|
||||||
|
const fields: any = {};
|
||||||
|
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||||
|
|
||||||
|
// 4) Parse multipart data
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||||
|
|
||||||
|
bb.on('file', (fieldname, file, info) => {
|
||||||
|
const { filename, mimeType } = info;
|
||||||
|
|
||||||
|
if (!filename) {
|
||||||
|
file.resume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let size = 0;
|
||||||
|
const MAX_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
|
file.on("data", (chunk) => {
|
||||||
|
size += chunk.length;
|
||||||
|
if (size > MAX_SIZE) {
|
||||||
|
file.destroy(new Error(`File ${filename} exceeds 5MB limit.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on("end", () => {
|
||||||
|
if (chunks.length > 0) {
|
||||||
|
files.push({
|
||||||
|
buffer: Buffer.concat(chunks),
|
||||||
|
mimeType,
|
||||||
|
fileName: filename,
|
||||||
|
fieldName: fieldname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on("error", (err) =>
|
||||||
|
reject(new ApiError(400, `File upload error: ${err.message}`))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on("field", (fieldname, val) => {
|
||||||
|
console.log(`FIELD RAW: ${fieldname} =`, val);
|
||||||
|
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
const cleaned = val.trim();
|
||||||
|
|
||||||
|
// If it starts and ends with quotes, remove them
|
||||||
|
const withoutQuotes =
|
||||||
|
(cleaned.startsWith('"') && cleaned.endsWith('"'))
|
||||||
|
? cleaned.slice(1, -1)
|
||||||
|
: cleaned;
|
||||||
|
|
||||||
|
fields[fieldname] = JSON.parse(withoutQuotes);
|
||||||
|
} catch {
|
||||||
|
fields[fieldname] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on("close", () => resolve());
|
||||||
|
bb.on("error", (err) =>
|
||||||
|
reject(new ApiError(400, `Multipart parsing error: ${err.message}`))
|
||||||
|
);
|
||||||
|
|
||||||
|
// IMPORTANT FIX for HTTP API
|
||||||
|
bb.write(bodyBuffer);
|
||||||
|
bb.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5) Extract required fields
|
||||||
|
const activityXid = Number(fields.activityXid);
|
||||||
|
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||||
|
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||||
|
const comments = fields.comments || null;
|
||||||
|
|
||||||
|
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
|
||||||
|
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
|
||||||
|
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
|
||||||
|
|
||||||
|
// 6) UPSERT header
|
||||||
|
const existingHeader = await hostService.findHeaderByCompositeKey(
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
);
|
||||||
|
|
||||||
|
let header;
|
||||||
|
if (existingHeader) {
|
||||||
|
console.log("🔄 Updating existing PQQ header");
|
||||||
|
header = await hostService.updateHeader(
|
||||||
|
existingHeader.id,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("🆕 Creating new PQQ header");
|
||||||
|
header = await hostService.createHeader(
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) Get existing supporting files
|
||||||
|
const existingSupportingFiles = await hostService.getSupportingFilesByHeaderId(header.id);
|
||||||
|
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
|
||||||
|
|
||||||
|
// 8) Parse incoming control fields
|
||||||
|
// fields.deletedFiles should be array like [{ id: number, url: string }, ...] or null
|
||||||
|
const deletedFiles: Array<{ id: number; url?: string }> = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
|
||||||
|
// fields.existingFiles can be an array of urls; we accept it but do not require it
|
||||||
|
const existingFilesFromFront: string[] = Array.isArray(fields.existingFiles) ? fields.existingFiles : [];
|
||||||
|
|
||||||
|
// Prepare response trackers
|
||||||
|
const deletedResults: Array<{ id: number; success: boolean; reason?: string }> = [];
|
||||||
|
const addedResults: Array<any> = [];
|
||||||
|
|
||||||
|
// 9) Handle explicit deletions (ONLY delete ids provided in deletedFiles)
|
||||||
|
if (deletedFiles.length > 0) {
|
||||||
|
console.log(`🗑️ Processing ${deletedFiles.length} explicit deletions`);
|
||||||
|
// Build a map of existing supporting files by id for quick lookup
|
||||||
|
const existingById = new Map<number, any>();
|
||||||
|
for (const f of existingSupportingFiles) {
|
||||||
|
existingById.set(f.id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const del of deletedFiles) {
|
||||||
|
const id = Number(del.id);
|
||||||
|
if (!id || !existingById.has(id)) {
|
||||||
|
deletedResults.push({ id, success: false, reason: 'Not found or invalid id' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = existingById.get(id);
|
||||||
|
try {
|
||||||
|
// delete from s3
|
||||||
|
if (record.mediaFileName) {
|
||||||
|
const s3Key = getS3KeyFromUrl(record.mediaFileName);
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete DB record
|
||||||
|
await hostService.deleteSupportingFile(record.id);
|
||||||
|
|
||||||
|
deletedResults.push({ id: record.id, success: true });
|
||||||
|
console.log(`🗑️ Deleted supporting file record ${record.id}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(`❌ Failed to delete supporting file id ${id}`, err);
|
||||||
|
deletedResults.push({ id, success: false, reason: err.message || 'delete failed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ No explicit deletions requested (deletedFiles empty)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10) Handle new uploaded files (these are ALWAYS added as new rows)
|
||||||
|
if (files.length > 0) {
|
||||||
|
console.log(`📤 Processing ${files.length} uploaded new file(s)`);
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const url = await uploadToS3(
|
||||||
|
file.buffer,
|
||||||
|
file.mimeType,
|
||||||
|
file.fileName,
|
||||||
|
`ActivityOnboarding/supportings/${activityXid}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// create DB record
|
||||||
|
const supporting = await hostService.addSupportingFile(
|
||||||
|
header.id,
|
||||||
|
file.mimeType,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
|
||||||
|
addedResults.push(supporting);
|
||||||
|
console.log(`🆕 Created new supporting file record: ${supporting.id}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('❌ Error uploading/creating supporting file', err);
|
||||||
|
// push failure result but continue processing other files
|
||||||
|
addedResults.push({ success: false, reason: err.message || 'upload/create failed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('📭 No new files uploaded in request');
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We DO NOT delete or modify existing supporting files that were not listed in deletedFiles.
|
||||||
|
// This satisfies your Case 2: "if no files are provided, do not touch existing supporting files".
|
||||||
|
|
||||||
|
const allPQPQuestionAnswerResponse = await hostService.getAllPQUpdatedResponse(activityXid)
|
||||||
|
|
||||||
|
// 11) Compose response
|
||||||
|
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Access-Control-Allow-Origin": "*"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: responseMessage,
|
||||||
|
data: {
|
||||||
|
responseOfUpdatedData: allPQPQuestionAnswerResponse,
|
||||||
|
operation: existingHeader ? 'updated' : 'created',
|
||||||
|
// summary label for UI convenience:
|
||||||
|
fileOperation: (deletedResults.length > 0 || addedResults.length > 0) ? 'modified' : 'unchanged'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("❌ Error in submitPqqAnswer:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const pqqService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
try {
|
||||||
|
// 1) Auth
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'Missing token.');
|
||||||
|
const user = await verifyHostToken(token);
|
||||||
|
|
||||||
|
const activity_xid = event.queryStringParameters?.activity_xid
|
||||||
|
? Number(event.queryStringParameters.activity_xid)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
await pqqService.submitpqqforreview(Number(activity_xid), Number(user.id))
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Access-Control-Allow-Origin": "*"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: "Your PQQ has been submitted for review.",
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("❌ Error in submitPqqAnswer:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import config from '../../../../../config/config';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
|
import AWS from 'aws-sdk';
|
||||||
|
import Busboy from 'busboy';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const pqqService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
const s3 = new AWS.S3({ region: config.aws.region });
|
||||||
|
|
||||||
|
// Function to extract S3 key from URL
|
||||||
|
function getS3KeyFromUrl(url: string): string {
|
||||||
|
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
|
return url.replace(bucketBaseUrl, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to delete file from S3
|
||||||
|
async function deleteFromS3(s3Key: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await s3.deleteObject({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
}).promise();
|
||||||
|
console.log(`✅ File deleted from S3: ${s3Key}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error deleting file from S3: ${s3Key}`, error);
|
||||||
|
// continue — we don't want S3 deletion failure to crash the whole request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> {
|
||||||
|
// We intentionally do NOT reuse old key. If existingUrl is provided we delete old file and create a new random key.
|
||||||
|
if (existingUrl) {
|
||||||
|
try {
|
||||||
|
const oldKey = getS3KeyFromUrl(existingUrl);
|
||||||
|
await deleteFromS3(oldKey);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Warning deleting existingUrl before upload', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
|
||||||
|
const s3Key = `${prefix}/${uniqueKey}`;
|
||||||
|
|
||||||
|
await s3.upload({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: mimeType,
|
||||||
|
ACL: 'private'
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
console.log(`✅ File uploaded to S3: ${s3Key}`);
|
||||||
|
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
try {
|
||||||
|
// 1) Auth
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'Missing token.');
|
||||||
|
const user = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// 2) Content-Type check
|
||||||
|
const contentType = event.headers["content-type"] || event.headers["Content-Type"];
|
||||||
|
if (!contentType?.includes("multipart/form-data"))
|
||||||
|
throw new ApiError(400, "Content-Type must be multipart/form-data");
|
||||||
|
|
||||||
|
// 3) Body decoding
|
||||||
|
const bodyBuffer = event.isBase64Encoded
|
||||||
|
? Buffer.from(event.body!, "base64")
|
||||||
|
: Buffer.from(event.body!, "binary");
|
||||||
|
|
||||||
|
const fields: any = {};
|
||||||
|
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||||
|
|
||||||
|
// 4) Parse multipart data
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||||
|
|
||||||
|
bb.on('file', (fieldname, file, info) => {
|
||||||
|
const { filename, mimeType } = info;
|
||||||
|
|
||||||
|
if (!filename) {
|
||||||
|
file.resume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let size = 0;
|
||||||
|
const MAX_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
|
file.on("data", (chunk) => {
|
||||||
|
size += chunk.length;
|
||||||
|
if (size > MAX_SIZE) {
|
||||||
|
file.destroy(new Error(`File ${filename} exceeds 5MB limit.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on("end", () => {
|
||||||
|
if (chunks.length > 0) {
|
||||||
|
files.push({
|
||||||
|
buffer: Buffer.concat(chunks),
|
||||||
|
mimeType,
|
||||||
|
fileName: filename,
|
||||||
|
fieldName: fieldname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on("error", (err) =>
|
||||||
|
reject(new ApiError(400, `File upload error: ${err.message}`))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on("field", (fieldname, val) => {
|
||||||
|
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
fields[fieldname] = JSON.parse(val);
|
||||||
|
} catch {
|
||||||
|
fields[fieldname] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on("close", () => resolve());
|
||||||
|
bb.on("error", (err) =>
|
||||||
|
reject(new ApiError(400, `Multipart parsing error: ${err.message}`))
|
||||||
|
);
|
||||||
|
|
||||||
|
// IMPORTANT FIX for HTTP API
|
||||||
|
bb.write(bodyBuffer);
|
||||||
|
bb.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5) Extract required fields
|
||||||
|
const activityXid = Number(fields.activityXid);
|
||||||
|
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||||
|
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||||
|
const comments = fields.comments || null;
|
||||||
|
|
||||||
|
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
|
||||||
|
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
|
||||||
|
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
|
||||||
|
|
||||||
|
// 6) UPSERT header
|
||||||
|
const existingHeader = await pqqService.findHeaderByCompositeKey(
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
);
|
||||||
|
|
||||||
|
let header;
|
||||||
|
if (existingHeader) {
|
||||||
|
console.log("🔄 Updating existing PQQ header");
|
||||||
|
header = await pqqService.updateHeader(
|
||||||
|
existingHeader.id,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("🆕 Creating new PQQ header");
|
||||||
|
header = await pqqService.createHeader(
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7) Get existing supporting files
|
||||||
|
const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id);
|
||||||
|
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
|
||||||
|
|
||||||
|
// 8) Parse incoming control fields
|
||||||
|
// fields.deletedFiles should be array like [{ id: number, url: string }, ...] or null
|
||||||
|
const deletedFiles: Array<{ id: number; url?: string }> = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
|
||||||
|
// fields.existingFiles can be an array of urls; we accept it but do not require it
|
||||||
|
const existingFilesFromFront: string[] = Array.isArray(fields.existingFiles) ? fields.existingFiles : [];
|
||||||
|
|
||||||
|
// Prepare response trackers
|
||||||
|
const deletedResults: Array<{ id: number; success: boolean; reason?: string }> = [];
|
||||||
|
const addedResults: Array<any> = [];
|
||||||
|
|
||||||
|
// 9) Handle explicit deletions (ONLY delete ids provided in deletedFiles)
|
||||||
|
if (deletedFiles.length > 0) {
|
||||||
|
console.log(`🗑️ Processing ${deletedFiles.length} explicit deletions`);
|
||||||
|
// Build a map of existing supporting files by id for quick lookup
|
||||||
|
const existingById = new Map<number, any>();
|
||||||
|
for (const f of existingSupportingFiles) {
|
||||||
|
existingById.set(f.id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const del of deletedFiles) {
|
||||||
|
const id = Number(del.id);
|
||||||
|
if (!id || !existingById.has(id)) {
|
||||||
|
deletedResults.push({ id, success: false, reason: 'Not found or invalid id' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = existingById.get(id);
|
||||||
|
try {
|
||||||
|
// delete from s3
|
||||||
|
if (record.mediaFileName) {
|
||||||
|
const s3Key = getS3KeyFromUrl(record.mediaFileName);
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete DB record
|
||||||
|
await pqqService.deleteSupportingFile(record.id);
|
||||||
|
|
||||||
|
deletedResults.push({ id: record.id, success: true });
|
||||||
|
console.log(`🗑️ Deleted supporting file record ${record.id}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(`❌ Failed to delete supporting file id ${id}`, err);
|
||||||
|
deletedResults.push({ id, success: false, reason: err.message || 'delete failed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ No explicit deletions requested (deletedFiles empty)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10) Handle new uploaded files (these are ALWAYS added as new rows)
|
||||||
|
if (files.length > 0) {
|
||||||
|
console.log(`📤 Processing ${files.length} uploaded new file(s)`);
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const url = await uploadToS3(
|
||||||
|
file.buffer,
|
||||||
|
file.mimeType,
|
||||||
|
file.fileName,
|
||||||
|
`ActivityOnboarding/supportings/${activityXid}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// create DB record
|
||||||
|
const supporting = await pqqService.addSupportingFile(
|
||||||
|
header.id,
|
||||||
|
file.mimeType,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
|
||||||
|
addedResults.push(supporting);
|
||||||
|
console.log(`🆕 Created new supporting file record: ${supporting.id}`);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('❌ Error uploading/creating supporting file', err);
|
||||||
|
// push failure result but continue processing other files
|
||||||
|
addedResults.push({ success: false, reason: err.message || 'upload/create failed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('📭 No new files uploaded in request');
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We DO NOT delete or modify existing supporting files that were not listed in deletedFiles.
|
||||||
|
// This satisfies your Case 2: "if no files are provided, do not touch existing supporting files".
|
||||||
|
|
||||||
|
// 11) Compose response
|
||||||
|
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Access-Control-Allow-Origin": "*"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: responseMessage,
|
||||||
|
data: {
|
||||||
|
headerId: header.id,
|
||||||
|
activityXid,
|
||||||
|
pqqQuestionXid,
|
||||||
|
pqqAnswerXid,
|
||||||
|
comments,
|
||||||
|
files: {
|
||||||
|
added: addedResults,
|
||||||
|
deleted: deletedResults,
|
||||||
|
existingKeptCount: (existingSupportingFiles.length - deletedResults.filter(d => d.success).length)
|
||||||
|
},
|
||||||
|
operation: existingHeader ? 'updated' : 'created',
|
||||||
|
// summary label for UI convenience:
|
||||||
|
fileOperation: (deletedResults.length > 0 || addedResults.length > 0) ? 'modified' : 'unchanged'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("❌ Error in submitPqqAnswer:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
let body: any = {};
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityPqqHeaderXid, activityPQQSuggestionId } = body;
|
||||||
|
|
||||||
|
if (!activityPqqHeaderXid) {
|
||||||
|
throw new ApiError(400, 'activityPqqHeaderXid is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hostService.markPQQSuggestionReviewed(
|
||||||
|
userInfo.id,
|
||||||
|
Number(activityPqqHeaderXid),
|
||||||
|
Number(activityPQQSuggestionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 201,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Suggestion reviewed successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await hostService.acceptMinglarAgreement(userInfo.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Application accepted successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if(!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate user using the shared authForHost function
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
const user_xid = userInfo.id;
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: { password?: string; confirmPassword?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { password, confirmPassword } = body;
|
||||||
|
|
||||||
|
if (!password || !confirmPassword) {
|
||||||
|
throw new ApiError(400, 'Password and confirm password are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate password match
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
throw new ApiError(400, 'Password and confirm password do not match');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate password length
|
||||||
|
if (password.length < 8) {
|
||||||
|
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hostService.createPassword(user_xid, password);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Password created successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
70
src/modules/host/handlers/Host_Admin/onboarding/login.ts
Normal file
70
src/modules/host/handlers/Host_Admin/onboarding/login.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { TokenService } from '../../../services/token.service';
|
||||||
|
import { GetHostLoginResponseDTO } from '../../../dto/host.dto';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
const tokenService = new TokenService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Parse request body
|
||||||
|
let body: { emailAddress?: string; userPassword?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { emailAddress, userPassword } = body;
|
||||||
|
|
||||||
|
if (!emailAddress || !userPassword) {
|
||||||
|
throw new ApiError(400, 'Email and password are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailToLowerCase = emailAddress.toLowerCase()
|
||||||
|
|
||||||
|
const loginForHost = await hostService.loginForHost(emailToLowerCase, userPassword);
|
||||||
|
|
||||||
|
if (!loginForHost) {
|
||||||
|
throw new ApiError(400, 'Failed to login');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginForHost.userPassword) {
|
||||||
|
throw new ApiError(401, 'Invalid credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateTokenForHost = await tokenService.generateAuthToken(
|
||||||
|
loginForHost.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!generateTokenForHost) {
|
||||||
|
throw new ApiError(500, 'Failed to generate token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginForHostResponse = new GetHostLoginResponseDTO(
|
||||||
|
loginForHost,
|
||||||
|
generateTokenForHost.access.token,
|
||||||
|
generateTokenForHost.refresh.token
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Login successful',
|
||||||
|
data: loginForHostResponse,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { MinglarService } from '../../../../minglaradmin/services/minglar.service';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get suggestions handler
|
||||||
|
* Retrieves suggestions based on user's role and host assignments
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
// Get suggestions using service
|
||||||
|
const suggestions = await minglarService.getHostSuggestions(userInfo.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Suggestions retrieved successfully',
|
||||||
|
data: suggestions,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
121
src/modules/host/handlers/Host_Admin/onboarding/signUp.ts
Normal file
121
src/modules/host/handlers/Host_Admin/onboarding/signUp.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import * as bcrypt from 'bcryptjs';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
|
||||||
|
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export async function generateHostRefNumber(tx: any) {
|
||||||
|
const lastrecord = await tx.user.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
id: 'desc',
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextId = lastrecord ? lastrecord.id + 1 : 1;
|
||||||
|
|
||||||
|
return `HS-${String(nextId).padStart(6, '0')}`;;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Parse request body
|
||||||
|
let body: { email?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { email } = body;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new ApiError(400, 'Email is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailToLowerCase = email.toLowerCase()
|
||||||
|
|
||||||
|
// Use a single transaction for user creation/lookup and OTP storage
|
||||||
|
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
||||||
|
const user = await tx.user.findUnique({
|
||||||
|
where: { emailAddress: emailToLowerCase },
|
||||||
|
select: { emailAddress: true, id: true, userPassword: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user && user.userPassword) {
|
||||||
|
throw new ApiError(409, 'User is already registered. Please login.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let newUserLocal;
|
||||||
|
|
||||||
|
const referenceNumber = await generateHostRefNumber(tx);
|
||||||
|
|
||||||
|
if (user && !user.userPassword) {
|
||||||
|
// reuse existing invited user record
|
||||||
|
newUserLocal = user;
|
||||||
|
} else {
|
||||||
|
// create new user record within the transaction
|
||||||
|
newUserLocal = await tx.user.create({
|
||||||
|
data: { emailAddress: emailToLowerCase, roleXid: ROLE.HOST, userRefNumber: referenceNumber },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate OTP (6-digit) and store within the same transaction
|
||||||
|
const otp = OtpGeneratorSixDigit.generateOtp();
|
||||||
|
const hashedOtp = await bcrypt.hash(otp, 10);
|
||||||
|
const expiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
|
||||||
|
|
||||||
|
// delete old active OTPs for this user/purpose
|
||||||
|
await tx.userOtp.deleteMany({
|
||||||
|
where: { userXid: Number(newUserLocal.id), otpType: 'Register', isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.userOtp.create({
|
||||||
|
data: {
|
||||||
|
userXid: Number(newUserLocal.id),
|
||||||
|
otpType: 'Register',
|
||||||
|
otpCode: hashedOtp,
|
||||||
|
expiresOn: expiry,
|
||||||
|
isVerified: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptedId = encryptUserId(String(newUserLocal.id));
|
||||||
|
|
||||||
|
return { newUser: newUserLocal, otp, encryptedId };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!transactionResult || !transactionResult.otp) {
|
||||||
|
throw new ApiError(500, 'Failed to generate OTP');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send OTP email outside the DB transaction
|
||||||
|
await sendOtpEmailForHost(transactionResult.newUser.emailAddress, transactionResult.otp);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'OTP sent successfully.',
|
||||||
|
data: {},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
// modules/host/handlers/addCompanyDetails.ts
|
||||||
|
import config from '../../../../../config/config';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
||||||
|
import AWS from 'aws-sdk';
|
||||||
|
import Busboy from 'busboy';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import {
|
||||||
|
hostCompanyDetailsSchema,
|
||||||
|
hostDocumentsSchema,
|
||||||
|
parentCompanySchema,
|
||||||
|
} from '../../../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { sendEmailToAM, sendEmailToMinglarAdmin } from '../../../services/sendHostResubmitEmailToAM.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
function getExtensionFromMime(mimeType: string) {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
'image/jpeg': 'jpg',
|
||||||
|
'image/png': 'png',
|
||||||
|
'application/pdf': 'pdf',
|
||||||
|
'image/webp': 'webp',
|
||||||
|
};
|
||||||
|
return map[mimeType] || 'bin';
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3 = new AWS.S3({
|
||||||
|
region: config.aws.region,
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeJsonField(fields: any, key: string) {
|
||||||
|
if (!fields[key]) return undefined;
|
||||||
|
const val = fields[key];
|
||||||
|
|
||||||
|
if (typeof val === 'object') return val;
|
||||||
|
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(val);
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(400, `Invalid JSON in field: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ApiError(400, `Invalid input: ${key} must be object or JSON string.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanEmptyStrings(obj: any) {
|
||||||
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
|
|
||||||
|
const cleaned: any = {};
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
if (obj[key] === '') cleaned[key] = undefined;
|
||||||
|
else if (typeof obj[key] === 'object') cleaned[key] = cleanEmptyStrings(obj[key]);
|
||||||
|
else cleaned[key] = obj[key];
|
||||||
|
}
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getS3KeyFromUrl(url: string): string {
|
||||||
|
const base = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
|
return url.replace(base, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFromS3(key: string) {
|
||||||
|
try {
|
||||||
|
await s3.deleteObject({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: key
|
||||||
|
}).promise();
|
||||||
|
|
||||||
|
console.log("✅ Deleted from S3:", key);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Failed to delete from S3:", key, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
/** 1) AUTH */
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
|
||||||
|
/** 2) CHECK CONTENT TYPE */
|
||||||
|
const contentType = event.headers['content-type'] || event.headers['Content-Type'];
|
||||||
|
if (!contentType?.includes('multipart/form-data')) {
|
||||||
|
throw new ApiError(400, 'Content-Type must be multipart/form-data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 3) HANDLE BODY */
|
||||||
|
const bodyBuffer = event.isBase64Encoded
|
||||||
|
? Buffer.from(event.body as string, 'base64')
|
||||||
|
: Buffer.from(event.body as string, 'binary');
|
||||||
|
|
||||||
|
const fields: Record<string, any> = {};
|
||||||
|
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const bb = Busboy({ headers: { 'content-type': contentType } });
|
||||||
|
|
||||||
|
bb.on('file', (fieldname, file, info) => {
|
||||||
|
const { filename, mimeType } = info;
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let totalSize = 0;
|
||||||
|
const MAX_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
|
file.on('data', (chunk) => {
|
||||||
|
totalSize += chunk.length;
|
||||||
|
if (totalSize > MAX_SIZE) {
|
||||||
|
file.destroy(new Error(`File ${filename} exceeds 5MB limit.`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on('end', () => {
|
||||||
|
if (chunks.length > 0) {
|
||||||
|
files.push({
|
||||||
|
buffer: Buffer.concat(chunks),
|
||||||
|
mimeType: mimeType || 'application/octet-stream',
|
||||||
|
fileName: filename || 'unknown',
|
||||||
|
fieldName: fieldname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on('error', (error) => reject(new ApiError(400, `File upload error: ${error.message}`)));
|
||||||
|
});
|
||||||
|
|
||||||
|
bb.on('field', (fieldname, val) => (fields[fieldname] = val));
|
||||||
|
bb.on('close', () => resolve());
|
||||||
|
bb.on('error', (error) => reject(new ApiError(400, `Multipart parsing error: ${error.message}`)));
|
||||||
|
|
||||||
|
bb.write(bodyBuffer);
|
||||||
|
bb.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
||||||
|
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
||||||
|
|
||||||
|
/** 4) Extract and clean isDraft flag */
|
||||||
|
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
||||||
|
|
||||||
|
/** 5) PROCESS companyDetails ONCE ONLY (IMPORTANT FIX) */
|
||||||
|
let companyDetailsRaw = normalizeJsonField(fields, 'companyDetails');
|
||||||
|
if (!companyDetailsRaw) throw new ApiError(400, 'companyDetails is required.');
|
||||||
|
|
||||||
|
if (isDraft) {
|
||||||
|
companyDetailsRaw = cleanEmptyStrings(companyDetailsRaw);
|
||||||
|
|
||||||
|
// IMPORTANT: also clean parent company nested fields
|
||||||
|
if (companyDetailsRaw.parentCompany) {
|
||||||
|
companyDetailsRaw.parentCompany = cleanEmptyStrings(companyDetailsRaw.parentCompany);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
companyDetailsRaw.parentCompany &&
|
||||||
|
Object.values(companyDetailsRaw.parentCompany).every(
|
||||||
|
(v) => v === undefined || v === null
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
companyDetailsRaw.parentCompany = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 6) Profile update if provided */
|
||||||
|
if (fields.userProfile) {
|
||||||
|
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
||||||
|
if (userProfileRaw) {
|
||||||
|
const { firstName, lastName, mobileNumber } = userProfileRaw;
|
||||||
|
|
||||||
|
await prismaClient.user.update({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
data: {
|
||||||
|
...(firstName && { firstName }),
|
||||||
|
...(lastName && { lastName }),
|
||||||
|
...(mobileNumber && { mobileNumber }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 7) VALIDATION - SKIPPED IF DRAFT */
|
||||||
|
let parsedCompany: any = companyDetailsRaw;
|
||||||
|
|
||||||
|
if (!isDraft) {
|
||||||
|
const validate = hostCompanyDetailsSchema.safeParse(companyDetailsRaw);
|
||||||
|
if (!validate.success) {
|
||||||
|
const message = validate.error.issues.map((i) => i.message).join(', ');
|
||||||
|
throw new ApiError(400, `Validation failed: ${message}`);
|
||||||
|
}
|
||||||
|
parsedCompany = validate.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 8) DOCUMENT METADATA */
|
||||||
|
const documentsMetadataRaw = normalizeJsonField(fields, 'documents');
|
||||||
|
if (!Array.isArray(documentsMetadataRaw)) throw new ApiError(400, 'documents must be an array.');
|
||||||
|
|
||||||
|
if (!isDraft) {
|
||||||
|
const docsParse = hostDocumentsSchema.safeParse(documentsMetadataRaw);
|
||||||
|
if (!docsParse.success) {
|
||||||
|
const message = docsParse.error.issues.map((i) => i.message).join(', ');
|
||||||
|
throw new ApiError(400, `Documents validation failed: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentsMetadata = documentsMetadataRaw.map((d: any) => ({
|
||||||
|
...d,
|
||||||
|
owner: d.owner || 'host',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const documentMetadata = documentsMetadata.map((doc: any) => {
|
||||||
|
const file = files.find((f) => f.fieldName === doc.fieldName);
|
||||||
|
|
||||||
|
// In DRAFT mode → allow missing documents
|
||||||
|
if (!file) {
|
||||||
|
return { ...doc, file: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...doc, file };
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 9) SPLIT host & parent docs */
|
||||||
|
const hostDocs = documentMetadata.filter((d) => d.owner === 'host');
|
||||||
|
const parentDocs = documentMetadata.filter((d) => d.owner === 'parent');
|
||||||
|
|
||||||
|
/** 10) VALIDATE PARENT COMPANY (ONLY IN FINAL SUBMISSION) */
|
||||||
|
let parsedParentCompany: any = null;
|
||||||
|
|
||||||
|
if (!isDraft && parsedCompany.isSubsidairy) {
|
||||||
|
if (!parsedCompany.parentCompany) {
|
||||||
|
throw new ApiError(400, 'isSubsidairy is true but parentCompany object is missing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentCheck = parentCompanySchema.safeParse(parsedCompany.parentCompany);
|
||||||
|
if (!parentCheck.success) {
|
||||||
|
const message = parentCheck.error.issues.map((i) => i.message).join(', ');
|
||||||
|
throw new ApiError(400, `Parent company validation failed: ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedParentCompany = parentCheck.data;
|
||||||
|
} else {
|
||||||
|
parsedParentCompany = parsedCompany.parentCompany || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 9.5) DELETE DOCUMENTS IF REQUESTED **/
|
||||||
|
if (Array.isArray(deletedFiles) && deletedFiles.length > 0) {
|
||||||
|
console.log(`🗑️ Deleting ${deletedFiles.length} documents...`);
|
||||||
|
|
||||||
|
for (const del of deletedFiles) {
|
||||||
|
const id = Number(del.id);
|
||||||
|
const url = del.url;
|
||||||
|
|
||||||
|
if (!id || !url) {
|
||||||
|
console.log("❌ Invalid delete entry:", del);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract S3 key
|
||||||
|
const s3Key = getS3KeyFromUrl(url);
|
||||||
|
|
||||||
|
// Delete from S3
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
|
||||||
|
// Delete from DB
|
||||||
|
await prismaClient.hostDocuments.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🗑️ Deleted host document ID ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 9.6) DELETE PARENT DOCUMENTS **/
|
||||||
|
if (parsedCompany.isSubsidairy && Array.isArray(parentDeletedFiles) && parentDeletedFiles.length > 0) {
|
||||||
|
console.log(`🗑️ Deleting ${parentDeletedFiles.length} PARENT documents...`);
|
||||||
|
|
||||||
|
for (const del of parentDeletedFiles) {
|
||||||
|
const id = Number(del.id);
|
||||||
|
const url = del.url;
|
||||||
|
|
||||||
|
if (!id || !url) {
|
||||||
|
console.log("⚠️ Invalid parent delete entry:", del);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3Key = getS3KeyFromUrl(url);
|
||||||
|
|
||||||
|
// Delete S3
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
|
||||||
|
// Delete DB
|
||||||
|
await prismaClient.hostParenetDocuments.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🗑️ Deleted PARENT document ID ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 11) UPLOAD DOCUMENTS */
|
||||||
|
async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) {
|
||||||
|
// const ext = originalName.split('.').pop() || 'jpg';
|
||||||
|
const ext = getExtensionFromMime(mimeType);
|
||||||
|
|
||||||
|
let s3Key = '';
|
||||||
|
|
||||||
|
if (folderType === 'logo') {
|
||||||
|
s3Key = `Documents/Host/${userInfo.id}/logo/company_logo.${ext}`;
|
||||||
|
}
|
||||||
|
else if (folderType === 'parent_company_logo') {
|
||||||
|
s3Key = `Documents/Host/${userInfo.id}/parent_company/logo/parent_company_logo.${ext}`;
|
||||||
|
}
|
||||||
|
else if (folderType === 'documents') {
|
||||||
|
s3Key = `Documents/Host/${userInfo.id}/documents/${documentTypeXid}_${fieldName}.${ext}`;
|
||||||
|
}
|
||||||
|
else if (folderType === 'parent_company') {
|
||||||
|
s3Key = `Documents/Host/${userInfo.id}/parent_company/${documentTypeXid}_${fieldName}.${ext}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await s3
|
||||||
|
.upload({
|
||||||
|
Bucket: config.aws.bucketName,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: mimeType,
|
||||||
|
ACL: 'private',
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Upload host docs */
|
||||||
|
const uploadedHostDocs: Array<any> = [];
|
||||||
|
for (const doc of hostDocs) {
|
||||||
|
if (!doc.file) continue;
|
||||||
|
|
||||||
|
const path = await uploadToS3(
|
||||||
|
doc.file.buffer,
|
||||||
|
doc.file.mimeType,
|
||||||
|
doc.file.fileName,
|
||||||
|
'documents',
|
||||||
|
doc.documentTypeXid,
|
||||||
|
doc.fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
|
uploadedHostDocs.push({
|
||||||
|
documentTypeXid: doc.documentTypeXid,
|
||||||
|
documentName: doc.fieldName,
|
||||||
|
filePath: path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Upload parent docs */
|
||||||
|
const uploadedParentDocs: Array<any> = [];
|
||||||
|
for (const doc of parentDocs) {
|
||||||
|
if (!doc.file) continue; // skip missing files in draft mode
|
||||||
|
|
||||||
|
const path = await uploadToS3(
|
||||||
|
doc.file.buffer,
|
||||||
|
doc.file.mimeType,
|
||||||
|
doc.file.fileName,
|
||||||
|
'parent_company',
|
||||||
|
doc.documentTypeXid,
|
||||||
|
doc.fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
|
uploadedParentDocs.push({
|
||||||
|
documentTypeXid: doc.documentTypeXid,
|
||||||
|
documentName: doc.documentName,
|
||||||
|
filePath: path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** UPLOAD LOGO (if provided) */
|
||||||
|
const logoFile = files.find(
|
||||||
|
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (logoFile && logoFile.buffer && logoFile.fileName) {
|
||||||
|
const logoUrl = await uploadToS3(
|
||||||
|
logoFile.buffer,
|
||||||
|
logoFile.mimeType,
|
||||||
|
logoFile.fileName,
|
||||||
|
'logo'
|
||||||
|
);
|
||||||
|
parsedCompany.logoPath = logoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** UPLOAD PARENT COMPANY LOGO (if provided) */
|
||||||
|
const parentLogoFile = files.find(
|
||||||
|
(f) => f.fieldName === 'parentCompanyLogo'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parentLogoFile && parentLogoFile.buffer && parentLogoFile.mimeType) {
|
||||||
|
// 🔒 Only upload when an actual file is present
|
||||||
|
const parentLogoUrl = await uploadToS3(
|
||||||
|
parentLogoFile.buffer,
|
||||||
|
parentLogoFile.mimeType,
|
||||||
|
parentLogoFile.fileName, // safe here because it's a real file
|
||||||
|
'parent_company_logo',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parsedParentCompany) {
|
||||||
|
parsedParentCompany.logoPath = parentLogoUrl;
|
||||||
|
} else {
|
||||||
|
parsedParentCompany = {
|
||||||
|
logoPath: parentLogoUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedCompany.cityXid) {
|
||||||
|
const city = await prismaClient.cities.findUnique({
|
||||||
|
where: { id: Number(parsedCompany.cityXid) }
|
||||||
|
});
|
||||||
|
if (!city) {
|
||||||
|
throw new ApiError(400, `City with ID ${parsedCompany.cityXid} not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedCompany.isSubsidairy) {
|
||||||
|
const parentDocuments = await hostService.getParentDocumentsByHostId(userInfo.id);
|
||||||
|
if (parentDocuments.length > 0) {
|
||||||
|
for (const doc of parentDocuments) {
|
||||||
|
try {
|
||||||
|
const s3Key = getS3KeyFromUrl(doc.filePath);
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("S3 delete failed:", doc.filePath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await hostService.deleteExistingParentRecords(userInfo.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 12) SAVE / UPDATE HOST ENTRY */
|
||||||
|
const createdOrUpdated = await hostService.addOrUpdateCompanyDetails(
|
||||||
|
userInfo.id,
|
||||||
|
parsedCompany,
|
||||||
|
uploadedHostDocs,
|
||||||
|
parsedParentCompany,
|
||||||
|
uploadedParentDocs,
|
||||||
|
isDraft,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||||
|
|
||||||
|
/** 13) SEND EMAIL ONLY IN FINAL SUBMISSION */
|
||||||
|
if (!isDraft) {
|
||||||
|
const details = await hostService.getSuggestionDetails(userInfo.id);
|
||||||
|
|
||||||
|
if (details.hostDetails.accountManagerXid) {
|
||||||
|
await sendEmailToAM(
|
||||||
|
details.hostDetails.accountManager.emailAddress,
|
||||||
|
details.hostDetails.accountManager.firstName,
|
||||||
|
details.hostDetails.companyName,
|
||||||
|
details.hostDetails.user.userRefNumber,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await sendEmailToMinglarAdmin(
|
||||||
|
config.MinglarAdminEmail,
|
||||||
|
config.MinglarAdminName,
|
||||||
|
details.hostDetails.companyName,
|
||||||
|
details.hostDetails.user.userRefNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** RESPONSE */
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: isDraft ? 'Company details saved as draft successfully.' : 'Company details uploaded successfully.',
|
||||||
|
data: {
|
||||||
|
id: createdOrUpdated.id,
|
||||||
|
isDraft,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ Error in addCompanyDetails:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { hostBankDetailsSchema } from '../../../../../common/utils/validation/host/addPaymentDetails.validation';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate user using the shared authForHost function
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
const hostId = userInfo.id;
|
||||||
|
|
||||||
|
if (Number.isNaN(hostId)) {
|
||||||
|
throw new ApiError(400, 'Host id must be a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = await hostService.getHostIdByUserXid(hostId);
|
||||||
|
if (!host) {
|
||||||
|
throw new ApiError(404, 'Host not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: { bankXid?: number; bankBranchXid?: number; accountNumber?: string; confirmAccountNumber?: string; accountHolderName?: string; currencyXid?: number };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Validate payload using Zod
|
||||||
|
const validationResult = hostBankDetailsSchema.safeParse({
|
||||||
|
...(body as object),
|
||||||
|
hostXid: host.host.id, // inject hostId from token (not from user input)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!validationResult.success) {
|
||||||
|
const errorMessages = validationResult.error.issues.map(e => e.message).join(', ');
|
||||||
|
throw new ApiError(400, `Validation failed: ${errorMessages}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedData = validationResult.data;
|
||||||
|
|
||||||
|
// Fetch IFSC code from bank branch
|
||||||
|
const bankBranch = await hostService.getBankBranchById(validatedData.bankBranchXid);
|
||||||
|
if (!bankBranch) {
|
||||||
|
throw new ApiError(404, 'Bank branch not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hostService.addPaymentDetails({
|
||||||
|
...validatedData,
|
||||||
|
ifscCode: bankBranch.ifscCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Payment details added successfully',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
53
src/modules/host/handlers/Host_Admin/onboarding/verifyOTP.ts
Normal file
53
src/modules/host/handlers/Host_Admin/onboarding/verifyOTP.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { TokenService } from '../../../services/token.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
const tokenService = new TokenService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Parse request body
|
||||||
|
let body: { email?: string; otp?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { email, otp } = body;
|
||||||
|
|
||||||
|
if (!email || !otp) {
|
||||||
|
throw new ApiError(400, 'Email and OTP are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailToLowerCase = email.toLowerCase();
|
||||||
|
|
||||||
|
await hostService.verifyHostOtp(emailToLowerCase, otp);
|
||||||
|
const user = await hostService.getHostByEmail(emailToLowerCase);
|
||||||
|
const generateTokenForHost = await tokenService.generateAuthToken(
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'OTP verified successfully',
|
||||||
|
accessToken: generateTokenForHost.access.token,
|
||||||
|
refreshToken: generateTokenForHost.refresh.token,
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
46
src/modules/host/handlers/getStepper.ts
Normal file
46
src/modules/host/handlers/getStepper.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { HostService } from '../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyHostToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user with their HostHeader stepper info
|
||||||
|
const host = await hostService.getHostIdByUserXid(userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Stepper information retrieved successfully',
|
||||||
|
data: {
|
||||||
|
stepper: host?.host?.stepper || null,
|
||||||
|
emailAddress: host.user?.emailAddress || null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
49
src/modules/host/handlers/getbyidhandler.ts
Normal file
49
src/modules/host/handlers/getbyidhandler.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import { HostService } from '../services/host.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Get host ID from path parameters
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyMinglarAdminHostToken(token);
|
||||||
|
const id = Number(userInfo.id)
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new ApiError(400, 'Host ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(id)) {
|
||||||
|
throw new ApiError(400, 'Invalid host ID format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostDetails = await hostService.getHostById(id);
|
||||||
|
|
||||||
|
if (!hostDetails) {
|
||||||
|
throw new ApiError(404, 'Host not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Host details retrieved successfully',
|
||||||
|
data: hostDetails,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = prismaClient;
|
||||||
|
|
||||||
export const handler = safeHandler(async (
|
export const handler = safeHandler(async (
|
||||||
event: APIGatewayProxyEvent
|
event: APIGatewayProxyEvent
|
||||||
@@ -11,7 +11,6 @@ export const handler = safeHandler(async (
|
|||||||
const result = await prisma.hostHeader.findMany({
|
const result = await prisma.hostHeader.findMany({
|
||||||
select: {
|
select: {
|
||||||
hostParent: true,
|
hostParent: true,
|
||||||
hostRefNumber: true,
|
|
||||||
hostStatusDisplay: true,
|
hostStatusDisplay: true,
|
||||||
accountManager: true,
|
accountManager: true,
|
||||||
},
|
},
|
||||||
|
|||||||
82
src/modules/host/handlers/resendOtp.ts
Normal file
82
src/modules/host/handlers/resendOtp.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
|
||||||
|
import { prismaClient } from "../../../common/database/prisma.lambda.service";
|
||||||
|
import { safeHandler } from "../../../common/utils/handlers/safeHandler";
|
||||||
|
import ApiError from "../../../common/utils/helper/ApiError";
|
||||||
|
import { resendOtpHelper } from "../../../common/utils/helper/resendOtpHelper";
|
||||||
|
import { resendOtpEmail } from "../services/resendOTPEmail.service";
|
||||||
|
|
||||||
|
const prisma = prismaClient;
|
||||||
|
|
||||||
|
// allowed purposes
|
||||||
|
const ALLOWED_PURPOSES = ["Register", "Login", "ForgotPassword"] as const;
|
||||||
|
type OtpPurpose = typeof ALLOWED_PURPOSES[number];
|
||||||
|
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||||
|
// parse body safely
|
||||||
|
let body: { email?: string; purpose?: string } = {};
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, "Invalid JSON in request body");
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow passing purpose via query string too (useful for GET requests)
|
||||||
|
const qsPurpose = event.queryStringParameters?.purpose;
|
||||||
|
const purposeRaw = (body.purpose || qsPurpose || "").trim();
|
||||||
|
|
||||||
|
if (!purposeRaw) {
|
||||||
|
throw new ApiError(400, "purpose is required. Allowed values: Register, Login, ForgotPassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ALLOWED_PURPOSES.includes(purposeRaw as OtpPurpose)) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
`Invalid purpose '${purposeRaw}'. Allowed values: ${ALLOWED_PURPOSES.join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const purpose = purposeRaw as OtpPurpose;
|
||||||
|
|
||||||
|
const email = (body.email || "").trim();
|
||||||
|
if (!email) throw new ApiError(400, "Email is required");
|
||||||
|
|
||||||
|
const emailToLowerCase = email.toLowerCase();
|
||||||
|
|
||||||
|
// find user (you can adapt the isActive / userStatus checks per your rules)
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { emailAddress: emailToLowerCase, isActive: true },
|
||||||
|
select: { id: true, emailAddress: true, role: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, "User not found");
|
||||||
|
}
|
||||||
|
const role = user.role.roleName
|
||||||
|
|
||||||
|
// call resend helper (old OTPs become inactive + verified, new OTP gets created)
|
||||||
|
const otpResult = await resendOtpHelper(
|
||||||
|
prisma,
|
||||||
|
user.id,
|
||||||
|
purpose,
|
||||||
|
6, // 6-digit OTP
|
||||||
|
5 // expires in 5 minutes
|
||||||
|
);
|
||||||
|
|
||||||
|
// send email (use appropriate template based on 'purpose' inside the email service)
|
||||||
|
await resendOtpEmail(user.emailAddress, otpResult.otp, role);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: "OTP resent successfully.",
|
||||||
|
data: { purpose },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
File diff suppressed because it is too large
Load Diff
39
src/modules/host/services/resendOTPEmail.service.ts
Normal file
39
src/modules/host/services/resendOTPEmail.service.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { brevoService } from "@/common/email/brevoApi";
|
||||||
|
import ApiError from "@/common/utils/helper/ApiError";
|
||||||
|
|
||||||
|
export async function resendOtpEmail(
|
||||||
|
emailAddress: string,
|
||||||
|
otp: string | number,
|
||||||
|
role: string
|
||||||
|
): Promise<{
|
||||||
|
sent: boolean;
|
||||||
|
// messageId: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const subject = "New OTP from Minglar Team";
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Dear ${role},</p>
|
||||||
|
<p>Your new OTP is: <strong>${otp}</strong></p>
|
||||||
|
<p>This code will be valid for the next 5 minutes.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await brevoService.sendEmail({
|
||||||
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sent: true,
|
||||||
|
// messageId: result.messageId
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Brevo email send failed:", err);
|
||||||
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { brevoService } from "@/common/email/brevoApi";
|
||||||
|
import ApiError from "@/common/utils/helper/ApiError";
|
||||||
|
|
||||||
|
export async function sendEmailToAM(
|
||||||
|
emailAddress: string,
|
||||||
|
amName: string,
|
||||||
|
hostCompanyName: string,
|
||||||
|
hostRefNumber: string
|
||||||
|
): Promise<{
|
||||||
|
sent: boolean;
|
||||||
|
// messageId: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const subject = `Host Application Re-Submited : ${hostCompanyName}`;
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Dear ${amName},</p>
|
||||||
|
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has re-submited the application with implimented suggestions.</p>
|
||||||
|
<p>Please review their appliaction and take the necessary action.</p>
|
||||||
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await brevoService.sendEmail({
|
||||||
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sent: true,
|
||||||
|
// messageId: result.messageId
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Brevo email send failed:", err);
|
||||||
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendEmailToMinglarAdmin(
|
||||||
|
emailAddress: string,
|
||||||
|
minglarAdminName: string,
|
||||||
|
hostCompanyName: string,
|
||||||
|
hostRefNumber: string
|
||||||
|
): Promise<{
|
||||||
|
sent: boolean;
|
||||||
|
// messageId: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const subject = `New Host Application Recieved : ${hostCompanyName}`;
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Dear ${minglarAdminName},</p>
|
||||||
|
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has submited their application.</p>
|
||||||
|
<p>Please review their appliaction and take the necessary action.</p>
|
||||||
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await brevoService.sendEmail({
|
||||||
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sent: true,
|
||||||
|
// messageId: result.messageId
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Brevo email send failed:", err);
|
||||||
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/modules/host/services/sendOTPEmail.service.ts
Normal file
39
src/modules/host/services/sendOTPEmail.service.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { brevoService } from "@/common/email/brevoApi";
|
||||||
|
import ApiError from "@/common/utils/helper/ApiError";
|
||||||
|
|
||||||
|
export async function sendOtpEmailForHost(
|
||||||
|
emailAddress: string,
|
||||||
|
otp: string | number
|
||||||
|
): Promise<{
|
||||||
|
sent: boolean;
|
||||||
|
// messageId: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const subject = "OTP for Host Registration";
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Dear Host,</p>
|
||||||
|
<p>You’re almost all set! 🎉</p>
|
||||||
|
<p>Enter <strong>${otp}</strong> to wrap your registration.</p>
|
||||||
|
<p>This code will be valid for the next 5 minutes.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await brevoService.sendEmail({
|
||||||
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sent: true,
|
||||||
|
// messageId: result.messageId
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Brevo email send failed:", err);
|
||||||
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
|
}
|
||||||
|
}
|
||||||
167
src/modules/host/services/token.service.ts
Normal file
167
src/modules/host/services/token.service.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||||
|
import moment from "moment";
|
||||||
|
import config from "../../../config/config";
|
||||||
|
|
||||||
|
export class TokenService {
|
||||||
|
constructor(private prisma: PrismaClient) { }
|
||||||
|
|
||||||
|
private generateToken(
|
||||||
|
user_xid: number,
|
||||||
|
expiresIn: Date,
|
||||||
|
type: string,
|
||||||
|
secret: string
|
||||||
|
): { token: string; expires: Date } {
|
||||||
|
const token = jwt.sign(
|
||||||
|
{
|
||||||
|
sub: user_xid,
|
||||||
|
iat: moment().unix(),
|
||||||
|
exp: moment(expiresIn).unix(),
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
secret
|
||||||
|
);
|
||||||
|
|
||||||
|
return { token, expires: expiresIn };
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAuthToken(
|
||||||
|
user_xid: number,
|
||||||
|
): Promise<{
|
||||||
|
access: { token: string; expires: Date };
|
||||||
|
refresh: { token: string; expires: Date };
|
||||||
|
}> {
|
||||||
|
const accessTokenExpires = moment()
|
||||||
|
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||||
|
.toDate();
|
||||||
|
|
||||||
|
const refreshTokenExpires = moment()
|
||||||
|
.add(config.jwt.refreshExpirationDays, "days")
|
||||||
|
.toDate();
|
||||||
|
|
||||||
|
const accessToken = this.generateToken(
|
||||||
|
user_xid,
|
||||||
|
accessTokenExpires,
|
||||||
|
"access",
|
||||||
|
config.jwt.secret
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshToken = this.generateToken(
|
||||||
|
user_xid,
|
||||||
|
refreshTokenExpires,
|
||||||
|
"refresh",
|
||||||
|
config.jwt.secret
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.prisma.token.deleteMany({
|
||||||
|
where: { userXid: user_xid }
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.prisma.token.create({
|
||||||
|
data: {
|
||||||
|
token: refreshToken.token,
|
||||||
|
expiringAt: refreshToken.expires,
|
||||||
|
tokenType: "refresh",
|
||||||
|
isBlackListed: false,
|
||||||
|
|
||||||
|
user: {
|
||||||
|
connect: { id: user_xid },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
access: accessToken,
|
||||||
|
refresh: refreshToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateAuthTokenAdmin(
|
||||||
|
user_xid: number
|
||||||
|
): Promise<{
|
||||||
|
access: { token: string; expires: Date };
|
||||||
|
refresh: { token: string; expires: Date };
|
||||||
|
}> {
|
||||||
|
const accessTokenExpires = moment()
|
||||||
|
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||||
|
.toDate();
|
||||||
|
|
||||||
|
const refreshTokenExpires = moment()
|
||||||
|
.add(config.jwt.refreshExpirationDays, "days")
|
||||||
|
.toDate();
|
||||||
|
|
||||||
|
const accessToken = this.generateToken(
|
||||||
|
user_xid,
|
||||||
|
accessTokenExpires,
|
||||||
|
"access",
|
||||||
|
config.jwt.secret
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshToken = this.generateToken(
|
||||||
|
user_xid,
|
||||||
|
refreshTokenExpires,
|
||||||
|
"refresh",
|
||||||
|
config.jwt.secret
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.prisma.token.deleteMany({
|
||||||
|
where: { userXid: user_xid }
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.prisma.token.create({
|
||||||
|
data: {
|
||||||
|
token: refreshToken.token,
|
||||||
|
expiringAt: refreshToken.expires,
|
||||||
|
tokenType: "refresh",
|
||||||
|
isBlackListed: false,
|
||||||
|
user: {
|
||||||
|
connect: { id: user_xid },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
access: accessToken,
|
||||||
|
refresh: refreshToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeToken(user_xid: number, deviceId: string): Promise<boolean> {
|
||||||
|
const existingToken = await this.prisma.token.findFirst({
|
||||||
|
where: {
|
||||||
|
id: user_xid,
|
||||||
|
deviceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingToken) return false;
|
||||||
|
|
||||||
|
await this.prisma.token.delete({ where: { id: existingToken.id } });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async isTokenBlackListed(token: string): Promise<boolean> {
|
||||||
|
const existing = await this.prisma.token.findUnique({
|
||||||
|
where: { token },
|
||||||
|
});
|
||||||
|
return existing ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyRefreshToken(
|
||||||
|
token: string
|
||||||
|
): Promise<string | JwtPayload | null> {
|
||||||
|
try {
|
||||||
|
return jwt.verify(token, config.jwt.secret);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async decodeToken(token: string): Promise<string | JwtPayload | null> {
|
||||||
|
try {
|
||||||
|
return jwt.decode(token);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/modules/minglaradmin/dto/minglar.dto.ts
Normal file
99
src/modules/minglaradmin/dto/minglar.dto.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// src/modules/host/dto/host.dto.ts
|
||||||
|
import { IsInt, IsOptional, IsString, IsBoolean, IsEmail } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateMinglarDto {
|
||||||
|
@IsString()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@IsEmail()
|
||||||
|
emailAddress: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
isdCode?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
mobileNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
userPassword?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
roleXid?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateMinglarDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
firstName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
lastName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
emailAddress?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetMinglarLoginResponseDTO {
|
||||||
|
id: number;
|
||||||
|
firstName: string | null;
|
||||||
|
lastName: string | null;
|
||||||
|
emailAddress: string;
|
||||||
|
mobileNumber: string | null;
|
||||||
|
isActive: boolean;
|
||||||
|
roleXid: number;
|
||||||
|
isProfileUpdated: boolean;
|
||||||
|
profileImage: string;
|
||||||
|
userStatus: string;
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
|
||||||
|
constructor(user: any, accessToken: string, refreshToken: string) {
|
||||||
|
this.id = user.id;
|
||||||
|
this.firstName = user.firstName;
|
||||||
|
this.lastName = user.lastName;
|
||||||
|
this.emailAddress = user.emailAddress;
|
||||||
|
this.mobileNumber = user.mobileNumber;
|
||||||
|
this.isActive = user.isActive;
|
||||||
|
this.roleXid = user.roleXid;
|
||||||
|
this.profileImage = user.profileImage;
|
||||||
|
this.isProfileUpdated = user.isProfileUpdated;
|
||||||
|
this.userStatus = user.userStatus;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddPaymentDetailsDTO {
|
||||||
|
bankXid: number;
|
||||||
|
bankBranchXid: number;
|
||||||
|
accountNumber: number;
|
||||||
|
accountHolderName: string;
|
||||||
|
ifscCode: string;
|
||||||
|
hostXid: number;
|
||||||
|
|
||||||
|
constructor(bankXid: number, bankBranchXid: number, accountNumber: number, accountHolderName: string, ifscCode: string, hostXid: number) {
|
||||||
|
this.bankXid = bankXid;
|
||||||
|
this.bankBranchXid = bankBranchXid;
|
||||||
|
this.accountNumber = accountNumber;
|
||||||
|
this.accountHolderName = accountHolderName;
|
||||||
|
this.ifscCode = ifscCode;
|
||||||
|
this.hostXid = hostXid;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/modules/minglaradmin/handlers/createPassword.ts
Normal file
65
src/modules/minglaradmin/handlers/createPassword.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Extract token from headers
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if(!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate user using the shared authForHost function
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
const user_xid = userInfo.id;
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: { password?: string; confirmPassword?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { password, confirmPassword } = body;
|
||||||
|
|
||||||
|
if (!password || !confirmPassword) {
|
||||||
|
throw new ApiError(400, 'Password and confirm password are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate password match
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
throw new ApiError(400, 'Password and confirm password do not match');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate password length
|
||||||
|
if (password.length < 8) {
|
||||||
|
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||||
|
}
|
||||||
|
|
||||||
|
await minglarService.createPassword(user_xid, password);
|
||||||
|
const userDetails = await minglarService.getBasicUserDetails(user_xid)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Password created successfully',
|
||||||
|
data: userDetails,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
65
src/modules/minglaradmin/handlers/getAMDetail_ById.ts
Normal file
65
src/modules/minglaradmin/handlers/getAMDetail_ById.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all host applications handler
|
||||||
|
* Returns host details with status, submission date, and account manager info
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token =
|
||||||
|
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
401,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
const amXid = event.pathParameters?.amXid;
|
||||||
|
if (!amXid) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'Account Manager XID is required in path parameters.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const amId = Number(amXid);
|
||||||
|
if (Number.isNaN(amId)) {
|
||||||
|
throw new ApiError(400, 'Account Manager XID must be a valid number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get all host applications from service based on user role
|
||||||
|
const getAmDetailsByid = await minglarService.getAMdetailById( amId );
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Account Manager details retrieved successfully',
|
||||||
|
data: getAmDetailsByid,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { sendEmailToHostForApprovedApplication } from '../../../services/approvalMailtoHost.service';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface AddSuggestionBody {
|
||||||
|
hostXid: number;
|
||||||
|
title: string;
|
||||||
|
comments: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: AddSuggestionBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hostXid } = body;
|
||||||
|
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await minglarService.acceptHostApplication(hostXid, userInfo.id);
|
||||||
|
const hostDetails = await minglarService.getUserDetails(hostXid)
|
||||||
|
await sendEmailToHostForApprovedApplication(hostDetails.emailAddress, hostDetails.firstName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Application accepted successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { sendAMPQQAcceptanceMailtoHost } from '../../../../minglaradmin/services/approvalMailtoHost.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface Body {
|
||||||
|
activityId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: Body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityId } = body;
|
||||||
|
|
||||||
|
if (!activityId) {
|
||||||
|
throw new ApiError(400, 'activityId is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await minglarService.acceptPQByAM(
|
||||||
|
Number(activityId),
|
||||||
|
Number(userInfo.id)
|
||||||
|
);
|
||||||
|
const hostXid = await minglarService.getHostXidByActivityId(activityId)
|
||||||
|
const hostDetails = await minglarService.getUserDetails(hostXid)
|
||||||
|
await sendAMPQQAcceptanceMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 201,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Approved PQ successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
// import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface AddSuggestionBody {
|
||||||
|
title: string;
|
||||||
|
comments: string;
|
||||||
|
activity_pqq_header_xid:number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: AddSuggestionBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, comments , activity_pqq_header_xid} = body;
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
throw new ApiError(400, 'Title is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!comments) {
|
||||||
|
throw new ApiError(400, 'Comments are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!activity_pqq_header_xid){
|
||||||
|
throw new ApiError(400 , "Activity Pqq HeaderXid Required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate title is one of the allowed types
|
||||||
|
// const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
|
||||||
|
// if (!allowedTitles.includes(title)) {
|
||||||
|
// throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid,user.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Suggestion added successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface AddSuggestionBody {
|
||||||
|
hostXid: number;
|
||||||
|
title: string;
|
||||||
|
comments: string;
|
||||||
|
isParent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: AddSuggestionBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hostXid, title, comments, isParent } = body;
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!hostXid) {
|
||||||
|
throw new ApiError(400, 'Host ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
throw new ApiError(400, 'Title is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!comments) {
|
||||||
|
throw new ApiError(400, 'Comments are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate title is one of the allowed types
|
||||||
|
// const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
|
||||||
|
// if (!allowedTitles.includes(title)) {
|
||||||
|
// throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await minglarService.addHostSuggestion(hostXid, title, comments, user.id, isParent);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Suggestion added successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all host applications handler with pagination
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details including role
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get query parameters
|
||||||
|
const search = event.queryStringParameters?.search || '';
|
||||||
|
const userStatus = event.queryStringParameters?.userStatus || '';
|
||||||
|
const roleFilter = event.queryStringParameters?.roleFilter || '';
|
||||||
|
|
||||||
|
// Parse pagination parameters
|
||||||
|
const paginationParams = paginationService.getPaginationFromEvent(event);
|
||||||
|
const paginationOptions = paginationService.parsePaginationParams(paginationParams);
|
||||||
|
const applicationStatus = event.queryStringParameters?.applicationStatus || '';
|
||||||
|
|
||||||
|
// Get paginated host applications
|
||||||
|
const { data, totalCount } = await minglarService.getAllHostApplications(
|
||||||
|
user.id,
|
||||||
|
Number(user.roleXid),
|
||||||
|
search,
|
||||||
|
userStatus,
|
||||||
|
paginationOptions,
|
||||||
|
roleFilter,
|
||||||
|
applicationStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create paginated response
|
||||||
|
const paginatedResponse = paginationService.createPaginatedResponse(
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
paginationOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Host applications retrieved successfully',
|
||||||
|
...paginatedResponse,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Get host ID from path parameters
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
const host_xid = event.pathParameters?.host_xid;
|
||||||
|
if (!host_xid) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'Host ID is required in path parameters.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const hostDetails = await minglarService.getHostDetailsById(Number(host_xid));
|
||||||
|
|
||||||
|
if (!hostDetails) {
|
||||||
|
throw new ApiError(404, 'Host not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Host details retrieved successfully',
|
||||||
|
data: hostDetails,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
import { sendAMRejectionMailtoHost } from '../../../services/rejectionMailtoHost.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface AddSuggestionBody {
|
||||||
|
hostXid: number;
|
||||||
|
title: string;
|
||||||
|
comments: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: AddSuggestionBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hostXid } = body;
|
||||||
|
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await minglarService.rejectHostApplicationAM(hostXid, userInfo.id);
|
||||||
|
const hostDetails = await minglarService.getUserDetails(hostXid)
|
||||||
|
await sendAMRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Application rejected successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
import { sendAMPQQRejectionMailtoHost } from '../../../../minglaradmin/services/rejectionMailtoHost.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface Body {
|
||||||
|
activityId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: Body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { activityId } = body;
|
||||||
|
|
||||||
|
if (!activityId) {
|
||||||
|
throw new ApiError(400, 'activityId is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await minglarService.rejectPQQbyAM(
|
||||||
|
Number(activityId),
|
||||||
|
Number(userInfo.id)
|
||||||
|
);
|
||||||
|
const hostXid = await minglarService.getHostXidByActivityId(activityId)
|
||||||
|
const hostDetails = await minglarService.getUserDetails(hostXid)
|
||||||
|
await sendAMPQQRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 201,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Rejected PQ successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface assignAMToHostBody {
|
||||||
|
host_xid: number;
|
||||||
|
account_manager_xid: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all host applications handler
|
||||||
|
* Returns host details with status, submission date, and account manager info
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token =
|
||||||
|
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
401,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details including role
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: assignAMToHostBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { host_xid, account_manager_xid } = body;
|
||||||
|
|
||||||
|
// Get all host applications from service based on user role
|
||||||
|
await minglarService.assignAMToHost(user.id, host_xid, account_manager_xid);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await minglarService.notifyAMOfAssignment(account_manager_xid);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to notify AM after assignment:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'AM assigned to host successfully',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface assignAMToHostBody {
|
||||||
|
host_xid: number,
|
||||||
|
agreementStartDate: string,
|
||||||
|
duration: number,
|
||||||
|
isCommisionBase: boolean,
|
||||||
|
commisionPer: number,
|
||||||
|
amountPerBooking: number,
|
||||||
|
durationFrequency: string,
|
||||||
|
payoutDurationNum: number,
|
||||||
|
payoutDurationFrequency: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details including role
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: assignAMToHostBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
host_xid,
|
||||||
|
agreementStartDate,
|
||||||
|
duration,
|
||||||
|
isCommisionBase,
|
||||||
|
commisionPer,
|
||||||
|
amountPerBooking,
|
||||||
|
durationFrequency,
|
||||||
|
payoutDurationNum,
|
||||||
|
payoutDurationFrequency
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
await minglarService.acceptHostApplicationMinglarAdmin(
|
||||||
|
host_xid,
|
||||||
|
userInfo.id,
|
||||||
|
agreementStartDate,
|
||||||
|
duration,
|
||||||
|
isCommisionBase,
|
||||||
|
commisionPer,
|
||||||
|
amountPerBooking,
|
||||||
|
durationFrequency,
|
||||||
|
payoutDurationNum,
|
||||||
|
payoutDurationFrequency);
|
||||||
|
// await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Details edited successfully',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
const prePopulateService = new PrePopulateService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
const hostXid = Number(event.pathParameters?.id)
|
||||||
|
|
||||||
|
// Get pagination params from event
|
||||||
|
const paginationParams = paginationService.getPaginationFromEvent(event);
|
||||||
|
const paginationOptions = paginationService.parsePaginationParams(paginationParams);
|
||||||
|
|
||||||
|
// Read optional search query (supports ?search= or ?q=)
|
||||||
|
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
|
||||||
|
|
||||||
|
const result = await minglarService.getAllHostActivityForMinglar(
|
||||||
|
search ? String(search) : undefined,
|
||||||
|
hostXid,
|
||||||
|
paginationOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
...result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
|
||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all host applications handler
|
||||||
|
* Returns host details with status, submission date, and account manager info
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token =
|
||||||
|
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
401,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details including role
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract optional search query
|
||||||
|
const queryParams = event.queryStringParameters || {};
|
||||||
|
const search =
|
||||||
|
(queryParams.search as string) ||
|
||||||
|
(queryParams.q as string) ||
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
const paginationParams = paginationService.getPaginationFromEvent(event);
|
||||||
|
const paginationOptions =
|
||||||
|
paginationService.parsePaginationParams(paginationParams);
|
||||||
|
|
||||||
|
// Get all host applications from service based on user role
|
||||||
|
const { data, totalCount } =
|
||||||
|
await minglarService.getAllOnboardingHostApplications(
|
||||||
|
paginationOptions,
|
||||||
|
search,
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginatedResponse = paginationService.createPaginatedResponse(
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
paginationOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Host applications retrieved successfully',
|
||||||
|
...paginatedResponse,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all NEW host applications handler
|
||||||
|
* Returns host details with status, submission date, and account manager info
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(
|
||||||
|
async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token =
|
||||||
|
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
401,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Get user details including role
|
||||||
|
const user = await prismaClient.user.findUnique({
|
||||||
|
where: { id: userInfo.id },
|
||||||
|
select: { id: true, roleXid: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ApiError(404, 'User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract optional search query
|
||||||
|
const queryParams = event.queryStringParameters || {};
|
||||||
|
const search =
|
||||||
|
(queryParams.search as string) ||
|
||||||
|
(queryParams.q as string) ||
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
const paginationParams = paginationService.getPaginationFromEvent(event);
|
||||||
|
const paginationOptions =
|
||||||
|
paginationService.parsePaginationParams(paginationParams);
|
||||||
|
|
||||||
|
// Get all host applications from service based on user role
|
||||||
|
const { data, totalCount } =
|
||||||
|
await minglarService.getAllOnboardingHostApplications_New(
|
||||||
|
paginationOptions,
|
||||||
|
search,
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginatedResponse = paginationService.createPaginatedResponse(
|
||||||
|
data,
|
||||||
|
totalCount,
|
||||||
|
paginationOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Host applications retrieved successfully',
|
||||||
|
...paginatedResponse,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../services/minglar.service';
|
||||||
|
import { sendEmailToHostForRejectedApplication } from '../../../services/rejectionMailtoHost.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
interface AddSuggestionBody {
|
||||||
|
hostXid: number;
|
||||||
|
title: string;
|
||||||
|
comments: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add suggestion handler for host applications
|
||||||
|
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
|
||||||
|
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
const userInfo = await verifyOnlyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
let body: AddSuggestionBody;
|
||||||
|
|
||||||
|
try {
|
||||||
|
body = event.body ? JSON.parse(event.body) : {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new ApiError(400, 'Invalid JSON in request body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hostXid } = body;
|
||||||
|
|
||||||
|
|
||||||
|
// Add suggestion using service
|
||||||
|
await minglarService.rejectHostApplication(hostXid, userInfo.id);
|
||||||
|
const hostDetails = await minglarService.getUserDetails(hostXid)
|
||||||
|
if (!hostDetails?.emailAddress) {
|
||||||
|
throw new ApiError(404, 'Host details or email address not found');
|
||||||
|
}
|
||||||
|
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress)
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Application rejected successfully',
|
||||||
|
data: null,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
|
||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { MinglarService } from '../../../../minglaradmin/services/minglar.service';
|
||||||
|
|
||||||
|
const minglarService = new MinglarService(prismaClient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get suggestions handler
|
||||||
|
* Retrieves suggestions based on user's role and host assignments
|
||||||
|
*/
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
// Verify authentication token
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify token and get user info
|
||||||
|
await verifyMinglarAdminToken(token);
|
||||||
|
|
||||||
|
const hostXid = Number(event.pathParameters?.hostXid)
|
||||||
|
|
||||||
|
if (!hostXid) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'Host ID is required in path parameters.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get suggestions using service
|
||||||
|
const suggestions = await minglarService.getSuggestionsForAM(hostXid);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Suggestions retrieved successfully',
|
||||||
|
data: suggestions,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user