first commit

This commit is contained in:
2026-05-22 09:17:28 +02:00
commit 26b3ed6994
33 changed files with 2943 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
export interface Video {
id: string;
title: string;
channel: string;
published_at: string;
duration_seconds: number;
view_count: number;
like_count: number;
thumbnail_url: string;
youtube_url: string;
tags: string[];
has_chapters: boolean;
score: number;
topics: string[];
source_queries: string[];
}
export interface VideoList {
total: number;
page: number;
page_size: number;
items: Video[];
}
export interface Filters {
q?: string;
min_score?: number;
topic?: string;
source_queries?: string[];
days?: number;
page?: number;
page_size?: number;
}
export async function fetchVideos(filters: Filters = {}): Promise<VideoList> {
const params = new URLSearchParams();
if (filters.q) params.set("q", filters.q);
if (filters.min_score !== undefined) params.set("min_score", String(filters.min_score));
if (filters.topic) params.set("topic", filters.topic);
if (filters.source_queries?.length) filters.source_queries.forEach((sq) => params.append("source_query", sq));
if (filters.days) params.set("days", String(filters.days));
if (filters.page) params.set("page", String(filters.page));
if (filters.page_size) params.set("page_size", String(filters.page_size));
const res = await fetch(`${API_BASE}/api/videos?${params}`, { next: { revalidate: 300 } });
if (!res.ok) throw new Error("Erreur lors du chargement des vidéos");
return res.json();
}
export async function triggerRefresh(queries?: string[]): Promise<void> {
const body = queries ? JSON.stringify({ queries }) : undefined;
await fetch(`${API_BASE}/api/refresh`, {
method: "POST",
headers: body ? { "Content-Type": "application/json" } : {},
body,
});
}
export async function fetchConfig(): Promise<{ queries: string[] }> {
const res = await fetch(`${API_BASE}/api/config`, { cache: "no-store" });
if (!res.ok) throw new Error("Erreur config");
return res.json();
}
export async function fetchStatus(): Promise<{ video_count: number; last_updated: string | null; refresh_running: boolean; quota_exceeded: boolean; quota_exceeded_at: string | null }> {
const res = await fetch(`${API_BASE}/api/status`, { cache: "no-store" });
if (!res.ok) throw new Error("Erreur status");
return res.json();
}
export function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) return `${h}h${String(m).padStart(2, "0")}`;
return `${m}:${String(s).padStart(2, "0")}`;
}
export function formatDate(iso: string): string {
return new Intl.DateTimeFormat("fr-FR", { day: "numeric", month: "short", year: "numeric" }).format(new Date(iso));
}
export const TOPICS: Record<string, string> = {
incident: "🔥 Incident",
architecture: "🏗️ Architecture",
"observabilité": "📊 Observabilité",
"sécurité": "🔒 Sécurité",
ci_cd: "⚙️ CI/CD",
scaling: "📈 Scaling",
migration: "🔄 Migration",
storage: "💾 Storage",
};