diff --git a/client/app/[username]/page.jsx b/client/app/[username]/page.jsx index 1188a0c..f3c0326 100644 --- a/client/app/[username]/page.jsx +++ b/client/app/[username]/page.jsx @@ -1,3 +1,14 @@ +/* client/app/[username]/page.jsx + * GenAI Usage Note: When implementing the gallery page to fetch and display user posts based + * on the URL username, I used Copilot to understand how to structure the data fetching logic. + * Example prompts: "How do I fetch user data and their posts from an API endpoint using + * the username from the URL params?" and "What's the best way to handle loading states and + * error handling when fetching multiple API endpoints in a useEffect?" I reviewed the React + * documentation on useEffect cleanup and implemented a mounted flag to prevent state updates + * after component unmount. I also structured the fetch calls to first get the user profile, + * then fetch their posts and scrapbooks, ensuring proper error handling for 404 cases. + */ + "use client"; import { Suspense, useState, useEffect } from "react"; diff --git a/client/app/account/page.jsx b/client/app/account/page.jsx index 4a30174..4fbb3cb 100644 --- a/client/app/account/page.jsx +++ b/client/app/account/page.jsx @@ -1,3 +1,13 @@ +/* client/app/account/page.jsx + * GenAI Usage Note: When adding profile picture upload functionality, I used Copilot to + * understand how to integrate UploadThing's UploadButton component. Example prompts: "How do + * I use UploadThing's UploadButton component in a Next.js app?" and "How do I handle the + * onClientUploadComplete callback to save the uploaded image URL to my backend?" I reviewed + * the UploadThing documentation and implemented the handleProfilePictureUpload function to + * immediately save the profile picture URL to the backend after upload completes, ensuring + * the UI updates in real-time. + */ + "use client"; import Link from "next/link"; import Image from "next/image"; diff --git a/client/app/api/uploadthing/core.js b/client/app/api/uploadthing/core.js index 8f94eaf..3f3c113 100644 --- a/client/app/api/uploadthing/core.js +++ b/client/app/api/uploadthing/core.js @@ -1,3 +1,13 @@ +/* client/app/api/uploadthing/core.js + * GenAI Usage Note: When adding the profile picture uploader endpoint, I used Copilot to + * understand how to create a new FileRoute in UploadThing. Example prompts: "How do I add + * a second file uploader endpoint in UploadThing with different settings?" and "What's the + * difference between file.url and file.ufsUrl in UploadThing?" I reviewed the UploadThing + * documentation and configured the profilePictureUploader with maxFileSize: "5MB" and + * maxFileCount: 1, and updated the console.log to use file.ufsUrl instead of the deprecated + * file.url property. + */ + import { createUploadthing } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; diff --git a/client/app/feed/components/likesDialog.module.css b/client/app/feed/components/likesDialog.module.css index 05a765b..bed3448 100644 --- a/client/app/feed/components/likesDialog.module.css +++ b/client/app/feed/components/likesDialog.module.css @@ -5,9 +5,9 @@ * I did some research on what @keyframes does, and once I understood it, I came to the conclusion * that it would be best to use it here. * I reviewed the keyframe animations and adjusted duration values to ensure - * the transitions felt natural and weren't too slow. + * the transitions felt natural and weren't too slow. */ - + .overlay { position: fixed; top: 0; diff --git a/server/config/middleware.js b/server/config/middleware.js index 8544a6d..9b81aa8 100644 --- a/server/config/middleware.js +++ b/server/config/middleware.js @@ -1,3 +1,13 @@ +/* server/config/middleware.js + * GenAI Usage Note: When fixing a CORS error where requests without an Origin header were + * being rejected, I asked Copilot: "How do I allow requests without an Origin header in CORS + * while still validating cross-origin requests?" The solution was to check if origin exists + * before validation - if no origin is present, allow the request (for same-origin requests + * and health checks), but if an origin is present, validate it against the allowed origins list. + * I reviewed the CORS middleware documentation to ensure this approach was secure and wouldn't + * allow unauthorized cross-origin requests. + */ + const cors = require("cors"); const express = require("express"); const session = require("express-session"); @@ -9,9 +19,7 @@ function setupMiddleware(app) { const allowedOrigins = process.env.NODE_ENV === "development" ? ["http://localhost:3000"] - : [ - process.env.CLIENT_ORIGIN, - ].filter(Boolean); // Remove any undefined values + : [process.env.CLIENT_ORIGIN].filter(Boolean); // Remove any undefined values app.use( cors({ diff --git a/server/routes/posts.js b/server/routes/posts.js index d2e5795..d91cfb7 100644 --- a/server/routes/posts.js +++ b/server/routes/posts.js @@ -12,20 +12,20 @@ const { ALLOWED_CATEGORIES } = require("../constants"); async function updateScrapbookCoversAfterPostDeletion(deletedPost) { try { // Find scrapbooks that used this post's image as cover - const postImageUrls = (deletedPost.images || []).map(img => img.url); + const postImageUrls = (deletedPost.images || []).map((img) => img.url); if (postImageUrls.length === 0) return; // Find scrapbooks where the cover image matches any of this post's images const affectedScrapbooks = await Scrapbook.find({ author: deletedPost.author, - coverImage: { $in: postImageUrls } - }).populate('posts'); + coverImage: { $in: postImageUrls }, + }).populate("posts"); for (const scrapbook of affectedScrapbooks) { // Find a replacement post that's still in the scrapbook - const remainingPosts = scrapbook.posts.filter(p => - p && !p._id.equals(deletedPost._id) + const remainingPosts = scrapbook.posts.filter( + (p) => p && !p._id.equals(deletedPost._id) ); if (remainingPosts.length > 0) { @@ -38,13 +38,18 @@ async function updateScrapbookCoversAfterPostDeletion(deletedPost) { } } else { // No posts left in scrapbook, set empty cover - scrapbook.coverImage = ''; + scrapbook.coverImage = ""; await scrapbook.save(); - console.log(`[Scrapbook] Cleared cover for empty scrapbook "${scrapbook.title}"`); + console.log( + `[Scrapbook] Cleared cover for empty scrapbook "${scrapbook.title}"` + ); } } } catch (error) { - console.error('[Scrapbook] Error updating covers after deletion:', error.message); + console.error( + "[Scrapbook] Error updating covers after deletion:", + error.message + ); } } @@ -298,7 +303,7 @@ router.get("/photo-locations", async (req, res) => { res.json({ success: true, photoLocations, count: photoLocations.length }); } catch (error) { - console.error('Error extracting photo locations:', error); + console.error("Error extracting photo locations:", error); res.status(500).json({ success: false, error: error.message }); } }); @@ -391,7 +396,7 @@ router.delete("/:postId", async (req, res) => { for (const image of post.images || []) { const url = image.url; // UploadThing URLs follow pattern: https://utfs.io/f/{fileKey} - if (url && url.includes('utfs.io/f/')) { + if (url && url.includes("utfs.io/f/")) { const match = url.match(/utfs\.io\/f\/([^/?]+)/); if (match && match[1]) { fileKeys.push(match[1]); @@ -402,13 +407,15 @@ router.delete("/:postId", async (req, res) => { // Delete files from UploadThing if we found any keys if (fileKeys.length > 0) { try { - const { UTApi } = require('uploadthing/server'); + const { UTApi } = require("uploadthing/server"); const utapi = new UTApi(); await utapi.deleteFiles(fileKeys); - console.log(`[UploadThing] Deleted ${fileKeys.length} file(s) for post ${postId}`); + console.log( + `[UploadThing] Deleted ${fileKeys.length} file(s) for post ${postId}` + ); } catch (utError) { // Log but don't fail the deletion if UploadThing deletion fails - console.error('[UploadThing] Error deleting files:', utError.message); + console.error("[UploadThing] Error deleting files:", utError.message); } }