Files
ef_front/app/api/contents/update/route.ts

175 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-09-09 00:15:08 +00:00
export const runtime = 'nodejs'
import { NextResponse } from 'next/server'
import { PrismaClient } from '@/app/generated/prisma'
import { auth } from '@/auth'
import fs from 'fs/promises'
import path from 'path'
import { parse } from 'csv-parse/sync'
function parseKoreanDateToISO(dateStr: string): string | null {
// 입력 예: '2025. 9. 1.' → ISO YYYY-MM-DDT00:00:00.000Z (로컬 기준 단순화)
const m = dateStr?.match(/^(\d{4})\.\s*(\d{1,2})\.\s*(\d{1,2})\.?$/);
if (!m) return null;
const yyyy = Number(m[1]);
const mm = Number(m[2]);
const dd = Number(m[3]);
// UTC 자정으로 맞춤
const d = new Date(Date.UTC(yyyy, mm - 1, dd, 0, 0, 0));
return d.toISOString();
}
function toInt(v: any): number {
if (v == null) return 0;
if (typeof v === 'number') return Math.floor(v);
const s = String(v).replace(/,/g, '').trim();
const n = Number(s);
return Number.isFinite(n) ? Math.floor(n) : 0;
}
export async function GET(request: Request) {
try {
2025-10-15 10:31:03 +00:00
const urlObj = new URL(request.url)
const hostHeader = (request.headers.get('host') || '').toLowerCase()
const isLocal = hostHeader.includes('localhost') || hostHeader.includes('127.0.0.1') || urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1'
if (!isLocal) {
// 세션 사용자 확인 (핸들 매핑용)
const session = await auth();
if (!session?.user?.email) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
2025-09-09 00:15:08 +00:00
}
const { searchParams } = new URL(request.url)
const dateParam = searchParams.get('date') || 'latest'
let rows: any[] = []
let dateStr: string | undefined = undefined
if (dateParam === 'files') {
// Read from local CSV and use fixed date 250831
const filePath = path.join(process.cwd(), 'datas', 'to0831.csv')
const csv = await fs.readFile(filePath, 'utf-8')
rows = parse(csv, {
columns: true,
skip_empty_lines: true,
bom: true,
relax_column_count: true,
trim: true,
}) as any[]
dateStr = '250831'
} else {
const upstream = await fetch(`http://localhost:9556/data?date=${encodeURIComponent(dateParam)}`, {
method: 'GET',
})
const text = await upstream.text()
let data: any = null
try {
data = text ? JSON.parse(text) : null
} catch {
data = { message: text }
}
rows = Array.isArray(data?.data) ? data.data : []
dateStr = data?.date
if (!dateStr) {
return NextResponse.json({ error: 'invalid upstream payload' }, { status: 502 })
}
}
// 유효성 체크
if (!dateStr || rows.length === 0) {
return NextResponse.json({ error: 'invalid upstream payload' }, { status: 502 })
}
if (rows.length <= 1) {
return NextResponse.json({ success: true, message: 'no rows to import', date: dateStr })
}
// Convert date string: supports 'YYYY. M. D.' or 'YYMMDD'
const parseYYMMDD = (s: string): string | null => {
const m = s.match(/^(\d{2})(\d{2})(\d{2})$/)
if (!m) return null
const yyyy = 2000 + Number(m[1])
const mm = Number(m[2])
const dd = Number(m[3])
return new Date(Date.UTC(yyyy, mm - 1, dd, 0, 0, 0)).toISOString()
}
const isoDate = parseKoreanDateToISO(dateStr) || parseYYMMDD(dateStr) || new Date().toISOString();
const prisma = new PrismaClient();
// 날짜를 하루 단위(UTC 자정)로 고정해 동일 날짜는 같은 값으로 취급
const dayStart = new Date(isoDate);
dayStart.setUTCHours(0, 0, 0, 0);
const nextDay = new Date(dayStart.getTime() + 24 * 60 * 60 * 1000);
2025-09-09 00:15:08 +00:00
try {
// handle 연결은 비워둠 (요청에 따라 핸들 매핑 생략)
let upserted = 0;
// 2번째 행부터 반영
for (let i = 1; i < rows.length; i++) {
const row = rows[i] || {};
const contentId: string | undefined = row['콘텐츠'] ?? row['contentId'] ?? row['콘텐츠 ID'];
const subject: string = row['동영상 제목'] ?? row['제목'] ?? row['title'] ?? row['동영상'] ?? `content-${i}`;
const publishAtRaw: string | undefined = row['동영상 게시 시간'] ?? row['게시 시간'] ?? row['publishedAt'];
const publishAtDate = publishAtRaw ? new Date(publishAtRaw) : new Date(isoDate);
// Content upsert: id=콘텐츠, subject=동영상 제목, pubDate=동영상 게시 시간
const upsertedContent = await prisma.content.upsert({
where: { id: String(contentId ?? `content-${i}`) },
update: {
subject,
pubDate: publishAtDate,
},
create: {
id: String(contentId ?? `content-${i}`),
subject,
pubDate: publishAtDate,
// handle 연결 없음
},
select: { id: true },
});
const views = toInt(row['조회수']);
const validViews = toInt(row['유효 조회수']);
const premiumViews = toInt(row['YouTube Premium 조회수']);
const watchTime = toInt(row['시청 시간(단위: 시간)']);
// 같은 날짜(하루 단위)에 이미 존재하면 update, 없으면 create
const existing = await prisma.contentDayView.findFirst({
where: {
2025-09-09 00:15:08 +00:00
contentId: upsertedContent.id,
date: { gte: dayStart, lt: nextDay },
2025-09-09 00:15:08 +00:00
},
select: { id: true },
2025-09-09 00:15:08 +00:00
});
if (existing) {
await prisma.contentDayView.update({
where: { id: existing.id },
data: { views, validViews, premiumViews, watchTime },
});
} else {
await prisma.contentDayView.create({
data: {
contentId: upsertedContent.id,
date: dayStart,
views,
validViews,
premiumViews,
watchTime,
},
});
}
2025-09-09 00:15:08 +00:00
upserted += 1;
}
return NextResponse.json({ success: true, date: dateStr, upserted, data: rows })
} finally {
try { await (prisma as any).$disconnect() } catch {}
}
} catch (e) {
console.error('contents/update upstream error:', e)
return NextResponse.json({ error: '업스트림 요청 실패' }, { status: 502 })
}
}