feat(variety): 방송사 프리셋을 입력 빈도수 기반으로 변경
- GET /admin/variety/broadcasters: DB에서 빈도수 상위 10개 조회 (Redis 1시간 캐시) - 일정 생성/수정 시 캐시 무효화 - 프론트엔드: 하드코딩 프리셋 제거, API에서 동적으로 로드 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a01d368728
commit
48ed3bb9e0
4 changed files with 59 additions and 12 deletions
|
|
@ -5,12 +5,43 @@ import { badRequest, notFound, serverError } from '../../utils/error.js';
|
|||
import { logActivity } from '../../utils/log.js';
|
||||
|
||||
const VARIETY_CATEGORY_ID = CATEGORY_IDS.VARIETY;
|
||||
const BROADCASTER_KEY = 'variety:broadcasters';
|
||||
|
||||
/**
|
||||
* 예능 관련 관리자 라우트
|
||||
*/
|
||||
export default async function varietyRoutes(fastify) {
|
||||
const { db, meilisearch } = fastify;
|
||||
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;
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/variety/schedule
|
||||
|
|
@ -96,6 +127,9 @@ export default async function varietyRoutes(fastify) {
|
|||
member_names: memberNames,
|
||||
});
|
||||
|
||||
// 방송사 캐시 무효화
|
||||
await redis.del(BROADCASTER_KEY);
|
||||
|
||||
logActivity(db, { actor: 'admin', action: 'create', category: 'schedule', targetType: 'variety_schedule', targetId: scheduleId, summary: `예능 일정 생성: ${title.trim()}` });
|
||||
return { success: true, scheduleId };
|
||||
} catch (err) {
|
||||
|
|
@ -181,6 +215,7 @@ export default async function varietyRoutes(fastify) {
|
|||
}
|
||||
|
||||
await syncScheduleById(meilisearch, db, parseInt(id));
|
||||
await redis.del(BROADCASTER_KEY);
|
||||
logActivity(db, { actor: 'admin', action: 'update', category: 'schedule', targetType: 'variety_schedule', targetId: parseInt(id), summary: `예능 일정 수정: ${title.trim()}` });
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -23,3 +23,10 @@ export async function getVarietySchedule(id) {
|
|||
export async function updateVarietySchedule(id, formData) {
|
||||
return fetchFormData(`/admin/variety/schedule/${id}`, formData, 'PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
* 자주 사용된 방송사/플랫폼 목록
|
||||
*/
|
||||
export async function getBroadcasters() {
|
||||
return fetchAuthApi('/admin/variety/broadcasters');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,8 @@ import Toast from "@/components/common/Toast";
|
|||
import { useToast } from "@/hooks/common";
|
||||
import { useAdminAuth } from "@/hooks/pc/admin";
|
||||
import { getMembers } from "@/api/public/members";
|
||||
import { getVarietySchedule, updateVarietySchedule } from "@/api/admin/variety";
|
||||
import { getVarietySchedule, updateVarietySchedule, getBroadcasters } from "@/api/admin/variety";
|
||||
|
||||
const broadcasterPresets = [
|
||||
"KBS", "MBC", "SBS", "tvN", "JTBC",
|
||||
"Mnet", "유튜브", "티빙", "웨이브", "쿠팡플레이",
|
||||
];
|
||||
|
||||
/**
|
||||
* 예능 일정 수정 폼
|
||||
|
|
@ -41,6 +37,13 @@ function VarietyEditForm() {
|
|||
enabled: isAuthenticated && !!id,
|
||||
});
|
||||
|
||||
const { data: broadcasterPresets = [] } = useQuery({
|
||||
queryKey: ["broadcasters"],
|
||||
queryFn: getBroadcasters,
|
||||
enabled: isAuthenticated,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [broadcaster, setBroadcaster] = useState("");
|
||||
const [date, setDate] = useState("");
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import TimePicker from "@/components/pc/admin/common/TimePicker";
|
|||
import { useToast } from "@/hooks/common";
|
||||
import { useAdminAuth } from "@/hooks/pc/admin";
|
||||
import { getMembers } from "@/api/public/members";
|
||||
import { createVarietySchedule } from "@/api/admin/variety";
|
||||
import { createVarietySchedule, getBroadcasters } from "@/api/admin/variety";
|
||||
|
||||
/**
|
||||
* 예능 일정 추가 폼
|
||||
|
|
@ -40,11 +40,13 @@ function VarietyForm() {
|
|||
const [selectedMemberIds, setSelectedMemberIds] = useState([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 방송사 프리셋
|
||||
const broadcasterPresets = [
|
||||
"KBS", "MBC", "SBS", "tvN", "JTBC",
|
||||
"Mnet", "유튜브", "티빙", "웨이브", "쿠팡플레이",
|
||||
];
|
||||
// 자주 사용된 방송사 목록
|
||||
const { data: broadcasterPresets = [] } = useQuery({
|
||||
queryKey: ["broadcasters"],
|
||||
queryFn: getBroadcasters,
|
||||
enabled: isAuthenticated,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
// 멤버 토글
|
||||
const toggleMember = (memberId) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue