Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1

This commit is contained in:
paritosh18
2026-02-26 11:06:10 +05:30
6 changed files with 85 additions and 9 deletions

View File

@@ -4,6 +4,7 @@ import { z } from 'zod';
export const MediaDto = z.object({
mediaType: z.string().optional(),
mediaFileName: z.string(),
isCoverImage: z.boolean().optional().default(false),
});
/* ================= PRICE ================= */
@@ -85,10 +86,12 @@ export const EligibilityDto = z.object({
/* ================= OTHER DETAILS ================= */
export const OtherDetailsDto = z.object({
exclusiveNotes: z.string().optional(),
safetyInstruction: z.string().optional(),
dosNotes: z.string().optional(),
dontsNotes: z.string().optional(),
tipsNotes: z.string().optional(),
termsAndCondition: z.string().optional(),
cancellations: z.string().optional(),
});
/* ================= CREATE ACTIVITY ================= */

View File

@@ -1,4 +1,3 @@
import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
@@ -59,8 +58,23 @@ export const handler = safeHandler(
activity.media = media.map((m: any) => ({
mediaType: m.mediaType ?? 'image',
mediaFileName: m.mediaFileName,
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) */
if (activity.cancellations !== undefined && activity.cancellations !== null) {
if (typeof activity.cancellations !== 'string') {
throw new ApiError(400, 'cancellations must be a string');
}
}
/* 5⃣ VALIDATION */
let parsedDto: CreateActivityInput;

View File

@@ -383,7 +383,9 @@ export class HostService {
}
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
const user = await this.prisma.user.findUnique({
const trimmedOtp = (otp || '').toString().trim();
const user = await this.prisma.user.findFirst({
where: { emailAddress: email, isActive: true },
select: {
id: true,
@@ -410,7 +412,7 @@ export class HostService {
throw new ApiError(400, 'OTP has expired.');
}
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
const isMatch = await bcrypt.compare(trimmedOtp, userOtp.otpCode);
if (!isMatch) {
throw new ApiError(400, 'Invalid OTP.');
@@ -2950,6 +2952,7 @@ export class HostService {
activityXid,
mediaType: m.mediaType ?? 'unknown',
mediaFileName: m.mediaFileName,
isCoverImage: m.isCoverImage ?? false,
displayOrder: index + 1,
})),
});
@@ -3498,6 +3501,22 @@ export class HostService {
data: {
activityXid,
exclusiveNotes: payload.otherDetails.exclusiveNotes ?? null,
SafetyInstruction: (() => {
const s = payload.otherDetails.safetyInstruction ?? null;
if (s === undefined || s === null) return null;
if (typeof s !== 'string') {
throw new ApiError(400, 'safetyInstruction must be a string');
}
return s;
})(),
Cancellations: (() => {
const c = payload.otherDetails.cancellations ?? null;
if (c === undefined || c === null) return null;
if (typeof c !== 'string') {
throw new ApiError(400, 'cancellations must be a string');
}
return c;
})(),
dosNotes: payload.otherDetails.dosNotes ?? null,
dontsNotes: payload.otherDetails.dontsNotes ?? null,
tipsNotes: payload.otherDetails.tipsNotes ?? null,

View File

@@ -23,10 +23,9 @@ import {
import { PaginationOptions } from '@/common/utils/pagination/pagination.types';
import config from '@/config/config';
import { Injectable } from '@nestjs/common';
import { User } from '@prisma/client';
import { PrismaClient, User } from '@prisma/client';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../../../common/database/prisma.service';
import { PrismaClient } from '@prisma/client';
import ApiError from '../../../common/utils/helper/ApiError';
import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto';
import { sendAMEmailForHostAssign } from './AMEmail.service';
@@ -154,8 +153,10 @@ export class MinglarService {
}
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
const user = await this.prisma.user.findUnique({
where: { emailAddress: email },
const trimmedOtp = (otp || '').toString().trim();
const user = await this.prisma.user.findFirst({
where: { emailAddress: email, isActive: true },
select: {
id: true,
emailAddress: true,
@@ -181,7 +182,7 @@ export class MinglarService {
throw new ApiError(400, 'OTP has expired.');
}
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
const isMatch = await bcrypt.compare(trimmedOtp, userOtp.otpCode);
if (!isMatch) {
throw new ApiError(400, 'Invalid OTP.');

View File

@@ -413,6 +413,8 @@ export class UserService {
}
async verifyHostOtp(mobileNumber: string, otp: string): Promise<boolean> {
const trimmedOtp = (otp || '').toString().trim();
const user = await this.prisma.user.findFirst({
where: { mobileNumber: mobileNumber, isActive: true },
select: {
@@ -440,7 +442,7 @@ export class UserService {
throw new ApiError(400, 'OTP has expired.');
}
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
const isMatch = await bcrypt.compare(trimmedOtp, userOtp.otpCode);
if (!isMatch) {
throw new ApiError(400, 'Invalid OTP.');
@@ -1896,6 +1898,39 @@ export class UserService {
const skip = (page - 1) * limit;
// 0⃣ Get user's interests and map to activity types
const userInterests = await this.prisma.userInterests.findMany({
where: { userXid: userId, isActive: true },
select: { interestXid: true },
});
if (!userInterests.length) {
return {
page,
limit,
totalCount: 0,
hasMore: false,
activities: [],
};
}
const activityTypeIds = (
await this.prisma.activityTypes.findMany({
where: { interestXid: { in: userInterests.map((u) => u.interestXid) }, isActive: true },
select: { id: true },
})
).map((t) => t.id);
if (!activityTypeIds.length) {
return {
page,
limit,
totalCount: 0,
hasMore: false,
activities: [],
};
}
// Rough bounding box in degrees to reduce DB scan
const earthRadiusKm = 6371;
const latDelta = (radiusKm / earthRadiusKm) * (180 / Math.PI);
@@ -1908,6 +1943,7 @@ export class UserService {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
activityTypeXid: { in: activityTypeIds },
checkInLat: {
not: null,
gte: userLat - latDelta,