diff --git a/app/(app)/(tabs)/tablon/[id].tsx b/app/(app)/(tabs)/tablon/[id].tsx
index 7fe8e2d..fed539b 100644
--- a/app/(app)/(tabs)/tablon/[id].tsx
+++ b/app/(app)/(tabs)/tablon/[id].tsx
@@ -132,7 +132,7 @@ export default function PostDetailScreen() {
) : null}
- {post.author.avatar_url ? (
+ {post.author?.avatar_url ? (
- {[post.author.name, post.author.surname]
+ {[post.author?.name, post.author?.surname]
.filter(Boolean)
.map((w) => w![0])
.join('')
@@ -151,7 +151,7 @@ export default function PostDetailScreen() {
)}
- {[post.author.name, post.author.surname].filter(Boolean).join(' ') || '—'}
+ {[post.author?.name, post.author?.surname].filter(Boolean).join(' ') || '—'}
{post.published_at ? ` · ${formatRelativeTime(post.published_at)}` : ''}
diff --git a/components/PostCard.tsx b/components/PostCard.tsx
index 8117ae8..3194e8d 100644
--- a/components/PostCard.tsx
+++ b/components/PostCard.tsx
@@ -32,7 +32,7 @@ export function PostCard({ post, onPress }: Props) {
const { myReaction, counts, loading, toggle } = usePostReactions(post.id);
const thumbSource = post.cover_image_url ? { uri: getThumbUrl(post.cover_image_url) } : null;
const authorName =
- [post.author.name, post.author.surname].filter(Boolean).join(' ') || '—';
+ [post.author?.name, post.author?.surname].filter(Boolean).join(' ') || '—';
const publishedAt = post.published_at ? formatRelativeTime(post.published_at) : '';
return (
diff --git a/lib/supabase/queries/posts.ts b/lib/supabase/queries/posts.ts
index cb46bad..ced7396 100644
--- a/lib/supabase/queries/posts.ts
+++ b/lib/supabase/queries/posts.ts
@@ -7,19 +7,19 @@ type ProfileSnippet = { name: string | null; surname: string | null };
type PostAuthor = { id: string; name: string | null; surname: string | null; avatar_url: string | null };
export type PostWithAuthor = Tables<'posts'> & {
- author: ProfileSnippet;
+ author: ProfileSnippet | null;
comments_count: number;
rating_average: number; // 0 when the post has no ratings yet
rating_count: number;
};
-export type PostDetail = Tables<'posts'> & { author: PostAuthor };
+export type PostDetail = Tables<'posts'> & { author: PostAuthor | null };
export type PostCursor = { published_at: string; id: string };
// PostgREST reverse-embed: post_comments(count) yields [{ count }] per post and
// post_ratings(rating) yields the rating rows, so the feed's comment and rating
// aggregates ride on the same query (no N+1).
type PostRow = Tables<'posts'> & {
- author: ProfileSnippet;
+ author: ProfileSnippet | null;
post_comments: { count: number }[];
post_ratings: { rating: number }[];
};
@@ -33,9 +33,14 @@ export async function listPublishedPosts({
pageSize?: number;
client?: SupabaseClient;
}): Promise<{ rows: PostWithAuthor[]; nextCursor: PostCursor | null }> {
+ // Author is embedded through profiles_public: RLS on profiles only lets staff
+ // read their own row, so embedding profiles directly returns author: null for
+ // every post they did not write.
let query = client
.from('posts')
- .select('*, author:profiles!author_id(name, surname), post_comments(count), post_ratings(rating)')
+ .select(
+ '*, author:profiles_public!author_id(name, surname), post_comments(count), post_ratings(rating)',
+ )
.eq('status', 'published')
.is('deleted_at', null)
.order('published_at', { ascending: false })
@@ -79,7 +84,7 @@ export async function listPublishedPosts({
export async function getPostById(id: string): Promise {
const { data, error } = await supabase
.from('posts')
- .select('*, author:profiles!author_id(id, name, surname, avatar_url)')
+ .select('*, author:profiles_public!author_id(id, name, surname, avatar_url)')
.eq('id', id)
.is('deleted_at', null)
.single();