94 Commits

Author SHA1 Message Date
4d3796c5f3 Refactor companyTypes seeding and enhance host retrieval to include user email address 2025-12-16 16:36:15 +05:30
2f3c531c56 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-16 16:09:28 +05:30
a2907929d4 updated serverless version 2025-12-16 16:08:59 +05:30
paritosh18
ef2b23ef83 Update serverless configuration to use exported stack output for Prisma Lambda Layer ARN, ensuring proper deployment functionality. 2025-12-16 16:07:03 +05:30
2767d29d79 Update parent company validation to allow optional fields and handle null values in company details submission. 2025-12-16 15:38:03 +05:30
46daec00ce fixed the keytooloong issue 2025-12-16 14:37:38 +05:30
43e494780d Refactor document name handling in host service to sanitize input and ensure valid connections for city, state, and country IDs during host creation and updates. 2025-12-16 13:16:22 +05:30
0da18b18f7 Add getSuggestionsForAM function and corresponding handler for retrieving suggestions based on host assignments. Update serverless configuration to include new API endpoint. 2025-12-16 12:07:42 +05:30
b5304b3c26 Refactor OTP email content for host registration and resend functionality to improve clarity and tone. 2025-12-15 16:53:12 +05:30
82340c2918 Implement email notifications for host application acceptance and rejection, including host details retrieval and email content customization. 2025-12-10 14:59:16 +05:30
b8f5f92c98 Add validation for city ID in company details submission to ensure existence in the database 2025-12-09 17:38:44 +05:30
3652d851f7 Enhance email notifications for host application approvals and rejections by including the host's first name in the greeting. 2025-12-09 15:02:15 +05:30
paritosh18
6166075967 Update service name in serverless configuration to 'minglar' and add 'zod' dependency in package-lock.json 2025-12-09 13:50:30 +05:30
paritosh18
b6cb5831c2 Refactor Prisma client usage and enhance service integration for improved connection management 2025-12-09 13:49:20 +05:30
paritosh18
a39cc1c3c8 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2025-12-09 12:09:38 +05:30
paritosh18
ae76618f7a Add environment configuration template and update .gitignore for new env files 2025-12-09 12:09:21 +05:30
e957fc5c50 Add token blacklist check in JWT middlewares to enhance session management and security 2025-12-09 12:07:03 +05:30
paritosh18
856db687c3 Update service name in serverless configuration to 'minglarDev' for development environment 2025-12-08 17:58:38 +05:30
a020a28993 Update host application handlers to retrieve user details using hostXid instead of userInfo.id for accurate email notifications. 2025-12-08 17:31:55 +05:30
1a520ae9e1 Remove email parameter from resendOtpHelper and normalize email addresses to lowercase in the resend OTP handler for consistent processing. 2025-12-08 15:25:21 +05:30
d5d6951e64 Normalize email addresses to lowercase in OTP verification handler for consistent processing. 2025-12-08 15:19:31 +05:30
bbd55562af Add HOST_LINK environment variable and update related configurations. Normalize email addresses to lowercase in login and registration handlers. Enhance email notifications for approved and rejected applications with HOST_LINK. 2025-12-08 15:08:05 +05:30
9abadba8f5 Refactor database access by introducing prisma.lambda.service for centralized PrismaClient usage across modules. Update imports in various handlers and services to utilize the new service, ensuring consistent database interactions and improved maintainability. 2025-12-08 11:23:58 +05:30
747566497c Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-06 16:33:57 +05:30
6c3e5ccd60 Add multiple states, cities, and banks to seed data in prisma/seed.ts 2025-12-06 16:33:35 +05:30
ca9ba601ad Add additional states and cities to seed data in Prisma 2025-12-06 16:27:54 +05:30
eab6565e12 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-06 16:11:53 +05:30
6a84876518 Remove profile completion percentage calculation from user update in MinglarService 2025-12-06 12:55:07 +05:30
d8fb4b242d Add roleXid field to user selection in MinglarService 2025-12-06 12:39:21 +05:30
51b053310f Add validation to prevent duplicate host accounts in addPaymentDetails method 2025-12-06 12:06:04 +05:30
06010ef6e8 Refactor API paths for acceptPQByAM and rejectPQQByAM to remove activityId from URL and update request body parsing for activityId 2025-12-05 19:21:19 +05:30
7b9763c668 Enhance getAllHostActivityForMinglar method to exclude activities with DRAFT_PQ status 2025-12-05 18:51:50 +05:30
4c1a04d043 Update serverless configuration to comment out Prisma client dependencies and enhance MinglarService to exclude draft activities from host activity retrieval. 2025-12-05 18:50:14 +05:30
paritosh18
ecf45c3e7c Merge branch 'mayank' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-05 14:56:45 +05:30
paritosh18
20a931ab27 Add AWS Lambda Bundle Size Optimization Guide and update build scripts for Prisma layer 2025-12-05 14:56:43 +05:30
6a84fbc0c3 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-05 14:38:32 +05:30
9fc8336bd9 fixed the routes 2025-12-05 14:37:37 +05:30
b049146664 Refactor serverless configuration to rename service, add Prisma layer support, and include AM_INVITATION_LINK in environment variables. Update validation schema and email invitation content for improved user experience. 2025-12-05 13:45:49 +05:30
963f84681c Enhance HostService to retrieve and utilize existing logo paths for host and parent companies, ensuring consistency in logo display across company details. 2025-12-04 20:18:22 +05:30
ab9e02972e Refactor hostCompanyDetails validation schema to make address and location fields optional; update login handler to remove password comparison logic; enhance host service to include user status check and return specific user fields; clean up profile completion logic in MinglarService. 2025-12-04 20:01:09 +05:30
33b330a15b Merge branch 'pqq-optimized-logic' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into pqq-optimized-logic 2025-12-04 18:40:44 +05:30
d898dcd8ff Add S3 document deletion functionality in submitCompanyDetails handler; update host service to return activity ID with sorted categories; modify PQP details retrieval to use new admin host token for authentication. 2025-12-04 18:40:41 +05:30
paritosh18
ff18fcbf9f Merge branch 'pqq-optimized-logic' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into pqq-optimized-logic 2025-12-04 11:54:12 +05:30
paritosh18
a872ed89e4 Enhance MinglarService: Add id field to selection in PQQ data retrieval and ensure activityPqqHeaderId is set at the category level for better data integrity. 2025-12-04 11:53:37 +05:30
759eeb298c Update sorting logic for categories and subcategories to ensure proper order in responses. 2025-12-04 08:33:21 +05:30
1b72e65a71 Enhance PQQ data retrieval: Add comments and pqqAnswerXid fields to the selection in HostService and MinglarService. Update PQQAnswers structure to include all answer options for questions, improving data handling in responses. 2025-12-03 19:43:22 +05:30
4a7e5fbb1e Add PQQ functionality: Introduce new endpoints for creating activities and submitting answers, along with updates to the Minglar service for retrieving PQQ details. Update serverless configuration to include new function files. 2025-12-03 19:21:21 +05:30
ca5936d0db Update referencedBy field in HostService to default to null if undefined 2025-12-03 14:56:15 +05:30
c0d607a321 Add optional referencedBy field to hostCompanyDetails validation schema and update HostService to handle referencedBy data 2025-12-03 14:41:00 +05:30
1d684b7de6 Add referencedBy field to HostHeader model and update import paths for various handlers 2025-12-03 14:34:53 +05:30
16b16ac7ca Refactor import path for verifyMinglarAdminHostToken and update selection fields in getAllPQQQuesAndSubmittedAns method 2025-12-03 13:56:39 +05:30
0e0c63e31a path updated for the renamed file 2025-12-03 13:49:56 +05:30
822b425c1d Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-03 13:43:41 +05:30
42e2d7a579 taking the activity xid in get all submited ques ans 2025-12-03 13:43:30 +05:30
paritosh18
78f49b35dd Update API path for retrieving all PQQ questions and answers for AM 2025-12-03 13:37:01 +05:30
paritosh18
930ae708a1 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into sprint1 2025-12-03 13:20:38 +05:30
paritosh18
8fb3f16d18 Add endpoint to retrieve all PQQ questions and answers for AM 2025-12-03 13:20:21 +05:30
3d6226ddac added search 2025-12-03 13:18:28 +05:30
38c616a7af making entries in host track 2025-12-03 12:44:07 +05:30
c6ab3e57c0 fixed the condition of submit pq for review 2025-12-02 20:19:03 +05:30
paritosh18
776c03911e Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into sprint1 2025-12-02 20:11:19 +05:30
paritosh18
781058c443 Implement pagination for host activity retrieval in HostService and MinglarService 2025-12-02 20:10:10 +05:30
3b723e5d1f made accept pq by am api 2025-12-02 20:09:42 +05:30
paritosh18
56ebf44d37 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2025-12-02 19:58:52 +05:30
4f8274adb9 Add pinCode field to location selection in MinglarService 2025-12-02 19:00:30 +05:30
39f182b8e9 sending city state abd country in getbyidAM 2025-12-02 18:59:06 +05:30
d9f7cd9a0f fixed the optional chaining 2025-12-02 18:08:39 +05:30
4772c320ba Enhance HostService and MinglarService to include account manager details and process profile images. Updated getAllHostActivity to retrieve account manager information and generate presigned URLs for media and profile images. Refactored media processing logic for improved clarity and consistency. 2025-12-02 17:53:19 +05:30
paritosh18
f19f5e46c4 Remove commented-out validation for allowed titles in addPQQSuggestion handler 2025-12-02 17:33:30 +05:30
paritosh18
156aed2429 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2025-12-02 17:27:57 +05:30
paritosh18
fb77111e34 Refactor payment details handling: remove IFSC code validation, add currencyXid to DTO, and implement bank branch lookup for IFSC code retrieval. 2025-12-02 17:27:36 +05:30
6b673a173d Refactor host functions and constants for improved clarity and functionality. Renamed 'getAllActivityType' to 'prePopulateNewActivity' and updated its path. Added 'submitPQQForReview' handler for submitting PQQ for review. Enhanced error handling and response structure in 'submitPQQ_Answer' and 'submitPQQForReview' methods. Updated constants to standardize PQQ-related statuses. Improved S3 file handling logic in various handlers. 2025-12-02 17:24:01 +05:30
3c4b0db39f Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-02 13:42:27 +05:30
e72c260b18 Add ActivityTrack model to schema and update User and Activities models to include activityTracks relation. Modify seed data to reflect new interest names and activity types. Implement activity reference number generation in HostService for activity creation. 2025-12-02 13:42:14 +05:30
paritosh18
9a777eb3f9 Refactor addOrUpdateCompanyDetails method in HostService to streamline host status handling and improve document management logic for both new and existing companies. 2025-12-02 13:40:41 +05:30
paritosh18
c3f0a1d82a Merge branch 'paritosh' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-02 13:19:16 +05:30
3f921febe0 Refactor host status determination logic in HostService to handle various submission cases, including updates and drafts. Update host suggestion review process to only mark suggestions as reviewed when not in draft state. 2025-12-01 20:25:23 +05:30
paritosh18
4bc5eb8d4d Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 19:56:43 +05:30
f3f0b2a81b Refactor HOST_SUGGESTION_TITLES to comments in minglar.constant.ts, update HostService to filter HostSuggestion by active and reviewed status, and modify addSuggestion handler to remove title validation against HOST_SUGGESTION_TITLES. Enhance MinglarService to include profile image handling and add city, country, and state selections in user queries. 2025-12-01 19:56:21 +05:30
paritosh18
6f0cdb4e0a Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 17:58:14 +05:30
ce9c8174d8 Merge branch 'paritosh' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-01 17:57:45 +05:30
c72e757bf3 Refactor Minglar admin functions: removed deprecated acceptHostApplicationMinglar handler, added editAgreementDetailsAndAccept handler, and updated related service methods for improved agreement processing. Cleaned up schema by removing isSubsidairy field and adjusted file handling logic in submitCompanyDetails. Enhanced suggestion handling by including isParent flag in addSuggestion. Updated host retrieval logic in getStepper. 2025-12-01 17:57:08 +05:30
paritosh18
6aaf49bf72 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 17:07:59 +05:30
paritosh18
1b31ca4a83 Implement pagination in getAllOnboardingHostApplications handler and update MinglarService to support pagination options. Enhance response structure to include total count of applications. 2025-12-01 17:05:08 +05:30
140f70615c Added adminStatusInternal field to the MinglarService for enhanced data handling. 2025-12-01 15:48:42 +05:30
32f4c7ce42 added filter in get host am api 2025-12-01 15:43:06 +05:30
paritosh18
067d1a1f1b Merge branch 'mayank' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:45:51 +05:30
paritosh18
470298a3fb Add search functionality to getAllOnboardingHostApplications and getAllOnboardingNewApplications handlers; update MinglarService to support search queries 2025-12-01 13:45:39 +05:30
1d7d0749b3 Refactor authentication: replaced instances of verifyHostToken with verifyMinglarAdminHostToken in prepopulate handlers for improved security and consistency. 2025-12-01 13:42:10 +05:30
paritosh18
d3fb1c85fb Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:32:29 +05:30
e723e680ab sending the internal admin status to frontend 2025-12-01 13:32:09 +05:30
paritosh18
0e50b8b187 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:29:46 +05:30
paritosh18
bbe725dd9e Update HTTP methods to PATCH for rejectHostApplication endpoints and implement pagination in getAllInvitationDetails and getAllInvitedCoadminAndAM handlers 2025-12-01 13:29:10 +05:30
e5861654e9 Enhance company types management: updated schema to include display order and relationships, modified validation to use company type XID, and seeded initial company types data. Updated services to reflect new structure and ensure proper data handling. 2025-12-01 13:26:06 +05:30
100 changed files with 4709 additions and 1521 deletions

44
.env.example Normal file
View 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=

4
.gitignore vendored
View File

@@ -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

View 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
View 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
View 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"
}
}
}
}

View 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"
}
}

10
package-lock.json generated
View File

@@ -46,7 +46,7 @@
"prisma": "^7.0.1", "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",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",
@@ -14467,12 +14467,12 @@
} }
}, },
"node_modules/serverless": { "node_modules/serverless": {
"version": "4.17.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/serverless/-/serverless-4.17.0.tgz", "resolved": "https://registry.npmjs.org/serverless/-/serverless-4.24.0.tgz",
"integrity": "sha512-hoZmipwyN/h7y9HwkWGlJ0YT06RFq7WNOD7fFEiPfnSnnUMVTzeNHq2BRrUlpHhf5s9srCHDc2wx5I06acfq1Q==", "integrity": "sha512-bgxFQ6QyOGJC9IZjZIXo4m6bdWMl9I7HNZ4jrmwSpdePdsRd46igGRpSnhdYFOc71GNplhSOeoCibL94yCHfrg==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"axios": "^1.8.3", "axios": "^1.12.1",
"axios-proxy-builder": "^0.1.2", "axios-proxy-builder": "^0.1.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"xml2js": "0.6.2" "xml2js": "0.6.2"

View File

@@ -63,7 +63,7 @@
"prisma": "^7.0.1", "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",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",

View File

@@ -1,8 +1,8 @@
// prisma.ts // prisma.ts
import { PrismaClient } from '@prisma/client'; // Re-export from the main singleton for consistency
import { prisma } from '../src/common/database/prisma.client';
// The DATABASE_URL environment variable will be automatically used export { prisma };
export const prisma = new PrismaClient();
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
await prisma.$disconnect(); await prisma.$disconnect();

View File

@@ -66,6 +66,7 @@ model User {
friendOf Friends[] @relation("FriendUser") friendOf Friends[] @relation("FriendUser")
userAddressDetails UserAddressDetails[] userAddressDetails UserAddressDetails[]
userDocuments UserDocuments[] userDocuments UserDocuments[]
activityTracks ActivityTrack[]
@@map("users") @@map("users")
@@schema("usr") @@schema("usr")
@@ -405,12 +406,15 @@ model FoodCuisines {
} }
model CompanyTypes { model CompanyTypes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
companyTypeName String @unique @map("company_type_name") @db.VarChar(30) companyTypeName String @unique @map("company_type_name") @db.VarChar(100)
isActive Boolean @default(true) @map("is_active") displayOrder Int @map("display_order")
createdAt DateTime @default(now()) @map("created_at") isActive Boolean @default(true) @map("is_active")
updatedAt DateTime @updatedAt @map("updated_at") createdAt DateTime @default(now()) @map("created_at")
deletedAt DateTime? @map("deleted_at") updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
hostHeaders HostHeader[]
hostParents HostParent[]
@@map("company_types") @@map("company_types")
@@schema("mst") @@schema("mst")
@@ -664,12 +668,13 @@ model HostHeader {
panNumber String? @map("pan_number") @db.VarChar(30) panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30) gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime? @map("formation_date") formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30) companyTypeXid Int? @map("company_type_xid")
websiteUrl String? @map("website_url") @db.VarChar(50) companyTypes CompanyTypes? @relation(fields: [companyTypeXid], references: [id], onDelete: Restrict)
instagramUrl String? @map("instagram_url") @db.VarChar(80) websiteUrl String? @map("website_url") @db.VarChar(250)
facebookUrl String? @map("facebook_url") @db.VarChar(80) instagramUrl String? @map("instagram_url") @db.VarChar(250)
linkedinUrl String? @map("linkedin_url") @db.VarChar(80) facebookUrl String? @map("facebook_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(80) linkedinUrl String? @map("linkedin_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(250)
currencyXid Int? @map("currency_xid") currencyXid Int? @map("currency_xid")
currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict) currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict)
stepper Int? @default(1) @map("stepper") stepper Int? @default(1) @map("stepper")
@@ -691,6 +696,7 @@ model HostHeader {
amountPerBooking Int? @map("amount_per_booking") amountPerBooking Int? @map("amount_per_booking")
payoutDurationNum Int? @map("payout_duration_num") payoutDurationNum Int? @map("payout_duration_num")
payoutDurationFrequency String? @map("payout_duration_frequency") @db.VarChar(20) payoutDurationFrequency String? @map("payout_duration_frequency") @db.VarChar(20)
referencedBy String? @default("null") @map("referenced_by") @db.VarChar(100)
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at") updatedAt DateTime @updatedAt @map("updated_at")
@@ -780,17 +786,17 @@ model HostParent {
countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict) countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict)
pinCode String? @map("pin_code") @db.VarChar(30) pinCode String? @map("pin_code") @db.VarChar(30)
logoPath String? @map("logo_path") @db.VarChar(400) logoPath String? @map("logo_path") @db.VarChar(400)
isSubsidairy Boolean @default(false) @map("is_subsidairy")
registrationNumber String? @map("registration_number") @db.VarChar(30) registrationNumber String? @map("registration_number") @db.VarChar(30)
panNumber String? @map("pan_number") @db.VarChar(30) panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30) gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime? @map("formation_date") formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30) companyTypeXid Int? @map("company_type_xid")
websiteUrl String? @map("website_url") @db.VarChar(80) companyTypes CompanyTypes? @relation(fields: [companyTypeXid], references: [id], onDelete: Restrict)
instagramUrl String? @map("instagram_url") @db.VarChar(80) websiteUrl String? @map("website_url") @db.VarChar(250)
facebookUrl String? @map("facebook_url") @db.VarChar(80) instagramUrl String? @map("instagram_url") @db.VarChar(250)
linkedinUrl String? @map("linkedin_url") @db.VarChar(80) facebookUrl String? @map("facebook_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(80) linkedinUrl String? @map("linkedin_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(250)
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at") updatedAt DateTime @updatedAt @map("updated_at")
@@ -904,6 +910,7 @@ model Activities {
ActivityEquipmentTaxes ActivityEquipmentTaxes[] ActivityEquipmentTaxes ActivityEquipmentTaxes[]
ScheduleHeader ScheduleHeader[] ScheduleHeader ScheduleHeader[]
ItineraryActivities ItineraryActivities[] ItineraryActivities ItineraryActivities[]
activityTracks ActivityTrack[]
@@map("activities") @@map("activities")
@@schema("act") @@schema("act")
@@ -928,6 +935,25 @@ model ActivityOtherDetails {
@@schema("act") @@schema("act")
} }
model ActivityTrack {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
trackType String? @default("PQQ") @map("track_type")
updatedByRole String? @map("updated_by_role")
trackStatus String? @map("track_status")
updatedByXid Int? @map("updated_by_xid")
user User? @relation(fields: [updatedByXid], references: [id], onDelete: Cascade)
updatedOn DateTime? @map("updated_on")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("activity_track")
@@schema("act")
}
model ActivitiesMedia { model ActivitiesMedia {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
@@ -1115,8 +1141,8 @@ model ActivityPQQheader {
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
pqqQuestionXid Int @map("pqq_question_xid") pqqQuestionXid Int @map("pqq_question_xid")
pqqQuestions PQQQuestions @relation(fields: [pqqQuestionXid], references: [id], onDelete: Restrict) pqqQuestions PQQQuestions @relation(fields: [pqqQuestionXid], references: [id], onDelete: Restrict)
pqqAnswerXid Int @map("pqq_answer_xid") pqqAnswerXid Int? @map("pqq_answer_xid")
pqqAnswers PQQAnswers @relation(fields: [pqqAnswerXid], references: [id], onDelete: Restrict) pqqAnswers PQQAnswers? @relation(fields: [pqqAnswerXid], references: [id], onDelete: Restrict)
comments String? @map("comments") @db.VarChar(200) comments String? @map("comments") @db.VarChar(200)
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")

View File

@@ -1,6 +1,10 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import 'dotenv/config';
const prisma = new PrismaClient(); const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});
async function main() { async function main() {
// ✅ Countries // ✅ Countries
@@ -29,11 +33,46 @@ async function main() {
update: {}, update: {},
create: { countryXid: india.id, stateName: 'Maharashtra' }, create: { countryXid: india.id, stateName: 'Maharashtra' },
}); });
const uttarpradesh = await prisma.states.upsert({
where: { stateName: 'Uttar-Pradesh' },
update: {},
create: { countryXid: india.id, stateName: 'Uttar-Pradesh' },
});
const Rajasthan = await prisma.states.upsert({
where: { stateName: 'Rajasthan' },
update: {},
create: { countryXid: india.id, stateName: 'Rajasthan' },
});
const Uttarakhand = await prisma.states.upsert({
where: { stateName: 'Uttarakhand' },
update: {},
create: { countryXid: india.id, stateName: 'Uttarakhand' },
});
const HimachalPradesh = await prisma.states.upsert({
where: { stateName: 'Himachal Pradesh' },
update: {},
create: { countryXid: india.id, stateName: 'Himachal Pradesh' },
});
const Gujrat = await prisma.states.upsert({
where: { stateName: 'Gujrat' },
update: {},
create: { countryXid: india.id, stateName: 'Gujrat' },
});
// ✅ Cities // ✅ Cities
await prisma.cities.createMany({ await prisma.cities.createMany({
data: [ data: [
{ stateXid: maharashtra.id, cityName: 'Mumbai' }, { stateXid: maharashtra.id, cityName: 'Mumbai' },
{ stateXid: uttarpradesh.id, cityName: 'Azamgarh' },
{ stateXid: uttarpradesh.id, cityName: 'Lucknow' },
{ stateXid: uttarpradesh.id, cityName: 'Prayagraj' },
{ stateXid: Rajasthan.id, cityName: 'Jaipur' },
{ stateXid: Rajasthan.id, cityName: 'Jaisalmer' },
{ stateXid: Uttarakhand.id, cityName: 'Haridwar' },
{ stateXid: HimachalPradesh.id, cityName: 'Manali' },
{ stateXid: Gujrat.id, cityName: 'Surat' },
{ stateXid: Gujrat.id, cityName: 'Ahemdabad' },
{ stateXid: Gujrat.id, cityName: 'Rajkot' },
], ],
skipDuplicates: true, skipDuplicates: true,
}); });
@@ -52,6 +91,21 @@ async function main() {
update: {}, update: {},
create: { countryXid: india.id, bankName: 'HDFC Bank' }, create: { countryXid: india.id, bankName: 'HDFC Bank' },
}); });
const indianBank = await prisma.banks.upsert({
where: { bankName: 'Indian Bank' },
update: {},
create: { countryXid: india.id, bankName: 'Indian Bank' },
});
const Kotak = await prisma.banks.upsert({
where: { bankName: 'Kotak Bank' },
update: {},
create: { countryXid: india.id, bankName: 'Kotak Bank' },
});
const BOI = await prisma.banks.upsert({
where: { bankName: 'Bank of India' },
update: {},
create: { countryXid: india.id, bankName: 'Bank of India' },
});
// ✅ Bank Branches // ✅ Bank Branches
await prisma.bankBranches.createMany({ await prisma.bankBranches.createMany({
@@ -63,6 +117,27 @@ async function main() {
branchAddress: 'HDFC Fort Branch, Mumbai', branchAddress: 'HDFC Fort Branch, Mumbai',
ifscCode: 'HDFC0001234', ifscCode: 'HDFC0001234',
}, },
{
bankXid: indianBank.id,
stateXid: maharashtra.id,
cityXid: (await prisma.cities.findFirst({ where: { cityName: 'Mumbai' } }))!.id,
branchAddress: 'Indian Bank Fort Branch, Mumbai',
ifscCode: 'IDIB0001234',
},
{
bankXid: Kotak.id,
stateXid: Uttarakhand.id,
cityXid: (await prisma.cities.findFirst({ where: { cityName: 'Haridwar' } }))!.id,
branchAddress: 'Kotak Fort Branch, Mumbai',
ifscCode: 'KTB0001234',
},
{
bankXid: BOI.id,
stateXid: uttarpradesh.id,
cityXid: (await prisma.cities.findFirst({ where: { cityName: 'Azamgarh' } }))!.id,
branchAddress: 'Bank of India Fort Branch, Mumbai',
ifscCode: 'BOI0001234',
},
], ],
skipDuplicates: true, skipDuplicates: true,
}); });
@@ -74,35 +149,51 @@ async function main() {
create: { interestName: 'Chill and Zen', displayOrder: 1 }, create: { interestName: 'Chill and Zen', displayOrder: 1 },
}); });
const sweatmode = await prisma.interests.upsert({ const sweatmode = await prisma.interests.upsert({
where: { interestName: 'Sweat Mode' }, where: { interestName: 'Sweat Mode On' },
update: {}, update: {},
create: { interestName: 'Sweat Mode', displayOrder: 2 }, create: { interestName: 'Sweat Mode On', displayOrder: 2 },
}); });
const gameon = await prisma.interests.upsert({ const trackracer = await prisma.interests.upsert({
where: { interestName: 'Game On' }, where: { interestName: 'Track Racer' },
update: {}, update: {},
create: { interestName: 'Game On', displayOrder: 3 }, create: { interestName: 'Track Racer', displayOrder: 3 },
});
const circuitracer = await prisma.interests.upsert({
where: { interestName: 'Circuit Racer' },
update: {},
create: { interestName: 'Circuit Racer', displayOrder: 4 },
});
const thermalGliding = await prisma.interests.upsert({
where: { interestName: 'Thermal Gliding' },
update: {},
create: { interestName: 'Thermal Gliding', displayOrder: 5 },
}); });
const partycentral = await prisma.interests.upsert({ const partycentral = await prisma.interests.upsert({
where: { interestName: 'Party Central' }, where: { interestName: 'Party Central' },
update: {}, update: {},
create: { interestName: 'Party Central', displayOrder: 4 }, create: { interestName: 'Party Central', displayOrder: 6 },
}); });
const artsy = await prisma.interests.upsert({ const aqua = await prisma.interests.upsert({
where: { interestName: 'Artsy' }, where: { interestName: 'Aqua' },
update: {}, update: {},
create: { interestName: 'Artsy', displayOrder: 5 }, create: { interestName: 'Aqua', displayOrder: 7 },
}); });
const foodiediaries = await prisma.interests.upsert({ const foodie = await prisma.interests.upsert({
where: { interestName: 'Foodie Diaries' }, where: { interestName: 'Foodie' },
update: {}, update: {},
create: { interestName: 'Foodie Diaries', displayOrder: 6 }, create: { interestName: 'Foodie', displayOrder: 8 },
}); });
await prisma.activityTypes.createMany({ await prisma.activityTypes.createMany({
data: [ data: [
{ interestXid: chillandzen.id, activityTypeName: 'Cricket' }, { interestXid: aqua.id, activityTypeName: 'Scuba-Diving' },
{ interestXid: chillandzen.id, activityTypeName: 'Football' }, { interestXid: sweatmode.id, activityTypeName: 'Cloudboarding' },
{ interestXid: partycentral.id, activityTypeName: 'Soaring Glider' },
{ interestXid: sweatmode.id, activityTypeName: 'Speedway Racer' },
{ interestXid: aqua.id, activityTypeName: 'Aerial Surfing' },
{ interestXid: foodie.id, activityTypeName: 'Wine Tasting' },
{ interestXid: trackracer.id, activityTypeName: 'Track Racer' },
{ interestXid: thermalGliding.id, activityTypeName: 'Thermal Gliding' },
], ],
skipDuplicates: true, skipDuplicates: true,
}); });
@@ -133,6 +224,49 @@ async function main() {
skipDuplicates: true, // prevents error if already seeded skipDuplicates: true, // prevents error if already seeded
}); });
// ✅ Company types data
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Proprietory' },
update: {},
create: { companyTypeName: 'Proprietory', displayOrder: 1 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'One Person Company' },
update: {},
create: { companyTypeName: 'One Person Company', displayOrder: 2 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Limited Liability Partnership' },
update: {},
create: { companyTypeName: 'Limited Liability Partnership', displayOrder: 3 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Partnership Firm' },
update: {},
create: { companyTypeName: 'Partnership Firm', displayOrder: 4 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Private Limited' },
update: {},
create: { companyTypeName: 'Private Limited', displayOrder: 5 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Non-Profit Organisation' },
update: {},
create: { companyTypeName: 'Non-Profit Organisation', displayOrder: 6 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Public Limited' },
update: {},
create: { companyTypeName: 'Public Limited', displayOrder: 7 },
});
// ✅ Food Types // ✅ Food Types
await prisma.foodTypes.createMany({ await prisma.foodTypes.createMany({
data: [ data: [

View File

@@ -1,16 +1,32 @@
service: minglarDev service: minglar
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider: provider:
name: aws name: aws
runtime: nodejs22.x runtime: nodejs22.x
region: ap-south-1 region: ap-south-1
stage: ${opt:stage, 'dev'}
versionFunctions: false versionFunctions: false
memorySize: 512 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: apiGateway:
binaryMediaTypes: binaryMediaTypes:
- '*/*' - '*/*'
minimumCompressionSize: 1024 minimumCompressionSize: 1024
environment: environment:
DATABASE_URL: ${env:DATABASE_URL} DATABASE_URL: ${env:DATABASE_URL}
DB_USERNAME: ${env:DB_USERNAME} DB_USERNAME: ${env:DB_USERNAME}
@@ -38,7 +54,9 @@ provider:
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME} S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME} MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME}
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK}
iam: iam:
role: role:
statements: statements:
@@ -51,27 +69,63 @@ provider:
Resource: Resource:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}' - 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- '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: true minify: true
sourcemap: false sourcemap: false
target: node20 target: node20
platform: node platform: node
concurrency: 5 # Mark as external so they're not bundled into the JS
external: external:
- '@prisma/client' - '@prisma/client'
- '.prisma/client'
- '.prisma' - '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
# Exclude prevents npm install of these packages in the zip
exclude: exclude:
- 'aws-sdk' - 'aws-sdk'
serverless-offline: - '@aws-sdk/*'
reloadHandler: true - '@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/**' - '!node_modules/**'
- '!node_modules/@prisma/**'
- '!node_modules/.prisma/**'
- '!**/*.test.js' - '!**/*.test.js'
- '!**/*.spec.js' - '!**/*.spec.js'
- '!**/test/**' - '!**/test/**'
@@ -82,12 +136,13 @@ package:
- '!*.config.js' - '!*.config.js'
- '!.git/**' - '!.git/**'
- '!.github/**' - '!.github/**'
# Import function definitions from separate files organized by module # Import function definitions from separate files organized by module
functions: functions:
- ${file(./serverless/functions/host.yml)} - ${file(./serverless/functions/host.yml)}
- ${file(./serverless/functions/minglaradmin.yml)} - ${file(./serverless/functions/minglaradmin.yml)}
- ${file(./serverless/functions/prepopulate.yml)} - ${file(./serverless/functions/prepopulate.yml)}
- ${file(./serverless/functions/pqq.yml)}
plugins: plugins:
- serverless-offline - serverless-offline

View File

@@ -161,7 +161,7 @@ getPQQ_LastUpdatedQuestion:
path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
method: get method: get
getAllActivityType: prePopulateNewActivity:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllActivityType.handler handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllActivityType.handler
memorySize: 384 memorySize: 384
package: package:
@@ -174,7 +174,7 @@ getAllActivityType:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /host/Activity_Hub/OnBoarding/get-activity-type path: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
method: get method: get
showSuggestion: showSuggestion:
@@ -253,28 +253,6 @@ submitCompanyDetails:
- 'src/modules/host/handlers/addCompanyDetails.*' - 'src/modules/host/handlers/addCompanyDetails.*'
- 'src/modules/host/services/**' - 'src/modules/host/services/**'
- 'src/common/**' - '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: events:
- httpApi: - httpApi:
path: /host/Host_Admin/onboarding/add-company-details path: /host/Host_Admin/onboarding/add-company-details
@@ -291,24 +269,6 @@ submitPQQ_Answer:
- ${file(./serverless/patterns/base.yml):pattern2} - ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3} - ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
- '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/**'
events: events:
- httpApi: - httpApi:
path: /host/Activity_Hub/OnBoarding/submit-pqq-answer path: /host/Activity_Hub/OnBoarding/submit-pqq-answer
@@ -330,6 +290,23 @@ updatePQQ_LastAnswer:
path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
method: post 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: getAllPQQwithSubmittedAns:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler
memorySize: 512 memorySize: 512

View File

@@ -1,8 +1,6 @@
# Minglar Admin Module Functions # Minglar Admin Module Functions
# Admin dashboard and management endpoints # Admin dashboard and management endpoints
minglarRegistration: minglarRegistration:
handler: src/modules/minglaradmin/handlers/registration.handler handler: src/modules/minglaradmin/handlers/registration.handler
memorySize: 384 memorySize: 384
@@ -60,22 +58,6 @@ updateMinglarProfile:
- ${file(./serverless/patterns/base.yml):pattern2} - ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3} - ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
- ${file(./serverless/patterns/aws-s3.yml):pattern1}
- ${file(./serverless/patterns/aws-s3.yml):pattern2}
- ${file(./serverless/patterns/aws-s3.yml):pattern3}
- ${file(./serverless/patterns/aws-s3.yml):pattern4}
- ${file(./serverless/patterns/aws-s3.yml):pattern5}
- ${file(./serverless/patterns/aws-s3.yml):pattern6}
- ${file(./serverless/patterns/aws-s3.yml):pattern7}
- ${file(./serverless/patterns/aws-s3.yml):pattern8}
- ${file(./serverless/patterns/aws-s3.yml):pattern9}
- ${file(./serverless/patterns/aws-s3.yml):pattern10}
- ${file(./serverless/patterns/aws-s3.yml):pattern11}
- ${file(./serverless/patterns/aws-s3.yml):pattern12}
- ${file(./serverless/patterns/aws-s3.yml):pattern13}
- ${file(./serverless/patterns/aws-s3.yml):pattern14}
- ${file(./serverless/patterns/aws-s3.yml):pattern15}
- ${file(./serverless/patterns/aws-s3.yml):pattern16}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/update-profile path: /minglaradmin/update-profile
@@ -96,7 +78,6 @@ prepopulateRole:
path: /minglaradmin/prepopulate-Roles path: /minglaradmin/prepopulate-Roles
method: get method: get
getHostDetailsById: getHostDetailsById:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/getByIdHostDetails.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/getByIdHostDetails.handler
memorySize: 384 memorySize: 384
@@ -283,8 +264,8 @@ assignAMToHost:
path: /minglaradmin/hosthub/onboarding/assign-am path: /minglaradmin/hosthub/onboarding/assign-am
method: patch method: patch
editAgreementDetails: editAgreementDetailsAndAccept:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/editAgreementDetails.handler handler: src/modules/minglaradmin/handlers/hosthub/onboarding/editAgreementDetailsAndAccept.handler
memorySize: 384 memorySize: 384
package: package:
patterns: patterns:
@@ -296,9 +277,24 @@ editAgreementDetails:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/edit-agreement path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
method: patch 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: acceptHostApplication:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptHostApplication.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptHostApplication.handler
memorySize: 384 memorySize: 384
@@ -328,15 +324,15 @@ RejectPQQByAM:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/reject-pqq-by-am path: /minglaradmin/hosthub/hosts/reject-pq-by-am
method: patch method: patch
acceptHostApplicationMinglar: acceptPQByAM:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/acceptHostAppMinglar.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM.handler
memorySize: 384 memorySize: 384
package: package:
patterns: patterns:
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**' - 'src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM**'
- 'src/modules/minglaradmin/services/**' - 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1} - ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2} - ${file(./serverless/patterns/base.yml):pattern2}
@@ -344,7 +340,7 @@ acceptHostApplicationMinglar:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/accept-host-application-minglar path: /minglaradmin/hosthub/hosts/accept-pq-by-am
method: patch method: patch
rejectHostApplication: rejectHostApplication:
@@ -361,7 +357,7 @@ rejectHostApplication:
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/reject-host-application path: /minglaradmin/hosthub/onboarding/reject-host-application
method: post method: patch
rejectHostApplicationAM: rejectHostApplicationAM:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectHostApplicationAM.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectHostApplicationAM.handler
@@ -377,7 +373,7 @@ rejectHostApplicationAM:
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/reject-host-application-am path: /minglaradmin/hosthub/hosts/reject-host-application-am
method: post method: patch
addPQQSuggestion: addPQQSuggestion:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler
@@ -394,3 +390,36 @@ addPQQSuggestion:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
method: post 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/pqp/getAllPQPDetailsForAM.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

View 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

View File

@@ -1,6 +1,9 @@
# Base packaging patterns shared across all functions # Base packaging patterns shared across all functions
pattern1: 'src/common/**' pattern1: 'src/common/**'
pattern2: 'common/**' pattern2: 'common/**'
pattern3: 'node_modules/@prisma/client/**' # REMOVED: Prisma is now provided by Lambda layer - DO NOT include in function packages
pattern4: 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node' # 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*' pattern5: '!node_modules/.prisma/client/libquery_engine*'

View File

@@ -1,11 +1,29 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); // Singleton pattern for Prisma client - prevents "Too many database connections" error
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = new PrismaClient({ function createPrismaClient() {
adapter, const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
log: process.env.NODE_ENV === 'dev' ? ['query', 'info', 'warn', 'error'] : ['error'],
}); 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;
}

View 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;

View File

@@ -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({

View File

@@ -1,15 +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 { PrismaPg } from '@prisma/adapter-pg'; 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() {
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); super();
super({ // Use the singleton instance
adapter, Object.assign(this, prisma);
log: process.env.NODE_ENV === 'dev' ? ['query', 'info', 'warn', 'error'] : ['error'],
});
} }
async onModuleInit() { async onModuleInit() {
@@ -19,10 +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();
});
}
}

View File

@@ -49,6 +49,17 @@ export async function verifyHostToken(token: string): Promise<{ id: number; role
include: { role: true }, 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) { if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found'); throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
} }
@@ -89,7 +100,7 @@ const verifyCallback = async (
try { try {
const userInfo = await verifyHostToken(token); const userInfo = await verifyHostToken(token);
// Attach user to request // Attach user to request
req.user = { id: userInfo.id.toString(), role: userInfo.role }; req.user = { id: userInfo.id.toString(), role: userInfo.role };
@@ -104,12 +115,12 @@ const verifyCallback = async (
*/ */
const authForHost = 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 authForHost; export default authForHost;

View File

@@ -49,6 +49,17 @@ export async function verifyMinglarAdminToken(token: string): Promise<{ id: numb
include: { role: true }, 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) { if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found'); throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
} }
@@ -62,7 +73,7 @@ export async function verifyMinglarAdminToken(token: string): Promise<{ id: numb
if (![ROLE.MINGLAR_ADMIN, ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER].includes(user.roleXid)) { if (![ROLE.MINGLAR_ADMIN, ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER].includes(user.roleXid)) {
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.'); throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
} }
return { id: user.id, role: user.role?.roleName }; return { id: user.id, role: user.role?.roleName };
} catch (error) { } catch (error) {
@@ -90,7 +101,7 @@ const verifyCallback = async (
try { try {
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
// Attach user to request // Attach user to request
req.user = { id: userInfo.id.toString(), role: userInfo.role }; req.user = { id: userInfo.id.toString(), role: userInfo.role };
@@ -105,12 +116,12 @@ const verifyCallback = async (
*/ */
const authForHost = 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 authForHost; export default authForHost;

View File

@@ -51,6 +51,17 @@ export async function verifyMinglarAdminHostToken(token: string): Promise<{ id:
include: { role: true }, 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) { if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found'); throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
} }

View File

@@ -49,6 +49,17 @@ export async function verifyOnlyMinglarAdminToken(token: string): Promise<{ id:
include: { role: true }, 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) { if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found'); throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
} }
@@ -62,7 +73,7 @@ export async function verifyOnlyMinglarAdminToken(token: string): Promise<{ id:
if (user.roleXid !== ROLE.MINGLAR_ADMIN) { if (user.roleXid !== ROLE.MINGLAR_ADMIN) {
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.'); throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
} }
return { id: user.id, role: user.role?.roleName }; return { id: user.id, role: user.role?.roleName };
} catch (error) { } catch (error) {
@@ -90,7 +101,7 @@ const verifyCallback = async (
try { try {
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Attach user to request // Attach user to request
req.user = { id: userInfo.id.toString(), role: userInfo.role }; req.user = { id: userInfo.id.toString(), role: userInfo.role };
@@ -105,12 +116,12 @@ const verifyCallback = async (
*/ */
const authForHost = 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 authForHost; export default authForHost;

View File

@@ -50,6 +50,17 @@ export async function verifyUserToken(token: string): Promise<{ id: number; role
include: { role: true }, 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) { if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found'); throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
} }
@@ -90,7 +101,7 @@ const verifyCallback = async (
try { try {
const userInfo = await verifyUserToken(token); const userInfo = await verifyUserToken(token);
// Attach user to request // Attach user to request
req.user = { id: userInfo.id.toString(), role: userInfo.role }; req.user = { id: userInfo.id.toString(), role: userInfo.role };
@@ -105,12 +116,12 @@ const verifyCallback = async (
*/ */
const authForHost = 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 authForHost; export default authForHost;

View File

@@ -23,19 +23,16 @@ export const STEPPER = {
REJECTED: 6 REJECTED: 6
} }
export const LAST_QUESTION_ID = {
Q_ID: 55
}
export const ACTIVITY_INTERNAL_STATUS = { export const ACTIVITY_INTERNAL_STATUS = {
DRAFT_PQ: 'Draft - PQ', DRAFT_PQ: 'Draft - PQ',
APPROVED: 'Approved', APPROVED: 'Approved',
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
PQQ_TO_UPDATE: 'PQ To Update', PQ_TO_UPDATE: 'PQ To Update',
PQQ_SUBMITTED: 'PQ Submitted' PQ_SUBMITTED: 'PQ Submitted',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_DISPLAY_STATUS = { export const ACTIVITY_DISPLAY_STATUS = {
@@ -44,9 +41,10 @@ export const ACTIVITY_DISPLAY_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enchancing', ENHANCING: 'Enchancing',
PQ_IN_REVIEW: 'PQ In Review' PQ_IN_REVIEW: 'PQ In Review',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_AM_INTERNAL_STATUS = { export const ACTIVITY_AM_INTERNAL_STATUS = {
@@ -55,9 +53,10 @@ export const ACTIVITY_AM_INTERNAL_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
PQQ_REJECTED: 'PQ Rejected', PQ_REJECTED: 'PQ Rejected',
PQQ_TO_REVIEW: 'PQ To Review' PQ_TO_REVIEW: 'PQ To Review',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_AM_DISPLAY_STATUS = { export const ACTIVITY_AM_DISPLAY_STATUS = {
@@ -66,7 +65,9 @@ export const ACTIVITY_AM_DISPLAY_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enchancing', ENHANCING: 'Enchancing',
NEW: 'New' NEW: 'New',
PQ_APPROVED: 'PQ Approved',
REVISED: 'Revised'
} }

View File

@@ -25,23 +25,35 @@ export const MINGLAR_INVITATION_STATUS = {
INVITED: 'Invited', INVITED: 'Invited',
}; };
export const HOST_SUGGESTION_TITLES = { export const ACTIVITY_TRACK_TYPE = {
COMPANY_DETAILS: 'Complete Details', PQ: 'PQ',
COMPANY_DOCUMENTATION: 'Company documentataion', ACTIVITY: 'Activity'
COMPANY_SOCIAL_PROOF: 'Social Proof', }
ACTIVITY_INFORMATION: 'Activity Information',
ACTIVITY_LOCATION: 'Activity Location', export const ACTIVITY_TRACK_STATUS = {
PICKUP_DROP_LOCATION: 'Pickup-Drop Location', REJECTED_BY_AM: 'Rejected By AM',
NUMBER_OF_PEOPLE: 'Number of People', ACCEPTED_BY_AM: 'Accepted By AM',
INCLUSION: 'Inclusion', ENHANCING: 'Enhancing',
TAX_SETUP: 'Tax Setup', PQ_SUBMITTED: 'PQ Submitted'
ENERGY_LEVEL: 'Energy Level', }
ELIGIBILITY_CRITERIA: 'Eligibility Criteria',
AMENITIES: 'Amenities', // export const HOST_SUGGESTION_TITLES = {
EXLUSIVE_NOTES: 'Exclusive Notes', // COMPANY_DETAILS: 'Complete Details',
CANCELLATION_POLICY: 'Cancellation Policy', // COMPANY_DOCUMENTATION: 'Company documentataion',
DOs_AND_DONTs: 'Dos and Donts', // COMPANY_SOCIAL_PROOF: 'Social Proof',
TIPS_FOR_USERS: 'Tips for Users', // ACTIVITY_INFORMATION: 'Activity Information',
SUSTAINABILITY: 'Sustainability', // ACTIVITY_LOCATION: 'Activity Location',
TERMS_AND_CONDITION_FOR_USER: 'Terms and Conditions for User' // 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: 'Dos and Donts',
// TIPS_FOR_USERS: 'Tips for Users',
// SUSTAINABILITY: 'Sustainability',
// TERMS_AND_CONDITION_FOR_USER: 'Terms and Conditions for User'
// };

View File

@@ -12,7 +12,6 @@ export interface OtpResult {
export async function resendOtpHelper( export async function resendOtpHelper(
prisma: any, prisma: any,
userId: number, userId: number,
email: string,
emailPurpose: "Register" | "Login" | "ForgotPassword", emailPurpose: "Register" | "Login" | "ForgotPassword",
otpLength: 4 | 6 = 4, otpLength: 4 | 6 = 4,
expiryMinutes: number = 5 expiryMinutes: number = 5

View File

@@ -11,11 +11,6 @@ export const hostBankDetailsSchema = z.object({
.nonempty("Account holder name is required") .nonempty("Account holder name is required")
.min(2, { message: "Account holder name must be at least 2 characters" }), .min(2, { message: "Account holder name must be at least 2 characters" }),
ifscCode: z
.string()
.nonempty("IFSC code is required")
.regex(/^[A-Z]{4}0[A-Z0-9]{6}$/, { message: "Invalid IFSC code format" }),
bankXid: z bankXid: z
.number() .number()
.int({ message: "Bank ID must be an integer" }) .int({ message: "Bank ID must be an integer" })

View File

@@ -2,31 +2,29 @@ import { z } from "zod";
export const parentCompanySchema = z.object({ export const parentCompanySchema = z.object({
companyName: z.string() companyName: z.string()
.min(1, "Parent company name is required") .max(100, "Parent company name cannot exceed 100 characters")
.max(100, "Parent company name cannot exceed 100 characters"), .optional(),
address1: z.string() address1: z.string()
.min(1, "Address1 is required") .max(150, "Address1 cannot exceed 150 characters")
.max(150, "Address1 cannot exceed 150 characters"), .optional(),
address2: z.string() address2: z.string()
.max(150, "Address2 cannot exceed 150 characters") .max(150, "Address2 cannot exceed 150 characters")
.optional(), .optional(),
cityXid: z.number().min(1, "City is required"), cityXid: z.number().optional(),
stateXid: z.number().min(1, "State is required"), stateXid: z.number().optional(),
countryXid: z.number().min(1, "Country is required"), countryXid: z.number().optional(),
pinCode: z.string() pinCode: z.string()
.min(4, "Pincode/Zipcode is required") .max(30, "Pincode cannot exceed 30 characters")
.max(30, "Pincode cannot exceed 30 characters"), .optional(),
logoPath: z.string() logoPath: z.string()
.max(400, "Logo path cannot exceed 400 characters") .max(400, "Logo path cannot exceed 400 characters")
.optional(), .optional(),
isSubsidairy: z.boolean().optional(),
registrationNumber: z.string() registrationNumber: z.string()
.max(30, "Registration number cannot exceed 30 characters") .max(30, "Registration number cannot exceed 30 characters")
.optional(), .optional(),
@@ -45,15 +43,15 @@ export const parentCompanySchema = z.object({
message: "Formation date must be a valid date", message: "Formation date must be a valid date",
}), }),
companyType: z.string() companyTypeXid: z.number()
.min(1, "Company type is required") .optional(),
.max(30, "Company type cannot exceed 30 characters"),
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(),
websiteUrl: z.string().url().max(80, "Website URL cannot exceed 80 characters").optional(),
instagramUrl: z.string().url().max(80, "Instagram URL cannot exceed 80 characters").optional(),
facebookUrl: z.string().url().max(80, "Facebook URL cannot exceed 80 characters").optional(),
linkedinUrl: z.string().url().max(80, "LinkedIn URL cannot exceed 80 characters").optional(),
twitterUrl: z.string().url().max(80, "Twitter URL cannot exceed 80 characters").optional(),
}); });
@@ -106,15 +104,20 @@ export const hostCompanyDetailsSchema = z.object({
message: "Formation date must be a valid date", message: "Formation date must be a valid date",
}), }),
companyType: z.string() companyTypeXid: z.number()
.min(1, "Company type is required") .int("Company type must be a valid integer")
.max(30, "Company type cannot exceed 30 characters"), .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(),
websiteUrl: z.string().url().max(50, "Website URL cannot exceed 50 characters").optional(),
instagramUrl: z.string().url().max(80, "Instagram URL cannot exceed 80 characters").optional(),
facebookUrl: z.string().url().max(80, "Facebook URL cannot exceed 80 characters").optional(),
linkedinUrl: z.string().url().max(80, "LinkedIn URL cannot exceed 80 characters").optional(),
twitterUrl: z.string().url().max(80, "Twitter URL cannot exceed 80 characters").optional(),
parentCompany: parentCompanySchema.optional(), parentCompany: parentCompanySchema.optional(),
}); });

View File

@@ -81,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);
@@ -158,6 +161,8 @@ function getConfig() {
//Minglar admin //Minglar admin
MinglarAdminEmail: envVars.MINGLAR_ADMIN_EMAIL, MinglarAdminEmail: envVars.MINGLAR_ADMIN_EMAIL,
MinglarAdminName: envVars.MINGLAR_ADMIN_NAME, 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,

View File

@@ -90,5 +90,6 @@ export class AddPaymentDetailsDTO {
this.accountHolderName = accountHolderName; this.accountHolderName = accountHolderName;
this.ifscCode = ifscCode; this.ifscCode = ifscCode;
this.hostXid = hostXid; this.hostXid = hostXid;
this.currencyXid = currencyXid;
} }
} }

View File

@@ -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
}),
};
},
);

View File

@@ -1,14 +1,13 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service'; import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
/** /**
* Add suggestion handler for host applications * Add suggestion handler for host applications

View File

@@ -1,14 +1,13 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { string } from 'zod'; import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
/** /**
@@ -29,11 +28,18 @@ export const handler = safeHandler(async (
// Verify token and get user info // Verify token and get user info
const userInfo = await verifyHostToken(token); 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=) // Read optional search query (supports ?search= or ?q=)
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined; const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
const data = await hostService.getAllHostActivity(search ? String(search) : undefined, Number(userInfo.id)); const result = await hostService.getAllHostActivity(
search ? String(search) : undefined,
Number(userInfo.id),
paginationOptions
);
return { return {
@@ -45,8 +51,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Data retrieved successfully', message: 'Data retrieved successfully',
data, ...result,
}), }),
}; };
}); });

View File

@@ -1,14 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -23,7 +20,15 @@ export const handler = safeHandler(async (
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyMinglarAdminHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await hostService.getAllPQQQuesAndSubmittedAns(); 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 { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,55 +1,42 @@
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
import crypto from 'crypto'; import crypto from 'crypto';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { LAST_QUESTION_ID } from '@/common/utils/constants/host.constant';
const prisma = new PrismaService(); const hostService = new HostService(prismaClient);
const pqqService = new HostService(prisma);
const s3 = new AWS.S3({ region: config.aws.region }); const s3 = new AWS.S3({ region: config.aws.region });
// Function to extract S3 key from URL // Extract S3 key from URL
function getS3KeyFromUrl(url: string): string { function getS3KeyFromUrl(url: string): string {
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`; const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
return url.replace(bucketBaseUrl, ''); return url.replace(bucketBaseUrl, '');
} }
// Function to delete file from S3 // Delete file from S3
async function deleteFromS3(s3Key: string): Promise<void> { async function deleteFromS3(s3Key: string): Promise<void> {
try { try {
await s3.deleteObject({ await s3.deleteObject({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
}).promise(); }).promise();
console.log(`File deleted from S3: ${s3Key}`); console.log(`Deleted from S3: ${s3Key}`);
} catch (error) { } catch (err) {
console.error(`Error deleting file from S3: ${s3Key}`, error); console.error(`Failed to delete from S3: ${s3Key}`, err);
// Don't throw error here, continue with upload
} }
} }
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> { // Upload new file
let s3Key: string; async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string): Promise<string> {
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
const s3Key = `${prefix}/${uniqueKey}`;
// If existing URL provided, use the same S3 key to replace the file
if (existingUrl) {
s3Key = getS3KeyFromUrl(existingUrl);
// Delete existing file first
await deleteFromS3(s3Key);
} else {
// Generate new unique key for new file
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
s3Key = `${prefix}/${uniqueKey}`;
}
// Upload new file (replaces existing if same key)
await s3.upload({ await s3.upload({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
@@ -58,253 +45,160 @@ async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string
ACL: 'private' ACL: 'private'
}).promise(); }).promise();
console.log(`File uploaded to S3: ${s3Key}`); console.log(`Uploaded to S3: ${s3Key}`);
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`; return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
} }
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try { try {
// 1) Auth // AUTH
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.'); if (!token) throw new ApiError(401, 'Missing token');
const user = await verifyHostToken(token); const user = await verifyHostToken(token);
// 2) Content-Type check // Content-Type
const contentType = event.headers["content-type"] || event.headers["Content-Type"]; const contentType = event.headers["content-type"] || event.headers["Content-Type"];
if (!contentType?.startsWith("multipart/form-data")) if (!contentType?.startsWith("multipart/form-data"))
throw new ApiError(400, "Content-Type must be multipart/form-data"); throw new ApiError(400, "Content-Type must be multipart/form-data");
if (!event.isBase64Encoded) if (!event.isBase64Encoded) throw new ApiError(400, "Body must be base64 encoded");
throw new ApiError(400, "Body must be base64 encoded");
const bodyBuffer = Buffer.from(event.body!, "base64"); const bodyBuffer = Buffer.from(event.body!, "base64");
const fields: any = {}; const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 3) Parse multipart data // Parse multipart
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const bb = Busboy({ headers: { 'content-type': contentType } }); const bb = Busboy({ headers: { 'content-type': contentType } });
bb.on('file', (fieldname, file, info) => { bb.on('file', (fieldname, file, info) => {
const { filename, mimeType } = info; const { filename, mimeType } = info;
if (!filename) return file.resume();
// Skip if no filename (empty file field)
if (!filename) {
file.resume();
return;
}
const chunks: Buffer[] = []; const chunks: Buffer[] = [];
let totalSize = 0; let size = 0;
const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
file.on('data', chunk => {
size += chunk.length;
if (size > 5 * 1024 * 1024)
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit`));
file.on('data', (chunk) => {
totalSize += chunk.length;
if (totalSize > MAX_SIZE) {
file.resume();
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit.`));
}
chunks.push(chunk); chunks.push(chunk);
}); });
file.on('end', () => { file.on('end', () => {
// Only add file if we have data
if (chunks.length > 0) { if (chunks.length > 0) {
files.push({ files.push({
buffer: Buffer.concat(chunks), buffer: Buffer.concat(chunks),
mimeType, mimeType,
fileName: filename, fileName: filename,
fieldName: fieldname, fieldName: fieldname
}); });
} }
}); });
file.on('error', (err) => {
reject(new ApiError(400, `File upload error: ${err.message}`));
});
}); });
bb.on('field', (fieldname, val) => { bb.on('field', (fieldname, val) => {
// Handle empty or null values try { fields[fieldname] = JSON.parse(val); }
if (val === '' || val === 'null' || val === 'undefined') { catch { fields[fieldname] = val; }
fields[fieldname] = null;
} else {
try {
fields[fieldname] = JSON.parse(val);
} catch {
fields[fieldname] = val;
}
}
});
bb.on('close', () => {
console.log("✅ Busboy parsing completed");
console.log("📌 Fields:", fields);
console.log("📁 Files:", files.length);
resolve();
});
bb.on('error', (err) => {
console.error("❌ Busboy error:", err);
reject(new ApiError(400, `Multipart parsing error: ${err.message}`));
}); });
bb.on('close', resolve);
bb.on('error', err => reject(new ApiError(400, err.message)));
bb.end(bodyBuffer); bb.end(bodyBuffer);
}); });
// 4) Extract required fields - only activityXid, pqqQuestionXid, pqqAnswerXid are required // Required fields
const activityXid = Number(fields.activityXid); const activityXid = Number(fields.activityXid);
const pqqQuestionXid = Number(fields.pqqQuestionXid); const pqqQuestionXid = Number(fields.pqqQuestionXid);
const pqqAnswerXid = Number(fields.pqqAnswerXid); const pqqAnswerXid = Number(fields.pqqAnswerXid);
// Comments and files are optional
const comments = fields.comments || null; const comments = fields.comments || null;
// Validate required fields if (!activityXid || !pqqQuestionXid || !pqqAnswerXid)
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required"); throw new ApiError(400, "Missing required fields");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (pqqQuestionXid !== LAST_QUESTION_ID.Q_ID) throw new ApiError(400, "Wrong question id.")
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// console.log(`📝 Processing - Activity: ${activityXid}, Question: ${pqqQuestionXid}, Answer: ${pqqAnswerXid}`);
// console.log(`💬 Comments: ${comments ? 'Provided' : 'Not provided'}`);
// console.log(`📎 Files: ${files.length}`);
// 5) UPSERT: Check if header already exists for this combination
const existingHeader = await pqqService.findHeaderByCompositeKey(
activityXid,
pqqQuestionXid,
pqqAnswerXid
);
// UPSERT header
const existingHeader = await hostService.findHeaderByCompositeKey(activityXid, pqqQuestionXid);
let header; let header;
if (existingHeader) { if (existingHeader) {
console.log("🔄 Updating existing PQQ header"); header = await hostService.updateHeader(existingHeader.id, pqqAnswerXid, comments);
// Update existing header (comments can be null)
header = await pqqService.updateHeader(
existingHeader.id,
comments
);
} else { } else {
console.log("🆕 Creating new PQQ header"); header = await hostService.createHeader(activityXid, pqqQuestionXid, pqqAnswerXid, comments);
// Create new header (comments can be null)
header = await pqqService.createHeader(
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
);
} }
// Calculate score after answer submission
const score = await pqqService.calculatePqqScoreForUser(activityXid);
// SCORE
const score = await hostService.calculatePqqScoreForUser(activityXid);
// 6) Get existing supporting files for this header // Existing supporting files
const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id); const existingSupportingFiles = await hostService.getSupportingFilesByHeaderId(header.id);
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
// 7) Handle file UPSERT - only if files are provided // Read deletedFiles from frontend
const uploadedFiles: any[] = []; 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) { if (files.length > 0) {
console.log("📤 Processing file uploads..."); for (const file of files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const existingFile = existingSupportingFiles[i] || null;
const url = await uploadToS3( const url = await uploadToS3(
file.buffer, file.buffer,
file.mimeType, file.mimeType,
file.fileName, file.fileName,
`ActivityOnboarding/supportings/${activityXid}`, `ActivityOnboarding/supportings/${activityXid}`
existingFile ? existingFile.mediaFileName : undefined
); );
let supporting; const newRec = await hostService.addSupportingFile(header.id, file.mimeType, url);
if (existingFile) { addResults.push(newRec);
// Update existing supporting file record
supporting = await pqqService.updateSupportingFile(
existingFile.id,
file.mimeType,
url
);
console.log(`🔄 Updated supporting file: ${existingFile.id}`);
} else {
// Create new supporting file record
supporting = await pqqService.addSupportingFile(
header.id,
file.mimeType,
url
);
console.log(`🆕 Created new supporting file: ${supporting.id}`);
}
uploadedFiles.push(supporting);
}
// 8) Delete any remaining existing files that weren't replaced
if (existingSupportingFiles.length > files.length) {
const filesToDelete = existingSupportingFiles.slice(files.length);
console.log(`🗑️ Deleting ${filesToDelete.length} unused supporting files`);
for (const fileToDelete of filesToDelete) {
await pqqService.deleteSupportingFile(fileToDelete.id);
// Also delete from S3
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
}
} else {
console.log("📭 No files provided in request");
// If no files provided but existing files exist, delete them (cleanup)
if (existingSupportingFiles.length > 0) {
console.log(`🗑️ No new files provided, deleting ${existingSupportingFiles.length} existing files`);
for (const fileToDelete of existingSupportingFiles) {
await pqqService.deleteSupportingFile(fileToDelete.id);
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
} }
} }
// 9) Prepare response const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
return { return {
statusCode: 200, statusCode: 200,
headers: { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: responseMessage, message: existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully",
data: { data: {
headerId: header.id, headerId: header.id,
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid, pqqAnswerXid,
comments: comments, comments,
score, score,
files: { getAllUpdatedQuestionResponse
uploaded: uploadedFiles,
total: uploadedFiles.length
},
operation: existingHeader ? 'updated' : 'created',
fileOperation: files.length > 0 ?
(existingSupportingFiles.length > 0 ? 'replaced' : 'added') :
(existingSupportingFiles.length > 0 ? 'removed' : 'unchanged')
} }
}) })
}; };
} catch (error: any) { } catch (err: any) {
console.error("❌ Error in submitPqqAnswer:", error); console.error("❌ Error:", err);
throw error; throw err;
} }
}); });

View File

@@ -1,13 +1,11 @@
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
context?: Context context?: Context

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
context?: Context context?: Context
@@ -29,15 +28,18 @@ export const handler = safeHandler(async (
if (!activity_xid || isNaN(activity_xid)) { if (!activity_xid || isNaN(activity_xid)) {
throw new ApiError(400, "Activity id is required and must be a number."); throw new ApiError(400, "Activity id is required and must be a number.");
} }
let result = null;
// Fetch user with their HostHeader stepper info // Fetch user with their HostHeader stepper info
const pqqQuestionDetails = await hostService.getLatestQuestionDetailsPQQ(activity_xid); const pqqQuestionDetails = await hostService.getLatestQuestionDetailsPQQ(activity_xid);
const result = { if (pqqQuestionDetails) {
pqqQuestionXid: pqqQuestionDetails.pqqQuestionXid, result = {
pqqAnswerXid: pqqQuestionDetails.pqqAnswerXid, pqqQuestionXid: pqqQuestionDetails.pqqQuestionXid,
pqqSubCategoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategoryXid, pqqAnswerXid: pqqQuestionDetails.pqqAnswerXid || null,
categoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategories.categoryXid pqqSubCategoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategoryXid || null,
categoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategories.categoryXid || null
}
} }
return { return {

View File

@@ -1,12 +1,11 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -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;
}
});

View File

@@ -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;
}
});

View File

@@ -1,16 +1,15 @@
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
import crypto from 'crypto'; import crypto from 'crypto';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prisma = new PrismaService(); const pqqService = new HostService(prismaClient);
const pqqService = new HostService(prisma);
const s3 = new AWS.S3({ region: config.aws.region }); const s3 = new AWS.S3({ region: config.aws.region });
@@ -30,34 +29,24 @@ async function deleteFromS3(s3Key: string): Promise<void> {
console.log(`✅ File deleted from S3: ${s3Key}`); console.log(`✅ File deleted from S3: ${s3Key}`);
} catch (error) { } catch (error) {
console.error(`❌ Error deleting file from S3: ${s3Key}`, error); console.error(`❌ Error deleting file from S3: ${s3Key}`, error);
// Don't throw error here, continue with upload // 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> { async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> {
let s3Key: string; // We intentionally do NOT reuse old key. If existingUrl is provided we delete old file and create a new random key.
// If existing URL provided, use the same S3 key to replace the file
// if (existingUrl) {
// s3Key = getS3KeyFromUrl(existingUrl);
// // Delete existing file first
// await deleteFromS3(s3Key);
// } else {
// // Generate new unique key for new file
// const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
// s3Key = `${prefix}/${uniqueKey}`;
// }
if (existingUrl) { if (existingUrl) {
// Delete old file, but DO NOT reuse its name try {
const oldKey = getS3KeyFromUrl(existingUrl); const oldKey = getS3KeyFromUrl(existingUrl);
await deleteFromS3(oldKey); await deleteFromS3(oldKey);
} catch (err) {
console.warn('Warning deleting existingUrl before upload', err);
}
} }
// Create new key always
const uniqueKey = `${crypto.randomUUID()}_${originalName}`; const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
s3Key = `${prefix}/${uniqueKey}`; const s3Key = `${prefix}/${uniqueKey}`;
// Upload new file (replaces existing if same key)
await s3.upload({ await s3.upload({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
@@ -82,7 +71,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
if (!contentType?.includes("multipart/form-data")) if (!contentType?.includes("multipart/form-data"))
throw new ApiError(400, "Content-Type must be multipart/form-data"); throw new ApiError(400, "Content-Type must be multipart/form-data");
// 3) Body decoding (FIXED same as addCompanyDetails) // 3) Body decoding
const bodyBuffer = event.isBase64Encoded const bodyBuffer = event.isBase64Encoded
? Buffer.from(event.body!, "base64") ? Buffer.from(event.body!, "base64")
: Buffer.from(event.body!, "binary"); : Buffer.from(event.body!, "binary");
@@ -90,7 +79,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const fields: any = {}; const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 4) Parse multipart data (FIXED using bb.write + bb.end exactly like working lambda) // 4) Parse multipart data
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const bb = Busboy({ headers: { 'content-type': contentType } }); const bb = Busboy({ headers: { 'content-type': contentType } });
@@ -152,41 +141,32 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
bb.end(); bb.end();
}); });
// 4) Extract required fields - only activityXid, pqqQuestionXid, pqqAnswerXid are required // 5) Extract required fields
const activityXid = Number(fields.activityXid); const activityXid = Number(fields.activityXid);
const pqqQuestionXid = Number(fields.pqqQuestionXid); const pqqQuestionXid = Number(fields.pqqQuestionXid);
const pqqAnswerXid = Number(fields.pqqAnswerXid); const pqqAnswerXid = Number(fields.pqqAnswerXid);
// Comments and files are optional
const comments = fields.comments || null; const comments = fields.comments || null;
// Validate required fields
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required"); 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 (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required"); if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// console.log(`📝 Processing - Activity: ${activityXid}, Question: ${pqqQuestionXid}, Answer: ${pqqAnswerXid}`); // 6) UPSERT header
// console.log(`💬 Comments: ${comments ? 'Provided' : 'Not provided'}`);
// console.log(`📎 Files: ${files.length}`);
// 5) UPSERT: Check if header already exists for this combination
const existingHeader = await pqqService.findHeaderByCompositeKey( const existingHeader = await pqqService.findHeaderByCompositeKey(
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid
); );
let header; let header;
if (existingHeader) { if (existingHeader) {
console.log("🔄 Updating existing PQQ header"); console.log("🔄 Updating existing PQQ header");
// Update existing header (comments can be null)
header = await pqqService.updateHeader( header = await pqqService.updateHeader(
existingHeader.id, existingHeader.id,
pqqAnswerXid,
comments comments
); );
} else { } else {
console.log("🆕 Creating new PQQ header"); console.log("🆕 Creating new PQQ header");
// Create new header (comments can be null)
header = await pqqService.createHeader( header = await pqqService.createHeader(
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
@@ -195,79 +175,93 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
); );
} }
// 6) Get existing supporting files for this header // 7) Get existing supporting files
const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id); const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id);
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`); console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
// 7) Handle file UPSERT - only if files are provided // 8) Parse incoming control fields
const uploadedFiles: any[] = []; // 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) { if (files.length > 0) {
console.log("📤 Processing file uploads..."); console.log(`📤 Processing ${files.length} uploaded new file(s)`);
for (const file of files) {
for (let i = 0; i < files.length; i++) { try {
const file = files[i]; const url = await uploadToS3(
const existingFile = existingSupportingFiles[i] || null; file.buffer,
const url = await uploadToS3(
file.buffer,
file.mimeType,
file.fileName,
`ActivityOnboarding/supportings/${activityXid}`,
existingFile ? existingFile.mediaFileName : undefined
);
let supporting;
if (existingFile) {
// Update existing supporting file record
supporting = await pqqService.updateSupportingFile(
existingFile.id,
file.mimeType, file.mimeType,
url file.fileName,
`ActivityOnboarding/supportings/${activityXid}`
); );
console.log(`🔄 Updated supporting file: ${existingFile.id}`);
} else { // create DB record
// Create new supporting file record const supporting = await pqqService.addSupportingFile(
supporting = await pqqService.addSupportingFile(
header.id, header.id,
file.mimeType, file.mimeType,
url url
); );
console.log(`🆕 Created new supporting file: ${supporting.id}`);
}
uploadedFiles.push(supporting); addedResults.push(supporting);
} console.log(`🆕 Created new supporting file record: ${supporting.id}`);
} catch (err: any) {
// 8) Delete any remaining existing files that weren't replaced console.error('❌ Error uploading/creating supporting file', err);
if (existingSupportingFiles.length > files.length) { // push failure result but continue processing other files
const filesToDelete = existingSupportingFiles.slice(files.length); addedResults.push({ success: false, reason: err.message || 'upload/create failed' });
console.log(`🗑️ Deleting ${filesToDelete.length} unused supporting files`);
for (const fileToDelete of filesToDelete) {
await pqqService.deleteSupportingFile(fileToDelete.id);
// Also delete from S3
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
} }
} }
} else { } else {
console.log("📭 No files provided in request"); console.log('📭 No new files uploaded in request');
// If no files provided but existing files exist, delete them (cleanup)
if (existingSupportingFiles.length > 0) {
console.log(`🗑️ No new files provided, deleting ${existingSupportingFiles.length} existing files`);
for (const fileToDelete of existingSupportingFiles) {
await pqqService.deleteSupportingFile(fileToDelete.id);
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
}
} }
// 9) Prepare response // 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"; const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
return { return {
@@ -284,15 +278,15 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid, pqqAnswerXid,
comments: comments, comments,
files: { files: {
uploaded: uploadedFiles, added: addedResults,
total: uploadedFiles.length deleted: deletedResults,
existingKeptCount: (existingSupportingFiles.length - deletedResults.filter(d => d.success).length)
}, },
operation: existingHeader ? 'updated' : 'created', operation: existingHeader ? 'updated' : 'created',
fileOperation: files.length > 0 ? // summary label for UI convenience:
(existingSupportingFiles.length > 0 ? 'replaced' : 'added') : fileOperation: (deletedResults.length > 0 || addedResults.length > 0) ? 'modified' : 'unchanged'
(existingSupportingFiles.length > 0 ? 'removed' : 'unchanged')
} }
}) })
}; };
@@ -301,4 +295,4 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
console.error("❌ Error in submitPqqAnswer:", error); console.error("❌ Error in submitPqqAnswer:", error);
throw error; throw error;
} }
}); });

View File

@@ -1,12 +1,11 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,12 +1,11 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
/** /**
* Add suggestion handler for host applications * Add suggestion handler for host applications

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,15 +1,13 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { TokenService } from '../../../services/token.service'; import { TokenService } from '../../../services/token.service';
import { GetHostLoginResponseDTO } from '../../../dto/host.dto'; import { GetHostLoginResponseDTO } from '../../../dto/host.dto';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import * as bcrypt from 'bcryptjs';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService); const tokenService = new TokenService(prismaClient);
const tokenService = new TokenService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -17,7 +15,7 @@ export const handler = safeHandler(async (
): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// Parse request body // Parse request body
let body: { emailAddress?: string; userPassword?: string }; let body: { emailAddress?: string; userPassword?: string };
try { try {
body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
} catch (error) { } catch (error) {
@@ -30,7 +28,9 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Email and password are required'); throw new ApiError(400, 'Email and password are required');
} }
const loginForHost = await hostService.loginForHost(emailAddress, userPassword); const emailToLowerCase = emailAddress.toLowerCase()
const loginForHost = await hostService.loginForHost(emailToLowerCase, userPassword);
if (!loginForHost) { if (!loginForHost) {
throw new ApiError(400, 'Failed to login'); throw new ApiError(400, 'Failed to login');
@@ -40,15 +40,6 @@ export const handler = safeHandler(async (
throw new ApiError(401, 'Invalid credentials'); throw new ApiError(401, 'Invalid credentials');
} }
const matchPassword = await bcrypt.compare(
userPassword,
loginForHost.userPassword
);
if (!matchPassword) {
throw new ApiError(401, 'Invalid credentials');
}
const generateTokenForHost = await tokenService.generateAuthToken( const generateTokenForHost = await tokenService.generateAuthToken(
loginForHost.id loginForHost.id
); );

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { MinglarService } from '../../../../minglaradmin/services/minglar.service'; import { MinglarService } from '../../../../minglaradmin/services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
/** /**
* Get suggestions handler * Get suggestions handler

View File

@@ -1,16 +1,15 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; 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 { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import * as bcrypt from 'bcryptjs';
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator'; import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { sendOtpEmailForHost } from '../../../services/sendOTPEmail.service'; import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
import { ROLE } from '../../../../../common/utils/constants/common.constant';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export async function generateHostRefNumber(tx: any) { export async function generateHostRefNumber(tx: any) {
const lastrecord = await tx.user.findFirst({ const lastrecord = await tx.user.findFirst({
@@ -46,10 +45,12 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Email is required'); throw new ApiError(400, 'Email is required');
} }
const emailToLowerCase = email.toLowerCase()
// Use a single transaction for user creation/lookup and OTP storage // Use a single transaction for user creation/lookup and OTP storage
const transactionResult = await prismaService.$transaction(async (tx) => { const transactionResult = await prismaClient.$transaction(async (tx) => {
const user = await tx.user.findUnique({ const user = await tx.user.findUnique({
where: { emailAddress: email }, where: { emailAddress: emailToLowerCase },
select: { emailAddress: true, id: true, userPassword: true }, select: { emailAddress: true, id: true, userPassword: true },
}); });
@@ -67,7 +68,7 @@ export const handler = safeHandler(async (
} else { } else {
// create new user record within the transaction // create new user record within the transaction
newUserLocal = await tx.user.create({ newUserLocal = await tx.user.create({
data: { emailAddress: email, roleXid: ROLE.HOST, userRefNumber: referenceNumber }, data: { emailAddress: emailToLowerCase, roleXid: ROLE.HOST, userRefNumber: referenceNumber },
}); });
} }
@@ -102,7 +103,7 @@ export const handler = safeHandler(async (
} }
// Send OTP email outside the DB transaction // Send OTP email outside the DB transaction
// await sendOtpEmailForHost(transactionResult.newUser.emailAddress, transactionResult.otp); await sendOtpEmailForHost(transactionResult.newUser.emailAddress, transactionResult.otp);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,10 +1,10 @@
// modules/host/handlers/addCompanyDetails.ts // modules/host/handlers/addCompanyDetails.ts
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { import {
@@ -15,8 +15,17 @@ import {
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { sendEmailToAM, sendEmailToMinglarAdmin } from '../../../services/sendHostResubmitEmailToAM.service'; import { sendEmailToAM, sendEmailToMinglarAdmin } from '../../../services/sendHostResubmitEmailToAM.service';
const prisma = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prisma);
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({ const s3 = new AWS.S3({
region: config.aws.region, region: config.aws.region,
@@ -50,6 +59,25 @@ function cleanEmptyStrings(obj: any) {
return cleaned; 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> => { export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try { try {
@@ -112,6 +140,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
bb.end(); bb.end();
}); });
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
/** 4) Extract and clean isDraft flag */ /** 4) Extract and clean isDraft flag */
const isDraft = fields.isDraft === 'true' || fields.isDraft === true; const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
@@ -128,13 +159,22 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
} }
} }
if (
companyDetailsRaw.parentCompany &&
Object.values(companyDetailsRaw.parentCompany).every(
(v) => v === undefined || v === null
)
) {
companyDetailsRaw.parentCompany = null;
}
/** 6) Profile update if provided */ /** 6) Profile update if provided */
if (fields.userProfile) { if (fields.userProfile) {
const userProfileRaw = normalizeJsonField(fields, 'userProfile'); const userProfileRaw = normalizeJsonField(fields, 'userProfile');
if (userProfileRaw) { if (userProfileRaw) {
const { firstName, lastName, mobileNumber } = userProfileRaw; const { firstName, lastName, mobileNumber } = userProfileRaw;
await prisma.user.update({ await prismaClient.user.update({
where: { id: userInfo.id }, where: { id: userInfo.id },
data: { data: {
...(firstName && { firstName }), ...(firstName && { firstName }),
@@ -178,13 +218,8 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const file = files.find((f) => f.fieldName === doc.fieldName); const file = files.find((f) => f.fieldName === doc.fieldName);
// In DRAFT mode → allow missing documents // In DRAFT mode → allow missing documents
if (isDraft && !file) {
return { ...doc, file: null };
}
// In FINAL mode → file must exist
if (!file) { if (!file) {
throw new ApiError(400, `File not found for field: ${doc.fieldName}`); return { ...doc, file: null };
} }
return { ...doc, file }; return { ...doc, file };
@@ -213,9 +248,65 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
parsedParentCompany = parsedCompany.parentCompany || null; 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 */ /** 11) UPLOAD DOCUMENTS */
async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) { async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) {
const ext = originalName.split('.').pop() || 'jpg'; // const ext = originalName.split('.').pop() || 'jpg';
const ext = getExtensionFromMime(mimeType);
let s3Key = ''; let s3Key = '';
@@ -249,7 +340,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
/** Upload host docs */ /** Upload host docs */
const uploadedHostDocs: Array<any> = []; const uploadedHostDocs: Array<any> = [];
for (const doc of hostDocs) { for (const doc of hostDocs) {
if (isDraft && !doc.file) continue; if (!doc.file) continue;
const path = await uploadToS3( const path = await uploadToS3(
doc.file.buffer, doc.file.buffer,
@@ -270,7 +361,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
/** Upload parent docs */ /** Upload parent docs */
const uploadedParentDocs: Array<any> = []; const uploadedParentDocs: Array<any> = [];
for (const doc of parentDocs) { for (const doc of parentDocs) {
if (!doc.file && isDraft) continue; // skip missing files in draft mode if (!doc.file) continue; // skip missing files in draft mode
const path = await uploadToS3( const path = await uploadToS3(
doc.file.buffer, doc.file.buffer,
@@ -289,28 +380,49 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
} }
/** UPLOAD LOGO (if provided) */ /** UPLOAD LOGO (if provided) */
const logoFile = files.find((f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'); const logoFile = files.find(
if (logoFile) { (f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
const logoUrl = await uploadToS3(logoFile.buffer, logoFile.mimeType, logoFile.fileName, 'logo'); );
if (logoFile && logoFile.buffer && logoFile.fileName) {
const logoUrl = await uploadToS3(
logoFile.buffer,
logoFile.mimeType,
logoFile.fileName,
'logo'
);
parsedCompany.logoPath = logoUrl; parsedCompany.logoPath = logoUrl;
} }
/** UPLOAD PARENT COMPANY LOGO (if provided) */ /** UPLOAD PARENT COMPANY LOGO (if provided) */
const parentLogoFile = files.find((f) => f.fieldName === 'parentCompanyLogo'); const parentLogoFile = files.find(
if (parentLogoFile) { (f) => f.fieldName === 'parentCompanyLogo'
);
if (parentLogoFile && parentLogoFile.buffer && parentLogoFile.mimeType) {
// 🔒 Only upload when an actual file is present
const parentLogoUrl = await uploadToS3( const parentLogoUrl = await uploadToS3(
parentLogoFile.buffer, parentLogoFile.buffer,
parentLogoFile.mimeType, parentLogoFile.mimeType,
parentLogoFile.fileName, parentLogoFile.fileName, // safe here because it's a real file
'parent_company_logo', 'parent_company_logo',
); );
if (parsedParentCompany) { if (parsedParentCompany) {
parsedParentCompany.logoPath = parentLogoUrl; parsedParentCompany.logoPath = parentLogoUrl;
} else { } else {
// if no parent object exists yet (drafts or other flows), attach it safely parsedParentCompany = {
parsedParentCompany = parsedParentCompany || {}; logoPath: parentLogoUrl,
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`);
} }
} }

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { hostBankDetailsSchema } from '@/common/utils/validation/host/addPaymentDetails.validation'; import { hostBankDetailsSchema } from '../../../../../common/utils/validation/host/addPaymentDetails.validation';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -33,7 +32,7 @@ export const handler = safeHandler(async (
} }
// Parse request body // Parse request body
let body: { bankXid?: number; bankBranchXid?: number; accountNumber?: string; confirmAccountNumber?: string; accountHolderName?: string; ifscCode?: string; currencyXid?: number }; let body: { bankXid?: number; bankBranchXid?: number; accountNumber?: string; confirmAccountNumber?: string; accountHolderName?: string; currencyXid?: number };
try { try {
body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
@@ -54,7 +53,16 @@ export const handler = safeHandler(async (
const validatedData = validationResult.data; const validatedData = validationResult.data;
await hostService.addPaymentDetails(validatedData); // 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 { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { TokenService } from '../../../services/token.service'; import { TokenService } from '../../../services/token.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService); const tokenService = new TokenService(prismaClient);
const tokenService = new TokenService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -28,8 +27,10 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Email and OTP are required'); throw new ApiError(400, 'Email and OTP are required');
} }
await hostService.verifyHostOtp(email, otp); const emailToLowerCase = email.toLowerCase();
const user = await hostService.getHostByEmail(email);
await hostService.verifyHostOtp(emailToLowerCase, otp);
const user = await hostService.getHostByEmail(emailToLowerCase);
const generateTokenForHost = await tokenService.generateAuthToken( const generateTokenForHost = await tokenService.generateAuthToken(
user.id user.id
); );

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
import { HostService } from '../services/host.service'; import { HostService } from '../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
context?: Context context?: Context
@@ -26,7 +25,7 @@ export const handler = safeHandler(async (
} }
// Fetch user with their HostHeader stepper info // Fetch user with their HostHeader stepper info
const host = await hostService.getHostById(userId); const host = await hostService.getHostIdByUserXid(userId);
if (!host) { if (!host) {
throw new ApiError(404, 'Host record not found'); throw new ApiError(404, 'Host record not found');
@@ -43,6 +42,7 @@ export const handler = safeHandler(async (
message: 'Stepper information retrieved successfully', message: 'Stepper information retrieved successfully',
data: { data: {
stepper: host.stepper, stepper: host.stepper,
emailAddress: host.user?.emailAddress || null,
}, },
}), }),
}; };

View File

@@ -1,12 +1,11 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { HostService } from '../services/host.service'; import { HostService } from '../services/host.service';
const prismaService = new PrismaService(); const hostService = new HostService(prismaClient);
const hostService = new HostService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -14,7 +13,7 @@ export const handler = safeHandler(async (
): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// Get host ID from path parameters // Get host ID from path parameters
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if(!token) { if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
} }

View File

@@ -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,
}, },

View File

@@ -1,11 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { PrismaService } from "../../../common/database/prisma.service"; import { prismaClient } from "../../../common/database/prisma.lambda.service";
import { safeHandler } from "../../../common/utils/handlers/safeHandler"; import { safeHandler } from "../../../common/utils/handlers/safeHandler";
import ApiError from "../../../common/utils/helper/ApiError"; import ApiError from "../../../common/utils/helper/ApiError";
import { resendOtpHelper } from "../../../common/utils/helper/resendOtpHelper"; import { resendOtpHelper } from "../../../common/utils/helper/resendOtpHelper";
import { resendOtpEmail } from "../services/resendOTPEmail.service"; import { resendOtpEmail } from "../services/resendOTPEmail.service";
const prisma = new PrismaService(); const prisma = prismaClient;
// allowed purposes // allowed purposes
const ALLOWED_PURPOSES = ["Register", "Login", "ForgotPassword"] as const; const ALLOWED_PURPOSES = ["Register", "Login", "ForgotPassword"] as const;
@@ -41,9 +41,11 @@ export const handler = safeHandler(
const email = (body.email || "").trim(); const email = (body.email || "").trim();
if (!email) throw new ApiError(400, "Email is required"); 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) // find user (you can adapt the isActive / userStatus checks per your rules)
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { emailAddress: email, isActive: true }, where: { emailAddress: emailToLowerCase, isActive: true },
select: { id: true, emailAddress: true, role: true }, select: { id: true, emailAddress: true, role: true },
}); });
@@ -56,7 +58,6 @@ export const handler = safeHandler(
const otpResult = await resendOtpHelper( const otpResult = await resendOtpHelper(
prisma, prisma,
user.id, user.id,
user.emailAddress,
purpose, purpose,
6, // 6-digit OTP 6, // 6-digit OTP
5 // expires in 5 minutes 5 // expires in 5 minutes

File diff suppressed because it is too large Load Diff

View File

@@ -15,8 +15,8 @@ export async function resendOtpEmail(
const htmlContent = ` const htmlContent = `
<p>Dear ${role},</p> <p>Dear ${role},</p>
<p>Your new OTP is: <strong>${otp}</strong></p> <p>Your new OTP is: <strong>${otp}</strong></p>
<p>This code is valid for 5 minutes. Please do not share it with anyone.</p> <p>This code will be valid for the next 5 minutes.</p>
<p>Best regards,<br/>Minglar Team</p> <p>Warm regards,<br/>Minglar Team</p>
`; `;
try { try {

View File

@@ -13,9 +13,10 @@ export async function sendOtpEmailForHost(
const htmlContent = ` const htmlContent = `
<p>Dear Host,</p> <p>Dear Host,</p>
<p>Your OTP for registration is: <strong>${otp}</strong></p> <p>Youre almost all set! 🎉</p>
<p>This code is valid for 5 minutes. Please do not share it with anyone.</p> <p>Enter <strong>${otp}</strong> to wrap your registration.</p>
<p>Best regards,<br/>Minglar Team</p> <p>This code will be valid for the next 5 minutes.</p>
<p>Warm regards,<br/>Minglar Team</p>
`; `;
try { try {

View File

@@ -1,10 +1,10 @@
import { PrismaService } from "../../../common/database/prisma.service"; import { PrismaClient } from '@prisma/client';
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
import moment from "moment"; import moment from "moment";
import config from "../../../config/config"; import config from "../../../config/config";
export class TokenService { export class TokenService {
constructor(private readonly prisma: PrismaService = new PrismaService()) {} constructor(private prisma: PrismaClient) { }
private generateToken( private generateToken(
user_xid: number, user_xid: number,
@@ -53,6 +53,10 @@ export class TokenService {
config.jwt.secret config.jwt.secret
); );
await this.prisma.token.deleteMany({
where: { userXid: user_xid }
})
await this.prisma.token.create({ await this.prisma.token.create({
data: { data: {
token: refreshToken.token, token: refreshToken.token,
@@ -100,6 +104,10 @@ export class TokenService {
config.jwt.secret config.jwt.secret
); );
await this.prisma.token.deleteMany({
where: { userXid: user_xid }
})
await this.prisma.token.create({ await this.prisma.token.create({
data: { data: {
token: refreshToken.token, token: refreshToken.token,

View File

@@ -1,13 +1,11 @@
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../common/database/prisma.service';
import { MinglarService } from '../services/minglar.service';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; import { MinglarService } from '../services/minglar.service';
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -3,14 +3,13 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../common/database/prisma.service';
import { MinglarService } from '../services/minglar.service';
import ApiError from '../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; 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 prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
/** /**
* Get all host applications handler * Get all host applications handler

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendEmailToHostForApprovedApplication } from '../../../services/approvalMailtoHost.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { sendEmailToHostForApprovedApplication } from '../../../services/approvalMailtoHost.service'
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody { interface AddSuggestionBody {
hostXid: number; hostXid: number;
@@ -47,8 +46,8 @@ export const handler = safeHandler(async (
// Add suggestion using service // Add suggestion using service
await minglarService.acceptHostApplication(hostXid, userInfo.id); await minglarService.acceptHostApplication(hostXid, userInfo.id);
const hostDetails = await minglarService.getUserDetails(userInfo.id) const hostDetails = await minglarService.getUserDetails(hostXid)
await sendEmailToHostForApprovedApplication(hostDetails.emailAddress) await sendEmailToHostForApprovedApplication(hostDetails.emailAddress, hostDetails.firstName)
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -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,
}),
};
});

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant'; 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 prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody { interface AddSuggestionBody {
title: string; title: string;
@@ -34,7 +33,7 @@ export const handler = safeHandler(async (
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
// Get user details // Get user details
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true }
}); });
@@ -67,10 +66,10 @@ export const handler = safeHandler(async (
} }
// Validate title is one of the allowed types // Validate title is one of the allowed types
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES); // const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
if (!allowedTitles.includes(title)) { // if (!allowedTitles.includes(title)) {
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`); // throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
} // }
// Add suggestion using service // Add suggestion using service
await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid,user.id); await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid,user.id);

View File

@@ -1,18 +1,17 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody { interface AddSuggestionBody {
hostXid: number; hostXid: number;
title: string; title: string;
comments: string; comments: string;
isParent: boolean;
} }
/** /**
@@ -34,7 +33,7 @@ export const handler = safeHandler(async (
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
// Get user details // Get user details
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true }
}); });
@@ -52,7 +51,7 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Invalid JSON in request body'); throw new ApiError(400, 'Invalid JSON in request body');
} }
const { hostXid, title, comments } = body; const { hostXid, title, comments, isParent } = body;
// Validate required fields // Validate required fields
if (!hostXid) { if (!hostXid) {
@@ -68,13 +67,13 @@ export const handler = safeHandler(async (
} }
// Validate title is one of the allowed types // Validate title is one of the allowed types
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES); // const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
if (!allowedTitles.includes(title)) { // if (!allowedTitles.includes(title)) {
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`); // throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
} // }
// Add suggestion using service // Add suggestion using service
await minglarService.addHostSuggestion(hostXid, title, comments, user.id); await minglarService.addHostSuggestion(hostXid, title, comments, user.id, isParent);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; 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 { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
/** /**
* Get all host applications handler with pagination * Get all host applications handler with pagination
@@ -26,7 +25,7 @@ export const handler = safeHandler(async (
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true }
}); });
@@ -43,6 +42,7 @@ export const handler = safeHandler(async (
// Parse pagination parameters // Parse pagination parameters
const paginationParams = paginationService.getPaginationFromEvent(event); const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions = paginationService.parsePaginationParams(paginationParams); const paginationOptions = paginationService.parsePaginationParams(paginationParams);
const applicationStatus = event.queryStringParameters?.applicationStatus || '';
// Get paginated host applications // Get paginated host applications
const { data, totalCount } = await minglarService.getAllHostApplications( const { data, totalCount } = await minglarService.getAllHostApplications(
@@ -51,7 +51,8 @@ export const handler = safeHandler(async (
search, search,
userStatus, userStatus,
paginationOptions, paginationOptions,
roleFilter roleFilter,
applicationStatus
); );
// Create paginated response // Create paginated response

View File

@@ -1,13 +1,11 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; 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 { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { PrismaService } from '@/common/database/prisma.service';
import { safeHandler } from '@/common/utils/handlers/safeHandler';
import ApiError from '@/common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,13 +1,12 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { sendAMRejectionMailtoHost } from '../../../services/rejectionMailtoHost.service'; import { sendAMRejectionMailtoHost } from '../../../services/rejectionMailtoHost.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody { interface AddSuggestionBody {
hostXid: number; hostXid: number;
@@ -47,8 +46,8 @@ export const handler = safeHandler(async (
// Add suggestion using service // Add suggestion using service
await minglarService.rejectHostApplicationAM(hostXid, userInfo.id); await minglarService.rejectHostApplicationAM(hostXid, userInfo.id);
const hostDetails = await minglarService.getUserDetails(userInfo.id) const hostDetails = await minglarService.getUserDetails(hostXid)
await sendAMRejectionMailtoHost(hostDetails.emailAddress) await sendAMRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,12 +1,16 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
import { MinglarService } from '@/modules/minglaradmin/services/minglar.service';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
import { sendAMPQQRejectionMailtoHost } from '../../../../minglaradmin/services/rejectionMailtoHost.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface Body {
activityId: number;
}
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -17,10 +21,12 @@ export const handler = safeHandler(async (
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
let body: any = {}; // Parse request body
let body: Body;
try { try {
body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
} catch (err) { } catch (error) {
throw new ApiError(400, 'Invalid JSON in request body'); throw new ApiError(400, 'Invalid JSON in request body');
} }
@@ -32,7 +38,11 @@ export const handler = safeHandler(async (
await minglarService.rejectPQQbyAM( await minglarService.rejectPQQbyAM(
Number(activityId), Number(activityId),
Number(userInfo.id)
); );
const hostXid = await minglarService.getHostXidByActivityId(activityId)
const hostDetails = await minglarService.getUserDetails(hostXid)
await sendAMPQQRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
return { return {
statusCode: 201, statusCode: 201,
@@ -42,7 +52,7 @@ export const handler = safeHandler(async (
}, },
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Rejected successfully', message: 'Rejected PQ successfully',
data: null, data: null,
}), }),
}; };

View File

@@ -1,68 +0,0 @@
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendEmailToHostForMinglarApproval } from '../../../services/approvalMailtoHost.service';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService);
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.acceptHostApplicationMinglarAdmin(hostXid, userInfo.id);
const hostDetails = await minglarService.getUserDetails(userInfo.id)
if (!hostDetails?.emailAddress) {
throw new ApiError(404, 'Host details or email address not found');
}
await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Application accepted successfully',
data: null,
}),
};
});

View File

@@ -3,15 +3,13 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { sendAMEmailForHostAssign } from '../../../services/AMEmail.service'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface assignAMToHostBody { interface assignAMToHostBody {
host_xid: number; host_xid: number;
@@ -41,7 +39,7 @@ export const handler = safeHandler(
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true }, select: { id: true, roleXid: true },
}); });

View File

@@ -1,12 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface assignAMToHostBody { interface assignAMToHostBody {
host_xid: number, host_xid: number,
@@ -34,7 +33,7 @@ export const handler = safeHandler(async (
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true }
}); });
@@ -64,8 +63,9 @@ export const handler = safeHandler(async (
payoutDurationFrequency payoutDurationFrequency
} = body; } = body;
await minglarService.editAgreementDetails( await minglarService.acceptHostApplicationMinglarAdmin(
host_xid, host_xid,
userInfo.id,
agreementStartDate, agreementStartDate,
duration, duration,
isCommisionBase, isCommisionBase,
@@ -73,8 +73,8 @@ export const handler = safeHandler(async (
amountPerBooking, amountPerBooking,
durationFrequency, durationFrequency,
payoutDurationNum, payoutDurationNum,
payoutDurationFrequency payoutDurationFrequency);
); // await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,14 +1,14 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service'; import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
/** /**
* Add suggestion handler for host applications * Add suggestion handler for host applications
@@ -30,11 +30,18 @@ export const handler = safeHandler(async (
const hostXid = Number(event.pathParameters?.id) 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=) // Read optional search query (supports ?search= or ?q=)
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined; const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
const data = await minglarService.getAllHostActivityForMinglar(search ? String(search) : undefined, hostXid); const result = await minglarService.getAllHostActivityForMinglar(
search ? String(search) : undefined,
hostXid,
paginationOptions
);
return { return {
@@ -46,7 +53,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Data retrieved successfully', message: 'Data retrieved successfully',
data, ...result,
}), }),
}; };
}); });

View File

@@ -1,53 +1,85 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
import { PrismaService } from '../../../../../common/database/prisma.service'; APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
/** /**
* Get all host applications handler * Get all host applications handler
* Returns host details with status, submission date, and account manager info * Returns host details with status, submission date, and account manager info
*/ */
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Verify authentication token ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; // Verify authentication token
if (!token) { const token =
throw new ApiError(401, 'This is a protected route. Please provide a valid 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 // Verify token and get user info
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true },
}); });
if (!user) { if (!user) {
throw new ApiError(404, 'User not found'); throw new ApiError(404, 'User not found');
} }
// Get all host applications from service based on user role // Extract optional search query
const hostApplications = await minglarService.getAllOnboardingHostApplications(); const queryParams = event.queryStringParameters || {};
const search =
(queryParams.search as string) ||
(queryParams.q as string) ||
undefined;
return { // Pagination
statusCode: 200, const paginationParams = paginationService.getPaginationFromEvent(event);
headers: { const paginationOptions =
'Content-Type': 'application/json', paginationService.parsePaginationParams(paginationParams);
'Access-Control-Allow-Origin': '*',
}, // Get all host applications from service based on user role
body: JSON.stringify({ const { data, totalCount } =
success: true, await minglarService.getAllOnboardingHostApplications(
message: 'Host applications retrieved successfully', paginationOptions,
data: hostApplications, 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,
}),
};
},
);

View File

@@ -1,53 +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 { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
/** /**
* Get all host applications handler * Get all NEW host applications handler
* Returns host details with status, submission date, and account manager info * Returns host details with status, submission date, and account manager info
*/ */
export const handler = safeHandler(async ( export const handler = safeHandler(
async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
context?: Context context?: Context,
): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// Verify authentication token // Verify authentication token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) { if (!token) {
throw new ApiError(401, 'This is a protected route. Please provide a valid token.'); throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
} }
// Verify token and get user info // Verify token and get user info
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaClient.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true },
}); });
if (!user) { if (!user) {
throw new ApiError(404, 'User not found'); 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 // Get all host applications from service based on user role
const hostApplications = await minglarService.getAllOnboardingHostApplications_New(); const { data, totalCount } =
await minglarService.getAllOnboardingHostApplications_New(
paginationOptions,
search,
);
const paginatedResponse = paginationService.createPaginatedResponse(
data,
totalCount,
paginationOptions,
);
return { return {
statusCode: 200, statusCode: 200,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
}, },
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Host applications retrieved successfully', message: 'Host applications retrieved successfully',
data: hostApplications, ...paginatedResponse,
}), }),
}; };
}); },
);

View File

@@ -1,13 +1,12 @@
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { sendEmailToHostForRejectedApplication } from '../../../services/rejectionMailtoHost.service'; import { sendEmailToHostForRejectedApplication } from '../../../services/rejectionMailtoHost.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody { interface AddSuggestionBody {
hostXid: number; hostXid: number;
@@ -47,7 +46,7 @@ export const handler = safeHandler(async (
// Add suggestion using service // Add suggestion using service
await minglarService.rejectHostApplication(hostXid, userInfo.id); await minglarService.rejectHostApplication(hostXid, userInfo.id);
const hostDetails = await minglarService.getUserDetails(userInfo.id) const hostDetails = await minglarService.getUserDetails(hostXid)
if (!hostDetails?.emailAddress) { if (!hostDetails?.emailAddress) {
throw new ApiError(404, 'Host details or email address not found'); throw new ApiError(404, 'Host details or email address not found');
} }

View File

@@ -0,0 +1,44 @@
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)
// 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,
}),
};
});

View File

@@ -0,0 +1,49 @@
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
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';
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
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 verifyMinglarAdminHostToken(token);
const activityXid = event.pathParameters?.activityXid;
if (!activityXid) {
throw new ApiError(
400,
'Host ID is required in path parameters.',
);
}
const pqpDetails = await minglarService.getAllPQPDetailsForAM(Number(activityXid));
if (!pqpDetails) {
throw new ApiError(404, 'Record not found');
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'PQ details retrieved successfully',
data: pqpDetails,
}),
};
});

View File

@@ -1,14 +1,13 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { GetMinglarLoginResponseDTO } from '../dto/minglar.dto'; import { GetMinglarLoginResponseDTO } from '../dto/minglar.dto';
import { MinglarService } from '../services/minglar.service'; import { MinglarService } from '../services/minglar.service';
import { TokenService } from "../services/token.service"; import { TokenService } from "../services/token.service";
const prismaService = new PrismaService(); const minglarSerivce = new MinglarService(prismaClient);
const minglarSerivce = new MinglarService(prismaService); const tokenService = new TokenService(prismaClient);
const tokenService = new TokenService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -16,20 +15,22 @@ export const handler = safeHandler(async (
): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// Parse request body // Parse request body
let body: { emailAddress?: string; userPassword?: string }; let body: { emailAddress?: string; userPassword?: string };
try { try {
body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
} catch (error) { } catch (error) {
throw new ApiError(400, 'Invalid JSON in request body'); throw new ApiError(400, 'Invalid JSON in request body');
} }
const { emailAddress ,userPassword} = body; const { emailAddress, userPassword } = body;
if (!emailAddress) { if (!emailAddress) {
throw new ApiError(400, 'Email is required'); throw new ApiError(400, 'Email is required');
} }
const loginForMinglar = await minglarSerivce.loginForMinglar(emailAddress ,userPassword); const emailToLowerCase = emailAddress.toLowerCase()
const loginForMinglar = await minglarSerivce.loginForMinglar(emailToLowerCase, userPassword);
if (!loginForMinglar) { if (!loginForMinglar) {
throw new ApiError(400, 'Failed to login'); throw new ApiError(400, 'Failed to login');

View File

@@ -3,13 +3,13 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { PrismaService } from '../../../common/database/prisma.service';
import ApiError from '../../../common/utils/helper/ApiError';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError';
const prismaService = new PrismaService(); const prismaService = prismaClient;
export const handler = safeHandler( export const handler = safeHandler(
async ( async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,14 +1,13 @@
import { ROLE, USER_STATUS } from '@/common/utils/constants/common.constant'; import { ROLE, USER_STATUS } from '../../../common/utils/constants/common.constant';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { generateOtpHelper } from '../../../common/utils/helper/sendOtp'; import { generateOtpHelper } from '../../../common/utils/helper/sendOtp';
import { sendOtpEmailForMinglarAdmin } from '../services/sendOTPEmail.service';
import { MinglarService } from './../services/minglar.service'; import { MinglarService } from './../services/minglar.service';
import { sendOtpEmailForMinglarAdmin } from '../services/sendOTPEmail.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -28,11 +27,15 @@ export const handler = safeHandler(async (
if (!email) { if (!email) {
throw new ApiError(400, 'Email is required'); throw new ApiError(400, 'Email is required');
} }
console.log(email, " -: Email")
const user = await prismaService.user.findUnique({ const emailToLowerCase = email.toLowerCase()
where: { emailAddress: email, isActive: true, userStatus: USER_STATUS.INVITED },
const user = await prismaClient.user.findUnique({
where: { emailAddress: emailToLowerCase, isActive: true, userStatus: USER_STATUS.INVITED },
select: { emailAddress: true, id: true, userPassword: true, roleXid: true }, select: { emailAddress: true, id: true, userPassword: true, roleXid: true },
}); });
console.log(user, "sljdfjdf")
if (!user) { if (!user) {
throw new ApiError(403, 'You are not allowed to register directly. Please contact minglar admin.'); throw new ApiError(403, 'You are not allowed to register directly. Please contact minglar admin.');
@@ -55,7 +58,7 @@ export const handler = safeHandler(async (
} }
const otpResult = await generateOtpHelper( const otpResult = await generateOtpHelper(
prismaService, // ⭐ pass Prisma from here prismaClient, // ⭐ pass Prisma from here
Number(newUser?.id), Number(newUser?.id),
newUser?.emailAddress, newUser?.emailAddress,
'Register', 'Register',
@@ -68,7 +71,7 @@ export const handler = safeHandler(async (
throw new ApiError(500, 'Failed to send OTP'); throw new ApiError(500, 'Failed to send OTP');
} }
// await sendOtpEmailForMinglarAdmin(newUser?.emailAddress, otpResult.otp); await sendOtpEmailForMinglarAdmin(newUser?.emailAddress, otpResult.otp);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,12 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,42 +1,64 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
import { PrismaService } from '../../../../../common/database/prisma.service'; APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Extract token from headers ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] // Extract token from headers
if(!token) { const token =
throw new ApiError(400, 'This is a protected route. Please provide a valid 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 // Authenticate user using the shared authForHost function
await verifyOnlyMinglarAdminToken(token); await verifyOnlyMinglarAdminToken(token);
// Extract search parameter from query string // Extract search parameter from query string
const search = event.queryStringParameters?.search || ''; const search = event.queryStringParameters?.search || '';
const result = await minglarService.getAllInvitationDetails(search); const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions =
paginationService.parsePaginationParams(paginationParams);
return { const { data, totalCount } = await minglarService.getAllInvitationDetails(
statusCode: 200, search,
headers: { paginationOptions,
'Content-Type': 'application/json', );
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});
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: 'Data retrieved successfully',
...paginatedResponse,
}),
};
},
);

View File

@@ -1,42 +1,63 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
import { PrismaService } from '../../../../../common/database/prisma.service'; APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Extract token from headers ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] // Extract token from headers
if(!token) { const token =
throw new ApiError(400, 'This is a protected route. Please provide a valid 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 // Authenticate user using the shared authForHost function
await verifyOnlyMinglarAdminToken(token); await verifyOnlyMinglarAdminToken(token);
// Extract search parameter from query string // Extract search parameter from query string
const search = event.queryStringParameters?.search || ''; const search = event.queryStringParameters?.search || '';
const response = await minglarService.getAllInvitedCoadminAndAM(search); // Pagination
const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions =
paginationService.parsePaginationParams(paginationParams);
return { const { data, totalCount } =
statusCode: 200, await minglarService.getAllInvitedCoadminAndAM(search, paginationOptions);
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: 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: 'Data retrieved successfully',
...paginatedResponse,
}),
};
},
);

View File

@@ -1,15 +1,14 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { MINGLAR_INVITATION_STATUS } from '@/common/utils/constants/minglar.constant';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { ROLE } from '../../../../../common/utils/constants/common.constant'; import { ROLE } from '../../../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendInvitationEmailForMinglarAdmin } from '../../../services/inviteTeammatesEmail.service'; import { sendInvitationEmailForMinglarAdmin } from '../../../services/inviteTeammatesEmail.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import config from '../../../../../config/config';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
interface InviteTeammateBody { interface InviteTeammateBody {
emailAddress: string; emailAddress: string;
@@ -62,6 +61,8 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Role is required'); throw new ApiError(400, 'Role is required');
} }
const emailToLowerCase = emailAddress.toLowerCase()
// Validate role is either Co_Admin or Account_Manager // Validate role is either Co_Admin or Account_Manager
if (![ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER].includes(roleXid)) { if (![ROLE.CO_ADMIN, ROLE.ACCOUNT_MANAGER].includes(roleXid)) {
throw new ApiError(400, 'Invalid role. Only Co_Admin and Account_Manager roles can be assigned.'); throw new ApiError(400, 'Invalid role. Only Co_Admin and Account_Manager roles can be assigned.');
@@ -80,7 +81,7 @@ export const handler = safeHandler(async (
// Use single service method that encapsulates the transaction // Use single service method that encapsulates the transaction
await minglarService.inviteTeammate( await minglarService.inviteTeammate(
emailAddress, emailToLowerCase,
roleXid, roleXid,
isFixedSalary, isFixedSalary,
perValue || 0, perValue || 0,
@@ -88,7 +89,7 @@ export const handler = safeHandler(async (
); );
// send email after transaction commits // send email after transaction commits
await sendInvitationEmailForMinglarAdmin(emailAddress); await sendInvitationEmailForMinglarAdmin(emailToLowerCase, config.AM_INVITATION_LINK);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,16 +1,15 @@
// modules/minglar/handlers/updateProfile.ts // modules/minglar/handlers/updateProfile.ts
import config from '@/config/config'; import config from '../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { parseJsonField, parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData'; import { parseJsonField, parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData';
import { MinglarService } from '../services/minglar.service'; import { MinglarService } from '../services/minglar.service';
const prismaService = new PrismaService(); const minglarService = new MinglarService(prismaClient);
const minglarService = new MinglarService(prismaService);
const s3 = new AWS.S3({ const s3 = new AWS.S3({
region: config.aws.region, region: config.aws.region,

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { sendAMEmailForHostAssign } from './AMEmail.service'; import { sendAMEmailForHostAssign } from './AMEmail.service';
@Injectable() @Injectable()
export class AMNotificationService { export class AMNotificationService {
constructor(private prisma: PrismaService) {} private prisma = prismaClient;
/** /**
* Fetch account manager email by id and send assignment email. * Fetch account manager email by id and send assignment email.

View File

@@ -1,8 +1,10 @@
import { brevoService } from "@/common/email/brevoApi"; import { brevoService } from "../../../common/email/brevoApi";
import ApiError from "@/common/utils/helper/ApiError"; import ApiError from "../../../common/utils/helper/ApiError";
import config from "../../../config/config";
export async function sendEmailToHostForApprovedApplication( export async function sendEmailToHostForApprovedApplication(
emailAddress: string, emailAddress: string,
name: string
): Promise<{ ): Promise<{
sent: boolean; sent: boolean;
// messageId: string // messageId: string
@@ -11,9 +13,11 @@ export async function sendEmailToHostForApprovedApplication(
const subject = "Approval for your application"; const subject = "Approval for your application";
const htmlContent = ` const htmlContent = `
<p>Dear Host,</p> <p>Dear ${name},</p>
<p>Congratulations, Your application to minglar admin has been approved.</p> <p>Congratulations, Your application to minglar admin has been approved.</p>
<p>You can start onboarding your activities through the host panel.</p> <p>You can start onboarding your activities through the host panel.</p>
<p> You can login to your account using the link below:<br/>
<strong>Link:</strong> ${config.HOST_LINK} </p>
<p>Best regards,<br/>Minglar Team</p> <p>Best regards,<br/>Minglar Team</p>
`; `;
@@ -70,3 +74,41 @@ export async function sendEmailToHostForMinglarApproval(
throw new ApiError(500, "Failed to send OTP to minglar admin via email."); throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
} }
} }
export async function sendAMPQQAcceptanceMailtoHost(
emailAddress: string,
name: string
): Promise<{
sent: boolean;
// messageId: string
}> {
const subject = "Approval for your activity onboarding application";
const htmlContent = `
<p>Dear ${name},</p>
<p>Congratulations, Your activity onboarding application to minglar admin has been approved.</p>
<p>You can start adding other details of your activity through the host panel.</p>
<p> You can login to your account using the link below:<br/>
<strong>Link:</strong> ${config.HOST_LINK} </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 minglar admin via email.");
}
}

View File

@@ -3,6 +3,7 @@ import ApiError from "@/common/utils/helper/ApiError";
export async function sendInvitationEmailForMinglarAdmin( export async function sendInvitationEmailForMinglarAdmin(
emailAddress: string, emailAddress: string,
link: string
): Promise<{ ): Promise<{
sent: boolean; sent: boolean;
// messageId: string // messageId: string
@@ -11,9 +12,19 @@ export async function sendInvitationEmailForMinglarAdmin(
const subject = "Minglar Admin: Your Team Invitation"; const subject = "Minglar Admin: Your Team Invitation";
const htmlContent = ` const htmlContent = `
<p>Dear,</p> <p>Hi there,</p>
<p>You are invited to join the Minglar Admin team. Please click the link below to create your account.</p> <p>We're excited to invite you to join the <strong>Minglar Admin Team</strong>!<br/>
<p>Best regards,<br/>Minglar Admin Team</p> Please use the link below to set up your account and get started.</p>
<p><strong>Access Your Invitation:</strong><br/>
<a href="${link}">${link}</a></p>
<p>If you have any questions or need assistance, feel free to reach out — were here to help.<br/>
We look forward to having you on board!</p>
<p>Welcome aboard!<br/>
<strong>The Minglar Admin Team</strong></p>
`; `;
try { try {

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import { brevoService } from "@/common/email/brevoApi"; import { brevoService } from "../../../common/email/brevoApi";
import ApiError from "@/common/utils/helper/ApiError"; import ApiError from "../../../common/utils/helper/ApiError";
import config from "../../../config/config";
export async function sendEmailToHostForRejectedApplication( export async function sendEmailToHostForRejectedApplication(
emailAddress: string, emailAddress: string,
@@ -38,6 +39,7 @@ export async function sendEmailToHostForRejectedApplication(
export async function sendAMRejectionMailtoHost( export async function sendAMRejectionMailtoHost(
emailAddress: string, emailAddress: string,
name: string
): Promise<{ ): Promise<{
sent: boolean; sent: boolean;
// messageId: string // messageId: string
@@ -46,11 +48,60 @@ export async function sendAMRejectionMailtoHost(
const subject = "Improvement of your application"; const subject = "Improvement of your application";
const htmlContent = ` const htmlContent = `
<p>Dear Host,</p> <p>Dear ${name},</p>
<p>Your account manager has made some suggestions on your application.<br/> <p> Your account manager has reviewed your application and provided some suggestions. <br/>
Please improve it and re-submit the application to onboard on minglar.</p> Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p>
<p>If you have any questions please contact to minglar admin.</p> <p> You may access your application using the link below:<br/>
<p>Best regards,<br/>Minglar Team</p> <strong>Link:</strong> ${config.HOST_LINK} </p>
<p> If you have any questions, please feel free to contact the Minglar Support Team. </p>
<p> Best regards,<br/>
<strong>Minglar Team</strong> </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 minglar admin via email.");
}
}
export async function sendAMPQQRejectionMailtoHost(
emailAddress: string,
name: string
): Promise<{
sent: boolean;
// messageId: string
}> {
const subject = "Improvement of your activity onboarding application";
const htmlContent = `
<p>Dear ${name},</p>
<p>Your account manager has reviewed your activity application and provided some suggestions.<br/>
Please make the necessary improvements and re-submit your activity application along with the pre-qualification answers to proceed with the onboarding process on Minglar.</p>
<p>You may access your activity onboarding application using the link below:<br/>
<strong>Link:</strong> ${config.HOST_LINK}</p>
<p>If you have any questions, please feel free to contact the Minglar Support Team.</p>
<p>Best regards,<br/>
<strong>Minglar Team</strong></p>
`; `;
try { try {

View File

@@ -1,10 +1,10 @@
import { PrismaService } from "../../../common/database/prisma.service"; import { PrismaClient } from '@prisma/client';
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
import moment from "moment"; import moment from "moment";
import config from "../../../config/config"; import config from "../../../config/config";
export class TokenService { export class TokenService {
constructor(private readonly prisma: PrismaService = new PrismaService()) {} constructor(private prisma: PrismaClient) { }
private generateToken( private generateToken(
user_xid: number, user_xid: number,

View File

@@ -1,13 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -20,7 +18,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const bankDetails = await prePopulateService.getAllBankDetails(); const bankDetails = await prePopulateService.getAllBankDetails();

View File

@@ -1,12 +1,11 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,

View File

@@ -1,13 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -20,7 +18,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await prePopulateService.getAllFrequencies(); const result = await prePopulateService.getAllFrequencies();

View File

@@ -1,13 +1,11 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -20,7 +18,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await prePopulateService.getAllPQQQuesAndAns(); const result = await prePopulateService.getAllPQQQuesAndAns();

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { PrismaService } from "../../../common/database/prisma.service"; import { prismaClient } from "../../../common/database/prisma.lambda.service";
import { verifyMinglarAdminHostToken } from "../../../common/middlewares/jwt/authForMinglarAdminHost";
import { safeHandler } from "../../../common/utils/handlers/safeHandler"; import { safeHandler } from "../../../common/utils/handlers/safeHandler";
import ApiError from "../../../common/utils/helper/ApiError"; import ApiError from "../../../common/utils/helper/ApiError";
import { PrePopulateService } from "../services/prepopulate.service"; import { PrePopulateService } from "../services/prepopulate.service";
import { verifyHostToken } from "@/common/middlewares/jwt/authForHost";
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -26,7 +25,7 @@ export const handler = safeHandler(async (
} }
// 2) Authenticate user // 2) Authenticate user
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
// 3) Get bankXid from query params // 3) Get bankXid from query params
const bankXid = Number(event.queryStringParameters?.bankXid); const bankXid = Number(event.queryStringParameters?.bankXid);

View File

@@ -1,12 +1,11 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { PrismaService } from "../../../common/database/prisma.service"; import { prismaClient } from "../../../common/database/prisma.lambda.service";
import { verifyMinglarAdminHostToken } from "../../../common/middlewares/jwt/authForMinglarAdminHost";
import { safeHandler } from "../../../common/utils/handlers/safeHandler"; import { safeHandler } from "../../../common/utils/handlers/safeHandler";
import ApiError from "../../../common/utils/helper/ApiError"; import ApiError from "../../../common/utils/helper/ApiError";
import { PrePopulateService } from "../services/prepopulate.service"; import { PrePopulateService } from "../services/prepopulate.service";
import { verifyHostToken } from "@/common/middlewares/jwt/authForHost";
const prismaService = new PrismaService(); const prePopulateService = new PrePopulateService(prismaClient);
const prePopulateService = new PrePopulateService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
@@ -26,7 +25,7 @@ export const handler = safeHandler(async (
} }
// 2) Authenticate user // 2) Authenticate user
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
// 3) Get bankXid from query params // 3) Get bankXid from query params
const stateXid = Number(event.queryStringParameters?.stateXid); const stateXid = Number(event.queryStringParameters?.stateXid);

View File

@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaClient } from '@prisma/client';
@Injectable() @Injectable()
export class PrePopulateService { export class PrePopulateService {
constructor(private prisma: PrismaService) { } constructor(private prisma: PrismaClient) { }
async getAllBankDetails() { async getAllBankDetails() {
return await this.prisma.banks.findMany({ return await this.prisma.banks.findMany({
@@ -11,15 +10,11 @@ export class PrePopulateService {
isActive: true, isActive: true,
deletedAt: null, deletedAt: null,
}, },
include: { select: {
BankBranches: { id: true,
select: { bankName: true,
id: true,
branchAddress: true,
ifscCode: true,
},
},
}, },
orderBy: { bankName: 'asc' }
}); });
} }
@@ -28,7 +23,6 @@ export class PrePopulateService {
where: { where: {
bankXid, bankXid,
isActive: true, isActive: true,
deletedAt: null
}, },
select: { select: {
id: true, id: true,
@@ -37,8 +31,8 @@ export class PrePopulateService {
} }
}); });
} }
async getCityByStateId(stateXid: number) { async getCityByStateId(stateXid: number) {
return await this.prisma.cities.findMany({ return await this.prisma.cities.findMany({
where: { where: {
@@ -60,37 +54,60 @@ export class PrePopulateService {
isActive: true, isActive: true,
deletedAt: null, deletedAt: null,
}, },
select: {
id: true,
currencyName: true,
currencySymbol: true,
},
orderBy: { currencyName: 'asc' }
}); });
} }
async getAllPQQQuesAndAns() { async getAllPQQQuesAndAns() {
return await this.prisma.pQQCategories.findMany({ return await this.prisma.pQQCategories.findMany({
where: { isActive: true }, where: { isActive: true },
include: { select: {
id: true,
categoryName: true,
displayOrder: true,
pqqsubCategories: { pqqsubCategories: {
include: { where: { isActive: true },
select: {
id: true,
subCategoryName: true,
categoryXid: true,
displayOrder: true,
questions: { questions: {
include: { where: { isActive: true },
select: {
id: true,
questionName: true,
maxPoints: true,
displayOrder: true,
PQQAnswers: { PQQAnswers: {
orderBy: { where: { isActive: true },
displayOrder: 'asc' orderBy: { displayOrder: 'asc' },
select: {
id: true,
answerName: true,
answerPoints: true,
displayOrder: true
} }
} }
}, },
orderBy: { orderBy: { displayOrder: 'asc' }
displayOrder: 'asc' },
}
}
}, },
orderBy: { displayOrder: 'asc' } orderBy: { displayOrder: 'asc' },
} },
}, },
orderBy: { displayOrder: 'asc' } orderBy: { displayOrder: 'asc' },
}); });
} }
async getAllDocumentTypeWithCountryStateCity() { async getAllDocumentTypeWithCountryStateCity() {
const [documentDetails, countryDetails, stateDetails] = const [documentDetails, countryDetails, stateDetails, companyTypeDetails] =
await this.prisma.$transaction([ await this.prisma.$transaction([
this.prisma.documentType.findMany({ this.prisma.documentType.findMany({
where: { isActive: true, isVisible: true }, where: { isActive: true, isVisible: true },
@@ -101,10 +118,15 @@ export class PrePopulateService {
}), }),
this.prisma.states.findMany({ this.prisma.states.findMany({
where: { isActive: true }, where: { isActive: true },
orderBy: { stateName: 'asc' }
}),
this.prisma.companyTypes.findMany({
where: { isActive: true },
orderBy: { companyTypeName: 'asc' }
}), }),
]); ]);
return { documentDetails, countryDetails, stateDetails }; return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
} }
async getAllFrequencies() { async getAllFrequencies() {