tangled
alpha
login
or
join now
ansxor.ca
/
catnip
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
wow i got far into making a route but so much work...
ansxor.ca
1 week ago
b02f81a3
162489e3
+71
-5
5 changed files
expand all
collapse all
unified
split
apps
api
index.ts
frontend
package.json
src
routes
index.tsx
tsconfig.json
bun.lock
+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
4
+
import type { BlobRef } from "db/schema"
4
5
import { cors } from '@atcute/xrpc-server/middlewares/cors';
5
5
-
import { parseCanonicalResourceUri, parseResourceUri } from "@atcute/lexicons"
6
6
7
7
import { CaAnsxorCatnipGetTracks, CaAnsxorCatnipTrack } from 'lexicon/atcute-lexicon';
8
8
+
import type { InferOutput } from '@atcute/lexicons/validations';
9
9
+
import type { Blob } from '@atcute/lexicons';
8
10
import { inArray } from 'drizzle-orm';
9
11
10
10
-
function schemaCollection<T extends { mainSchema: { object: { shape: { $type: { expected: string}}}}}>(schema: T) {
11
11
-
return schema.mainSchema.object.shape.$type.expected;
12
12
+
type TrackOutput = InferOutput<typeof CaAnsxorCatnipTrack.mainSchema>;
13
13
+
14
14
+
type DbTrack = Awaited<ReturnType<typeof db.query.tracks.findMany>>[number] & {
15
15
+
trackArtists: {
16
16
+
artist: { did: string | null; name: string | null };
17
17
+
position: number;
18
18
+
}[];
19
19
+
};
20
20
+
21
21
+
function blobRefToBlob(ref: BlobRef): Blob {
22
22
+
return {
23
23
+
$type: 'blob',
24
24
+
mimeType: ref.mimeType,
25
25
+
ref: { $link: ref.ref.$link },
26
26
+
size: ref.size,
27
27
+
};
28
28
+
}
29
29
+
30
30
+
function dbTrackToLexicon(track: DbTrack) {
31
31
+
return {
32
32
+
$type: 'ca.ansxor.catnip.track',
33
33
+
title: track.title,
34
34
+
description: track.description ?? undefined,
35
35
+
createdAt: track.createdAt.toISOString(),
36
36
+
releaseDate: track.releaseDate?.toISOString(),
37
37
+
durationMs: track.durationMs ?? undefined,
38
38
+
artists: track.trackArtists
39
39
+
.sort((a, b) => a.position - b.position)
40
40
+
.map(({ artist }) => ({
41
41
+
did: artist.did?.match(/^did:.+:.+$/) ? artist.did : undefined,
42
42
+
name: artist.name ?? undefined,
43
43
+
} as InferOutput<CaAnsxorCatnipTrack.artistCreditSchema>)),
44
44
+
tags: track.tags ?? undefined,
45
45
+
language: track.language ?? undefined,
46
46
+
license: track.license ?? undefined,
47
47
+
lyrics: track.lyrics ?? undefined,
48
48
+
albumArt: track.albumArt ? blobRefToBlob(track.albumArt) : undefined,
49
49
+
audio: blobRefToBlob(track.audio!),
50
50
+
externalUrl: track.externalUrl as TrackOutput["externalUrl"],
51
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
31
-
return json({ tracks });
71
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
13
+
"@atcute/client": "^4.2.1",
13
14
"@tanstack/react-router": "^1.166.3",
14
15
"@tanstack/react-router-devtools": "^1.166.3",
16
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
1
+
import { Client, simpleFetchHandler } from '@atcute/client'
1
2
import { createFileRoute } from '@tanstack/react-router'
3
3
+
import type {} from "lexicon/atcute-lexicon"
4
4
+
5
5
+
const rpc = new Client({ handler: simpleFetchHandler({ service: 'http://localhost:3000' })})
2
6
3
7
export const Route = createFileRoute('/')({
4
8
component: Index,
9
9
+
loader: async () => {
10
10
+
const result = await rpc.get("ca.ansxor.catnip.getTracks", {
11
11
+
params: {
12
12
+
uris: ["owo"]
13
13
+
}
14
14
+
})
15
15
+
if (!result.ok) {
16
16
+
throw new Error("noooo")
17
17
+
}
18
18
+
return result.data;
19
19
+
}
5
20
})
6
21
7
22
function Index() {
23
23
+
const data = Route.useLoaderData();
24
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
-
]
6
6
+
],
7
7
+
"compilerOptions": {
8
8
+
"types": ["lexicon/atcute-lexicon"]
9
9
+
}
7
10
}
+4
bun.lock
···
35
35
"name": "frontend",
36
36
"version": "0.0.0",
37
37
"dependencies": {
38
38
+
"@atcute/client": "^4.2.1",
38
39
"@tanstack/react-router": "^1.166.3",
39
40
"@tanstack/react-router-devtools": "^1.166.3",
41
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
91
+
92
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