this repo has no description

wow i got far into making a route but so much work...

+71 -5
+44 -4
apps/api/index.ts
··· 1 1 import { XRPCRouter, json } from '@atcute/xrpc-server'; 2 2 import { drizzle } from "drizzle-orm/postgres-js"; 3 3 import * as dbschema from "db/schema" 4 + import type { BlobRef } from "db/schema" 4 5 import { cors } from '@atcute/xrpc-server/middlewares/cors'; 5 - import { parseCanonicalResourceUri, parseResourceUri } from "@atcute/lexicons" 6 6 7 7 import { CaAnsxorCatnipGetTracks, CaAnsxorCatnipTrack } from 'lexicon/atcute-lexicon'; 8 + import type { InferOutput } from '@atcute/lexicons/validations'; 9 + import type { Blob } from '@atcute/lexicons'; 8 10 import { inArray } from 'drizzle-orm'; 9 11 10 - function schemaCollection<T extends { mainSchema: { object: { shape: { $type: { expected: string}}}}}>(schema: T) { 11 - return schema.mainSchema.object.shape.$type.expected; 12 + type TrackOutput = InferOutput<typeof CaAnsxorCatnipTrack.mainSchema>; 13 + 14 + type DbTrack = Awaited<ReturnType<typeof db.query.tracks.findMany>>[number] & { 15 + trackArtists: { 16 + artist: { did: string | null; name: string | null }; 17 + position: number; 18 + }[]; 19 + }; 20 + 21 + function blobRefToBlob(ref: BlobRef): Blob { 22 + return { 23 + $type: 'blob', 24 + mimeType: ref.mimeType, 25 + ref: { $link: ref.ref.$link }, 26 + size: ref.size, 27 + }; 28 + } 29 + 30 + function dbTrackToLexicon(track: DbTrack) { 31 + return { 32 + $type: 'ca.ansxor.catnip.track', 33 + title: track.title, 34 + description: track.description ?? undefined, 35 + createdAt: track.createdAt.toISOString(), 36 + releaseDate: track.releaseDate?.toISOString(), 37 + durationMs: track.durationMs ?? undefined, 38 + artists: track.trackArtists 39 + .sort((a, b) => a.position - b.position) 40 + .map(({ artist }) => ({ 41 + did: artist.did?.match(/^did:.+:.+$/) ? artist.did : undefined, 42 + name: artist.name ?? undefined, 43 + } as InferOutput<CaAnsxorCatnipTrack.artistCreditSchema>)), 44 + tags: track.tags ?? undefined, 45 + language: track.language ?? undefined, 46 + license: track.license ?? undefined, 47 + lyrics: track.lyrics ?? undefined, 48 + albumArt: track.albumArt ? blobRefToBlob(track.albumArt) : undefined, 49 + audio: blobRefToBlob(track.audio!), 50 + externalUrl: track.externalUrl as TrackOutput["externalUrl"], 51 + } satisfies TrackOutput; 12 52 } 13 53 14 54 const db = drizzle("postgresql://postgres:postgres@0.0.0.0:5432/catnip", { schema: dbschema }); ··· 28 68 }, 29 69 }); 30 70 31 - return json({ tracks }); 71 + return json({ tracks: tracks.map(t => dbTrackToLexicon(t as DbTrack)) }); 32 72 }, 33 73 }); 34 74
+2
apps/frontend/package.json
··· 10 10 "preview": "vite preview" 11 11 }, 12 12 "dependencies": { 13 + "@atcute/client": "^4.2.1", 13 14 "@tanstack/react-router": "^1.166.3", 14 15 "@tanstack/react-router-devtools": "^1.166.3", 16 + "lexicon": "workspace:*", 15 17 "react": "^19.2.0", 16 18 "react-dom": "^19.2.0" 17 19 },
+17
apps/frontend/src/routes/index.tsx
··· 1 + import { Client, simpleFetchHandler } from '@atcute/client' 1 2 import { createFileRoute } from '@tanstack/react-router' 3 + import type {} from "lexicon/atcute-lexicon" 4 + 5 + const rpc = new Client({ handler: simpleFetchHandler({ service: 'http://localhost:3000' })}) 2 6 3 7 export const Route = createFileRoute('/')({ 4 8 component: Index, 9 + loader: async () => { 10 + const result = await rpc.get("ca.ansxor.catnip.getTracks", { 11 + params: { 12 + uris: ["owo"] 13 + } 14 + }) 15 + if (!result.ok) { 16 + throw new Error("noooo") 17 + } 18 + return result.data; 19 + } 5 20 }) 6 21 7 22 function Index() { 23 + const data = Route.useLoaderData(); 24 + 8 25 return ( 9 26 <div className="p-2"> 10 27 <h3>Welcome Home!</h3>
+4 -1
apps/frontend/tsconfig.json
··· 3 3 "references": [ 4 4 { "path": "./tsconfig.app.json" }, 5 5 { "path": "./tsconfig.node.json" } 6 - ] 6 + ], 7 + "compilerOptions": { 8 + "types": ["lexicon/atcute-lexicon"] 9 + } 7 10 }
+4
bun.lock
··· 35 35 "name": "frontend", 36 36 "version": "0.0.0", 37 37 "dependencies": { 38 + "@atcute/client": "^4.2.1", 38 39 "@tanstack/react-router": "^1.166.3", 39 40 "@tanstack/react-router-devtools": "^1.166.3", 41 + "lexicon": "workspace:*", 40 42 "react": "^19.2.0", 41 43 "react-dom": "^19.2.0", 42 44 }, ··· 86 88 "@atcute/cbor": ["@atcute/cbor@2.3.2", "", { "dependencies": { "@atcute/cid": "^2.4.1", "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1" } }, "sha512-xP2SORSau/VVI00x2V4BjwIkHr6EQ7l/MXEOPaa4LGYtePFc4gnD4L1yN10dT5NEuUnvGEuCh6arLB7gz1smVQ=="], 87 89 88 90 "@atcute/cid": ["@atcute/cid@2.4.1", "", { "dependencies": { "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1" } }, "sha512-bwhna69RCv7yetXudtj+2qrMPYvhhIQqvJz6YUpUS98v7OdF3X2dnye9Nig2NDrklZcuyOsu7sQo7GOykJXRLQ=="], 91 + 92 + "@atcute/client": ["@atcute/client@4.2.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.6" } }, "sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw=="], 89 93 90 94 "@atcute/crypto": ["@atcute/crypto@2.4.0", "", { "dependencies": { "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1", "@noble/secp256k1": "^3.0.0" } }, "sha512-XtEeDaSgfr92C7b1VDRvd3F9pI8tVUyy8PJAeu8IWQC7+e/GXZOSl58uWh5YP/9p1Lsa0I16uKHwogygxEwlMQ=="], 91 95