fromis_9/backend/src/routes/admin/youtube.js

165 lines
4.7 KiB
JavaScript
Raw Normal View History

import { fetchVideoInfo } from '../../services/youtube/api.js';
import { addOrUpdateSchedule } from '../../services/meilisearch/index.js';
const YOUTUBE_CATEGORY_ID = 2;
/**
* YouTube 관련 관리자 라우트
*/
export default async function youtubeRoutes(fastify) {
const { db, meilisearch } = fastify;
/**
* GET /api/admin/youtube/video-info
* YouTube 영상 정보 조회
*/
fastify.get('/video-info', {
schema: {
tags: ['admin/youtube'],
summary: 'YouTube 영상 정보 조회',
security: [{ bearerAuth: [] }],
querystring: {
type: 'object',
properties: {
url: { type: 'string', description: 'YouTube URL' },
},
required: ['url'],
},
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { url } = request.query;
// YouTube URL에서 video ID 추출
const videoId = extractVideoId(url);
if (!videoId) {
return reply.code(400).send({ error: '유효하지 않은 YouTube URL입니다.' });
}
try {
const video = await fetchVideoInfo(videoId);
if (!video) {
return reply.code(404).send({ error: '영상을 찾을 수 없습니다.' });
}
return {
videoId: video.videoId,
title: video.title,
channelId: video.channelId,
channelName: video.channelTitle,
publishedAt: video.publishedAt,
date: video.date,
time: video.time,
videoType: video.videoType,
videoUrl: video.videoUrl,
};
} catch (err) {
fastify.log.error(`YouTube 영상 조회 오류: ${err.message}`);
return reply.code(500).send({ error: err.message });
}
});
/**
* POST /api/admin/youtube/schedule
* YouTube 일정 저장
*/
fastify.post('/schedule', {
schema: {
tags: ['admin/youtube'],
summary: 'YouTube 일정 저장',
security: [{ bearerAuth: [] }],
body: {
type: 'object',
properties: {
videoId: { type: 'string' },
title: { type: 'string' },
channelId: { type: 'string' },
channelName: { type: 'string' },
date: { type: 'string' },
time: { type: 'string' },
videoType: { type: 'string' },
},
required: ['videoId', 'title', 'date'],
},
},
preHandler: [fastify.authenticate],
}, async (request, reply) => {
const { videoId, title, channelId, channelName, date, time, videoType } = request.body;
try {
// 중복 체크
const [existing] = await db.query(
'SELECT id FROM schedule_youtube WHERE video_id = ?',
[videoId]
);
if (existing.length > 0) {
return reply.code(409).send({ error: '이미 등록된 영상입니다.' });
}
// schedules 테이블에 저장
const [result] = await db.query(
'INSERT INTO schedules (category_id, title, date, time) VALUES (?, ?, ?, ?)',
[YOUTUBE_CATEGORY_ID, title, date, time || null]
);
const scheduleId = result.insertId;
// schedule_youtube 테이블에 저장
await db.query(
'INSERT INTO schedule_youtube (schedule_id, video_id, video_type, channel_id, channel_name) VALUES (?, ?, ?, ?, ?)',
[scheduleId, videoId, videoType || 'video', channelId, channelName]
);
// Meilisearch 동기화
const [categoryRows] = await db.query(
'SELECT name, color FROM schedule_categories WHERE id = ?',
[YOUTUBE_CATEGORY_ID]
);
const category = categoryRows[0] || {};
await addOrUpdateSchedule(meilisearch, {
id: scheduleId,
title,
date,
time: time || '',
category_id: YOUTUBE_CATEGORY_ID,
category_name: category.name || '',
category_color: category.color || '',
source_name: channelName || '',
});
return { success: true, scheduleId };
} catch (err) {
fastify.log.error(`YouTube 일정 저장 오류: ${err.message}`);
return reply.code(500).send({ error: err.message });
}
});
}
/**
* YouTube URL에서 video ID 추출
*/
function extractVideoId(url) {
if (!url) return null;
const patterns = [
// https://www.youtube.com/watch?v=VIDEO_ID
/(?:youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/,
// https://youtu.be/VIDEO_ID
/(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/,
// https://www.youtube.com/shorts/VIDEO_ID
/(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/,
// https://www.youtube.com/embed/VIDEO_ID
/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/,
// https://www.youtube.com/v/VIDEO_ID
/(?:youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/,
];
for (const pattern of patterns) {
const match = url.match(pattern);
if (match) {
return match[1];
}
}
return null;
}