mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-06-28 06:34:15 +00:00
feat: astro test
This commit is contained in:
parent
df6e31ba89
commit
84144c383c
262 changed files with 18993 additions and 2655 deletions
3
backend/src/app/api/auth/[...nextauth]/route.ts
Normal file
3
backend/src/app/api/auth/[...nextauth]/route.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { handlers } from "@/lib/auth";
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
34
backend/src/app/api/auth/about-me/route.ts
Normal file
34
backend/src/app/api/auth/about-me/route.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { profanity } from "@2toad/profanity";
|
||||
import z from "zod";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { RateLimit } from "@/lib/rate-limit";
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const rateLimit = new RateLimit(request, 3);
|
||||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
const { description } = await request.json();
|
||||
if (!description) return rateLimit.sendResponse({ error: "New about me is required" }, 400);
|
||||
|
||||
const validation = z.string().trim().max(256).safeParse(description);
|
||||
if (!validation.success) return rateLimit.sendResponse({ error: validation.error.issues[0].message }, 400);
|
||||
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: Number(session.user?.id) },
|
||||
data: { description: profanity.censor(description) },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update description:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to update description" }, 500);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
}
|
||||
25
backend/src/app/api/auth/delete/route.ts
Normal file
25
backend/src/app/api/auth/delete/route.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { RateLimit } from "@/lib/rate-limit";
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session || !session.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const rateLimit = new RateLimit(request, 1);
|
||||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
try {
|
||||
await prisma.user.delete({
|
||||
where: { id: Number(session.user.id) },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete user:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to delete account" }, 500);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
}
|
||||
37
backend/src/app/api/auth/name/route.ts
Normal file
37
backend/src/app/api/auth/name/route.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { profanity } from "@2toad/profanity";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { userNameSchema } from "@tomodachi-share/shared/schemas";
|
||||
import { RateLimit } from "@/lib/rate-limit";
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session || !session.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const rateLimit = new RateLimit(request, 3);
|
||||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
const { name } = await request.json();
|
||||
if (!name) return rateLimit.sendResponse({ error: "New name is required" }, 400);
|
||||
|
||||
const validation = userNameSchema.safeParse(name);
|
||||
if (!validation.success) return rateLimit.sendResponse({ error: validation.error.issues[0].message }, 400);
|
||||
|
||||
// Check for inappropriate words
|
||||
if (profanity.exists(name)) return rateLimit.sendResponse({ error: "Name contains inappropriate words" }, 400);
|
||||
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: Number(session.user.id) },
|
||||
data: { name },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update name:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to update name" }, 500);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
}
|
||||
85
backend/src/app/api/auth/picture/route.ts
Normal file
85
backend/src/app/api/auth/picture/route.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { RateLimit } from "@/lib/rate-limit";
|
||||
import { validateImage } from "@/lib/images";
|
||||
|
||||
const uploadsDirectory = path.join(process.cwd(), "uploads", "user");
|
||||
|
||||
const formDataSchema = z.object({
|
||||
image: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
});
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const rateLimit = new RateLimit(request, 3);
|
||||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
// Check if profile picture was updated in the last 7 days
|
||||
const user = await prisma.user.findUnique({ where: { id: Number(session.user?.id) } });
|
||||
if (user && user.imageUpdatedAt) {
|
||||
const timePeriod = dayjs().subtract(7, "days");
|
||||
const lastUpdate = dayjs(user.imageUpdatedAt);
|
||||
|
||||
if (lastUpdate.isAfter(timePeriod)) return rateLimit.sendResponse({ error: "Profile picture was changed in the last 7 days" }, 400);
|
||||
}
|
||||
|
||||
// Parse data
|
||||
const formData = await request.formData();
|
||||
const parsed = formDataSchema.safeParse({
|
||||
image: formData.get("image"),
|
||||
});
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { image } = parsed.data;
|
||||
|
||||
// If there is no image, set the profile picture to the guest image
|
||||
if (!image) {
|
||||
await prisma.user.update({
|
||||
where: { id: Number(session.user?.id) },
|
||||
data: { image: `/guest.png`, imageUpdatedAt: new Date() },
|
||||
});
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
}
|
||||
|
||||
// Validate image contents
|
||||
const imageValidation = await validateImage(image);
|
||||
if (!imageValidation.valid) return rateLimit.sendResponse({ error: imageValidation.error }, imageValidation.status ?? 400);
|
||||
|
||||
// Ensure directories exist
|
||||
await fs.mkdir(uploadsDirectory, { recursive: true });
|
||||
|
||||
try {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const pngBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).png({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(uploadsDirectory, `${session.user?.id}.png`);
|
||||
|
||||
await fs.writeFile(fileLocation, pngBuffer);
|
||||
} catch (error) {
|
||||
console.error("Error uploading profile picture:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to store profile picture" }, 500);
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: Number(session.user?.id) },
|
||||
data: { image: `/profile/${session.user?.id}/picture`, imageUpdatedAt: new Date() },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update profile picture:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to update profile picture" }, 500);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
}
|
||||
6
backend/src/app/api/auth/signin/[provider]/route.ts
Normal file
6
backend/src/app/api/auth/signin/[provider]/route.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { type NextRequest } from "next/server";
|
||||
import { signIn } from "@/lib/auth";
|
||||
|
||||
export async function GET(req: NextRequest, { params }: { params: Promise<{ provider: string }> }) {
|
||||
return signIn((await params).provider);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue