Compare commits
7 Commits
sprint4Sta
...
swagger
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3937be710b | ||
|
|
31c312eb3c | ||
|
|
f6e01ac9e3 | ||
|
|
76970c914e | ||
|
|
61737235a4 | ||
|
|
844bbf9618 | ||
|
|
cdae23ec6c |
@@ -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
62
Dockerfile
Normal 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
86
docker-compose.yml
Normal 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
19
init.sql
Normal 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;
|
||||||
@@ -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();
|
|
||||||
73
layers/prisma/nodejs/package-lock.json
generated
73
layers/prisma/nodejs/package-lock.json
generated
@@ -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
561
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -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,29 +48,19 @@
|
|||||||
"@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",
|
||||||
"razorpay": "^2.9.6",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"serverless": "4.24.0",
|
"serverless": "4.24.0",
|
||||||
@@ -100,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",
|
||||||
|
|||||||
@@ -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!')
|
|
||||||
}
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
2668
prisma/seed.ts
2668
prisma/seed.ts
File diff suppressed because it is too large
Load Diff
@@ -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)}
|
|
||||||
|
|
||||||
@@ -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)}
|
|
||||||
|
|
||||||
@@ -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)}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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)}
|
|
||||||
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# Legacy monolith config. For new deployments use serverless.*.yml files.
|
|
||||||
service: minglar
|
service: minglar
|
||||||
|
|
||||||
|
|
||||||
useDotenv: true
|
useDotenv: true
|
||||||
|
|
||||||
params:
|
params:
|
||||||
@@ -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
|
||||||
@@ -34,8 +27,6 @@ provider:
|
|||||||
binaryMediaTypes:
|
binaryMediaTypes:
|
||||||
- '*/*'
|
- '*/*'
|
||||||
minimumCompressionSize: 1024
|
minimumCompressionSize: 1024
|
||||||
websocketsApiName: minglar-ws-${sls:stage}
|
|
||||||
websocketsApiRouteSelectionExpression: $request.body.action
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: ${env:DATABASE_URL}
|
DATABASE_URL: ${env:DATABASE_URL}
|
||||||
@@ -66,10 +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}
|
|
||||||
RAZORPAY_KEY_ID: ${env:RAZORPAY_KEY_ID}
|
|
||||||
RAZORPAY_KEY_SECRET: ${env:RAZORPAY_KEY_SECRET}
|
|
||||||
RAZORPAY_WEBHOOK_SECRET: ${env:RAZORPAY_WEBHOOK_SECRET}
|
|
||||||
|
|
||||||
iam:
|
iam:
|
||||||
role:
|
role:
|
||||||
@@ -83,11 +70,12 @@ provider:
|
|||||||
Resource:
|
Resource:
|
||||||
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
|
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
|
||||||
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
|
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
|
||||||
- Effect: Allow
|
|
||||||
Action:
|
custom:
|
||||||
- execute-api:ManageConnections
|
serverless-offline:
|
||||||
Resource:
|
reloadHandler: true
|
||||||
- 'arn:aws:execute-api:${self:provider.region}:*:*/*/@connections/*'
|
httpPort: 3000
|
||||||
|
noPrependStageInUrl: true
|
||||||
|
|
||||||
build:
|
build:
|
||||||
esbuild:
|
esbuild:
|
||||||
@@ -157,19 +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/websocket.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
|
|
||||||
@@ -1,138 +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}
|
|
||||||
AM_INTERFACE_LINK: ${env:AM_INTERFACE_LINK}
|
|
||||||
HOST_LINK: ${env:HOST_LINK}
|
|
||||||
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
|
|
||||||
RAZORPAY_KEY_ID: ${env:RAZORPAY_KEY_ID}
|
|
||||||
RAZORPAY_KEY_SECRET: ${env:RAZORPAY_KEY_SECRET}
|
|
||||||
RAZORPAY_WEBHOOK_SECRET: ${env:RAZORPAY_WEBHOOK_SECRET}
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -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,102 +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
|
|
||||||
|
|
||||||
inviteHostMember:
|
|
||||||
handler: src/modules/host/handlers/settings/inviteMember.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/handlers/settings/**'
|
|
||||||
- '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: /settings/invite-member
|
|
||||||
method: post
|
|
||||||
|
|
||||||
getAllInvitedCoadminAndOperator:
|
|
||||||
handler: src/modules/host/handlers/settings/getAllInvitedCoadminAndOperator.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/handlers/settings/**'
|
|
||||||
- '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: /settings/invited-coadmin-operators
|
|
||||||
method: get
|
|
||||||
|
|
||||||
saveRolePermissions:
|
|
||||||
handler: src/modules/host/handlers/settings/saveRolePermissions.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/handlers/settings/**'
|
|
||||||
- '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: /settings/save-role-permissions
|
|
||||||
method: post
|
|
||||||
|
|
||||||
getPermissionMasters:
|
|
||||||
handler: src/modules/host/handlers/settings/getPermissionMasters.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/handlers/settings/**'
|
|
||||||
- '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: /settings/permission-masters
|
|
||||||
method: get
|
|
||||||
|
|
||||||
getHostMemberRoles:
|
|
||||||
handler: src/modules/host/handlers/settings/getMemberRoles.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/host/handlers/settings/**'
|
|
||||||
- '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: /settings/member-roles
|
|
||||||
method: get
|
|
||||||
|
|
||||||
# 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
|
||||||
@@ -400,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:
|
||||||
@@ -416,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:
|
||||||
@@ -432,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
|
||||||
@@ -448,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:
|
||||||
@@ -463,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:
|
||||||
@@ -493,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:
|
||||||
@@ -510,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
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ minglarRegistration:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /registration
|
path: /minglaradmin/registration
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
minglarLoginForAdmin:
|
minglarLoginForAdmin:
|
||||||
@@ -28,7 +28,7 @@ minglarLoginForAdmin:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /login
|
path: /minglaradmin/login
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
minglarCreatePassword:
|
minglarCreatePassword:
|
||||||
@@ -43,7 +43,7 @@ minglarCreatePassword:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /create-password
|
path: /minglaradmin/create-password
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
updateMinglarProfile:
|
updateMinglarProfile:
|
||||||
@@ -60,7 +60,7 @@ updateMinglarProfile:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /update-profile
|
path: /minglaradmin/update-profile
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
prepopulateRole:
|
prepopulateRole:
|
||||||
@@ -75,7 +75,7 @@ prepopulateRole:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /prepopulate-Roles
|
path: /minglaradmin/prepopulate-Roles
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getHostDetailsById:
|
getHostDetailsById:
|
||||||
@@ -90,7 +90,7 @@ getHostDetailsById:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/get-host-details/{host_xid}
|
path: /minglaradmin/hosthub/hosts/get-host-details/{host_xid}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
inviteTeammate:
|
inviteTeammate:
|
||||||
@@ -105,7 +105,7 @@ inviteTeammate:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /settings/teammates/invite-teammate
|
path: /minglaradmin/settings/teammates/invite-teammate
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllHostApplication:
|
getAllHostApplication:
|
||||||
@@ -121,7 +121,7 @@ getAllHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/get-all-host-applications-am
|
path: /minglaradmin/hosthub/hosts/get-all-host-applications-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllHostActivityForAdmin:
|
getAllHostActivityForAdmin:
|
||||||
@@ -137,7 +137,7 @@ getAllHostActivityForAdmin:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /get-all-activity-of-host/{id}
|
path: /minglaradmin/get-all-activity-of-host/{id}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllOnboardingHostApplications:
|
getAllOnboardingHostApplications:
|
||||||
@@ -153,7 +153,7 @@ getAllOnboardingHostApplications:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/get-all-host-applications-admin
|
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllOnboardingHostApplications_New:
|
getAllOnboardingHostApplications_New:
|
||||||
@@ -169,7 +169,7 @@ getAllOnboardingHostApplications_New:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/get-all-host-applications-admin-new
|
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin-new
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllInvitationDetails:
|
getAllInvitationDetails:
|
||||||
@@ -184,7 +184,7 @@ getAllInvitationDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /settings/teammates/get-all-invitation-details
|
path: /minglaradmin/settings/teammates/get-all-invitation-details
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
addSuggestion:
|
addSuggestion:
|
||||||
@@ -200,7 +200,7 @@ addSuggestion:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/add-suggestion
|
path: /minglaradmin/hosthub/hosts/add-suggestion
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllCoadminAndAMDetails:
|
getAllCoadminAndAMDetails:
|
||||||
@@ -215,7 +215,7 @@ getAllCoadminAndAMDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /settings/teammates/get-all-coadmin-am
|
path: /minglaradmin/settings/teammates/get-all-coadmin-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllInvitedCoadminAndAMDetails:
|
getAllInvitedCoadminAndAMDetails:
|
||||||
@@ -230,7 +230,7 @@ getAllInvitedCoadminAndAMDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /settings/teammates/get-all-invited-coadmin-am
|
path: /minglaradmin/settings/teammates/get-all-invited-coadmin-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAmDetailsbyId:
|
getAmDetailsbyId:
|
||||||
@@ -245,7 +245,7 @@ getAmDetailsbyId:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /settings/teammates/get-am-details-by-id/{amXid}
|
path: /minglaradmin/settings/teammates/get-am-details-by-id/{amXid}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
assignAMToHost:
|
assignAMToHost:
|
||||||
@@ -261,7 +261,7 @@ assignAMToHost:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/assign-am
|
path: /minglaradmin/hosthub/onboarding/assign-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
editAgreementDetailsAndAccept:
|
editAgreementDetailsAndAccept:
|
||||||
@@ -277,7 +277,7 @@ editAgreementDetailsAndAccept:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/edit-agreement-accept-host
|
path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
getAllPqqQuesAnsForAM:
|
getAllPqqQuesAnsForAM:
|
||||||
@@ -292,7 +292,7 @@ getAllPqqQuesAnsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
path: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
acceptHostApplication:
|
acceptHostApplication:
|
||||||
@@ -308,7 +308,7 @@ acceptHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/accept-host-application
|
path: /minglaradmin/hosthub/hosts/accept-host-application
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
RejectPQQByAM:
|
RejectPQQByAM:
|
||||||
@@ -324,23 +324,7 @@ RejectPQQByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/reject-pq-by-am
|
path: /minglaradmin/hosthub/hosts/reject-pq-by-am
|
||||||
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: /hosthub/hosts/reject-activity-application-by-am
|
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
acceptPQByAM:
|
acceptPQByAM:
|
||||||
@@ -356,23 +340,7 @@ acceptPQByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/accept-pq-by-am
|
path: /minglaradmin/hosthub/hosts/accept-pq-by-am
|
||||||
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: /hosthub/hosts/accept-activity-application-by-am
|
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
rejectHostApplication:
|
rejectHostApplication:
|
||||||
@@ -388,7 +356,7 @@ rejectHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/reject-host-application
|
path: /minglaradmin/hosthub/onboarding/reject-host-application
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
rejectHostApplicationAM:
|
rejectHostApplicationAM:
|
||||||
@@ -404,7 +372,7 @@ rejectHostApplicationAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/reject-host-application-am
|
path: /minglaradmin/hosthub/hosts/reject-host-application-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
addPQQSuggestion:
|
addPQQSuggestion:
|
||||||
@@ -420,23 +388,7 @@ addPQQSuggestion:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/hosts/add-Pqq-suggestion
|
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
|
||||||
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: /hosthub/hosts/add-Activity-suggestion
|
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllPQPDetailsForAM:
|
getAllPQPDetailsForAM:
|
||||||
@@ -452,9 +404,10 @@ getAllPQPDetailsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /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
|
||||||
@@ -468,5 +421,5 @@ getSuggestionsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
path: /minglaradmin/hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
||||||
method: get
|
method: get
|
||||||
|
|||||||
29
serverless/functions/pqq.yml
Normal file
29
serverless/functions/pqq.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
createActivityAndAllQuestionsEntry:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/create-activity
|
||||||
|
method: post
|
||||||
|
|
||||||
|
submitPQAnswer:
|
||||||
|
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer.handler
|
||||||
|
memorySize: 384
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /host/Activity_Hub/OnBoarding/submit-pq-answer
|
||||||
|
method: patch
|
||||||
@@ -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
|
||||||
|
|||||||
20
serverless/functions/swagger.yml
Normal file
20
serverless/functions/swagger.yml
Normal 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
|
||||||
@@ -1,559 +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
|
|
||||||
|
|
||||||
searchConnectionPeople:
|
|
||||||
handler: src/modules/user/handlers/connections/searchConnectionPeople.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-connection-people
|
|
||||||
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
|
|
||||||
|
|
||||||
getUserItineraryDetails:
|
|
||||||
handler: src/modules/user/handlers/itinerary/getUserItineraryDetails.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: /itinerary/get-user-itinerary-details
|
|
||||||
method: get
|
|
||||||
|
|
||||||
getItineraryCheckoutDetails:
|
|
||||||
handler: src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.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: /itinerary/get-itinerary-checkout-details
|
|
||||||
method: get
|
|
||||||
|
|
||||||
saveUserItinerary:
|
|
||||||
handler: src/modules/user/handlers/itinerary/saveUserItinerary.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: /itinerary/save-user-itinerary
|
|
||||||
method: post
|
|
||||||
|
|
||||||
saveItineraryActivitySelections:
|
|
||||||
handler: src/modules/user/handlers/itinerary/saveItineraryActivitySelections.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: /itinerary/save-itinerary-activity-selections
|
|
||||||
method: post
|
|
||||||
|
|
||||||
getAllUserSavedItineraries:
|
|
||||||
handler: src/modules/user/handlers/itinerary/getAllUserSavedItineraries.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: /itinerary/get-all-user-saved-itineraries
|
|
||||||
method: get
|
|
||||||
|
|
||||||
createRazorpayOrder:
|
|
||||||
handler: src/modules/user/handlers/payment/createOrder.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: /payment/create-order
|
|
||||||
method: post
|
|
||||||
|
|
||||||
verifyRazorpayPayment:
|
|
||||||
handler: src/modules/user/handlers/payment/verifyPayment.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: /payment/verify-payment
|
|
||||||
method: post
|
|
||||||
|
|
||||||
razorpayWebhook:
|
|
||||||
handler: src/modules/user/handlers/payment/razorpayWebhook.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: /payment/webhook/razorpay
|
|
||||||
method: post
|
|
||||||
|
|
||||||
getMatchingBucketInterestedActivities:
|
|
||||||
handler: src/modules/user/handlers/itinerary/getMatchingBucketInterestedActivities.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: /itinerary/get-matching-bucket-interested-activities
|
|
||||||
method: post
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
websocketConnect:
|
|
||||||
handler: src/modules/websocket/handlers/connect.handler
|
|
||||||
memorySize: 256
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/websocket/**'
|
|
||||||
- 'src/common/**'
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
|
||||||
events:
|
|
||||||
- websocket:
|
|
||||||
route: $connect
|
|
||||||
|
|
||||||
websocketDisconnect:
|
|
||||||
handler: src/modules/websocket/handlers/disconnect.handler
|
|
||||||
memorySize: 256
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/websocket/**'
|
|
||||||
- 'src/common/**'
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
|
||||||
events:
|
|
||||||
- websocket:
|
|
||||||
route: $disconnect
|
|
||||||
|
|
||||||
websocketDefault:
|
|
||||||
handler: src/modules/websocket/handlers/default.handler
|
|
||||||
memorySize: 256
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/websocket/**'
|
|
||||||
- 'src/common/**'
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
|
||||||
events:
|
|
||||||
- websocket:
|
|
||||||
route: $default
|
|
||||||
|
|
||||||
websocketSendMessage:
|
|
||||||
handler: src/modules/websocket/handlers/sendMessage.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/websocket/**'
|
|
||||||
- 'src/common/**'
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
|
||||||
events:
|
|
||||||
- websocket:
|
|
||||||
route: sendMessage
|
|
||||||
|
|
||||||
websocketGetMessages:
|
|
||||||
handler: src/modules/websocket/handlers/getMessages.handler
|
|
||||||
memorySize: 384
|
|
||||||
package:
|
|
||||||
patterns:
|
|
||||||
- 'src/modules/websocket/**'
|
|
||||||
- 'src/common/**'
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
|
||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
|
||||||
events:
|
|
||||||
- websocket:
|
|
||||||
route: getMessages
|
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import httpStatus from 'http-status';
|
|
||||||
import ApiError from '../../utils/helper/ApiError';
|
|
||||||
import config from '../../../config/config';
|
|
||||||
import { ROLE } from '@/common/utils/constants/common.constant';
|
|
||||||
import { prisma } from '../../database/prisma.client';
|
|
||||||
|
|
||||||
interface DecodedToken {
|
|
||||||
id?: number;
|
|
||||||
sub?: string | number;
|
|
||||||
role?: string;
|
|
||||||
iat: number;
|
|
||||||
exp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function verifyAnyToken(
|
|
||||||
token: string
|
|
||||||
): Promise<{ id: number; roleXid: number; role?: string }> {
|
|
||||||
if (!token) {
|
|
||||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(token, config.jwt.secret) as unknown as DecodedToken;
|
|
||||||
|
|
||||||
const userId = decoded.id ?? (decoded.sub ? Number(decoded.sub) : null);
|
|
||||||
if (!userId) {
|
|
||||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload');
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { id: userId },
|
|
||||||
include: { role: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
const latestToken = await prisma.token.findFirst({
|
|
||||||
where: { userXid: userId },
|
|
||||||
orderBy: { id: 'desc' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (latestToken?.isBlackListed === true) {
|
|
||||||
throw new ApiError(401, 'This session is expired. Please login.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new ApiError(httpStatus.UNAUTHORIZED, 'User not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.isActive === false) {
|
|
||||||
throw new ApiError(
|
|
||||||
httpStatus.FORBIDDEN,
|
|
||||||
'Your account is deactivated by admin.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.roleXid !== ROLE.USER && user.roleXid !== ROLE.HOST) {
|
|
||||||
throw new ApiError(httpStatus.FORBIDDEN, 'Access denied.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { id: user.id, roleXid: user.roleXid || 0, role: user.role?.roleName };
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof jwt.TokenExpiredError) {
|
|
||||||
throw new ApiError(
|
|
||||||
httpStatus.UNAUTHORIZED,
|
|
||||||
'Your session has expired. Please log in again.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof ApiError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApiError(
|
|
||||||
httpStatus.FORBIDDEN,
|
|
||||||
'Invalid or expired authentication token.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import ApiError from '../utils/helper/ApiError';
|
|
||||||
|
|
||||||
interface SendMessageInput {
|
|
||||||
activityXid: number;
|
|
||||||
senderXid: number;
|
|
||||||
receiverXid: number;
|
|
||||||
message: string;
|
|
||||||
status?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetMessagesInput {
|
|
||||||
activityXid: number;
|
|
||||||
userXid: number;
|
|
||||||
otherUserXid: number;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatService {
|
|
||||||
constructor(private prisma: PrismaClient) {}
|
|
||||||
|
|
||||||
private async getHostUserIdForActivity(activityXid: number): Promise<number> {
|
|
||||||
const activity = await this.prisma.activities.findUnique({
|
|
||||||
where: { id: activityXid },
|
|
||||||
select: { host: { select: { userXid: true } } },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!activity) {
|
|
||||||
throw new ApiError(404, 'Activity not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostUserXid = activity.host?.userXid;
|
|
||||||
|
|
||||||
if (!hostUserXid) {
|
|
||||||
throw new ApiError(400, 'Host user not found for activity');
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostUserXid;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMessage(input: SendMessageInput) {
|
|
||||||
if (!input.activityXid || isNaN(input.activityXid)) {
|
|
||||||
throw new ApiError(400, 'Valid activityXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.senderXid || isNaN(input.senderXid)) {
|
|
||||||
throw new ApiError(400, 'Valid senderXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.receiverXid || isNaN(input.receiverXid)) {
|
|
||||||
throw new ApiError(400, 'Valid receiverXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.senderXid === input.receiverXid) {
|
|
||||||
throw new ApiError(400, 'Sender and receiver cannot be the same');
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = input.message?.trim();
|
|
||||||
if (!message) {
|
|
||||||
throw new ApiError(400, 'Message is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostUserXid = await this.getHostUserIdForActivity(input.activityXid);
|
|
||||||
|
|
||||||
if (input.senderXid !== hostUserXid && input.receiverXid !== hostUserXid) {
|
|
||||||
throw new ApiError(
|
|
||||||
400,
|
|
||||||
'Sender or receiver must be the host for this activity'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiverExists = await this.prisma.user.findUnique({
|
|
||||||
where: { id: input.receiverXid },
|
|
||||||
select: { id: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!receiverExists) {
|
|
||||||
throw new ApiError(404, 'Receiver not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.activityMessages.create({
|
|
||||||
data: {
|
|
||||||
activityXid: input.activityXid,
|
|
||||||
senderXid: input.senderXid,
|
|
||||||
receivedXid: input.receiverXid,
|
|
||||||
message,
|
|
||||||
status: input.status?.trim() || 'unread',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMessages(input: GetMessagesInput) {
|
|
||||||
if (!input.activityXid || isNaN(input.activityXid)) {
|
|
||||||
throw new ApiError(400, 'Valid activityXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.userXid || isNaN(input.userXid)) {
|
|
||||||
throw new ApiError(400, 'Valid userXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.otherUserXid || isNaN(input.otherUserXid)) {
|
|
||||||
throw new ApiError(400, 'Valid otherUserXid is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.userXid === input.otherUserXid) {
|
|
||||||
throw new ApiError(400, 'Invalid otherUserXid');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostUserXid = await this.getHostUserIdForActivity(input.activityXid);
|
|
||||||
|
|
||||||
if (input.userXid !== hostUserXid && input.otherUserXid !== hostUserXid) {
|
|
||||||
throw new ApiError(
|
|
||||||
400,
|
|
||||||
'Conversation must include the host for this activity'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const limit = Math.min(Math.max(input.limit || 50, 1), 200);
|
|
||||||
|
|
||||||
const messages = await this.prisma.activityMessages.findMany({
|
|
||||||
where: {
|
|
||||||
activityXid: input.activityXid,
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
senderXid: input.userXid,
|
|
||||||
receivedXid: input.otherUserXid,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
senderXid: input.otherUserXid,
|
|
||||||
receivedXid: input.userXid,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: 'asc' },
|
|
||||||
take: limit,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.prisma.activityMessages.updateMany({
|
|
||||||
where: {
|
|
||||||
activityXid: input.activityXid,
|
|
||||||
senderXid: input.otherUserXid,
|
|
||||||
receivedXid: input.userXid,
|
|
||||||
status: 'unread',
|
|
||||||
},
|
|
||||||
data: { status: 'read' },
|
|
||||||
});
|
|
||||||
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
export class WebSocketService {
|
|
||||||
constructor(private prisma: PrismaClient) {}
|
|
||||||
|
|
||||||
async connect(params: {
|
|
||||||
connectionId: string;
|
|
||||||
userXid: number;
|
|
||||||
activityXid?: number | null;
|
|
||||||
}) {
|
|
||||||
const { connectionId, userXid, activityXid } = params;
|
|
||||||
return this.prisma.chatConnections.upsert({
|
|
||||||
where: { connectionId },
|
|
||||||
create: {
|
|
||||||
connectionId,
|
|
||||||
userXid,
|
|
||||||
activityXid: activityXid ?? null,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
userXid,
|
|
||||||
activityXid: activityXid ?? null,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnect(connectionId: string) {
|
|
||||||
return this.prisma.chatConnections.updateMany({
|
|
||||||
where: { connectionId },
|
|
||||||
data: {
|
|
||||||
isActive: false,
|
|
||||||
deletedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getConnectionById(connectionId: string) {
|
|
||||||
return this.prisma.chatConnections.findFirst({
|
|
||||||
where: { connectionId, isActive: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getConnectionsForUser(params: {
|
|
||||||
userXid: number;
|
|
||||||
activityXid?: number | null;
|
|
||||||
}) {
|
|
||||||
const { userXid, activityXid } = params;
|
|
||||||
return this.prisma.chatConnections.findMany({
|
|
||||||
where: {
|
|
||||||
userXid,
|
|
||||||
isActive: true,
|
|
||||||
...(activityXid ? { activityXid } : {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 Host’s 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, Minglar’s 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 manufacturer’s 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 Host’s 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 Host’s 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 Host’s 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]
|
|
||||||
`;
|
|
||||||
@@ -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",
|
|
||||||
}
|
}
|
||||||
@@ -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_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_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',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
|
||||||
|
|
||||||
|
|
||||||
@@ -46,18 +46,6 @@ export const parentCompanySchema = z.object({
|
|||||||
companyTypeXid: z.number()
|
companyTypeXid: z.number()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
||||||
firstName: z.string()
|
|
||||||
.max(50, "First name cannot exceed 50 characters")
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
lastName: z.string()
|
|
||||||
.max(50, "Last name cannot exceed 50 characters")
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
mobileNumber: z.string()
|
|
||||||
.max(15, "Mobile number cannot exceed 15 characters")
|
|
||||||
.optional(),
|
|
||||||
|
|
||||||
websiteUrl: z.string().nullable().optional(),
|
websiteUrl: z.string().nullable().optional(),
|
||||||
instagramUrl: z.string().nullable().optional(),
|
instagramUrl: z.string().nullable().optional(),
|
||||||
facebookUrl: z.string().nullable().optional(),
|
facebookUrl: z.string().nullable().optional(),
|
||||||
|
|||||||
@@ -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>;
|
|
||||||
@@ -83,12 +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'),
|
||||||
AM_INTERFACE_LINK:yup.string().required('Link to am interface 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'),
|
|
||||||
RAZORPAY_KEY_SECRET: yup.string().required('Razorpay key secret is required'),
|
|
||||||
RAZORPAY_KEY_ID: yup.string().required('Razorpay key id is required'),
|
|
||||||
RAZORPAY_WEBHOOK_SECRET: yup.string().required('Razorpay webhook secret is required'),
|
|
||||||
})
|
})
|
||||||
.noUnknown(true);
|
.noUnknown(true);
|
||||||
|
|
||||||
@@ -168,12 +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,
|
|
||||||
RAZORPAY_KEY_ID: envVars.RAZORPAY_KEY_ID,
|
|
||||||
RAZORPAY_KEY_SECRET: envVars.RAZORPAY_KEY_SECRET,
|
|
||||||
RAZORPAY_WEBHOOK_SECRET: envVars.RAZORPAY_WEBHOOK_SECRET,
|
|
||||||
AM_INTERFACE_LINK: envVars.AM_INTERFACE_LINK,
|
|
||||||
|
|
||||||
// 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
140
src/handlers/swagger.ts
Normal 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'
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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 venue’s 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>;
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
export interface ScheduleSlotDTO {
|
|
||||||
startTime: string;
|
|
||||||
endTime: string;
|
|
||||||
weekDay?: 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY' | 'SUNDAY' | null;
|
|
||||||
dayOfMonth?: number | null; // 1–31
|
|
||||||
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;
|
|
||||||
// }
|
|
||||||
@@ -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);
|
||||||
|
|
||||||
/* 2️⃣ PARSE JSON BODY */
|
/* 2️⃣ CONTENT 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,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
@@ -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=)
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -8,7 +8,6 @@ import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHo
|
|||||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { HostService } from '../../../services/host.service';
|
import { HostService } from '../../../services/host.service';
|
||||||
import { sendPQPEmailToAM } from '../../../services/sendHostResubmitEmailToAM.service';
|
|
||||||
|
|
||||||
const hostService = new HostService(prismaClient);
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
@@ -178,15 +177,6 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
|
const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
|
||||||
|
|
||||||
const details = await hostService.getSuggestionDetails(user.id);
|
|
||||||
|
|
||||||
await sendPQPEmailToAM(
|
|
||||||
details.hostDetails.accountManager.emailAddress,
|
|
||||||
details.hostDetails.accountManager.firstName,
|
|
||||||
details.hostDetails.companyName,
|
|
||||||
details.hostDetails.user.userRefNumber,
|
|
||||||
)
|
|
||||||
|
|
||||||
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
|
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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,
|
||||||
// }),
|
}),
|
||||||
// };
|
};
|
||||||
// });
|
});
|
||||||
|
|||||||
@@ -13,40 +13,6 @@ const hostService = new HostService(prismaClient);
|
|||||||
|
|
||||||
const s3 = new AWS.S3({ region: config.aws.region });
|
const s3 = new AWS.S3({ region: config.aws.region });
|
||||||
|
|
||||||
function parseMultipartFieldValue(val: string) {
|
|
||||||
if (val === '' || val === 'null' || val === 'undefined') return null;
|
|
||||||
|
|
||||||
const cleaned = val.trim();
|
|
||||||
const looksLikeJson =
|
|
||||||
(cleaned.startsWith('{') && cleaned.endsWith('}')) ||
|
|
||||||
(cleaned.startsWith('[') && cleaned.endsWith(']')) ||
|
|
||||||
(cleaned.startsWith('"') && cleaned.endsWith('"'));
|
|
||||||
|
|
||||||
if (!looksLikeJson) return val;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(cleaned);
|
|
||||||
} catch {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeComments(comments: unknown): string | null {
|
|
||||||
if (comments === null || comments === undefined || comments === '') return null;
|
|
||||||
|
|
||||||
const value = String(comments).trim();
|
|
||||||
if (!value) return null;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(value.startsWith('"') && value.endsWith('"')) ||
|
|
||||||
(value.startsWith("'") && value.endsWith("'"))
|
|
||||||
) {
|
|
||||||
return value.slice(1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to extract S3 key from URL
|
// Function to extract S3 key from URL
|
||||||
function getS3KeyFromUrl(url: string): string {
|
function getS3KeyFromUrl(url: string): string {
|
||||||
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
@@ -156,7 +122,22 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
bb.on("field", (fieldname, val) => {
|
bb.on("field", (fieldname, val) => {
|
||||||
console.log(`FIELD RAW: ${fieldname} =`, val);
|
console.log(`FIELD RAW: ${fieldname} =`, val);
|
||||||
fields[fieldname] = parseMultipartFieldValue(val);
|
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
const cleaned = val.trim();
|
||||||
|
|
||||||
|
// If it starts and ends with quotes, remove them
|
||||||
|
const withoutQuotes =
|
||||||
|
(cleaned.startsWith('"') && cleaned.endsWith('"'))
|
||||||
|
? cleaned.slice(1, -1)
|
||||||
|
: cleaned;
|
||||||
|
|
||||||
|
fields[fieldname] = JSON.parse(withoutQuotes);
|
||||||
|
} catch {
|
||||||
|
fields[fieldname] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
bb.on("close", () => resolve());
|
bb.on("close", () => resolve());
|
||||||
@@ -173,11 +154,11 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
const activityXid = Number(fields.activityXid);
|
const activityXid = Number(fields.activityXid);
|
||||||
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||||
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||||
const comments = normalizeComments(fields.comments);
|
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(
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
|||||||
import { HostService } from '../../../services/host.service';
|
import { HostService } from '../../../services/host.service';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
import { sendWelcomeEmailToHost } from '../../../services/sendOTPEmail.service';
|
|
||||||
|
|
||||||
const hostService = new HostService(prismaClient);
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
@@ -47,8 +46,7 @@ export const handler = safeHandler(async (
|
|||||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await hostService.createPassword(user_xid, password);
|
await hostService.createPassword(user_xid, password);
|
||||||
await sendWelcomeEmailToHost(result.emailAddress);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -4,9 +4,13 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
|||||||
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
||||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
|
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
|
||||||
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
||||||
|
import { HostService } from '../../../services/host.service';
|
||||||
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
||||||
|
|
||||||
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
export async function generateHostRefNumber(tx: any) {
|
export async function generateHostRefNumber(tx: any) {
|
||||||
const lastrecord = await tx.user.findFirst({
|
const lastrecord = await tx.user.findFirst({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
@@ -19,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 (
|
||||||
@@ -41,23 +45,13 @@ export const handler = safeHandler(async (
|
|||||||
throw new ApiError(400, 'Email is required');
|
throw new ApiError(400, 'Email is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailToLowerCase = email.trim().toLowerCase();
|
const emailToLowerCase = email.toLowerCase()
|
||||||
|
|
||||||
if (!emailToLowerCase) {
|
|
||||||
throw new ApiError(400, 'Email is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a single transaction for user creation/lookup and OTP storage
|
// Use a single transaction for user creation/lookup and OTP storage
|
||||||
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
||||||
const user = await tx.user.findUnique({
|
const user = await tx.user.findUnique({
|
||||||
where: { emailAddress: emailToLowerCase },
|
where: { emailAddress: emailToLowerCase },
|
||||||
select: {
|
select: { emailAddress: true, id: true, userPassword: true },
|
||||||
emailAddress: true,
|
|
||||||
id: true,
|
|
||||||
userPassword: true,
|
|
||||||
dataConsentAccepted: true,
|
|
||||||
dataConsentAcceptedOn: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && user.userPassword) {
|
if (user && user.userPassword) {
|
||||||
@@ -99,18 +93,9 @@ export const handler = safeHandler(async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.user.update({
|
const encryptedId = encryptUserId(String(newUserLocal.id));
|
||||||
where: { id: Number(newUserLocal.id) },
|
|
||||||
data: {
|
|
||||||
dataConsentAccepted: true,
|
|
||||||
dataConsentAcceptedOn:
|
|
||||||
user?.dataConsentAccepted && user?.dataConsentAcceptedOn
|
|
||||||
? user.dataConsentAcceptedOn
|
|
||||||
: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { newUser: newUserLocal, otp };
|
return { newUser: newUserLocal, otp, encryptedId };
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!transactionResult || !transactionResult.otp) {
|
if (!transactionResult || !transactionResult.otp) {
|
||||||
|
|||||||
@@ -142,10 +142,6 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
||||||
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
||||||
const deleteCompanyLogo =
|
|
||||||
fields.deleteCompanyLogo === 'true' || fields.deleteCompanyLogo === true;
|
|
||||||
const deleteParentCompanyLogo =
|
|
||||||
fields.deleteParentCompanyLogo === 'true' || fields.deleteParentCompanyLogo === true;
|
|
||||||
|
|
||||||
/** 4) Extract and clean isDraft flag */
|
/** 4) Extract and clean isDraft flag */
|
||||||
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
||||||
@@ -176,46 +172,14 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
if (fields.userProfile) {
|
if (fields.userProfile) {
|
||||||
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
||||||
if (userProfileRaw) {
|
if (userProfileRaw) {
|
||||||
const firstName =
|
const { firstName, lastName, mobileNumber } = userProfileRaw;
|
||||||
typeof userProfileRaw.firstName === 'string'
|
|
||||||
? userProfileRaw.firstName.trim()
|
|
||||||
: undefined;
|
|
||||||
const lastName =
|
|
||||||
typeof userProfileRaw.lastName === 'string'
|
|
||||||
? userProfileRaw.lastName.trim()
|
|
||||||
: undefined;
|
|
||||||
const mobileNumber =
|
|
||||||
typeof userProfileRaw.mobileNumber === 'string'
|
|
||||||
? userProfileRaw.mobileNumber.trim()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (mobileNumber) {
|
|
||||||
const existingUser = await prismaClient.user.findFirst({
|
|
||||||
where: {
|
|
||||||
mobileNumber,
|
|
||||||
id: {
|
|
||||||
not: Number(userInfo.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
throw new ApiError(
|
|
||||||
409,
|
|
||||||
'Mobile number already exists for another user. Please use a different mobile number.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await prismaClient.user.update({
|
await prismaClient.user.update({
|
||||||
where: { id: userInfo.id },
|
where: { id: userInfo.id },
|
||||||
data: {
|
data: {
|
||||||
...(firstName !== undefined && { firstName }),
|
...(firstName && { firstName }),
|
||||||
...(lastName !== undefined && { lastName }),
|
...(lastName && { lastName }),
|
||||||
...(mobileNumber !== undefined && { mobileNumber }),
|
...(mobileNumber && { mobileNumber }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -415,63 +379,6 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DELETE EXISTING LOGO IF REQUESTED */
|
|
||||||
if (deleteCompanyLogo) {
|
|
||||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
|
||||||
where: { userXid: userInfo.id },
|
|
||||||
select: { logoPath: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingHost?.logoPath) {
|
|
||||||
try {
|
|
||||||
const s3Key = getS3KeyFromUrl(existingHost.logoPath);
|
|
||||||
await deleteFromS3(s3Key);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('S3 delete failed for company logo:', existingHost.logoPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedCompany.logoPath = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** DELETE EXISTING PARENT COMPANY LOGO IF REQUESTED */
|
|
||||||
if (deleteParentCompanyLogo && parsedCompany.isSubsidairy) {
|
|
||||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
|
||||||
where: { userXid: userInfo.id },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostParent: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
logoPath: true,
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingParent = Array.isArray(existingHost?.hostParent)
|
|
||||||
? existingHost.hostParent[0]
|
|
||||||
: existingHost?.hostParent;
|
|
||||||
|
|
||||||
if (existingParent?.logoPath) {
|
|
||||||
try {
|
|
||||||
const s3Key = getS3KeyFromUrl(existingParent.logoPath);
|
|
||||||
await deleteFromS3(s3Key);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('S3 delete failed for parent company logo:', existingParent.logoPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedParentCompany) {
|
|
||||||
parsedParentCompany.logoPath = null;
|
|
||||||
} else {
|
|
||||||
parsedParentCompany = {
|
|
||||||
logoPath: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** UPLOAD LOGO (if provided) */
|
/** UPLOAD LOGO (if provided) */
|
||||||
const logoFile = files.find(
|
const logoFile = files.find(
|
||||||
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
||||||
@@ -542,7 +449,6 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
parsedParentCompany,
|
parsedParentCompany,
|
||||||
uploadedParentDocs,
|
uploadedParentDocs,
|
||||||
isDraft,
|
isDraft,
|
||||||
{ deleteCompanyLogo, deleteParentCompanyLogo },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,61 +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 { paginationService } from '../../../../common/utils/pagination/pagination.service';
|
|
||||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
|
||||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
|
||||||
import { HostMemberService } from '../../services/hostMember.service';
|
|
||||||
|
|
||||||
const hostMemberService = new HostMemberService(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 verifyHostToken(token);
|
|
||||||
const search = event.queryStringParameters?.search || '';
|
|
||||||
|
|
||||||
const paginationParams = paginationService.getPaginationFromEvent(event);
|
|
||||||
const paginationOptions =
|
|
||||||
paginationService.parsePaginationParams(paginationParams);
|
|
||||||
|
|
||||||
const { data, totalCount } =
|
|
||||||
await hostMemberService.getAllInvitedCoadminAndOperator({
|
|
||||||
hostUserXid: userInfo.id,
|
|
||||||
search,
|
|
||||||
paginationOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
const paginatedResponse = paginationService.createPaginatedResponse(
|
|
||||||
data,
|
|
||||||
totalCount,
|
|
||||||
paginationOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: 'Invited co-admin and operator members fetched successfully',
|
|
||||||
...paginatedResponse,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,59 +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 { ROLE } from '../../../../common/utils/constants/common.constant';
|
|
||||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
|
||||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
|
||||||
|
|
||||||
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.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await verifyHostToken(token);
|
|
||||||
|
|
||||||
const roles = await prismaClient.roles.findMany({
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
in: [ROLE.CO_ADMIN, ROLE.OPERATOR],
|
|
||||||
},
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
roleName: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
id: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: 'Host member roles fetched successfully',
|
|
||||||
data: {
|
|
||||||
roles,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,60 +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';
|
|
||||||
|
|
||||||
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.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await verifyHostToken(token);
|
|
||||||
|
|
||||||
const permissionMasters = await prismaClient.hostPermissionMasters.findMany({
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
permissionKey: true,
|
|
||||||
permissionGroup: true,
|
|
||||||
permissionSection: true,
|
|
||||||
permissionAction: true,
|
|
||||||
displayLabel: true,
|
|
||||||
displayOrder: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
displayOrder: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: 'Permission masters fetched successfully',
|
|
||||||
data: {
|
|
||||||
permissionMasters,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,111 +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 { HostMemberService } from '../../services/hostMember.service';
|
|
||||||
import { sendHostMemberInvitationEmail } from '../../services/sendHostMemberInvitationEmail.service';
|
|
||||||
|
|
||||||
const hostMemberService = new HostMemberService(prismaClient);
|
|
||||||
|
|
||||||
interface InviteMemberBody {
|
|
||||||
emailAddress: string;
|
|
||||||
roleXid: number;
|
|
||||||
permissionMasterXid: number;
|
|
||||||
activityXids: 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(
|
|
||||||
400,
|
|
||||||
'This is a protected route. Please provide a valid token.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userInfo = await verifyHostToken(token);
|
|
||||||
|
|
||||||
let body: Partial<InviteMemberBody> = {};
|
|
||||||
if (event.body) {
|
|
||||||
try {
|
|
||||||
body = JSON.parse(event.body);
|
|
||||||
} catch {
|
|
||||||
throw new ApiError(400, 'Invalid JSON body');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailAddress =
|
|
||||||
typeof body.emailAddress === 'string' ? body.emailAddress.trim() : '';
|
|
||||||
const roleXid = Number(body.roleXid);
|
|
||||||
const permissionMasterXid = Number(body.permissionMasterXid);
|
|
||||||
const activityXids = Array.isArray(body.activityXids)
|
|
||||||
? body.activityXids
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (!emailAddress) {
|
|
||||||
throw new ApiError(400, 'emailAddress is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isInteger(roleXid) || roleXid <= 0) {
|
|
||||||
throw new ApiError(400, 'roleXid is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isInteger(permissionMasterXid) || permissionMasterXid <= 0) {
|
|
||||||
throw new ApiError(400, 'permissionMasterXid is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activityXids.length) {
|
|
||||||
throw new ApiError(400, 'activityXids is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const inviteResult = await hostMemberService.inviteMember({
|
|
||||||
inviterUserXid: userInfo.id,
|
|
||||||
emailAddress,
|
|
||||||
roleXid,
|
|
||||||
permissionMasterXid,
|
|
||||||
activityXids,
|
|
||||||
});
|
|
||||||
|
|
||||||
await sendHostMemberInvitationEmail(
|
|
||||||
inviteResult.user.emailAddress ?? emailAddress,
|
|
||||||
inviteResult.host.companyName,
|
|
||||||
inviteResult.permissionMaster.role.roleName,
|
|
||||||
inviteResult.permissionDetails.map((permission) => permission.displayLabel),
|
|
||||||
inviteResult.activities.map((activity) => activity.activityTitle ?? `Activity #${activity.id}`),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: 'Host member invited successfully',
|
|
||||||
data: {
|
|
||||||
hostMemberId: inviteResult.hostMember.id,
|
|
||||||
hostXid: inviteResult.hostMember.hostXid,
|
|
||||||
userXid: inviteResult.hostMember.userXid,
|
|
||||||
emailAddress: inviteResult.user.emailAddress,
|
|
||||||
roleXid: inviteResult.hostMember.roleXid,
|
|
||||||
permissionMasterXid: inviteResult.hostMember.hostRolePermissionMasterXid,
|
|
||||||
permissionMasterXids: inviteResult.permissionMaster.permissionMasterXids,
|
|
||||||
permissionLabels: inviteResult.permissionDetails.map((permission) => permission.displayLabel),
|
|
||||||
activityXids: inviteResult.activities.map((activity) => activity.id),
|
|
||||||
activityNames: inviteResult.activities.map((activity) => activity.activityTitle ?? null),
|
|
||||||
memberStatus: inviteResult.hostMember.memberStatus,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,81 +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 { HostRolePermissionService } from '../../services/hostRolePermission.service';
|
|
||||||
|
|
||||||
const hostRolePermissionService = new HostRolePermissionService(prismaClient);
|
|
||||||
|
|
||||||
interface SaveRolePermissionsBody {
|
|
||||||
roleXid: number;
|
|
||||||
permissionMasterXids: 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(
|
|
||||||
400,
|
|
||||||
'This is a protected route. Please provide a valid token.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userInfo = await verifyHostToken(token);
|
|
||||||
|
|
||||||
let body: Partial<SaveRolePermissionsBody> = {};
|
|
||||||
if (event.body) {
|
|
||||||
try {
|
|
||||||
body = JSON.parse(event.body);
|
|
||||||
} catch {
|
|
||||||
throw new ApiError(400, 'Invalid JSON body');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleXid = Number(body.roleXid);
|
|
||||||
const permissionMasterXids = Array.isArray(body.permissionMasterXids)
|
|
||||||
? body.permissionMasterXids
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (!Number.isInteger(roleXid) || roleXid <= 0) {
|
|
||||||
throw new ApiError(400, 'roleXid is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!permissionMasterXids.length) {
|
|
||||||
throw new ApiError(400, 'permissionMasterXids is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await hostRolePermissionService.saveRolePermissions({
|
|
||||||
hostUserXid: userInfo.id,
|
|
||||||
roleXid,
|
|
||||||
permissionMasterXids,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Access-Control-Allow-Origin': '*',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: 'Role permissions saved successfully',
|
|
||||||
data: {
|
|
||||||
permissionMasterXid: result.saved.id,
|
|
||||||
hostXid: result.saved.hostXid,
|
|
||||||
roleXid: result.saved.roleXid,
|
|
||||||
permissionMasterXids: result.saved.permissionMasterXids,
|
|
||||||
selectedPermissions: result.selectedPermissions,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -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 (1–31)
|
|
||||||
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
@@ -1,497 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
import { ROLE, USER_STATUS } from '../../../common/utils/constants/common.constant';
|
|
||||||
import { PaginationOptions } from '../../../common/utils/pagination/pagination.types';
|
|
||||||
import ApiError from '../../../common/utils/helper/ApiError';
|
|
||||||
|
|
||||||
const ALLOWED_MEMBER_ROLES = new Set([ROLE.CO_ADMIN, ROLE.OPERATOR]);
|
|
||||||
|
|
||||||
function normalizeIdArray(values: unknown): number[] {
|
|
||||||
if (!Array.isArray(values)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(
|
|
||||||
new Set(
|
|
||||||
values
|
|
||||||
.map((item) => Number(item))
|
|
||||||
.filter((item) => Number.isInteger(item) && item > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HostMemberService {
|
|
||||||
constructor(private prisma: PrismaClient) {}
|
|
||||||
|
|
||||||
async getAllInvitedCoadminAndOperator(input: {
|
|
||||||
hostUserXid: number;
|
|
||||||
search?: string;
|
|
||||||
paginationOptions?: PaginationOptions;
|
|
||||||
}) {
|
|
||||||
const host = await this.prisma.hostHeader.findFirst({
|
|
||||||
where: {
|
|
||||||
userXid: input.hostUserXid,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
companyName: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!host) {
|
|
||||||
throw new ApiError(404, 'Host company not found for the logged-in user.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const filters: any = {
|
|
||||||
hostXid: host.id,
|
|
||||||
roleXid: {
|
|
||||||
in: [ROLE.CO_ADMIN, ROLE.OPERATOR],
|
|
||||||
},
|
|
||||||
memberStatus: 'invited',
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
user: {
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (input.search?.trim()) {
|
|
||||||
const term = input.search.trim();
|
|
||||||
filters.user = {
|
|
||||||
...filters.user,
|
|
||||||
OR: [
|
|
||||||
{ emailAddress: { contains: term, mode: 'insensitive' as const } },
|
|
||||||
{ firstName: { contains: term, mode: 'insensitive' as const } },
|
|
||||||
{ lastName: { contains: term, mode: 'insensitive' as const } },
|
|
||||||
{ mobileNumber: { contains: term, mode: 'insensitive' as const } },
|
|
||||||
{ userRefNumber: { contains: term, mode: 'insensitive' as const } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalCount = await this.prisma.hostMembers.count({
|
|
||||||
where: filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
const members = await this.prisma.hostMembers.findMany({
|
|
||||||
where: filters,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
userXid: true,
|
|
||||||
roleXid: true,
|
|
||||||
memberStatus: true,
|
|
||||||
invitedOn: true,
|
|
||||||
acceptedOn: true,
|
|
||||||
hostRolePermissionMasterXid: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
emailAddress: true,
|
|
||||||
mobileNumber: true,
|
|
||||||
userRefNumber: true,
|
|
||||||
userStatus: true,
|
|
||||||
role: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
roleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
roleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
invitedBy: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
emailAddress: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hostRolePermissionMaster: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
permissionMasterXids: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
managedActivities: {
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
activityXid: true,
|
|
||||||
activity: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
activityTitle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
activityXid: 'asc',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
invitedOn: 'desc',
|
|
||||||
},
|
|
||||||
skip: input.paginationOptions?.skip ?? 0,
|
|
||||||
take: input.paginationOptions?.limit ?? 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const permissionIds = Array.from(
|
|
||||||
new Set(
|
|
||||||
members.flatMap((member) =>
|
|
||||||
normalizeIdArray(member.hostRolePermissionMaster?.permissionMasterXids),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const permissionMasters = permissionIds.length
|
|
||||||
? await this.prisma.hostPermissionMasters.findMany({
|
|
||||||
where: {
|
|
||||||
id: { in: permissionIds },
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
displayLabel: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const permissionLabelMap = new Map(
|
|
||||||
permissionMasters.map((permission) => [permission.id, permission.displayLabel]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = members.map((member) => {
|
|
||||||
const permissionMasterXids = normalizeIdArray(
|
|
||||||
member.hostRolePermissionMaster?.permissionMasterXids,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
hostMemberId: member.id,
|
|
||||||
hostXid: member.hostXid,
|
|
||||||
hostCompanyName: host.companyName,
|
|
||||||
userXid: member.userXid,
|
|
||||||
roleXid: member.roleXid,
|
|
||||||
roleName: member.role?.roleName ?? member.user.role?.roleName ?? null,
|
|
||||||
permissionMasterXid: member.hostRolePermissionMasterXid,
|
|
||||||
permissionMasterXids,
|
|
||||||
permissionLabels: permissionMasterXids
|
|
||||||
.map((permissionId) => permissionLabelMap.get(permissionId))
|
|
||||||
.filter(Boolean),
|
|
||||||
memberStatus: member.memberStatus,
|
|
||||||
invitedOn: member.invitedOn,
|
|
||||||
acceptedOn: member.acceptedOn,
|
|
||||||
invitedBy: member.invitedBy
|
|
||||||
? {
|
|
||||||
id: member.invitedBy.id,
|
|
||||||
firstName: member.invitedBy.firstName,
|
|
||||||
lastName: member.invitedBy.lastName,
|
|
||||||
emailAddress: member.invitedBy.emailAddress,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
user: {
|
|
||||||
id: member.user.id,
|
|
||||||
firstName: member.user.firstName,
|
|
||||||
lastName: member.user.lastName,
|
|
||||||
emailAddress: member.user.emailAddress,
|
|
||||||
mobileNumber: member.user.mobileNumber,
|
|
||||||
userRefNumber: member.user.userRefNumber,
|
|
||||||
userStatus: member.user.userStatus,
|
|
||||||
},
|
|
||||||
activities: member.managedActivities.map((activityLink) => ({
|
|
||||||
id: activityLink.activity.id,
|
|
||||||
activityXid: activityLink.activityXid,
|
|
||||||
activityTitle: activityLink.activity.activityTitle,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
totalCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async inviteMember(input: {
|
|
||||||
inviterUserXid: number;
|
|
||||||
emailAddress: string;
|
|
||||||
roleXid: number;
|
|
||||||
permissionMasterXid: number;
|
|
||||||
activityXids: unknown;
|
|
||||||
}) {
|
|
||||||
const normalizedEmail = input.emailAddress.trim().toLowerCase();
|
|
||||||
const roleXid = Number(input.roleXid);
|
|
||||||
const permissionMasterXid = Number(input.permissionMasterXid);
|
|
||||||
const activityXids = normalizeIdArray(input.activityXids);
|
|
||||||
|
|
||||||
if (!normalizedEmail) {
|
|
||||||
throw new ApiError(400, 'emailAddress is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isInteger(roleXid) || !ALLOWED_MEMBER_ROLES.has(roleXid)) {
|
|
||||||
throw new ApiError(
|
|
||||||
400,
|
|
||||||
'roleXid must be one of CO_ADMIN or OPERATOR.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isInteger(permissionMasterXid) || permissionMasterXid <= 0) {
|
|
||||||
throw new ApiError(400, 'permissionMasterXid is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activityXids.length) {
|
|
||||||
throw new ApiError(400, 'At least one activity is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.$transaction(async (tx) => {
|
|
||||||
const host = await tx.hostHeader.findFirst({
|
|
||||||
where: {
|
|
||||||
userXid: input.inviterUserXid,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
companyName: true,
|
|
||||||
userXid: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!host) {
|
|
||||||
throw new ApiError(404, 'Host company not found for the logged-in user.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (host.userXid !== input.inviterUserXid) {
|
|
||||||
throw new ApiError(403, 'Only the host owner can invite members.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissionMaster = await tx.hostRolePermissionMasters.findFirst({
|
|
||||||
where: {
|
|
||||||
id: permissionMasterXid,
|
|
||||||
hostXid: host.id,
|
|
||||||
roleXid,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
roleXid: true,
|
|
||||||
permissionMasterXids: true,
|
|
||||||
role: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
roleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!permissionMaster) {
|
|
||||||
throw new ApiError(
|
|
||||||
404,
|
|
||||||
'Permission master not found for the selected host and role.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPermissionMasterXids = normalizeIdArray(
|
|
||||||
permissionMaster.permissionMasterXids,
|
|
||||||
);
|
|
||||||
|
|
||||||
const permissionDetails = await tx.hostPermissionMasters.findMany({
|
|
||||||
where: {
|
|
||||||
id: { in: selectedPermissionMasterXids },
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
permissionKey: true,
|
|
||||||
permissionGroup: true,
|
|
||||||
permissionSection: true,
|
|
||||||
permissionAction: true,
|
|
||||||
displayLabel: true,
|
|
||||||
displayOrder: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
displayOrder: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (permissionDetails.length !== selectedPermissionMasterXids.length) {
|
|
||||||
throw new ApiError(
|
|
||||||
400,
|
|
||||||
'One or more saved permission XIDs no longer exist in the master table.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activities = await tx.activities.findMany({
|
|
||||||
where: {
|
|
||||||
id: { in: activityXids },
|
|
||||||
hostXid: host.id,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
activityTitle: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (activities.length !== activityXids.length) {
|
|
||||||
throw new ApiError(
|
|
||||||
400,
|
|
||||||
'One or more selected activities are invalid for this host.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingUser = await tx.user.findFirst({
|
|
||||||
where: {
|
|
||||||
emailAddress: normalizedEmail,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
emailAddress: true,
|
|
||||||
roleXid: true,
|
|
||||||
userStatus: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let user =
|
|
||||||
existingUser ??
|
|
||||||
(await tx.user.create({
|
|
||||||
data: {
|
|
||||||
emailAddress: normalizedEmail,
|
|
||||||
roleXid: ROLE.HOST,
|
|
||||||
userStatus: USER_STATUS.INVITED,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
emailAddress: true,
|
|
||||||
roleXid: true,
|
|
||||||
userStatus: true,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (existingUser && existingUser.roleXid !== ROLE.HOST) {
|
|
||||||
user = await tx.user.update({
|
|
||||||
where: { id: existingUser.id },
|
|
||||||
data: {
|
|
||||||
roleXid: ROLE.HOST,
|
|
||||||
userStatus: USER_STATUS.INVITED,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
emailAddress: true,
|
|
||||||
roleXid: true,
|
|
||||||
userStatus: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingMembership = await tx.hostMembers.findFirst({
|
|
||||||
where: {
|
|
||||||
userXid: user.id,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingMembership && existingMembership.hostXid !== host.id) {
|
|
||||||
throw new ApiError(
|
|
||||||
409,
|
|
||||||
'This person is already invited or assigned to another host company.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const membershipData = {
|
|
||||||
hostXid: host.id,
|
|
||||||
userXid: user.id,
|
|
||||||
roleXid,
|
|
||||||
hostRolePermissionMasterXid: permissionMaster.id,
|
|
||||||
memberStatus: 'invited',
|
|
||||||
invitedByXid: input.inviterUserXid,
|
|
||||||
invitedOn: new Date(),
|
|
||||||
acceptedOn: null,
|
|
||||||
isActive: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const hostMember = existingMembership
|
|
||||||
? await tx.hostMembers.update({
|
|
||||||
where: { id: existingMembership.id },
|
|
||||||
data: membershipData,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
userXid: true,
|
|
||||||
roleXid: true,
|
|
||||||
hostRolePermissionMasterXid: true,
|
|
||||||
memberStatus: true,
|
|
||||||
invitedByXid: true,
|
|
||||||
invitedOn: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: await tx.hostMembers.create({
|
|
||||||
data: membershipData,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
userXid: true,
|
|
||||||
roleXid: true,
|
|
||||||
hostRolePermissionMasterXid: true,
|
|
||||||
memberStatus: true,
|
|
||||||
invitedByXid: true,
|
|
||||||
invitedOn: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.hostMemberActivities.deleteMany({
|
|
||||||
where: {
|
|
||||||
hostMemberXid: hostMember.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await tx.hostMemberActivities.createMany({
|
|
||||||
data: activities.map((activity) => ({
|
|
||||||
hostMemberXid: hostMember.id,
|
|
||||||
activityXid: activity.id,
|
|
||||||
isActive: true,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
host,
|
|
||||||
user,
|
|
||||||
hostMember,
|
|
||||||
permissionMaster,
|
|
||||||
permissionDetails,
|
|
||||||
activities,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
import ApiError from '../../../common/utils/helper/ApiError';
|
|
||||||
|
|
||||||
function normalizeIdArray(values: unknown): number[] {
|
|
||||||
if (!Array.isArray(values)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(
|
|
||||||
new Set(
|
|
||||||
values
|
|
||||||
.map((item) => Number(item))
|
|
||||||
.filter((item) => Number.isInteger(item) && item > 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HostRolePermissionService {
|
|
||||||
constructor(private prisma: PrismaClient) {}
|
|
||||||
|
|
||||||
async saveRolePermissions(input: {
|
|
||||||
hostUserXid: number;
|
|
||||||
roleXid: number;
|
|
||||||
permissionMasterXids: unknown;
|
|
||||||
}) {
|
|
||||||
const permissionMasterXids = normalizeIdArray(input.permissionMasterXids);
|
|
||||||
|
|
||||||
if (!permissionMasterXids.length) {
|
|
||||||
throw new ApiError(400, 'permissionMasterXids is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.$transaction(async (tx) => {
|
|
||||||
const host = await tx.hostHeader.findFirst({
|
|
||||||
where: {
|
|
||||||
userXid: input.hostUserXid,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
companyName: true,
|
|
||||||
userXid: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!host) {
|
|
||||||
throw new ApiError(404, 'Host company not found for the logged-in user.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = await tx.roles.findFirst({
|
|
||||||
where: {
|
|
||||||
id: input.roleXid,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
roleName: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
throw new ApiError(400, 'Invalid roleXid.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedPermissions = await tx.hostPermissionMasters.findMany({
|
|
||||||
where: {
|
|
||||||
id: { in: permissionMasterXids },
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
permissionKey: true,
|
|
||||||
permissionGroup: true,
|
|
||||||
permissionSection: true,
|
|
||||||
permissionAction: true,
|
|
||||||
displayLabel: true,
|
|
||||||
displayOrder: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
displayOrder: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedPermissions.length !== permissionMasterXids.length) {
|
|
||||||
throw new ApiError(400, 'One or more permissionMasterXids are invalid.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const saved = await tx.hostRolePermissionMasters.upsert({
|
|
||||||
where: {
|
|
||||||
hostXid_roleXid: {
|
|
||||||
hostXid: host.id,
|
|
||||||
roleXid: role.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
hostXid: host.id,
|
|
||||||
roleXid: role.id,
|
|
||||||
permissionMasterXids,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
permissionMasterXids,
|
|
||||||
isActive: true,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hostXid: true,
|
|
||||||
roleXid: true,
|
|
||||||
permissionMasterXids: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
host,
|
|
||||||
role,
|
|
||||||
saved,
|
|
||||||
selectedPermissions,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,16 +10,13 @@ export async function resendOtpEmail(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Your Minglar Verification Code";
|
const subject = "New OTP from Minglar Team";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${role},</p>
|
<p>Dear ${role},</p>
|
||||||
<p>Here's your verification code to get started:</p>
|
<p>Your new OTP is: <strong>${otp}</strong></p>
|
||||||
<p><strong>${otp}</strong></p>
|
<p>This code will be valid for the next 5 minutes.</p>
|
||||||
<p>This code is valid for the next 5 minutes.</p>
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
<p>Once verified, you can continue setting up your Minglar account. If you didn't request this, you can safely ignore this email.</p>
|
|
||||||
<p>Need help? Reach out to us at info@minglargroup.com.</p>
|
|
||||||
<p>Warm regards,<br/>Team Minglar</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -40,5 +37,3 @@ export async function resendOtpEmail(
|
|||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { brevoService } from '../../../common/email/brevoApi';
|
|
||||||
import ApiError from '../../../common/utils/helper/ApiError';
|
|
||||||
import config from '../../../config/config';
|
|
||||||
|
|
||||||
export async function sendHostMemberInvitationEmail(
|
|
||||||
emailAddress: string,
|
|
||||||
hostName: string,
|
|
||||||
memberRole: string,
|
|
||||||
permissionLabels: string[],
|
|
||||||
activityNames: string[],
|
|
||||||
): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
}> {
|
|
||||||
const subject = `Invitation to join ${hostName} on Minglar Host`;
|
|
||||||
|
|
||||||
const permissionsHtml = permissionLabels.length
|
|
||||||
? `<ul>${permissionLabels.map((permission) => `<li>${permission}</li>`).join('')}</ul>`
|
|
||||||
: '<p>No permissions were assigned.</p>';
|
|
||||||
|
|
||||||
const activitiesHtml = activityNames.length
|
|
||||||
? `<ul>${activityNames.map((activity) => `<li>${activity}</li>`).join('')}</ul>`
|
|
||||||
: '<p>No activities were assigned.</p>';
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi there,</p>
|
|
||||||
<p>You have been invited by <strong>${hostName}</strong> to join the Minglar Host portal as <strong>${memberRole}</strong>.</p>
|
|
||||||
<p>The following permissions have been assigned to your account:</p>
|
|
||||||
${permissionsHtml}
|
|
||||||
<p>The following activities have been assigned to you:</p>
|
|
||||||
${activitiesHtml}
|
|
||||||
<p>You can access the host portal using the link below:</p>
|
|
||||||
<p><a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a></p>
|
|
||||||
<p>If you were not expecting this invitation, you can ignore this email.</p>
|
|
||||||
<p>Warm regards,<br/>Team Minglar</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send host member invitation email.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,24 @@
|
|||||||
import { brevoService } from "@/common/email/brevoApi";
|
import { brevoService } from "@/common/email/brevoApi";
|
||||||
import ApiError from "@/common/utils/helper/ApiError";
|
import ApiError from "@/common/utils/helper/ApiError";
|
||||||
import config from '../../../config/config';
|
|
||||||
|
|
||||||
export async function sendEmailToAM(
|
export async function sendEmailToAM(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
amName: string,
|
amName: string,
|
||||||
hostCompanyName: string,
|
hostCompanyName: string,
|
||||||
activityName: string
|
hostRefNumber: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = `${hostCompanyName} Has Resubmitted Their Application`;
|
const subject = `Host Application Re-Submited : ${hostCompanyName}`;
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hello ${amName},</p>
|
<p>Dear ${amName},</p>
|
||||||
|
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has re-submited the application with implimented suggestions.</p>
|
||||||
<p>
|
<p>Please review their appliaction and take the necessary action.</p>
|
||||||
${hostCompanyName} has updated and re-submitted their pre-qualification details of ${activityName} for your review.
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
</p>
|
`;
|
||||||
|
|
||||||
<p>
|
|
||||||
Please click on the link below to log in to your dashboard to review the revised submission and proceed with the necessary action.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>Review Application</strong><br/>
|
|
||||||
<a href="${config.AM_INTERFACE_LINK}" target="_blank">${config.AM_INTERFACE_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Thank you,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
@@ -65,14 +49,13 @@ export async function sendEmailToMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "New Host Application Submitted for Review";
|
const subject = `New Host Application Recieved : ${hostCompanyName}`;
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${minglarAdminName},</p>
|
<p>Dear ${minglarAdminName},</p>
|
||||||
<p>${hostCompanyName} has submitted their application and is awaiting your review.</p>
|
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has submited their application.</p>
|
||||||
<p>Reference number: <strong>${hostRefNumber}</strong></p>
|
<p>Please review their appliaction and take the necessary action.</p>
|
||||||
<p>Please log in to your dashboard to review the submission and take the necessary action.</p>
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
<p>Thank you,<br/>Minglar Team</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -93,57 +76,3 @@ export async function sendEmailToMinglarAdmin(
|
|||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendPQPEmailToAM(
|
|
||||||
emailAddress: string,
|
|
||||||
minglarAdminName: string,
|
|
||||||
hostCompanyName: string,
|
|
||||||
activityName: string
|
|
||||||
): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
// messageId: string
|
|
||||||
}> {
|
|
||||||
|
|
||||||
const subject = "New Host Application Submitted for Review";
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${minglarAdminName},</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
${hostCompanyName} has submitted the pre-qualification details for ${activityName} and is awaiting your review.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Please click the link below to log in to your dashboard, review the submission, and take the necessary action:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>Review Application</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Thank you,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log("📧 Email sent successfully:", result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
// messageId: result.messageId
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Brevo email send failed:", err);
|
|
||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { brevoService } from '../../../common/email/brevoApi';
|
import { brevoService } from "@/common/email/brevoApi";
|
||||||
import ApiError from '../../../common/utils/helper/ApiError';
|
import ApiError from "@/common/utils/helper/ApiError";
|
||||||
import config from '../../../config/config';
|
|
||||||
|
|
||||||
export async function sendOtpEmailForHost(
|
export async function sendOtpEmailForHost(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
otp: string | number,
|
otp: string | number
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = 'Your Minglar Verification Code';
|
|
||||||
|
const subject = "OTP for Host Registration";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi there 👋</p>
|
<p>Dear Host,</p>
|
||||||
<p>Here’s your verification code to get started:</p>
|
<p>You’re almost all set! 🎉</p>
|
||||||
<p><strong>${otp}</strong></p>
|
<p>Enter <strong>${otp}</strong> to wrap your registration.</p>
|
||||||
<p>This code is valid for the next 5 minutes.</p>
|
<p>This code will be valid for the next 5 minutes.</p>
|
||||||
<p>Once verified, you can continue setting up your Minglar account. If you didn’t request this, you can safely ignore this email.</p>
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
<p>Need help? Reach out to us at info@minglargroup.com.</p>
|
|
||||||
<p>Warm regards,<br />Team Minglar</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -35,67 +33,7 @@ export async function sendOtpEmailForHost(
|
|||||||
// messageId: result.messageId
|
// messageId: result.messageId
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Brevo email send failed:', err);
|
console.error("Brevo email send failed:", err);
|
||||||
throw new ApiError(500, 'Failed to send OTP to host via email.');
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendWelcomeEmailToHost(emailAddress: string): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
// messageId: string
|
|
||||||
}> {
|
|
||||||
const subject = 'Get Started as a Minglar Host';
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${emailAddress},</p>
|
|
||||||
|
|
||||||
<p>We’re excited to have you join Minglar as a host. Welcome aboard! 🌟</p>
|
|
||||||
|
|
||||||
<p>To get started and bring your activities live, here’s what comes next:</p>
|
|
||||||
|
|
||||||
<p><strong>Your next steps:</strong></p>
|
|
||||||
|
|
||||||
<p>1. Complete your host profile</p>
|
|
||||||
<p>2. Complete the pre-qualification process for all your activities</p>
|
|
||||||
<p>3. Submit your activity details for review</p>
|
|
||||||
<p>4. Go live and start receiving bookings</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
👉 <strong>Access your Host Portal:</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If you need any support along the way, our team is always here to help.
|
|
||||||
You can reach us anytime at
|
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We’re looking forward to seeing your experiences come to life on Minglar.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Team Minglar
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log("📧 Email sent successfully:", result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
// messageId: result.messageId
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send OTP to host via email.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,55 +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';
|
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const handler = safeHandler(async (
|
|||||||
if (!hostDetails?.emailAddress) {
|
if (!hostDetails?.emailAddress) {
|
||||||
throw new ApiError(404, 'Host details or email address not found');
|
throw new ApiError(404, 'Host details or email address not found');
|
||||||
}
|
}
|
||||||
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress, hostDetails.firstName || 'Host');
|
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -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.');
|
||||||
|
|||||||
@@ -2,30 +2,19 @@
|
|||||||
import { brevoService } from '@/common/email/brevoApi';
|
import { brevoService } from '@/common/email/brevoApi';
|
||||||
import ApiError from '@/common/utils/helper/ApiError';
|
import ApiError from '@/common/utils/helper/ApiError';
|
||||||
|
|
||||||
export async function sendAMEmailForHostAssign(emailAddress: string, accountManagerName?: string): Promise<{
|
export async function sendAMEmailForHostAssign(emailAddress: string): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = "You've Been Assigned a New Host";
|
const subject = 'Minglar Admin: Host Assignment Notification';
|
||||||
|
|
||||||
const displayName = accountManagerName?.trim() || "there";
|
const htmlContent = `
|
||||||
|
<p>Hi,</p>
|
||||||
|
|
||||||
const htmlContent = `
|
<p>You’ve been assigned the <strong>Host</strong> role by Minglar Admin.</p>
|
||||||
<p>Hi ${displayName},</p>
|
|
||||||
|
|
||||||
<p>
|
<p>Best regards,<br/>Minglar Admin Team</p>
|
||||||
A new host has been assigned to you by the Minglar team.
|
`;
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You can now manage and support this host through your admin dashboard. Log in to review the host’s details, connect with them, and take the next steps as needed.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
@@ -46,5 +35,3 @@ export async function sendAMEmailForHostAssign(emailAddress: string, accountMana
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ...existing code...
|
// ...existing code...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ export class AMNotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const amName = [amUser.firstName, amUser.lastName].filter(Boolean).join(' ').trim();
|
await sendAMEmailForHostAssign(amUser.emailAddress);
|
||||||
await sendAMEmailForHostAssign(amUser.emailAddress, amName);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending AM assignment email', err);
|
console.error('Error sending AM assignment email', err);
|
||||||
|
|||||||
@@ -1,277 +1,114 @@
|
|||||||
import { brevoService } from '../../../common/email/brevoApi';
|
import { brevoService } from "../../../common/email/brevoApi";
|
||||||
import ApiError from '../../../common/utils/helper/ApiError';
|
import ApiError from "../../../common/utils/helper/ApiError";
|
||||||
import config from '../../../config/config';
|
import config from "../../../config/config";
|
||||||
|
|
||||||
export async function sendEmailToHostForApprovedApplication(
|
export async function sendEmailToHostForApprovedApplication(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
name: string,
|
name: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = 'Host Onboarding Application Approved';
|
|
||||||
|
|
||||||
const htmlContent = `
|
const subject = "Approval for your application";
|
||||||
<p>Hi ${emailAddress},</p>
|
|
||||||
|
|
||||||
<p>
|
const htmlContent = `
|
||||||
We’re pleased to inform you that your host onboarding application has been approved by our team.
|
<p>Dear ${name},</p>
|
||||||
</p>
|
<p>Congratulations, Your application to minglar admin has been approved.</p>
|
||||||
|
<p>You can start onboarding your activities through the host panel.</p>
|
||||||
|
<p> You can login to your account using the link below:<br/>
|
||||||
|
<strong>Link:</strong> ${config.HOST_LINK} </p>
|
||||||
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
<p>
|
try {
|
||||||
You can now proceed with completing your activity pre-qualification process.
|
const result = await brevoService.sendEmail({
|
||||||
</p>
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
<p>
|
console.log("📧 Email sent successfully:", result);
|
||||||
Please click the link below to log in to your account and continue:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
return {
|
||||||
<strong>Host Portal Login</strong><br/>
|
sent: true,
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
// messageId: result.messageId
|
||||||
</p>
|
};
|
||||||
|
} catch (err) {
|
||||||
<p>
|
console.error("Brevo email send failed:", err);
|
||||||
If you have any questions or need assistance, feel free to reach out to us at
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>.
|
}
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📧 Email sent successfully:', result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
// messageId: result.messageId
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send OTP to minglar admin via email.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendEmailToHostForMinglarApproval(
|
export async function sendEmailToHostForMinglarApproval(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = 'Host Onboarding Application Approved';
|
|
||||||
|
|
||||||
const htmlContent = `
|
const subject = "Approval for your application";
|
||||||
<p>Hi there,</p>
|
|
||||||
<p>We're pleased to inform you that your host onboarding application has been approved by our team.</p>
|
const htmlContent = `
|
||||||
<p>You can now proceed with completing your activity pre-qualification process.</p>
|
<p>Dear Host,</p>
|
||||||
<p>Please click the link below to log in to your account and continue:</p>
|
<p>Congratulations, Your application to minglar admin has been approved by minglar admin.</p>
|
||||||
<p><a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a></p>
|
<p>Minglar admin will assign account manager to your application.</p>
|
||||||
<p>If you have any questions or need assistance, feel free to reach out to us at info@minglargroup.com.</p>
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
<p>Warm regards,<br/>Minglar Team</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
recipients: [{ email: emailAddress }],
|
recipients: [{ email: emailAddress }],
|
||||||
subject,
|
subject,
|
||||||
htmlContent,
|
htmlContent,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('📧 Email sent successfully:', result);
|
console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sent: true,
|
sent: true,
|
||||||
// messageId: result.messageId
|
// messageId: result.messageId
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Brevo email send failed:', err);
|
console.error("Brevo email send failed:", err);
|
||||||
throw new ApiError(500, 'Failed to send OTP to minglar admin via email.');
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendAMPQQAcceptanceMailtoHost(
|
export async function sendAMPQQAcceptanceMailtoHost(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
name: string,
|
name: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = 'Your Activity Has Been Qualified for Onboarding';
|
|
||||||
|
|
||||||
const htmlContent = `
|
const subject = "Approval for your activity onboarding application";
|
||||||
<p>Hi ${name},</p>
|
|
||||||
|
|
||||||
<p>
|
const htmlContent = `
|
||||||
We’re pleased to inform you that your activity has been qualified on the Minglar platform.
|
<p>Dear ${name},</p>
|
||||||
</p>
|
<p>Congratulations, Your activity onboarding application to minglar admin has been approved.</p>
|
||||||
|
<p>You can start adding other details of your activity through the host panel.</p>
|
||||||
|
<p> You can login to your account using the link below:<br/>
|
||||||
|
<strong>Link:</strong> ${config.HOST_LINK} </p>
|
||||||
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
<p>
|
try {
|
||||||
You can now proceed to complete the details of your activity through the Host portal.
|
const result = await brevoService.sendEmail({
|
||||||
</p>
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
<p>
|
console.log("📧 Email sent successfully:", result);
|
||||||
Please click the link below to log in to your account and continue:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
return {
|
||||||
<strong>Host Portal Login</strong><br/>
|
sent: true,
|
||||||
<a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a>
|
// messageId: result.messageId
|
||||||
</p>
|
};
|
||||||
|
} catch (err) {
|
||||||
<p>
|
console.error("Brevo email send failed:", err);
|
||||||
If you have any questions or need assistance, feel free to reach out at
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>.
|
}
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📧 Email sent successfully:', result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
// messageId: result.messageId
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send OTP to minglar admin via email.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendActivityAcceptanceMailtoHost(
|
|
||||||
emailAddress: string,
|
|
||||||
name: string,
|
|
||||||
): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
// messageId: string
|
|
||||||
}> {
|
|
||||||
const subject =
|
|
||||||
'Onboarding Completed | You Can Now Set Up Your Activity Schedule and Listing';
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${name},</p>
|
|
||||||
|
|
||||||
<p>Great news! 🎉</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You have successfully completed the onboarding process for your activity on Minglar.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You can now move on to the next step by setting up your activity’s schedule. Once this is done, your activity will be ready to be listed on the Minglar app.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
👉 <strong>Access your Host Portal:</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If you have any questions or need assistance while setting things up, our team is here to help at
|
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We’re excited to see your activity take shape and look forward to having it live on Minglar soon.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Team Minglar
|
|
||||||
</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 sendActivityScheduleApprovedMailtoHost(
|
|
||||||
emailAddress: string,
|
|
||||||
name: string,
|
|
||||||
): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
}> {
|
|
||||||
const subject = 'Activity Schedule Approved | Let’s Go Live!!';
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${name},</p>
|
|
||||||
|
|
||||||
<p>Your activity schedule has been officially approved.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Everything is now in place. Your experience is fully configured and queued for launch.
|
|
||||||
Our team is completing the final activation before it goes live on Minglar.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You can continue managing your availability or adding new time slots anytime through your Host Portal:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
👉 <strong>Host Portal</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is a big step. We’re excited to bring your experience to life on Minglar.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
The Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📧 Email sent successfully:', result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send schedule approval email to host.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,36 +9,23 @@ export async function sendInvitationEmailForMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Team Invitation: Account Manager at Minglar";
|
const subject = "Minglar Admin: Your Team Invitation";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${emailAddress},</p>
|
<p>Hi there,</p>
|
||||||
|
<p>We're excited to invite you to join the <strong>Minglar Admin Team</strong>!<br/>
|
||||||
|
Please use the link below to set up your account and get started.</p>
|
||||||
|
|
||||||
<p>
|
<p><strong>Access Your Invitation:</strong><br/>
|
||||||
We’re happy to invite you to join the Minglar team as an Account Manager.
|
<a href="${link}">${link}</a></p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>If you have any questions or need assistance, feel free to reach out — we’re here to help.<br/>
|
||||||
To get started, please set up your account using the link below:
|
We look forward to having you on board!</p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>Welcome aboard!<br/>
|
||||||
<a href="${link}" target="_blank">${link}</a>
|
<strong>The Minglar Admin Team</strong></p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
`;
|
||||||
If you have any questions or need help during the setup process, feel free to reach out.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We look forward to working with you.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
@@ -58,4 +45,3 @@ export async function sendInvitationEmailForMinglarAdmin(
|
|||||||
throw new ApiError(500, "Failed to send invitation via email.");
|
throw new ApiError(500, "Failed to send invitation via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -137,26 +138,24 @@ 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, isActive: true },
|
where: { id: id }
|
||||||
});
|
})
|
||||||
const userDetails = await this.prisma.user.findUnique({
|
const userDetails = await this.prisma.user.findUnique({
|
||||||
where: { id: hostDetail.userXid, isActive: true },
|
where: { id: hostDetail.userXid },
|
||||||
});
|
});
|
||||||
return userDetails;
|
return userDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,12 +309,10 @@ export class MinglarService {
|
|||||||
companyName: true,
|
companyName: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
firstName: true,
|
userRefNumber: true
|
||||||
lastName: true,
|
}
|
||||||
userRefNumber: true,
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ActivityAmDetails: {
|
ActivityAmDetails: {
|
||||||
select: {
|
select: {
|
||||||
@@ -342,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,
|
||||||
@@ -361,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);
|
||||||
@@ -374,57 +367,15 @@ export class MinglarService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { paginationService } = require('@/common/utils/pagination/pagination.service');
|
||||||
paginationService,
|
return paginationService.createPaginatedResponse(
|
||||||
} = require('@/common/utils/pagination/pagination.service');
|
|
||||||
|
|
||||||
let hostDetails = null;
|
|
||||||
|
|
||||||
if (hostXid) {
|
|
||||||
hostDetails = await this.prisma.hostHeader.findUnique({
|
|
||||||
where: { id: hostXid },
|
|
||||||
select: {
|
|
||||||
companyName: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
userRefNumber: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const paginatedResponse = paginationService.createPaginatedResponse(
|
|
||||||
hostActivities,
|
hostActivities,
|
||||||
totalCount,
|
totalCount,
|
||||||
paginationOptions || { page: 1, limit: 10, skip: 0 },
|
paginationOptions || { page: 1, limit: 10, skip: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 👇 ADD THIS BLOCK
|
|
||||||
if (hostActivities.length === 0 && hostDetails) {
|
|
||||||
paginatedResponse.data = [
|
|
||||||
{
|
|
||||||
id: null,
|
|
||||||
activityRefNumber: null,
|
|
||||||
activityTitle: null,
|
|
||||||
totalScore: null,
|
|
||||||
activityInternalStatus: null,
|
|
||||||
activityDisplayStatus: null,
|
|
||||||
amInternalStatus: null,
|
|
||||||
amDisplayStatus: null,
|
|
||||||
createdAt: null,
|
|
||||||
host: hostDetails,
|
|
||||||
ActivityAmDetails: [],
|
|
||||||
activityType: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return paginatedResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async createUserRevenue(
|
async createUserRevenue(
|
||||||
userXid: number,
|
userXid: number,
|
||||||
isFixedSalary: boolean,
|
isFixedSalary: boolean,
|
||||||
@@ -488,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
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -537,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) => {
|
||||||
@@ -789,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,
|
||||||
@@ -860,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;
|
||||||
}
|
}
|
||||||
@@ -875,20 +821,17 @@ export class MinglarService {
|
|||||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||||
display: MINGLAR_STATUS_DISPLAY.NEW,
|
display: MINGLAR_STATUS_DISPLAY.NEW,
|
||||||
},
|
},
|
||||||
Re_Submitted: {
|
To_Review: {
|
||||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||||
display: MINGLAR_STATUS_DISPLAY.RE_SUBMITTED,
|
display: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
|
||||||
},
|
},
|
||||||
Enhancing: {
|
Enhancing: {
|
||||||
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];
|
||||||
@@ -899,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;
|
||||||
@@ -930,7 +874,7 @@ export class MinglarService {
|
|||||||
emailAddress: true,
|
emailAddress: true,
|
||||||
mobileNumber: true,
|
mobileNumber: true,
|
||||||
userRefNumber: true,
|
userRefNumber: true,
|
||||||
profileImage: true,
|
profileImage: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
accountManager: {
|
accountManager: {
|
||||||
@@ -941,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,
|
||||||
});
|
});
|
||||||
@@ -954,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);
|
||||||
@@ -981,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,
|
||||||
@@ -988,7 +933,6 @@ export class MinglarService {
|
|||||||
const where: any = {
|
const where: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
hostStatusInternal: { notIn: [HOST_STATUS_INTERNAL.DRAFT] },
|
hostStatusInternal: { notIn: [HOST_STATUS_INTERNAL.DRAFT] },
|
||||||
adminStatusInternal: { notIn: [MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW] },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (search?.trim()) {
|
if (search?.trim()) {
|
||||||
@@ -1075,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
|
||||||
* --------------------------------- */
|
* --------------------------------- */
|
||||||
@@ -1082,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);
|
||||||
@@ -1206,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
|
||||||
* --------------------------------- */
|
* --------------------------------- */
|
||||||
@@ -1235,9 +1181,7 @@ export class MinglarService {
|
|||||||
{ 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 },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
@@ -1411,7 +1355,7 @@ export class MinglarService {
|
|||||||
|
|
||||||
const amUser = await this.prisma.user.findUnique({
|
const amUser = await this.prisma.user.findUnique({
|
||||||
where: { id: accountManagerXid, isActive: true },
|
where: { id: accountManagerXid, isActive: true },
|
||||||
select: { emailAddress: true, firstName: true, lastName: true },
|
select: { emailAddress: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!amUser || !amUser.emailAddress) {
|
if (!amUser || !amUser.emailAddress) {
|
||||||
@@ -1422,8 +1366,7 @@ export class MinglarService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const amName = [amUser.firstName, amUser.lastName].filter(Boolean).join(' ').trim();
|
await sendAMEmailForHostAssign(amUser.emailAddress);
|
||||||
await sendAMEmailForHostAssign(amUser.emailAddress, amName);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending AM assignment email', err);
|
console.error('Error sending AM assignment email', err);
|
||||||
@@ -1496,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 },
|
||||||
@@ -1550,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 },
|
||||||
@@ -1625,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: {
|
||||||
@@ -1677,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,
|
||||||
@@ -1694,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,
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1756,7 +1648,6 @@ export class MinglarService {
|
|||||||
isEmailVerfied: true,
|
isEmailVerfied: true,
|
||||||
isMobileVerfied: true,
|
isMobileVerfied: true,
|
||||||
isBiometric: true,
|
isBiometric: true,
|
||||||
createdAt: true,
|
|
||||||
userAddressDetails: {
|
userAddressDetails: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -1771,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);
|
||||||
@@ -1830,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,
|
||||||
@@ -1841,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) {
|
||||||
@@ -1850,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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1892,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,
|
||||||
@@ -1907,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,
|
||||||
@@ -1977,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: {
|
||||||
@@ -2004,7 +1839,7 @@ export class MinglarService {
|
|||||||
companyTypeName: true,
|
companyTypeName: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
HostBankDetails: true,
|
HostBankDetails: true,
|
||||||
HostDocuments: {
|
HostDocuments: {
|
||||||
@@ -2022,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,
|
||||||
@@ -2033,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;
|
||||||
@@ -2057,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);
|
||||||
@@ -2069,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);
|
||||||
@@ -2080,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);
|
||||||
@@ -2103,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,
|
||||||
@@ -2147,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
|
||||||
@@ -2160,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 ----------
|
||||||
@@ -2201,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
|
||||||
@@ -2226,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);
|
||||||
}
|
}
|
||||||
@@ -2234,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,
|
||||||
@@ -2242,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);
|
||||||
@@ -2269,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);
|
||||||
@@ -2283,5 +2078,6 @@ export class MinglarService {
|
|||||||
|
|
||||||
// ---------- RETURN GROUPED STRUCTURE ----------
|
// ---------- RETURN GROUPED STRUCTURE ----------
|
||||||
return sortedCategories;
|
return sortedCategories;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,35 +4,19 @@ import config from "../../../config/config";
|
|||||||
|
|
||||||
export async function sendEmailToHostForRejectedApplication(
|
export async function sendEmailToHostForRejectedApplication(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
firstName: string
|
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Action Needed: Host Onboarding Application";
|
const subject = "Rejection for your application";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${firstName},</p>
|
<p>Dear Host,</p>
|
||||||
|
<p>Sorry to say that, But your application to minglar admin has been rejected.</p>
|
||||||
<p>
|
<p>If you have any questions please contact to minglar admin.</p>
|
||||||
After reviewing your submission, we’re unable to proceed at this stage, as some details require further updates. We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
</p>
|
`;
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>Host Portal Login</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We appreciate your interest in Minglar and look forward to reviewing your updated application.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
@@ -55,21 +39,27 @@ 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
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Action Needed: Host Onboarding Application";
|
const subject = "Improvement of your application";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${name},</p>
|
<p>Dear ${name},</p>
|
||||||
<p>After reviewing your submission, we're unable to proceed at this stage, as some details require further updates. We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.</p>
|
<p> Your account manager has reviewed your application and provided some suggestions. <br/>
|
||||||
<p><a href="${link}" target="_blank">${link}</a></p>
|
Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p>
|
||||||
<p>We appreciate your interest in Minglar and look forward to reviewing your updated application.</p>
|
<p> You may access your application using the link below:<br/>
|
||||||
<p>Warm regards,<br/>Team Minglar</p>
|
<strong>Link:</strong>
|
||||||
|
<a href="${config.HOST_LINK}" target="_blank">
|
||||||
|
${config.HOST_LINK}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p> If you have any questions, please feel free to contact the Minglar Support Team. </p>
|
||||||
|
<p> Best regards,<br/>
|
||||||
|
<strong>Minglar Team</strong> </p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -100,42 +90,23 @@ export async function sendAMPQQRejectionMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Action Needed: Activity Pre-qualification";
|
const subject = "Improvement of your activity onboarding application";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi ${name},</p>
|
<p>Dear ${name},</p>
|
||||||
|
|
||||||
<p>
|
<p>Your account manager has reviewed your activity application and provided some suggestions.<br/>
|
||||||
Thank you for taking the time to submit your activity pre-qualification details on the Minglar platform.
|
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>
|
|
||||||
|
|
||||||
<p>
|
<p>You may access your activity onboarding application using the link below:<br/>
|
||||||
After reviewing your submission, we’re unable to approve the application at this stage. However, we encourage you to make the suggested updates and refinements, as many applications are successfully approved after revision.
|
<strong>Link:</strong> ${config.HOST_LINK}</p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>If you have any questions, please feel free to contact the Minglar Support Team.</p>
|
||||||
You can log in to the Host portal to review the feedback and continue updating your application:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>Best regards,<br/>
|
||||||
<strong>Host Portal Login</strong><br/>
|
<strong>Minglar Team</strong></p>
|
||||||
<a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
`;
|
||||||
If you need any guidance, feel free to reach out to us at
|
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We appreciate your interest in partnering with Minglar and look forward to reviewing your updated submission.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Thank you,<br/>
|
|
||||||
Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await brevoService.sendEmail({
|
const result = await brevoService.sendEmail({
|
||||||
@@ -155,123 +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 = "Action Needed: Activity Onboarding";
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${name},</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Thank you for submitting your activity for review.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
After evaluating the details provided, we’re unable to approve the listing at this stage. A few updates are required before we can proceed.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Please log in to your Host Portal to review the feedback and make the necessary revisions.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
👉 <strong>Access your Host Portal:</strong><br/>
|
|
||||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Once the updates have been submitted, our team will re-evaluate your activity promptly.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If you have any questions or need clarification on the feedback, feel free to reach out to us at
|
|
||||||
<a href="mailto:info@minglargroup.com">info@minglargroup.com</a>. We’re happy to assist.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Warm regards,<br/>
|
|
||||||
The 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 sendActivityScheduleRejectedMailtoHost(
|
|
||||||
emailAddress: string,
|
|
||||||
name: string,
|
|
||||||
): Promise<{
|
|
||||||
sent: boolean;
|
|
||||||
}> {
|
|
||||||
const subject = 'Changes Required to Approve Your Activity Schedule';
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<p>Hi ${name},</p>
|
|
||||||
|
|
||||||
<p>Thank you for submitting your activity schedule for review.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
At this stage, we’re unable to approve the schedule. Please log in to your Host Portal
|
|
||||||
to review the changes required and update the details accordingly.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
👉 <a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Once the revisions have been made, our team will promptly review the schedule again so
|
|
||||||
we can move you closer to going live on Minglar.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
We’re looking forward to activating your experience soon.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The Minglar Team
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await brevoService.sendEmail({
|
|
||||||
recipients: [{ email: emailAddress }],
|
|
||||||
subject,
|
|
||||||
htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📧 Email sent successfully:', result);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent: true,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Brevo email send failed:', err);
|
|
||||||
throw new ApiError(500, 'Failed to send schedule rejection email to host.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ export async function sendOtpEmailForMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Your Minglar Verification Code";
|
const subject = "OTP for Minglar Admin Registration";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi there,</p>
|
<p>Dear User,</p>
|
||||||
<p>To continue your Minglar Admin registration, please use the following One-Time Password (OTP):</p>
|
<p>Your OTP for minglar admin registration is: <strong>${otp}</strong></p>
|
||||||
<p><strong>${otp}</strong></p>
|
<p>This code is valid for 5 minutes. Please do not share it with anyone.</p>
|
||||||
<p>This code is valid for 5 minutes.</p>
|
<p>Best regards,<br/>Minglar Team</p>
|
||||||
<p>For your security, please do not share this code with anyone.</p>
|
|
||||||
<p>If you did not request this OTP, please ignore this email.</p>
|
|
||||||
<p>Warm regards,<br/>Minglar Team</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -39,4 +36,3 @@ export async function sendOtpEmailForMinglarAdmin(
|
|||||||
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -140,9 +126,7 @@ export class PrePopulateService {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const adminEmail = config.MinglarAdminEmail;
|
return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
|
||||||
|
|
||||||
return { documentDetails, countryDetails, stateDetails, companyTypeDetails, adminEmail };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllFrequencies() {
|
async getAllFrequencies() {
|
||||||
@@ -163,6 +147,7 @@ export class PrePopulateService {
|
|||||||
foodType,
|
foodType,
|
||||||
cuisineDetails,
|
cuisineDetails,
|
||||||
vehicleType,
|
vehicleType,
|
||||||
|
navigationMode,
|
||||||
taxDetails,
|
taxDetails,
|
||||||
energyLevel,
|
energyLevel,
|
||||||
aminitiesDetails,
|
aminitiesDetails,
|
||||||
@@ -180,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 },
|
||||||
}),
|
}),
|
||||||
@@ -188,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 },
|
||||||
@@ -201,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,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export class SetPasscodeDTO {
|
|
||||||
userPasscode: string;
|
|
||||||
confirmPasscode: string;
|
|
||||||
|
|
||||||
constructor(userPasscode: string, confirmPasscode: string) {
|
|
||||||
this.userPasscode = userPasscode;
|
|
||||||
this.confirmPasscode = confirmPasscode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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 }),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -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,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user