Close remaining web gaps: rich profile, login history, lightbox/slider, flash client, admin chatlog/DJ/chart/WYSIWYG, niche API
- Rich profile (/u/[username]): wallet (credits/duckets/diamonds), friends grid (messenger_friendships), and owned rooms sections. - Login history: new website_login_logs table (model + migration 0007), recorded on every successful sign-in (ip + user-agent), surfaced on a new /settings/sessions page (with failed-attempt list from failed_logins). - Photos lightbox + home article slider (client components, no Swiper dep). - /client/flash launcher (SSO ticket like the Nitro page). - Admin: private chatlogs section in /admin/logs, /admin/radio/moderation (shout moderation), a "users by rank" inline bar chart on the dashboard, and a TinyMCE rich-text editor on the article admin forms. - Niche API: /api/values/[id], /api/guilds(+/[id]), /api/radio/auto-play. Verified live (prod, amx_test): login recorded → /settings/sessions shows it with device; profile renders wallet/friends/rooms; dashboard chart + private-chat logs + /client/flash + /api/guilds all OK. Reverted test data. tsc 0, vitest 49/49, next build 0.
This commit is contained in:
1 parent
f7b3845131
commit
4dfe698009
25 files changed
+1463
-64
No files matched your search
@@ -0,0 +1,55 @@
|
||||
// Public REST: a single guild (guilds) with its member count
|
||||
// (guilds_members). AtomCMS JSON API parity. Returns { error } (404) when the
|
||||
// id is unknown. The owner is the user_id column (accessor userId), surfaced
|
||||
// here as ownerId.
|
||||
import { apiJson } from "@/lib/api";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params;
|
||||
|
||||
if (!/^\d+$/.test(id)) {
|
||||
return apiJson({ error: "Guild not found" }, { status: 404 });
|
||||
}
|
||||
const guildId = Number(id);
|
||||
|
||||
try {
|
||||
const guild = await prisma.guilds.findUnique({
|
||||
where: { id: guildId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
userId: true,
|
||||
roomId: true,
|
||||
badge: true,
|
||||
dateCreated: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!guild) {
|
||||
return apiJson({ error: "Guild not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Member count is a separate guarded query (no relation is modelled).
|
||||
let memberCount = 0;
|
||||
try {
|
||||
memberCount = await prisma.guildsMembers.count({
|
||||
where: { guildId: guild.id },
|
||||
});
|
||||
} catch {
|
||||
memberCount = 0;
|
||||
}
|
||||
|
||||
const { userId, ...rest } = guild;
|
||||
return apiJson({ data: { ...rest, ownerId: userId, memberCount } });
|
||||
} catch {
|
||||
// DB unavailable — treat as not found rather than a 500.
|
||||
return apiJson({ error: "Guild not found" }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Public REST: hotel guilds (guilds). AtomCMS JSON API parity — read-only,
|
||||
// paginated list ordered newest first (by id), exposing the basic public
|
||||
// fields. The owner is stored as the user_id column (accessor userId), surfaced
|
||||
// here as ownerId for API clarity.
|
||||
import { apiJson, pagination } from "@/lib/api";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const sp = new URL(req.url).searchParams;
|
||||
const { page, perPage, skip, take } = pagination(sp);
|
||||
|
||||
try {
|
||||
const [total, rows] = await Promise.all([
|
||||
prisma.guilds.count(),
|
||||
prisma.guilds.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true,
|
||||
userId: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
// Rename userId → ownerId for the public payload.
|
||||
const data = rows.map(({ userId, ...rest }) => ({
|
||||
...rest,
|
||||
ownerId: userId,
|
||||
}));
|
||||
|
||||
return apiJson({
|
||||
data,
|
||||
meta: { page, perPage, total, lastPage: Math.max(1, Math.ceil(total / perPage)) },
|
||||
});
|
||||
} catch {
|
||||
// DB unreachable — never 500; return an empty, well-formed payload.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user