first commit

This commit is contained in:
2025-09-07 22:57:43 +00:00
commit 3bd542adbf
122 changed files with 45056 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
import { NextResponse } from 'next/server';
import { PrismaClient } from '@/app/generated/prisma';
export async function POST(request: Request) {
const prisma = new PrismaClient();
try {
const body = await request.json();
const id: string | undefined = body?.id;
const approve: boolean | undefined = body?.approve;
if (!id || typeof approve !== 'boolean') {
return NextResponse.json({ error: 'id와 approve(boolean)가 필요합니다' }, { status: 400 });
}
const updated = await prisma.userHandle.update({
where: { id },
data: { isApproved: approve },
select: { id: true, isApproved: true },
});
return NextResponse.json({ ok: true, item: updated });
} catch (e) {
console.error('POST /api/admin/user_handles/approve 오류:', e);
return NextResponse.json({ error: '업데이트 실패' }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,20 @@
import { NextResponse } from 'next/server';
import { PrismaClient } from '@/app/generated/prisma';
export async function GET() {
const prisma = new PrismaClient();
try {
const handles = await prisma.userHandle.findMany({
orderBy: { createtime: 'desc' },
select: { id: true, email: true, handle: true, isApproved: true, createtime: true, icon: true }
});
return NextResponse.json({ items: handles });
} catch (e) {
console.error('admin user_handles 오류:', e);
return NextResponse.json({ error: '조회 실패' }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,2 @@
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers

View File

@@ -0,0 +1,67 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
import { PrismaClient, Prisma } from '@/app/generated/prisma';
import { randomBytes } from 'crypto';
export async function GET(request: Request) {
const session = await auth();
if (!session) {
console.log(session)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const { searchParams } = new URL(request.url);
const handle = searchParams.get('handle');
if (!handle) {
return NextResponse.json({ error: '핸들이 필요합니다' }, { status: 400 });
}
const prisma = new PrismaClient();
// 안전한 난수 기반 32자 코드 (충돌 확률 극히 낮음)
const randomcode = randomBytes(16).toString('hex');
// 중복 여부 확인 (email + handle 기준)
const exists = await prisma.registerChannel.findFirst({
where: {
email: session.user?.email as string,
handle: handle,
},
select: { id: true, randomcode: true },
});
if (exists) {
return NextResponse.json(
{ error: '이미 등록된 요청입니다', randomcode: exists.randomcode },
{ status: 409 }
);
}
// DB에 저장
const created = await prisma.registerChannel.create({
data: {
email: session.user?.email as string,
handle: handle,
randomcode: randomcode
}
});
return NextResponse.json({ message: '성공', code: handle, randomcode: created.randomcode, id: created.id }, { status: 200 });
} catch (error: unknown) {
console.error('에러 발생:', error);
// Prisma 에러 코드별 분기
if (error instanceof Prisma.PrismaClientKnownRequestError) {
// 고유 제약조건 위반 등
if (error.code === 'P2002') {
return NextResponse.json({ error: '중복된 값으로 저장할 수 없습니다' }, { status: 409 });
}
return NextResponse.json({ error: '요청이 올바르지 않습니다', code: error.code }, { status: 400 });
}
if (error instanceof Prisma.PrismaClientValidationError) {
return NextResponse.json({ error: '유효하지 않은 데이터입니다' }, { status: 400 });
}
return NextResponse.json({ error: '서버 에러가 발생했습니다' }, { status: 500 });
}
}

View File

@@ -0,0 +1,14 @@
// import { PrismaClient } from '../../generated/prisma/client';
// const prisma = new PrismaClient();
// export async function GET() {
// const publishers = await prisma.content.findMany({
// distinct: ['publisher'],
// select: {
// publisher: true,
// },
// });
// const publisherList = publishers.map(p => p.publisher);
// return Response.json(publisherList);
// }

View File

@@ -0,0 +1,8 @@
import { PrismaClient } from '../../generated/prisma/client';
const prisma = new PrismaClient();
export async function GET() {
const contents = await prisma.content.findMany();
return Response.json(contents);
}

View File

@@ -0,0 +1,38 @@
import { NextResponse } from 'next/server';
import { PrismaClient } from '@/app/generated/prisma';
const prisma = new PrismaClient();
export async function GET() {
try {
const row = await prisma.costPerView.findUnique({ where: { id: 1 } });
return NextResponse.json({ value: row?.costPerView ?? null });
} catch (e) {
console.error('GET /api/cost_per_view 오류:', e);
return NextResponse.json({ error: '조회 실패' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const body = await request.json();
const valueRaw = body?.value;
const value = typeof valueRaw === 'string' ? parseFloat(valueRaw) : valueRaw;
if (typeof value !== 'number' || Number.isNaN(value)) {
return NextResponse.json({ error: '유효한 숫자 값을 제공하세요' }, { status: 400 });
}
const saved = await prisma.costPerView.upsert({
where: { id: 1 },
update: { costPerView: value },
create: { id: 1, costPerView: value },
});
return NextResponse.json({ ok: true, value: saved.costPerView });
} catch (e) {
console.error('POST /api/cost_per_view 오류:', e);
return NextResponse.json({ error: '저장 실패' }, { status: 500 });
}
}

View File

@@ -0,0 +1,41 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
import { PrismaClient } from '@/app/generated/prisma';
export async function GET() {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const email = session.user?.email as string | undefined;
if (!email) {
return NextResponse.json({ error: '세션 이메일을 찾을 수 없습니다' }, { status: 400 });
}
const prisma = new PrismaClient();
try {
if (email === 'wsx204@naver.com') {
const allHandles = await prisma.userHandle.findMany({
orderBy: { createtime: 'desc' },
select: { id: true, email: true, handle: true, isApproved: true, createtime: true, icon: true }
});
return NextResponse.json({ items: allHandles });
}
else{
const handles = await prisma.userHandle.findMany({
where: { email },
orderBy: { createtime: 'desc' },
select: { id: true, email: true, handle: true, isApproved: true, createtime: true, icon: true }
});
return NextResponse.json({ items: handles });
}
} catch (e) {
console.error('list_channel 오류:', e);
return NextResponse.json({ error: '조회 실패' }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,115 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
import { PrismaClient } from '@/app/generated/prisma';
const prisma = new PrismaClient();
function parseDateOr(value: string | null, fallback: Date): Date {
if (!value) return fallback;
const d = new Date(value);
return isNaN(d.getTime()) ? fallback : d;
}
export async function GET(request: Request) {
try {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const email = session.user?.email as string | undefined;
if (!email) return NextResponse.json({ error: '세션 이메일을 찾을 수 없습니다' }, { status: 400 });
const { searchParams } = new URL(request.url);
const endDefault = new Date();
const startDefault = new Date(endDefault.getTime() - 30 * 24 * 60 * 60 * 1000);
const startParam = searchParams.get('start');
const endParam = searchParams.get('end');
const startDate = startParam ? new Date(startParam) : startDefault;
const endDate = endParam ? new Date(endParam) : endDefault;
// 날짜 경계 보정: 하루 전체 포함 (UTC 기준)
const startInclusive = new Date(startDate);
startInclusive.setUTCHours(0, 0, 0, 0);
const endInclusive = new Date(endDate);
endInclusive.setUTCHours(23, 59, 59, 999);
// 안전가드
if (endInclusive < startInclusive) endInclusive.setTime(startInclusive.getTime());
console.log('startDate', startDate.toISOString());
console.log('endDate', endDate.toISOString());
// 1) 내 핸들
const handles = await prisma.userHandle.findMany({
where: { email },
select: { id: true, handle: true, icon: true }
});
if (handles.length === 0) return NextResponse.json({ items: [] });
const handleStrs = handles.map(h => h.handle);
const handleByStr = new Map(handles.map(h => [h.handle, h] as const));
// 2) 매핑된 콘텐츠
const links = await prisma.contentHandle.findMany({
where: { handle: { in: handleStrs } },
select: { contentId: true, handle: true }
});
if (links.length === 0) return NextResponse.json({ items: [] });
const contentIds = links.map(l => l.contentId);
const normalizeId = (s: string) => (s ?? '').trim();
const contentIdsNormalized = Array.from(new Set(contentIds.map(normalizeId)));
const idsForQuery = Array.from(new Set([...contentIds, ...contentIdsNormalized]));
const handleByContentId = new Map(links.map(l => [l.contentId, l.handle] as const));
// 3) 콘텐츠 본문
const contents = await prisma.content.findMany({
where: { id: { in: contentIds } },
select: {
id: true, subject: true, pubDate: true,
views: true, premiumViews: true, watchTime: true
}
});
// 4) 기간 합계 유효조회수 (findMany로 가져와 JS에서 합산 - groupBy 일부 환경 이슈 회피)
const viewRows = await prisma.viewPerDay.findMany({
where: {
contented: { in: idsForQuery },
date: { gte: startInclusive, lte: endInclusive }
},
select: { contented: true, validViewDay: true }
});
const validSumById = new Map<string, number>();
for (const r of viewRows) {
const key = normalizeId(r.contented);
validSumById.set(key, (validSumById.get(key) ?? 0) + (r.validViewDay ?? 0));
}
// 5) 비용 단가
const cpvRow = await prisma.costPerView.findUnique({ where: { id: 1 } });
const cpv = cpvRow?.costPerView ?? 0;
const items = contents.map(c => {
const handle = handleByContentId.get(c.id) ?? '';
const handleObj = handleByStr.get(handle);
const validViews = validSumById.get(normalizeId(c.id)) ?? 0;
const expectedRevenue = validViews * cpv;
return {
id: c.id,
subject: c.subject,
pubDate: c.pubDate,
views: c.views,
premiumViews: c.premiumViews,
watchTime: c.watchTime,
handle,
handleId: handleObj?.id ?? null,
icon: handleObj?.icon ?? '',
validViews,
expectedRevenue,
};
});
return NextResponse.json({ items, cpv, start: startInclusive.toISOString(), end: endInclusive.toISOString() });
} catch (e) {
console.error('my_contents 오류:', e);
return NextResponse.json({ error: '조회 실패' }, { status: 500 });
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,19 @@
import { PrismaClient } from '../../../generated/prisma/client';
const prisma = new PrismaClient();
export async function GET() {
const notices = await prisma.noticeBoard.findMany({
where: {
isDeleted: false,
},
select: {
id: true,
title: true,
pubDate: true,
tag: true,
// 필요한 다른 필드들도 추가
},
});
return Response.json(notices);
}

59
app/api/notice/route.ts Normal file
View File

@@ -0,0 +1,59 @@
import { PrismaClient } from '../../generated/prisma/client';
const prisma = new PrismaClient();
export async function GET() {
const notices = await prisma.noticeBoard.findMany({
where: {
isDeleted: false,
},
select: {
id: true,
title: true,
pubDate: true,
tag: true,
content: true,
// 필요한 다른 필드들도 추가
},
});
return Response.json(notices);
}
export async function POST(request: Request) {
const { title, content, tag } = await request.json();
const newNotice = await prisma.noticeBoard.create({
data: {
title,
content,
tag,
pubDate: new Date(),
},
});
return Response.json(newNotice, { status: 201 });
}
export async function PUT(request: Request) {
const { id, ...updateData } = await request.json();
const updatedNotice = await prisma.noticeBoard.update({
where: { id },
data: updateData,
});
return Response.json(updatedNotice);
}
export async function DELETE(request: Request) {
const url = new URL(request.url);
const id = url.searchParams.get('id');
if (!id) {
return new Response('ID is required', { status: 400 });
}
const deletedNotice = await prisma.noticeBoard.update({
where: { id },
data: { isDeleted: true },
});
return new Response(JSON.stringify(deletedNotice), { status: 200 });
}

View File

@@ -0,0 +1,46 @@
import { NextResponse } from 'next/server';
import { auth } from '@/auth';
export async function GET(request: Request) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const { searchParams } = new URL(request.url);
const handle = searchParams.get('handle');
const email = session.user?.email as string | undefined;
if (!handle) {
return NextResponse.json({ error: '핸들이 필요합니다' }, { status: 400 });
}
if (!email) {
return NextResponse.json({ error: '세션 이메일을 찾을 수 없습니다' }, { status: 400 });
}
const upstream = await fetch('http://localhost:10001/register_channel', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, handle })
});
let data: any = null;
const text = await upstream.text();
try {
data = text ? JSON.parse(text) : null;
} catch {
data = { message: text };
}
return NextResponse.json(data ?? {}, { status: upstream.status });
} catch (error) {
console.error('register_channel 프록시 오류:', error);
return NextResponse.json({ error: '업스트림 요청 실패' }, { status: 502 });
}
}

23
app/api/upload/route.ts Normal file
View File

@@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import { mkdir, writeFile } from "fs/promises";
import path from "path";
import crypto from "crypto";
export const runtime = "nodejs"; // 파일 시스템 사용
export async function POST(req: Request) {
const form = await req.formData();
const file = form.get("file") as File | null;
if (!file) return NextResponse.json({ ok: false, error: "NO_FILE" }, { status: 400 });
const bytes = Buffer.from(await file.arrayBuffer());
const ext = (file.name.split(".").pop() || "png").toLowerCase();
const name = `${crypto.randomUUID()}.${ext}`;
const dir = path.join(process.cwd(), "public", "uploads");
await mkdir(dir, { recursive: true });
await writeFile(path.join(dir, name), bytes);
// 정적 서빙: public/uploads/...
const url = `/uploads/${name}`;
return NextResponse.json({ ok: true, url });
}