diff --git a/serverless/functions/host.yml b/serverless/functions/host.yml index ca7714a..888609c 100644 --- a/serverless/functions/host.yml +++ b/serverless/functions/host.yml @@ -416,6 +416,22 @@ mediaUploadTos3: 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 diff --git a/src/modules/host/handlers/mediaUploadForVenueToS3.ts b/src/modules/host/handlers/mediaUploadForVenueToS3.ts new file mode 100644 index 0000000..4fba408 --- /dev/null +++ b/src/modules/host/handlers/mediaUploadForVenueToS3.ts @@ -0,0 +1,97 @@ +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(activityXid); + if (!activityDetails) { + throw new ApiError(404, 'Activity not found'); + } + + const results: Array = []; + + 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) { + console.error(err); + return response(500, 'Failed to generate venue presigned URLs'); + } +}; + +function response(statusCode: number, body: any) { + return { + statusCode, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify(body), + }; +} diff --git a/src/modules/host/handlers/mediaUploadToS3.ts b/src/modules/host/handlers/mediaUploadToS3.ts index 7ddb977..7d6396f 100644 --- a/src/modules/host/handlers/mediaUploadToS3.ts +++ b/src/modules/host/handlers/mediaUploadToS3.ts @@ -2,13 +2,23 @@ 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; @@ -21,6 +31,11 @@ export const handler: APIGatewayProxyHandler = async (event) => { throw new ApiError(400, 'activityXid is required in path parameters'); } + const activityDetails = await hostService.getActivityDetailsById(activityXid); + if (!activityDetails) { + throw new ApiError(404, 'Activity not found'); + } + const results = []; for (const file of files) { diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 741ecf0..051e6f3 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -116,6 +116,11 @@ export class HostService { async getAllHosts() { return this.prisma.user.findMany({ where: { roleXid: 3 } }); } + + + async getActivityDetailsById(activityXid: number) { + return this.prisma.activities.findFirst({ where: { id: activityXid } }); + } async getHostIdByUserXid(user_xid: number) { const host = await this.prisma.hostHeader.findFirst({