first commit
This commit is contained in:
29
app/api/admin/user_handles/approve/route.ts
Normal file
29
app/api/admin/user_handles/approve/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
app/api/admin/user_handles/route.ts
Normal file
20
app/api/admin/user_handles/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
app/api/auth/[...nextauth]/route.js
Normal file
2
app/api/auth/[...nextauth]/route.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import { handlers } from "@/auth" // Referring to the auth.ts we just created
|
||||
export const { GET, POST } = handlers
|
||||
67
app/api/channel_code/route.ts
Normal file
67
app/api/channel_code/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
14
app/api/channels/notuse.ts
Normal file
14
app/api/channels/notuse.ts
Normal 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);
|
||||
// }
|
||||
8
app/api/contents/route.ts
Normal file
8
app/api/contents/route.ts
Normal 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);
|
||||
}
|
||||
38
app/api/cost_per_view/route.ts
Normal file
38
app/api/cost_per_view/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
41
app/api/list_channel/route.ts
Normal file
41
app/api/list_channel/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
115
app/api/my_contents/route.ts
Normal file
115
app/api/my_contents/route.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
app/api/notice/item/route.ts
Normal file
19
app/api/notice/item/route.ts
Normal 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
59
app/api/notice/route.ts
Normal 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 });
|
||||
}
|
||||
46
app/api/register_channel/route.ts
Normal file
46
app/api/register_channel/route.ts
Normal 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
23
app/api/upload/route.ts
Normal 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user