diff --git a/package.json b/package.json index b3fb943..73abd81 100755 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@t3-oss/env-nextjs": "^0.10.1", + "@types/formidable": "^3.4.5", "drizzle-orm": "^0.33.0", "geist": "^1.3.1", "jsonwebtoken": "^9.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 247ce92..aeedb9a 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.10.1 version: 0.10.1(typescript@5.6.2)(zod@3.23.8) + '@types/formidable': + specifier: ^3.4.5 + version: 3.4.5 drizzle-orm: specifier: ^0.33.0 version: 0.33.0(@types/react@18.3.11)(postgres@3.4.4)(react@18.3.1) @@ -541,6 +544,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/formidable@3.4.5': + resolution: {integrity: sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2413,6 +2419,10 @@ snapshots: '@types/estree@1.0.6': {} + '@types/formidable@3.4.5': + dependencies: + '@types/node': 20.16.10 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} diff --git a/public/uploads/profile-pictures/001048.90e6fe2945c1487c84c17a4d360c75b9.0102_1728672056503.jpg b/public/uploads/profile-pictures/001048.90e6fe2945c1487c84c17a4d360c75b9.0102_1728672056503.jpg new file mode 100644 index 0000000..126a57f Binary files /dev/null and b/public/uploads/profile-pictures/001048.90e6fe2945c1487c84c17a4d360c75b9.0102_1728672056503.jpg differ diff --git a/public/uploads/profile-pictures/madeline.jpg b/public/uploads/profile-pictures/madeline.jpg new file mode 100755 index 0000000..396d7b6 Binary files /dev/null and b/public/uploads/profile-pictures/madeline.jpg differ diff --git a/src/app/api/relationships/checkStatusByAppleId/route.ts b/src/app/api/relationships/checkStatusByAppleId/route.ts new file mode 100644 index 0000000..58c5230 --- /dev/null +++ b/src/app/api/relationships/checkStatusByAppleId/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { getPartnerByAppleId, checkRelationshipStatusByAppleId } from "~/server/functions"; +import { middleware } from "~/middleware"; + +export const GET = async (request: NextRequest) => { + const middlewareResponse = await middleware(request); + if (middlewareResponse) return middlewareResponse; + + const appleId = request.nextUrl.searchParams.get('appleId'); + + if (!appleId) { + return NextResponse.json({ message: "Missing required fields" }, { status: 400 }); + } + + try { + const relationship = await checkRelationshipStatusByAppleId(appleId); + const partner = await getPartnerByAppleId(appleId); + + return NextResponse.json({ relationship, partner }); + } catch (error) { + if (error instanceof Error) { + return NextResponse.json({ message: error.message }, { status: 404 }); + } + return NextResponse.json({ message: "Error fetching relationship status" }, { status: 500 }); + } +}; diff --git a/src/app/api/users/updatePFP/route.ts b/src/app/api/users/updatePFP/route.ts deleted file mode 100644 index a4a3b9f..0000000 --- a/src/app/api/users/updatePFP/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -"use server"; -import { NextResponse } from "next/server"; -import type { NextRequest } from "next/server"; -import { updateUserPFP } from "~/server/functions"; -import { middleware } from "~/middleware"; - -type UpdatePfpRequest = { - userId: number; - pfpURL: string; -} - -export async function POST(request: NextRequest) { - const middlewareResponse = await middleware(request); - if (middlewareResponse) return middlewareResponse; - try { - const { userId, pfpURL } = await request.json() as UpdatePfpRequest; - console.log("Received request:", { userId, pfpURL }); - - if (!userId || !pfpURL || isNaN(userId)) - return NextResponse.json({ message: "Missing required fields" }, { status: 400 }); - - console.log("Updating profile picture for user:", userId); - const result = await updateUserPFP(userId, pfpURL); - - if (result.success) { - console.log("Profile picture updated successfully"); - return NextResponse.json({ message: "Profile picture updated successfully" }); - } else { - throw new Error("Failed to update profile picture"); - } - } catch (error) { - console.error("Error in updatePFP:", error); - if (error instanceof Error) { - return NextResponse.json({ message: error.message }, { status: 400 }); - } - return NextResponse.json({ message: "Error updating profile picture" }, { status: 500 }); - } -} diff --git a/src/app/api/users/updatePfp/route.ts b/src/app/api/users/updatePfp/route.ts new file mode 100644 index 0000000..5944612 --- /dev/null +++ b/src/app/api/users/updatePfp/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { updateUserPfpUrl, getUserPfpUrl } from "~/server/functions"; +import { middleware } from "~/middleware"; +import fs from "fs/promises"; +import path from "path"; + +const UPLOAD_DIR = path.resolve(process.cwd(), "public/uploads/profile-pictures"); + +export async function POST(request: NextRequest) { + console.log("Received POST request to /api/users/updatePfp"); + + // Apply middleware + const middlewareResponse = await middleware(request); + if (middlewareResponse) { + console.log("Middleware rejected the request"); + return middlewareResponse; + } + + try { + console.log("Parsing form data..."); + const formData = await request.formData(); + console.log("Form data parsed successfully"); + + const file = formData.get('file') as File | null; + const appleId = formData.get('appleId') as string | null; + + console.log("Received file:", file ? "Yes" : "No"); + console.log("Received appleId:", appleId); + + if (!file || !appleId) { + console.log("File or appleId missing"); + return NextResponse.json({ message: "File or appleId missing." }, { status: 400 }); + } + + // Validate file type + console.log("File type:", file.type); + if (!file.type.startsWith('image/')) { + console.log("Invalid file type"); + return NextResponse.json({ message: "Invalid file type. Only images are allowed." }, { status: 400 }); + } + + // Ensure upload directory exists + console.log("Creating upload directory if it doesn't exist"); + await fs.mkdir(UPLOAD_DIR, { recursive: true }); + + // Generate a unique filename + const fileExtension = path.extname(file.name); + const fileName = `${appleId}_${Date.now()}${fileExtension}`; + const filePath = path.join(UPLOAD_DIR, fileName); + console.log("Generated file path:", filePath); + + // Write file to disk + console.log("Writing file to disk..."); + const buffer = Buffer.from(await file.arrayBuffer()); + await fs.writeFile(filePath, buffer); + console.log("File written successfully"); + + // Get the relative URL for the uploaded file + const pfpURL = `/uploads/profile-pictures/${fileName}`; + console.log("New pfpURL:", pfpURL); + + // Get old pfpURL and remove the old file if it exists + console.log("Fetching old pfpURL..."); + const oldPfpURL = await getUserPfpUrl(appleId); + if (oldPfpURL) { + console.log("Old pfpURL found:", oldPfpURL); + const oldFilePath = path.join(process.cwd(), 'public', oldPfpURL); + console.log("Attempting to delete old file:", oldFilePath); + await fs.unlink(oldFilePath).catch((err) => { + console.log("Error deleting old file:", err.message); + }); + } else { + console.log("No old pfpURL found"); + } + + // Update the user's pfpURL in the database + console.log("Updating user's pfpURL in the database..."); + await updateUserPfpUrl(appleId, pfpURL); + console.log("Database updated successfully"); + + console.log("Sending successful response"); + return NextResponse.json({ message: "Profile picture updated successfully", pfpURL }); + } catch (error) { + console.error("Error in updatePFP:", error); + if (error instanceof Error) { + console.error("Error details:", error.message); + return NextResponse.json({ message: error.message }, { status: 500 }); + } + return NextResponse.json({ message: "Error updating profile picture" }, { status: 500 }); + } +} diff --git a/src/app/users/updatePFP/page.tsx b/src/app/users/updatePfp/page.tsx similarity index 59% rename from src/app/users/updatePFP/page.tsx rename to src/app/users/updatePfp/page.tsx index 448d57c..1ce5bac 100644 --- a/src/app/users/updatePFP/page.tsx +++ b/src/app/users/updatePfp/page.tsx @@ -3,33 +3,45 @@ import React, { useState } from 'react'; export default function TestUpdatePFP() { - const [userId, setUserId] = useState(''); - const [pfpURL, setPfpURL] = useState(''); + const [appleId, setAppleId] = useState(''); + const [file, setFile] = useState(null); const [result, setResult] = useState(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setResult(null); + if (!file) { + setResult('Please select a file'); + return; + } + try { - const response = await fetch('/api/users/updatePFP', { + const formData = new FormData(); + formData.append('appleId', appleId); + formData.append('file', file); + + const response = await fetch('/api/users/updatePfp', { method: 'POST', headers: { - 'Content-Type': 'application/json', 'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '', }, - body: JSON.stringify({ - userId, - pfpURL - }) + body: formData }); - const data = await response.json() as {message: string} + const data = await response.json(); setResult(JSON.stringify(data, null, 2)); } catch (error) { console.error('Error:', error); setResult('An error occurred'); } }; + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + setFile(e.target.files[0]); + } + }; + return (
@@ -37,24 +49,24 @@ export default function TestUpdatePFP() {

Test Update PFP

- + setUserId(e.target.value)} + id="appleId" + value={appleId} + onChange={(e) => setAppleId(e.target.value)} className="border p-2 w-full bg-black" required />
- + setPfpURL(e.target.value)} + type="file" + id="profilepic" + onChange={handleFileChange} className="border p-2 w-full bg-black" + accept="image/*" required />
diff --git a/src/env.js b/src/env.js index 6b80db4..3a65873 100755 --- a/src/env.js +++ b/src/env.js @@ -9,9 +9,10 @@ export const env = createEnv({ NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), - JWT_SECRET: z.string(), - JWT_REFRESH_SECRET: z.string(), + //JWT_SECRET: z.string(), + //JWT_REFRESH_SECRET: z.string(), SKIP_ENV_VALIDATION: z.boolean().optional(), + ROOT_PATH: z.string(), }, client: { @@ -22,10 +23,11 @@ export const env = createEnv({ DATABASE_URL: process.env.DATABASE_URL, API_KEY: process.env.API_KEY, NODE_ENV: process.env.NODE_ENV, - JWT_SECRET: process.env.JWT_SECRET, - JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET, + //JWT_SECRET: process.env.JWT_SECRET, + //JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET, NEXT_PUBLIC_API_KEY: process.env.NEXT_PUBLIC_API_KEY, SKIP_ENV_VALIDATION: process.env.SKIP_ENV_VALIDATION, + ROOT_PATH: process.env.ROOT_PATH, }, skipValidation: !!process.env.SKIP_ENV_VALIDATION, diff --git a/src/server/functions.ts b/src/server/functions.ts index 553e823..d0c0dab 100755 --- a/src/server/functions.ts +++ b/src/server/functions.ts @@ -1,7 +1,7 @@ import 'server-only'; import { db } from '~/server/db'; import * as schema from '~/server/db/schema'; -import { eq, and, or, sql } from 'drizzle-orm'; +import { eq, and, or, sql, not } from 'drizzle-orm'; // --- Helper Functions --- // @@ -20,6 +20,21 @@ export const ensureUserExists = async (userId: number) => { } }; +export const ensureUserExistsByAppleId = async (appleId: string) => { + try { + const user = await db.select().from(schema.users) + .where(eq(schema.users.appleId, appleId)); + + return (user.length > 0) ? user[0] : null; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Error checking user: ${error.message}`); + } else { + throw new Error("Unknown error occurred while checking user"); + } + } +}; + export const ensureRelationshipExistsByRelationshipId = async (relationshipId: number) => { try { const relationship = await db.select({ @@ -168,12 +183,11 @@ export const createUser = async ( } }; -export const updateUserPFP = async (userId: number, pfpURL: string) => { +export const updateUserPfpUrl = async (appleId: string, pfpURL: string) => { try { await db.update(schema.users) .set({ pfpURL }) - .where(eq(schema.users.id, userId)); - + .where(eq(schema.users.appleId, appleId)); return { success: true }; } catch (error) { if (error instanceof Error) { @@ -184,6 +198,20 @@ export const updateUserPFP = async (userId: number, pfpURL: string) => { } }; +export const getUserPfpUrl = async (appleId: string) => { + try { + const user = await db.select().from(schema.users) + .where(eq(schema.users.appleId, appleId)); + return (user.length > 0) ? user[0]?.pfpURL : null; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to fetch user by appleId: ${error.message}`); + } else { + throw new Error("Unknown error occurred while fetching user by appleId"); + } + } +}; + export const updateUserPushToken = async (userId: number, pushToken: string) => { try { await db.update(schema.users) @@ -218,6 +246,67 @@ export const updateUserPushTokenByAppleId = async (appleId: string, pushToken: s // --- Relationship Management Functions --- // +export const checkRelationshipStatusByAppleId = async (appleId: string) => { + try { + await ensureUserExistsByAppleId(appleId); + const user = await getUserByAppleId(appleId); + if (!user) throw new Error("User not found"); + // Check and see if this user is in any relationships + const relationships = await db.select().from(schema.userRelationships) + .where(eq(schema.userRelationships.userId, user.id)); + if (!relationships.length) throw new Error("No relationships found for this user"); + // User can only be in one relationship at a time + const relationship = relationships[0]; + if (!relationship) throw new Error("No relationship found for this user"); + // Check if the relationship is accepted + const relationshipStatus = await db.select().from(schema.relationships) + .where(eq(schema.relationships.id, relationship.relationshipId)); + if (!relationshipStatus.length) throw new Error("Relationship not found"); + if (!relationshipStatus[0]) throw new Error("Relationship not found"); + if (relationshipStatus[0].status !== "accepted") throw new Error("Relationship not accepted"); + else return relationshipStatus[0]; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to check relationship status: ${error.message}`); + } else { + throw new Error("Unknown error occurred while checking relationship status"); + } + } +}; + +export const getPartnerByAppleId = async (appleId: string) => { + try { + const user = await getUserByAppleId(appleId); + if (!user) throw new Error("User not found"); + + const relationship = await checkRelationshipStatusByAppleId(appleId); + if (!relationship) throw new Error("User is not in a relationship"); + + const partnerRelation = await db.select() + .from(schema.userRelationships) + .where( + and( + eq(schema.userRelationships.relationshipId, relationship.id), + not(eq(schema.userRelationships.userId, user.id)) + ) + ); + + if (!partnerRelation.length) throw new Error("Partner not found"); + if (!partnerRelation[0]) throw new Error("Partner not found"); + + const partner = await getUserById(partnerRelation[0].userId); + if (!partner) throw new Error("Partner not found"); + + return partner; + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to get partner: ${error.message}`); + } else { + throw new Error("Unknown error occurred while getting partner"); + } + } +}; + export const createRelationshipRequest = async (requestorId: number, requestedId: number) => { try { await ensureUserExists(requestorId); diff --git a/undotree_2 b/undotree_2 new file mode 100644 index 0000000..555f636 --- /dev/null +++ b/undotree_2 @@ -0,0 +1,3 @@ +" Press ? for help. + + * >0< (Original)