Initial commit

This commit is contained in:
root
2026-01-26 08:34:48 +01:00
commit 387314581f
216 changed files with 81204 additions and 0 deletions
@@ -0,0 +1,53 @@
/**
* Account NSFW Setting API
*
* POST: Mark/unmark your account as NSFW (content creator setting)
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, users } from '@/db';
import { eq } from 'drizzle-orm';
import { requireAuth } from '@/lib/auth';
import { z } from 'zod';
const updateSchema = z.object({
isNsfw: z.boolean(),
});
/**
* POST /api/settings/account-nsfw
*
* Mark your account as producing NSFW content.
* All your posts will be treated as NSFW.
*/
export async function POST(request: NextRequest) {
try {
const user = await requireAuth();
const body = await request.json();
const { isNsfw } = updateSchema.parse(body);
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 500 });
}
await db.update(users)
.set({
isNsfw,
updatedAt: new Date(),
})
.where(eq(users.id, user.id));
return NextResponse.json({
success: true,
isNsfw,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 });
}
if (error instanceof Error && error.message === 'Authentication required') {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
}
return NextResponse.json({ error: 'Failed to update settings' }, { status: 500 });
}
}
@@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/db';
import { blocks, users } from '@/db/schema';
import { eq, and } from 'drizzle-orm';
import { requireAuth } from '@/lib/auth';
// GET - List blocked users
export async function GET() {
try {
const currentUser = await requireAuth();
const blocked = await db.query.blocks.findMany({
where: eq(blocks.userId, currentUser.id),
with: {
blockedUser: true,
},
orderBy: (t, { desc }) => [desc(t.createdAt)],
});
return NextResponse.json({
blockedUsers: blocked.map(b => ({
id: b.blockedUser.id,
handle: b.blockedUser.handle,
displayName: b.blockedUser.displayName,
avatarUrl: b.blockedUser.avatarUrl,
blockedAt: b.createdAt.toISOString(),
})),
});
} catch (error) {
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.error('Get blocked users error:', error);
return NextResponse.json({ error: 'Failed to get blocked users' }, { status: 500 });
}
}
// DELETE - Unblock a user by ID
export async function DELETE(req: NextRequest) {
try {
const currentUser = await requireAuth();
const { searchParams } = new URL(req.url);
const userId = searchParams.get('userId');
if (!userId) {
return NextResponse.json({ error: 'User ID is required' }, { status: 400 });
}
await db.delete(blocks).where(
and(
eq(blocks.userId, currentUser.id),
eq(blocks.blockedUserId, userId)
)
);
return NextResponse.json({ unblocked: true });
} catch (error) {
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.error('Unblock user error:', error);
return NextResponse.json({ error: 'Failed to unblock user' }, { status: 500 });
}
}
+99
View File
@@ -0,0 +1,99 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/db';
import { mutedNodes } from '@/db/schema';
import { eq, and } from 'drizzle-orm';
import { requireAuth } from '@/lib/auth';
// GET - List muted nodes
export async function GET() {
try {
const currentUser = await requireAuth();
const muted = await db.query.mutedNodes.findMany({
where: eq(mutedNodes.userId, currentUser.id),
orderBy: (t, { desc }) => [desc(t.createdAt)],
});
return NextResponse.json({
mutedNodes: muted.map(m => ({
domain: m.nodeDomain,
mutedAt: m.createdAt.toISOString(),
})),
});
} catch (error) {
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.error('Get muted nodes error:', error);
return NextResponse.json({ error: 'Failed to get muted nodes' }, { status: 500 });
}
}
// POST - Mute a node
export async function POST(req: NextRequest) {
try {
const currentUser = await requireAuth();
const { domain } = await req.json();
if (!domain || typeof domain !== 'string') {
return NextResponse.json({ error: 'Domain is required' }, { status: 400 });
}
const normalizedDomain = domain.toLowerCase().trim();
// Check if already muted
const existing = await db.query.mutedNodes.findFirst({
where: and(
eq(mutedNodes.userId, currentUser.id),
eq(mutedNodes.nodeDomain, normalizedDomain)
),
});
if (existing) {
return NextResponse.json({ muted: true, domain: normalizedDomain });
}
await db.insert(mutedNodes).values({
userId: currentUser.id,
nodeDomain: normalizedDomain,
});
return NextResponse.json({ muted: true, domain: normalizedDomain });
} catch (error) {
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.error('Mute node error:', error);
return NextResponse.json({ error: 'Failed to mute node' }, { status: 500 });
}
}
// DELETE - Unmute a node
export async function DELETE(req: NextRequest) {
try {
const currentUser = await requireAuth();
const { searchParams } = new URL(req.url);
const domain = searchParams.get('domain');
if (!domain) {
return NextResponse.json({ error: 'Domain is required' }, { status: 400 });
}
const normalizedDomain = domain.toLowerCase().trim();
await db.delete(mutedNodes).where(
and(
eq(mutedNodes.userId, currentUser.id),
eq(mutedNodes.nodeDomain, normalizedDomain)
)
);
return NextResponse.json({ muted: false, domain: normalizedDomain });
} catch (error) {
if (error instanceof Error && error.message === 'Unauthorized') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
console.error('Unmute node error:', error);
return NextResponse.json({ error: 'Failed to unmute node' }, { status: 500 });
}
}
+104
View File
@@ -0,0 +1,104 @@
/**
* NSFW Settings API
*
* GET: Get current user's NSFW settings
* POST: Update NSFW settings (requires age verification for enabling)
*/
import { NextRequest, NextResponse } from 'next/server';
import { db, users } from '@/db';
import { eq } from 'drizzle-orm';
import { requireAuth } from '@/lib/auth';
import { z } from 'zod';
const updateSchema = z.object({
nsfwEnabled: z.boolean(),
confirmAge: z.boolean().optional(), // Must be true when enabling NSFW
});
/**
* GET /api/settings/nsfw
*
* Returns current user's NSFW settings
*/
export async function GET() {
try {
const user = await requireAuth();
return NextResponse.json({
nsfwEnabled: user.nsfwEnabled,
ageVerifiedAt: user.ageVerifiedAt?.toISOString() || null,
isNsfw: user.isNsfw, // Whether their account is marked NSFW
});
} catch (error) {
if (error instanceof Error && error.message === 'Authentication required') {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
}
return NextResponse.json({ error: 'Failed to get settings' }, { status: 500 });
}
}
/**
* POST /api/settings/nsfw
*
* Update NSFW settings. Enabling requires age confirmation.
*/
export async function POST(request: NextRequest) {
try {
const user = await requireAuth();
const body = await request.json();
const { nsfwEnabled, confirmAge } = updateSchema.parse(body);
if (!db) {
return NextResponse.json({ error: 'Database not available' }, { status: 500 });
}
// If enabling NSFW and not already verified, require age confirmation
if (nsfwEnabled && !user.ageVerifiedAt) {
if (!confirmAge) {
return NextResponse.json({
error: 'Age verification required',
requiresAgeConfirmation: true,
message: 'You must confirm you are 18 or older to view NSFW content',
}, { status: 400 });
}
// Record age verification
await db.update(users)
.set({
nsfwEnabled: true,
ageVerifiedAt: new Date(),
updatedAt: new Date(),
})
.where(eq(users.id, user.id));
return NextResponse.json({
success: true,
nsfwEnabled: true,
ageVerifiedAt: new Date().toISOString(),
});
}
// Update preference (already verified or disabling)
await db.update(users)
.set({
nsfwEnabled,
updatedAt: new Date(),
})
.where(eq(users.id, user.id));
return NextResponse.json({
success: true,
nsfwEnabled,
ageVerifiedAt: user.ageVerifiedAt?.toISOString() || null,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ error: 'Invalid input', details: error.issues }, { status: 400 });
}
if (error instanceof Error && error.message === 'Authentication required') {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
}
return NextResponse.json({ error: 'Failed to update settings' }, { status: 500 });
}
}