first commit
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user