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
+1
View File
@@ -0,0 +1 @@
+167
View File
@@ -0,0 +1,167 @@
import { createSupabaseServiceRoleClient } from "@/lib/supabase/service-role";
export type PublicGalleryPhotoRow = {
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;
likes: number | null;
urls: Record<string, unknown>;
user_info: Record<string, unknown>;
photo_created_at: string | null;
picked_at: string | null;
oss_url: string | null;
};
export const publicGalleryPhotoSelect =
"id,unsplash_id,slug,description,alt_description,width,height,color,color_family,likes,urls,user_info,photo_created_at,picked_at,oss_url";
export function getPublicPhotoUrl(photo: PublicGalleryPhotoRow) {
const candidates = [
photo.oss_url,
photo.urls?.regular,
photo.urls?.small,
photo.urls?.raw,
photo.urls?.thumb,
];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.length > 0) {
return candidate;
}
}
return null;
}
export async function getFavoritePhotoIds({
photoIds,
userId,
}: {
photoIds: string[];
userId: string | null;
}) {
if (!userId || photoIds.length === 0) {
return new Set<string>();
}
const supabase = createSupabaseServiceRoleClient();
const { data, error } = await supabase
.from("wg_user_favorites")
.select("photo_id")
.eq("user_id", userId)
.in("photo_id", photoIds);
if (error) {
throw error;
}
return new Set((data ?? []).map((favorite) => favorite.photo_id));
}
export function mapPublicGalleryPhoto({
favoritePhotoIds,
photo,
}: {
favoritePhotoIds?: Set<string>;
photo: PublicGalleryPhotoRow;
}) {
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,
likes: photo.likes,
url: getPublicPhotoUrl(photo),
sourceUrl: getPublicPhotoUrl({ ...photo, oss_url: null }),
user: photo.user_info,
photoCreatedAt: photo.photo_created_at,
pickedAt: photo.picked_at,
isFavorited: favoritePhotoIds?.has(photo.id) ?? false,
};
}
export async function listPickedGalleryPhotos({
colorFamily,
page,
pageSize,
userId,
}: {
colorFamily: string | null;
page: number;
pageSize: number;
userId: string | null;
}) {
const supabase = createSupabaseServiceRoleClient();
let query = supabase
.from("wg_gallery_photos")
.select(publicGalleryPhotoSelect, { count: "exact" })
.eq("is_picked", true);
if (colorFamily && colorFamily !== "all") {
query = query.eq("color_family", colorFamily);
}
const { data, error, count } = await query
.order("picked_at", { ascending: false, nullsFirst: false })
.order("photo_created_at", { ascending: false })
.range((page - 1) * pageSize, page * pageSize - 1);
if (error) {
throw error;
}
const photos = (data ?? []) as PublicGalleryPhotoRow[];
const favoritePhotoIds = await getFavoritePhotoIds({
userId,
photoIds: photos.map((photo) => photo.id),
});
return {
data: photos.map((photo) =>
mapPublicGalleryPhoto({ photo, favoritePhotoIds }),
),
total: count ?? 0,
};
}
export async function getPickedGalleryPhotoById({
id,
userId,
}: {
id: string;
userId: string | null;
}) {
const supabase = createSupabaseServiceRoleClient();
const { data, error } = await supabase
.from("wg_gallery_photos")
.select(publicGalleryPhotoSelect)
.eq("id", id)
.eq("is_picked", true)
.maybeSingle<PublicGalleryPhotoRow>();
if (error) {
throw error;
}
if (!data) {
return null;
}
const favoritePhotoIds = await getFavoritePhotoIds({
userId,
photoIds: [data.id],
});
return mapPublicGalleryPhoto({ photo: data, favoritePhotoIds });
}
+225
View File
@@ -0,0 +1,225 @@
import { createSupabaseServiceRoleClient } from "@/lib/supabase/service-role";
import {
getFavoritePhotoIds,
mapPublicGalleryPhoto,
} from "@/server/site/gallery-photos";
import type { CollectionPhoto } from "@/types/admin/collection";
type PublicCollectionRow = {
id: string;
title: string;
slug: string;
description: string | null;
cover_photo_id: string | null;
published_at: string | null;
created_at: string;
};
type CollectionItemRow = {
photo_id: string;
sort_order: number;
};
const collectionSelect =
"id,title,slug,description,cover_photo_id,published_at,created_at";
const photoSelect =
"id,unsplash_id,slug,description,alt_description,width,height,color,color_family,likes,urls,user_info,photo_created_at,picked_at,oss_url";
function getPhotoUrl(photo: CollectionPhoto | null) {
if (!photo) {
return null;
}
const urls = photo.urls as Record<string, string> | undefined;
return (
photo.oss_url ??
urls?.regular ??
urls?.small ??
urls?.raw ??
urls?.thumb ??
null
);
}
async function getPhotosById(photoIds: string[]) {
if (photoIds.length === 0) {
return new Map<string, CollectionPhoto>();
}
const supabase = createSupabaseServiceRoleClient();
const { data, error } = await supabase
.from("wg_gallery_photos")
.select(photoSelect)
.eq("is_picked", true)
.in("id", photoIds);
if (error) {
throw error;
}
return new Map(
((data ?? []) as CollectionPhoto[]).map((photo) => [photo.id, photo]),
);
}
export async function listPublishedCollections({
page,
pageSize,
userId,
}: {
page: number;
pageSize: number;
userId: string | null;
}) {
const supabase = createSupabaseServiceRoleClient();
const { data, error, count } = await supabase
.from("wg_photo_collections")
.select(collectionSelect, { count: "exact" })
.eq("is_published", true)
.order("published_at", { ascending: false, nullsFirst: false })
.order("created_at", { ascending: false })
.range((page - 1) * pageSize, page * pageSize - 1);
if (error) {
throw error;
}
const collections = (data ?? []) as PublicCollectionRow[];
const collectionIds = collections.map((collection) => collection.id);
const coverPhotoIds = collections
.map((collection) => collection.cover_photo_id)
.filter((id): id is string => Boolean(id));
const { data: items, error: itemsError } =
collectionIds.length > 0
? await supabase
.from("wg_photo_collection_items")
.select("collection_id,photo_id")
.in("collection_id", collectionIds)
: { data: [], error: null };
if (itemsError) {
throw itemsError;
}
const photosById = await getPhotosById(coverPhotoIds);
const favoritePhotoIds = await getFavoritePhotoIds({
userId,
photoIds: coverPhotoIds,
});
const itemRows = (items ?? []) as Array<{
collection_id: string;
photo_id: string;
}>;
const countByCollection = itemRows.reduce(
(counts, item) => {
counts[item.collection_id] = (counts[item.collection_id] ?? 0) + 1;
return counts;
},
{} as Record<string, number>,
);
return {
data: collections.map((collection) => {
const coverPhoto = collection.cover_photo_id
? (photosById.get(collection.cover_photo_id) ?? null)
: null;
return {
id: collection.id,
title: collection.title,
slug: collection.slug,
description: collection.description,
coverPhoto: coverPhoto
? mapPublicGalleryPhoto({
photo: coverPhoto,
favoritePhotoIds,
})
: null,
coverUrl: getPhotoUrl(coverPhoto),
photoCount: countByCollection[collection.id] ?? 0,
publishedAt: collection.published_at,
};
}),
total: count ?? 0,
};
}
export async function getPublishedCollectionBySlug({
page,
pageSize,
slug,
userId,
}: {
page: number;
pageSize: number;
slug: string;
userId: string | null;
}) {
const supabase = createSupabaseServiceRoleClient();
const { data: collection, error } = await supabase
.from("wg_photo_collections")
.select(collectionSelect)
.eq("slug", slug)
.eq("is_published", true)
.maybeSingle<PublicCollectionRow>();
if (error) {
throw error;
}
if (!collection) {
return null;
}
const {
data: items,
error: itemsError,
count,
} = await supabase
.from("wg_photo_collection_items")
.select("photo_id,sort_order", { count: "exact" })
.eq("collection_id", collection.id)
.order("sort_order", { ascending: true })
.order("created_at", { ascending: true })
.range((page - 1) * pageSize, page * pageSize - 1);
if (itemsError) {
throw itemsError;
}
const itemRows = (items ?? []) as CollectionItemRow[];
const photoIds = [
...itemRows.map((item) => item.photo_id),
...(collection.cover_photo_id ? [collection.cover_photo_id] : []),
];
const photosById = await getPhotosById(Array.from(new Set(photoIds)));
const favoritePhotoIds = await getFavoritePhotoIds({
userId,
photoIds,
});
const coverPhoto = collection.cover_photo_id
? (photosById.get(collection.cover_photo_id) ?? null)
: null;
return {
collection: {
id: collection.id,
title: collection.title,
slug: collection.slug,
description: collection.description,
coverPhoto: coverPhoto
? mapPublicGalleryPhoto({ photo: coverPhoto, favoritePhotoIds })
: null,
coverUrl: getPhotoUrl(coverPhoto),
photoCount: count ?? 0,
publishedAt: collection.published_at,
},
photos: itemRows
.map((item) => photosById.get(item.photo_id))
.filter((photo): photo is CollectionPhoto => Boolean(photo))
.map((photo) => mapPublicGalleryPhoto({ photo, favoritePhotoIds })),
total: count ?? 0,
};
}