This MR fixes critical authentication issues that prevented login on localhost and improves the developer experience with consolidated rebuild scripts and a working help modal keyboard shortcut. #5

Merged
ilia merged 51 commits from dev into main 2026-01-05 19:42:46 -05:00
4 changed files with 37 additions and 23 deletions
Showing only changes of commit f4461b277c - Show all commits

View File

@ -47,7 +47,7 @@ export async function DELETE(
try {
await unlink(filepath)
} catch (error) {
console.error(`Failed to delete file ${filepath}:`, error)
console.error("Failed to delete file:", filepath, error)
// Continue with database deletion even if file deletion fails
}
}

View File

@ -2,7 +2,6 @@ import { NextRequest, NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { prisma } from "@/lib/prisma"
import { sendNewPhotoEmail } from "@/lib/email"
import type { Prisma } from "@prisma/client"
// Legacy endpoint for URL-based uploads (kept for backward compatibility)
export async function POST(req: NextRequest) {
@ -47,7 +46,8 @@ export async function POST(req: NextRequest) {
answerName: answerName.trim(),
points: pointsValue,
maxAttempts: maxAttemptsValue,
} as Prisma.PhotoUncheckedCreateInput,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
include: {
uploader: {
select: {
@ -73,7 +73,9 @@ export async function POST(req: NextRequest) {
Promise.all(
allUsers.map((user: { id: string; email: string; name: string }) =>
sendNewPhotoEmail(user.email, user.name, photo.id, photo.uploader.name).catch(
(err) => console.error(`Failed to send email to ${user.email}:`, err)
(err) => {
console.error("Failed to send email to:", user.email, err)
}
)
)
)

View File

@ -6,7 +6,6 @@ import { writeFile } from "fs/promises"
import { join } from "path"
import { existsSync, mkdirSync } from "fs"
import { createHash } from "crypto"
import type { Prisma } from "@prisma/client"
export async function POST(req: NextRequest) {
try {
@ -44,9 +43,15 @@ export async function POST(req: NextRequest) {
mkdirSync(uploadsDir, { recursive: true })
}
type PhotoWithUploader = Prisma.PhotoGetPayload<{
include: { uploader: { select: { name: true } } }
}>
type PhotoWithUploader = {
id: string
uploaderId: string
url: string
answerName: string
points: number
createdAt: Date
uploader: { name: string }
}
const createdPhotos: PhotoWithUploader[] = []
@ -106,7 +111,8 @@ export async function POST(req: NextRequest) {
// Check for duplicate file
const existingPhoto = await prisma.photo.findFirst({
where: { fileHash } as Prisma.PhotoWhereInput,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
where: { fileHash } as any,
})
if (existingPhoto) {
@ -118,9 +124,12 @@ export async function POST(req: NextRequest) {
const timestamp = Date.now()
const randomStr = Math.random().toString(36).substring(2, 15)
const extension = file.name.split(".").pop() || "jpg"
// Sanitize extension - only allow alphanumeric characters
const rawExtension = file.name.split(".").pop() || "jpg"
const extension = rawExtension.replace(/[^a-zA-Z0-9]/g, "").toLowerCase() || "jpg"
const filename = `${timestamp}-${i}-${randomStr}.${extension}`
// Filename is generated server-side (timestamp + random), safe for path.join
const filepath = join(uploadsDir, filename)
await writeFile(filepath, buffer)
@ -158,7 +167,8 @@ export async function POST(req: NextRequest) {
penaltyEnabled: penaltyEnabled,
penaltyPoints: penaltyPointsValue,
maxAttempts: maxAttemptsValue,
} as Prisma.PhotoUncheckedCreateInput,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
include: {
uploader: {
select: {
@ -193,12 +203,9 @@ export async function POST(req: NextRequest) {
user.name,
photo.id,
photo.uploader.name
).catch((err) =>
console.error(
`Failed to send email to ${user.email} for photo ${photo.id}:`,
err
)
)
).catch((err) => {
console.error("Failed to send email to:", user.email, "for photo:", photo.id, err)
})
)
)
)

View File

@ -6,7 +6,6 @@ import { writeFile } from "fs/promises"
import { join } from "path"
import { existsSync, mkdirSync } from "fs"
import { createHash } from "crypto"
import type { Prisma } from "@prisma/client"
export async function POST(req: NextRequest) {
try {
@ -63,7 +62,8 @@ export async function POST(req: NextRequest) {
// Check for duplicate file
const existingPhoto = await prisma.photo.findFirst({
where: { fileHash } as Prisma.PhotoWhereInput,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
where: { fileHash } as any,
})
if (existingPhoto) {
@ -76,7 +76,9 @@ export async function POST(req: NextRequest) {
// Generate unique filename
const timestamp = Date.now()
const randomStr = Math.random().toString(36).substring(2, 15)
const extension = file.name.split(".").pop() || "jpg"
// Sanitize extension - only allow alphanumeric characters
const rawExtension = file.name.split(".").pop() || "jpg"
const extension = rawExtension.replace(/[^a-zA-Z0-9]/g, "").toLowerCase() || "jpg"
const filename = `${timestamp}-${randomStr}.${extension}`
// Ensure uploads directory exists
@ -85,7 +87,7 @@ export async function POST(req: NextRequest) {
mkdirSync(uploadsDir, { recursive: true })
}
// Save file
// Filename is generated server-side (timestamp + random), safe for path.join
const filepath = join(uploadsDir, filename)
await writeFile(filepath, buffer)
@ -123,7 +125,8 @@ export async function POST(req: NextRequest) {
answerName: answerName.trim(),
points: pointsValue,
maxAttempts: maxAttemptsValue,
} as Prisma.PhotoUncheckedCreateInput,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
include: {
uploader: {
select: {
@ -149,7 +152,9 @@ export async function POST(req: NextRequest) {
Promise.all(
allUsers.map((user: { id: string; email: string; name: string }) =>
sendNewPhotoEmail(user.email, user.name, photo.id, photo.uploader.name).catch(
(err) => console.error(`Failed to send email to ${user.email}:`, err)
(err) => {
console.error("Failed to send email to:", user.email, err)
}
)
)
)