7 Commits

Author SHA1 Message Date
paritosh18
3937be710b Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into swagger 2025-12-22 13:35:24 +05:30
paritosh18
31c312eb3c Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into swagger 2025-12-10 11:40:51 +05:30
paritosh18
f6e01ac9e3 Update CURL example and enhance Swagger documentation for admin profile update 2025-12-08 16:58:30 +05:30
paritosh18
76970c914e Merge branch 'swagger' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into swagger 2025-12-08 12:27:39 +05:30
paritosh18
61737235a4 Update Swagger documentation: refine request/response schemas, enhance descriptions, and adjust parameter requirements 2025-12-06 13:13:56 +05:30
paritosh18
844bbf9618 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into swagger 2025-12-06 12:37:19 +05:30
paritosh18
cdae23ec6c Add Swagger documentation and handlers for API endpoints
- Created swagger.yml to define Swagger UI and JSON endpoints.
- Implemented swagger.ts to serve Swagger UI HTML and JSON specifications.
- Updated swagger.json with detailed API documentation, including paths, components, and schemas for various requests and responses.
2025-12-05 20:35:46 +05:30
101 changed files with 4215 additions and 16232 deletions

View File

@@ -1,7 +1,7 @@
# CURL Command for Testing addCompanyDetails Lambda # CURL Command for Testing addCompanyDetails Lambda
## Prerequisites ## Prerequisites
1. Replace `YOUR_API_URL` with your actual API Gateway URL 1. Replace `YOUR_API_URL` with your actual API Gateway URL
2. Replace `YOUR_AUTH_TOKEN` with a valid JWT token 2. Replace `YOUR_AUTH_TOKEN` with a valid JWT token
3. Replace file paths with actual document files on your system 3. Replace file paths with actual document files on your system

62
Dockerfile Normal file
View File

@@ -0,0 +1,62 @@
# Multi-stage build for NestJS Serverless Application
FROM node:18-alpine AS builder
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY . .
# Generate Prisma client
RUN npx prisma generate
# Build the application
RUN npm run build
# Production stage
FROM node:18-alpine AS production
# Set working directory
WORKDIR /app
# Install serverless framework globally
RUN npm install -g serverless
# Copy package files
COPY package*.json ./
# Install production dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
# Copy serverless configuration
COPY serverless*.yml ./
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001
# Change ownership
RUN chown -R nestjs:nodejs /app
USER nestjs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start the application
CMD ["npm", "run", "start:prod"]

86
docker-compose.yml Normal file
View File

@@ -0,0 +1,86 @@
version: '3.8'
services:
# PostgreSQL Database
postgres:
image: postgres:15-alpine
container_name: nestjs-postgres
restart: unless-stopped
environment:
POSTGRES_DB: nestjs_user_crud
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- nestjs-network
# Redis for caching (optional)
redis:
image: redis:7-alpine
container_name: nestjs-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- nestjs-network
# NestJS Application
app:
build:
context: .
dockerfile: Dockerfile
target: production
container_name: nestjs-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/nestjs_user_crud?schema=public
JWT_SECRET: docker-jwt-secret-key
JWT_EXPIRES_IN: 7d
API_PREFIX: api/v1
API_VERSION: 1.0.0
THROTTLE_TTL: 60
THROTTLE_LIMIT: 10
CORS_ORIGIN: http://localhost:3000
depends_on:
- postgres
- redis
networks:
- nestjs-network
volumes:
- ./src:/app/src
- ./prisma:/app/prisma
# Prisma Studio
prisma-studio:
image: node:18-alpine
container_name: nestjs-prisma-studio
restart: unless-stopped
ports:
- "5555:5555"
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/nestjs_user_crud?schema=public
working_dir: /app
volumes:
- .:/app
command: sh -c "npm install && npx prisma generate && npx prisma studio --hostname 0.0.0.0"
depends_on:
- postgres
networks:
- nestjs-network
volumes:
postgres_data:
redis_data:
networks:
nestjs-network:
driver: bridge

19
init.sql Normal file
View File

@@ -0,0 +1,19 @@
-- Initialize database for NestJS Serverless Application
-- This file is executed when the PostgreSQL container starts
-- Create database if it doesn't exist
SELECT 'CREATE DATABASE nestjs_user_crud'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'nestjs_user_crud')\gexec
-- Connect to the database
\c nestjs_user_crud;
-- Create extensions if needed
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Set timezone
SET timezone = 'UTC';
-- Create a user for the application (optional)
-- CREATE USER nestjs_user WITH PASSWORD 'nestjs_password';
-- GRANT ALL PRIVILEGES ON DATABASE nestjs_user_crud TO nestjs_user;

View File

@@ -1,53 +0,0 @@
import { PrismaClient } from '@prisma/client';
import fs from 'fs';
import path from 'path';
const prisma = new PrismaClient();
async function insertCities() {
try {
const statesFolder = path.join(process.cwd(), 'states-cities');
const files = fs.readdirSync(statesFolder);
for (const file of files) {
if (!file.endsWith('.json')) continue;
const stateName = file.replace('.json', '');
const state = await prisma.states.findFirst({
where: { stateName },
});
if (!state) {
console.log(`❌ State not found: ${stateName}`);
continue;
}
const filePath = path.join(statesFolder, file);
const citiesData = JSON.parse(
fs.readFileSync(filePath, 'utf-8')
);
await prisma.cities.createMany({
data: citiesData.map((city) => ({
stateXid: state.id,
cityName:
typeof city === 'string'
? city.trim()
: city.cityName.trim(),
})),
skipDuplicates: true,
});
console.log(`${stateName} cities inserted`);
}
console.log('🎉 All cities inserted successfully');
} catch (error) {
console.error('Error inserting cities:', error);
} finally {
await prisma.$disconnect();
}
}
insertCities();

View File

@@ -15,21 +15,23 @@
} }
}, },
"node_modules/@prisma/adapter-pg": { "node_modules/@prisma/adapter-pg": {
"version": "7.2.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.0.1.tgz",
"integrity": "sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==", "integrity": "sha512-01GpPPhLMoDMF4ipgfZz0L87fla/TV/PBQcmHy+9vV1ml6gUoqF8dUIRNI5Yf2YKpOwzQg9sn8C7dYD1Yio9Ug==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/driver-adapter-utils": "7.2.0", "@prisma/driver-adapter-utils": "7.0.1",
"pg": "^8.16.3", "pg": "^8.16.3",
"postgres-array": "3.0.4" "postgres-array": "3.0.4"
} }
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "7.2.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz",
"integrity": "sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==", "integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/client-runtime-utils": "7.2.0" "@prisma/client-runtime-utils": "7.0.1"
}, },
"engines": { "engines": {
"node": "^20.19 || ^22.12 || >=24.0" "node": "^20.19 || ^22.12 || >=24.0"
@@ -48,27 +50,31 @@
} }
}, },
"node_modules/@prisma/client-runtime-utils": { "node_modules/@prisma/client-runtime-utils": {
"version": "7.2.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz",
"integrity": "sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA==" "integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==",
"license": "Apache-2.0"
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "7.2.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz",
"integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==" "integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==",
"license": "Apache-2.0"
}, },
"node_modules/@prisma/driver-adapter-utils": { "node_modules/@prisma/driver-adapter-utils": {
"version": "7.2.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.0.1.tgz",
"integrity": "sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==", "integrity": "sha512-sBbxm/yysHLLF2iMAB+qcX/nn3WFgsiC4DQNz0uM6BwGSIs8lIvgo0u8nR9nxe5gvFgKiIH8f4z2fgOEMeXc8w==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.2.0" "@prisma/debug": "7.0.1"
} }
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.16.3", "version": "8.16.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.9.1", "pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1", "pg-pool": "^3.10.1",
@@ -95,17 +101,20 @@
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
"license": "MIT",
"optional": true "optional": true
}, },
"node_modules/pg-connection-string": { "node_modules/pg-connection-string": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
"license": "MIT"
}, },
"node_modules/pg-int8": { "node_modules/pg-int8": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
} }
@@ -114,6 +123,7 @@
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"pg": ">=8.0" "pg": ">=8.0"
} }
@@ -121,12 +131,14 @@
"node_modules/pg-protocol": { "node_modules/pg-protocol": {
"version": "1.10.3", "version": "1.10.3",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
"license": "MIT"
}, },
"node_modules/pg-types": { "node_modules/pg-types": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": { "dependencies": {
"pg-int8": "1.0.1", "pg-int8": "1.0.1",
"postgres-array": "~2.0.0", "postgres-array": "~2.0.0",
@@ -142,6 +154,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@@ -150,6 +163,7 @@
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": { "dependencies": {
"split2": "^4.1.0" "split2": "^4.1.0"
} }
@@ -158,14 +172,16 @@
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
"integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/postgres-bytea": { "node_modules/postgres-bytea": {
"version": "1.0.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -174,6 +190,7 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -182,6 +199,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"xtend": "^4.0.0" "xtend": "^4.0.0"
}, },
@@ -193,6 +211,7 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": { "engines": {
"node": ">= 10.x" "node": ">= 10.x"
} }
@@ -201,14 +220,16 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "4.2.1", "version": "4.1.13",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }

561
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@
"@aws-crypto/sha256-browser": "^5.2.0", "@aws-crypto/sha256-browser": "^5.2.0",
"@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/sha256-js": "^5.2.0",
"@aws-sdk/client-s3": "^3.928.0", "@aws-sdk/client-s3": "^3.928.0",
"@aws-sdk/s3-request-presigner": "^3.928.0", "@aws-sdk/s3-request-presigner": "^3.310.0",
"@aws/lambda-invoke-store": "^0.2.1", "@aws/lambda-invoke-store": "^0.2.1",
"@nestjs/common": "^10.3.0", "@nestjs/common": "^10.3.0",
"@nestjs/config": "^3.1.1", "@nestjs/config": "^3.1.1",
@@ -48,27 +48,18 @@
"@types/http-status": "^1.1.2", "@types/http-status": "^1.1.2",
"ajv": "8.12.0", "ajv": "8.12.0",
"aws-lambda": "^1.0.7", "aws-lambda": "^1.0.7",
"aws-sdk": "^2.1692.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dayjs": "^1.11.19",
"docx": "^9.6.0",
"docxtemplater": "^3.68.3",
"fast-xml-parser": "^5.3.1", "fast-xml-parser": "^5.3.1",
"fs": "^0.0.1-security",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"http-status": "^2.1.0", "http-status": "^2.1.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"number-to-words": "^1.2.4",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"path": "^0.12.7",
"pdf-lib": "^1.17.1",
"pizzip": "^3.2.0",
"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",
@@ -99,9 +90,9 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"serverless-esbuild": "^1.55.1", "serverless-esbuild": "^1.55.1",
"serverless-offline": "^14.4.0", "serverless-offline": "^14.4.0",
"serverless-plugin-split-stacks": "^1.14.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"supertest": "^6.3.4", "supertest": "^6.3.4",
"swagger-ui-express": "^5.0.1",
"ts-jest": "^29.1.2", "ts-jest": "^29.1.2",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",

View File

@@ -1,64 +0,0 @@
import fs from 'fs'
import path from 'path'
import { PrismaClient } from '@prisma/client'
export async function seedCities(prisma: PrismaClient) {
const statesFolder = path.join(process.cwd(), 'states-cities')
const files = fs.readdirSync(statesFolder)
for (const file of files) {
if (!file.endsWith('.json')) continue
const stateName = file.replace('.json', '')
const state = await prisma.states.findFirst({
where: {
stateName: {
equals: stateName,
mode: 'insensitive',
},
},
})
if (!state) {
console.log(`❌ State not found: ${stateName}`)
continue
}
const filePath = path.join(statesFolder, file)
const rawData = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
if (!rawData.districts) {
console.log(`❌ Invalid structure in ${file}`)
continue
}
const allVillages: string[] = []
for (const district of rawData.districts) {
for (const sub of district.subDistricts || []) {
for (const village of sub.villages || []) {
if (village && village.trim()) {
allVillages.push(village.trim())
}
}
}
}
console.log(`📦 Total villages found in ${stateName}:`, allVillages.length)
const result = await prisma.cities.createMany({
data: allVillages.map((village) => ({
stateXid: state.id,
cityName: village,
})),
skipDuplicates: true,
})
console.log(`${stateName} inserted: ${result.count}`)
}
console.log('🎉 All states processed successfully!')
}

View File

@@ -1,8 +1,7 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Lambda Node 18/20 (Amazon Linux) target binaryTargets = ["native", "rhel-openssl-3.0.x"] // Add Linux target
previewFeatures = ["multiSchema"] previewFeatures = ["multiSchema"]
engineType = "library"
} }
datasource db { datasource db {
@@ -17,13 +16,12 @@ model User {
lastName String? @map("last_name") @db.VarChar(50) lastName String? @map("last_name") @db.VarChar(50)
roleXid Int? @map("role_xid") roleXid Int? @map("role_xid")
dateOfBirth DateTime? @map("date_of_birth") dateOfBirth DateTime? @map("date_of_birth")
genderName String? @map("gender_name") @db.VarChar(20)
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict) role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
emailAddress String? @unique @map("email_address") @db.VarChar(150) emailAddress String @unique @map("email_address") @db.VarChar(150)
isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc. isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc.
mobileNumber String? @unique @map("mobile_number") @db.VarChar(15) // international safe limit mobileNumber String? @map("mobile_number") @db.VarChar(15) // international safe limit
userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords
userPasscode String? @map("user_passcode") @db.VarChar(255) // 46 digit passcode userPasscode String? @map("user_passcode") @db.VarChar(10) // 46 digit passcode
profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL
userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444" userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444"
userLong String? @map("user_long") @db.VarChar(20) userLong String? @map("user_long") @db.VarChar(20)
@@ -31,7 +29,7 @@ model User {
isEmailVerfied Boolean? @default(false) @map("is_email_verified") isEmailVerfied Boolean? @default(false) @map("is_email_verified")
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified") isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
isProfileUpdated Boolean? @default(false) @map("is_profile_updated") isProfileUpdated Boolean? @default(false) @map("is_profile_updated")
userRefNumber String? @unique @map("user_ref_number") @db.VarChar(20) userRefNumber String? @map("user_ref_number") @db.VarChar(20)
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")
@@ -71,11 +69,9 @@ model User {
activityTracks ActivityTrack[] activityTracks ActivityTrack[]
// 🔹 Activities created by this user // 🔹 Activities created by this user
createdActivities Activities[] @relation("UserActivities") createdActivities Activities[] @relation("UserActivities")
userBucketInterests UserBucketInterested[]
// 🔹 Activities where this user is Account Manager // 🔹 Activities where this user is Account Manager
managedActivities Activities[] @relation("ActivityAccountManager") managedActivities Activities[] @relation("ActivityAccountManager")
activitySortings ActivitySorting[]
@@map("users") @@map("users")
@@schema("usr") @@schema("usr")
@@ -177,35 +173,16 @@ model UserRevenue {
@@schema("usr") @@schema("usr")
} }
model ActivitySorting {
id Int @id @default(autoincrement())
userXid Int @map("user_xid")
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
activitySortXid Int @map("activity_sort_xid")
activitySort ActivitySortFilter @relation(fields: [activitySortXid], references: [id], onDelete: Restrict)
sortOrder String @map("sort_order") @db.VarChar(20) // "asc", "desc"
filterValue String @map("filter_value") @db.VarChar(50) // e.g. "frequency", "created_at", "activity_date"
displayOrder Int @map("display_order")
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")
@@unique([userXid, activitySortXid])
@@map("activity_sorting")
@@schema("usr")
}
model ConnectDetails { model ConnectDetails {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
userXid Int @map("user_xid") userXid Int @map("user_xid")
user User @relation(fields: [userXid], references: [id], onDelete: Cascade) user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
schoolCompanyXid Int @map("school_company_xid") connectXid Int @map("connect_xid")
schoolCompany SchoolCompany @relation(fields: [schoolCompanyXid], references: [id], onDelete: Restrict) connect Connections @relation(fields: [connectXid], references: [id], onDelete: Cascade)
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
@@map("connect_details") @@map("connect_details")
@@schema("usr") @@schema("usr")
@@ -241,40 +218,6 @@ model UserInterests {
@@schema("usr") @@schema("usr")
} }
model UserBucketInterested {
id Int @id @default(autoincrement())
userXid Int @map("user_xid")
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
isBucket Boolean @default(true) @map("is_bucket")
activityXid Int @map("activity_xid")
Activities Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
bucketTypeName String? @map("bucket_type_name") @db.VarChar(20) // "want_to_do", "tried_and_loved", "tried_and_disliked"
activityStatus String? @map("activity_status") @db.VarChar(20) // "pending", "completed", "removed"
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("user_bucket_interested")
@@schema("usr")
}
model SchoolCompany {
id Int @id @default(autoincrement())
schoolCompanyName String @map("school_company_name") @db.VarChar(255)
isSchool Boolean @map("is_school")
cityXid Int @map("city_xid")
cities Cities @relation(fields: [cityXid], references: [id], onDelete: Restrict)
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")
connectDetails ConnectDetails[]
@@map("school_company")
@@schema("mst")
}
model Countries { model Countries {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
countryName String @unique @map("country_name") @db.VarChar(50) countryName String @unique @map("country_name") @db.VarChar(50)
@@ -291,9 +234,6 @@ model Countries {
HostHeader HostHeader[] HostHeader HostHeader[]
hostParent HostParent[] hostParent HostParent[]
userAddressDetails UserAddressDetails[] userAddressDetails UserAddressDetails[]
// 🔹 Activity relations
checkInActivities Activities[] @relation("CheckInCountry")
checkOutActivities Activities[] @relation("CheckOutCountry")
@@map("countries") @@map("countries")
@@schema("mst") @@schema("mst")
@@ -331,9 +271,6 @@ model States {
HostHeader HostHeader[] HostHeader HostHeader[]
hostParent HostParent[] hostParent HostParent[]
userAddressDetails UserAddressDetails[] userAddressDetails UserAddressDetails[]
// 🔹 Activity relations
checkInActivities Activities[] @relation("CheckInState")
checkOutActivities Activities[] @relation("CheckOutState")
@@map("states") @@map("states")
@@schema("mst") @@schema("mst")
@@ -343,7 +280,7 @@ model Cities {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
stateXid Int @map("state_xid") stateXid Int @map("state_xid")
states States @relation(fields: [stateXid], references: [id], onDelete: Cascade) states States @relation(fields: [stateXid], references: [id], onDelete: Cascade)
cityName String @map("city_name") @db.VarChar(50) cityName String @unique @map("city_name") @db.VarChar(50)
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")
@@ -352,12 +289,7 @@ model Cities {
HostHeader HostHeader[] HostHeader HostHeader[]
hostParent HostParent[] hostParent HostParent[]
userAddressDetails UserAddressDetails[] userAddressDetails UserAddressDetails[]
// 🔹 Activity relations
checkInActivities Activities[] @relation("CheckInCity")
checkOutActivities Activities[] @relation("CheckOutCity")
schoolCompanies SchoolCompany[]
@@unique([stateXid, cityName])
@@map("cities") @@map("cities")
@@schema("mst") @@schema("mst")
} }
@@ -400,21 +332,6 @@ model Banks {
@@schema("mst") @@schema("mst")
} }
model ActivitySortFilter {
id Int @id @default(autoincrement())
sortFilterName String @map("sort_filter_name") @db.VarChar(50)
isSort Boolean @default(false) @map("is_sort")
displayOrder Int @map("display_order")
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")
activitySortings ActivitySorting[]
@@map("activity_sort_filter")
@@schema("mst")
}
model BankBranches { model BankBranches {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
bankXid Int @map("bank_xid") bankXid Int @map("bank_xid")
@@ -438,9 +355,6 @@ model BankBranches {
model Interests { model Interests {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
interestName String @unique @map("interest_name") @db.VarChar(50) interestName String @unique @map("interest_name") @db.VarChar(50)
interestColor String @map("interest_color") @db.VarChar(20)
interestImage String @map("interest_image") @db.VarChar(500)
interestCode String @unique @map("interest_code") @db.VarChar(10)
displayOrder Int @map("display_order") displayOrder Int @map("display_order")
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")
@@ -457,9 +371,7 @@ model ActivityTypes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
interestXid Int @map("interest_xid") interestXid Int @map("interest_xid")
interests Interests @relation(fields: [interestXid], references: [id], onDelete: Restrict) interests Interests @relation(fields: [interestXid], references: [id], onDelete: Restrict)
activityTypeName String @unique @map("activity_type_name") @db.VarChar(50) activityTypeName String @unique @map("activity_type_name") @db.VarChar(30)
energyLevelXid Int @map("energy_level_xid")
energyLevel EnergyLevels @relation(fields: [energyLevelXid], references: [id], onDelete: Restrict)
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")
@@ -517,7 +429,6 @@ model CompanyTypes {
model Amenities { model Amenities {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
amenitiesName String @unique @map("amenities_name") @db.VarChar(30) amenitiesName String @unique @map("amenities_name") @db.VarChar(30)
amenitiesIcon String @map("amenities_icon") @db.VarChar(500)
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")
@@ -555,6 +466,20 @@ model Frequencies {
@@schema("mst") @@schema("mst")
} }
model NavigationModes {
id Int @id @default(autoincrement())
navigationModeName String @unique @map("navigation_mode_name") @db.VarChar(30)
navigationModeIcon String @map("navigation_mode_icon") @db.VarChar(500)
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")
ActivityNavigationModes ActivityNavigationModes[]
@@map("navigation_modes")
@@schema("mst")
}
model TransportModes { model TransportModes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
transportModeName String @unique @map("transport_mode_name") @db.VarChar(60) transportModeName String @unique @map("transport_mode_name") @db.VarChar(60)
@@ -636,15 +561,15 @@ model PQQAnswers {
} }
model AgeRestrictions { model AgeRestrictions {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
ageRestrictionName String @unique @map("age_restriction_name") @db.VarChar(30) ageRestrictionName String @unique @map("age_restriction_name") @db.VarChar(30)
minAge Int @map("min_age") minAge Int @map("min_age")
maxAge Int @map("max_age") maxAge Int @map("max_age")
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
// ActivityEligibility ActivityEligibility[] ActivityEligibility ActivityEligibility[]
@@map("age_restrictions") @@map("age_restrictions")
@@schema("mst") @@schema("mst")
@@ -677,32 +602,32 @@ model Roles {
} }
model Connections { model Connections {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
connectionType String @unique @map("connection_type") @db.VarChar(30) connectionType String @unique @map("connection_type") @db.VarChar(30)
connectionName String @map("connection_name") @db.VarChar(30) connectionName String @map("connection_name") @db.VarChar(30)
locationDetails String @map("location_details") @db.VarChar(100) locationDetails String @map("location_details") @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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
User User[] User User[]
connectDetails ConnectDetails[]
@@map("connections") @@map("connections")
@@schema("mst") @@schema("mst")
} }
model EnergyLevels { model EnergyLevels {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
energyLevelName String @unique @map("energy_level_name") @db.VarChar(30) energyLevelName String @map("energy_level_name") @db.VarChar(30)
energyIcon String @map("energy_icon") @db.VarChar(400) energyIcon String @map("energy_icon") @db.VarChar(400)
energyColor String @map("energy_color") @db.VarChar(20) energyColor String @map("energy_color") @db.VarChar(20)
displayOrder Int @map("display_order") 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") deletedAt DateTime? @map("deleted_at")
deletedAt DateTime? @map("deleted_at")
User User[] User User[]
activityTypes ActivityTypes[] Activities Activities[]
@@map("energy_levels") @@map("energy_levels")
@@schema("mst") @@schema("mst")
@@ -790,7 +715,6 @@ model HostHeader {
hostParent HostParent[] hostParent HostParent[]
HostTrack HostTrack[] HostTrack HostTrack[]
Activities Activities[] Activities Activities[]
hostAgreements HostAgreement[]
@@map("host_header") @@map("host_header")
@@schema("hst") @@schema("hst")
@@ -835,26 +759,11 @@ model HostDocuments {
@@schema("hst") @@schema("hst")
} }
model HostAgreement {
id Int @id @default(autoincrement())
hostXid Int @map("host_xid")
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
filePath String @map("file_path") @db.VarChar(400)
versionNumber String @map("version_number") @db.VarChar(20)
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("host_agreement")
@@schema("hst")
}
model HostSuggestion { model HostSuggestion {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
hostXid Int @map("host_xid") hostXid Int @map("host_xid")
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade) host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
title String @map("title") @db.VarChar(50) title String @map("title") @db.VarChar(20)
comments String @map("comments") @db.VarChar(200) comments String @map("comments") @db.VarChar(200)
isparent Boolean @default(false) @map("is_parent") isparent Boolean @default(false) @map("is_parent")
isreviewed Boolean @default(false) @map("is_reviewed") isreviewed Boolean @default(false) @map("is_reviewed")
@@ -943,53 +852,41 @@ model HostTrack {
// ACTIVITY MODELS // ACTIVITY MODELS
model Activities { model Activities {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
hostXid Int @map("host_xid") hostXid Int @map("host_xid")
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade) host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
activityTypeXid Int @map("activity_type_xid") activityTypeXid Int @map("activity_type_xid")
activityType ActivityTypes @relation(fields: [activityTypeXid], references: [id], onDelete: Restrict) activityType ActivityTypes @relation(fields: [activityTypeXid], references: [id], onDelete: Restrict)
frequenciesXid Int? @map("frequencies_xid") frequenciesXid Int? @map("frequencies_xid")
frequency Frequencies? @relation(fields: [frequenciesXid], references: [id], onDelete: Restrict) frequency Frequencies? @relation(fields: [frequenciesXid], references: [id], onDelete: Restrict)
activityRefNumber String? @map("activity_ref_number") @db.VarChar(30) activityRefNumber String? @map("activity_ref_number") @db.VarChar(30)
activityTitle String? @map("activity_title") @db.VarChar(150) activityTitle String? @map("activity_title") @db.VarChar(30)
activityDescription String? @map("activity_description") @db.VarChar(2000) activityDescription String? @map("activity_description") @db.VarChar(255)
checkInLat Float? @map("check_in_lat") checkInLat Float? @map("check_in_lat")
checkInLong Float? @map("check_in_long") checkInLong Float? @map("check_in_long")
checkInAddress String? @map("check_in_address") @db.VarChar(150) checkInAddress String? @map("check_in_address") @db.VarChar(150)
isCheckOutSame Boolean? @default(true) @map("is_check_out_same") isCheckOutSame Boolean? @default(true) @map("is_check_out_same")
checkOutLat Float? @map("check_out_lat") checkOutLat Float? @map("check_out_lat")
checkOutLong Float? @map("check_out_long") checkOutLong Float? @map("check_out_long")
checkOutAddress String? @map("check_out_address") @db.VarChar(150) checkOutAddress String? @map("check_out_address") @db.VarChar(150)
set_early_checkin_time_mins String @default("null") @map("set_early_checkin_time_mins") @db.VarChar(200) energyLevelXid Int? @map("energy_level_xid")
activityDurationMins Int? @map("activity_duration_mins") energyLevel EnergyLevels? @relation(fields: [energyLevelXid], references: [id], onDelete: Restrict)
foodAvailable Boolean? @map("food_available") activityDurationMins Int? @map("activity_duration_mins")
foodIsChargeable Boolean? @map("food_is_chargeable") foodAvailable Boolean? @default(false) @map("food_available")
alcoholAvailable Boolean? @map("alcohol_available") foodIsChargeable Boolean? @default(false) @map("food_is_chargeable")
trainerAvailable Boolean? @map("trainer_available") alcoholAvailable Boolean? @default(false) @map("alcohol_available")
trainerIsChargeable Boolean? @map("trainer_is_chargeable") trainerAvailable Boolean? @default(false) @map("trainer_available")
pickUpDropAvailable Boolean? @map("pick_up_drop_available") trainerIsChargeable Boolean? @default(false) @map("trainer_is_chargeable")
pickUpDropIsChargeable Boolean? @map("pick_up_drop_is_chargeable") pickUpDropAvailable Boolean? @default(false) @map("pick_up_drop_available")
inActivityAvailable Boolean? @map("in_activity_available") pickUpDropIsChargeable Boolean? @default(false) @map("pick_up_drop_is_chargeable")
inActivityIsChargeable Boolean? @map("in_activity_is_chargeable") inActivityAvailable Boolean? @default(false) @map("in_activity_available")
isLateCheckingAllowed Boolean? @map("is_late_checking_allowed") inActivityIsChargeable Boolean? @default(false) @map("in_activity_is_chargeable")
equipmentAvailable Boolean? @map("equipment_available") equipmentAvailable Boolean? @default(false) @map("equipment_available")
equipmentIsChargeable Boolean? @map("equipment_is_chargeable") equipmentIsChargeable Boolean? @default(false) @map("equipment_is_chargeable")
cancellationAvailable Boolean? @map("cancellation_available") cancellationAvailable Boolean? @default(false) @map("cancellation_available")
checkInStateXid Int? @map("check_in_state_xid")
checkInState States? @relation("CheckInState", fields: [checkInStateXid], references: [id], onDelete: Restrict)
checkInCityXid Int? @map("check_in_city_xid")
checkInCity Cities? @relation("CheckInCity", fields: [checkInCityXid], references: [id], onDelete: Restrict)
checkInCountryXid Int? @map("check_in_country_xid")
checkInCountry Countries? @relation("CheckInCountry", fields: [checkInCountryXid], references: [id], onDelete: Restrict)
checkOutStateXid Int? @map("check_out_state_xid")
checkOutState States? @relation("CheckOutState", fields: [checkOutStateXid], references: [id], onDelete: Restrict)
checkOutCityXid Int? @map("check_out_city_xid")
checkOutCity Cities? @relation("CheckOutCity", fields: [checkOutCityXid], references: [id], onDelete: Restrict)
checkOutCountryXid Int? @map("check_out_country_xid")
checkOutCountry Countries? @relation("CheckOutCountry", fields: [checkOutCountryXid], references: [id], onDelete: Restrict)
// 🔹 Creator / owner // 🔹 Creator / owner
userId Int? userId Int?
user User? @relation("UserActivities", fields: [userId], references: [id]) user User? @relation("UserActivities", fields: [userId], references: [id])
// 🔹 Account Manager // 🔹 Account Manager
accountManagerXid Int? accountManagerXid Int?
@@ -1029,7 +926,6 @@ model Activities {
activityFoodTypes ActivityFoodTypes[] activityFoodTypes ActivityFoodTypes[]
activityCuisines ActivityCuisine[] activityCuisines ActivityCuisine[]
activityPickUpTransports ActivityPickUpTransport[] activityPickUpTransports ActivityPickUpTransport[]
userBucketInterests UserBucketInterested[]
@@map("activities") @@map("activities")
@@schema("act") @@schema("act")
@@ -1039,13 +935,11 @@ model ActivityOtherDetails {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
exclusiveNotes String? @map("exclusive_notes") @db.Text exclusiveNotes String? @map("exclusive_notes") @db.VarChar(500)
SafetyInstruction String? @map("safety_instruction") @db.Text dosNotes String? @map("dos_notes") @db.VarChar(200)
Cancellations String? @map("cancellations") @db.Text dontsNotes String? @map("donts_notes") @db.VarChar(200)
dosNotes String? @map("dos_notes") @db.Text tipsNotes String? @map("tips_notes") @db.VarChar(100)
dontsNotes String? @map("donts_notes") @db.Text termsAndCondition String? @map("terms_and_condition") @db.VarChar(500)
tipsNotes String? @map("tips_notes") @db.Text
termsAndCondition String? @map("terms_and_condition") @db.Text
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")
@@ -1080,7 +974,6 @@ model ActivitiesMedia {
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
mediaType String @map("media_type") @db.VarChar(30) mediaType String @map("media_type") @db.VarChar(30)
mediaFileName String @map("media_file_name") @db.VarChar(400) mediaFileName String @map("media_file_name") @db.VarChar(400)
isCoverImage Boolean @default(false) @map("is_cover_image")
displayOrder Int @map("display_order") displayOrder Int @map("display_order")
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")
@@ -1092,25 +985,24 @@ model ActivitiesMedia {
} }
model ActivityVenues { model ActivityVenues {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
venueName String? @map("venue_name") @db.VarChar(50) venueName String @map("venue_name") @db.VarChar(50)
venueLabel String? @map("venue_label") @db.VarChar(70) venueCapacity Int @map("venue_capacity")
venueCapacity Int? @map("venue_capacity") availableSeats Int @map("available_seats")
availableSeats Int? @map("available_seats") isMinPeopleReqMandatory Boolean @default(false) @map("is_min_people_req_mandatory")
isMinPeopleReqMandatory Boolean @default(false) @map("is_min_people_req_mandatory") minPeopleRequired Int? @map("min_people_required")
minPeopleRequired Int? @map("min_people_required") minReqfullfilledBeforeMins Int? @map("min_req_fullfilled_before_mins")
minReqfullfilledBeforeMins Int? @map("min_req_fullfilled_before_mins") venueDescription String? @map("venue_description") @db.VarChar(200)
venueDescription String? @map("venue_description") @db.VarChar(400) 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") deletedAt DateTime? @map("deleted_at")
deletedAt DateTime? @map("deleted_at")
ScheduleHeader ScheduleHeader[] ScheduleHeader ScheduleHeader[]
ItineraryActivities ItineraryActivities[] ItineraryActivities ItineraryActivities[]
ActivityPrices ActivityPrices[] // <-- Added opposite relation ActivityPrices ActivityPrices[] // <-- Added opposite relation
ActivityVenueArtifacts ActivityVenueArtifacts[] // <-- Added opposite relation ActivityVenueArtifacts ActivityVenueArtifacts[] // <-- Added opposite relation
@@map("activity_venues") @@map("activity_venues")
@@schema("act") @@schema("act")
@@ -1150,35 +1042,28 @@ model ActivityTrainerTaxes {
} }
model ActivityEligibility { model ActivityEligibility {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
isAgeRestriction Boolean @default(false) @map("is_age_restriction") isAgeRestriction Boolean @default(false) @map("is_age_restriction")
// ageRestrictionXid Int? @map("age_restriction_xid") ageRestrictionXid Int? @map("age_restriction_xid")
// ageRestriction AgeRestrictions? @relation(fields: [ageRestrictionXid], references: [id], onDelete: Restrict) ageRestriction AgeRestrictions? @relation(fields: [ageRestrictionXid], references: [id], onDelete: Restrict)
ageRestrictionName String? @map("age_restriction_name") @db.VarChar(30) isWeightRestriction Boolean @default(false) @map("is_weight_restriction")
ageEntered Int? @map("age_entered") weightRestrictionName String? @map("weight_restriction_name") @db.VarChar(30)
ageIn String? @map("age_in") @db.VarChar(30) weightEntered Int? @map("weight_entered")
minAge Int? @map("min_age") weightIn String? @map("weight_in") @db.VarChar(30)
maxAge Int? @map("max_age") minWeight Int? @map("min_weight")
isWeightRestriction Boolean @default(false) @map("is_weight_restriction") maxWeight Int? @map("max_weight")
weightRestrictionName String? @map("weight_restriction_name") @db.VarChar(30) isHeightRestriction Boolean @default(false) @map("is_height_restriction")
weightEntered Int? @map("weight_entered") heightRestrictionName String? @map("height_restriction_name") @db.VarChar(30)
weightIn String? @map("weight_in") @db.VarChar(30) heightEntered Int? @map("height_entered")
minWeight Int? @map("min_weight") heightIn String? @map("height_in") @db.VarChar(30)
maxWeight Int? @map("max_weight") minHeight Int? @map("min_height")
isHeightRestriction Boolean @default(false) @map("is_height_restriction") maxHeight Int? @map("max_height")
heightRestrictionName String? @map("height_restriction_name") @db.VarChar(30) isActive Boolean @default(true) @map("is_active")
heightEntered Int? @map("height_entered") createdAt DateTime @default(now()) @map("created_at")
heightIn String? @map("height_in") @db.VarChar(30) updatedAt DateTime @updatedAt @map("updated_at")
minHeight Int? @map("min_height") deletedAt DateTime? @map("deleted_at")
maxHeight Int? @map("max_height")
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")
// ageRestrictions AgeRestrictions? @relation(fields: [ageRestrictionsId], references: [id])
// ageRestrictionsId Int?
@@map("activity_eligibility") @@map("activity_eligibility")
@@schema("act") @@schema("act")
@@ -1221,7 +1106,7 @@ model ActivityAmDetails {
model ActivityPrices { model ActivityPrices {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityVenueXid Int @map("activity_venue_xid") activityVenueXid Int @map("activity_venue_xid")
activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade) activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade)
noOfSession Int @map("no_of_session") noOfSession Int @map("no_of_session")
isPackage Boolean @default(false) @map("is_package") isPackage Boolean @default(false) @map("is_package")
sessionValidity Int @map("session_validity") sessionValidity Int @map("session_validity")
@@ -1256,15 +1141,15 @@ model ActivityPriceTaxes {
} }
model ActivityVenueArtifacts { model ActivityVenueArtifacts {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityVenueXid Int @map("activity_venue_xid") activityVenueXid Int @map("activity_venue_xid")
activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade) activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade)
mediaType String @map("media_type") @db.VarChar(30) mediaType String @map("media_type") @db.VarChar(30)
mediaFileName String @map("media_file_name") @db.VarChar(400) mediaFileName String @map("media_file_name") @db.VarChar(400)
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
@@map("activity_venue_artifacts") @@map("activity_venue_artifacts")
@@schema("act") @@schema("act")
@@ -1405,35 +1290,35 @@ model ActivityFoodTaxes {
} }
model ActivityEquipments { model ActivityEquipments {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
equipmentName String @map("equipment_name") @db.VarChar(30) equipmentName String @map("equipment_name") @db.VarChar(30)
isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable") isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable")
equipmentBasePrice Int @map("equipment_base_price") equipmentBasePrice Int @map("equipment_base_price")
equipmentTotalPrice Int @map("equipment_total_price") equipmentTotalPrice Int @map("equipment_total_price")
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
ActivityEquipmentTaxes ActivityEquipmentTaxes[] ActivityEquipmentTaxes ActivityEquipmentTaxes[]
@@map("activity_equipments") @@map("activity_equipments")
@@schema("act") @@schema("act")
} }
model ActivityEquipmentTaxes { model ActivityEquipmentTaxes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityEquipmentXid Int @map("activity_equipment_xid") activityEquipmentXid Int @map("activity_equipment_xid")
activityEquipment ActivityEquipments @relation(fields: [activityEquipmentXid], references: [id], onDelete: Cascade) activityEquipment ActivityEquipments @relation(fields: [activityEquipmentXid], references: [id], onDelete: Cascade)
taxXid Int @map("tax_xid") taxXid Int @map("tax_xid")
taxes Taxes @relation(fields: [taxXid], references: [id], onDelete: Restrict) taxes Taxes @relation(fields: [taxXid], references: [id], onDelete: Restrict)
taxPer Float @map("tax_per") taxPer Float @map("tax_per")
taxAmount Int @map("tax_amount") taxAmount Int @map("tax_amount")
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
@@map("activity_equipment_taxes") @@map("activity_equipment_taxes")
@@schema("act") @@schema("act")
@@ -1443,7 +1328,8 @@ model ActivityNavigationModes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
navigationModeName String @map("navigation_mode_name") @db.VarChar(30) navigationModeXid Int @map("navigation_mode_xid")
navigationMode NavigationModes @relation(fields: [navigationModeXid], references: [id], onDelete: Restrict)
isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable") isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable")
navigationModesBasePrice Int @map("navigation_modes_base_price") navigationModesBasePrice Int @map("navigation_modes_base_price")
navigationModesTotalPrice Int @map("navigation_modes_total_price") navigationModesTotalPrice Int @map("navigation_modes_total_price")
@@ -1476,8 +1362,8 @@ model ActivityNavigationModesTaxes {
model ActivityPickUpDetails { model ActivityPickUpDetails {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activities Activities? @relation(fields: [activitiesXid], references: [id]) activityPickUpTransportXid Int @map("activity_pick_up_transport_xid")
activitiesXid Int? @map("activity_xid") activityPickUpTransport ActivityPickUpTransport @relation(fields: [activityPickUpTransportXid], references: [id], onDelete: Cascade)
isPickUp Boolean @default(false) @map("is_pick_up") isPickUp Boolean @default(false) @map("is_pick_up")
locationLat Float? @map("location_lat") locationLat Float? @map("location_lat")
locationLong Float? @map("location_long") locationLong Float? @map("location_long")
@@ -1488,6 +1374,8 @@ model ActivityPickUpDetails {
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
activities Activities? @relation(fields: [activitiesId], references: [id])
activitiesId Int?
activityPickUpTransportTaxes ActivityPickUpTransportTaxes[] activityPickUpTransportTaxes ActivityPickUpTransportTaxes[]
@@map("activity_pick_up_details") @@map("activity_pick_up_details")
@@ -1495,15 +1383,18 @@ model ActivityPickUpDetails {
} }
model ActivityPickUpTransport { model ActivityPickUpTransport {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
transportModeXid Int @map("transport_mode_xid") transportModeXid Int @map("transport_mode_xid")
transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict) transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict)
isActive Boolean @default(true) @map("is_active") isTransportModeChargeable Boolean @default(false) @map("is_transport_mode_chargeable")
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")
pickupDetails ActivityPickUpDetails[]
@@map("activity_pick_up_transport") @@map("activity_pick_up_transport")
@@schema("act") @@schema("act")
@@ -1511,7 +1402,7 @@ model ActivityPickUpTransport {
model ActivityPickUpTransportTaxes { model ActivityPickUpTransportTaxes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityPickUpDetailsXid Int @map("activity_pick_up_xid") activityPickUpDetailsXid Int @map("activity_pick_up_details_xid")
activityPickUpDetails ActivityPickUpDetails @relation(fields: [activityPickUpDetailsXid], references: [id], onDelete: Cascade) activityPickUpDetails ActivityPickUpDetails @relation(fields: [activityPickUpDetailsXid], references: [id], onDelete: Cascade)
taxXid Int @map("tax_xid") taxXid Int @map("tax_xid")
taxes Taxes @relation(fields: [taxXid], references: [id], onDelete: Restrict) taxes Taxes @relation(fields: [taxXid], references: [id], onDelete: Restrict)
@@ -1551,11 +1442,9 @@ model ScheduleHeader {
activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade) activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade)
scheduleType String @map("schedule_type") @db.VarChar(30) scheduleType String @map("schedule_type") @db.VarChar(30)
startDate DateTime @map("start_date") startDate DateTime @map("start_date")
endDate DateTime? @map("end_date") endDate DateTime @map("end_date")
earlyCheckInMins Int? @map("early_check_in_mins") byWeekday Boolean @default(false) @map("by_weekday")
bookingCutOffMins Int? @map("booking_cut_off_mins") earlyCheckInMins Int @map("early_check_in_mins")
effectiveFromDt String? @map("effective_from_dt")
effectiveToDt String? @map("effective_to_dt")
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")
@@ -1563,8 +1452,6 @@ model ScheduleHeader {
ScheduleDetails ScheduleDetails[] ScheduleDetails ScheduleDetails[]
Cancellations Cancellations[] Cancellations Cancellations[]
ItineraryActivities ItineraryActivities[] ItineraryActivities ItineraryActivities[]
scheduleOccurences ScheduleOccurences[]
scheduleRecurrences ScheduleRecurrence[]
@@map("schedule_header") @@map("schedule_header")
@@schema("sch") @@schema("sch")
@@ -1574,12 +1461,8 @@ model ScheduleDetails {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
scheduleHeaderXid Int @map("schedule_header_xid") scheduleHeaderXid Int @map("schedule_header_xid")
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade) scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
occurenceDate DateTime? @map("occurence_date")
weekDay String? @map("week_day") @db.VarChar(30)
dayOfMonth Int? @map("day_of_month")
startTime String @map("start_time") @db.VarChar(30) startTime String @map("start_time") @db.VarChar(30)
endTime String @map("end_time") @db.VarChar(30) endTime String @map("end_time") @db.VarChar(30)
maxCapacity Int @map("max_capacity")
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")
@@ -1589,50 +1472,21 @@ model ScheduleDetails {
@@schema("sch") @@schema("sch")
} }
model ScheduleOccurences {
id Int @id @default(autoincrement())
scheduleHeaderXid Int @map("schedule_header_xid")
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
occurenceDate DateTime @map("occurence_date")
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("schedule_occurences")
@@schema("sch")
}
model ScheduleRecurrence {
id Int @id @default(autoincrement())
scheduleHeaderXid Int @map("schedule_header_xid")
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
weekDay String? @map("week_day") @db.VarChar(30)
dayOfMonth Int? @map("day_of_month")
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("schedule_recurrence")
@@schema("sch")
}
model Cancellations { model Cancellations {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
scheduleHeaderXid Int @map("schedule_header_xid") scheduleHeaderXid Int @map("schedule_header_xid")
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade) scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
occurenceDate DateTime? @map("occurence_date") occurenceDate DateTime @map("occurence_date")
startTime String? @map("start_time") @db.VarChar(30) startTime String @map("start_time") @db.VarChar(30)
endTime String? @map("end_time") @db.VarChar(30) endTime String @map("end_time") @db.VarChar(30)
cancellationReason String? @map("cancellation_reason") cancellationReason String @map("cancellation_reason")
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")
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
@@map("cancellations") @@map("cancellations")
@@schema("sch") @@schema("act")
} }
// ITINERARY MODELS // ITINERARY MODELS

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
service: minglar-host
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
functions:
- ${file(./serverless/functions/host.yml)}

View File

@@ -1,13 +0,0 @@
service: minglar-admin
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
functions:
- ${file(./serverless/functions/minglaradmin.yml)}

View File

@@ -1,13 +0,0 @@
service: minglar-prepopulate
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
functions:
- ${file(./serverless/functions/prepopulate.yml)}

View File

@@ -1,32 +0,0 @@
service: minglar-prisma-layer
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider:
name: aws
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
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

View File

@@ -1,13 +0,0 @@
service: minglar-user
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
functions:
- ${file(./serverless/functions/user.yml)}

View File

@@ -1,6 +1,4 @@
# Legacy monolith config. For new deployments use serverless.*.yml files. service: minglar
service: minglarDev
useDotenv: true useDotenv: true
@@ -17,11 +15,6 @@ provider:
runtime: nodejs22.x runtime: nodejs22.x
region: ap-south-1 region: ap-south-1
stage: ${opt:stage, 'dev'} stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false versionFunctions: false
memorySize: 512 memorySize: 512
# Apply Prisma layer to all functions # Apply Prisma layer to all functions
@@ -64,7 +57,6 @@ provider:
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL} MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK} AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK} HOST_LINK: ${env:HOST_LINK}
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
iam: iam:
role: role:
@@ -79,6 +71,12 @@ provider:
- '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:
serverless-offline:
reloadHandler: true
httpPort: 3000
noPrependStageInUrl: true
build: build:
esbuild: esbuild:
bundle: true bundle: true
@@ -147,18 +145,8 @@ 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/user.yml)} - ${file(./serverless/functions/pqq.yml)}
- ${file(./serverless/functions/swagger.yml)}
plugins: plugins:
- serverless-offline - serverless-offline
- serverless-plugin-split-stacks
custom:
serverless-offline:
reloadHandler: true
# split-stacks configuration to avoid CloudFormation resource limit
splitStacks:
perFunction: true
perType: true
perGroupFunction: false

View File

@@ -1,134 +0,0 @@
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider:
name: aws
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
memorySize: 512
# Apply Prisma layer to all functions
# Reference the layer defined in the dedicated layer stack
layers:
- ${cf:minglar-prisma-layer-${sls:stage}.PrismaLambdaLayerQualifiedArn}
apiGateway:
binaryMediaTypes:
- '*/*'
minimumCompressionSize: 1024
environment:
DATABASE_URL: ${env:DATABASE_URL}
DB_USERNAME: ${env:DB_USERNAME}
DB_PASSWORD: ${env:DB_PASSWORD}
DB_DATABASE_NAME: ${env:DB_DATABASE_NAME}
DB_HOSTNAME: ${env:DB_HOSTNAME}
DB_PORT: ${env:DB_PORT}
BY_PASS_EMAIL: ${env:BY_PASS_EMAIL}
BYPASS_OTP: ${env:BYPASS_OTP}
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
BREVO_API_BASEURL: ${env:BREVO_API_BASEURL}
BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL}
BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST}
BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT}
BREVO_SMTP_USER: ${env:BREVO_SMTP_USER}
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
JWT_SECRET: ${env:JWT_SECRET}
JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES}
JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS}
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES}
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES}
SALT_ROUNDS: ${env:SALT_ROUNDS}
NODE_ENV: ${env:NODE_ENV}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME}
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK}
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
iam:
role:
statements:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node20
platform: node
# Mark as external so they're not bundled into the JS
external:
- '@prisma/client'
- '.prisma/client'
- '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
# Exclude prevents npm install of these packages in the zip
exclude:
- 'aws-sdk'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
- '@prisma/adapter-pg'
- '@prisma/client'
- '.prisma'
- '.prisma/client'
- 'pg'
- 'zod'
- 'pg-*'
- 'postgres-*'
- 'pgpass'
- 'split2'
- 'xtend'
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'
- '!*.config.js'
- '!.git/**'
- '!.github/**'
plugins:
- serverless-offline
custom:
serverless-offline:
reloadHandler: true

View File

@@ -14,7 +14,7 @@ getHosts:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: / path: /host
method: get method: get
verifyOTP: verifyOTP:
@@ -30,7 +30,7 @@ verifyOTP:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/verify-otp path: /host/Host_Admin/onboarding/verify-otp
method: post method: post
login: login:
@@ -46,7 +46,7 @@ login:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/login path: /host/Host_Admin/onboarding/login
method: post method: post
signUp: signUp:
@@ -62,7 +62,7 @@ signUp:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/registration path: /host/Host_Admin/onboarding/registration
method: post method: post
createPassword: createPassword:
@@ -78,7 +78,7 @@ createPassword:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/create-password path: /host/Host_Admin/onboarding/create-password
method: post method: post
updateBankDetails: updateBankDetails:
@@ -94,7 +94,7 @@ updateBankDetails:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/add-payment-details path: /host/Host_Admin/onboarding/add-payment-details
method: post method: post
saveActivity_ForPQQ: saveActivity_ForPQQ:
@@ -110,7 +110,7 @@ saveActivity_ForPQQ:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/add-activity path: /host/Activity_Hub/OnBoarding/add-activity
method: post method: post
getHostById: getHostById:
@@ -126,7 +126,7 @@ getHostById:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /getById path: /host/getById
method: get method: get
getPQQ_ByQuestionId: getPQQ_ByQuestionId:
@@ -142,7 +142,7 @@ getPQQ_ByQuestionId:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/get-pqq-question-details path: /host/Activity_Hub/OnBoarding/get-pqq-question-details
method: get method: get
getPQQ_LastUpdatedQuestion: getPQQ_LastUpdatedQuestion:
@@ -158,7 +158,7 @@ getPQQ_LastUpdatedQuestion:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/get-latest-pqq-question-details path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
method: get method: get
prePopulateNewActivity: prePopulateNewActivity:
@@ -174,7 +174,7 @@ prePopulateNewActivity:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/prepopulate-new-activity path: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
method: get method: get
createNewActivity: createNewActivity:
@@ -191,7 +191,7 @@ createNewActivity:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/create-new-activity path: /host/Activity_Hub/OnBoarding/create-new-activity
method: patch method: patch
showSuggestion: showSuggestion:
@@ -207,23 +207,7 @@ showSuggestion:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-suggestion path: /host/get-suggestion
method: get
getAllActivitySuggestion:
handler: src/modules/host/handlers/Host_Admin/onboarding/getAllActvitySuggestion.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Host_Admin/onboarding/getAllActvitySuggestion.handler.*'
- 'src/modules/host/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-Activity-suggestion
method: get method: get
getAllHostActivity: getAllHostActivity:
@@ -239,7 +223,7 @@ getAllHostActivity:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/get-all-host-activity path: /host/Activity_Hub/OnBoarding/get-all-host-activity
method: get method: get
acceptAggrement: acceptAggrement:
@@ -255,25 +239,9 @@ acceptAggrement:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/accept-agreement path: /host/Host_Admin/onboarding/accept-agreement
method: patch method: patch
getLatestAgreement:
handler: src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.*'
- '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_Admin/onboarding/get-latest-agreement
method: get
getStepperInfo: getStepperInfo:
handler: src/modules/host/handlers/getStepper.handler handler: src/modules/host/handlers/getStepper.handler
memorySize: 384 memorySize: 384
@@ -292,22 +260,6 @@ getStepperInfo:
path: /stepper path: /stepper
method: get method: get
updateHostProfile:
handler: src/modules/host/handlers/updateHostProfile.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/updateHostProfile.*'
- '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: /profile
method: patch
# Functions with S3/AWS SDK dependencies # Functions with S3/AWS SDK dependencies
submitCompanyDetails: submitCompanyDetails:
handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler
@@ -320,7 +272,7 @@ submitCompanyDetails:
- 'src/common/**' - 'src/common/**'
events: events:
- httpApi: - httpApi:
path: /Host_Admin/onboarding/add-company-details path: /host/Host_Admin/onboarding/add-company-details
method: patch method: patch
submitPQQ_Answer: submitPQQ_Answer:
@@ -336,7 +288,7 @@ submitPQQ_Answer:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/submit-pqq-answer path: /host/Activity_Hub/OnBoarding/submit-pqq-answer
method: patch method: patch
updatePQQ_LastAnswer: updatePQQ_LastAnswer:
@@ -352,9 +304,10 @@ updatePQQ_LastAnswer:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/submit-final-pqq-answer path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
method: post method: post
submitPQQForReview: submitPQQForReview:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.handler handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.handler
memorySize: 384 memorySize: 384
@@ -368,7 +321,7 @@ submitPQQForReview:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/submit-pqq-for-review path: /host/Activity_Hub/OnBoarding/submit-pqq-for-review
method: patch method: patch
getAllPQQwithSubmittedAns: getAllPQQwithSubmittedAns:
@@ -383,22 +336,7 @@ getAllPQQwithSubmittedAns:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans path: /host/Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
method: get
getAllDetailsOfActivityAndVenue:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllDetailsOfActivityAndVenue.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-all-details-activity-venue/{activityXid}
method: get method: get
updateSuggestionAsReviewed: updateSuggestionAsReviewed:
@@ -413,7 +351,7 @@ updateSuggestionAsReviewed:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /Activity_Hub/OnBoarding/update-suggestion-reviewed path: /host/Activity_Hub/OnBoarding/update-suggestion-reviewed
method: patch method: patch
resendOTPmail: resendOTPmail:
@@ -430,158 +368,3 @@ resendOTPmail:
- httpApi: - httpApi:
path: /resend-otp path: /resend-otp
method: post method: post
mediaUploadTos3:
handler: src/modules/host/handlers/mediaUploadToS3.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/mediaUploadToS3/**'
- ${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: /media/upload/activity/{activityXid}
method: post
venueMediaUploadTos3:
handler: src/modules/host/handlers/mediaUploadForVenueToS3.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/mediaUploadForVenueToS3/**'
- ${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: /media/upload/venue/activity/{activityXid}
method: post
mediaDeleteFroms3:
handler: src/modules/host/handlers/mediaDeleteFromS3.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/mediaDeleteFromS3/**'
- ${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: /media/delete
method: delete
createSchedulingForAct:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/createSchedulingOfAct.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/**'
- ${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: /scheduling/create
method: post
getActivitiesByStatus:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/getSchedulingOfAct.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/getSchedulingOfAct.*'
- '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: /scheduling/get-all-activities
method: get
getVenueDurationByAct:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/getVenueDurationByAct.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/getVenueDurationByAct.*'
- '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: /scheduling/get-venue-duration/{activityXid}
method: get
cancelSlotForActivity:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/cancelSlot.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/**'
- ${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: /scheduling/cancel-slot
method: post
openCanceledSlotForActivity:
handler: src/modules/host/handlers/Activity_Hub/Scheduling/openCanceledSlot.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/Scheduling/**'
- ${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: /scheduling/open-canceled-slot
method: patch
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: /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: /Activity_Hub/OnBoarding/submit-pq-answer
method: patch

View File

@@ -327,22 +327,6 @@ RejectPQQByAM:
path: /minglaradmin/hosthub/hosts/reject-pq-by-am path: /minglaradmin/hosthub/hosts/reject-pq-by-am
method: patch method: patch
rejectActivityDetailsApplicationByAM:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectActivityApplicationByAM.handler
memorySize: 384
package:
patterns:
- 'src/modules/minglaradmin/handlers/hosthub/hosts/rejectActivityApplicationByAM**'
- 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /minglaradmin/hosthub/hosts/reject-activity-application-by-am
method: patch
acceptPQByAM: acceptPQByAM:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM.handler
memorySize: 384 memorySize: 384
@@ -359,22 +343,6 @@ acceptPQByAM:
path: /minglaradmin/hosthub/hosts/accept-pq-by-am path: /minglaradmin/hosthub/hosts/accept-pq-by-am
method: patch method: patch
acceptActivityDetailsApplicationByAM:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptActivityApplicationByAM.handler
memorySize: 384
package:
patterns:
- 'src/modules/minglaradmin/handlers/hosthub/hosts/acceptActivityApplicationByAM**'
- 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /minglaradmin/hosthub/hosts/accept-activity-application-by-am
method: patch
rejectHostApplication: rejectHostApplication:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/rejectHostApplication.handler handler: src/modules/minglaradmin/handlers/hosthub/onboarding/rejectHostApplication.handler
memorySize: 384 memorySize: 384
@@ -423,22 +391,6 @@ addPQQSuggestion:
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
method: post method: post
addActivitySuggestion:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addActivtiySuggestion.handler
memorySize: 384
package:
patterns:
- 'src/modules/minglaradmin/handlers/hosthub/hosts/**'
- 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /minglaradmin/hosthub/hosts/add-Activity-suggestion
method: post
getAllPQPDetailsForAM: getAllPQPDetailsForAM:
handler: src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM.handler handler: src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM.handler
memorySize: 384 memorySize: 384
@@ -455,6 +407,7 @@ getAllPQPDetailsForAM:
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid} path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
method: get method: get
getSuggestionsForAM: getSuggestionsForAM:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/showSuggestionToAM.handler handler: src/modules/minglaradmin/handlers/hosthub/onboarding/showSuggestionToAM.handler
memorySize: 384 memorySize: 384

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

@@ -13,7 +13,7 @@ getAllBankAndCurrencyDetails:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-all-bank-currency-details path: /prepopulate/get-all-bank-currency-details
method: get method: get
getCityByState: getCityByState:
@@ -29,7 +29,7 @@ getCityByState:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-city-by-state path: /prepopulate/get-city-by-state
method: get method: get
getBranchByBankXid: getBranchByBankXid:
@@ -45,7 +45,7 @@ getBranchByBankXid:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-branch-by-bank path: /prepopulate/get-branch-by-bank
method: get method: get
getAllDocumentCountryStateCityDetails: getAllDocumentCountryStateCityDetails:
@@ -60,7 +60,7 @@ getAllDocumentCountryStateCityDetails:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-all-doc-country path: /prepopulate/get-all-doc-country
method: get method: get
getAllPqqQuesAns: getAllPqqQuesAns:
@@ -75,7 +75,7 @@ getAllPqqQuesAns:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-all-pqq-ques-ans path: /prepopulate/get-all-pqq-ques-ans
method: get method: get
getFrequenciesOfActivity: getFrequenciesOfActivity:
@@ -90,7 +90,7 @@ getFrequenciesOfActivity:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-all-Frequencies path: /prepopulate/get-all-Frequencies
method: get method: get
getAddActivityPrePopulate: getAddActivityPrePopulate:
@@ -105,5 +105,5 @@ getAddActivityPrePopulate:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /get-add-activity-prepopulate path: /prepopulate/get-add-activity-prepopulate
method: get method: get

View File

@@ -0,0 +1,20 @@
# Swagger Documentation Functions
swaggerUi:
handler: src/handlers/swagger.swaggerUi
memorySize: 256
events:
- httpApi:
path: /api-docs
method: get
swaggerJson:
handler: src/handlers/swagger.swaggerJson
memorySize: 256
package:
patterns:
- 'swagger.json'
events:
- httpApi:
path: /swagger.json
method: get

View File

@@ -1,409 +0,0 @@
# Prepopulate Module Functions
# Reference data and lookup endpoints
registerUser:
handler: src/modules/user/handlers/authentication/registration.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /register
method: post
submitPersonalInfo:
handler: src/modules/user/handlers/authentication/submitPersonalInfo.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /submit-personal-info
method: post
verifyOtpForUser:
handler: src/modules/user/handlers/authentication/verifyOtpForUser.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /verify-otp
method: post
generateAccessFromRefreshToken:
handler: src/modules/user/handlers/authentication/generateRefereshToAccess.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /generate-access-from-refresh
method: post
setPasscodeForMobile:
handler: src/modules/user/handlers/authentication/setPasscodeForMobile.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /set-passcode
method: post
verifyPasscode:
handler: src/modules/user/handlers/authentication/verifyPasscode.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /verify-passcode
method: post
setUserInterest:
handler: src/modules/user/handlers/authentication/SetuserInterest.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /set-interests
method: post
setUserLocationss:
handler: src/modules/user/handlers/authentication/SetLocationofUser.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/**'
- ${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: /set-location-user
method: post
getLandingPageDetails:
handler: src/modules/user/handlers/activities/landingPageAllDetails.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/activities/**'
- ${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: /activities/get-landing-page-details
method: get
getSurpriseMePageDetails:
handler: src/modules/user/handlers/activities/surpriseMePage.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/activities/**'
- ${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: /activities/get-surprise-me-page-details
method: get
getActivityDetailsById:
handler: src/modules/user/handlers/activities/getByIdActivityDetails.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/activities/**'
- ${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: /activities/get-activity-details-by-id/{activity_xid}
method: get
checkAvailabilityDetails:
handler: src/modules/user/handlers/activities/checkAvailabilityDetails.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/check-availability/{activity_xid}
method: get
searchActivities:
handler: src/modules/user/handlers/activities/getSpecificSearchApi.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/specific-search
method: get
searchSchoolsAndCompanies:
handler: src/modules/user/handlers/connections/getSchoolCompanyName.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/search-schools-companies
method: get
searchCities:
handler: src/modules/user/handlers/connections/searchCities.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/search-cities
method: get
addSchoolCompanyDetail:
handler: src/modules/user/handlers/connections/addSchoolCompanyDetail.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/add-school-company
method: post
removeConnectionDetails:
handler: src/modules/user/handlers/connections/removeConnectionDetails.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/remove-connection-details
method: delete
getAllConnectionOfUser:
handler: src/modules/user/handlers/connections/getAllConnectionDetailsOfUser.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/get-all-connections-details
method: get
getActivityFromConnectionsInterest:
handler: src/modules/user/handlers/connections/getActivityFromConnectionsInterest.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${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: /connections/get-activity-from-connections-interest
method: get
viewMoreActivitiesByInterest:
handler: src/modules/user/handlers/activities/viewMoreActivities.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/view-more-activities
method: get
viewMoreActivitiesUpperSection:
handler: src/modules/user/handlers/activities/viewMoreUpperSection.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/view-more-activities-upper-section
method: get
getRandomActiveActivity:
handler: src/modules/user/handlers/activities/getRandomActiveActivity.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/get-random-active-activity
method: get
getNearbyActivities:
handler: src/modules/user/handlers/activities/getNearbyActivities.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/get-nearby-activities
method: get
addActivityToBucketInterested:
handler: src/modules/user/handlers/activities/addToBucketInterested.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/add-to-bucket-interested
method: post
removeActivityFromBucketInterested:
handler: src/modules/user/handlers/activities/removeFromBucketInterested.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${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: /activities/remove-from-bucket-interested
method: post
getFilteredLandingPageAllDetails:
handler: src/modules/user/handlers/activities/filteredLandingPageAllDetails.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${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: /activities/get-filtered-landing-page-details
method: get
getAllBucketActivities:
handler: src/modules/user/handlers/activities/getAllBucketActivities.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${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: /activities/get-all-bucket-activities
method: get

View File

@@ -1,5 +1,5 @@
import { Global, Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service'; // correct export location import { PrismaService } from './prisma.lambda.service';
@Global() @Global()
@Module({ @Module({

View File

@@ -1,357 +0,0 @@
export const AGREEMENT_TEMPLATE = `
MINGLAR HOST AGREEMENT
Effective Date: [EFFECTIVE_DATE]
BETWEEN
Minglar India Private Limited, a company incorporated under the Companies Act, 2013, having its registered office at 602, Aaradhya Avenue X Eve, Naidu Colony, Pant Nagar, Ghatkopar (East), Mumbai 400075 (hereinafter referred to as “Minglar India Private Limited”, which expression shall include its successors and permitted assigns);
AND
[HOST_LEGAL_NAME], a [COMPANY_TYPE], having its principal place of business at [FULL_ADDRESS] (hereinafter referred to as “Host”, which expression shall include its owners, partners, directors, employees, representatives, and permitted assigns).
Minglar India Private Limited and Host are individually referred to as a “Party” and collectively as the “Parties”.
1. PURPOSE AND RELATIONSHIP
1.1 Platform Overview
Minglar India Private Limited operates a curated digital marketplace under the brand “Minglar” that enables users (“Users”) to discover, review, and book experiential activities, events, workshops, tours, and related services (“Activities”) offered by independent Hosts.
1.2 Engagement
The Host desires to list and provide its Activities on the Minglar platform subject to the terms and standards set forth in this Agreement.
1.3 Independent Contractor
Nothing in this Agreement shall be construed as creating a partnership under the Indian Partnership Act, 1932, joint venture, agency, employment, franchise, or profit-sharing arrangement. The Host operates strictly as an independent contractor and shall have full operational control over execution of its Activities.
2. TERM
2.1 Duration
This Agreement shall remain valid for a fixed term of [DURATION_TEXT] and shall automatically expire on [EXPIRY_DATE], unless terminated earlier in accordance with Section 16.
2.2 Renewal
Renewal shall be subject to mutual agreement. Commission adjustments, if any, shall apply only at renewal due to inflation or additional expenses incurred by Minglar India Private Limited for platform upgrades or new features.
3. HOST RESPONSIBILITIES
3.1 Legal Compliance and Document Submission
3.1.1 Compliance
The Host shall obtain, maintain, and keep valid at all times all licenses, permits, approvals, registrations, certifications, insurance policies, and governmental permissions required under applicable laws for the lawful conduct of the Activities.
3.1.2 Document Upload
The Host shall, at the time of onboarding on the Minglar platform, upload true, complete, and legible copies of licenses, permits, registrations, tax certificates (including GST registration, where applicable), identity proof, business registration documents, insurance certificates, and any other documents reasonably required by Minglar India Private Limited for verification purposes.
3.1.3 Accuracy and Updates
The Host represents and warrants that all documents submitted are authentic, valid, and up to date. The Host shall promptly upload updated copies whenever any document expires, is renewed, modified, or replaced. Failure to provide valid documentation may result in suspension, delisting, or termination of this Agreement at the sole discretion of Minglar India Private Limited.
3.1.4 Verification
Minglar India Private Limited shall have the right to verify such documents and request additional documentation if required for regulatory, compliance, safety, or audit purposes.
3.2 Accurate Listing and Host Anonymity
3.2.1 Activity Details
The Host shall provide complete, accurate, and up-to-date descriptions for each Activity, including inclusions, exclusions, duration, safety requirements, pricing, and applicable tax percentages at the time of onboarding. The Host shall ensure that all information remains current throughout the term of this Agreement.
3.2.2 Host Content
Hosts retain ownership of original content they upload (such as activity descriptions, images, and videos).
However, by uploading content, the Host grants Minglar a:
- Worldwide
- Non-exclusive
- Royalty-free
- Transferable
- Sub-licensable
license to use, reproduce, modify, adapt, publish, translate, distribute and display such content for purposes of operating, marketing and promoting the platform.
This license continues for as long as the content remains on the platform.
3.2.3 Unique Activity Name
The Host shall provide a unique name for each Activity during onboarding. The Hosts actual company name, personal name, or brand shall not be visible to Users on the activity card or in any public-facing listing.
3.2.4 Prohibition on Branding and Contact
The Host shall not display, embed, or otherwise reveal its contact information, company name, logo, website, email, or other identifiable details in any photos, videos, descriptions, chat messages, or other content shared with Users via the Minglar platform.
3.2.5 Breach and Suspension
Any attempt to circumvent these provisions or display unauthorized branding or contact details shall be considered a material breach of this Agreement. Minglar India Private Limited reserves the right to suspend or delist the Activity immediately and take other remedial actions as necessary.
3.3 Taxes
3.3.1 Tax Responsibility
The total Activity price listed shall be inclusive of all applicable taxes. Minglar India Private Limited shall collect such taxes from Users and transfer them to the Host. The Host shall be solely responsible for depositing and complying with tax obligations before local authorities.
4. SAFETY AND OPERATIONAL STANDARDS
4.1 General Duty of Care
The Host shall conduct all Activities in a safe, hygienic, and controlled manner ensuring the well-being of Users at all times.
4.2 Risk Management
The Host shall conduct risk assessments, maintain standard operating procedures, provide safety briefings, and ensure trained and competent personnel supervise Activities.
4.3 Transportation
If the Host provides pick-up, drop-off, or in-Activity transportation, such transportation shall be safe, clean, reasonably comfortable, and legally compliant. The Host shall remain responsible for User safety during transport.
4.4 SOS Emergency Protocol
During execution of Activities, Minglars SOS feature shall be active. If activated by a User, the Host Operator shall receive immediate notification and live location details. The Host Operator shall immediately contact and reach the User. The emergency shall be cleared only after ensuring the User is safe. The Host Operator shall be the first point of contact for all emergencies.
4.5 Equipment Standards
The Host shall ensure that all equipment, tools, and materials used for conducting any Activity are:
1. Maintained in accordance with the manufacturers recommendations and operational guidelines.
2. Tested regularly to confirm they are safe and functional before each Activity.
3. Kept clean, hygienic, and in good working order at all times.
4. Adequate for the number of Users participating, ensuring no overuse or overcrowding that may compromise safety.
The Host shall be solely responsible for any incidents, accidents, or injuries caused due to faulty, poorly maintained, unhygienic, or unsafe equipment. Failure to comply may result in suspension, delisting, or immediate termination of this Agreement under Section 16.
5. QUALITY AND PUNCTUALITY
5.1 Quality Standards
The Host shall maintain consistently high service standards and continuously strive to improve user experience toward achieving five-star ratings.
5.2 Timeliness
The Host shall ensure timely check-in, commencement, and completion of Activities. Delays impacting Users onward travel or other bookings shall constitute service failure.
6. INSURANCE
The Host shall obtain, maintain, and keep valid insurance coverage appropriate for the risk associated with the respective Activities. Coverage shall be sufficient to protect Users, the Host, and Minglar India Private Limited against claims arising from accidents, injuries, fatalities, or property damage.
6.1 Risk-Based Coverage
The Host shall maintain Public Liability Insurance appropriate to the risk and scale of the respective Activities.
6.2 Coverage Amount
For high-risk or multi-participant Activities, coverage is recommended up to INR 5 Crores or an amount appropriate to cover potential claims arising from injury, death, or property damage.
6.3 Additional Insured
Policies shall name Minglar India Private Limited and Minglar Group Pte Ltd, Singapore as Additional Insured.
6.4 Proof
Valid insurance certificates must be submitted prior to onboarding and maintained throughout the Agreement term.
7. USER WAIVERS
The Host shall ensure that Users acknowledge and accept all standard waivers, terms, and risk disclosures prior to participation in any Activity, as provided by the Minglar platform.
8. INDEMNITY
The Host shall indemnify, defend, and hold harmless Minglar India Private Limited, its affiliates, employees, directors, and representatives from any claims, losses, damages, liabilities, fines, or expenses arising directly or indirectly from the Hosts failure to comply with laws, negligence, or breach of this Agreement.
9. LIMITATION OF LIABILITY
The total aggregate liability of Minglar India Private Limited arising out of or in connection with any claim relating to a specific Activity shall not exceed the total commission earned by Minglar India Private Limited from that particular Activity (or substantially similar activity category) conducted by the Host during the three (3) months immediately preceding the date of the claim.
If the Host lists multiple different Activities, the liability cap applies only to the commission earned from the specific Activity giving rise to the claim and does not include commission earned from other unrelated Activities.
Under no circumstances shall Minglar India Private Limited be liable for indirect, incidental, consequential, punitive, or special damages, including loss of profits, goodwill, reputation, or business opportunity.
10. PAYMENT AND COMMISSION
10.1 Commission
10.1.1 Standard Commission
A [COMMISSION_TEXT] commission shall be charged by Minglar India Private Limited on the Activity revenue (after deduction of applicable taxes).
10.1.2 Fixed During Term
The term of this agreement is [DURATION_TEXT]. This commission shall remain unchanged during the term of this Agreement.
10.1.3 Renewal Adjustment
At the time of renewal, Minglar India Private Limited reserves the right to adjust commission rates due to inflation or platform upgrades.
10.2 Host Payout
10.2.1 Timing
Minglar India Private Limited shall remit payments to the Host 24 hours after check-in completion or no-show.
10.2.2 Monthly Commission Invoice
Minglar India Private Limited shall issue a monthly invoice to the Host detailing the commission earned. GST shall be charged extra.
10.2.3 Banking and Gateway Charges
All banking, payment gateway, and transaction fees arising from normal bookings shall be borne by the Host.
11. BOOKING CANCELLATION
11.1 Host-Related Cancellation
If an Activity is cancelled due to host health, extreme weather, natural disasters, government restrictions, venue issues, equipment failure, team/partner unavailability, or other uncontrollable circumstances, the Host shall pay the applicable cancellation fee charged by the payment gateway. No payment will be remitted to the Host, and 100% of the booking amount including taxes shall be refunded to Users.
11.2 User-Initiated Cancellation
If Users cancel within the permitted period, the platform fee shall be deducted and the remaining amount refunded to Users. The Host will not receive payment for such cancellations.
12. PARTICIPATION OF MINGLAR ACCOUNT MANAGER FOR AUDIT
In case of low performance, low bookings, or safety/quality issues reported by Users that remain unresolved for more than one week, Minglar India Private Limited may send an Account Manager to audit the Activity at the Hosts location.
The date and slot for such an audit shall be coordinated by the Account Manager and the Host.
The participation of the Account Manager is free of charge. If the audit requires overnight stay or the Activity duration exceeds one day, the Host shall provide accommodation.
Transportation costs for the Account Manager shall be shared 50% by the Host.
Minglar India Private Limited reserves the right to send an Account Manager once per year or sooner in case of low performance, safety concerns, or user complaints with less than 2-star ratings.
13. DATA PROTECTION, PRIVACY & INFORMATION SECURITY
13.1 Compliance with Applicable Laws
The Parties acknowledge that Minglar Group Pte. Ltd. (Singapore) and/or Minglar India Private Limited (India) collects and processes personal data in compliance with applicable laws, including:
- The Personal Data Protection Act 2012 (“PDPA”); and
- The Digital Personal Data Protection Act 2023 (“DPDP Act”).
Each Party agrees to comply with all applicable privacy and data protection laws in the jurisdiction where the Activity is conducted.
13.2 Roles of the Parties
a) Minglar shall act as the Data Fiduciary / Data Controller for personal data collected via the Minglar platform.
b) The Host shall act as a Data Processor / Authorized Data Recipient when processing User personal data solely for the purpose of conducting booked Activities.
The Host shall not independently determine the purpose or means of processing User personal data without prior written authorization from Minglar.
PART A USER PERSONAL DATA
13.3 Categories of User Data Collected
The Host acknowledges that Minglar may collect and process the following categories of User personal data through the platform:
a) Full name
b) Date of birth
c) Mobile number and email address
d) Gender
e) Height and weight (where Activity eligibility or safety restrictions apply)
f) School and college information
g) Profile photograph
h) Home location and/or real-time GPS location
i) Check-in and check-out data
j) SOS or emergency alerts
k) Payment method details and transaction information
l) Spending preferences or monthly budget indicators
m) Biometric verification data (including facial verification, where applicable)
n) Medical conditions or health disclosures (Phase 2 implementation)
o) Attendance logs and activity participation records
13.4 Sensitive Personal Data
Certain categories of data may constitute sensitive personal data, including:
- Biometric data
- Medical or health information
- Real-time location data
- Financial/payment information
- Profile photographs capable of biometric identification
Such data shall:
a) Be processed only where necessary for safety, verification, compliance, or Activity execution;
b) Be accessed strictly on a need-to-know basis;
c) Be protected using enhanced security measures;
d) Not be downloaded, stored externally, or retained by the Host beyond the Activity duration unless legally required.
The Host shall not independently collect additional medical, biometric, or financial data outside the Minglar platform without prior written approval.
13.5 Confidentiality and Use Restrictions
The Host shall treat all User and platform data as strictly confidential, including attendance logs, SOS alerts, emergency records, and location data collected during Activities.
User data shall:
a) Be used solely for Activity execution and safety;
b) Not be shared with third parties without prior written consent from Minglar India Private Limited;
c) Not be used for independent marketing, profiling, or database creation;
d) Not be retained after completion of the Activity unless legally required.
The Host shall not contact Users outside the Minglar platform unless expressly authorized.
13.6 Security Measures
The Host shall implement reasonable technical and organizational measures to protect personal data from unauthorized access, alteration, misuse, or disclosure, including:
- Secure password-protected systems;
- Restricted employee access;
- Confidentiality undertakings from employees and operators;
- Secure handling of operator devices used for check-in/check-out;
- Immediate reporting of lost or compromised devices.
13.7 Data Breach Notification
Any actual or suspected breach involving personal data — including medical, biometric, financial, or location data — shall be reported to Minglar India Private Limited immediately and in any event within twenty-four (24) hours of discovery.
The Host shall fully cooperate in investigation, mitigation, and regulatory reporting obligations.
13.8 Retention and Deletion
The Host shall not retain personal data beyond the period necessary for conducting the Activity unless required by law.
Upon termination of this Agreement, the Host shall delete all User data in its possession and confirm deletion upon request.
13.9 User Responsibility for Medical Disclosures
Users remain responsible for the accuracy and completeness of any medical or health information voluntarily disclosed.
The Host may rely on such disclosures in good faith for safety and eligibility determinations.
Minglar shall not be liable for losses arising from incomplete or inaccurate medical information provided by Users.
PART B HOST PERSONAL DATA
13.10 Collection of Host Personal Data
The Host acknowledges that Minglar may collect and process personal data relating to the Host and its directors, partners, employees, and authorized representatives for:
- KYC verification and onboarding;
- Regulatory compliance;
- Risk assessment and fraud prevention;
- Payment processing and settlement;
- Audit and safety review;
- Enforcement of this Agreement.
13.11 Sharing and Cross-Border Transfers
Host personal data may be shared:
- Within Minglar Group entities;
- With banks, payment processors, verification agencies, insurers, auditors, and professional advisors;
- With regulatory or governmental authorities where legally required.
The Host acknowledges that personal data may be transferred between Singapore, India, and other jurisdictions where Minglar operates, subject to compliance with applicable cross-border transfer laws.
PART C ACTIVITY LOCATION & OPERATIONAL DATA
13.12 Collection of Activity Location and Operational Data
The Host agrees that Minglar may collect and process operational data relating to Activities, including:
- Venue address;
- Check-in and check-out locations;
- GPS coordinates;
- Pick-up and drop-off locations;
- Route and logistical details;
- Activity schedules.
Such data may include precise geo-location information.
13.13 Use and Display of Location Data
The Host expressly consents to Minglar:
- Displaying Activity venue, pick-up, and drop-off details on the platform;
- Using location data for navigation, safety monitoring, and emergency coordination;
- Using such data for fraud prevention and verification.
The Host warrants that all location data provided is accurate and lawful.
Minglar shall not be liable for losses arising from inaccurate or misleading information provided by the Host.
PART D ANALYTICS & PLATFORM OPTIMIZATION
13.14 Anonymized and Aggregated Data
Minglar may use anonymized, aggregated, or de-identified data derived from User or Host activity for:
- Platform improvement;
- Artificial intelligence systems;
- Recommendation engines;
- Safety analytics;
- Market research;
- Business strategy and investor reporting.
Such data shall not identify any individual User or Host.
All analytics models, algorithms, insights, and derived data shall remain the exclusive intellectual property of Minglar.
PART E LIABILITY & SURVIVAL
13.15 Indemnity
The Host shall indemnify and hold harmless Minglar Group Pte. Ltd. and Minglar India Private Limited against any losses, penalties, regulatory actions, claims, damages, or costs arising from:
- Unauthorized use of personal data;
- Data breaches attributable to the Host;
- Non-compliance with applicable data protection laws;
- Inaccurate operational or location data provided by the Host.
13.16 Survival
This Section 13 shall survive termination or expiry of this Agreement.
14. CONFIDENTIALITY
The Host shall maintain strict confidentiality regarding all platform operations, processes, user data, and commercial information. These obligations survive five (5) years post-termination. Exceptions only apply if required by law.
15. NON-EXCLUSIVITY
This Agreement does not restrict the Host from offering Activities on other platforms. Minglar India Private Limited does not claim exclusivity. The Host shall not interfere with other platform operations or solicit Users to bypass Minglar.
16. TERMINATION
16.1 Immediate Termination
Minglar India Private Limited may terminate immediately in case of breach, misrepresentation, failure to maintain safety or quality standards, or violation of this Agreement.
16.2 Termination with Notice
Either Party may terminate this Agreement by providing 30 days written notice.
16.3 Post-Termination Obligations
Upon termination, the Host must execute all bookings already made by Users. Minglar India Private Limited will block further bookings for the Hosts Activities.
16.4 Commission Adjustment at Renewal
Any change in commission shall only occur at renewal of this Agreement. No adjustments shall be made during the active term.
16.5 Non-Exclusivity
Termination or continuation of this Agreement does not prevent the Host from offering Activities elsewhere.
17. GOVERNING LAW
This Agreement shall be governed by the laws of India. Courts at the registered office jurisdiction of Minglar India Private Limited shall have exclusive jurisdiction.
18. ENTIRE AGREEMENT
This Agreement constitutes the complete understanding between the Parties and supersedes all prior communications.
19. HOST ACKNOWLEDGMENT & ELECTRONIC CONSENT
19.1 Electronic Acceptance
By clicking the “I Agree” button, the Host acknowledges they have read, understood, and accepted the terms of this Agreement and the Minglar Privacy Policy.
19.2 Binding Agreement
Electronic acceptance constitutes a legally binding contract, equivalent to a wet signature.
19.3 Host Obligations
- Provide accurate and lawful data
- Protect User data and follow emergency protocols
- Notify Minglar of breaches or unauthorized access
- Comply with applicable laws
19.4 Electronic Records
All electronic records and communications are valid and enforceable.
19.5 Updates
Continued use after updates constitutes acceptance of amended terms.
Signed Electronically by:
Minglar India Private Limited
Authorized Signatory
Host:
[HOST_LEGAL_NAME]
Date: [ACCEPT_DATE]
`;

View File

@@ -21,9 +21,4 @@ export const USER_STATUS = {
ACTIVE: "Active", ACTIVE: "Active",
DE_ACTIVATED: "De-activated", DE_ACTIVATED: "De-activated",
REJECTED: "Rejected" REJECTED: "Rejected"
}
export const RESTRICTION_NAME = {
ABOVE: "Above",
BELOW: "Below",
} }

View File

@@ -40,25 +40,24 @@ export const ACTIVITY_INTERNAL_STATUS = {
ACTIVITY_REJECTED: 'Activity Rejected', ACTIVITY_REJECTED: 'Activity Rejected',
ACTIVITY_APPROVED: 'Activity Approved', ACTIVITY_APPROVED: 'Activity Approved',
ACTIVITY_LISTED: 'Activity Listed', ACTIVITY_LISTED: 'Activity Listed',
ACTIVITY_UNLISTED: 'Activity UnListed ', ACTIVITY_UNLISTED: 'Activity Un Listed By Host',
ACTIVITY_NOT_LISTED: 'Activity Not Listed',
}; };
export const ACTIVITY_DISPLAY_STATUS = { export const ACTIVITY_DISPLAY_STATUS = {
DRAFT_PQ: 'Draft', DRAFT_PQ: 'Draft - PQ',
APPROVED: 'Approved', APPROVED: 'Approved',
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQ_FAILED: 'PQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enhancing', ENHANCING: 'Enchancing',
PQ_IN_REVIEW: 'PQ In Review', PQ_IN_REVIEW: 'PQ In Review',
PQ_APPROVED: 'PQ Approved', PQ_APPROVED: 'PQ Approved',
ACTIVITY_DRAFT: 'Draft - Activity', ACTIVITY_DRAFT: 'Draft - Activity',
ACTIVITY_IN_REVIEW: 'In Review', ACTIVITY_IN_REVIEW: 'In Review',
ACTIVITY_TO_REVIEW: 'Re-submitted', ACTIVITY_TO_REVIEW: 'To Review',
NOT_LISTED: 'Not Listed', ACTIVITY_NOT_LISTED: 'Not Listed',
ACTIVITY_LISTED: 'Listed', ACTIVITY_LISTED: 'Listed',
ACTIVITY_UNLISTED: 'Un Listed', ACTIVITY_UNLISTED: 'Un Listed',
}; };
@@ -79,33 +78,23 @@ export const ACTIVITY_AM_INTERNAL_STATUS = {
ACTIVITY_REJECTED: 'Activity Rejected', ACTIVITY_REJECTED: 'Activity Rejected',
ACTIVITY_APPROVED: 'Activity Approved', ACTIVITY_APPROVED: 'Activity Approved',
ACTIVITY_LISTED: 'Activity Listed', ACTIVITY_LISTED: 'Activity Listed',
ACTIVITY_SUBMITED: 'Activity Submitted',
}; };
export const ACTIVITY_AM_DISPLAY_STATUS = { export const ACTIVITY_AM_DISPLAY_STATUS = {
DRAFT_PQ: 'Draft', DRAFT_PQ: 'Draft - PQ',
APPROVED: 'Approved', APPROVED: 'Approved',
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQ_FAILED: 'PQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enhancing', ENHANCING: 'Enchancing',
NEW: 'New', NEW: 'New',
PQ_APPROVED: 'PQ Approved', PQ_APPROVED: 'PQ Approved',
REVISED: 'Revised', REVISED: 'Revised',
ACTIVITY_DRAFT: 'Draft - Activity', ACTIVITY_DRAFT: 'Draft - Activity',
ACTIVITY_NEW: 'New', ACTIVITY_NEW: 'To Review',
ACTIVITY_TO_REVIEW: 'Activity To Review',
ACTIVITY_ENHANCING: 'Enhancing', ACTIVITY_ENHANCING: 'Enhancing',
NOT_LISTED: 'Not Listed', ACTIVITY_NOT_LISTED: 'Not Listed',
ACTIVITY_LISTED: 'Listed', ACTIVITY_LISTED: 'Listed',
ACTIVITY_REVISED: 'Activity Revised'
};
export const SCHEDULING_TYPE = {
ONCE: 'ONCE',
WEEKLY: 'WEEKLY',
MONTHLY: 'MONTHLY',
CUSTOM: 'CUSTOM',
}; };

View File

@@ -15,7 +15,6 @@ export const MINGLAR_STATUS_DISPLAY = {
ENHANCING: 'Enhancing', ENHANCING: 'Enhancing',
APPROVED: 'Approved', APPROVED: 'Approved',
REJECTED: 'Rejected', REJECTED: 'Rejected',
RE_SUBMITTED: 'Re-submitted',
DRAFT: 'Draft' DRAFT: 'Draft'
}; };

View File

@@ -1,76 +0,0 @@
import { z } from 'zod';
import { SCHEDULING_TYPE } from '../../constants/host.constant';
const WeekdayEnum = z.enum([
'MONDAY',
'TUESDAY',
'WEDNESDAY',
'THURSDAY',
'FRIDAY',
'SATURDAY',
'SUNDAY',
]);
export const scheduleActivity = z.object({
activityXid: z.number(),
listNow: z.boolean(),
scheduleType: z.enum([
SCHEDULING_TYPE.ONCE,
SCHEDULING_TYPE.WEEKLY,
SCHEDULING_TYPE.MONTHLY,
SCHEDULING_TYPE.CUSTOM,
]),
dateRange: z.object({
startDate: z.string(),
endDate: z.string().nullable().optional(),
}),
rules: z.object({
weekdays: z.array(WeekdayEnum).optional(),
monthDates: z.array(z.number()).optional(),
customDates: z.array(z.string()).optional(),
}),
venues: z.array(
z.object({
venueXid: z.number(),
slots: z.array(
z.object({
startTime: z.string(),
endTime: z.string(),
weekDay: WeekdayEnum.nullable().optional(),
dayOfMonth: z.number().nullable().optional(),
occurrenceDate: z.string().nullable().optional(),
maxCapacity: z.number(),
})
).optional().default([]),
})
),
earlyCheckInMins: z.number().optional(),
bookingCutOffMins: z.number().optional(),
isLateCheckingAllowed: z.boolean().optional(),
isInstantBooking: z.boolean().optional(),
})
.superRefine((data, ctx) => {
if (data.scheduleType === 'WEEKLY' && !data.rules.weekdays?.length) {
ctx.addIssue({ path: ['rules', 'weekdays'], message: 'Weekdays required for WEEKLY schedule', code: 'custom' });
}
if (data.scheduleType === 'MONTHLY' && !data.rules.monthDates?.length) {
ctx.addIssue({ path: ['rules', 'monthDates'], message: 'Month dates required for MONTHLY schedule', code: 'custom' });
}
if (
(data.scheduleType === 'CUSTOM' || data.scheduleType === 'ONCE') &&
!data.rules.customDates?.length
) {
ctx.addIssue({ path: ['rules', 'customDates'], message: 'Custom dates required', code: 'custom' });
}
});
export type ScheduleActivityDTO = z.infer<typeof scheduleActivity>;

View File

@@ -1,26 +0,0 @@
// validations/hostBankDetails.validation.ts
import { z } from "zod";
export const userPersonalInfoSchema = z.object({
firstName: z
.string()
.nonempty("First name is required"),
lastName: z
.string()
.optional(),
genderName: z
.string()
.nonempty("Gender is required"),
dateOfBirth: z
.string()
.nonempty("Date of birth is required")
.refine(val => !isNaN(Date.parse(val)), {
message: "Date of birth must be a valid ISO date (YYYY-MM-DD)",
}),
});
export type UserPersonalInfoSchema = z.infer<typeof userPersonalInfoSchema>;

View File

@@ -83,8 +83,7 @@ const envVarsSchema = yup
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'), BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
// Email links // Email links
AM_INVITATION_LINK: yup.string().required('Link to send in AM invitation mail is required'), 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'), HOST_LINK: yup.string().required('Link to host panel is required')
HOST_LINK_PQ: yup.string().required('Link to host panel pqp is required')
}) })
.noUnknown(true); .noUnknown(true);
@@ -164,7 +163,6 @@ function getConfig() {
MinglarAdminName: envVars.MINGLAR_ADMIN_NAME, MinglarAdminName: envVars.MINGLAR_ADMIN_NAME,
AM_INVITATION_LINK: envVars.AM_INVITATION_LINK, AM_INVITATION_LINK: envVars.AM_INVITATION_LINK,
HOST_LINK: envVars.HOST_LINK, HOST_LINK: envVars.HOST_LINK,
HOST_LINK_PQ: envVars.HOST_LINK_PQ,
// oneSignal: { // oneSignal: {
// appID: envVars.ONESIGNAL_APPID, // appID: envVars.ONESIGNAL_APPID,
// restApiKey: envVars.ONESIGNAL_REST_APIKEY, // restApiKey: envVars.ONESIGNAL_REST_APIKEY,

140
src/handlers/swagger.ts Normal file
View File

@@ -0,0 +1,140 @@
// src/handlers/swagger.ts
// Swagger UI handler for serverless-offline
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as fs from 'fs';
import * as path from 'path';
// Swagger UI HTML template
const getSwaggerHtml = (specUrl: string) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minglar API Documentation</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
<style>
html { box-sizing: border-box; overflow-y: scroll; }
*, *:before, *:after { box-sizing: inherit; }
body { margin: 0; background: #fafafa; }
.swagger-ui .topbar { display: none; }
.swagger-ui .info { margin: 20px 0; }
.swagger-ui .info .title { font-size: 36px; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
persistAuthorization: true,
displayRequestDuration: true,
filter: true,
showExtensions: true,
tryItOutEnabled: true
});
window.ui = ui;
};
</script>
</body>
</html>
`;
// Handler for Swagger UI HTML page
export const swaggerUi = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
const host = event.headers?.Host || event.headers?.host || 'localhost:3000';
// For serverless-offline, use simple direct URL without stage
const specUrl = `/swagger.json`;
return {
statusCode: 200,
headers: {
'Content-Type': 'text/html',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
},
body: getSwaggerHtml(specUrl),
};
};
// Handler for swagger.json
export const swaggerJson = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
try {
// Read swagger.json from project root
const swaggerPath = path.join(process.cwd(), 'swagger.json');
const swaggerContent = fs.readFileSync(swaggerPath, 'utf8');
const swaggerDoc = JSON.parse(swaggerContent);
// Update server URL dynamically - ALWAYS use root URL without stage
const host = event.headers?.Host || event.headers?.host || 'localhost:3000';
const protocol = event.headers?.['X-Forwarded-Proto'] || 'http';
// For local development, use root URL. For AWS, use the full URL with stage only if deployed
if (process.env.AWS_LAMBDA_FUNCTION_NAME && process.env.AWS_EXECUTION_ENV) {
// Only add stage for actual AWS deployment, not serverless-offline
const stage = event.requestContext?.stage;
const stagePrefix = stage && stage !== '$default' ? `/${stage}` : '';
swaggerDoc.servers = [
{
url: `${protocol}://${host}${stagePrefix}`,
description: 'AWS API Gateway'
}
];
} else {
// Local development - no stage prefix
swaggerDoc.servers = [
{
url: `http://${host}`,
description: 'Local Development (serverless-offline)'
}
];
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
},
body: JSON.stringify(swaggerDoc, null, 2),
};
} catch (error) {
console.error('Error loading swagger.json:', error);
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Failed to load swagger.json',
message: error instanceof Error ? error.message : 'Unknown error'
}),
};
}
};

View File

@@ -2,32 +2,36 @@ import { z } from 'zod';
/* ================= MEDIA ================= */ /* ================= MEDIA ================= */
export const MediaDto = z.object({ export const MediaDto = z.object({
mediaType: z.string().optional(), mediaType: z.string().optional(), // "image/jpeg", "video/mp4", etc.
mediaFileName: z.string(), mediaFileName: z.string(), // S3 file URL
isCoverImage: z.boolean().optional().default(false),
}); });
/* ================= PRICE ================= */ /* ================= PRICE =================
* ❌ No tax info here; root-level only
*/
export const PriceDto = z.object({ export const PriceDto = z.object({
noOfSession: z.number().int().optional().default(1), noOfSession: z.number().int().optional().default(1),
isPackage: z.boolean().optional().default(false), isPackage: z.boolean().optional().default(false),
sessionValidity: z.number().int().optional().default(0), sessionValidity: z.number().int().optional().default(0),
sessionValidityFrequency: z.string().optional().default('Days'), sessionValidityFrequency: z.string().optional().default('Days'),
basePrice: z.number().int().optional().default(0), basePrice: z.number().int().optional().default(0),
sellPrice: z.number().int(), sellPrice: z.number().int(), // required
}); });
/* ================= VENUE ================= */ /* ================= VENUE ================= */
export const VenueDto = z.object({ export const VenueDto = z.object({
venueName: z.string(), venueName: z.string(),
venueLabel: z.string(),
venueCapacity: z.number().int().optional().default(0), venueCapacity: z.number().int().optional().default(0),
availableSeats: z.number().int().optional().default(0), availableSeats: z.number().int().optional().default(0),
isMinPeopleReqMandatory: z.boolean().optional().default(false), isMinPeopleReqMandatory: z.boolean().optional().default(false),
minPeopleRequired: z.number().int().nullable().optional(), minPeopleRequired: z.number().int().nullable().optional(),
minReqfullfilledBeforeMins: z.number().int().nullable().optional(), minReqfullfilledBeforeMins: z.number().int().nullable().optional(),
venueDescription: z.string().optional(), venueDescription: z.string().optional(),
// ✅ new: media per venue (for ActivityVenueArtifacts)
media: z.array(MediaDto).optional().default([]), media: z.array(MediaDto).optional().default([]),
// price list per venue
prices: z.array(PriceDto).optional().default([]), prices: z.array(PriceDto).optional().default([]),
}); });
@@ -37,36 +41,28 @@ export const PickupDetailDto = z.object({
locationLat: z.number().nullable().optional(), locationLat: z.number().nullable().optional(),
locationLong: z.number().nullable().optional(), locationLong: z.number().nullable().optional(),
locationAddress: z.string().nullable().optional(), locationAddress: z.string().nullable().optional(),
transportTotalPrice: z.number().int().min(0), transportBasePrice: z.number().int().optional().default(0),
transportTotalPrice: z.number().int().optional().default(0),
}); });
export const PickupTransportDto = z.object({ export const PickupTransportDto = z.object({
transportModeXid: z.number().int(), transportModeXid: z.number().int(),
isTransportModeChargeable: z.boolean().optional().default(false),
pickupDetails: z.array(PickupDetailDto).optional().default([]),
}); });
/* ================= EQUIPMENT ================= */ /* ================= EQUIPMENT ================= */
export const EquipmentDto = z.object({ export const EquipmentDto = z.object({
equipmentName: z.string(), equipmentName: z.string(),
isEquipmentChargeable: z.boolean().optional(), isEquipmentChargeable: z.boolean().optional().default(false),
equipmentBasePrice: z.number().int().optional().default(0), equipmentBasePrice: z.number().int().optional().default(0),
equipmentTotalPrice: z.number().int().optional().default(0), equipmentTotalPrice: z.number().int().optional().default(0),
}); });
/* ================= NAVIGATION MODE ================= */
export const NavigationModeDto = z.object({
navigationModeName: z.string().optional(),
isChargeable: z.boolean().optional(),
totalPrice: z.number().int().optional().default(0),
});
/* ================= ELIGIBILITY ================= */ /* ================= ELIGIBILITY ================= */
export const EligibilityDto = z.object({ export const EligibilityDto = z.object({
isAgeRestriction: z.boolean().optional().default(false), isAgeRestriction: z.boolean().optional().default(false),
ageRestrictionName: z.string().nullable().optional(), ageRestrictionXid: z.number().int().nullable().optional(),
ageEntered: z.number().int().nullable().optional(),
ageIn: z.string().nullable().optional(),
minAge: z.number().int().nullable().optional(),
maxAge: z.number().int().nullable().optional(),
isWeightRestriction: z.boolean().optional().default(false), isWeightRestriction: z.boolean().optional().default(false),
weightRestrictionName: z.string().nullable().optional(), weightRestrictionName: z.string().nullable().optional(),
@@ -86,23 +82,24 @@ export const EligibilityDto = z.object({
/* ================= OTHER DETAILS ================= */ /* ================= OTHER DETAILS ================= */
export const OtherDetailsDto = z.object({ export const OtherDetailsDto = z.object({
exclusiveNotes: z.string().optional(), exclusiveNotes: z.string().optional(),
safetyInstruction: z.string().optional(),
dosNotes: z.string().optional(), dosNotes: z.string().optional(),
dontsNotes: z.string().optional(), dontsNotes: z.string().optional(),
tipsNotes: z.string().optional(), tipsNotes: z.string().optional(),
termsAndCondition: z.string().optional(), termsAndCondition: z.string().optional(),
cancellations: z.string().optional(),
}); });
/* ================= CREATE ACTIVITY ================= */ /* ================= CREATE ACTIVITY ================= */
export const CreateActivityDto = z.object({ export const CreateActivityDto = z.object({
/* 🔑 REQUIRED */
activityXid: z.number().int(), activityXid: z.number().int(),
/* OPTIONAL CORE */
activityTypeXid: z.number().int().optional(), activityTypeXid: z.number().int().optional(),
frequenciesXid: z.number().int().nullable().optional(), frequenciesXid: z.number().int().nullable().optional(),
activityTitle: z.string().optional(), activityTitle: z.string().optional(),
activityDescription: z.string().optional(), activityDescription: z.string().optional(),
/* LOCATION */
checkInLat: z.number().nullable().optional(), checkInLat: z.number().nullable().optional(),
checkInLong: z.number().nullable().optional(), checkInLong: z.number().nullable().optional(),
checkInAddress: z.string().nullable().optional(), checkInAddress: z.string().nullable().optional(),
@@ -110,67 +107,56 @@ export const CreateActivityDto = z.object({
checkOutLat: z.number().nullable().optional(), checkOutLat: z.number().nullable().optional(),
checkOutLong: z.number().nullable().optional(), checkOutLong: z.number().nullable().optional(),
checkOutAddress: z.string().nullable().optional(), checkOutAddress: z.string().nullable().optional(),
checkInStateName: z.string().nullable().optional(),
checkInCityName: z.string().nullable().optional(),
checkInCountryName: z.string().nullable().optional(),
checkOutStateName: z.string().nullable().optional(),
checkOutCityName: z.string().nullable().optional(),
checkOutCountryName: z.string().nullable().optional(),
/* DURATION / ENERGY */
energyLevelXid: z.number().int().nullable().optional(), energyLevelXid: z.number().int().nullable().optional(),
durationDays: z.number().int().optional(), activityDurationMins: z.number().int().nullable().optional(),
durationHours: z.number().int().optional(), durationHours: z.number().int().optional(),
durationMins: z.number().int().optional(), durationMins: z.number().int().optional(),
foodAvailable: z.boolean().nullable().optional(), /* FLAGS */
foodAvailable: z.boolean().optional().default(false),
foodIsChargeable: z.boolean().optional().default(false), foodIsChargeable: z.boolean().optional().default(false),
alcoholAvailable: z.boolean().nullable().optional(), alcoholAvailable: z.boolean().optional().default(false),
trainerAvailable: z.boolean().nullable().optional(), trainerAvailable: z.boolean().optional().default(false),
trainerIsChargeable: z.boolean().optional().default(false), trainerIsChargeable: z.boolean().optional().default(false),
pickUpDropAvailable: z.boolean().nullable().optional(), pickUpDropAvailable: z.boolean().optional().default(false),
pickUpDropIsChargeable: z.boolean().optional().default(false), pickUpDropIsChargeable: z.boolean().optional().default(false),
inActivityAvailable: z.boolean().nullable().optional(), inActivityAvailable: z.boolean().optional().default(false),
inActivityIsChargeable: z.boolean().optional().default(false), inActivityIsChargeable: z.boolean().optional().default(false),
equipmentAvailable: z.boolean().nullable().optional(), equipmentAvailable: z.boolean().optional().default(false),
equipmentIsChargeable: z.boolean().optional().default(false), equipmentIsChargeable: z.boolean().optional().default(false),
cancellationAvailable: z.boolean().nullable().optional(), cancellationAvailable: z.boolean().optional().default(false),
cancellationAllowedBeforeMins: z.number().int().nullable().optional(),
/* MONEY / CURRENCY */
currencyXid: z.number().int().nullable().optional(), currencyXid: z.number().int().nullable().optional(),
sustainabilityScore: z.number().int().nullable().optional(), sustainabilityScore: z.number().int().nullable().optional(),
safetyScore: z.number().int().nullable().optional(), safetyScore: z.number().int().nullable().optional(),
isInstantBooking: z.boolean().optional().default(false), isInstantBooking: z.boolean().optional().default(false),
/* 🔥 ROOT-LEVEL TAX (SINGLE SOURCE OF TRUTH) */
taxXids: z.array(z.number().int()).optional().default([]), taxXids: z.array(z.number().int()).optional().default([]),
media: z.array(MediaDto).optional().default([]), /* 🔥 MEDIA ARRAYS */
venues: z.array(VenueDto).optional().default([]), media: z.array(MediaDto).optional().default([]), // Activity-level media
venues: z.array(VenueDto).optional().default([]), // Each venues media + prices
/* RELATION ARRAYS */
foodTypeIds: z.array(z.number().int()).optional().default([]), foodTypeIds: z.array(z.number().int()).optional().default([]),
cuisineIds: z.array(z.number().int()).optional().default([]), cuisineIds: z.array(z.number().int()).optional().default([]),
pickupTransports: z.array(PickupTransportDto).optional().default([]), pickupTransports: z.array(PickupTransportDto).optional().default([]),
pickupDetails: z.array(PickupDetailDto).optional().default([]), navigationModes: z.array(z.number().int()).optional().default([]),
navigationModes: z
.array(NavigationModeDto)
.optional()
.default([]),
equipments: z.array(EquipmentDto).optional().default([]), equipments: z.array(EquipmentDto).optional().default([]),
amenitiesIds: z.array(z.number().int()).optional().default([]), amenitiesIds: z.array(z.number().int()).optional().default([]),
foodTotalAmount: z.number().int().optional().default(0), /* EXTRA OBJECTS */
eligibility: EligibilityDto.optional(), eligibility: EligibilityDto.optional(),
otherDetails: OtherDetailsDto.optional(), otherDetails: OtherDetailsDto.optional(),
allowedEntryTypes: z.array(z.number().int()).optional().default([]),
trainerTotalAmount: z.number().int().optional().default(0),
}); });
export type CreateActivityInput = z.infer<typeof CreateActivityDto>; export type CreateActivityInput = z.infer<typeof CreateActivityDto>;

View File

@@ -1,39 +0,0 @@
export interface ScheduleSlotDTO {
startTime: string;
endTime: string;
weekDay?: 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY' | null;
dayOfMonth?: number | null; // 131
occurrenceDate?: string | null;
maxCapacity: number;
}
export interface ScheduleVenueDTO {
venueXid: number;
slots: ScheduleSlotDTO[];
}
// export interface ScheduleActivityDTO {
// activityXid: number;
// scheduleType: 'ONCE' | 'WEEKLY' | 'MONTHLY' | 'CUSTOM';
// dateRange: {
// startDate: string;
// endDate?: string | null;
// };
// rules: {
// weekdays?: (
// 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' |
// 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY'
// )[];
// monthDates?: number[];
// customDates?: string[];
// };
// venues: ScheduleVenueDTO[];
// earlyCheckInMins?: number;
// bookingCutOffMins?: number;
// isLateCheckingAllowed?: boolean;
// isInstantBooking?: boolean;
// }

View File

@@ -1,4 +1,7 @@
import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk';
import Busboy from 'busboy';
import { prismaClient } from '../../../../../common/database/prisma.lambda.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';
@@ -10,95 +13,286 @@ import {
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const hostService = new HostService(prismaClient); const hostService = new HostService(prismaClient);
const s3 = new AWS.S3({ region: config.aws.region });
/* ------------------------------- Utilities ------------------------------- */
function getExtensionFromMime(mimeType: string) {
const map: Record<string, string> = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/webp': 'webp',
'video/mp4': 'mp4',
'video/quicktime': 'mov',
'video/x-msvideo': 'avi',
'video/x-matroska': 'mkv',
};
return map[mimeType] || 'bin';
}
function normalizeJsonField(fields: any, key: string) {
if (!fields[key]) return undefined;
if (typeof fields[key] === 'object') return fields[key];
try {
return JSON.parse(fields[key]);
} catch {
throw new ApiError(400, `Invalid JSON in field: ${key}`);
}
}
/* -------------------------------- Handler -------------------------------- */
export const handler = safeHandler( export const handler = safeHandler(
async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
/* 1⃣ AUTH */ /* 1⃣ AUTH */
const token = const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token']; event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) { if (!token) {
throw new ApiError(401, 'Missing auth token'); throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
} }
const userInfo = await verifyHostToken(token); const userInfo = await verifyHostToken(token);
/* 2PARSE JSON BODY */ /* 2CONTENT TYPE */
if (!event.body) { const contentType =
throw new ApiError(400, 'Request body is required'); 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');
} }
let body: any; /* 3⃣ BODY BUFFER */
try { const bodyBuffer = event.isBase64Encoded
body = JSON.parse(event.body); ? Buffer.from(event.body as string, 'base64')
} catch { : Buffer.from(event.body as string);
throw new ApiError(400, 'Invalid JSON body');
}
const { const fields: Record<string, any> = {};
activity, const files: Array<{
media = [], buffer: Buffer;
isDraft = false, mimeType: string;
} = body; fileName: string;
fieldName: string;
}> = [];
if (!activity) { await new Promise<void>((resolve, reject) => {
const bb = Busboy({
headers: {
...event.headers,
'content-type': contentType,
},
});
bb.on('field', (name, value) => {
fields[name] = value;
});
bb.on('file', (fieldName, file, info) => {
const { filename, mimeType } = info;
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 exceeds 5MB limit'));
return;
}
chunks.push(chunk);
});
file.on('end', () => {
if (chunks.length > 0) {
files.push({
buffer: Buffer.concat(chunks),
mimeType: mimeType || 'application/octet-stream',
fileName: filename || 'unknown',
fieldName,
});
}
});
});
bb.on('finish', () => resolve());
bb.on('error', (err) => reject(new ApiError(400, err.message)));
bb.end(bodyBuffer);
});
/* 4⃣ FLAGS */
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
/* 5⃣ ACTIVITY PAYLOAD */
const activityPayload: any = normalizeJsonField(fields, 'activity');
if (!activityPayload) {
throw new ApiError(400, 'activity payload is required'); throw new ApiError(400, 'activity payload is required');
} }
/* 3️⃣ NORMALIZE ACTIVITY ID */ /* 6️⃣ NORMALIZE IDS */
if (activity.activityXid) { if (activityPayload.activityXid) {
activity.activityXid = Number(activity.activityXid); activityPayload.activityXid = Number(activityPayload.activityXid);
} }
/* 4⃣ ATTACH ACTIVITY MEDIA (S3 URLs) */ const numberKeys = [
if (!Array.isArray(media)) { 'currencyXid',
throw new ApiError(400, 'media must be an array'); 'energyLevelXid',
} 'activityDurationMins',
'activityTypeXid',
'frequenciesXid',
'trainerTotalAmount',
'pickupDropTotalPrice',
'navigationModeTotalPrice',
'sustainabilityScore',
'safetyScore',
'checkInLat',
'checkInLong',
'checkOutLat',
'checkOutLong',
];
activity.media = media.map((m: any) => ({ for (const key of numberKeys) {
mediaType: m.mediaType ?? 'image', if (activityPayload[key] !== undefined && activityPayload[key] !== null && activityPayload[key] !== '') {
mediaFileName: m.mediaFileName, activityPayload[key] = Number(activityPayload[key]);
isCoverImage: m.isCoverImage ?? false,
}));
/* 4.1️⃣ ATTACH SAFETY INSTRUCTIONS (string only) */
if (activity.safetyInstruction !== undefined && activity.safetyInstruction !== null) {
if (typeof activity.safetyInstruction !== 'string') {
throw new ApiError(400, 'safetyInstruction must be a string');
} }
} }
/* 4.2️⃣ ATTACH CANCELLATIONS (string only) */ /* 7⃣ NORMALIZE BOOLEANS */
if (activity.cancellations !== undefined && activity.cancellations !== null) { const booleanKeys = [
if (typeof activity.cancellations !== 'string') { 'isInstantBooking',
throw new ApiError(400, 'cancellations must be a string'); 'foodAvailable',
'foodIsChargeable',
'alcoholAvailable',
'trainerAvailable',
'trainerIsChargeable',
'pickUpDropAvailable',
'pickUpDropIsChargeable',
'inActivityAvailable',
'inActivityIsChargeable',
'equipmentAvailable',
'equipmentIsChargeable',
'cancellationAvailable',
'isCheckOutSame',
];
for (const key of booleanKeys) {
if (activityPayload[key] === 'true') activityPayload[key] = true;
if (activityPayload[key] === 'false') activityPayload[key] = false;
}
/* 8⃣ UPLOAD ACTIVITY-LEVEL MEDIA (images/videos) */
const uploadedActivityMedia: Array<{ mediaType?: string; mediaFileName: string }> = [];
for (const file of files.filter(
(f) => f.fieldName === 'activityImages' || f.fieldName === 'activityVideos',
)) {
const s3Key = `ActivityOnboarding/Activity_${activityPayload.activityXid}/Media/${Date.now()}_${file.fileName}`;
if (s3Key.length > 900) {
throw new ApiError(400, 'Generated S3 key too long');
}
await s3
.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: file.buffer,
ContentType: file.mimeType,
ACL: 'private',
})
.promise();
uploadedActivityMedia.push({
mediaType: file.mimeType,
mediaFileName: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`,
});
}
/* 🔥 MERGE ACTIVITY MEDIA */
const existingMedia = Array.isArray(activityPayload.media)
? activityPayload.media
: [];
activityPayload.media = [...existingMedia, ...uploadedActivityMedia];
/* 9⃣ PROCESS VENUE MEDIA UPLOADS */
// Group venue files by index: venueImages[0], venueImages[1], etc.
const venueFilesMap: Map<number, Array<{ buffer: Buffer; mimeType: string; fileName: string }>> = new Map();
for (const file of files) {
// Match patterns like: venueImages[0], venueVideos[1], etc.
const match = file.fieldName.match(/^venue(Images|Videos)\[(\d+)\]$/);
if (match) {
const venueIndex = parseInt(match[2], 10);
if (!venueFilesMap.has(venueIndex)) {
venueFilesMap.set(venueIndex, []);
}
venueFilesMap.get(venueIndex)!.push(file);
} }
} }
/* 5⃣ VALIDATION */ // Upload venue files and attach to corresponding venues
if (Array.isArray(activityPayload.venues)) {
for (let i = 0; i < activityPayload.venues.length; i++) {
const venue = activityPayload.venues[i];
const venueFiles = venueFilesMap.get(i) || [];
const uploadedVenueMedia: Array<{ mediaType?: string; mediaFileName: string }> = [];
for (const file of venueFiles) {
const s3Key = `ActivityOnboarding/Activity_${activityPayload.activityXid}/Venue_${i}/Media/${Date.now()}_${file.fileName}`;
if (s3Key.length > 900) {
throw new ApiError(400, 'Generated S3 key too long for venue media');
}
await s3
.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: file.buffer,
ContentType: file.mimeType,
ACL: 'private',
})
.promise();
uploadedVenueMedia.push({
mediaType: file.mimeType,
mediaFileName: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`,
});
}
// Merge with existing venue media
const existingVenueMedia = Array.isArray(venue.media) ? venue.media : [];
venue.media = [...existingVenueMedia, ...uploadedVenueMedia];
}
}
/* 🔟 VALIDATION */
let parsedDto: CreateActivityInput; let parsedDto: CreateActivityInput;
if (!isDraft) { if (!isDraft) {
const parsed = CreateActivityDto.safeParse(activity); const parsed = CreateActivityDto.safeParse(activityPayload);
if (!parsed.success) { if (!parsed.success) {
throw new ApiError( throw new ApiError(
400, 400,
parsed.error.issues.map((i) => i.message).join(', ') parsed.error.issues.map((i) => i.message).join(', '),
); );
} }
parsedDto = parsed.data; parsedDto = parsed.data;
} else { } else {
parsedDto = activity as CreateActivityInput; parsedDto = activityPayload as CreateActivityInput;
} }
/* 6️⃣ SAVE TO DB */ /* 1⃣1️⃣ SAVE ACTIVITY */
const result = await hostService.createOrUpdateActivity( const createdActivity = await hostService.createOrUpdateActivity(
userInfo.id, userInfo.id,
parsedDto, parsedDto,
isDraft isDraft,
); );
/* 7️⃣ RESPONSE */ /* 1⃣2️⃣ RESPONSE */
return { return {
statusCode: 200, statusCode: 200,
headers: { headers: {
@@ -109,9 +303,9 @@ export const handler = safeHandler(
success: true, success: true,
message: isDraft message: isDraft
? 'Activity saved as draft successfully' ? 'Activity saved as draft successfully'
: 'Activity submitted successfully', : 'Activity created successfully',
data: result, data: createdActivity,
}), }),
}; };
} },
); );

View File

@@ -25,7 +25,7 @@ export const handler = safeHandler(async (
} }
// Verify token and get user info // Verify token and get user info
await verifyHostToken(token); const userInfo = await verifyHostToken(token);
// Read optional search query (supports ?search= or ?q=) // Read optional search query (supports ?search= or ?q=)

View File

@@ -1,47 +0,0 @@
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const hostService = new HostService(prismaClient);
/**
* 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
await verifyMinglarAdminHostToken(token);
const activityXid = event.pathParameters?.activityXid
if (!activityXid) {
throw new ApiError(400, 'activityXid is required in path parameters');
}
const data = await hostService.getAllDetailsOfActivityAndVenue(Number(activityXid));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data,
}),
};
});

View File

@@ -1,50 +1,50 @@
// 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 { prismaClient } from '../../../../../common/database/prisma.lambda.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 hostService = new HostService(prismaClient); const hostService = new HostService(prismaClient);
// export const handler = safeHandler(async ( export const handler = safeHandler(async (
// event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
// context?: Context context?: Context
// ): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// 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, 'This is a protected route. Please provide a valid token.'); if (!token) throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
// const userInfo = await verifyHostToken(token); const userInfo = await verifyHostToken(token);
// let body: any = {}; let body: any = {};
// try { try {
// body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
// } catch (err) { } catch (err) {
// throw new ApiError(400, 'Invalid JSON in request body'); throw new ApiError(400, 'Invalid JSON in request body');
// } }
// const { activityTypeXid, frequenciesXid } = body; const { activityTypeXid, frequenciesXid } = body;
// if (!activityTypeXid) { if (!activityTypeXid) {
// throw new ApiError(400, 'activityTypeXid is required'); throw new ApiError(400, 'activityTypeXid is required');
// } }
// await hostService.createActivity( await hostService.createActivity(
// userInfo.id, userInfo.id,
// Number(activityTypeXid), Number(activityTypeXid),
// frequenciesXid ? Number(frequenciesXid) : undefined, frequenciesXid ? Number(frequenciesXid) : undefined,
// ); );
// return { return {
// statusCode: 201, statusCode: 201,
// 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: 'Activity created successfully', message: 'Activity created successfully',
// data: null, data: null,
// }), }),
// }; };
// }); });

View File

@@ -156,9 +156,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const pqqAnswerXid = Number(fields.pqqAnswerXid); const pqqAnswerXid = Number(fields.pqqAnswerXid);
const comments = fields.comments || null; const comments = fields.comments || null;
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity"); if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question"); if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Please select a valid answer"); if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// 6) UPSERT header // 6) UPSERT header
const existingHeader = await hostService.findHeaderByCompositeKey( const existingHeader = await hostService.findHeaderByCompositeKey(

View File

@@ -147,9 +147,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const pqqAnswerXid = Number(fields.pqqAnswerXid); const pqqAnswerXid = Number(fields.pqqAnswerXid);
const comments = fields.comments || null; const comments = fields.comments || null;
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity"); if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question"); if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Please select a valid answer"); if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// 6) UPSERT header // 6) UPSERT header
const existingHeader = await pqqService.findHeaderByCompositeKey( const existingHeader = await pqqService.findHeaderByCompositeKey(

View File

@@ -1,100 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { SchedulingService } from '../../../services/activityScheduling.service';
import { HostService } from '../../../services/host.service';
const schedulingService = new SchedulingService(prismaClient);
const hostService = new HostService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyHostToken(token);
const hostId = userInfo.id;
if (Number.isNaN(hostId)) {
throw new ApiError(400, 'Host id must be a number');
}
const host = await hostService.getHostIdByUserXid(hostId);
if (!host) {
throw new ApiError(404, 'Host not found');
}
let body: {
activityXid: number;
venueXid: number;
cancellations: {
scheduleHeaderXid: number;
occurenceDate: string;
startTime: string;
endTime: string;
cancellationReason: string
}[]
};
try {
body = event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON payload');
}
if (!body.activityXid || !body.venueXid || !Array.isArray(body.cancellations) || body.cancellations.length === 0) {
throw new ApiError(400, 'Missing required fields');
}
const activity = await schedulingService.getActivityByXid(body.activityXid);
if (!activity) {
throw new ApiError(404, "Activity not found");
}
const venueExists = await schedulingService.getVenueFromVenueXid(
body.venueXid,
body.activityXid
);
if (!venueExists) {
throw new ApiError(
404,
`Venue not found for this activity`
)
}
await schedulingService.cancelMultipleSlotsForActivity(
body.cancellations.map((item: any) => ({
scheduleHeaderXid: Number(item.scheduleHeaderXid),
occurenceDate: item.occurenceDate,
startTime: item.startTime,
endTime: item.endTime,
cancellationReason: item.cancellationReason
}))
);
const result = await schedulingService.getVenueDurationByAct(Number(body.activityXid), Number(hostId));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Slot blocked successfully',
data: result
}),
};
});

View File

@@ -1,89 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { SchedulingService } from '../../../services/activityScheduling.service';
import { HostService } from '../../../services/host.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { scheduleActivity } from '../../../../../common/utils/validation/host/createSchedulingOfAct.validation';
import { z } from 'zod';
const schedulingService = new SchedulingService(prismaClient);
const hostService = new HostService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyHostToken(token);
const hostId = userInfo.id;
if (Number.isNaN(hostId)) {
throw new ApiError(400, 'Host id must be a number');
}
const host = await hostService.getHostIdByUserXid(hostId);
if (!host) {
throw new ApiError(404, 'Host not found');
}
let body: unknown;
try {
body = event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON payload');
}
// ✅ Validate payload using Zod
const parsed = scheduleActivity.safeParse(body);
if (!parsed.success) {
const msg = parsed.error.issues
.map(e => e.message)
.join(', ');
throw new ApiError(400, `Validation failed: ${msg}`);
}
const activity = await schedulingService.getActivityByXid(parsed.data.activityXid);
if (!activity) {
throw new ApiError(404, "Activity not found");
}
if (parsed.data.venues && parsed.data.venues.length > 0) {
for (const venue of parsed.data.venues) {
const venueExists = await schedulingService.getVenueFromVenueXid(
venue.venueXid,
parsed.data.activityXid
);
if (!venueExists) {
throw new ApiError(
404,
`Venue with xid ${venue.venueXid} not found for this activity`
);
}
}
}
await schedulingService.addSchedulingForActivity(parsed.data);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Scheduling details updated successfully',
}),
};
});

View File

@@ -1,64 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { SchedulingService } from '../../../services/activityScheduling.service';
import { ACTIVITY_INTERNAL_STATUS } from '../../../../../common/utils/constants/host.constant';
const schedulingService = new SchedulingService(prismaClient);
/**
* GET /activities
* Query Parameters:
* - status: Listed | Unlisted | Not_Listed (optional - if not provided, returns all)
* - hostId: ID of host (required from token)
*
* Returns activities based on status filter
*/
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Get and verify token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
const userInfo = await verifyMinglarAdminHostToken(token);
const userId = Number(userInfo.id);
// Get status filter from query parameters
const status = event.queryStringParameters?.status as string | undefined;
const hostId = await schedulingService.getHostIdByUserId(userId);
// Validate status if provided
const validStatuses = [ACTIVITY_INTERNAL_STATUS.ACTIVITY_APPROVED, ACTIVITY_INTERNAL_STATUS.ACTIVITY_UNLISTED, ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED];
if (status && !validStatuses.includes(status)) {
throw new ApiError(400, `Invalid status. Must be one of: ${validStatuses.join(', ')}`);
}
// Get activities from service
const activities = await schedulingService.getActivitiesByStatus(hostId, status);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Activities retrieved successfully',
data: {
total: activities.length,
activities: activities,
filter: {
status: status || 'All',
},
},
}),
};
});

View File

@@ -1,52 +0,0 @@
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 { SchedulingService } from '../../../services/activityScheduling.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
const schedulingService = new SchedulingService(prismaClient);
/**
* GET /activities
* Query Parameters:
* - status: Listed | Unlisted | Not_Listed (optional - if not provided, returns all)
* - hostId: ID of host (required from token)
*
* Returns activities based on status filter
*/
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Get and verify token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
const userInfo = await verifyHostToken(token);
const userId = Number(userInfo.id);
const activityXid = event.pathParameters?.activityXid
if (!activityXid) {
throw new ApiError(400, 'activityXid is required in path parameters');
}
const hostId = await schedulingService.getHostIdByUserId(userId);
const result = await schedulingService.getVenueDurationByAct(Number(activityXid), Number(hostId));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Details retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,103 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { SchedulingService } from '../../../services/activityScheduling.service';
import { HostService } from '../../../services/host.service';
const schedulingService = new SchedulingService(prismaClient);
const hostService = new HostService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyHostToken(token);
const hostId = userInfo.id;
if (Number.isNaN(hostId)) {
throw new ApiError(400, 'Host id must be a number');
}
const host = await hostService.getHostIdByUserXid(hostId);
if (!host) {
throw new ApiError(404, 'Host not found');
}
let body: {
activityXid: number;
venueXid: number;
cancellations: { cancellationXid: number; }[];
};
try {
body = event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON payload');
}
if (
!body.activityXid ||
!body.venueXid ||
!Array.isArray(body.cancellations) ||
body.cancellations.length === 0
) {
throw new ApiError(400, 'Missing required fields');
}
const activity = await schedulingService.getActivityByXid(body.activityXid);
if (!activity) {
throw new ApiError(404, 'Activity not found');
}
const venueExists = await schedulingService.getVenueFromVenueXid(
body.venueXid,
body.activityXid,
);
if (!venueExists) {
throw new ApiError(404, `Venue not found for this activity`);
}
await schedulingService.openCanceledSlot(
body.cancellations.map((item: any) => ({
cancellationXid: Number(item.cancellationXid),
})),
);
const result = await schedulingService.getVenueDurationByAct(
Number(body.activityXid),
Number(hostId),
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Slot opened successfully',
data: result,
}),
};
},
);

View File

@@ -1,6 +1,6 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service'; import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
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';
@@ -25,8 +25,9 @@ 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);
// Accept agreement and get dynamic fields and PDF URL
const result = await hostService.acceptMinglarAgreement(userInfo.id); // Add suggestion using service
await hostService.acceptMinglarAgreement(userInfo.id);
return { return {
statusCode: 200, statusCode: 200,
@@ -37,10 +38,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Application accepted successfully', message: 'Application accepted successfully',
data: { data: null,
filePath: result.filePath,
dynamicFields: result.dynamicFields,
},
}), }),
}; };
}); });

View File

@@ -1,54 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { MinglarService } from '../../../../minglaradmin/services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
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 extract user info
const userInfo = await verifyMinglarAdminHostToken(token);
// ✅ Extract activityXid from query parameters
const activityXidParam = event.queryStringParameters?.activityXid;
if (!activityXidParam) {
throw new ApiError(400, 'Missing required query parameter: activityXid');
}
const activityXid = Number(activityXidParam);
if (isNaN(activityXid) || activityXid <= 0) {
throw new ApiError(400, 'Invalid activityXid provided. Must be a positive number.');
}
// ✅ Fetch suggestions from the service
const suggestions = await minglarService.getHostSuggestionsForActivity(activityXid);
// ✅ Return response
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

@@ -1,54 +0,0 @@
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const hostService = new HostService(prismaClient);
/**
* Get latest active agreement for a specific host by hostXid.
* Accessible for Minglar Admin / Host Admin using admin-host token.
*/
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(400, 'This is a protected route. Please provide a valid token.');
}
// Validate admin/host admin token
await verifyMinglarAdminHostToken(token);
const hostXidParam =
event.queryStringParameters?.hostXid ?? event.queryStringParameters?.host_xid;
const hostXid = Number(hostXidParam);
if (!hostXidParam) {
throw new ApiError(400, 'hostXid is required');
}
if (Number.isNaN(hostXid)) {
throw new ApiError(400, 'Invalid hostXid format');
}
const agreement = await hostService.getLatestHostAgreement(hostXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Latest host agreement retrieved successfully',
data: agreement,
}),
};
});

View File

@@ -23,7 +23,7 @@ export async function generateHostRefNumber(tx: any) {
const nextId = lastrecord ? lastrecord.id + 1 : 1; const nextId = lastrecord ? lastrecord.id + 1 : 1;
return `076-H-${String(nextId).padStart(6, '0')}`;; return `HS-${String(nextId).padStart(6, '0')}`;;
} }
export const handler = safeHandler(async ( export const handler = safeHandler(async (

View File

@@ -39,7 +39,6 @@ export const handler = safeHandler(async (
data: { data: {
stepper: host?.host?.stepper || null, stepper: host?.host?.stepper || null,
emailAddress: host.user?.emailAddress || null, emailAddress: host.user?.emailAddress || null,
hostId: host.user?.userRefNumber || null,
}, },
}), }),
}; };

View File

@@ -1,107 +0,0 @@
import { APIGatewayProxyHandler } from 'aws-lambda';
import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3';
import config from '../../../config/config';
import ApiError from '../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
const s3 = new S3Client({ region: config.aws.region });
function extractS3Key(input: string): string {
if (input.startsWith('s3://')) {
return input.replace(`s3://${config.aws.bucketName}/`, '');
}
if (input.startsWith('https://')) {
const url = new URL(input);
return url.pathname.replace(/^\/+/, '');
}
return input;
}
export const handler: APIGatewayProxyHandler = async (event) => {
try {
/* ---------------- AUTH ---------------- */
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
await verifyHostToken(token);
/* ---------------- BODY ---------------- */
const body = JSON.parse(event.body || '{}');
const { key, mediaSource, mediaId } = body;
if (mediaSource && mediaId) {
if (!['ACTIVITY', 'VENUE'].includes(mediaSource)) {
throw new ApiError(400, 'Invalid mediaSource');
}
/* ---------------- DB DELETE ---------------- */
if (mediaSource === 'ACTIVITY') {
const media = await prismaClient.activitiesMedia.findUnique({
where: { id: Number(mediaId) },
});
if (!media) throw new ApiError(404, 'Activity media not found');
await prismaClient.activitiesMedia.delete({
where: { id: media.id },
});
}
if (mediaSource === 'VENUE') {
const media = await prismaClient.activityVenueArtifacts.findUnique({
where: { id: Number(mediaId) },
});
if (!media) throw new ApiError(404, 'Venue media not found');
await prismaClient.activityVenueArtifacts.delete({
where: { id: media.id },
});
}
}
const s3Key = extractS3Key(key);
/* ---------------- PATH SAFETY ---------------- */
const allowedPrefixes = ['ActivityOnboarding/'];
if (!allowedPrefixes.some((p) => s3Key.startsWith(p))) {
throw new ApiError(403, 'Unauthorized delete path');
}
/* ---------------- S3 DELETE ---------------- */
await s3.send(
new DeleteObjectCommand({
Bucket: config.aws.bucketName!,
Key: s3Key,
}),
);
return response(200, {
success: true,
message: 'Media deleted from DB and S3 successfully',
});
} catch (err: any) {
console.error('ERROR:', err);
if (err instanceof ApiError) {
return response(err.statusCode, err.message);
}
return response(500, 'Internal server error');
}
};
function response(statusCode: number, body: any) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(body),
};
}

View File

@@ -1,104 +0,0 @@
import { APIGatewayProxyHandler } from 'aws-lambda';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { v4 as uuid } from 'uuid';
import ApiError from '../../../common/utils/helper/ApiError';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { HostService } from '../services/host.service';
import config from '../../../config/config';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
const s3 = new S3Client({ region: config.aws.region });
const hostService = new HostService(prismaClient);
export const handler: APIGatewayProxyHandler = async (event) => {
try {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
await verifyHostToken(token);
const body = JSON.parse(event.body || '{}');
const { files, venueTempId } = body;
if (!venueTempId) {
throw new ApiError(400, 'venueTempId is required');
}
if (!Array.isArray(files) || files.length === 0) {
throw new ApiError(400, 'files array is required');
}
const activityXid = event.pathParameters?.activityXid;
if (!activityXid) {
throw new ApiError(400, 'activityXid is required in path parameters');
}
const activityDetails = await hostService.getActivityDetailsById(Number(activityXid));
if (!activityDetails) {
throw new ApiError(404, 'Activity not found');
}
const results: Array<any> = [];
for (const file of files) {
const { fileName, mimeType } = file;
if (!fileName || !mimeType) {
throw new ApiError(400, 'Each file must have fileName and mimeType');
}
const safeFileName = fileName
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9._-]/g, '')
.toLowerCase();
const key = `ActivityOnboarding/Activity_${activityXid}/Venues/${venueTempId}/${uuid()}_${safeFileName}`;
const command = new PutObjectCommand({
Bucket: config.aws.bucketName!,
Key: key,
ContentType: mimeType,
});
const uploadUrl = await getSignedUrl(s3, command, {
expiresIn: 300, // 5 minutes
});
results.push({
uploadUrl,
key,
fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`,
});
}
return response(200, {
venueTempId,
files: results,
});
} catch (err: any) {
console.error('ERROR:', err);
// If it's your ApiError, return its status & message
if (err instanceof ApiError) {
return response(err.statusCode, err.message);
}
// Fallback for unknown errors
return response(500, 'Internal server error');
}
};
function response(statusCode: number, body: any) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(body),
};
}

View File

@@ -1,98 +0,0 @@
import { APIGatewayProxyHandler } from 'aws-lambda';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { v4 as uuid } from 'uuid';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { HostService } from '../services/host.service';
import ApiError from '../../../common/utils/helper/ApiError';
import config from '../../../config/config';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
const s3 = new S3Client({ region: config.aws.region });
const hostService = new HostService(prismaClient);
export const handler: APIGatewayProxyHandler = async (event) => {
try {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
await verifyHostToken(token);
const body = JSON.parse(event.body || '{}');
const { files } = body;
if (!Array.isArray(files) || files.length === 0) {
throw new ApiError(400, 'files array is required');
}
const activityXid = event.pathParameters?.activityXid;
if (!activityXid) {
throw new ApiError(400, 'activityXid is required in path parameters');
}
const activityDetails = await hostService.getActivityDetailsById(Number(activityXid));
if (!activityDetails) {
throw new ApiError(404, 'Activity not found');
}
const results = [];
for (const file of files) {
const { fileName, mimeType } = file;
if (!fileName || !mimeType) {
throw new ApiError(400, 'Each file must have fileName and mimeType');
}
const safeFileName = fileName
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9._-]/g, '')
.toLowerCase();
const key = `ActivityOnboarding/Activity_${activityXid}/Artifacts/${uuid()}_${safeFileName}`;
const command = new PutObjectCommand({
Bucket: config.aws.bucketName!,
Key: key,
ContentType: mimeType,
});
const uploadUrl = await getSignedUrl(s3, command, {
expiresIn: 300,
});
results.push({
uploadUrl,
key,
fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`,
});
}
return response(200, { files: results });
} catch (err: any) {
console.error('ERROR:', err);
// If it's your ApiError, return its status & message
if (err instanceof ApiError) {
return response(err.statusCode, err.message);
}
// Fallback for unknown errors
return response(500, 'Internal server error');
}
};
function response(statusCode: number, body: any) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(body),
};
}

View File

@@ -1,344 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import AWS from 'aws-sdk';
import dayjs from 'dayjs';
import { z } from 'zod';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from "../../../common/utils/helper/ApiError";
import { parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData';
import config from '../../../config/config';
const s3 = new AWS.S3({
region: config.aws.region,
});
const updateHostProfileSchema = z
.strictObject({
// Personal
fullName: z.string().min(1).optional(),
firstName: z.string().min(1).optional(),
lastName: z.string().min(1).optional(),
isdCode: z.string().min(1).max(6).optional(),
mobileNumber: z.string().min(5).max(15).optional(),
dateOfBirth: z.string().min(1).optional(),
profileImage: z.string().url().optional(),
// Address
address1: z.string().min(1).optional(),
address2: z.string().min(1).optional(),
countryXid: z.number().int().positive().optional(),
stateXid: z.number().int().positive().optional(),
cityXid: z.number().int().positive().optional(),
pinCode: z.string().min(1).optional(),
// explicitly forbidden
emailAddress: z.any().optional(),
})
.strip();
async function uploadProfileImageToS3(buffer: Buffer, mimeType: string, originalName: string, userId: number) {
const sanitizeFileName = (name: string) => {
return name
.toLowerCase()
.replace(/[^a-z0-9.]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
};
const fileExtension = originalName.split('.').pop() || 'jpg';
const fileName = `profile_image.${fileExtension}`;
const sanitizedFileName = sanitizeFileName(fileName);
const s3Key = `Host/ProfileImages/${userId}/${sanitizedFileName}`;
await s3
.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private',
})
.promise();
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
}
function parseDob(dateOfBirth: string): Date {
const parsed = dayjs(dateOfBirth, ['YYYY-MM-DD', 'MM/DD/YYYY', 'DD/MM/YYYY'], true);
if (!parsed.isValid()) {
throw new ApiError(400, 'Invalid dateOfBirth. Use YYYY-MM-DD (recommended) or MM/DD/YYYY.');
}
return parsed.toDate();
}
function splitFullName(fullName: string): { firstName: string; lastName: string | null } {
const parts = fullName.trim().split(/\s+/).filter(Boolean);
const firstName = parts[0] || '';
const lastName = parts.length > 1 ? parts.slice(1).join(' ') : null;
return { firstName, lastName };
}
function getAuthToken(event: APIGatewayProxyEvent): string {
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.');
}
return token;
}
function parseJsonBody(event: APIGatewayProxyEvent): any {
try {
return event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON in request body');
}
}
function validateBody(body: any) {
const parsed = updateHostProfileSchema.safeParse(body);
if (!parsed.success) {
throw new ApiError(400, parsed.error.issues.map((i) => i.message).join(', '));
}
if (parsed.data.emailAddress !== undefined) {
throw new ApiError(400, 'Email address cannot be updated.');
}
return parsed.data;
}
function normalizeNameFields(data: any): { firstName?: string; lastName?: string | null } {
if (data.fullName && !data.firstName && !data.lastName) {
const split = splitFullName(data.fullName);
return { firstName: split.firstName, lastName: split.lastName };
}
return { firstName: data.firstName, lastName: data.lastName };
}
function buildAddressInput(data: any) {
return {
address1: data.address1,
address2: data.address2,
countryXid: data.countryXid,
stateXid: data.stateXid,
cityXid: data.cityXid,
pinCode: data.pinCode,
};
}
function hasAnyDefined(obj: Record<string, unknown>) {
return Object.values(obj).some((v) => v !== undefined);
}
async function ensureHostUser(tx: any, userId: number) {
const user = await tx.user.findUnique({
where: { id: userId, isActive: true },
select: { id: true, roleXid: true },
});
if (!user) throw new ApiError(404, 'User not found');
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
}
async function updateUserIfNeeded(
tx: any,
userId: number,
input: {
firstName?: string;
lastName?: string | null;
isdCode?: string;
mobileNumber?: string;
dateOfBirth?: string;
profileImage?: string;
},
) {
const userUpdateData: any = {};
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
if (input.dateOfBirth !== undefined) {
userUpdateData.dateOfBirth = input.dateOfBirth ? parseDob(input.dateOfBirth) : null;
}
if (input.profileImage !== undefined) {
userUpdateData.profileImage = input.profileImage || null;
}
if (!hasAnyDefined(userUpdateData)) return;
await tx.user.update({
where: { id: userId },
data: {
...userUpdateData,
isProfileUpdated: true,
},
});
}
async function upsertAddressIfNeeded(tx: any, userId: number, addressData: Record<string, any>) {
if (!hasAnyDefined(addressData)) return;
const existingAddress = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: { id: true },
});
const addressUpdateData: any = {};
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
if (existingAddress) {
await tx.userAddressDetails.update({
where: { id: existingAddress.id },
data: addressUpdateData,
});
return;
}
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
const missing = required.filter((k) => addressData[k] === undefined);
if (missing.length) {
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
}
await tx.userAddressDetails.create({
data: {
userXid: userId,
...addressUpdateData,
},
});
}
async function getProfileSnapshot(tx: any, userId: number) {
const updated = await tx.user.findUnique({
where: { id: userId },
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
isdCode: true,
mobileNumber: true,
dateOfBirth: true,
profileImage: true,
isProfileUpdated: true,
userAddressDetails: {
where: { isActive: true },
take: 1,
select: {
id: true,
address1: true,
address2: true,
countryXid: true,
stateXid: true,
cityXid: true,
pinCode: true,
},
},
},
});
return {
user: updated,
address: updated?.userAddressDetails?.[0] ?? null,
};
}
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = getAuthToken(event);
const userInfo = await verifyHostToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user id');
}
const contentType = event.headers['Content-Type'] || event.headers['content-type'] || '';
const isMultipart = contentType.includes('multipart/form-data');
let body: any;
if (isMultipart) {
const isBase64Encoded = event.isBase64Encoded || false;
const { fields, files } = parseMultipartFormData(event.body || null, contentType, isBase64Encoded);
const multipartBody: any = {};
const copyIfPresent = (key: string) => {
if (fields[key] !== undefined) {
multipartBody[key] = fields[key];
}
};
['fullName', 'firstName', 'lastName', 'isdCode', 'mobileNumber', 'dateOfBirth', 'address1', 'address2', 'pinCode'].forEach(
copyIfPresent,
);
const parseNumberField = (key: string) => {
if (fields[key] !== undefined) {
const value = Number(fields[key]);
if (!Number.isNaN(value)) {
multipartBody[key] = value;
}
}
};
['countryXid', 'stateXid', 'cityXid'].forEach(parseNumberField);
const profileImageFile = files.find((f) => f.fieldName === 'profileImage');
if (profileImageFile) {
const uploadedUrl = await uploadProfileImageToS3(
profileImageFile.data,
profileImageFile.contentType,
profileImageFile.fileName,
userId,
);
multipartBody.profileImage = uploadedUrl;
} else if (fields.profileImage) {
multipartBody.profileImage = fields.profileImage;
}
body = multipartBody;
} else {
body = parseJsonBody(event);
}
const data = validateBody(body);
const name = normalizeNameFields(data);
const address = buildAddressInput(data);
const result = await prismaClient.$transaction(async (tx) => {
await ensureHostUser(tx, userId);
await updateUserIfNeeded(tx, userId, {
firstName: name.firstName,
lastName: name.lastName,
isdCode: data.isdCode,
mobileNumber: data.mobileNumber,
dateOfBirth: data.dateOfBirth,
profileImage: data.profileImage,
});
await upsertAddressIfNeeded(tx, userId, address);
return getProfileSnapshot(tx, userId);
});
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Profile updated successfully',
data : null// no data payload per request
}),
};
});

View File

@@ -1,743 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import {
ACTIVITY_AM_DISPLAY_STATUS,
ACTIVITY_AM_INTERNAL_STATUS,
ACTIVITY_DISPLAY_STATUS,
ACTIVITY_INTERNAL_STATUS,
SCHEDULING_TYPE,
} from '../../../common/utils/constants/host.constant';
import ApiError from '../../../common/utils/helper/ApiError';
import { ScheduleActivityDTO } from '../../../common/utils/validation/host/createSchedulingOfAct.validation';
import config from '../../../config/config';
const bucket = config.aws.bucketName;
@Injectable()
export class SchedulingService {
constructor(private prisma: PrismaClient) { }
async getHostIdByUserId(userId: number) {
const host = await this.prisma.hostHeader.findFirst({
where: {
userXid: userId,
isActive: true,
},
select: {
id: true,
},
});
if (!host) {
throw new ApiError(404, 'Host not found for the given user.');
}
return host.id;
}
async addSchedulingForActivity(data: ScheduleActivityDTO) {
const {
activityXid,
listNow,
scheduleType,
dateRange,
rules,
venues,
earlyCheckInMins,
bookingCutOffMins,
isLateCheckingAllowed,
isInstantBooking,
} = data;
return this.prisma.$transaction(async (tx) => {
const venueXids = venues.map((v) => v.venueXid);
/* ----------------------------------
🧹 0⃣ DELETE OLD SCHEDULING (PER ACTIVITY + VENUES)
---------------------------------- */
const oldHeaders = await tx.scheduleHeader.findMany({
where: {
activityXid,
activityVenueXid: { in: venueXids },
isActive: true,
},
select: { id: true },
});
const headerIds = oldHeaders.map((h) => h.id);
if (headerIds.length) {
// Delete in correct FK order
await tx.cancellations.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleDetails.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleOccurences.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleRecurrence.deleteMany({
where: { scheduleHeaderXid: { in: headerIds } },
});
await tx.scheduleHeader.deleteMany({
where: { id: { in: headerIds } },
});
}
/* ----------------------------------
1⃣ CREATE NEW SCHEDULING
---------------------------------- */
const createdHeaders: number[] = [];
if (
isInstantBooking !== undefined ||
isLateCheckingAllowed !== undefined
) {
await tx.activities.update({
where: { id: activityXid, isActive: true },
data: { isInstantBooking, isLateCheckingAllowed },
});
}
if (listNow) {
await tx.activities.update({
where: { id: activityXid, isActive: true },
data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_LISTED,
},
});
}
for (const venue of venues) {
if (!venue.slots || venue.slots.length === 0) {
continue;
}
const header = await tx.scheduleHeader.create({
data: {
activityXid,
activityVenueXid: venue.venueXid,
scheduleType,
startDate: new Date(dateRange.startDate),
endDate: dateRange.endDate ? new Date(dateRange.endDate) : null,
earlyCheckInMins,
bookingCutOffMins,
isActive: true,
},
});
createdHeaders.push(header.id);
// WEEKLY
if (scheduleType === SCHEDULING_TYPE.WEEKLY) {
const uniqueWeekdays = [
...new Set(
venue.slots
.map((s) => s.weekDay)
.filter(
(
d,
): d is
| 'MONDAY'
| 'TUESDAY'
| 'WEDNESDAY'
| 'THURSDAY'
| 'FRIDAY'
| 'SATURDAY'
| 'SUNDAY' => !!d,
),
),
];
if (!uniqueWeekdays.length) {
throw new ApiError(
400,
'Weekly schedule requires weekDay in slots',
);
}
await tx.scheduleRecurrence.createMany({
data: uniqueWeekdays.map((day) => ({
scheduleHeaderXid: header.id,
weekDay: day,
isActive: true,
})),
});
}
// MONTHLY
if (scheduleType === SCHEDULING_TYPE.MONTHLY) {
const uniqueDays = [
...new Set(
venue.slots
.map((s) => s.dayOfMonth)
.filter((d): d is number => d !== null && d !== undefined),
),
];
if (!uniqueDays.length) {
throw new ApiError(
400,
'Monthly schedule requires dayOfMonth in slots',
);
}
await tx.scheduleRecurrence.createMany({
data: uniqueDays.map((day) => ({
scheduleHeaderXid: header.id,
dayOfMonth: day,
isActive: true,
})),
});
}
// CUSTOM / ONCE
if (
scheduleType === SCHEDULING_TYPE.CUSTOM ||
scheduleType === SCHEDULING_TYPE.ONCE
) {
const uniqueDates = [
...new Set(
venue.slots.map((s) => s.occurrenceDate).filter(Boolean),
),
];
await tx.scheduleOccurences.createMany({
data: uniqueDates.map((d) => ({
scheduleHeaderXid: header.id,
occurenceDate: new Date(d!),
isActive: true,
})),
});
}
// Slots
for (const slot of venue.slots) {
await tx.scheduleDetails.create({
data: {
scheduleHeaderXid: header.id,
occurenceDate: slot.occurrenceDate
? new Date(slot.occurrenceDate)
: null,
weekDay: slot.weekDay ?? null,
dayOfMonth: slot.dayOfMonth ?? null,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
isActive: true,
},
});
}
}
return { success: true, scheduleHeaderIds: createdHeaders };
});
}
async getAvailableSlotsForDate(activityXid: number, selectedDate: string) {
const date = new Date(selectedDate);
if (isNaN(date.getTime())) {
throw new ApiError(400, 'Invalid date format');
}
const weekDay = date
.toLocaleDateString('en-US', { weekday: 'long' })
.toUpperCase();
const dayOfMonth = date.getDate();
/* --------------------------------
1⃣ FETCH ACTIVE SCHEDULE HEADERS
-------------------------------- */
const scheduleHeaders = await this.prisma.scheduleHeader.findMany({
where: {
activityXid,
isActive: true,
startDate: { lte: date },
OR: [{ endDate: null }, { endDate: { gte: date } }],
},
include: {
activityVenue: {
select: {
id: true,
venueName: true,
venueLabel: true,
venueCapacity: true,
},
},
scheduleRecurrences: {
where: { isActive: true },
},
ScheduleDetails: {
where: {
isActive: true,
OR: [
{ occurenceDate: date }, // ONLY_ONCE / CUSTOM
{ weekDay: weekDay }, // WEEKLY
{ dayOfMonth: dayOfMonth }, // MONTHLY
],
},
},
Cancellations: {
where: {
occurenceDate: date,
isActive: true,
},
},
},
});
if (!scheduleHeaders.length) {
return [];
}
/* --------------------------------
2⃣ BUILD RESPONSE
-------------------------------- */
const response = [];
for (const header of scheduleHeaders) {
// Build cancellation set using time matching
const cancelledSlots = new Set(
header.Cancellations.map(
(c) => `${c.startTime}-${c.endTime}`
)
);
const slots = header.ScheduleDetails
.filter(
(slot) =>
!cancelledSlots.has(`${slot.startTime}-${slot.endTime}`)
)
.map((slot) => ({
slotId: slot.id,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
}));
if (!slots.length) continue;
response.push({
venueXid: header.activityVenue.id,
venueName: header.activityVenue.venueName,
venueLabel: header.activityVenue.venueLabel,
venueCapacity: header.activityVenue.venueCapacity,
slots,
});
}
return response;
}
/**
* Return full schedule header + venue + slots for a given activity and date
*/
async getScheduleDetailsForDate(activityXid: number, selectedDate: string) {
const date = new Date(selectedDate);
if (isNaN(date.getTime())) {
throw new ApiError(400, 'Invalid date format');
}
const weekDay = date
.toLocaleDateString('en-US', { weekday: 'long' })
.toUpperCase();
const dayOfMonth = date.getDate();
const scheduleHeaders = await this.prisma.scheduleHeader.findMany({
where: {
activityXid,
isActive: true,
startDate: { lte: date },
OR: [{ endDate: null }, { endDate: { gte: date } }],
ScheduleDetails: {
some: {}
}
},
include: {
activityVenue: {
select: {
id: true,
venueName: true,
venueLabel: true,
venueCapacity: true,
},
},
scheduleRecurrences: {
where: { isActive: true },
},
ScheduleDetails: {
where: {
isActive: true,
OR: [
{ occurenceDate: date },
{ weekDay: weekDay },
{ dayOfMonth: dayOfMonth },
],
},
},
Cancellations: {
where: {
occurenceDate: date,
isActive: true,
},
},
},
});
if (!scheduleHeaders.length) return [];
const response = scheduleHeaders.map((header) => {
// Match cancelled slots using startTime + endTime
const cancelledSlots = new Set(
header.Cancellations.map(
(c) => `${c.startTime}-${c.endTime}`
)
);
const slots = header.ScheduleDetails
.filter(
(slot) =>
!cancelledSlots.has(`${slot.startTime}-${slot.endTime}`)
)
.map((slot) => ({
slotId: slot.id,
occurenceDate: slot.occurenceDate,
weekDay: slot.weekDay,
dayOfMonth: slot.dayOfMonth,
startTime: slot.startTime,
endTime: slot.endTime,
maxCapacity: slot.maxCapacity,
}));
return {
scheduleHeaderXid: header.id,
scheduleType: header.scheduleType,
startDate: header.startDate,
endDate: header.endDate,
earlyCheckInMins: header.earlyCheckInMins,
bookingCutOffMins: header.bookingCutOffMins,
activityVenue: {
venueXid: header.activityVenue.id,
venueName: header.activityVenue.venueName,
venueLabel: header.activityVenue.venueLabel,
venueCapacity: header.activityVenue.venueCapacity,
},
slots, // only active slots, no cancellation flag
};
});
return response;
}
async getVenueFromVenueXid(venueXid: number, activityXid: number) {
return await this.prisma.activityVenues.findUnique({
where: { id: venueXid, activityXid: activityXid, isActive: true },
select: {
id: true,
venueName: true,
venueLabel: true,
venueCapacity: true,
},
});
}
async getActivityByXid(activityXid: number) {
return await this.prisma.activities.findUnique({
where: { id: activityXid, isActive: true },
select: {
id: true,
activityTitle: true,
},
});
}
/**
* Get activities by status and host ID
* @param hostId - ID of the host
* @param status - Filter by status (Listed, Unlisted, Not_Listed) - optional
* @returns Array of activities matching the criteria
*/
async getActivitiesByStatus(hostId: number, status?: string) {
// Build where clause
const whereClause: any = {
hostXid: hostId,
isActive: true,
deletedAt: null,
activityInternalStatus: {
in: [
ACTIVITY_INTERNAL_STATUS.ACTIVITY_APPROVED,
ACTIVITY_INTERNAL_STATUS.ACTIVITY_UNLISTED,
ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
],
},
};
// Add status filter if provided
if (status) {
whereClause.activityInternalStatus = status;
}
// Query activities
const activities = await this.prisma.activities.findMany({
where: whereClause,
select: {
id: true,
activityRefNumber: true,
activityTitle: true,
activityDescription: true,
activityDisplayStatus: true,
activityInternalStatus: true,
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
ScheduleHeader: {
where: { isActive: true },
select: {
id: true,
scheduleType: true,
startDate: true,
},
orderBy: { createdAt: 'desc' },
},
},
orderBy: {
createdAt: 'desc',
},
});
for (const activity of activities) {
if (activity.ActivitiesMedia?.length) {
for (const media of activity.ActivitiesMedia) {
if (!media.mediaFileName) continue;
const key = media.mediaFileName.startsWith('http')
? media.mediaFileName.split('.com/')[1]
: media.mediaFileName;
(media as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
}
// Transform response
return activities.map((activity) => ({
activityId: activity.id,
activityRefNumber: activity.activityRefNumber,
activityName: activity.activityTitle,
activityDescription: activity.activityDescription,
activityInternalStatus: activity.activityInternalStatus,
activityDisplayStatus: activity.activityDisplayStatus,
media: activity.ActivitiesMedia,
scheduleType: activity.ScheduleHeader?.length
? activity.ScheduleHeader[0].scheduleType
: null,
scheduleStartDate: activity.ScheduleHeader?.length
? activity.ScheduleHeader[0].startDate
: null,
}));
}
async getVenueDurationByAct(activityXid: number, hostId: number) {
const result = await this.prisma.activities.findUnique({
where: { id: activityXid, hostXid: hostId, isActive: true },
select: {
id: true,
activityDurationMins: true,
activityTitle: true,
activityRefNumber: true,
isLateCheckingAllowed: true,
isInstantBooking: true,
frequenciesXid: true,
frequency: {
where: { isActive: true },
select: {
id: true,
frequencyName: true,
},
},
ActivityVenues: {
where: { isActive: true },
select: {
id: true,
venueName: true,
venueLabel: true,
ScheduleHeader: {
where: { isActive: true },
select: {
id: true,
scheduleType: true,
startDate: true,
endDate: true,
earlyCheckInMins: true,
bookingCutOffMins: true,
ScheduleDetails: {
where: { isActive: true },
select: {
id: true,
occurenceDate: true,
weekDay: true,
dayOfMonth: true,
startTime: true,
endTime: true,
},
},
Cancellations: {
where: { isActive: true },
select: {
id: true,
cancellationReason: true,
occurenceDate: true,
startTime: true,
endTime: true,
},
},
scheduleOccurences: {
where: { isActive: true },
select: {
id: true,
occurenceDate: true,
},
},
scheduleRecurrences: {
where: { isActive: true },
select: {
id: true,
weekDay: true,
dayOfMonth: true,
},
},
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
},
});
if (!result) return null;
if (result.ActivitiesMedia?.length) {
for (const media of result.ActivitiesMedia) {
if (!media.mediaFileName) continue;
const key = media.mediaFileName.startsWith('http')
? media.mediaFileName.split('.com/')[1]
: media.mediaFileName;
(media as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
for (const venue of result.ActivityVenues ?? []) {
for (const header of venue.ScheduleHeader ?? []) {
/* -------------------------------
📅 FRONTEND FRIENDLY META
-------------------------------- */
// WEEKLY → send weekdays
if (header.scheduleType === 'WEEKLY') {
(header as any).scheduleDays = [
...new Set(
header.scheduleRecurrences?.map((r) => r.weekDay).filter(Boolean),
),
];
}
// MONTHLY → send dates (131)
if (header.scheduleType === 'MONTHLY') {
(header as any).scheduleDates = [
...new Set(
header.scheduleRecurrences
?.map((r) => r.dayOfMonth)
.filter(Boolean),
),
];
}
// CUSTOM / ONCE → send exact dates
if (
header.scheduleType === 'CUSTOM' ||
header.scheduleType === 'ONCE'
) {
(header as any).scheduleDates = [
...new Set(
header.scheduleOccurences
?.map((o) => o.occurenceDate?.toISOString().split('T')[0])
.filter(Boolean),
),
];
}
}
}
(result as any).availableScheduleTypes = [
'ONCE',
'WEEKLY',
'MONTHLY',
'CUSTOM',
];
return result;
}
async cancelMultipleSlotsForActivity(
cancellations: {
scheduleHeaderXid: number;
occurenceDate: string;
startTime: string;
endTime: string;
cancellationReason?: string;
}[],
) {
return await this.prisma.cancellations.createMany({
data: cancellations.map((item) => ({
scheduleHeaderXid: item.scheduleHeaderXid,
occurenceDate: item.occurenceDate,
startTime: item.startTime,
endTime: item.endTime,
cancellationReason: item.cancellationReason || 'No reason provided',
})),
skipDuplicates: true,
});
}
async openCanceledSlot(
cancellations: { cancellationXid: number; }[],
) {
return await this.prisma.cancellations.updateMany({
where: {
id: { in: cancellations.map((c) => c.cancellationXid) },
},
data: {
isActive: false,
},
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -53,10 +53,10 @@ export class TokenService {
config.jwt.secret config.jwt.secret
); );
// Optionally keep existing refresh tokens alive instead of deleting await this.prisma.token.deleteMany({
// Removed deleteMany call so the same refresh token can be used multiple where: { userXid: user_xid }
// times. If you want to limit refresh tokens later you can implement })
// rotation or blacklist logic elsewhere.
await this.prisma.token.create({ await this.prisma.token.create({
data: { data: {
token: refreshToken.token, token: refreshToken.token,

View File

@@ -1,59 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendActivityAcceptanceMailtoHost } from '../../../../minglaradmin/services/approvalMailtoHost.service';
import { MinglarService } from '../../../services/minglar.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.acceptActivityApplicationByAM(
Number(activityId),
Number(userInfo.id)
);
const hostXid = await minglarService.getHostXidByActivityId(activityId)
const hostDetails = await minglarService.getUserDetails(hostXid)
await sendActivityAcceptanceMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Approved activity details application successfully',
data: null,
}),
};
});

View File

@@ -1,89 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
// import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant';
const minglarService = new MinglarService(prismaClient);
interface AddSuggestionBody {
title: string;
comments: string;
activity_xid:number
}
/**
* Add suggestion handler for host applications
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
*/
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Verify authentication token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyMinglarAdminToken(token);
// Get user details
const user = await prismaClient.user.findUnique({
where: { id: userInfo.id },
select: { id: true, roleXid: true }
});
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: AddSuggestionBody;
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { title, comments , activity_xid} = body;
if (!title) {
throw new ApiError(400, 'Title is required');
}
if (!comments) {
throw new ApiError(400, 'Comments are required');
}
if(!activity_xid){
throw new ApiError(400 , "Activity Pqq HeaderXid Required");
}
// Validate title is one of the allowed types
// const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
// if (!allowedTitles.includes(title)) {
// throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
// }
// Add suggestion using service
await minglarService.addActivtiySuggestion(title, comments, activity_xid,user.id);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Suggestion added successfully',
data: null,
}),
};
});

View File

@@ -1,59 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendActivityRejectionMailtoHost } from '../../../../minglaradmin/services/rejectionMailtoHost.service';
import { MinglarService } from '../../../services/minglar.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.rejectActivityApplicationByAM(
Number(activityId),
Number(userInfo.id)
);
const hostXid = await minglarService.getHostXidByActivityId(activityId)
const hostDetails = await minglarService.getUserDetails(hostXid)
await sendActivityRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Rejected activity details application successfully',
data: null,
}),
};
});

View File

@@ -5,7 +5,6 @@ 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';
import config from '../../../../../config/config';
const minglarService = new MinglarService(prismaClient); const minglarService = new MinglarService(prismaClient);
@@ -48,7 +47,7 @@ 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(hostXid) const hostDetails = await minglarService.getUserDetails(hostXid)
await sendAMRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName, config.HOST_LINK) await sendAMRejectionMailtoHost(hostDetails.emailAddress, hostDetails.firstName)
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -27,6 +27,7 @@ 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 emailToLowerCase = email.toLowerCase() const emailToLowerCase = email.toLowerCase()
@@ -34,6 +35,7 @@ export const handler = safeHandler(async (
where: { emailAddress: emailToLowerCase, isActive: true, userStatus: USER_STATUS.INVITED }, 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.');

View File

@@ -90,44 +90,6 @@ export async function sendAMPQQAcceptanceMailtoHost(
<p>Congratulations, Your activity onboarding application to minglar admin has been approved.</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 start adding other details of your activity through the host panel.</p>
<p> You can login to your account using the link below:<br/> <p> You can login to your account using the link below:<br/>
<strong>Link:</strong> ${config.HOST_LINK_PQ} </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.");
}
}
export async function sendActivityAcceptanceMailtoHost(
emailAddress: string,
name: string
): Promise<{
sent: boolean;
// messageId: string
}> {
const subject = "Approval for your activity details application";
const htmlContent = `
<p>Dear ${name},</p>
<p>Congratulations, Your activity details application to minglar admin has been approved.</p>
<p>You can start getting orders for your activity.</p>
<p>You can login to your account using the link below:<br/>
<strong>Link:</strong> ${config.HOST_LINK} </p> <strong>Link:</strong> ${config.HOST_LINK} </p>
<p>Best regards,<br/>Minglar Team</p> <p>Best regards,<br/>Minglar Team</p>
`; `;

View File

@@ -23,9 +23,10 @@ import {
import { PaginationOptions } from '@/common/utils/pagination/pagination.types'; import { PaginationOptions } from '@/common/utils/pagination/pagination.types';
import config from '@/config/config'; import config from '@/config/config';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaClient, User } from '@prisma/client'; import { User } from '@prisma/client';
import * as bcrypt from 'bcryptjs'; import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { PrismaClient } from '@prisma/client';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto'; import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto';
import { sendAMEmailForHostAssign } from './AMEmail.service'; import { sendAMEmailForHostAssign } from './AMEmail.service';
@@ -34,7 +35,7 @@ const bucket = config.aws.bucketName;
@Injectable() @Injectable()
export class MinglarService { export class MinglarService {
constructor(private prisma: PrismaService | PrismaClient) {} constructor(private prisma: PrismaService | PrismaClient) { }
async createPassword(user_xid: number, password: string): Promise<boolean> { async createPassword(user_xid: number, password: string): Promise<boolean> {
// Find user by id // Find user by id
@@ -137,15 +138,15 @@ export class MinglarService {
async getHostXidByActivityId(activityId: number) { async getHostXidByActivityId(activityId: number) {
const activityDetails = await this.prisma.activities.findFirst({ const activityDetails = await this.prisma.activities.findFirst({
where: { id: activityId }, where: { id: activityId }
}); })
return activityDetails.hostXid; return activityDetails.hostXid;
} }
async getUserDetails(id: number) { async getUserDetails(id: number) {
const hostDetail = await this.prisma.hostHeader.findFirst({ const hostDetail = await this.prisma.hostHeader.findFirst({
where: { id: id }, where: { id: id }
}); })
const userDetails = await this.prisma.user.findUnique({ const userDetails = await this.prisma.user.findUnique({
where: { id: hostDetail.userXid }, where: { id: hostDetail.userXid },
}); });
@@ -153,10 +154,8 @@ export class MinglarService {
} }
async verifyHostOtp(email: string, otp: string): Promise<boolean> { async verifyHostOtp(email: string, otp: string): Promise<boolean> {
const trimmedOtp = (otp || '').toString().trim(); const user = await this.prisma.user.findUnique({
where: { emailAddress: email },
const user = await this.prisma.user.findFirst({
where: { emailAddress: email, isActive: true },
select: { select: {
id: true, id: true,
emailAddress: true, emailAddress: true,
@@ -182,7 +181,7 @@ export class MinglarService {
throw new ApiError(400, 'OTP has expired.'); throw new ApiError(400, 'OTP has expired.');
} }
const isMatch = await bcrypt.compare(trimmedOtp, userOtp.otpCode); const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
if (!isMatch) { if (!isMatch) {
throw new ApiError(400, 'Invalid OTP.'); throw new ApiError(400, 'Invalid OTP.');
@@ -218,7 +217,7 @@ export class MinglarService {
userStatus: true, userStatus: true,
profileImage: true, profileImage: true,
userPassword: true, userPassword: true,
}, }
}); });
if (!existingUser) { if (!existingUser) {
@@ -242,8 +241,8 @@ export class MinglarService {
} }
if (existingUser?.profileImage) { if (existingUser?.profileImage) {
const key = existingUser.profileImage.startsWith('http') const key = existingUser.profileImage.startsWith("http")
? existingUser.profileImage.split('.com/')[1] ? existingUser.profileImage.split(".com/")[1]
: existingUser.profileImage; : existingUser.profileImage;
existingUser.profileImage = await getPresignedUrl(bucket, key); existingUser.profileImage = await getPresignedUrl(bucket, key);
@@ -270,11 +269,7 @@ export class MinglarService {
}); });
} }
async getAllHostActivityForMinglar( async getAllHostActivityForMinglar(search?: string, hostXid?: number, paginationOptions?: { page: number; limit: number; skip: number }) {
search?: string,
hostXid?: number,
paginationOptions?: { page: number; limit: number; skip: number },
) {
const whereClause: any = { const whereClause: any = {
isActive: true, isActive: true,
activityInternalStatus: { notIn: [ACTIVITY_INTERNAL_STATUS.DRAFT_PQ] }, activityInternalStatus: { notIn: [ACTIVITY_INTERNAL_STATUS.DRAFT_PQ] },
@@ -290,8 +285,8 @@ export class MinglarService {
{ activityTitle: { contains: term, mode: 'insensitive' } }, { activityTitle: { contains: term, mode: 'insensitive' } },
{ {
activityType: { activityType: {
activityTypeName: { contains: term, mode: 'insensitive' }, activityTypeName: { contains: term, mode: 'insensitive' }
}, }
}, },
]; ];
} }
@@ -314,10 +309,10 @@ export class MinglarService {
companyName: true, companyName: true,
user: { user: {
select: { select: {
userRefNumber: true, userRefNumber: true
}, }
}, }
}, }
}, },
ActivityAmDetails: { ActivityAmDetails: {
select: { select: {
@@ -340,10 +335,10 @@ export class MinglarService {
interests: { interests: {
select: { select: {
id: true, id: true,
interestName: true, interestName: true
}, }
}, }
}, }
}, },
}, },
skip: paginationOptions?.skip || 0, skip: paginationOptions?.skip || 0,
@@ -359,8 +354,8 @@ export class MinglarService {
const am = activity.ActivityAmDetails?.[0]?.accountManager; const am = activity.ActivityAmDetails?.[0]?.accountManager;
if (am?.profileImage) { if (am?.profileImage) {
const key = am.profileImage.startsWith('http') const key = am.profileImage.startsWith("http")
? am.profileImage.split('.com/')[1] ? am.profileImage.split(".com/")[1]
: am.profileImage; : am.profileImage;
const presignedUrl = await getPresignedUrl(bucket, key); const presignedUrl = await getPresignedUrl(bucket, key);
@@ -372,16 +367,15 @@ export class MinglarService {
} }
} }
const { const { paginationService } = require('@/common/utils/pagination/pagination.service');
paginationService,
} = require('@/common/utils/pagination/pagination.service');
return paginationService.createPaginatedResponse( return paginationService.createPaginatedResponse(
hostActivities, hostActivities,
totalCount, totalCount,
paginationOptions || { page: 1, limit: 10, skip: 0 }, paginationOptions || { page: 1, limit: 10, skip: 0 }
); );
} }
async createUserRevenue( async createUserRevenue(
userXid: number, userXid: number,
isFixedSalary: boolean, isFixedSalary: boolean,
@@ -445,7 +439,7 @@ export class MinglarService {
emailAddress: emailAddress, emailAddress: emailAddress,
roleXid: roleXid, roleXid: roleXid,
userStatus: USER_STATUS.INVITED, userStatus: USER_STATUS.INVITED,
userRefNumber: referenceNumber, userRefNumber: referenceNumber
}, },
}); });
@@ -494,11 +488,7 @@ export class MinglarService {
cityXid?: number; cityXid?: number;
pinCode?: string; pinCode?: string;
}, },
documents: Array<{ documents: Array<{ fileName: string; filePath: string, documentTypeName?: string }>,
fileName: string;
filePath: string;
documentTypeName?: string;
}>,
) { ) {
try { try {
return await this.prisma.$transaction(async (tx) => { return await this.prisma.$transaction(async (tx) => {
@@ -746,7 +736,7 @@ export class MinglarService {
userStatus?: string, userStatus?: string,
paginationOptions?: PaginationOptions, paginationOptions?: PaginationOptions,
roleFilter?: string, roleFilter?: string,
applicationStatus?: string, applicationStatus?: string
) { ) {
const filters: any = { const filters: any = {
isActive: true, isActive: true,
@@ -817,8 +807,7 @@ export class MinglarService {
/** USER STATUS FILTER **/ /** USER STATUS FILTER **/
if ( if (
userStatus && userStatus &&
userStatus.trim().toLowerCase() === userStatus.trim().toLowerCase() === MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
) { ) {
filters.adminStatusInternal = MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW; filters.adminStatusInternal = MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW;
} }
@@ -840,12 +829,9 @@ export class MinglarService {
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED, internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
display: MINGLAR_STATUS_DISPLAY.ENHANCING, display: MINGLAR_STATUS_DISPLAY.ENHANCING,
}, },
Approved: {
internal: MINGLAR_STATUS_INTERNAL.AM_APPROVED,
display: MINGLAR_STATUS_DISPLAY.APPROVED,
}
}; };
if (applicationStatus?.trim()) { if (applicationStatus?.trim()) {
const key = applicationStatus.trim(); const key = applicationStatus.trim();
const statusObj = APPLICATION_STATUS_MAP[key]; const statusObj = APPLICATION_STATUS_MAP[key];
@@ -856,6 +842,7 @@ export class MinglarService {
} }
} }
/** ROLE-BASED FILTER **/ /** ROLE-BASED FILTER **/
if (userRoleXid === ROLE.CO_ADMIN || userRoleXid === ROLE.ACCOUNT_MANAGER) { if (userRoleXid === ROLE.CO_ADMIN || userRoleXid === ROLE.ACCOUNT_MANAGER) {
filters.accountManagerXid = userId; filters.accountManagerXid = userId;
@@ -887,7 +874,7 @@ export class MinglarService {
emailAddress: true, emailAddress: true,
mobileNumber: true, mobileNumber: true,
userRefNumber: true, userRefNumber: true,
profileImage: true, profileImage: true
}, },
}, },
accountManager: { accountManager: {
@@ -898,11 +885,11 @@ export class MinglarService {
emailAddress: true, emailAddress: true,
mobileNumber: true, mobileNumber: true,
roleXid: true, roleXid: true,
profileImage: true, profileImage: true
}, },
}, },
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: "desc" },
skip: paginationOptions?.skip || 0, skip: paginationOptions?.skip || 0,
take: paginationOptions?.limit || 10, take: paginationOptions?.limit || 10,
}); });
@@ -911,8 +898,8 @@ export class MinglarService {
const am = user.accountManager; const am = user.accountManager;
if (am?.profileImage) { if (am?.profileImage) {
const key = am.profileImage.startsWith('http') const key = am.profileImage.startsWith("http")
? am.profileImage.split('.com/')[1] ? am.profileImage.split(".com/")[1]
: am.profileImage; : am.profileImage;
am.profileImage = await getPresignedUrl(bucket, key); am.profileImage = await getPresignedUrl(bucket, key);
@@ -938,6 +925,7 @@ export class MinglarService {
return { data: transformedData, totalCount }; return { data: transformedData, totalCount };
} }
async getAllOnboardingHostApplications( async getAllOnboardingHostApplications(
paginationOptions?: PaginationOptions, paginationOptions?: PaginationOptions,
search?: string, search?: string,
@@ -1031,6 +1019,7 @@ export class MinglarService {
take: paginationOptions?.limit ?? undefined, take: paginationOptions?.limit ?? undefined,
}); });
/** --------------------------------- /** ---------------------------------
* Add presigned URL for AM profile * Add presigned URL for AM profile
* --------------------------------- */ * --------------------------------- */
@@ -1038,8 +1027,8 @@ export class MinglarService {
const am = host.accountManager; const am = host.accountManager;
if (am?.profileImage) { if (am?.profileImage) {
const key = am.profileImage.startsWith('http') const key = am.profileImage.startsWith("http")
? am.profileImage.split('.com/')[1] ? am.profileImage.split(".com/")[1]
: am.profileImage; : am.profileImage;
am.profileImage = await getPresignedUrl(bucket, key); am.profileImage = await getPresignedUrl(bucket, key);
@@ -1162,6 +1151,7 @@ export class MinglarService {
take: paginationOptions?.limit ?? 10, take: paginationOptions?.limit ?? 10,
}); });
/** --------------------------------- /** ---------------------------------
* Add presigned URL for AM profile * Add presigned URL for AM profile
* --------------------------------- */ * --------------------------------- */
@@ -1187,15 +1177,13 @@ export class MinglarService {
// Build search filter if search term is provided // Build search filter if search term is provided
const searchFilter = search const searchFilter = search
? { ? {
OR: [ OR: [
{ email: { contains: search, mode: 'insensitive' as const } }, { email: { contains: search, mode: 'insensitive' as const } },
{ firstName: { contains: search, mode: 'insensitive' as const } }, { firstName: { contains: search, mode: 'insensitive' as const } },
{ lastName: { contains: search, mode: 'insensitive' as const } }, { lastName: { contains: search, mode: 'insensitive' as const } },
{ { userRefNumber: { contains: search, mode: 'insensitive' as const } },
userRefNumber: { contains: search, mode: 'insensitive' as const }, ],
}, }
],
}
: {}; : {};
// 1. Fetch all required users (Admin, Co-Admin, AM) // 1. Fetch all required users (Admin, Co-Admin, AM)
@@ -1451,37 +1439,6 @@ export class MinglarService {
return true; return true;
} }
async addActivtiySuggestion(
title: string,
comments: string,
activity_xid: number,
reviewedByXid: number,
) {
// Check if host exists
const ActivityHeader = await this.prisma.activities.findUnique({
where: { id: activity_xid, isActive: true },
select: { id: true },
});
if (!ActivityHeader) {
throw new ApiError(404, 'Host not found');
}
await this.prisma.activitySuggestions.create({
data: {
title: title,
comments: comments,
isReviewed: false,
reviewedOn: new Date(),
isActive: true,
activityXid: activity_xid,
reviewedByXid: reviewedByXid,
},
});
return true;
}
async getHostSuggestions(userId: number) { async getHostSuggestions(userId: number) {
const hostDetail = await this.prisma.hostHeader.findFirst({ const hostDetail = await this.prisma.hostHeader.findFirst({
where: { userXid: userId, isActive: true }, where: { userXid: userId, isActive: true },
@@ -1505,24 +1462,6 @@ export class MinglarService {
return suggestions; return suggestions;
} }
async getHostSuggestionsForActivity(actvityXid: number) {
const suggestions = await this.prisma.activitySuggestions.findMany({
where: { activityXid: actvityXid, isReviewed: false, isActive: true },
select: {
id: true,
title: true,
comments: true,
isReviewed: true,
reviewedOn: true,
},
orderBy: {
id: 'asc',
},
});
return suggestions;
}
async getSuggestionsForAM(hostXid: number) { async getSuggestionsForAM(hostXid: number) {
const suggestions = await this.prisma.hostSuggestion.findMany({ const suggestions = await this.prisma.hostSuggestion.findMany({
where: { hostXid: hostXid, isreviewed: false, isActive: true }, where: { hostXid: hostXid, isreviewed: false, isActive: true },
@@ -1580,8 +1519,7 @@ export class MinglarService {
amountPerBooking: number, amountPerBooking: number,
durationFrequency: string, durationFrequency: string,
payoutDurationNum: number, payoutDurationNum: number,
payoutDurationFrequency: string, payoutDurationFrequency: string,) {
) {
return await this.prisma.$transaction(async (tx) => { return await this.prisma.$transaction(async (tx) => {
await this.prisma.hostHeader.update({ await this.prisma.hostHeader.update({
where: { where: {
@@ -1632,7 +1570,6 @@ export class MinglarService {
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW, hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
}, },
data: { data: {
stepper: STEPPER.NOT_SUBMITTED,
hostStatusInternal: HOST_STATUS_INTERNAL.REJECTED, hostStatusInternal: HOST_STATUS_INTERNAL.REJECTED,
hostStatusDisplay: HOST_STATUS_DISPLAY.REJECTED, hostStatusDisplay: HOST_STATUS_DISPLAY.REJECTED,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_REJECTED, adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_REJECTED,
@@ -1649,12 +1586,12 @@ export class MinglarService {
}, },
}); });
// await tx.user.update({ await tx.user.update({
// where: { id: hostDetails.userXid }, where: { id: hostDetails.userXid },
// data: { data: {
// userStatus: USER_STATUS.REJECTED, userStatus: USER_STATUS.REJECTED,
// }, },
// }); });
}); });
} }
@@ -1725,46 +1662,48 @@ export class MinglarService {
country: { country: {
select: { select: {
id: true, id: true,
countryName: true, countryName: true
}, }
}, },
cities: { cities: {
select: { select: {
id: true, id: true,
cityName: true, cityName: true,
}, }
}, },
states: { states: {
select: { select: {
id: true, id: true,
stateName: true, stateName: true
}, }
}, }
}, }
}, },
userDocuments: { userDocuments: {
select: { select: {
id: true, id: true,
fileName: true, fileName: true,
}, }
}, },
userRevenues: { userRevenues: {
select: { select: {
id: true, id: true,
is_fixed_salary: true, is_fixed_salary: true,
per_value: true, per_value: true
}, }
}, },
}, },
}); });
if (user.userDocuments?.length) { if (user.userDocuments?.length) {
for (const media of user.userDocuments) { for (const media of user.userDocuments) {
if (!media.fileName) continue; if (!media.fileName) continue;
// Extract S3 key if URL or keep raw key // Extract S3 key if URL or keep raw key
const key = media.fileName.startsWith('http') const key = media.fileName.startsWith("http")
? media.fileName.split('.com/')[1] ? media.fileName.split(".com/")[1]
: media.fileName; : media.fileName;
media.fileName = await getPresignedUrl(bucket, key); media.fileName = await getPresignedUrl(bucket, key);
@@ -1784,7 +1723,7 @@ export class MinglarService {
async getBasicUserDetails(user_xid) { async getBasicUserDetails(user_xid) {
return await this.prisma.user.findFirst({ return await this.prisma.user.findFirst({
where: { where: {
id: user_xid, id: user_xid
}, },
select: { select: {
id: true, id: true,
@@ -1795,8 +1734,8 @@ export class MinglarService {
isProfileUpdated: true, isProfileUpdated: true,
roleXid: true, roleXid: true,
role: true, role: true,
}, }
}); })
} }
async rejectPQQbyAM(activityId: number, user_xid: number) { async rejectPQQbyAM(activityId: number, user_xid: number) {
@@ -1804,41 +1743,12 @@ export class MinglarService {
await tx.activities.update({ await tx.activities.update({
where: { where: {
id: activityId, id: activityId,
isActive: true, isActive: true
}, },
data: { data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_TO_UPDATE, activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_TO_UPDATE,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ENHANCING, activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ENHANCING,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_REJECTED, amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_REJECTED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ENHANCING,
},
});
await tx.activityTrack.create({
data: {
activityXid: activityId,
trackType: ACTIVITY_TRACK_TYPE.PQ,
trackStatus: ACTIVITY_TRACK_STATUS.REJECTED_BY_AM,
updatedByXid: user_xid,
updatedByRole: ROLE_NAME.ACCOUNT_MANAGER,
updatedOn: new Date(),
},
});
});
}
async rejectActivityApplicationByAM(activityId: number, user_xid: number) {
return await this.prisma.$transaction(async (tx) => {
await tx.activities.update({
where: {
id: activityId,
isActive: true
},
data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_REJECTED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.ENHANCING,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_REJECTED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ENHANCING amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.ENHANCING
} }
}) })
@@ -1846,7 +1756,7 @@ export class MinglarService {
await tx.activityTrack.create({ await tx.activityTrack.create({
data: { data: {
activityXid: activityId, activityXid: activityId,
trackType: ACTIVITY_TRACK_TYPE.ACTIVITY, trackType: ACTIVITY_TRACK_TYPE.PQ,
trackStatus: ACTIVITY_TRACK_STATUS.REJECTED_BY_AM, trackStatus: ACTIVITY_TRACK_STATUS.REJECTED_BY_AM,
updatedByXid: user_xid, updatedByXid: user_xid,
updatedByRole: ROLE_NAME.ACCOUNT_MANAGER, updatedByRole: ROLE_NAME.ACCOUNT_MANAGER,
@@ -1861,49 +1771,20 @@ export class MinglarService {
await tx.activities.update({ await tx.activities.update({
where: { where: {
id: activityId, id: activityId,
isActive: true, isActive: true
}, },
data: { data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_APPROVED, activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_APPROVED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.PQ_APPROVED, activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.PQ_APPROVED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_APPROVED, amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_APPROVED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.PQ_APPROVED, amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.PQ_APPROVED
},
});
await tx.activityTrack.create({
data: {
activityXid: activityId,
trackType: ACTIVITY_TRACK_TYPE.PQ,
trackStatus: ACTIVITY_TRACK_STATUS.ACCEPTED_BY_AM,
updatedByXid: user_xid,
updatedByRole: ROLE_NAME.ACCOUNT_MANAGER,
updatedOn: new Date(),
},
});
});
}
async acceptActivityApplicationByAM(activityId: number, user_xid: number) {
return await this.prisma.$transaction(async (tx) => {
await tx.activities.update({
where: {
id: activityId,
isActive: true
},
data: {
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_APPROVED,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.NOT_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_APPROVED,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.NOT_LISTED
} }
}) })
await tx.activityTrack.create({ await tx.activityTrack.create({
data: { data: {
activityXid: activityId, activityXid: activityId,
trackType: ACTIVITY_TRACK_TYPE.ACTIVITY, trackType: ACTIVITY_TRACK_TYPE.PQ,
trackStatus: ACTIVITY_TRACK_STATUS.ACCEPTED_BY_AM, trackStatus: ACTIVITY_TRACK_STATUS.ACCEPTED_BY_AM,
updatedByXid: user_xid, updatedByXid: user_xid,
updatedByRole: ROLE_NAME.ACCOUNT_MANAGER, updatedByRole: ROLE_NAME.ACCOUNT_MANAGER,
@@ -1931,26 +1812,26 @@ export class MinglarService {
filePath: true, filePath: true,
documentName: true, documentName: true,
documentTypeXid: true, documentTypeXid: true,
documentType: true, documentType: true
}, }
}, },
cities: { cities: {
select: { select: {
id: true, id: true,
cityName: true, cityName: true,
}, }
}, },
countries: { countries: {
select: { select: {
id: true, id: true,
countryName: true, countryName: true
}, }
}, },
states: { states: {
select: { select: {
id: true, id: true,
stateName: true, stateName: true
}, }
}, },
companyTypes: { companyTypes: {
select: { select: {
@@ -1958,7 +1839,7 @@ export class MinglarService {
companyTypeName: true, companyTypeName: true,
}, },
}, },
}, }
}, },
HostBankDetails: true, HostBankDetails: true,
HostDocuments: { HostDocuments: {
@@ -1976,7 +1857,7 @@ export class MinglarService {
profileImage: true, profileImage: true,
userStatus: true, userStatus: true,
userRefNumber: true, userRefNumber: true,
}, }
}, },
HostSuggestion: true, HostSuggestion: true,
HostTrack: true, HostTrack: true,
@@ -1987,7 +1868,9 @@ export class MinglarService {
}, },
}); });
if (host.HostDocuments?.length) { if (host.HostDocuments?.length) {
for (const doc of host.HostDocuments) { for (const doc of host.HostDocuments) {
if (doc.filePath) { if (doc.filePath) {
const filePath = doc.filePath; const filePath = doc.filePath;
@@ -2011,8 +1894,8 @@ export class MinglarService {
} }
if (host.user.profileImage) { if (host.user.profileImage) {
const key = host.user.profileImage.startsWith('http') const key = host.user.profileImage.startsWith("http")
? host.user.profileImage.split('.com/')[1] ? host.user.profileImage.split(".com/")[1]
: host.user.profileImage; : host.user.profileImage;
host.user.profileImage = await getPresignedUrl(bucket, key); host.user.profileImage = await getPresignedUrl(bucket, key);
@@ -2023,8 +1906,8 @@ export class MinglarService {
// Parent company logo // Parent company logo
if (parent.logoPath) { if (parent.logoPath) {
const key = parent.logoPath.startsWith('http') const key = parent.logoPath.startsWith("http")
? parent.logoPath.split('.com/')[1] ? parent.logoPath.split(".com/")[1]
: parent.logoPath; : parent.logoPath;
parent.logoPath = await getPresignedUrl(bucket, key); parent.logoPath = await getPresignedUrl(bucket, key);
@@ -2034,8 +1917,8 @@ export class MinglarService {
if (parent.HostParenetDocuments?.length) { if (parent.HostParenetDocuments?.length) {
for (const doc of parent.HostParenetDocuments) { for (const doc of parent.HostParenetDocuments) {
if (doc.filePath) { if (doc.filePath) {
const key = doc.filePath.startsWith('http') const key = doc.filePath.startsWith("http")
? doc.filePath.split('.com/')[1] ? doc.filePath.split(".com/")[1]
: doc.filePath; : doc.filePath;
(doc as any).presignedUrl = await getPresignedUrl(bucket, key); (doc as any).presignedUrl = await getPresignedUrl(bucket, key);
@@ -2057,35 +1940,6 @@ export class MinglarService {
id: true, id: true,
comments: true, comments: true,
pqqAnswerXid: true, pqqAnswerXid: true,
activity: {
select: {
id: true,
activityTitle: true,
activityRefNumber: true,
activityDisplayStatus: true,
activityInternalStatus: true,
amInternalStatus: true,
amDisplayStatus: true,
activityType: {
select: {
id: true,
activityTypeName: true
}
},
host: {
select: {
id: true,
companyName: true,
logoPath: true,
user: {
select: {
userRefNumber: true,
}
}
}
}
}
},
pqqQuestions: { pqqQuestions: {
select: { select: {
id: true, id: true,
@@ -2101,10 +1955,10 @@ export class MinglarService {
select: { select: {
id: true, id: true,
categoryName: true, categoryName: true,
displayOrder: true, displayOrder: true
}, }
}, }
}, }
}, },
// 🔥 ALL ANSWER OPTIONS FOR THIS QUESTION // 🔥 ALL ANSWER OPTIONS FOR THIS QUESTION
@@ -2114,31 +1968,31 @@ export class MinglarService {
id: true, id: true,
answerName: true, answerName: true,
answerPoints: true, answerPoints: true,
displayOrder: true, displayOrder: true
}, },
orderBy: { displayOrder: 'asc' }, orderBy: { displayOrder: "asc" }
}, }
}, }
}, },
ActivityPQQSuggestions: { ActivityPQQSuggestions: {
where: { isActive: true, isReviewed: false }, where: { isActive: true },
select: { select: {
id: true, id: true,
title: true, title: true,
comments: true, comments: true,
activityPqqHeaderXid: true, activityPqqHeaderXid: true
}, }
}, },
ActivityPQQSupportings: { ActivityPQQSupportings: {
where: { isActive: true }, where: { isActive: true },
select: { select: {
id: true, id: true,
mediaType: true, mediaType: true,
mediaFileName: true, mediaFileName: true
}, }
}, },
}, },
orderBy: { id: 'asc' }, orderBy: { id: "asc" }
}); });
// ---------- GROUPING START ---------- // ---------- GROUPING START ----------
@@ -2155,17 +2009,8 @@ export class MinglarService {
id: cat.id, id: cat.id,
categoryName: cat.categoryName, categoryName: cat.categoryName,
displayOrder: cat.displayOrder, displayOrder: cat.displayOrder,
hostId: item.activity.host.id, activityPqqHeaderId: item.id,
hostCompanyName: item.activity.host.companyName, pqqsubCategories: []
activityTypeName: item.activity.activityType.activityTypeName,
hostLogoPath: item.activity.host.logoPath,
activityRefNumber: item.activity.activityRefNumber,
activityDisplayStatus: item.activity.activityDisplayStatus,
activityInternalStatus: item.activity.activityInternalStatus,
amInternalStatus: item.activity.amInternalStatus,
amDisplayStatus: item.activity.amDisplayStatus,
userRefNumber: item.activity.host.user.userRefNumber,
pqqsubCategories: [],
}; };
} else if (!grouped[cat.id].activityPqqHeaderId) { } else if (!grouped[cat.id].activityPqqHeaderId) {
// Ensure header id is present at category level // Ensure header id is present at category level
@@ -2180,7 +2025,7 @@ export class MinglarService {
id: sub.id, id: sub.id,
subCategoryName: sub.subCategoryName, subCategoryName: sub.subCategoryName,
displayOrder: sub.displayOrder, displayOrder: sub.displayOrder,
questions: [], questions: []
}; };
category.pqqsubCategories.push(subCat); category.pqqsubCategories.push(subCat);
} }
@@ -2188,7 +2033,6 @@ export class MinglarService {
// 3⃣ Questions level // 3⃣ Questions level
subCat.questions.push({ subCat.questions.push({
id: q.id, id: q.id,
activityPqqHeaderId: item.id,
questionName: q.questionName, questionName: q.questionName,
maxPoints: q.maxPoints, maxPoints: q.maxPoints,
pqqAnswerXid: item.pqqAnswerXid, pqqAnswerXid: item.pqqAnswerXid,
@@ -2196,19 +2040,16 @@ export class MinglarService {
displayOrder: q.displayOrder, displayOrder: q.displayOrder,
allAnswerOptions: q.PQQAnswers || [], allAnswerOptions: q.PQQAnswers || [],
suggestions: item.ActivityPQQSuggestions, suggestions: item.ActivityPQQSuggestions,
supportings: item.ActivityPQQSupportings, supportings: item.ActivityPQQSupportings
}); });
} }
// ---------- SORTING ---------- // ---------- SORTING ----------
const sortedCategories: any = Object.values(grouped).sort( const sortedCategories: any = Object.values(grouped)
(a: any, b: any) => a.displayOrder - b.displayOrder, .sort((a: any, b: any) => a.displayOrder - b.displayOrder);
);
for (const cat of sortedCategories) { for (const cat of sortedCategories) {
cat.pqqsubCategories.sort( cat.pqqsubCategories.sort((a: any, b: any) => a.displayOrder - b.displayOrder);
(a: any, b: any) => a.displayOrder - b.displayOrder,
);
for (const sub of cat.pqqsubCategories) { for (const sub of cat.pqqsubCategories) {
sub.questions.sort((a: any, b: any) => a.displayOrder - b.displayOrder); sub.questions.sort((a: any, b: any) => a.displayOrder - b.displayOrder);
@@ -2223,8 +2064,8 @@ export class MinglarService {
for (const doc of q.supportings) { for (const doc of q.supportings) {
if (doc.mediaFileName) { if (doc.mediaFileName) {
const filePath = doc.mediaFileName; const filePath = doc.mediaFileName;
const key = filePath.startsWith('http') const key = filePath.startsWith("http")
? filePath.split('.com/')[1] ? filePath.split(".com/")[1]
: filePath; : filePath;
doc.presignedUrl = await getPresignedUrl(bucket, key); doc.presignedUrl = await getPresignedUrl(bucket, key);
@@ -2237,5 +2078,6 @@ export class MinglarService {
// ---------- RETURN GROUPED STRUCTURE ---------- // ---------- RETURN GROUPED STRUCTURE ----------
return sortedCategories; return sortedCategories;
} }
} }

View File

@@ -14,7 +14,6 @@ export async function sendEmailToHostForRejectedApplication(
const htmlContent = ` const htmlContent = `
<p>Dear Host,</p> <p>Dear Host,</p>
<p>Sorry to say that, But your application to minglar admin has been rejected.</p> <p>Sorry to say that, But your application to minglar admin has been rejected.</p>
<p>Please update your application and resubmit it.</p>
<p>If you have any questions please contact to minglar admin.</p> <p>If you have any questions please contact to minglar admin.</p>
<p>Best regards,<br/>Minglar Team</p> <p>Best regards,<br/>Minglar Team</p>
`; `;
@@ -40,8 +39,7 @@ export async function sendEmailToHostForRejectedApplication(
export async function sendAMRejectionMailtoHost( export async function sendAMRejectionMailtoHost(
emailAddress: string, emailAddress: string,
name: string, name: string
link: string
): Promise<{ ): Promise<{
sent: boolean; sent: boolean;
// messageId: string // messageId: string
@@ -55,8 +53,8 @@ export async function sendAMRejectionMailtoHost(
Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p> Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p>
<p> You may access your application using the link below:<br/> <p> You may access your application using the link below:<br/>
<strong>Link:</strong> <strong>Link:</strong>
<a href="${link}" target="_blank"> <a href="${config.HOST_LINK}" target="_blank">
${link} ${config.HOST_LINK}
</a> </a>
</p> </p>
<p> If you have any questions, please feel free to contact the Minglar Support Team. </p> <p> If you have any questions, please feel free to contact the Minglar Support Team. </p>
@@ -128,49 +126,3 @@ export async function sendAMPQQRejectionMailtoHost(
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 sendActivityRejectionMailtoHost(
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 {
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

@@ -27,21 +27,15 @@ export const handler = safeHandler(async (
// 2) Authenticate user // 2) Authenticate user
await verifyMinglarAdminHostToken(token); await verifyMinglarAdminHostToken(token);
// 3) Get stateXid and optional search term from query params // 3) Get bankXid from query params
const stateXid = Number(event.queryStringParameters?.stateXid); const stateXid = Number(event.queryStringParameters?.stateXid);
const search = event.queryStringParameters?.search?.trim();
if (!stateXid || isNaN(stateXid)) { if (!stateXid || isNaN(stateXid)) {
throw new ApiError(400, "Valid stateXid is required in query params."); throw new ApiError(400, "Valid stateXid is required in query params.");
} }
// If search is provided, enforce minimum 3 characters // 4) Fetch branches for the bank
if (search && search.length < 3) { const branches = await prePopulateService.getCityByStateId(stateXid);
throw new ApiError(400, "Search term must be at least 3 characters long.");
}
// 4) Fetch cities for the state (optionally filtered by search)
const branches = await prePopulateService.getCityByStateId(stateXid, search);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,11 +1,5 @@
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import config from '../../../config/config';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
const bucket = config.aws.bucketName;
@Injectable() @Injectable()
export class PrePopulateService { export class PrePopulateService {
constructor(private prisma: PrismaClient) { } constructor(private prisma: PrismaClient) { }
@@ -39,20 +33,12 @@ export class PrePopulateService {
} }
async getCityByStateId(stateXid: number, search?: string) { async getCityByStateId(stateXid: number) {
return await this.prisma.cities.findMany({ return await this.prisma.cities.findMany({
where: { where: {
stateXid, stateXid,
isActive: true, isActive: true,
deletedAt: null, deletedAt: null
...(search && search.length >= 3
? {
cityName: {
contains: search,
mode: 'insensitive',
},
}
: {}),
}, },
select: { select: {
id: true, id: true,
@@ -161,6 +147,7 @@ export class PrePopulateService {
foodType, foodType,
cuisineDetails, cuisineDetails,
vehicleType, vehicleType,
navigationMode,
taxDetails, taxDetails,
energyLevel, energyLevel,
aminitiesDetails, aminitiesDetails,
@@ -178,6 +165,9 @@ export class PrePopulateService {
this.prisma.transportModes.findMany({ this.prisma.transportModes.findMany({
where: { isActive: true }, where: { isActive: true },
}), }),
this.prisma.navigationModes.findMany({
where: { isActive: true },
}),
this.prisma.taxes.findMany({ this.prisma.taxes.findMany({
where: { isActive: true }, where: { isActive: true },
}), }),
@@ -186,12 +176,10 @@ export class PrePopulateService {
}), }),
this.prisma.amenities.findMany({ this.prisma.amenities.findMany({
where: { isActive: true }, where: { isActive: true },
select: { id: true, amenitiesName: true, amenitiesIcon: true },
orderBy: { amenitiesName: 'asc' }
}), }),
this.prisma.allowedEntryTypes.findMany({ this.prisma.allowedEntryTypes.findMany({
where: { isActive: true }, where: { isActive: true },
orderBy: { id: 'asc' } orderBy: { allowedEntryTypeName: 'asc' }
}), }),
this.prisma.ageRestrictions.findMany({ this.prisma.ageRestrictions.findMany({
where: { isActive: true }, where: { isActive: true },
@@ -199,26 +187,11 @@ export class PrePopulateService {
}), }),
]); ]);
if (aminitiesDetails?.length) {
for (const amenity of aminitiesDetails) {
if (amenity.amenitiesIcon) {
const filePath = amenity.amenitiesIcon;
// Extract key if full URL stored
const key = filePath.startsWith('http')
? filePath.split('.com/')[1]
: filePath;
(amenity as any).presignedUrl = await getPresignedUrl(bucket, key);
}
}
}
return { return {
foodType, foodType,
cuisineDetails, cuisineDetails,
vehicleType, vehicleType,
navigationMode,
taxDetails, taxDetails,
energyLevel, energyLevel,
aminitiesDetails, aminitiesDetails,

View File

@@ -1,13 +0,0 @@
export class AddSchoolCompanyDetailDTO {
schoolCompanyName: string;
isSchool: boolean;
cityXid: number;
userId: number;
constructor(schoolCompanyName: string, isSchool: boolean, cityXid: number, userId: number) {
this.schoolCompanyName = schoolCompanyName;
this.isSchool = isSchool;
this.cityXid = cityXid;
this.userId = userId;
}
}

View File

@@ -1,9 +0,0 @@
export class SetPasscodeDTO {
userPasscode: string;
confirmPasscode: string;
constructor(userPasscode: string, confirmPasscode: string) {
this.userPasscode = userPasscode;
this.confirmPasscode = confirmPasscode;
}
}

View File

@@ -1,74 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { activityXid: number; isBucket: boolean; bucketTypeName: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityXid, isBucket, bucketTypeName } = body;
// Validate required fields
if (
typeof activityXid !== 'number' ||
typeof isBucket !== 'boolean' ||
!bucketTypeName
) {
throw new ApiError(400, 'Required fields missing or invalid');
}
// Set the passcode
const counts = await userService.addToBucketInterested(userId, isBucket, bucketTypeName, activityXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `Activity added to ${isBucket ? 'bucket' : 'interested'} successfully`,
data: {
bucketCount: counts.bucketCount,
interestedCount: counts.interestedCount,
coverImage: counts.coverImage,
coverImagePresignedUrl: counts.coverImagePresignedUrl,
}
}),
};
});

View File

@@ -1,122 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { SchedulingService } from '../../../host/services/activityScheduling.service';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
const schedulingService = new SchedulingService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const activityXid = Number(event.pathParameters?.activity_xid);
if (!activityXid || isNaN(activityXid)) {
throw new ApiError(400, 'Valid activityXid is required');
}
// selected date may be passed as query param `selectedDate`
const selectedDate =
event.queryStringParameters?.selectedDate ||
event.queryStringParameters?.date ||
(event.body ? JSON.parse(event.body).selectedDate : undefined);
if (!selectedDate) {
throw new ApiError(400, 'selectedDate query parameter is required');
}
// Fetch activity details (basic) and schedule details for the selected date
const activityDetails = await userService.getActivityDetailsById(userId, activityXid);
const scheduleDetails = await schedulingService.getScheduleDetailsForDate(activityXid, selectedDate);
// Shape response to match UI: only include fields shown in image
const activity = activityDetails.activity;
// Rooms: combine ActivityVenues with their respective slots for the selected date
const Venues = (activity.ActivityVenues || [])
.map((v: any) => {
const header = scheduleDetails.find(
(h: any) => h.activityVenue?.venueXid === v.id
);
if (!header || !header.slots?.length) {
return null; // ❌ venue has no slots for selected date
}
const roomSlots = header.slots.map((s: any) => {
let status = "Available";
if (s.maxCapacity === 0) status = "Housefull";
else if (s.maxCapacity <= 2) status = "2 Slots Left";
else if (s.maxCapacity <= 5) status = "Fast Filling";
return {
slotId: s.slotId,
startTime: s.startTime,
endTime: s.endTime,
status,
maxCapacity: s.maxCapacity,
};
});
return {
venueXid: v.id,
venueName: v.venueName,
venueLabel: v.venueLabel,
venueCapacity: v.venueCapacity,
availableSeats: v.availableSeats ?? null,
price: v.ActivityPrices?.[0]?.sellPrice ?? null,
endDate: header?.endDate ?? null,
slots: roomSlots,
slotsCount: roomSlots.length,
venueMedia: (v.ActivityVenueArtifacts || []).map((media: any) => ({
id: media.id,
mediaType: media.mediaType,
mediaFileName: media.mediaFileName,
presignedUrl: media.presignedUrl,
})),
};
})
.filter(Boolean); // ✅ removes null venues
// derive check-in/out from all room slots (earliest start, latest end)
const allSlots = Venues.flatMap(r => r.slots || []);
const startTimes = allSlots.map(s => s.startTime).filter(Boolean);
const endTimes = allSlots.map(s => s.endTime).filter(Boolean);
const checkInTime = startTimes.length ? startTimes.sort()[0] : null;
const checkOutTime = endTimes.length ? endTimes.sort().reverse()[0] : null;
const responsePayload = {
selectedDate,
Venues,
checkInTime,
checkOutTime,
};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ success: true, data: responsePayload }),
};
});

View File

@@ -1,74 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { FilteredLandingPageService } from '../../services/filteredLandingPage.service';
const filteredLandingPageService = new FilteredLandingPageService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName ?? '';
const stateName = event.queryStringParameters?.stateName ?? '';
const cityName = event.queryStringParameters?.cityName ?? '';
const userLat = event.queryStringParameters?.userLat ?? '';
const userLong = event.queryStringParameters?.userLong ?? '';
let activityTypeXids: number[] | undefined;
if (event.queryStringParameters?.activityTypeXids) {
try {
activityTypeXids = JSON.parse(event.queryStringParameters.activityTypeXids);
} catch (error) {
// Handle invalid JSON if needed
}
}
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
// Fetch filtered landing page details
const result = await filteredLandingPageService.getFilteredLandingPageAllDetails(
userId,
page,
limit,
countryName,
stateName,
cityName,
userLat,
userLong,
activityTypeXids
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Filtered landing page data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,46 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getAllBucketActivities(
userId
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,53 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const activityXid = Number(event.pathParameters?.activity_xid);
if (!activityXid || isNaN(activityXid)) {
throw new ApiError(400, 'Valid activityXid is required');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getActivityDetailsById(
userId,
activityXid
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,71 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const latParam = event.queryStringParameters?.lat ?? event.queryStringParameters?.latitude;
const longParam = event.queryStringParameters?.long ?? event.queryStringParameters?.lng ?? event.queryStringParameters?.longitude;
const radiusParam = event.queryStringParameters?.radiusKm ?? event.queryStringParameters?.radius;
const userLat = latParam ? Number(latParam) : undefined;
const userLong = longParam ? Number(longParam) : undefined;
const radiusKm = radiusParam ? Number(radiusParam) : 15; // default 15km
if (
(userLat !== undefined && Number.isNaN(userLat)) ||
(userLong !== undefined && Number.isNaN(userLong)) ||
Number.isNaN(radiusKm)
) {
throw new ApiError(400, 'Invalid lat/long values');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
const result = await userService.getNearbyActivities(
userId,
userLat,
userLong,
radiusKm,
page,
limit,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Nearby activities retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,59 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Fetch 50 random active activities
const result = await userService.getRandomActiveActivity();
if (!result || result.length === 0) {
return {
statusCode: 404,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: false,
message: 'No active activities found',
data: [],
}),
};
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Random active activities retrieved successfully',
data: result,
count: result.length,
}),
};
});

View File

@@ -1,49 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Extract query parameters for search
const activityType = event.queryStringParameters?.activityType?.trim();
// Fetch activities based on search criteria
const result = await userService.searchActivities(
activityType
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,65 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName ?? '';
const stateName = event.queryStringParameters?.stateName ?? '';
const cityName = event.queryStringParameters?.cityName ?? '';
const userLat = event.queryStringParameters?.userLat ?? '';
const userLong = event.queryStringParameters?.userLong ?? '';
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getLandingPageAllDetails(
userId,
page,
limit,
countryName,
stateName,
cityName,
userLat,
userLong
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,71 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { activityXid: number; isBucket: boolean; bucketTypeName: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityXid, isBucket, bucketTypeName } = body;
// Validate required fields
if (
typeof activityXid !== 'number' ||
typeof isBucket !== 'boolean' ||
!bucketTypeName
) {
throw new ApiError(400, 'Required fields missing or invalid');
}
// Remove from bucket/interested
const counts = await userService.removeFromBucketInterested(userId, isBucket, bucketTypeName, activityXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `Activity removed from ${isBucket ? 'bucket' : 'interested'} successfully`,
data: {
bucketCount: counts.bucketCount,
interestedCount: counts.interestedCount,
}
}),
};
});

View File

@@ -1,61 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName ?? '';
const stateName = event.queryStringParameters?.stateName ?? '';
const cityName = event.queryStringParameters?.cityName ?? '';
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getSurpriseMeDetails(
userId,
page,
limit,
countryName,
stateName,
cityName,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,53 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route.');
}
const userInfo = await verifyUserToken(token);
// const userId = Number(userInfo.id);
const interestId = Number(event.queryStringParameters?.interestId);
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
if (!interestId) {
throw new ApiError(400, 'Interest ID is required');
}
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
const result = await userService.viewMoreActivitiesByInterest(
interestId,
page,
limit
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Interest activities fetched successfully',
data: result,
}),
};
});

View File

@@ -1,56 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route.');
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
const type = event.queryStringParameters?.type;
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName;
const stateName = event.queryStringParameters?.stateName;
const cityName = event.queryStringParameters?.cityName;
if (!type) {
throw new ApiError(400, 'Type is required');
}
const result = await userService.viewMoreActivities(
userId,
type,
page,
limit,
countryName,
stateName,
cityName
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `${type} activities fetched successfully`,
data: result,
}),
};
});

View File

@@ -1,92 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: {
countryName?: string;
stateName?: string;
cityName?: string;
pinCode?: string;
latitude?: number;
longitude?: number;
locationName?: string;
locationAddress?: string;
};
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { countryName, stateName, cityName, pinCode, latitude, longitude, locationName, locationAddress } = body;
// Validate required fields
if (!countryName || !stateName || !cityName) {
throw new ApiError(400, 'Country name, state name, and city name are required');
}
// Set the location details
await userService.setUserLocationDetails(
userId,
countryName,
stateName,
cityName,
pinCode,
latitude,
longitude,
locationName,
locationAddress,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Location details set successfully',
}),
};
},
);

View File

@@ -1,64 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { interest_Xid?: number[]; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { interest_Xid } = body;
// Validate required fields
if (!interest_Xid || !Array.isArray(interest_Xid)) {
throw new ApiError(400, 'interest_Xid must be a non-empty array of numbers');
}
// Set the interests
await userService.setUserInterests(userId, interest_Xid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Interests set successfully',
}),
};
});

View File

@@ -1,95 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { JwtPayload } from 'jsonwebtoken';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { TokenService } from '../../../host/services/token.service';
const tokenService = new TokenService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Parse request body
let body: { refreshToken?: string };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { refreshToken } = body;
if (!refreshToken) {
throw new ApiError(400, 'Refresh token is required');
}
// Verify refresh token
const decodedToken = await tokenService.verifyRefreshToken(refreshToken);
if (!decodedToken || typeof decodedToken === 'string') {
throw new ApiError(401, 'Invalid or expired refresh token');
}
const payload = decodedToken as JwtPayload;
if (payload.type !== 'refresh') {
throw new ApiError(401, 'Token is not a refresh token');
}
const userId = payload.sub;
if (!userId) {
throw new ApiError(401, 'Invalid token payload');
}
// Check if user exists
const user = await prismaClient.user.findUnique({
where: { id: parseInt(userId, 10) },
select: { id: true, isActive: true },
});
if (!user || !user.isActive) {
throw new ApiError(401, 'User not found or inactive');
}
// Check if refresh token exists in database and is not blacklisted
const tokenRecord = await prismaClient.token.findFirst({
where: {
token: refreshToken,
userXid: parseInt(userId, 10),
tokenType: 'refresh',
isBlackListed: false,
},
});
if (!tokenRecord) {
throw new ApiError(401, 'Refresh token is invalid or blacklisted');
}
// Generate new access token
const newAccessToken = await tokenService.generateAuthToken(Number(userId));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Access token generated successfully',
accessToken: newAccessToken.access.token,
accessTokenExpires: newAccessToken.access.expires,
data: null,
}),
};
},
);

View File

@@ -1,165 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import * as bcrypt from 'bcryptjs';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { ROLE, USER_STATUS } from '../../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { encryptUserId } from '../../../../common/utils/helper/CodeGenerator';
import { OtpGeneratorSixDigit } from '../../../../common/utils/helper/OtpGenerator';
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Parse request body
let body: { mobileNumber?: string };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { mobileNumber } = body;
if (!mobileNumber || !/^\d{10,15}$/.test(mobileNumber)) {
throw new ApiError(400, 'Mobile number is required');
}
// Use a single transaction for user creation/lookup and OTP storage
const transactionResult = await prismaClient.$transaction(async (tx) => {
const user = await tx.user.findFirst({
where: { mobileNumber: mobileNumber, isActive: true, userStatus: USER_STATUS.ACTIVE },
select: { id: true, userPasscode: true, mobileNumber: true },
});
let newUserLocal;
let isNewUser = false;
if (user && !user.userPasscode) {
// reuse existing invited user record
newUserLocal = user;
} else if (user) {
// Fully registered user already exists
newUserLocal = user;
}
else {
// create new user record within the transaction
newUserLocal = await tx.user.create({
data: {
mobileNumber: mobileNumber,
role: {
connect: {
id: ROLE.USER, // 👈 Role ID
},
},
userStatus: USER_STATUS.ACTIVE
},
});
const referenceNumber = `USR-${String(newUserLocal.id).padStart(6, '0')}`;
await tx.user.update({
where: { id: newUserLocal.id },
data: { userRefNumber: referenceNumber }
});
await tx.activitySorting.createMany({
data: [
{
userXid: newUserLocal.id,
activitySortXid: 1, // Default sorting (e.g., "Rating")
sortOrder: 'desc', // Default order
filterValue: 'rating', // Default filter
displayOrder: 1, // First in the list
isActive: true
},
{
userXid: newUserLocal.id,
activitySortXid: 2, // e.g., "Price"
sortOrder: 'asc',
filterValue: 'price',
displayOrder: 2,
isActive: true
},
{
userXid: newUserLocal.id,
activitySortXid: 3, // e.g., "Distance"
sortOrder: 'desc',
filterValue: 'sustainability',
displayOrder: 3,
isActive: true
},
{
userXid: newUserLocal.id,
activitySortXid: 4, // e.g., "Distance"
sortOrder: 'asc',
filterValue: 'nearbyradius',
displayOrder: 4,
isActive: true
},
{
userXid: newUserLocal.id,
activitySortXid: 5, // e.g., "Distance"
sortOrder: 'asc',
filterValue: 'quality',
displayOrder: 5,
isActive: true
},
],
skipDuplicates: true
});
isNewUser = true;
}
// Generate OTP (6-digit) and store within the same transaction
const otp = OtpGeneratorSixDigit.generateOtp();
const hashedOtp = await bcrypt.hash(otp, 10);
const expiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
// delete old active OTPs for this user/purpose
await tx.userOtp.deleteMany({
where: { userXid: Number(newUserLocal.id), otpType: 'Register', isActive: true },
});
await tx.userOtp.create({
data: {
userXid: Number(newUserLocal.id),
otpType: 'Register',
otpCode: hashedOtp,
expiresOn: expiry,
isVerified: false,
isActive: true,
},
});
const encryptedId = encryptUserId(String(newUserLocal.id));
return { newUser: newUserLocal, otp, encryptedId, isNewUser };
});
if (!transactionResult || !transactionResult.otp) {
throw new ApiError(500, 'Failed to generate OTP');
}
// Send OTP email outside the DB transaction
// await sendOtpEmailForHost(transactionResult.newUser.emailAddress, transactionResult.otp);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'OTP sent successfully.',
data: {
isNewUser: transactionResult.isNewUser,
},
}),
};
});

View File

@@ -1,64 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { userPasscode?: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { userPasscode } = body;
// Validate required fields
if (!userPasscode) {
throw new ApiError(400, 'userPasscode is required');
}
// Set the passcode
await userService.setUserPasscode(userId, userPasscode);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Passcode set successfully',
}),
};
});

View File

@@ -1,73 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { userPersonalInfoSchema } from '../../../../common/utils/validation/user/addPersonalInfo.validation';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using the shared authForHost function
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { firstName?: string; lastName?: string; genderName: string; dateOfBirth?: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
// ✅ Validate payload using Zod
const validationResult = userPersonalInfoSchema.safeParse({
...(body as object),
});
if (!validationResult.success) {
const errorMessages = validationResult.error.issues.map(e => e.message).join(', ');
throw new ApiError(400, `Validation failed: ${errorMessages}`);
}
const validatedData = validationResult.data;
await userService.addPersonalInfo(userId, {
...validatedData
});
const interests = await userService.getAllInterestDetails();
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Personal Info added successfully',
data: interests
}),
};
});

View File

@@ -1,51 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { UserService } from '../../services/user.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { TokenService } from '../../../host/services/token.service';
const userService = new UserService(prismaClient);
const tokenService = new TokenService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Parse request body
let body: { mobileNumber?: string; otp?: string };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { mobileNumber, otp } = body;
if (!mobileNumber || !otp) {
throw new ApiError(400, 'Mobile number and OTP are required');
}
await userService.verifyHostOtp(mobileNumber, otp);
const user = await userService.getUserByMobileNumber(mobileNumber);
const generateTokenForUser = await tokenService.generateAuthToken(
user.id
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'OTP verified successfully',
accessToken: generateTokenForUser.access.token,
refreshToken: generateTokenForUser.refresh.token,
data: null,
}),
};
});

View File

@@ -1,62 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
// Parse request body
let body: { passcode?: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { passcode } = body;
// Validate required fields
if (!passcode) {
throw new ApiError(400, 'passcode is required');
}
// Verify the passcode
const isValid = await userService.verifyUserPasscode(userId, passcode);
if (!isValid) {
throw new ApiError(400, 'Invalid passcode');
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Passcode verified successfully',
}),
};
});

View File

@@ -1,108 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { AddSchoolCompanyDetailDTO } from '../../dto/addSchoolCompanyDetail.dto';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract and verify token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Extract body parameters
let body;
try {
body = JSON.parse(event.body || '{}');
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { schoolCompanyName, isSchool, cityXid } = body;
// Validate required inputs
if (!schoolCompanyName || schoolCompanyName.trim().length === 0) {
throw new ApiError(400, 'schoolCompanyName is required');
}
if (schoolCompanyName.trim().length < 2) {
throw new ApiError(
400,
'schoolCompanyName must be at least 2 characters long',
);
}
if (isSchool === undefined || isSchool === null) {
throw new ApiError(
400,
'isSchool is required and must be a boolean value',
);
}
if (typeof isSchool !== 'boolean') {
throw new ApiError(
400,
'isSchool must be a boolean value (true or false)',
);
}
if (!cityXid || typeof cityXid !== 'number') {
throw new ApiError(400, 'cityXid is required and must be a number');
}
const recordCount = await userService.getConnectionCountOfUser(userId);
if(recordCount >= 10) {
throw new ApiError(400, 'You have reached the maximum number of connections (10). Please remove an existing connection before adding a new one.');
}
// Create DTO
const dto = new AddSchoolCompanyDetailDTO(
schoolCompanyName.trim().toLowerCase(),
isSchool,
cityXid,
userId
);
// Call service to add or find school/company
const result = await userService.addOrFindSchoolCompanyDetail(dto);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Connection added successfully',
data: null,
}),
};
},
);

View File

@@ -1,69 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => {
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'Token is required');
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId) {
throw new ApiError(400, 'Invalid user');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName ?? '';
const stateName = event.queryStringParameters?.stateName ?? '';
const cityName = event.queryStringParameters?.cityName ?? '';
const schoolCompanyXidsParam = event.queryStringParameters?.schoolCompanyXids;
if (!schoolCompanyXidsParam) {
throw new ApiError(400, 'schoolCompanyXids is required');
}
const schoolCompanyXids = schoolCompanyXidsParam
.split(',')
.map(id => Number(id))
.filter(id => !isNaN(id));
if (!schoolCompanyXids.length) {
throw new ApiError(400, 'Invalid schoolCompanyXids');
}
const result = await userService.getAllActivitiesFromConnectionsUserInterests(
userId,
schoolCompanyXids,
page,
limit,
countryName,
stateName,
cityName,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
data: result,
}),
};
});

View File

@@ -1,44 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getAllConnectionDetailsOfUser(userId);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,102 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract and verify token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Extract query parameters
const searchQuery = event.queryStringParameters?.searchQuery?.trim();
const isSchool = event.queryStringParameters?.isSchool?.toLowerCase();
// Validate inputs
if (!searchQuery || searchQuery.length === 0) {
throw new ApiError(400, 'Search query is required');
}
if (searchQuery.length < 2) {
throw new ApiError(
400,
'Search query must be at least 2 characters long',
);
}
// Validate isSchool parameter
if (!isSchool || !['true', 'false'].includes(isSchool)) {
throw new ApiError(
400,
'isSchool parameter must be either "true" (for schools) or "false" (for companies)',
);
}
// Convert isSchool to boolean
const filterBySchool = isSchool === 'true';
// Call service to search
const results = await userService.searchSchoolsAndCompanies(
searchQuery,
filterBySchool,
);
// Check if any results found
if (results.length === 0) {
const type = filterBySchool ? 'school' : 'company';
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `No ${type}s found matching your search`,
data: [],
count: 0,
}),
};
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `${filterBySchool ? 'Schools' : 'Companies'} found successfully`,
data: results,
count: results.length,
}),
};
},
);

View File

@@ -1,68 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { AddSchoolCompanyDetailDTO } from '../../dto/addSchoolCompanyDetail.dto';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract and verify token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Extract body parameters
let body;
try {
body = JSON.parse(event.body || '{}');
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { connectDetailsXid } = body;
if (!connectDetailsXid) {
throw new ApiError(400, 'connectDetailsXid is required');
}
// Call service to add or find school/company
const result = await userService.deleteConnectDetails(userId, Number(connectDetailsXid));
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Connection details removed successfully',
data: null,
}),
};
},
);

View File

@@ -1,86 +0,0 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Extract and verify token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Extract query parameters
const searchQuery = event.queryStringParameters?.searchQuery?.trim();
// Validate inputs
if (!searchQuery || searchQuery.length === 0) {
throw new ApiError(400, 'Search query is required');
}
if (searchQuery.length < 2) {
throw new ApiError(
400,
'Search query must be at least 2 characters long',
);
}
// Call service to search cities
const results = await userService.searchCities(searchQuery);
// Check if any results found
if (results.length === 0) {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'No cities found matching your search',
data: [],
count: 0,
}),
};
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Cities found successfully',
data: results, // already capped at 50 in DB query
count: results.length,
}),
};
},
);

View File

@@ -1,938 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import {
ACTIVITY_AM_INTERNAL_STATUS,
ACTIVITY_INTERNAL_STATUS,
} from '../../../common/utils/constants/host.constant';
import config from '../../../config/config';
const bucket = config.aws.bucketName;
const attachPresignedUrl = async (file: string | null | undefined) => {
if (!file) return null;
const key = file.startsWith('http')
? new URL(file).pathname.replace(/^\/+/, '')
: file;
return getPresignedUrl(bucket, key);
};
const attachMediaWithPresignedUrl = async (mediaArr: any[] = []) => {
return Promise.all(
mediaArr.map(async (m) => ({
...m,
presignedUrl: await attachPresignedUrl(m.mediaFileName),
})),
);
};
@Injectable()
export class FilteredLandingPageService {
constructor(private readonly prisma: PrismaClient) { }
normalizeName = (name: string): string => {
return name
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.replace(/\s+/g, ' ')
.trim();
};
findOrCreateLocation = async (
countryName: string,
stateName: string,
cityName: string,
tx: any,
) => {
const normalizedCountry = this.normalizeName(countryName);
const normalizedState = this.normalizeName(stateName);
const normalizedCity = this.normalizeName(cityName);
let country = await tx.countries.findFirst({
where: {
countryName: { contains: normalizedCountry, mode: 'insensitive' },
},
});
if (!country) {
country = await tx.countries.create({
data: {
countryName: countryName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
let state = await tx.states.findFirst({
where: {
countryXid: country.id,
stateName: { contains: normalizedState, mode: 'insensitive' },
},
});
if (!state) {
state = await tx.states.create({
data: {
countryXid: country.id,
stateName: stateName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
let city = await tx.cities.findFirst({
where: {
stateXid: state.id,
cityName: { contains: normalizedCity, mode: 'insensitive' },
},
});
if (!city) {
city = await tx.cities.create({
data: {
stateXid: state.id,
cityName: cityName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
return {
countryXid: country.id,
stateXid: state.id,
cityXid: city.id,
};
};
// attachMediaWithPresignedUrl = async (mediaArr = []) => {
// return (
// await Promise.all(
// mediaArr.map(async (m) => {
// return {
// ...m,
// presignedUrl: await this.attachPresignedUrl(m.mediaFileName),
// };
// }),
// )
// );
// };
calculateDistance = (
lat1: number | null,
lon1: number | null,
lat2: number | null,
lon2: number | null,
) => {
if (!lat1 || !lon1 || !lat2 || !lon2) return null;
const R = 6371; // km
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
// return distance rounded to 2 decimals
return Number(distance.toFixed(2));
};
async rankAndPaginateActivities(
tx: any,
whereClause: any,
page: number,
limit: number,
connectionInterestMap: Map<number, number>,
) {
const skip = (page - 1) * limit;
// Get total count
const totalCount = await tx.activities.count({ where: whereClause });
// Fetch activities with ranking metadata
const activities = await tx.activities.findMany({
where: whereClause,
skip,
take: limit,
select: {
id: true,
activityTitle: true,
sustainabilityScore: true,
totalScore: true,
activityType: {
select: {
interestXid: true,
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
// Fetch ranking metadata
ItineraryActivities: {
select: {
ActivityFeedbacks: {
select: { activityStars: true },
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: { sellPrice: true },
},
},
},
},
});
// Sort and format
const sortedActivities = activities
.map((act) => {
const feedbacks = act.ItineraryActivities.flatMap(
(ia) => ia.ActivityFeedbacks,
);
const totalStars = feedbacks.reduce(
(sum, f) => sum + f.activityStars,
0,
);
const avgRating =
feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
const prices = act.ActivityVenues.flatMap((v) =>
v.ActivityPrices.map((p) => p.sellPrice),
).filter((p) => p !== null) as number[];
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
return {
...act,
avgRating,
minPrice,
sustainabilityScore: act.sustainabilityScore ?? 0,
totalScore: act.totalScore ?? 0,
};
})
.sort((a, b) => {
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
if (b.sustainabilityScore !== a.sustainabilityScore)
return b.sustainabilityScore - a.sustainabilityScore;
return b.totalScore - a.totalScore;
});
const formattedActivities = await Promise.all(
sortedActivities.map(async (activity) => ({
interestXid: activity.activityType.interestXid,
activityId: activity.id,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityTitle: activity.activityTitle,
sustainabilityScore: activity.sustainabilityScore,
cheapestPrice: activity.minPrice === Infinity ? null : activity.minPrice,
distance: 0,
rating: activity.avgRating,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
})),
);
return {
page,
limit,
totalCount,
hasMore: skip + limit < totalCount,
activities: formattedActivities,
};
}
async getFilteredLandingPageAllDetails(
userId: number,
page: number,
limit: number,
countryName: string,
stateName: string,
cityName: string,
userLat: string,
userLong: string,
activityTypeXids?: number[],
) {
const data = await this.prisma.$transaction(async (tx) => {
const userAddressDetails = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: {
id: true,
address1: true,
address2: true,
pinCode: true,
locationName: true,
stateXid: true,
cityXid: true,
countryXid: true,
locationLat: true,
locationLong: true,
},
});
const userLatitude = userAddressDetails?.locationLat ?? null;
const userLongitude = userAddressDetails?.locationLong ?? null;
let effectiveLocation: {
countryXid?: number | null;
stateXid?: number | null;
cityXid?: number | null;
} | null = null;
const hasRequestLocation = countryName && stateName && cityName;
if (hasRequestLocation) {
effectiveLocation = await this.findOrCreateLocation(
countryName!,
stateName!,
cityName!,
tx,
);
} else if (userAddressDetails) {
effectiveLocation = {
countryXid: userAddressDetails.countryXid,
stateXid: userAddressDetails.stateXid,
cityXid: userAddressDetails.cityXid,
};
}
const effectiveCountryXid = effectiveLocation?.countryXid ?? null;
const effectiveStateXid = effectiveLocation?.stateXid ?? null;
// Get all activity types for user interests, filtered by selected activity types if provided
const activityTypeWhere: any = {
isActive: true,
};
if (activityTypeXids && activityTypeXids.length > 0) {
activityTypeWhere.id = { in: activityTypeXids };
}
const activityTypesWithInterests = await tx.activityTypes.findMany({
where: activityTypeWhere,
select: {
id: true,
activityTypeName: true,
interestXid: true,
interests: {
select: {
id: true,
interestName: true,
interestColor: true,
interestImage: true,
displayOrder: true,
},
},
},
});
if (!activityTypesWithInterests.length) {
return {
userAddressDetails,
interests: [],
activityTypes: [],
otherStatesActivities: null,
overSeasActivities: null,
};
}
const userBucketInterested = await tx.userBucketInterested.findMany({
where: {
userXid: userId,
isActive: true,
},
select: {
activityXid: true,
isBucket: true,
},
});
const userBucketActivityIds = userBucketInterested
.filter(u => u.isBucket)
.map(u => u.activityXid);
const userInterestedActivityIds = userBucketInterested
.filter(u => !u.isBucket)
.map(u => u.activityXid);
const allUserExcludedActivityIds = userBucketInterested.map(
u => u.activityXid,
);
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: {
id: true,
schoolCompanyXid: true,
}
});
const otherConnectionUsers = await tx.connectDetails.findMany({
where: { userXid: { notIn: [userId] }, isActive: true, schoolCompanyXid: { in: userConnectionDetails.map((u) => u.schoolCompanyXid) } },
select: {
id: true,
userXid: true,
}
});
const connectionUserIds =
otherConnectionUsers.length > 0
? otherConnectionUsers.map(u => u.userXid)
: [-1];
const connectionInterestByActivity = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
},
_count: {
activityXid: true,
},
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map(item => [
item.activityXid,
item._count.activityXid,
])
);
const skip = (page - 1) * limit;
// Group activity types by interest
const activityTypesByInterest = activityTypesWithInterests.reduce((acc, at) => {
if (!acc[at.interestXid]) {
acc[at.interestXid] = {
interest: at.interests,
activityTypes: [],
};
}
acc[at.interestXid].activityTypes.push({
activityTypeId: at.id,
activityTypeName: at.activityTypeName,
});
return acc;
}, {} as any);
// Fetch activities for each activity type with excluded activities filter
const activitiesByActivityType = await Promise.all(
activityTypesWithInterests.map(async (activityType) => {
const activities = await tx.activities.findMany({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
activityTypeXid: activityType.id,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
},
skip,
take: limit,
orderBy: { id: 'desc' },
select: {
id: true,
activityTitle: true,
activityDurationMins: true,
sustainabilityScore: true,
checkInLat: true,
checkInLong: true,
activityType: {
select: {
id: true,
activityTypeName: true,
interestXid: true,
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: {
sellPrice: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
},
});
const formattedActivities = await Promise.all(
activities.map(async (activity) => {
const cheapestPrice =
activity.ActivityVenues.flatMap((v) => v.ActivityPrices)
.map((p) => p.sellPrice)
.filter(Boolean)
.sort((a, b) => a - b)[0] ?? null;
const distance = this.calculateDistance(
userLatitude,
userLongitude,
activity.checkInLat,
activity.checkInLong,
);
return {
activityId: activity.id,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityTitle: activity.activityTitle,
activityDurationMins: activity.activityDurationMins,
sustainabilityScore: activity.sustainabilityScore,
cheapestPrice,
distance,
rating: 0,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
};
}),
);
return {
activityTypeId: activityType.id,
activityTypeName: activityType.activityTypeName,
interestXid: activityType.interestXid,
activities: formattedActivities,
pagination: {
page,
limit,
hasMore: formattedActivities.length === limit,
},
};
}),
);
// Group by interests for the final structure
const interestsWithActivities = await Promise.all(
Object.values(activityTypesByInterest).map(async (interestGroup: any) => {
// collect all activities belonging to this interest
const activitiesForInterest = activitiesByActivityType
.filter(a => a.interestXid === interestGroup.interest.id)
.flatMap(a => a.activities);
return {
interestId: interestGroup.interest.id,
interestName: interestGroup.interest.interestName,
interestColor: interestGroup.interest.interestColor,
interestImage: interestGroup.interest.interestImage,
interestImagePresignedUrl: await attachPresignedUrl(
interestGroup.interest.interestImage
),
displayOrder: interestGroup.interest.displayOrder,
page,
limit,
hasMore: activitiesForInterest.length === limit,
activities: activitiesForInterest
};
})
);
// Most Hyped Activities with filtering
const mostHypedGrouped = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
isActive: true,
isBucket: false,
activityXid: {
notIn: allUserExcludedActivityIds.length ? allUserExcludedActivityIds : [-1],
},
},
_count: {
activityXid: true,
},
orderBy: {
_count: {
activityXid: 'desc',
},
},
});
// Filter most hyped activities by activity type if provided
let filteredMostHypedActivityIds = mostHypedGrouped.map((a) => a.activityXid);
if (activityTypeXids && activityTypeXids.length > 0) {
const activitiesWithTypes = await tx.activities.findMany({
where: {
id: { in: filteredMostHypedActivityIds },
activityTypeXid: { in: activityTypeXids },
},
select: { id: true },
});
filteredMostHypedActivityIds = activitiesWithTypes.map(a => a.id);
}
const finalMostHypedGrouped = mostHypedGrouped
.filter(group => filteredMostHypedActivityIds.includes(group.activityXid))
.slice(skip, skip + limit);
const totalHypedActivities = filteredMostHypedActivityIds.length;
const mostHypedActivityIds = finalMostHypedGrouped.map((a) => a.activityXid);
const mostHypedActivitiesRaw = await tx.activities.findMany({
where: {
id: {
in: mostHypedActivityIds,
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
},
select: {
id: true,
activityTitle: true,
sustainabilityScore: true,
totalScore: true,
activityType: {
select: {
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
ItineraryActivities: {
select: {
ActivityFeedbacks: {
select: { activityStars: true },
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: { sellPrice: true },
},
},
},
},
});
// Sort Most Hyped by the 4 criteria
const mostHypedSorted = mostHypedActivitiesRaw
.map((act) => {
const feedbacks = act.ItineraryActivities.flatMap(
(ia) => ia.ActivityFeedbacks,
);
const totalStars = feedbacks.reduce(
(sum, f) => sum + f.activityStars,
0,
);
const avgRating =
feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
const prices = act.ActivityVenues.flatMap((v) =>
v.ActivityPrices.map((p) => p.sellPrice),
).filter((p) => p !== null) as number[];
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
return {
...act,
avgRating,
minPrice,
sustainabilityScore: act.sustainabilityScore ?? 0,
totalScore: act.totalScore ?? 0,
hypeCount:
finalMostHypedGrouped.find((g) => g.activityXid === act.id)?._count
.activityXid ?? 0,
};
})
.sort((a, b) => {
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
if (b.sustainabilityScore !== a.sustainabilityScore)
return b.sustainabilityScore - a.sustainabilityScore;
return b.totalScore - a.totalScore;
});
const mostHypedActivities = await Promise.all(
mostHypedSorted.map(async (activity) => ({
activityId: activity.id,
activityTitle: activity.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
hypeCount: activity.hypeCount,
distance: 0,
rating: 0,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
})),
);
const formattedMostHypedActivities = {
page,
limit,
totalCount: totalHypedActivities,
hasMore: skip + limit < totalHypedActivities,
activities: mostHypedActivities,
};
// New Arrivals with filtering
const newArrivalsWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
};
if (activityTypeXids && activityTypeXids.length > 0) {
newArrivalsWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedNewArrivalsActivities = await this.rankAndPaginateActivities(
tx,
newArrivalsWhere,
page,
limit,
connectionInterestMap
);
// Other States Activities with filtering
const otherStatesWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
};
if (effectiveCountryXid) {
otherStatesWhere.checkInCountryXid = effectiveCountryXid;
}
if (effectiveStateXid) {
otherStatesWhere.checkInStateXid = { not: effectiveStateXid };
}
if (activityTypeXids && activityTypeXids.length > 0) {
otherStatesWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedOtherStatesActivities = await this.rankAndPaginateActivities(
tx,
otherStatesWhere,
page,
limit,
connectionInterestMap
);
// Random Activities with filtering
const totalActiveCount = await tx.activities.count({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
...(activityTypeXids && activityTypeXids.length > 0 && {
activityTypeXid: { in: activityTypeXids },
}),
},
});
let randomActivities: any[] = [];
if (totalActiveCount > 0) {
const takeCount = Math.min(5, totalActiveCount);
const randomOffsets = new Set<number>();
while (randomOffsets.size < takeCount) {
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
}
const randomFetched = await Promise.all(
Array.from(randomOffsets).map((offset) =>
tx.activities.findFirst({
skip: offset,
where: {
isActive: true,
activityInternalStatus:
ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus:
ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
...(activityTypeXids && activityTypeXids.length > 0 && {
activityTypeXid: { in: activityTypeXids },
}),
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
mediaFileName: true,
},
},
},
}),
),
);
randomActivities = await Promise.all(
randomFetched
.filter(Boolean)
.map(async (activity) => {
const cover = activity!.ActivitiesMedia?.[0];
return {
activityId: activity!.id,
activityTitle: activity!.activityTitle,
coverImage: cover?.mediaFileName ?? null,
coverImagePresignedUrl: await attachPresignedUrl(
cover?.mediaFileName
),
};
}),
);
}
// Overseas Activities with filtering
const overseasWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
};
if (effectiveCountryXid) {
overseasWhere.checkInCountryXid = { not: effectiveCountryXid };
}
if (activityTypeXids && activityTypeXids.length > 0) {
overseasWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedOverSeasActivities = await this.rankAndPaginateActivities(
tx,
overseasWhere,
page,
limit,
connectionInterestMap
);
return {
userAddressDetails,
experiencesLogged: 0,
citiesDiscovered: 0,
loggedInNetworkCount: 0,
citiesInNetworkCount: 0,
rating: 0,
interestedCount: userInterestedActivityIds.length,
bucketCount: userBucketActivityIds.length,
pagination: {
page,
limit,
},
randomActivities,
interests: interestsWithActivities,
otherStatesActivities: formattedOtherStatesActivities,
overSeasActivities: formattedOverSeasActivities,
newArrivalsActivities: formattedNewArrivalsActivities,
mostHypedActivities: formattedMostHypedActivities,
};
});
return data;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More