feat: add shared @sexy.pivoine.art/types package and fix type safety across frontend/backend

- Create packages/types with shared TypeScript domain model interfaces (User, Video, Model, Article, Comment, Recording, etc.)
- Wire both frontend and backend packages to use @sexy.pivoine.art/types via workspace:*
- Update backend Pothos objectRef types to use shared interfaces instead of inline types
- Update frontend $lib/types.ts to re-export from shared package
- Fix all type errors introduced by more accurate nullable types (avatar/banner as string|null UUIDs, author nullable, events/device_info as object[])
- Add artist_name to comment user select in backend resolver
- Widen utility function signatures (getAssetUrl, getUserInitials, calcReadingTime) to accept null/undefined

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 11:01:11 +01:00
parent c6126c13e9
commit 97269788ee
31 changed files with 839 additions and 822 deletions

View File

@@ -18,9 +18,9 @@
.filter((model) => {
const matchesSearch =
searchQuery === "" ||
model.artist_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
model.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesCategory = categoryFilter === "all" || model.category === categoryFilter;
model.artist_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
model.tags?.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesCategory = categoryFilter === "all";
return matchesSearch && matchesCategory;
})
.sort((a, b) => {
@@ -31,7 +31,7 @@
// }
// if (sortBy === "rating") return b.rating - a.rating;
// if (sortBy === "videos") return b.videos - a.videos;
return a.artist_name.localeCompare(b.artist_name);
return (a.artist_name ?? "").localeCompare(b.artist_name ?? "");
});
});
</script>
@@ -205,7 +205,7 @@
<!-- Stats -->
<div class="flex items-center justify-between text-sm text-muted-foreground mb-4">
<!-- <span>{model.videos} videos</span> -->
<span class="capitalize">{model.category}</span>
<!-- category not available -->
</div>
<!-- Action Buttons -->

View File

@@ -16,7 +16,7 @@
const { data } = $props();
let images = $derived(
data.model.photos.map((p) => ({
(data.model.photos ?? []).map((p) => ({
...p,
url: getAssetUrl(p.id),
thumbnail: getAssetUrl(p.id, "thumbnail"),
@@ -29,7 +29,7 @@
</script>
<Meta
title={data.model.artist_name}
title={data.model.artist_name ?? ""}
description={data.model.description}
image={getAssetUrl(data.model.avatar, "medium")!}
/>
@@ -44,7 +44,7 @@
{#if data.model.banner}
<img
src={getAssetUrl(data.model.banner, "banner")}
alt={$_(data.model.artist_name)}
alt={data.model.artist_name ?? ""}
class="w-full h-full object-cover"
/>
<div