first commit

This commit is contained in:
luoyangwei
2026-06-06 00:49:07 +08:00
commit 2b860d201e
134 changed files with 22773 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
import { createSupabaseServiceRoleClient } from "@/lib/supabase/service-role";
type FavoriteRow = {
photo_id: string;
created_at: string;
};
type FavoritePhotoRow = {
id: string;
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;
urls: Record<string, unknown>;
user_info: Record<string, unknown>;
picked_at: string | null;
oss_url: string | null;
};
const favoritePhotoSelect =
"id,unsplash_id,slug,description,alt_description,width,height,color,color_family,urls,user_info,picked_at,oss_url";
function getPhotoUrl(photo: FavoritePhotoRow) {
const urls = photo.urls as Record<string, string> | undefined;
return (
photo.oss_url ??
urls?.regular ??
urls?.small ??
urls?.raw ??
urls?.thumb ??
null
);
}
function mapPhoto(photo: FavoritePhotoRow, favoritedAt: string | null) {
return {
id: photo.id,
unsplashId: photo.unsplash_id,
slug: photo.slug,
description: photo.description,
altDescription: photo.alt_description,
width: photo.width,
height: photo.height,
color: photo.color,
colorFamily: photo.color_family,
url: getPhotoUrl(photo),
sourceUrl: getPhotoUrl({ ...photo, oss_url: null }),
user: photo.user_info,
pickedAt: photo.picked_at,
favoritedAt,
};
}
export async function listUserFavorites({
page,
pageSize,
userId,
}: {
page: number;
pageSize: number;
userId: string;
}) {
const supabase = createSupabaseServiceRoleClient();
const { data, error, count } = await supabase
.from("wg_user_favorites")
.select("photo_id,created_at", { count: "exact" })
.eq("user_id", userId)
.order("created_at", { ascending: false })
.range((page - 1) * pageSize, page * pageSize - 1);
if (error) {
throw error;
}
const favorites = (data ?? []) as FavoriteRow[];
const photoIds = favorites.map((favorite) => favorite.photo_id);
if (photoIds.length === 0) {
return {
data: [],
total: count ?? 0,
};
}
const { data: photos, error: photosError } = await supabase
.from("wg_gallery_photos")
.select(favoritePhotoSelect)
.eq("is_picked", true)
.in("id", photoIds);
if (photosError) {
throw photosError;
}
const favoritedAtByPhotoId = new Map(
favorites.map((favorite) => [favorite.photo_id, favorite.created_at]),
);
const photoById = new Map(
((photos ?? []) as FavoritePhotoRow[]).map((photo) => [
photo.id,
photo,
]),
);
return {
data: photoIds
.map((photoId) => photoById.get(photoId))
.filter((photo): photo is FavoritePhotoRow => Boolean(photo))
.map((photo) =>
mapPhoto(photo, favoritedAtByPhotoId.get(photo.id) ?? null),
),
total: count ?? 0,
};
}
export async function addUserFavorite({
photoId,
userId,
}: {
photoId: string;
userId: string;
}) {
const supabase = createSupabaseServiceRoleClient();
const { data: photo, error: photoError } = await supabase
.from("wg_gallery_photos")
.select(favoritePhotoSelect)
.eq("id", photoId)
.eq("is_picked", true)
.maybeSingle<FavoritePhotoRow>();
if (photoError) {
throw photoError;
}
if (!photo) {
throw new Error("Picked photo was not found.");
}
const { data, error } = await supabase
.from("wg_user_favorites")
.upsert(
{
user_id: userId,
photo_id: photoId,
},
{ onConflict: "user_id,photo_id" },
)
.select("created_at")
.single<{ created_at: string }>();
if (error) {
throw error;
}
return mapPhoto(photo, data.created_at);
}
export async function removeUserFavorite({
photoId,
userId,
}: {
photoId: string;
userId: string;
}) {
const supabase = createSupabaseServiceRoleClient();
const { error } = await supabase
.from("wg_user_favorites")
.delete()
.eq("user_id", userId)
.eq("photo_id", photoId);
if (error) {
throw error;
}
}