forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 🎵
1import { Hono } from "hono";
2import { ctx } from "context";
3import users from "schema/users";
4import albums, { type SelectAlbum } from "schema/albums";
5import artists, { type SelectArtist } from "schema/artists";
6import tracks, { type SelectTrack } from "schema/tracks";
7import scrobbles from "schema/scrobbles";
8import { eq, or } from "drizzle-orm";
9
10const app = new Hono();
11
12app.get("/", async (c) => {
13 const path = c.req.query("path");
14
15 if (!path) {
16 return c.text("OG Service: please provide a path query parameter.", 400);
17 }
18
19 let m = path.match(/^\/profile\/([^/]+)$/);
20 if (m) {
21 const handle = decodeURIComponent(m[1]);
22 const user = await ctx.db
23 .select()
24 .from(users)
25 .where(or(eq(users.handle, handle), eq(users.did, handle)))
26 .limit(1)
27 .execute()
28 .then(([row]) => row);
29 if (!user) {
30 return c.text("OG Service: user not found.", 404);
31 }
32 return c.json({
33 title: `@${user.handle} on Rocksky`,
34 description:
35 "Rocksky user profile — recent scrobbles, top artists, albums, and tracks.",
36 image: user.avatar.endsWith("/@jpeg") ? undefined : user.avatar,
37 url: `https://rocksky.app/profile/${user.handle}`,
38 type: "website",
39 twitterCard: "summary_large_image",
40 });
41 }
42
43 m = path.match(/^\/(did:plc:[^/]+)\/(scrobble|album|artist|song)\/([^/]+)$/);
44 if (m) {
45 const did = decodeURIComponent(m[1]);
46 const kind = m[2] as "scrobble" | "album" | "artist" | "song";
47 const rkey = decodeURIComponent(m[3]);
48 const uri = `at://${did}/app.rocksky.${kind}/${rkey}`;
49
50 const tableMap = {
51 scrobble: scrobbles,
52 album: albums,
53 artist: artists,
54 song: tracks,
55 };
56
57 const table = tableMap[kind];
58 if (kind === "scrobble") {
59 const record = await ctx.db
60 .select({
61 scrobbles: scrobbles,
62 users: users,
63 tracks: tracks,
64 artists: artists,
65 albums: albums,
66 })
67 .from(table)
68 .where(eq(table.uri, uri))
69 .leftJoin(users, eq(scrobbles.userId, users.id))
70 .leftJoin(tracks, eq(scrobbles.trackId, tracks.id))
71 .leftJoin(artists, eq(scrobbles.artistId, artists.id))
72 .leftJoin(albums, eq(scrobbles.albumId, albums.id))
73 .limit(1)
74 .execute()
75 .then(([row]) => row);
76
77 if (!record) {
78 return c.text("OG Service: record not found.", 404);
79 }
80
81 return c.json({
82 title: `Scrobble: ${record.tracks.title} by ${record.artists.name}`,
83 description: `A listening activity (scrobble) - ${record.tracks.title} - ${record.artists.name} by @${record.users.handle} on Rocksky.`,
84 image: record.albums.albumArt,
85 url: `https://rocksky.app/${did}/scrobble/${rkey}`,
86 type: "website",
87 twitterCard: "summary_large_image",
88 });
89 }
90
91 const record = await ctx.db
92 .select()
93 .from(table)
94 .where(eq(table.uri, uri))
95 .limit(1)
96 .execute()
97 .then(([row]) => row);
98 if (!record) {
99 return c.text("OG Service: record not found.", 404);
100 }
101
102 let title;
103 let description;
104 let image;
105 const url = `https://rocksky.app/${did}/${kind}/${rkey}`;
106
107 if (kind === "album") {
108 title = `Album: ${(record as SelectAlbum).title} by ${(record as SelectAlbum).artist}`;
109 description = `See listening stats and favorites for ${(record as SelectAlbum).title} by ${(record as SelectAlbum).artist} on Rocksky.`;
110 image = (record as SelectAlbum).albumArt;
111 }
112
113 if (kind === "artist") {
114 title = `Artist: ${(record as SelectArtist).name}`;
115 description = `See listening stats and favorites for ${(record as SelectArtist).name} on Rocksky.`;
116 image = (record as SelectArtist).picture;
117 }
118
119 if (kind === "song") {
120 title = `Track: ${(record as SelectTrack).title} by ${(record as SelectTrack).artist}`;
121 description = `See listening stats and favorites for ${(record as SelectTrack).title} by ${(record as SelectTrack).artist} on Rocksky.`;
122 image = (record as SelectTrack).albumArt;
123 }
124
125 return c.json({
126 title,
127 description,
128 image,
129 url,
130 type: "website",
131 twitterCard: "summary_large_image",
132 });
133 }
134
135 return c.text("OG Service: unsupported path format.", 400);
136});
137
138export default app;