Files
2026-06-06 00:49:07 +08:00

183 lines
4.9 KiB
TypeScript

import { createSupabaseServiceRoleClient } from "@/lib/supabase/service-role";
import {
fetchUnsplashWallpaperPhotos,
type UnsplashPhoto,
} from "@/server/api/unsplash";
import { getGalleryColorFamily } from "@/server/gallery/color";
const SOURCE = "unsplash-wallpapers";
const DEFAULT_PAGE = 1;
const DEFAULT_PER_PAGE = 20;
type GallerySyncState = {
source: string;
next_page: number;
per_page: number;
};
type GalleryPhotoRow = {
unsplash_id: string;
slug: string | null;
description: string | null;
alt_description: string | null;
width: number | null;
height: number | null;
color: string | null;
color_family: string | null;
blur_hash: string | null;
likes: number | null;
photo_created_at: string | null;
photo_updated_at: string | null;
urls: Record<string, unknown>;
links: Record<string, unknown>;
user_info: Record<string, unknown>;
raw: Record<string, unknown>;
source: string;
};
function normalizeDate(value: string | null | undefined) {
if (!value) {
return null;
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date.toISOString();
}
function mapPhotoToRow(photo: UnsplashPhoto): GalleryPhotoRow {
const raw = photo as Record<string, unknown>;
return {
unsplash_id: photo.id,
slug: photo.slug ?? null,
description: photo.description ?? null,
alt_description: photo.alt_description ?? null,
width: photo.width ?? null,
height: photo.height ?? null,
color: photo.color ?? null,
color_family: getGalleryColorFamily(photo.color),
blur_hash: photo.blur_hash ?? null,
likes: photo.likes ?? null,
photo_created_at: normalizeDate(photo.created_at),
photo_updated_at: normalizeDate(photo.updated_at),
urls: photo.urls ?? {},
links: photo.links ?? {},
user_info: photo.user ?? {},
raw,
source: SOURCE,
};
}
async function getSyncState() {
const supabase = createSupabaseServiceRoleClient();
const { data, error } = await supabase
.from("wg_gallery_sync_state")
.select("source,next_page,per_page")
.eq("source", SOURCE)
.maybeSingle<GallerySyncState>();
if (error) {
throw error;
}
if (data) {
return {
supabase,
state: data,
};
}
const initialState = {
source: SOURCE,
next_page: DEFAULT_PAGE,
per_page: DEFAULT_PER_PAGE,
};
const { error: insertError } = await supabase
.from("wg_gallery_sync_state")
.insert(initialState);
if (insertError) {
throw insertError;
}
return {
supabase,
state: initialState,
};
}
export async function syncUnsplashWallpapersToGallery() {
const { supabase, state } = await getSyncState();
const page = state.next_page || DEFAULT_PAGE;
const perPage = state.per_page || DEFAULT_PER_PAGE;
try {
const photos = await fetchUnsplashWallpaperPhotos({ page, perPage });
const rows = photos.map(mapPhotoToRow);
let insertedCount = 0;
if (rows.length > 0) {
const { data, error } = await supabase
.from("wg_gallery_photos")
.upsert(rows, {
onConflict: "unsplash_id",
ignoreDuplicates: true,
})
.select("unsplash_id");
if (error) {
throw error;
}
insertedCount = data?.length ?? 0;
}
const nextPage = rows.length === 0 ? DEFAULT_PAGE : page + 1;
const { error: updateError } = await supabase
.from("wg_gallery_sync_state")
.upsert(
{
source: SOURCE,
next_page: nextPage,
per_page: perPage,
last_synced_at: new Date().toISOString(),
last_inserted_count: insertedCount,
last_seen_count: photos.length,
last_error: null,
},
{ onConflict: "source" },
);
if (updateError) {
throw updateError;
}
return {
source: SOURCE,
page,
nextPage,
seenCount: photos.length,
insertedCount,
};
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown sync error.";
await supabase.from("wg_gallery_sync_state").upsert(
{
source: SOURCE,
next_page: page,
per_page: perPage,
last_synced_at: new Date().toISOString(),
last_inserted_count: 0,
last_seen_count: 0,
last_error: message,
},
{ onConflict: "source" },
);
throw error;
}
}