Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staging sync #263

Open
wants to merge 20 commits into
base: prod
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions app/(default)/api/events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import Eventmodel from "@/models/Events";
import connectMongoDB from "@/lib/dbConnect";
import { v4 as uuidv4 } from "uuid";
import { cloudinary } from '@/Cloudinary';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { cloudinary } from '@/Cloudinary';
import { trace } from '@opentelemetry/api';
import { logger } from '@/lib/logger'; // Assuming logger is available
import { trackEvent } from '@/lib/analytics'; // Assuming analytics tracking is available

/**
* @swagger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @swagger
const tracer = trace.getTracer('events-api');
const span = tracer.startSpan('get-events');
logger.info('Processing get events request');

* /api/events:
Expand Down Expand Up @@ -353,6 +354,38 @@ export async function DELETE(request: Request) {
{ status: 400 }
);
}
const event = await Eventmodel.findOne({ id: eventid });

if (!event) {
return NextResponse.json(
{ error: "Event not found" },
{ status: 404 }
);
}

// If there's an image URL, delete it from Cloudinary
if (event.imageURL) {
try {
// Extract public_id from the Cloudinary URL
const matches = event.imageURL.match(/\/v\d+\/(.+?)\./);
const publicId = matches ? matches[1] : null;

if (publicId) {
console.log('Attempting to delete image with public ID:', publicId);
const result = await cloudinary.uploader.destroy(publicId);
console.log('Cloudinary deletion result:', result);
} else {
console.warn('Could not extract public ID from URL:', event.imageURL);
}
} catch (cloudinaryError) {
console.error("Error deleting image from Cloudinary:", cloudinaryError);
// Log detailed error for debugging
if (cloudinaryError instanceof Error) {
console.error("Error details:", cloudinaryError.message);
}
// Continue with event deletion even if image deletion fails
}
}

await Eventmodel.deleteOne({ id: eventid });
return NextResponse.json(
Expand Down
167 changes: 167 additions & 0 deletions app/(default)/api/events/upload/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { NextResponse } from "next/server";
import { Readable } from "stream";
import { cloudinary } from "@/Cloudinary";
import { UploadApiResponse } from "cloudinary";

/**
* Handles file uploads and uploads the file to Cloudinary.
*
* @param {Request} request - The incoming HTTP request
* @returns {Promise<Response>} - A response containing the uploaded image URL or an error message
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @returns {Promise<Response>} - A response containing the uploaded image URL or an error message
import { trace } from '@opentelemetry/api';
import { logger } from '@/lib/logger'; // Assuming logger is available
import { trackEvent } from '@/lib/analytics'; // Assuming analytics tracking is available

*/
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
const tracer = trace.getTracer('events-api');
const span = tracer.startSpan('events-upload');
logger.info('Processing event upload request');

* @swagger
* /api/upload:
* post:
* summary: Upload an image file to Cloudinary
* description: This endpoint handles image file uploads and uploads the file to Cloudinary.
* tags:
* - Events
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* - Events
logger.warn('No file provided in upload request');
span.setStatus({ code: SpanStatusCode.ERROR });
span.setAttribute('error.type', 'missing_file');
span.end();
trackEvent('event_upload_error', { error_type: 'missing_file' });

* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* properties:
logger.warn(`Invalid file type: ${file.type}`);
span.setStatus({ code: SpanStatusCode.ERROR });
span.setAttribute('error.type', 'invalid_file_type');
span.setAttribute('file.type', file.type);
span.end();
trackEvent('event_upload_error', { error_type: 'invalid_file_type', file_type: file.type });

* file:
* type: string
* format: binary
* description: The image file to be uploaded.
* responses:
* 200:
* description: Successfully uploaded the image
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* description: Successfully uploaded the image
logger.debug('Processing file upload', {
fileName: file.name,
fileSize: buffer.length,
fileType: file.type
});
span.setAttribute('file.name', file.name);
span.setAttribute('file.size', buffer.length);
span.setAttribute('file.type', file.type);

* content:
* application/json:
* schema:
* type: object
* properties:
* imageUrl:
* type: string
* description: The secure URL of the uploaded image
* 400:
* description: Bad request, no file uploaded
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* type: string
logger.info('File successfully uploaded to S3', {
location: uploadResponse.Location,
key: uploadResponse.Key
});
span.setAttribute('s3.location', uploadResponse.Location);
span.setAttribute('s3.key', uploadResponse.Key);
trackEvent('event_upload_success', {
file_type: file.type,
file_size: buffer.length
});

* example: "Bad Request"
* details:
* type: string
* example: "No file uploaded"
* 500:
* description: Internal server error during upload
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Cloudinary Upload Failed"
* details:
* type: string
* example: "Unknown upload error"
*/
export async function POST(request: Request): Promise<Response> {
try {
// Parse the form data from the incoming request
const formData = await request.formData();

// Retrieve the uploaded file from the form data
const image: File | null = formData.get("file") as File;
const name: string | null = formData.get("name") as string;
const existingUrl: string | null = formData.get("existingUrl") as string;

// Validate if a file was uploaded
if (!image || !name) {
return NextResponse.json(
{
message: "Bad Request",
details: "No file uploaded",
},
{ status: 400 } // HTTP 400 Bad Request
);
}

// If there's an existing image, extract its public_id and delete it
if (existingUrl) {
try {
const publicId = existingUrl.split('/').slice(-1)[0].split('.')[0];
if (publicId) {
await cloudinary.uploader.destroy(`events/${publicId}`);
}
} catch (deleteError) {
console.error("Error deleting existing image:", deleteError);
// Continue with upload even if delete fails
}
}
// Generating a unique public_id using the event name and timestamp
const uniquePublicId = `${name}-${Date.now()}`;
// Convert the uploaded file (File object) to a Buffer
const arrayBuffer = await image.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

// Create a readable stream from the Buffer
const stream = Readable.from(buffer);

try {
// Upload the image to Cloudinary
const uploadResult: UploadApiResponse = await new Promise(
(resolve, reject) => {
const uploadStream = cloudinary.uploader.upload_stream(
{
folder: "events",
public_id: uniquePublicId,
overwrite: false // Prevent accidental overwrites
},
(error, result) => {
if (error || !result) {
reject(error); // Handle upload errors
} else {
resolve(result); // Resolve with the upload result
}
}
);

// Pipe the readable stream into the Cloudinary upload stream
stream.pipe(uploadStream);
}
);

// Return the secure URL of the uploaded image as the response
return NextResponse.json({
imageUrl: uploadResult.secure_url,
});
} catch (uploadError) {
// Log and handle errors during the Cloudinary upload process
console.error("Cloudinary upload error:", uploadError);
return NextResponse.json(
{
message: "Cloudinary Upload Failed",
details:
uploadError instanceof Error
? uploadError.message
: "Unknown upload error",
},
{ status: 500 } // HTTP 500 Internal Server Error
);
}
} catch (parseError) {
// Log and handle errors while parsing form data
console.error("Error parsing form data:", parseError);
return NextResponse.json(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return NextResponse.json(
logger.error('Error processing event upload', {
error: error instanceof Error ? error.message : String(error)
});
span.recordException(error instanceof Error ? error : new Error(String(error)));
span.setStatus({ code: SpanStatusCode.ERROR });
trackEvent('event_upload_error', {
error_type: 'server_error',
error_message: error instanceof Error ? error.message : String(error)
});
} finally {
span.end();

{
message: "Invalid Request",
details:
parseError instanceof Error
? parseError.message
: "Unable to process request",
},
{ status: 400 } // HTTP 400 Bad Request
);
}
}
Loading