middle 2
This commit is contained in:
152
app/api/contents/update/route.ts
Normal file
152
app/api/contents/update/route.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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 {
|
||||
// 세션 사용자 확인 (핸들 매핑용)
|
||||
const session = await auth();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
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();
|
||||
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['시청 시간(단위: 시간)']);
|
||||
|
||||
// contentId + date 복합 유니크로 upsert
|
||||
await prisma.contentDayView.upsert({
|
||||
where: { contentId_date: { contentId: upsertedContent.id, date: new Date(isoDate) } },
|
||||
update: { views, validViews, premiumViews, watchTime },
|
||||
create: {
|
||||
contentId: upsertedContent.id,
|
||||
date: new Date(isoDate),
|
||||
views,
|
||||
validViews,
|
||||
premiumViews,
|
||||
watchTime,
|
||||
},
|
||||
});
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user