From a777c0ab7377bc46e07145f48eb3473233056da1 Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Thu, 9 Oct 2025 18:47:42 +0900 Subject: [PATCH] =?UTF-8?q?10.5=20=EA=B0=90=EC=82=AC=20=EC=9D=B4=EB=A0=A5/?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EB=82=B4=EC=97=AD/=EC=97=B4=EB=9E=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/logs/page.tsx | 47 +++++++++++++++++++++++++++ src/app/api/admin/audit-logs/route.ts | 14 ++++++++ src/app/api/admin/reports/route.ts | 9 +++++ src/app/api/admin/views/route.ts | 9 +++++ todolist.txt | 2 +- 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/app/admin/logs/page.tsx create mode 100644 src/app/api/admin/audit-logs/route.ts create mode 100644 src/app/api/admin/reports/route.ts create mode 100644 src/app/api/admin/views/route.ts diff --git a/src/app/admin/logs/page.tsx b/src/app/admin/logs/page.tsx new file mode 100644 index 0000000..5b274bd --- /dev/null +++ b/src/app/admin/logs/page.tsx @@ -0,0 +1,47 @@ +"use client"; +import useSWR from "swr"; + +const fetcher = (url: string) => fetch(url).then((r) => r.json()); + +export default function AdminLogsPage() { + const { data: audits } = useSWR<{ logs: any[] }>("/api/admin/audit-logs?limit=100", fetcher); + const { data: reports } = useSWR<{ reports: any[] }>("/api/admin/reports", fetcher); + const { data: views } = useSWR<{ views: any[] }>("/api/admin/views", fetcher); + return ( +
+

감사/신고/열람 로그

+

감사 로그

+ ({ a: x.createdAt, b: x.action, c: x.targetType, d: x.targetId }))} headers={["시각", "행위", "대상", "ID"]} /> +

신고

+
({ a: x.createdAt, b: x.targetType, c: x.reason, d: x.status }))} headers={["시각", "대상", "사유", "상태"]} /> +

열람 로그

+
({ a: x.createdAt, b: x.postId, c: x.userId, d: x.ip }))} headers={["시각", "postId", "userId", "ip"]} /> + + ); +} + +function Table({ headers, rows }: { headers: string[]; rows: { a: any; b: any; c: any; d: any }[] }) { + return ( +
+ + + {headers.map((h) => ( + + ))} + + + + {rows.map((r, i) => ( + + + + + + + ))} + +
{h}
{String(r.a)}{String(r.b)}{String(r.c)}{String(r.d)}
+ ); +} + + diff --git a/src/app/api/admin/audit-logs/route.ts b/src/app/api/admin/audit-logs/route.ts new file mode 100644 index 0000000..446a049 --- /dev/null +++ b/src/app/api/admin/audit-logs/route.ts @@ -0,0 +1,14 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; + +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const limit = Number(searchParams.get("limit") || 100); + const logs = await prisma.auditLog.findMany({ + orderBy: { createdAt: "desc" }, + take: Math.min(limit, 200), + }); + return NextResponse.json({ logs }); +} + + diff --git a/src/app/api/admin/reports/route.ts b/src/app/api/admin/reports/route.ts new file mode 100644 index 0000000..08a23e2 --- /dev/null +++ b/src/app/api/admin/reports/route.ts @@ -0,0 +1,9 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; + +export async function GET() { + const items = await prisma.report.findMany({ orderBy: { createdAt: "desc" }, take: 200 }); + return NextResponse.json({ reports: items }); +} + + diff --git a/src/app/api/admin/views/route.ts b/src/app/api/admin/views/route.ts new file mode 100644 index 0000000..0eb3dcc --- /dev/null +++ b/src/app/api/admin/views/route.ts @@ -0,0 +1,9 @@ +import { NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; + +export async function GET() { + const items = await prisma.postViewLog.findMany({ orderBy: { createdAt: "desc" }, take: 200 }); + return NextResponse.json({ views: items }); +} + + diff --git a/todolist.txt b/todolist.txt index bf79cb3..3dc3fd2 100644 --- a/todolist.txt +++ b/todolist.txt @@ -73,7 +73,7 @@ 10.2 게시판 스키마/설정 관리 UI o 10.3 사용자 검색/정지/권한 변경 o 10.4 공지/배너 등록 및 노출 설정 o -10.5 감사 이력/신고 내역/열람 로그 +10.5 감사 이력/신고 내역/열람 로그 o 10.6 카테고리 유형/설정 관리(일반/특수/승인/레벨/익명/태그) [테스트/품질]