feat: room photo gallery, featured photo, and comments moderation

This commit is contained in:
2026-06-27 19:25:00 -05:00
parent 7cc1877674
commit a11bd3ada8
10 changed files with 583 additions and 69 deletions
+17
View File
@@ -0,0 +1,17 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { requireRole } from '@/lib/auth';
export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
try {
await requireRole('admin');
const commentId = parseInt(params.id, 10);
await db.query('UPDATE room_comments SET deleted_by_admin = TRUE WHERE id = $1', [commentId]);
return NextResponse.json({ ok: true });
} catch (err: any) {
return NextResponse.json(
{ error: err.message || 'Failed to delete comment' },
{ status: err.message === 'Unauthorized' ? 401 : 500 }
);
}
}
+37 -1
View File
@@ -5,7 +5,7 @@ import { db } from '@/lib/db';
export async function GET() {
try {
await requireRole('admin');
const { rows } = await db.query('SELECT id, username, role, created_at FROM users ORDER BY created_at DESC');
const { rows } = await db.query('SELECT id, username, role, comments_disabled, first_name, last_name, created_at FROM users ORDER BY created_at DESC');
return NextResponse.json({ data: rows });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to fetch users' }, { status: err.message === 'Unauthorized' ? 401 : 500 });
@@ -32,3 +32,39 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: err.message || 'Failed to create user' }, { status: err.message === 'Unauthorized' ? 401 : 500 });
}
}
export async function PUT(request: NextRequest) {
try {
await requireRole('admin');
const body = await request.json();
const { id, first_name, last_name, comments_disabled } = body;
const { rows } = await db.query(
'UPDATE users SET first_name = $1, last_name = $2, comments_disabled = $3 WHERE id = $4 RETURNING id, username, role, first_name, last_name, comments_disabled, created_at',
[first_name || null, last_name || null, comments_disabled === true, id]
);
if (rows.length === 0) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
return NextResponse.json({ data: rows[0] });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to update user' }, { status: err.message === 'Unauthorized' ? 401 : 500 });
}
}
export async function DELETE(request: NextRequest) {
try {
await requireRole('admin');
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json({ error: 'User ID required' }, { status: 400 });
}
await db.query('DELETE FROM users WHERE id = $1', [id]);
return NextResponse.json({ ok: true });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to delete user' }, { status: err.message === 'Unauthorized' ? 401 : 500 });
}
}
+15 -1
View File
@@ -1,4 +1,5 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET() {
@@ -6,5 +7,18 @@ export async function GET() {
if (!session) {
return NextResponse.json({ authenticated: false }, { status: 401 });
}
return NextResponse.json({ authenticated: true, role: session.role, username: session.username });
const { rows } = await db.query(
'SELECT id, username, role, first_name, last_name, comments_disabled FROM users WHERE id = $1',
[session.id]
);
const user = rows[0] || {};
return NextResponse.json({
authenticated: true,
role: session.role,
username: session.username,
id: session.id,
first_name: user.first_name || null,
last_name: user.last_name || null,
comments_disabled: user.comments_disabled === true,
});
}
+62
View File
@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { requireAuth, canComment } from '@/lib/auth';
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
const roomId = parseInt(params.id, 10);
const { searchParams } = new URL(request.url);
const photoUrl = searchParams.get('photo_url') || null;
const { rows } = await db.query(
`SELECT id, room_id, photo_url, user_id, first_name, last_initial, text, created_at, deleted_by_admin
FROM room_comments
WHERE room_id = $1 AND ($2::varchar IS NULL OR photo_url = $2) AND deleted_by_admin = FALSE
ORDER BY created_at DESC`,
[roomId, photoUrl]
);
return NextResponse.json({ data: rows });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to fetch comments' }, { status: 500 });
}
}
export async function POST(request: NextRequest, { params }: { params: { id: string } }) {
try {
const session = await requireAuth();
const allowed = await canComment(session.id);
if (!allowed) {
return NextResponse.json({ error: 'Comments are disabled for your account' }, { status: 403 });
}
const roomId = parseInt(params.id, 10);
const body = await request.json();
const { photo_url, text } = body;
if (!text || !text.trim()) {
return NextResponse.json({ error: 'Comment text is required' }, { status: 400 });
}
const { rows } = await db.query(
`SELECT first_name, last_name FROM users WHERE id = $1`,
[session.id]
);
const user = rows[0] || {};
const firstName = user.first_name || session.username || 'Guest';
const lastInitial = user.last_name ? user.last_name.charAt(0).toUpperCase() : null;
const { rows: inserted } = await db.query(
`INSERT INTO room_comments (room_id, photo_url, user_id, first_name, last_initial, text)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
[roomId, photo_url || null, session.id, firstName, lastInitial, text.trim()]
);
return NextResponse.json({ data: inserted[0] });
} catch (err: any) {
return NextResponse.json(
{ error: err.message || 'Failed to post comment' },
{ status: err.message === 'Unauthorized' ? 401 : 500 }
);
}
}
+4 -4
View File
@@ -7,12 +7,12 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
await requireRole('admin');
const id = parseInt(params.id, 10);
const body = await request.json();
const { name, description, price, photos, active, sort_order } = body;
const { name, description, price, photos, active, sort_order, featured_photo } = body;
const { rows } = await db.query(
`UPDATE rooms SET name = $1, description = $2, price = $3, photos = $4, active = $5, sort_order = $6, updated_at = NOW()
WHERE id = $7 RETURNING *`,
[name, description, price, photos || [], active, sort_order || 0, id]
`UPDATE rooms SET name = $1, description = $2, price = $3, photos = $4, active = $5, sort_order = $6, featured_photo = $7, updated_at = NOW()
WHERE id = $8 RETURNING *`,
[name, description, price, photos || [], active, sort_order || 0, featured_photo || null, id]
);
if (rows.length === 0) {
+4 -4
View File
@@ -16,17 +16,17 @@ export async function POST(request: NextRequest) {
try {
await requireRole('admin');
const body = await request.json();
const { name, description, price, photos, sort_order } = body;
const { name, description, price, photos, sort_order, featured_photo } = body;
if (!name || !description || price === undefined) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
const { rows } = await db.query(
`INSERT INTO rooms (name, description, price, photos, sort_order)
VALUES ($1, $2, $3, $4, $5)
`INSERT INTO rooms (name, description, price, photos, sort_order, featured_photo)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *`,
[name, description, price, photos || [], sort_order || 0]
[name, description, price, photos || [], sort_order || 0, featured_photo || null]
);
return NextResponse.json({ data: rows[0] });