feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
import { CATEGORY _IDS } from '../../config/index.js' ;
2026-04-04 19:07:08 +09:00
import { uploadVarietyThumbnail } from '../../services/image.js' ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
import { addOrUpdateSchedule , syncScheduleById } from '../../services/meilisearch/index.js' ;
import { badRequest , notFound , serverError } from '../../utils/error.js' ;
import { logActivity } from '../../utils/log.js' ;
const VARIETY _CATEGORY _ID = CATEGORY _IDS . VARIETY ;
2026-04-05 13:37:52 +09:00
const BROADCASTER _KEY = 'variety:broadcasters' ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
/ * *
* 예능 관련 관리자 라우트
* /
export default async function varietyRoutes ( fastify ) {
2026-04-05 13:37:52 +09:00
const { db , meilisearch , redis } = fastify ;
/ * *
* GET / api / admin / variety / broadcasters
* 자주 사용된 방송사 / 플랫폼 목록 ( 상위 10 개 )
* /
fastify . get ( '/broadcasters' , {
preHandler : [ fastify . authenticate ] ,
} , async ( ) => {
// Redis에 캐시가 있으면 사용
const cached = await redis . get ( BROADCASTER _KEY ) ;
if ( cached ) {
return JSON . parse ( cached ) ;
}
// DB에서 빈도수 조회
const [ rows ] = await db . query (
` SELECT broadcaster, COUNT(*) as cnt
FROM schedule _variety
GROUP BY broadcaster
ORDER BY cnt DESC
LIMIT 10 `
) ;
const broadcasters = rows . map ( r => r . broadcaster ) ;
// Redis 캐시 (1시간)
await redis . setex ( BROADCASTER _KEY , 3600 , JSON . stringify ( broadcasters ) ) ;
return broadcasters ;
} ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
/ * *
* POST / api / admin / variety / schedule
2026-04-04 19:07:08 +09:00
* 예능 일정 저장 ( multipart / form - data )
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
* /
fastify . post ( '/schedule' , {
preHandler : [ fastify . authenticate ] ,
} , async ( request , reply ) => {
2026-04-04 19:07:08 +09:00
const parts = request . parts ( ) ;
let title = '' ;
let date = '' ;
let time = null ;
let broadcaster = '' ;
let replayUrl = null ;
let memberIds = [ ] ;
let thumbnailBuffer = null ;
for await ( const part of parts ) {
if ( part . type === 'file' && part . fieldname === 'thumbnail' ) {
thumbnailBuffer = await part . toBuffer ( ) ;
} else if ( part . type === 'field' ) {
if ( part . fieldname === 'title' ) title = part . value ;
else if ( part . fieldname === 'date' ) date = part . value ;
else if ( part . fieldname === 'time' ) time = part . value || null ;
else if ( part . fieldname === 'broadcaster' ) broadcaster = part . value ;
else if ( part . fieldname === 'replayUrl' ) replayUrl = part . value || null ;
else if ( part . fieldname === 'memberIds' ) memberIds = JSON . parse ( part . value ) ;
}
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
}
2026-04-04 19:07:08 +09:00
if ( ! title ? . trim ( ) ) return badRequest ( reply , '프로그램명은 필수입니다.' ) ;
if ( ! date ) return badRequest ( reply , '날짜는 필수입니다.' ) ;
if ( ! broadcaster ? . trim ( ) ) return badRequest ( reply , '방송사/플랫폼은 필수입니다.' ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
try {
// schedules 테이블
const [ scheduleResult ] = await db . query (
'INSERT INTO schedules (category_id, title, date, time) VALUES (?, ?, ?, ?)' ,
2026-04-04 19:07:08 +09:00
[ VARIETY _CATEGORY _ID , title . trim ( ) , date , time ]
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
) ;
const scheduleId = scheduleResult . insertId ;
2026-04-04 19:07:08 +09:00
// 썸네일 업로드
let thumbnailId = null ;
if ( thumbnailBuffer && thumbnailBuffer . length > 0 ) {
const { originalUrl , mediumUrl , thumbUrl } = await uploadVarietyThumbnail ( scheduleId , thumbnailBuffer ) ;
const [ imgResult ] = await db . query (
'INSERT INTO images (original_url, medium_url, thumb_url) VALUES (?, ?, ?)' ,
[ originalUrl , mediumUrl , thumbUrl ]
) ;
thumbnailId = imgResult . insertId ;
}
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
// schedule_variety 테이블
await db . query (
2026-04-04 19:07:08 +09:00
'INSERT INTO schedule_variety (schedule_id, broadcaster, replay_url, thumbnail_id) VALUES (?, ?, ?, ?)' ,
[ scheduleId , broadcaster . trim ( ) , replayUrl ? . trim ( ) || null , thumbnailId ]
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
) ;
// schedule_members 테이블
2026-04-04 19:07:08 +09:00
if ( memberIds . length > 0 ) {
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
const values = memberIds . map ( memberId => [ scheduleId , memberId ] ) ;
await db . query ( 'INSERT INTO schedule_members (schedule_id, member_id) VALUES ?' , [ values ] ) ;
}
// Meilisearch 동기화
2026-04-04 19:07:08 +09:00
const [ categoryRows ] = await db . query ( 'SELECT name, color FROM schedule_categories WHERE id = ?' , [ VARIETY _CATEGORY _ID ] ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
const category = categoryRows [ 0 ] || { } ;
let memberNames = '' ;
2026-04-04 19:07:08 +09:00
if ( memberIds . length > 0 ) {
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
const [ members ] = await db . query ( 'SELECT name FROM members WHERE id IN (?) ORDER BY id' , [ memberIds ] ) ;
memberNames = members . map ( m => m . name ) . join ( ',' ) ;
}
await addOrUpdateSchedule ( meilisearch , {
id : scheduleId ,
title : title . trim ( ) ,
date ,
time : time || '' ,
category _id : VARIETY _CATEGORY _ID ,
category _name : category . name || '' ,
category _color : category . color || '' ,
member _names : memberNames ,
2026-06-07 16:39:40 +09:00
} , redis ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
2026-04-05 13:37:52 +09:00
// 방송사 캐시 무효화
await redis . del ( BROADCASTER _KEY ) ;
2026-04-04 19:07:08 +09:00
logActivity ( db , { actor : 'admin' , action : 'create' , category : 'schedule' , targetType : 'variety_schedule' , targetId : scheduleId , summary : ` 예능 일정 생성: ${ title . trim ( ) } ` } ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
return { success : true , scheduleId } ;
} catch ( err ) {
fastify . log . error ( ` 예능 일정 저장 오류: ${ err . message } ` ) ;
return serverError ( reply , err . message ) ;
}
} ) ;
/ * *
* PUT / api / admin / variety / schedule / : id
2026-04-04 19:07:08 +09:00
* 예능 일정 수정 ( multipart / form - data )
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
* /
fastify . put ( '/schedule/:id' , {
preHandler : [ fastify . authenticate ] ,
} , async ( request , reply ) => {
const { id } = request . params ;
2026-04-04 19:07:08 +09:00
const parts = request . parts ( ) ;
let title = '' ;
let date = '' ;
let time = null ;
let broadcaster = '' ;
let replayUrl = null ;
let memberIds = [ ] ;
let thumbnailBuffer = null ;
let removeThumbnail = false ;
for await ( const part of parts ) {
if ( part . type === 'file' && part . fieldname === 'thumbnail' ) {
thumbnailBuffer = await part . toBuffer ( ) ;
} else if ( part . type === 'field' ) {
if ( part . fieldname === 'title' ) title = part . value ;
else if ( part . fieldname === 'date' ) date = part . value ;
else if ( part . fieldname === 'time' ) time = part . value || null ;
else if ( part . fieldname === 'broadcaster' ) broadcaster = part . value ;
else if ( part . fieldname === 'replayUrl' ) replayUrl = part . value || null ;
else if ( part . fieldname === 'memberIds' ) memberIds = JSON . parse ( part . value ) ;
else if ( part . fieldname === 'removeThumbnail' ) removeThumbnail = part . value === 'true' ;
}
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
}
2026-04-04 19:07:08 +09:00
if ( ! title ? . trim ( ) ) return badRequest ( reply , '프로그램명은 필수입니다.' ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
try {
const [ existing ] = await db . query ( 'SELECT id FROM schedules WHERE id = ?' , [ id ] ) ;
2026-04-04 19:07:08 +09:00
if ( existing . length === 0 ) return notFound ( reply , '일정을 찾을 수 없습니다.' ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
// schedules 업데이트
2026-04-04 19:07:08 +09:00
await db . query ( 'UPDATE schedules SET title = ?, date = ?, time = ? WHERE id = ?' , [ title . trim ( ) , date , time , id ] ) ;
// 기존 variety 데이터 조회
const [ varietyRows ] = await db . query ( 'SELECT thumbnail_id FROM schedule_variety WHERE schedule_id = ?' , [ id ] ) ;
let thumbnailId = varietyRows [ 0 ] ? . thumbnail _id || null ;
// 썸네일 업데이트
if ( thumbnailBuffer && thumbnailBuffer . length > 0 ) {
const { originalUrl , mediumUrl , thumbUrl } = await uploadVarietyThumbnail ( id , thumbnailBuffer ) ;
if ( thumbnailId ) {
await db . query ( 'UPDATE images SET original_url = ?, medium_url = ?, thumb_url = ? WHERE id = ?' , [ originalUrl , mediumUrl , thumbUrl , thumbnailId ] ) ;
} else {
const [ imgResult ] = await db . query ( 'INSERT INTO images (original_url, medium_url, thumb_url) VALUES (?, ?, ?)' , [ originalUrl , mediumUrl , thumbUrl ] ) ;
thumbnailId = imgResult . insertId ;
}
} else if ( removeThumbnail && thumbnailId ) {
await db . query ( 'DELETE FROM images WHERE id = ?' , [ thumbnailId ] ) ;
thumbnailId = null ;
}
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
2026-04-04 19:07:08 +09:00
// schedule_variety upsert
if ( varietyRows . length > 0 ) {
await db . query ( 'UPDATE schedule_variety SET broadcaster = ?, replay_url = ?, thumbnail_id = ? WHERE schedule_id = ?' ,
[ broadcaster ? . trim ( ) || '' , replayUrl ? . trim ( ) || null , thumbnailId , id ] ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
} else {
2026-04-04 19:07:08 +09:00
await db . query ( 'INSERT INTO schedule_variety (schedule_id, broadcaster, replay_url, thumbnail_id) VALUES (?, ?, ?, ?)' ,
[ id , broadcaster ? . trim ( ) || '' , replayUrl ? . trim ( ) || null , thumbnailId ] ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
}
// 멤버 업데이트
await db . query ( 'DELETE FROM schedule_members WHERE schedule_id = ?' , [ id ] ) ;
2026-04-04 19:07:08 +09:00
if ( memberIds . length > 0 ) {
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
const values = memberIds . map ( memberId => [ id , memberId ] ) ;
await db . query ( 'INSERT INTO schedule_members (schedule_id, member_id) VALUES ?' , [ values ] ) ;
}
2026-06-07 16:39:40 +09:00
await syncScheduleById ( meilisearch , db , parseInt ( id ) , redis ) ;
2026-04-05 13:37:52 +09:00
await redis . del ( BROADCASTER _KEY ) ;
2026-04-04 19:07:08 +09:00
logActivity ( db , { actor : 'admin' , action : 'update' , category : 'schedule' , targetType : 'variety_schedule' , targetId : parseInt ( id ) , summary : ` 예능 일정 수정: ${ title . trim ( ) } ` } ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
return { success : true } ;
} catch ( err ) {
fastify . log . error ( ` 예능 일정 수정 오류: ${ err . message } ` ) ;
return serverError ( reply , err . message ) ;
}
} ) ;
/ * *
* GET / api / admin / variety / schedule / : id
* 예능 일정 상세 조회 ( 수정 폼용 )
* /
fastify . get ( '/schedule/:id' , {
preHandler : [ fastify . authenticate ] ,
} , async ( request , reply ) => {
const { id } = request . params ;
try {
const [ rows ] = await db . query ( `
SELECT s . id , s . title , s . date , s . time ,
2026-04-04 19:07:08 +09:00
sv . broadcaster , sv . replay _url , sv . thumbnail _id ,
i . original _url as thumb _original , i . medium _url as thumb _medium , i . thumb _url as thumb _thumb
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
FROM schedules s
LEFT JOIN schedule _variety sv ON s . id = sv . schedule _id
2026-04-04 19:07:08 +09:00
LEFT JOIN images i ON sv . thumbnail _id = i . id
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
WHERE s . id = ?
` , [id]);
2026-04-04 19:07:08 +09:00
if ( rows . length === 0 ) return notFound ( reply , '일정을 찾을 수 없습니다.' ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
2026-04-04 19:07:08 +09:00
const s = rows [ 0 ] ;
const [ memberRows ] = await db . query ( 'SELECT member_id FROM schedule_members WHERE schedule_id = ?' , [ id ] ) ;
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
return {
2026-04-04 19:07:08 +09:00
id : s . id ,
title : s . title ,
date : s . date instanceof Date ? s . date . toISOString ( ) . split ( 'T' ) [ 0 ] : s . date ? . split ( 'T' ) [ 0 ] || '' ,
time : s . time ? s . time . substring ( 0 , 5 ) : '' ,
broadcaster : s . broadcaster || '' ,
replayUrl : s . replay _url || '' ,
thumbnailUrl : s . thumb _medium || s . thumb _original || '' ,
feat: 예능 카테고리 관리 기능 구현
- 백엔드: POST/PUT/GET /admin/variety/schedule API
- 백엔드: 일정 상세 응답에 broadcaster, replayUrl, thumbnailUrl 포함
- 프론트엔드: VarietyForm (추가), VarietyEditForm (수정) 페이지
- 방송사 프리셋 버튼 (KBS, MBC, SBS, tvN, 유튜브, 티빙 등)
- 출연 멤버 선택, 다시보기 링크, 썸네일 URL 지원
- 라우트 등록 및 일정 목록 편집 링크 연결
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:16:16 +09:00
memberIds : memberRows . map ( r => r . member _id ) ,
} ;
} catch ( err ) {
fastify . log . error ( ` 예능 일정 조회 오류: ${ err . message } ` ) ;
return serverError ( reply , err . message ) ;
}
} ) ;
}