Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions client/app/[username]/page.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
10 changes: 10 additions & 0 deletions client/app/account/page.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
10 changes: 10 additions & 0 deletions client/app/api/uploadthing/core.js
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
4 changes: 2 additions & 2 deletions client/app/feed/components/likesDialog.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 11 additions & 3 deletions server/config/middleware.js
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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({
Expand Down
33 changes: 20 additions & 13 deletions server/routes/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
);
}
}

Expand Down Expand Up @@ -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 });
}
});
Expand Down Expand Up @@ -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]);
Expand All @@ -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);
}
}

Expand Down