an app to share curated trails sidetrail.app
1

Configure Feed

Select the types of activity you want to include in your feed.

1import "server-only"; 2import { cache } from "react"; 3import { getDb, drafts } from "@/data/db"; 4import { eq, and, desc } from "drizzle-orm"; 5import { getCurrentDid } from "@/auth"; 6 7// ============================================================================ 8// Types 9// ============================================================================ 10 11export type DraftListItem = { 12 rkey: string; 13 title: string; 14 description: string; 15 stopsCount: number; 16 accentColor: string; 17 backgroundColor: string; 18 updatedAt: Date; 19}; 20 21export type DraftDetail = { 22 rkey: string; 23 version: number; 24 title: string; 25 description: string; 26 stops: Array<{ 27 tid: string; 28 title: string; 29 content: string; 30 buttonText?: string; 31 external?: { 32 uri: string; 33 title?: string; 34 description?: string; 35 thumb?: string; 36 }; 37 }>; 38 accentColor: string; 39 backgroundColor: string; 40 createdAt: Date; 41 updatedAt: Date; 42}; 43 44// ============================================================================ 45// Auth Helper 46// ============================================================================ 47 48const requireAuth = cache(async function requireAuth(): Promise<string> { 49 const did = await getCurrentDid(); 50 if (!did) { 51 throw new Error("Authentication required"); 52 } 53 return did; 54}); 55 56// ============================================================================ 57// Queries 58// ============================================================================ 59 60export const loadDrafts = cache(async function loadDrafts(): Promise<DraftListItem[]> { 61 const did = await requireAuth(); 62 const db = getDb(); 63 64 const rows = await db 65 .select() 66 .from(drafts) 67 .where(eq(drafts.authorDid, did)) 68 .orderBy(desc(drafts.updatedAt)); 69 70 return rows.map((row) => ({ 71 rkey: row.rkey, 72 title: row.record.title, 73 description: row.record.description, 74 stopsCount: row.record.stops.length, 75 accentColor: row.record.accentColor, 76 backgroundColor: row.record.backgroundColor, 77 updatedAt: row.updatedAt, 78 })); 79}); 80 81export const loadDraftDetail = cache(async function loadDraftDetail( 82 rkey: string, 83): Promise<DraftDetail | null> { 84 const did = await requireAuth(); 85 const db = getDb(); 86 87 const [row] = await db 88 .select() 89 .from(drafts) 90 .where(and(eq(drafts.authorDid, did), eq(drafts.rkey, rkey))) 91 .limit(1); 92 93 if (!row) return null; 94 95 return { 96 rkey: row.rkey, 97 version: row.version, 98 title: row.record.title, 99 description: row.record.description, 100 stops: row.record.stops, 101 accentColor: row.record.accentColor, 102 backgroundColor: row.record.backgroundColor, 103 createdAt: row.createdAt, 104 updatedAt: row.updatedAt, 105 }; 106}); 107 108export const loadDraftsBadges = cache(async function loadDraftsBadges(): Promise< 109 Array<{ accentColor: string; key: string }> 110> { 111 const did = await requireAuth(); 112 const db = getDb(); 113 114 const rows = await db 115 .select() 116 .from(drafts) 117 .where(eq(drafts.authorDid, did)) 118 .orderBy(desc(drafts.updatedAt)) 119 .limit(3); 120 121 return rows.map((row) => ({ 122 key: row.rkey, 123 accentColor: row.record.accentColor, 124 })); 125});