183 lines
4.9 KiB
TypeScript
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;
|
|
}
|
|
}
|