Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

move to postgres

+2300 -2677
+10
apps/amethyst/app/(tabs)/_layout.tsx
··· 8 8 //import useIsMobile from "@/hooks/useIsMobile"; 9 9 import { useStore } from "@/stores/mainStore"; 10 10 import { useColorScheme } from "nativewind"; 11 + import AuthOptions from "../auth/options"; 11 12 12 13 function TabBarIcon(props: { name: LucideIcon; color: string }) { 13 14 const Name = props.name; ··· 20 21 const authStatus = useStore((state) => state.status); 21 22 // if we are on web but not native and web width is greater than 1024px 22 23 const hideTabBar = authStatus !== "loggedIn"; // || useIsMobile() 24 + 25 + const j = useStore((state) => state.status); 26 + // @me 27 + const agent = useStore((state) => state.pdsAgent); 28 + const profile = useStore((state) => state.profiles[agent?.did ?? ""]); 29 + 30 + if (j !== "loggedIn") { 31 + return <AuthOptions />; 32 + } 23 33 24 34 return ( 25 35 <Tabs
+1 -1
apps/aqua/.env.example
··· 2 2 PORT=3000 3 3 HOST=localhost 4 4 PUBLIC_URL= 5 - DB_PATH=:memory 5 + DATABASE_URL=
apps/aqua/bun.lockb

This is a binary file and will not be displayed.

-28
apps/aqua/src/auth/client.ts
··· 1 - import { NodeOAuthClient } from "@atproto/oauth-client-node"; 2 - import type { Database } from "@teal/db/connect"; 3 - import { env } from "@/lib/env"; 4 - import { SessionStore, StateStore } from "./storage"; 5 - 6 - import { db } from "@teal/db/connect"; 7 - 8 - const publicUrl = env.PUBLIC_URL; 9 - const url = publicUrl || `http://127.0.0.1:${env.PORT}`; 10 - const enc = encodeURIComponent; 11 - export const atclient = new NodeOAuthClient({ 12 - clientMetadata: { 13 - client_name: "teal", 14 - client_id: publicUrl 15 - ? `${url}/client-metadata.json` 16 - : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 17 - client_uri: url, 18 - redirect_uris: [`${url}/oauth/callback`, `${url}/oauth/callback/app`], 19 - scope: "atproto transition:generic", 20 - grant_types: ["authorization_code", "refresh_token"], 21 - response_types: ["code"], 22 - application_type: "web", 23 - token_endpoint_auth_method: "none", 24 - dpop_bound_access_tokens: true, 25 - }, 26 - stateStore: new StateStore(db), 27 - sessionStore: new SessionStore(db), 28 - });
-166
apps/aqua/src/auth/router.ts
··· 1 - import { atclient } from "./client"; 2 - import { db } from "@teal/db/connect"; 3 - import { atProtoSession } from "@teal/db/schema"; 4 - import { eq } from "drizzle-orm"; 5 - import { EnvWithCtx, TealContext } from "@/ctx"; 6 - import { Hono } from "hono"; 7 - import { tealSession } from "@teal/db/schema"; 8 - import { setCookie } from "hono/cookie"; 9 - import { env } from "@/lib/env"; 10 - 11 - const publicUrl = env.PUBLIC_URL; 12 - const redirectBase = publicUrl || `http://127.0.0.1:${env.PORT}`; 13 - 14 - export function generateState(prefix?: string) { 15 - const state = crypto.randomUUID(); 16 - return `${prefix}${prefix ? ":" : ""}${state}`; 17 - } 18 - 19 - const SPA_PREFIX = "a37d"; 20 - 21 - // /oauth/login?handle=teal.fm 22 - export async function login(c: TealContext) { 23 - const { handle, spa } = c.req.query(); 24 - if (!handle) { 25 - return Response.json({ error: "Missing handle" }); 26 - } 27 - const url = await atclient.authorize(handle, { 28 - scope: "atproto transition:generic", 29 - // state.appState in callback 30 - state: generateState(spa ? SPA_PREFIX : undefined), 31 - }); 32 - return c.json({ url }); 33 - } 34 - 35 - // Redirect to the app's callback URL. 36 - async function callbackToApp(c: TealContext) { 37 - const queries = c.req.query(); 38 - const params = new URLSearchParams(queries); 39 - return c.redirect(`${env.APP_URI}/oauth/callback?${params.toString()}`); 40 - } 41 - 42 - export async function callback(c: TealContext, isSpa: boolean = false) { 43 - try { 44 - const honoParams = c.req.query(); 45 - console.log("params", honoParams); 46 - const params = new URLSearchParams(honoParams); 47 - 48 - const { session, state } = await atclient.callback(params); 49 - 50 - console.log("state", state); 51 - 52 - const did = session.did; 53 - 54 - // Process successful authentication here 55 - console.log("User authenticated as:", did); 56 - 57 - // gen opaque tealSessionKey 58 - const sess = crypto.randomUUID(); 59 - await db 60 - .insert(tealSession) 61 - .values({ 62 - key: sess, 63 - // ATP session key (DID) 64 - session: JSON.stringify(did), 65 - provider: "atproto", 66 - }) 67 - .execute(); 68 - 69 - // cookie time 70 - console.log("Setting cookie", sess); 71 - setCookie(c, "tealSession", "teal:" + sess, { 72 - httpOnly: true, 73 - secure: env.HOST.startsWith("https"), 74 - sameSite: "lax", 75 - path: "/", 76 - maxAge: 60 * 60 * 24 * 365, 77 - }); 78 - 79 - if (isSpa) { 80 - return c.json({ 81 - provider: "atproto", 82 - jwt: did, 83 - accessToken: did, 84 - }); 85 - } 86 - 87 - return c.redirect("/"); 88 - } catch (e) { 89 - console.error(e); 90 - return Response.json({ error: "Could not authorize user" }); 91 - } 92 - } 93 - 94 - // Refresh an access token from a refresh token. Should be only used in SPAs. 95 - // Pass in 'key' and 'refresh_token' query params. 96 - export async function refresh(c: TealContext) { 97 - try { 98 - const honoParams = c.req.query(); 99 - console.log("params", honoParams); 100 - const params = new URLSearchParams(honoParams); 101 - let key = params.get("key"); 102 - let refresh_token = params.get("refresh_token"); 103 - if (!key || !refresh_token) { 104 - return Response.json({ error: "Missing key or refresh_token" }); 105 - } 106 - // check if refresh token is valid 107 - let r_tk_check = (await db 108 - .select() 109 - .from(atProtoSession) 110 - .where(eq(atProtoSession.key, key)) 111 - .execute()) as any; 112 - 113 - if (r_tk_check.tokenSet.refresh_token !== refresh_token) { 114 - return Response.json({ error: "Invalid refresh token" }); 115 - } 116 - 117 - const session = await atclient.restore(key); 118 - 119 - const did = session.did; 120 - 121 - // Process successful authentication here 122 - console.log("User authenticated as:", did); 123 - 124 - // gen opaque tealSessionKey 125 - const sess = crypto.randomUUID(); 126 - await db 127 - .insert(tealSession) 128 - .values({ 129 - key: sess, 130 - // ATP session key (DID) 131 - session: JSON.stringify(did), 132 - provider: "atproto", 133 - }) 134 - .execute(); 135 - 136 - // cookie time 137 - console.log("Setting cookie", sess); 138 - setCookie(c, "tealSession", "teal:" + sess, { 139 - httpOnly: true, 140 - secure: env.HOST.startsWith("https"), 141 - sameSite: "lax", 142 - path: "/", 143 - maxAge: 60 * 60 * 24 * 365, 144 - }); 145 - 146 - return c.json({ 147 - provider: "atproto", 148 - jwt: did, 149 - accessToken: did, 150 - }); 151 - } catch (e) { 152 - console.error(e); 153 - return Response.json({ error: "Could not authorize user" }); 154 - } 155 - } 156 - 157 - const app = new Hono<EnvWithCtx>(); 158 - 159 - app.get("/login", async (c) => login(c)); 160 - app.get("/callback", async (c) => callback(c)); 161 - app.get("/callback/app", async (c) => callback(c, true)); 162 - app.get("/refresh", async (c) => refresh(c)); 163 - 164 - export const getAuthRouter = () => { 165 - return app; 166 - };
-71
apps/aqua/src/auth/storage.ts
··· 1 - import type { 2 - NodeSavedSession, 3 - NodeSavedSessionStore, 4 - NodeSavedState, 5 - NodeSavedStateStore, 6 - } from "@atproto/oauth-client-node"; 7 - import type { Database } from "@teal/db/connect"; 8 - import { atProtoSession, authState } from "@teal/db/schema"; 9 - import { eq } from "drizzle-orm"; 10 - 11 - export class StateStore implements NodeSavedStateStore { 12 - constructor(private db: Database) {} 13 - async get(key: string): Promise<NodeSavedState | undefined> { 14 - const result = await this.db 15 - .select() 16 - .from(authState) 17 - .where(eq(authState.key, key)) 18 - .limit(1) 19 - .execute(); 20 - if (!result[0]) return; 21 - return JSON.parse(result[0].state) as NodeSavedState; 22 - } 23 - async set(key: string, val: NodeSavedState) { 24 - const state = JSON.stringify(val); 25 - console.log("inserting state", key, state); 26 - await this.db 27 - .insert(authState) 28 - .values({ key, state }) 29 - .onConflictDoUpdate({ 30 - set: { state: state }, 31 - target: authState.key, 32 - }) 33 - .execute(); 34 - } 35 - async del(key: string) { 36 - await this.db.delete(authState).where(eq(authState.key, key)).execute(); 37 - //.deleteFrom("auth_state").where("key", "=", key).execute(); 38 - } 39 - } 40 - 41 - export class SessionStore implements NodeSavedSessionStore { 42 - constructor(private db: Database) {} 43 - async get(key: string): Promise<NodeSavedSession | undefined> { 44 - const result = await this.db 45 - .select() 46 - .from(atProtoSession) 47 - .where(eq(atProtoSession.key, key)) 48 - .limit(1) 49 - .all(); 50 - if (!result[0]) return; 51 - return JSON.parse(result[0].session) as NodeSavedSession; 52 - } 53 - async set(key: string, val: NodeSavedSession) { 54 - const session = JSON.stringify(val); 55 - console.log("inserting session", key, session); 56 - await this.db 57 - .insert(atProtoSession) 58 - .values({ key, session }) 59 - .onConflictDoUpdate({ 60 - set: { session: session }, 61 - target: atProtoSession.key, 62 - }) 63 - .execute(); 64 - } 65 - async del(key: string) { 66 - await this.db 67 - .delete(atProtoSession) 68 - .where(eq(atProtoSession.key, key)) 69 - .execute(); 70 - } 71 - }
+5 -10
apps/aqua/src/ctx.ts
··· 1 1 import { NodeOAuthClient } from "@atproto/oauth-client-node"; 2 - import { Client } from "@libsql/client/."; 3 - import { LibSQLDatabase } from "drizzle-orm/libsql"; 2 + import { PostgresJsDatabase } from "drizzle-orm/postgres-js"; 4 3 import { Context, Next } from "hono"; 5 4 import { Logger } from "pino"; 6 - import { atclient } from "./auth/client"; 5 + 6 + import { db as tdb } from "@teal/db"; 7 7 8 8 export type TealContext = Context<EnvWithCtx, any, any>; 9 9 ··· 13 13 14 14 export type Ctx = { 15 15 auth: NodeOAuthClient; 16 - db: LibSQLDatabase<typeof import("@teal/db/schema")> & { 17 - $client: Client; 18 - }; 16 + db: typeof tdb; 19 17 logger: Logger<never, boolean>; 20 18 }; 21 19 22 20 export const setupContext = async ( 23 21 c: TealContext, 24 - db: LibSQLDatabase<typeof import("@teal/db/schema")> & { 25 - $client: Client; 26 - }, 22 + db: typeof tdb, 27 23 logger: Logger<never, boolean>, 28 24 next: Next, 29 25 ) => { 30 26 c.set("db", db); 31 - c.set("auth", atclient); 32 27 c.set("logger", logger); 33 28 await next(); 34 29 };
+40 -258
apps/aqua/src/index.ts
··· 1 + import { env } from "./lib/env"; 2 + 1 3 import { serve } from "@hono/node-server"; 2 4 import { serveStatic } from "@hono/node-server/serve-static"; 3 5 import { Hono } from "hono"; 4 6 import { db } from "@teal/db/connect"; 5 - import { getAuthRouter } from "./auth/router"; 6 7 import pino from "pino"; 7 8 import { EnvWithCtx, setupContext, TealContext } from "./ctx"; 8 - import { env } from "./lib/env"; 9 - import { getCookie, deleteCookie } from "hono/cookie"; 10 - import { atclient } from "./auth/client"; 11 - import { getSessionAgent } from "./lib/auth"; 12 - import { RichText } from "@atproto/api"; 13 - import { sanitizeUrl } from "@braintree/sanitize-url"; 14 9 import { getXrpcRouter } from "./xrpc/route"; 15 10 16 - const HEAD = `<head> 17 - <link rel="stylesheet" href="/latex.css"> 18 - </head>`; 19 - 20 11 const logger = pino({ name: "server start" }); 21 12 22 13 const app = new Hono<EnvWithCtx>(); 23 14 24 15 app.use((c, next) => setupContext(c, db, logger, next)); 25 16 26 - app.route("/oauth", getAuthRouter()); 17 + const HOME_TEXT = ` 27 18 28 - app.route("/xrpc", getXrpcRouter()); 19 + █████ 20 + ███ ███ 21 + ██ ▐▌ ██ ██▌ 22 + █ ██ █ ▐█ █▌ 23 + █ ███████ █ ██ 24 + █ ██ █ ▐█ ▐▄▄▄ ▄▄▄ 25 + █ ██ █ ▐█ ▐█▌ ██ █▌ ██ 26 + █ ██ █ █████ █▌ ▐█▌ ▐█ 27 + █ ██ █ ██ █▌ ▐█ ▐█ ▐█ 28 + ██ ███ █████ █▌ █▌ █▌ █▌▐ 29 + ███ █████■██ ██ █ █ ▐█▀ 30 + █████ ███ █▌ 31 + ▐█ █▌ 32 + ▐██ 29 33 30 - app.get("/client-metadata.json", (c) => { 31 - return c.json(atclient.clientMetadata); 32 - }); 34 + You have reached 'aqua', an AT Protocol Application View (AppView) 35 + for the 'teal.fm' application. 33 36 34 - app.get("/", async (c) => { 35 - const tealSession = getCookie(c, "tealSession"); 37 + Most API routes are under /xrpc/ 36 38 37 - // Serve logged in content 38 - if (tealSession) { 39 - // const followers = await agent?.getFollowers(); 40 - return c.html( 41 - ` 42 - ${HEAD} 43 - <div id="root"> 44 - <div id="header" style="display: flex; flex-direction: column; gap: 0.5rem; width: 100%;"> 45 - <div> 46 - <h1>teal.fm</h1> 47 - <p>Your music, beautifully tracked. (soon.)</p> 48 - </div> 49 - <div style=" width: 100%; display: flex; flex-direction: row; justify-content: space-between; gap: 0.5rem;"> 50 - <div> 51 - <a href="/">home</a> 52 - <a href="/stamp">stamp</a> 53 - </div> 54 - <form action="/logout" method="post" class="session-form"> 55 - <button type="submit" style="background-color: #cc0000; color: white; border: none; padding: 0rem 0.5rem; border-radius: 0.5rem;">logout</button> 56 - </form> 57 - </div> 58 - </div> 59 - <div class="container"> 39 + Code: <a href="https://github.com/teal-fm/teal">github.com/teal-fm/teal</a> 40 + Protocol: <a href="https://atproto.com">atproto.com</a> 60 41 61 - </div> 62 - </div>`, 63 - ); 64 - } 42 + Visit <a href="https://docs.teal.fm">docs.teal.fm</a> for more information.`; 65 43 66 - // Serve non-logged in content 67 - return c.html( 68 - ` 69 - ${HEAD} 70 - <div id="root"> 71 - <div id="header"> 72 - <h1>teal.fm</h1> 73 - <p>Your music, beautifully tracked. (soon.)</p> 74 - <div style=" width: 100%; display: flex; flex-direction: row; justify-content: space-between; gap: 0.5rem;"> 75 - <div> 76 - <a href="/">home</a> 77 - <a href="/stamp">stamp</a> 78 - </div> 79 - <button style="background-color: #acf; color: white; border: none; padding: 0rem 0.5rem; border-radius: 0.5rem;"><a href="/login">Login</a></button> 80 - </div> 81 - </div> 82 - <div class="container"> 83 - <div class="signup-cta"> 84 - Don't have an account on the Atmosphere? 85 - <a href="https://bsky.app">Sign up for Bluesky</a> to create one now! 86 - </div> 87 - </div> 88 - </div>`, 89 - ); 90 - }); 91 - 92 - app.get("/login", (c) => { 93 - const tealSession = getCookie(c, "tealSession"); 94 - 95 - return c.html( 96 - ` 97 - ${HEAD} 98 - <div id="root"> 99 - <div id="header"> 100 - <h1>teal.fm</h1> 101 - <p>Your music, beautifully tracked. (soon.)</p> 102 - <div style=" width: 100%; display: flex; flex-direction: row; justify-content: space-between; gap: 0.5rem;"> 103 - <div> 104 - <a href="/">home</a> 105 - <a href="/stamp">stamp</a> 106 - </div> 107 - <div /> 108 - </div> 109 - </div> 110 - <div class="container"> 111 - <form action="/login" method="post" class="login-form"> 112 - <input 113 - type="text" 114 - name="handle" 115 - placeholder="Enter your handle (eg alice.bsky.social)" 116 - required 117 - /> 118 - <button type="submit">Log in</button> 119 - </form> 120 - <div class="signup-cta"> 121 - Don't have an account on the Atmosphere? 122 - <a href="https://bsky.app">Sign up for Bluesky</a> to create one now! 123 - </div> 124 - </div> 125 - </div>`, 126 - ); 127 - }); 128 - 129 - app.post("/login", async (c: TealContext) => { 130 - const body = await c.req.parseBody(); 131 - let { handle } = body; 132 - // shouldn't be a file, escape now 133 - if (handle instanceof File) return c.redirect("/login"); 134 - handle = sanitizeUrl(handle); 135 - console.log("handle", handle); 136 - // Initiate the OAuth flow 137 - try { 138 - console.log("Calling authorize"); 139 - if (typeof handle === "string") { 140 - const url = await atclient.authorize(handle, { 141 - scope: "atproto transition:generic", 142 - }); 143 - console.log("Redirecting to oauth login page"); 144 - console.log(url); 145 - return Response.redirect(url); 146 - } 147 - } catch (e) { 148 - console.error(e); 149 - return Response.json({ error: "Could not authorize user" }); 44 + const HOME_STYLES = ` 45 + body { 46 + background-color: #000; 47 + color: #ddd; 150 48 } 151 - }); 152 - 153 - app.post("/logout", (c) => { 154 - deleteCookie(c, "tealSession"); 155 - // TODO: delete session record from db?? 156 - return c.redirect("/"); 157 - }); 158 - 159 - app.get("/stamp", (c) => { 160 - // check logged in 161 - const tealSession = getCookie(c, "tealSession"); 162 - if (!tealSession) { 163 - return c.redirect("/login"); 49 + a { 50 + color: #aaf; 51 + text-decoration: none; 164 52 } 165 - return c.html( 166 - ` 167 - ${HEAD} 168 - <div id="root"> 169 - <div id="header"> 170 - <h1>teal.fm</h1> 171 - <p>Your music, beautifully tracked. (soon.)</p> 172 - <div style=" width: 100%; display: flex; flex-direction: row; justify-content: space-between; gap: 0.5rem;"> 173 - <div> 174 - <a href="/">home</a> 175 - <a href="/stamp">stamp</a> 176 - </div> 177 - <form action="/logout" method="post" class="session-form"> 178 - <button type="submit" style="background-color: #cc0000; color: white; border: none; padding: 0rem 0.5rem; border-radius: 0.5rem;">logout</button> 179 - </form> 180 - </div> 181 - </div> 182 - <div class="container"> 183 - <p>🛠️ while we're building our music tracker, share what you're listening to here!<br/> 184 - <a href="https://emojipedia.org/white-flower">💮</a> we'll create a post on Bluesky for you to share with the world!<br/>​</p> 185 - <form action="/stamp" method="post" class="login-form" style="display: flex; flex-direction: column; gap: 0.5rem;"> 186 - <input 187 - type="text" 188 - name="artist" 189 - placeholder="artist name (eg blink-182)" 190 - required 191 - /> 192 - <input 193 - type="text" 194 - name="track" 195 - placeholder="track title (eg what's my age again?)" 196 - required 197 - /> 198 - <input 199 - type="text" 200 - name="link" 201 - placeholder="https://www.youtube.com/watch?v=K7l5ZeVVoCA" 202 - /> 203 - <button type="submit" style="width: 15%">Stamp!</button> 204 - </form> 205 - <div class="signup-cta"> 206 - Don't have an account on the Atmosphere? 207 - <a href="https://bsky.app">Sign up for Bluesky</a> to create one now! 208 - </div> 209 - </div> 210 - </div>`, 211 - ); 212 - }); 53 + a:hover { 54 + text-decoration: underline; 55 + } 56 + `; 213 57 214 - app.post("/stamp", async (c: TealContext) => { 215 - const body = await c.req.parseBody(); 216 - let { artist, track, link } = body; 217 - // shouldn't get a File, escape now 218 - if (artist instanceof File || track instanceof File || link instanceof File) 219 - return c.redirect("/stamp"); 220 - 221 - artist = sanitizeUrl(artist); 222 - track = sanitizeUrl(track); 223 - link = sanitizeUrl(link); 224 - 225 - const agent = await getSessionAgent(c); 226 - 227 - if (agent) { 228 - const rt = new RichText({ 229 - text: `💮 now playing: 230 - artist: ${artist} 231 - track: ${track} 232 - 233 - powered by @teal.fm`, 234 - }); 235 - await rt.detectFacets(agent); 236 - 237 - let embed = undefined; 238 - if (link) { 239 - embed = { 240 - $type: "app.bsky.embed.external", 241 - external: { 242 - uri: link, 243 - title: track, 244 - description: `${artist} - ${track}`, 245 - }, 246 - }; 247 - } 248 - const post = await agent.post({ 249 - text: rt.text, 250 - facets: rt.facets, 251 - embed: embed, 252 - }); 58 + app.get("/", (c) => 59 + c.html( 60 + `<html><head><style>${HOME_STYLES}</style></head><body><pre>${HOME_TEXT}</pre></body></html>`, 61 + ), 62 + ); 253 63 254 - return c.html( 255 - ` 256 - ${HEAD} 257 - <div id="root"> 258 - <div id="header"> 259 - <h1>teal.fm</h1> 260 - <p>Your music, beautifully tracked. (soon.)</p> 261 - <div style=" width: 100%; display: flex; flex-direction: row; justify-content: space-between; gap: 0.5rem;"> 262 - <div> 263 - <a href="/">home</a> 264 - <a href="/stamp">stamp</a> 265 - </div> 266 - <form action="/logout" method="post" class="session-form"> 267 - <button type="submit" style="background-color: #cc0000; color: white; border: none; padding: 0rem 0.5rem; border-radius: 0.5rem;">logout</button> 268 - </form> 269 - </div> 270 - </div> 271 - <div class="container"> 272 - <h2 class="stamp-success">Success! 🎉</h2> 273 - <p>Your post is being tracked by the Atmosphere.</p> 274 - <p>You can view it <a href="https://bsky.app/profile/${agent.did}/post/${post.uri.split("/").pop()}">here</a>.</p> 275 - </div> 276 - </div>`, 277 - ); 278 - } 279 - return c.html( 280 - `<h1>doesn't look like you're logged in... try <a href="/login">logging in?</a></h1>`, 281 - ); 282 - }); 64 + app.route("/xrpc", getXrpcRouter()); 283 65 284 66 app.use("/*", serveStatic({ root: "/public" })); 285 67
-77
apps/aqua/src/lib/auth.ts
··· 1 - import { TealContext } from "@/ctx"; 2 - import { db } from "@teal/db/connect"; 3 - import { Session } from "@atproto/oauth-client-node"; 4 - import { tealSession } from "@teal/db/schema"; 5 - import { eq } from "drizzle-orm"; 6 - import { deleteCookie, getCookie } from "hono/cookie"; 7 - import { atclient } from "@/auth/client"; 8 - import { Agent } from "@atproto/api"; 9 - 10 - interface UserSession { 11 - did: string; 12 - /// The session JWT from ATProto 13 - session: Session; 14 - } 15 - 16 - interface UserInfo { 17 - did: string; 18 - handle: string; 19 - } 20 - 21 - export async function getUserInfo( 22 - c: TealContext 23 - ): Promise<UserInfo | undefined> { 24 - // init session agent 25 - const agent = await getSessionAgent(c); 26 - if (agent && agent.did) { 27 - // fetch from ATProto 28 - const res = await agent.app.bsky.actor.getProfile({ 29 - actor: agent.did, 30 - }); 31 - if (res.success) { 32 - return { 33 - did: agent.did, 34 - handle: res.data.handle, 35 - }; 36 - } else { 37 - throw new Error("Failed to fetch user info"); 38 - } 39 - } 40 - } 41 - 42 - export async function getContextDID(c: TealContext): Promise<string> { 43 - let authSession = getCookie(c, "tealSession")?.split("teal:")[1]; 44 - console.log(`tealSession cookie: ${authSession}`); 45 - if (!authSession) { 46 - authSession = c.req.header("Authorization"); 47 - } 48 - if (!authSession) { 49 - throw new Error("No auth session found"); 50 - } else { 51 - // get the DID from the session 52 - const session = await db.query.tealSession.findFirst({ 53 - where: eq(tealSession.key, authSession), 54 - }).execute(); 55 - 56 - if (!session) { 57 - // we should log them out here and redirect to home to double check 58 - deleteCookie(c, "tealSession"); 59 - c.redirect("/"); 60 - throw new Error("No DID found in session"); 61 - } 62 - return session.session.replace(/['"]/g, ""); 63 - } 64 - } 65 - 66 - export async function getSessionAgent(c: TealContext) { 67 - const did = await getContextDID(c); 68 - 69 - if (did != undefined) { 70 - const oauthsession = await atclient.restore(did); 71 - const agent = new Agent(oauthsession); 72 - if (!agent) { 73 - return null; 74 - } 75 - return agent; 76 - } 77 - }
+1 -1
apps/aqua/src/lib/env.ts
··· 15 15 PORT: port({ devDefault: testOnly(3000) }), 16 16 PUBLIC_URL: str({}), 17 17 APP_URI: str({ devDefault: "fm.teal.amethyst://" }), 18 - DB_PATH: str({ devDefault: "file:./db.sqlite" }), 18 + DATABASE_URL: str({ devDefault: "file:./db.sqlite" }), 19 19 COOKIE_SECRET: str({ devDefault: "secret_cookie! very secret!" }), 20 20 });
+81 -34
apps/aqua/src/xrpc/feed/getActorFeed.ts
··· 1 1 import { TealContext } from "@/ctx"; 2 - import { db, tealSession, play } from "@teal/db"; 3 - import { eq, and, lt } from "drizzle-orm"; 2 + import { artists, db, plays, playToArtists } from "@teal/db"; 3 + import { eq, and, lt, desc, sql } from "drizzle-orm"; 4 4 import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed"; 5 5 6 6 export default async function getActorFeed(c: TealContext) { ··· 9 9 throw new Error("authorDid is required"); 10 10 } 11 11 12 - let limit = 20 12 + let limit = 20; 13 13 14 - if(params.limit) { 15 - limit = Number(params.limit) 16 - if(limit > 50) throw new Error("Limit is over max allowed.") 14 + if (params.limit) { 15 + limit = Number(params.limit); 16 + if (limit > 50) throw new Error("Limit is over max allowed."); 17 17 } 18 18 19 19 // 'and' is here for typing reasons 20 - let whereClause = and(eq(play.authorDid, params.authorDid)); 20 + let whereClause = and(eq(plays.did, params.authorDid)); 21 21 22 22 // Add cursor pagination if provided 23 23 if (params.cursor) { 24 - const [cursorPlay] = await db 25 - .select({ createdAt: play.createdAt }) 26 - .from(play) 27 - .where(eq(play.uri, params.cursor)) 24 + const cursorResult = await db 25 + .select() 26 + .from(plays) 27 + .where(eq(plays.uri, params.cursor)) 28 28 .limit(1); 29 29 30 + const cursorPlay = cursorResult[0]?.playedTime; 31 + 30 32 if (!cursorPlay) { 31 33 throw new Error("Cursor not found"); 32 34 } 33 35 34 - whereClause = and(whereClause, lt(play.createdAt, cursorPlay.createdAt)); 36 + whereClause = and(whereClause, lt(plays.playedTime, cursorPlay as any)); 35 37 } 36 38 37 - const plays = await db 38 - .select() 39 - .from(play) 39 + const playRes = await db 40 + .select({ 41 + uri: plays.uri, 42 + did: plays.did, 43 + playedTime: plays.playedTime, 44 + trackName: plays.trackName, 45 + cid: plays.cid, 46 + recordingMbid: plays.recordingMbid, 47 + duration: plays.duration, 48 + releaseName: plays.releaseName, 49 + releaseMbid: plays.releaseMbid, 50 + isrc: plays.isrc, 51 + originUrl: plays.originUrl, 52 + processedTime: plays.processedTime, 53 + submissionClientAgent: plays.submissionClientAgent, 54 + musicServiceBaseDomain: plays.musicServiceBaseDomain, 55 + artists: sql<Array<{ mbid: string; name: string }>>` 56 + COALESCE 57 + array_agg( 58 + CASE WHEN ${playToArtists.artistMbid} IS NOT NULL THEN 59 + jsonb_build_object( 60 + 'mbid', ${playToArtists.artistMbid}, 61 + 'name', ${playToArtists.artistName} 62 + ) 63 + END 64 + ) FILTER (WHERE ${playToArtists.artistName} IS NOT NULL), 65 + ARRAY[]::jsonb[] 66 + ) 67 + `.as("artists"), 68 + }) 69 + .from(plays) 70 + .leftJoin(playToArtists, sql`${plays.uri} = ${playToArtists.playUri}`) 40 71 .where(whereClause) 41 - .orderBy(play.createdAt) 42 - .limit(10); 72 + .groupBy( 73 + plays.uri, 74 + plays.cid, 75 + plays.did, 76 + plays.duration, 77 + plays.isrc, 78 + plays.musicServiceBaseDomain, 79 + plays.originUrl, 80 + plays.playedTime, 81 + plays.processedTime, 82 + plays.rkey, 83 + plays.recordingMbid, 84 + plays.releaseMbid, 85 + plays.releaseName, 86 + plays.submissionClientAgent, 87 + plays.trackName, 88 + ) 89 + .orderBy(desc(plays.playedTime)) 90 + .limit(limit); 43 91 44 - if (plays.length === 0) { 92 + if (playRes.length === 0) { 45 93 throw new Error("Play not found"); 46 94 } 47 95 48 96 return { 49 - plays: plays.map( 97 + plays: playRes.map( 50 98 ({ 51 99 uri, 52 - authorDid, 53 - createdAt, 54 - indexedAt, 100 + did: authorDid, 101 + processedTime: createdAt, 102 + processedTime: indexedAt, 55 103 trackName, 56 - trackMbId, 57 - recordingMbId, 104 + cid: trackMbId, 105 + recordingMbid, 58 106 duration, 59 - artistNames, 60 - artistMbIds, 107 + artists, 61 108 releaseName, 62 - releaseMbId, 109 + releaseMbid, 63 110 isrc, 64 111 originUrl, 65 112 musicServiceBaseDomain, ··· 68 115 }) => ({ 69 116 uri, 70 117 authorDid, 71 - createdAt, 72 - indexedAt, 118 + createdAt: createdAt?.toISOString(), 119 + indexedAt: indexedAt?.toISOString(), 73 120 trackName, 74 121 trackMbId, 75 - recordingMbId, 122 + recordingMbId: recordingMbid, 76 123 duration, 77 - artistNames, 78 - artistMbIds, 124 + artistNames: artists.map((artist) => artist.name), 125 + artistMbIds: artists.map((artist) => artist.mbid), 79 126 releaseName, 80 - releaseMbId, 127 + releaseMbId: releaseMbid, 81 128 isrc, 82 129 originUrl, 83 130 musicServiceBaseDomain, 84 131 submissionClientAgent, 85 - playedTime, 132 + playedTime: playedTime?.toISOString(), 86 133 }), 87 134 ), 88 135 } as OutputSchema;
+89 -34
apps/aqua/src/xrpc/feed/getPlay.ts
··· 1 1 import { TealContext } from "@/ctx"; 2 - import { db, tealSession, play } from "@teal/db"; 3 - import { eq, and } from "drizzle-orm"; 4 - import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getPlay"; 2 + import { db, plays, playToArtists, artists } from "@teal/db"; 3 + import { eq, and, lt, desc, sql } from "drizzle-orm"; 4 + import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed"; 5 5 6 - export default async function getPlay(c: TealContext) { 7 - // do we have required query params? 6 + export default async function getActorFeed(c: TealContext) { 8 7 const params = c.req.query(); 9 - if (params.authorDid === undefined) { 8 + if (!params.authorDid) { 10 9 throw new Error("authorDid is required"); 11 10 } 12 11 if (!params.rkey) { 13 12 throw new Error("rkey is required"); 14 13 } 15 14 16 - let res = await db 17 - .select() 18 - .from(play) 19 - .where( 20 - and(eq(play.authorDid, params.authorDid), and(eq(play.uri, params.rkey))), 15 + // Get plays with artists as arrays 16 + const playRes = await db 17 + .select({ 18 + play: plays, 19 + artists: sql<Array<{ mbid: string; name: string }>>` 20 + COALESCE( 21 + array_agg( 22 + CASE WHEN ${artists.mbid} IS NOT NULL THEN 23 + jsonb_build_object( 24 + 'mbid', ${artists.mbid}, 25 + 'name', ${artists.name} 26 + ) 27 + END 28 + ) FILTER (WHERE ${artists.mbid} IS NOT NULL), 29 + ARRAY[]::jsonb[] 30 + ) 31 + `.as("artists"), 32 + }) 33 + .from(plays) 34 + .leftJoin(playToArtists, sql`${plays.uri} = ${playToArtists.playUri}`) 35 + .leftJoin(artists, sql`${playToArtists.artistMbid} = ${artists.mbid}`) 36 + .where(and(eq(plays.did, params.authorDid), eq(plays.rkey, params.rkey))) 37 + .groupBy( 38 + plays.uri, 39 + plays.cid, 40 + plays.did, 41 + plays.duration, 42 + plays.isrc, 43 + plays.musicServiceBaseDomain, 44 + plays.originUrl, 45 + plays.playedTime, 46 + plays.processedTime, 47 + plays.rkey, 48 + plays.recordingMbid, 49 + plays.releaseMbid, 50 + plays.releaseName, 51 + plays.submissionClientAgent, 52 + plays.trackName, 21 53 ) 22 - .execute(); 54 + .orderBy(desc(plays.playedTime)) 55 + .limit(1); 23 56 24 - if (res.length === 0) { 57 + if (playRes.length === 0) { 25 58 throw new Error("Play not found"); 26 59 } 27 - res[0]; 28 60 29 - // return a PlayView 30 61 return { 31 - play: { 32 - uri: res[0].uri, 33 - authorDid: res[0].authorDid, 34 - createdAt: res[0].createdAt, 35 - indexedAt: res[0].indexedAt, 36 - trackName: res[0].trackName, 37 - trackMbId: res[0].trackMbId, 38 - recordingMbId: res[0].recordingMbId, 39 - duration: res[0].duration, 40 - artistNames: res[0].artistNames, 41 - artistMbIds: res[0].artistMbIds, 42 - releaseName: res[0].releaseName, 43 - releaseMbId: res[0].releaseMbId, 44 - isrc: res[0].isrc, 45 - originUrl: res[0].originUrl, 46 - musicServiceBaseDomain: res[0].musicServiceBaseDomain, 47 - submissionClientAgent: res[0].submissionClientAgent, 48 - playedTime: res[0].playedTime, 49 - }, 62 + plays: playRes.map(({ play, artists }) => { 63 + const { 64 + uri, 65 + did: authorDid, 66 + processedTime: createdAt, 67 + processedTime: indexedAt, 68 + trackName, 69 + cid: trackMbId, 70 + cid: recordingMbId, 71 + duration, 72 + rkey, 73 + releaseName, 74 + cid: releaseMbId, 75 + isrc, 76 + originUrl, 77 + musicServiceBaseDomain, 78 + submissionClientAgent, 79 + playedTime, 80 + } = play; 81 + 82 + return { 83 + uri, 84 + authorDid, 85 + createdAt: createdAt?.toISOString(), 86 + indexedAt: indexedAt?.toISOString(), 87 + trackName, 88 + trackMbId, 89 + recordingMbId, 90 + duration, 91 + // Replace these with actual artist data from the array 92 + artistNames: artists.map((artist) => artist.name), 93 + artistMbIds: artists.map((artist) => artist.mbid), 94 + // Or, if you want to keep the full artist objects: 95 + // artists: artists, 96 + releaseName, 97 + releaseMbId, 98 + isrc, 99 + originUrl, 100 + musicServiceBaseDomain, 101 + submissionClientAgent, 102 + playedTime: playedTime?.toISOString(), 103 + }; 104 + }), 50 105 } as OutputSchema; 51 106 }
+1 -1
apps/aqua/src/xrpc/route.ts
··· 6 6 // mount this on /xrpc 7 7 const app = new Hono<EnvWithCtx>(); 8 8 9 - app.get("fm.teal.alpha.getPlay", async (c) => c.json(await getPlay(c))); 9 + app.get("fm.teal.alpha.feed.getPlay", async (c) => c.json(await getPlay(c))); 10 10 app.get("fm.teal.alpha.feed.getActorFeed", async (c) => 11 11 c.json(await getActorFeed(c)), 12 12 );
+46
packages/db/.drizzle/0000_perfect_war_machine.sql
··· 1 + CREATE TABLE "artists" ( 2 + "mbid" uuid PRIMARY KEY NOT NULL, 3 + "name" text NOT NULL, 4 + "play_count" integer DEFAULT 0 5 + ); 6 + --> statement-breakpoint 7 + CREATE TABLE "play_to_artists" ( 8 + "play_uri" text NOT NULL, 9 + "artist_mbid" uuid NOT NULL, 10 + "artist_name" text, 11 + CONSTRAINT "play_to_artists_play_uri_artist_mbid_pk" PRIMARY KEY("play_uri","artist_mbid") 12 + ); 13 + --> statement-breakpoint 14 + CREATE TABLE "plays" ( 15 + "uri" text PRIMARY KEY NOT NULL, 16 + "did" text NOT NULL, 17 + "rkey" text NOT NULL, 18 + "cid" text NOT NULL, 19 + "isrc" text, 20 + "duration" integer, 21 + "track_name" text NOT NULL, 22 + "played_time" timestamp with time zone, 23 + "processed_time" timestamp with time zone DEFAULT now(), 24 + "release_mbid" uuid, 25 + "release_name" text, 26 + "recording_mbid" uuid, 27 + "submission_client_agent" text, 28 + "music_service_base_domain" text 29 + ); 30 + --> statement-breakpoint 31 + CREATE TABLE "recordings" ( 32 + "mbid" uuid PRIMARY KEY NOT NULL, 33 + "name" text NOT NULL, 34 + "play_count" integer DEFAULT 0 35 + ); 36 + --> statement-breakpoint 37 + CREATE TABLE "releases" ( 38 + "mbid" uuid PRIMARY KEY NOT NULL, 39 + "name" text NOT NULL, 40 + "play_count" integer DEFAULT 0 41 + ); 42 + --> statement-breakpoint 43 + ALTER TABLE "play_to_artists" ADD CONSTRAINT "play_to_artists_play_uri_plays_uri_fk" FOREIGN KEY ("play_uri") REFERENCES "public"."plays"("uri") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 44 + ALTER TABLE "play_to_artists" ADD CONSTRAINT "play_to_artists_artist_mbid_artists_mbid_fk" FOREIGN KEY ("artist_mbid") REFERENCES "public"."artists"("mbid") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 45 + ALTER TABLE "plays" ADD CONSTRAINT "plays_release_mbid_releases_mbid_fk" FOREIGN KEY ("release_mbid") REFERENCES "public"."releases"("mbid") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 46 + ALTER TABLE "plays" ADD CONSTRAINT "plays_recording_mbid_recordings_mbid_fk" FOREIGN KEY ("recording_mbid") REFERENCES "public"."recordings"("mbid") ON DELETE no action ON UPDATE no action;
-17
packages/db/.drizzle/0000_same_maelstrom.sql
··· 1 - CREATE TABLE `auth_session` ( 2 - `key` text PRIMARY KEY NOT NULL, 3 - `session` text NOT NULL 4 - ); 5 - --> statement-breakpoint 6 - CREATE TABLE `auth_state` ( 7 - `key` text PRIMARY KEY NOT NULL, 8 - `state` text NOT NULL 9 - ); 10 - --> statement-breakpoint 11 - CREATE TABLE `status` ( 12 - `uri` text PRIMARY KEY NOT NULL, 13 - `authorDid` text NOT NULL, 14 - `status` text NOT NULL, 15 - `createdAt` text NOT NULL, 16 - `indexedAt` text NOT NULL 17 - );
-3
packages/db/.drizzle/0001_fresh_tana_nile.sql
··· 1 - ALTER TABLE `status` RENAME COLUMN "authorDid" TO "author_did";--> statement-breakpoint 2 - ALTER TABLE `status` RENAME COLUMN "createdAt" TO "created_at";--> statement-breakpoint 3 - ALTER TABLE `status` RENAME COLUMN "indexedAt" TO "indexed_at";
+6
packages/db/.drizzle/0001_swift_maddog.sql
··· 1 + CREATE MATERIALIZED VIEW "public"."mv_artist_play_counts" AS (select "artists"."mbid", "artists"."name", count("plays"."uri") as "play_count" from "artists" left join "play_to_artists" on "artists"."mbid" = "play_to_artists"."artist_mbid" left join "plays" on "plays"."uri" = "play_to_artists"."play_uri" group by "artists"."mbid", "artists"."name");--> statement-breakpoint 2 + CREATE MATERIALIZED VIEW "public"."mv_global_play_count" AS (select count("uri") as "total_plays", count(distinct "did") as "unique_listeners" from "plays");--> statement-breakpoint 3 + CREATE MATERIALIZED VIEW "public"."mv_recording_play_counts" AS (select "recordings"."mbid", "recordings"."name", count("plays"."uri") as "play_count" from "recordings" left join "plays" on "plays"."recording_mbid" = "recordings"."mbid" group by "recordings"."mbid", "recordings"."name");--> statement-breakpoint 4 + CREATE MATERIALIZED VIEW "public"."mv_release_play_counts" AS (select "releases"."mbid", "releases"."name", count("plays"."uri") as "play_count" from "releases" left join "plays" on "plays"."release_mbid" = "releases"."mbid" group by "releases"."mbid", "releases"."name");--> statement-breakpoint 5 + CREATE MATERIALIZED VIEW "public"."mv_top_artists_30days" AS (select "artists"."mbid", "artists"."name", count("plays"."uri") as "play_count" from "artists" inner join "play_to_artists" on "artists"."mbid" = "play_to_artists"."artist_mbid" inner join "plays" on "plays"."uri" = "play_to_artists"."play_uri" where "plays"."played_time" >= NOW() - INTERVAL '30 days' group by "artists"."mbid", "artists"."name" order by count("plays"."uri") DESC);--> statement-breakpoint 6 + CREATE MATERIALIZED VIEW "public"."mv_top_releases_30days" AS (select "releases"."mbid", "releases"."name", count("plays"."uri") as "play_count" from "releases" inner join "plays" on "plays"."release_mbid" = "releases"."mbid" where "plays"."played_time" >= NOW() - INTERVAL '30 days' group by "releases"."mbid", "releases"."name" order by count("plays"."uri") DESC);
-1
packages/db/.drizzle/0002_moaning_roulette.sql
··· 1 - ALTER TABLE `auth_session` RENAME TO `atp_session`;
+1
packages/db/.drizzle/0002_stale_the_spike.sql
··· 1 + ALTER TABLE "plays" ADD COLUMN "origin_url" text;
-12
packages/db/.drizzle/0003_sharp_medusa.sql
··· 1 - CREATE TABLE `teal_session` ( 2 - `key` text PRIMARY KEY NOT NULL, 3 - `session` text NOT NULL, 4 - `provider` text NOT NULL 5 - ); 6 - --> statement-breakpoint 7 - CREATE TABLE `teal_user` ( 8 - `did` text PRIMARY KEY NOT NULL, 9 - `handle` text NOT NULL, 10 - `email` text NOT NULL, 11 - `created_at` text NOT NULL 12 - );
+15
packages/db/.drizzle/0003_worried_unicorn.sql
··· 1 + CREATE TABLE "profiles" ( 2 + "did" text PRIMARY KEY NOT NULL, 3 + "display_name" text NOT NULL, 4 + "description" text NOT NULL, 5 + "description_facets" jsonb NOT NULL, 6 + "avatar" text NOT NULL, 7 + "banner" text NOT NULL, 8 + "created_at" timestamp NOT NULL 9 + ); 10 + --> statement-breakpoint 11 + CREATE TABLE "featured_items" ( 12 + "did" text PRIMARY KEY NOT NULL, 13 + "mbid" text NOT NULL, 14 + "type" text NOT NULL 15 + );
-29
packages/db/.drizzle/0004_exotic_ironclad.sql
··· 1 - CREATE TABLE `follow` ( 2 - `follower` text PRIMARY KEY NOT NULL, 3 - `followed` text NOT NULL, 4 - `created_at` text NOT NULL 5 - ); 6 - --> statement-breakpoint 7 - CREATE TABLE `play` ( 8 - `uri` text PRIMARY KEY NOT NULL, 9 - `author_did` text NOT NULL, 10 - `created_at` text NOT NULL, 11 - `indexed_at` text NOT NULL, 12 - `track_name` text NOT NULL, 13 - `track_mb_id` text, 14 - `recording_mb_id` text, 15 - `duration` integer, 16 - `artist_name` text NOT NULL, 17 - `artist_mb_ids` text, 18 - `release_name` text, 19 - `release_mb_id` text, 20 - `isrc` text, 21 - `origin_url` text, 22 - `music_service_base_domain` text, 23 - `submission_client_agent` text, 24 - `played_time` text 25 - ); 26 - --> statement-breakpoint 27 - ALTER TABLE `teal_user` ADD `avatar` text NOT NULL;--> statement-breakpoint 28 - ALTER TABLE `teal_user` ADD `bio` text;--> statement-breakpoint 29 - ALTER TABLE `teal_user` DROP COLUMN `email`;
+7
packages/db/.drizzle/0004_furry_gravity.sql
··· 1 + ALTER TABLE "profiles" ALTER COLUMN "display_name" DROP NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "profiles" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint 3 + ALTER TABLE "profiles" ALTER COLUMN "description_facets" DROP NOT NULL;--> statement-breakpoint 4 + ALTER TABLE "profiles" ALTER COLUMN "avatar" DROP NOT NULL;--> statement-breakpoint 5 + ALTER TABLE "profiles" ALTER COLUMN "banner" DROP NOT NULL;--> statement-breakpoint 6 + ALTER TABLE "profiles" ALTER COLUMN "created_at" DROP NOT NULL;--> statement-breakpoint 7 + ALTER TABLE "profiles" ADD COLUMN "handle" text;
-12
packages/db/.drizzle/0005_conscious_johnny_blaze.sql
··· 1 - PRAGMA foreign_keys=OFF;--> statement-breakpoint 2 - CREATE TABLE `__new_follow` ( 3 - `rel_id` text PRIMARY KEY NOT NULL, 4 - `follower` text NOT NULL, 5 - `followed` text NOT NULL, 6 - `created_at` text NOT NULL 7 - ); 8 - --> statement-breakpoint 9 - INSERT INTO `__new_follow`("rel_id", "follower", "followed", "created_at") SELECT '0', "follower", "followed", "created_at" FROM `follow`;--> statement-breakpoint 10 - DROP TABLE `follow`;--> statement-breakpoint 11 - ALTER TABLE `__new_follow` RENAME TO `follow`;--> statement-breakpoint 12 - PRAGMA foreign_keys=ON;
-25
packages/db/.drizzle/0006_supreme_hairball.sql
··· 1 - PRAGMA foreign_keys=OFF;--> statement-breakpoint 2 - CREATE TABLE `__new_play` ( 3 - `uri` text PRIMARY KEY NOT NULL, 4 - `author_did` text NOT NULL, 5 - `created_at` text NOT NULL, 6 - `indexed_at` text NOT NULL, 7 - `track_name` text NOT NULL, 8 - `track_mb_id` text, 9 - `recording_mb_id` text, 10 - `duration` integer, 11 - `artist_names` text, 12 - `artist_mb_ids` text, 13 - `release_name` text, 14 - `release_mb_id` text, 15 - `isrc` text, 16 - `origin_url` text, 17 - `music_service_base_domain` text, 18 - `submission_client_agent` text, 19 - `played_time` text 20 - ); 21 - --> statement-breakpoint 22 - INSERT INTO `__new_play`("uri", "author_did", "created_at", "indexed_at", "track_name", "track_mb_id", "recording_mb_id", "duration", "artist_names", "artist_mb_ids", "release_name", "release_mb_id", "isrc", "origin_url", "music_service_base_domain", "submission_client_agent", "played_time") SELECT "uri", "author_did", "created_at", "indexed_at", "track_name", "track_mb_id", "recording_mb_id", "duration", "artist_name", "artist_mb_ids", "release_name", "release_mb_id", "isrc", "origin_url", "music_service_base_domain", "submission_client_agent", "played_time" FROM `play`;--> statement-breakpoint 23 - DROP TABLE `play`;--> statement-breakpoint 24 - ALTER TABLE `__new_play` RENAME TO `play`;--> statement-breakpoint 25 - PRAGMA foreign_keys=ON;
-1
packages/db/.drizzle/0007_demonic_toad.sql
··· 1 - ALTER TABLE `play` RENAME COLUMN "uri" TO "rkey";
+251 -56
packages/db/.drizzle/meta/0000_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "417cce08-b23b-4a9f-ad7e-e96215e0fb38", 2 + "id": "8724646f-1dc1-484d-97ad-641f3a4c2aa1", 5 3 "prevId": "00000000-0000-0000-0000-000000000000", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "auth_session": { 8 - "name": "auth_session", 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 9 10 "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 13 14 "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 15 + "notNull": true 16 16 }, 17 - "session": { 18 - "name": "session", 17 + "name": { 18 + "name": "name", 19 19 "type": "text", 20 20 "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 23 29 } 24 30 }, 25 31 "indexes": {}, 26 32 "foreignKeys": {}, 27 33 "compositePrimaryKeys": {}, 28 34 "uniqueConstraints": {}, 29 - "checkConstraints": {} 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 30 38 }, 31 - "auth_state": { 32 - "name": "auth_state", 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 33 42 "columns": { 34 - "key": { 35 - "name": "key", 43 + "play_uri": { 44 + "name": "play_uri", 36 45 "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 46 + "primaryKey": false, 47 + "notNull": true 40 48 }, 41 - "state": { 42 - "name": "state", 49 + "artist_mbid": { 50 + "name": "artist_mbid", 51 + "type": "uuid", 52 + "primaryKey": false, 53 + "notNull": true 54 + }, 55 + "artist_name": { 56 + "name": "artist_name", 43 57 "type": "text", 44 58 "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 59 + "notNull": false 47 60 } 48 61 }, 49 62 "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 63 + "foreignKeys": { 64 + "play_to_artists_play_uri_plays_uri_fk": { 65 + "name": "play_to_artists_play_uri_plays_uri_fk", 66 + "tableFrom": "play_to_artists", 67 + "tableTo": "plays", 68 + "columnsFrom": [ 69 + "play_uri" 70 + ], 71 + "columnsTo": [ 72 + "uri" 73 + ], 74 + "onDelete": "no action", 75 + "onUpdate": "no action" 76 + }, 77 + "play_to_artists_artist_mbid_artists_mbid_fk": { 78 + "name": "play_to_artists_artist_mbid_artists_mbid_fk", 79 + "tableFrom": "play_to_artists", 80 + "tableTo": "artists", 81 + "columnsFrom": [ 82 + "artist_mbid" 83 + ], 84 + "columnsTo": [ 85 + "mbid" 86 + ], 87 + "onDelete": "no action", 88 + "onUpdate": "no action" 89 + } 90 + }, 91 + "compositePrimaryKeys": { 92 + "play_to_artists_play_uri_artist_mbid_pk": { 93 + "name": "play_to_artists_play_uri_artist_mbid_pk", 94 + "columns": [ 95 + "play_uri", 96 + "artist_mbid" 97 + ] 98 + } 99 + }, 52 100 "uniqueConstraints": {}, 53 - "checkConstraints": {} 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 54 104 }, 55 - "status": { 56 - "name": "status", 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 57 108 "columns": { 58 109 "uri": { 59 110 "name": "uri", 60 111 "type": "text", 61 112 "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 113 + "notNull": true 114 + }, 115 + "did": { 116 + "name": "did", 117 + "type": "text", 118 + "primaryKey": false, 119 + "notNull": true 120 + }, 121 + "rkey": { 122 + "name": "rkey", 123 + "type": "text", 124 + "primaryKey": false, 125 + "notNull": true 126 + }, 127 + "cid": { 128 + "name": "cid", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": true 132 + }, 133 + "isrc": { 134 + "name": "isrc", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "duration": { 140 + "name": "duration", 141 + "type": "integer", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "track_name": { 146 + "name": "track_name", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": true 150 + }, 151 + "played_time": { 152 + "name": "played_time", 153 + "type": "timestamp with time zone", 154 + "primaryKey": false, 155 + "notNull": false 156 + }, 157 + "processed_time": { 158 + "name": "processed_time", 159 + "type": "timestamp with time zone", 160 + "primaryKey": false, 161 + "notNull": false, 162 + "default": "now()" 163 + }, 164 + "release_mbid": { 165 + "name": "release_mbid", 166 + "type": "uuid", 167 + "primaryKey": false, 168 + "notNull": false 169 + }, 170 + "release_name": { 171 + "name": "release_name", 172 + "type": "text", 173 + "primaryKey": false, 174 + "notNull": false 64 175 }, 65 - "authorDid": { 66 - "name": "authorDid", 176 + "recording_mbid": { 177 + "name": "recording_mbid", 178 + "type": "uuid", 179 + "primaryKey": false, 180 + "notNull": false 181 + }, 182 + "submission_client_agent": { 183 + "name": "submission_client_agent", 67 184 "type": "text", 68 185 "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 186 + "notNull": false 71 187 }, 72 - "status": { 73 - "name": "status", 188 + "music_service_base_domain": { 189 + "name": "music_service_base_domain", 74 190 "type": "text", 75 191 "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 192 + "notNull": false 193 + } 194 + }, 195 + "indexes": {}, 196 + "foreignKeys": { 197 + "plays_release_mbid_releases_mbid_fk": { 198 + "name": "plays_release_mbid_releases_mbid_fk", 199 + "tableFrom": "plays", 200 + "tableTo": "releases", 201 + "columnsFrom": [ 202 + "release_mbid" 203 + ], 204 + "columnsTo": [ 205 + "mbid" 206 + ], 207 + "onDelete": "no action", 208 + "onUpdate": "no action" 209 + }, 210 + "plays_recording_mbid_recordings_mbid_fk": { 211 + "name": "plays_recording_mbid_recordings_mbid_fk", 212 + "tableFrom": "plays", 213 + "tableTo": "recordings", 214 + "columnsFrom": [ 215 + "recording_mbid" 216 + ], 217 + "columnsTo": [ 218 + "mbid" 219 + ], 220 + "onDelete": "no action", 221 + "onUpdate": "no action" 222 + } 223 + }, 224 + "compositePrimaryKeys": {}, 225 + "uniqueConstraints": {}, 226 + "policies": {}, 227 + "checkConstraints": {}, 228 + "isRLSEnabled": false 229 + }, 230 + "public.recordings": { 231 + "name": "recordings", 232 + "schema": "", 233 + "columns": { 234 + "mbid": { 235 + "name": "mbid", 236 + "type": "uuid", 237 + "primaryKey": true, 238 + "notNull": true 78 239 }, 79 - "createdAt": { 80 - "name": "createdAt", 240 + "name": { 241 + "name": "name", 81 242 "type": "text", 82 243 "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 244 + "notNull": true 85 245 }, 86 - "indexedAt": { 87 - "name": "indexedAt", 246 + "play_count": { 247 + "name": "play_count", 248 + "type": "integer", 249 + "primaryKey": false, 250 + "notNull": false, 251 + "default": 0 252 + } 253 + }, 254 + "indexes": {}, 255 + "foreignKeys": {}, 256 + "compositePrimaryKeys": {}, 257 + "uniqueConstraints": {}, 258 + "policies": {}, 259 + "checkConstraints": {}, 260 + "isRLSEnabled": false 261 + }, 262 + "public.releases": { 263 + "name": "releases", 264 + "schema": "", 265 + "columns": { 266 + "mbid": { 267 + "name": "mbid", 268 + "type": "uuid", 269 + "primaryKey": true, 270 + "notNull": true 271 + }, 272 + "name": { 273 + "name": "name", 88 274 "type": "text", 89 275 "primaryKey": false, 90 - "notNull": true, 91 - "autoincrement": false 276 + "notNull": true 277 + }, 278 + "play_count": { 279 + "name": "play_count", 280 + "type": "integer", 281 + "primaryKey": false, 282 + "notNull": false, 283 + "default": 0 92 284 } 93 285 }, 94 286 "indexes": {}, 95 287 "foreignKeys": {}, 96 288 "compositePrimaryKeys": {}, 97 289 "uniqueConstraints": {}, 98 - "checkConstraints": {} 290 + "policies": {}, 291 + "checkConstraints": {}, 292 + "isRLSEnabled": false 99 293 } 100 294 }, 295 + "enums": {}, 296 + "schemas": {}, 297 + "sequences": {}, 298 + "roles": {}, 299 + "policies": {}, 101 300 "views": {}, 102 - "enums": {}, 103 301 "_meta": { 302 + "columns": {}, 104 303 "schemas": {}, 105 - "tables": {}, 106 - "columns": {} 107 - }, 108 - "internal": { 109 - "indexes": {} 304 + "tables": {} 110 305 } 111 306 }
+366 -61
packages/db/.drizzle/meta/0001_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "0668ebf0-fa9b-41bc-90fa-f25992bf4b76", 5 - "prevId": "417cce08-b23b-4a9f-ad7e-e96215e0fb38", 2 + "id": "1a16d013-9247-4174-beed-2db2c4b372a9", 3 + "prevId": "8724646f-1dc1-484d-97ad-641f3a4c2aa1", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "auth_session": { 8 - "name": "auth_session", 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 9 10 "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 13 14 "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 15 + "notNull": true 16 16 }, 17 - "session": { 18 - "name": "session", 17 + "name": { 18 + "name": "name", 19 19 "type": "text", 20 20 "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 23 29 } 24 30 }, 25 31 "indexes": {}, 26 32 "foreignKeys": {}, 27 33 "compositePrimaryKeys": {}, 28 34 "uniqueConstraints": {}, 29 - "checkConstraints": {} 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 30 38 }, 31 - "auth_state": { 32 - "name": "auth_state", 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 33 42 "columns": { 34 - "key": { 35 - "name": "key", 43 + "play_uri": { 44 + "name": "play_uri", 36 45 "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 46 + "primaryKey": false, 47 + "notNull": true 48 + }, 49 + "artist_mbid": { 50 + "name": "artist_mbid", 51 + "type": "uuid", 52 + "primaryKey": false, 53 + "notNull": true 40 54 }, 41 - "state": { 42 - "name": "state", 55 + "artist_name": { 56 + "name": "artist_name", 43 57 "type": "text", 44 58 "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 59 + "notNull": false 47 60 } 48 61 }, 49 62 "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 63 + "foreignKeys": { 64 + "play_to_artists_play_uri_plays_uri_fk": { 65 + "name": "play_to_artists_play_uri_plays_uri_fk", 66 + "tableFrom": "play_to_artists", 67 + "tableTo": "plays", 68 + "columnsFrom": [ 69 + "play_uri" 70 + ], 71 + "columnsTo": [ 72 + "uri" 73 + ], 74 + "onDelete": "no action", 75 + "onUpdate": "no action" 76 + }, 77 + "play_to_artists_artist_mbid_artists_mbid_fk": { 78 + "name": "play_to_artists_artist_mbid_artists_mbid_fk", 79 + "tableFrom": "play_to_artists", 80 + "tableTo": "artists", 81 + "columnsFrom": [ 82 + "artist_mbid" 83 + ], 84 + "columnsTo": [ 85 + "mbid" 86 + ], 87 + "onDelete": "no action", 88 + "onUpdate": "no action" 89 + } 90 + }, 91 + "compositePrimaryKeys": { 92 + "play_to_artists_play_uri_artist_mbid_pk": { 93 + "name": "play_to_artists_play_uri_artist_mbid_pk", 94 + "columns": [ 95 + "play_uri", 96 + "artist_mbid" 97 + ] 98 + } 99 + }, 52 100 "uniqueConstraints": {}, 53 - "checkConstraints": {} 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 54 104 }, 55 - "status": { 56 - "name": "status", 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 57 108 "columns": { 58 109 "uri": { 59 110 "name": "uri", 60 111 "type": "text", 61 112 "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 113 + "notNull": true 114 + }, 115 + "did": { 116 + "name": "did", 117 + "type": "text", 118 + "primaryKey": false, 119 + "notNull": true 120 + }, 121 + "rkey": { 122 + "name": "rkey", 123 + "type": "text", 124 + "primaryKey": false, 125 + "notNull": true 126 + }, 127 + "cid": { 128 + "name": "cid", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": true 132 + }, 133 + "isrc": { 134 + "name": "isrc", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "duration": { 140 + "name": "duration", 141 + "type": "integer", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "track_name": { 146 + "name": "track_name", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": true 150 + }, 151 + "played_time": { 152 + "name": "played_time", 153 + "type": "timestamp with time zone", 154 + "primaryKey": false, 155 + "notNull": false 156 + }, 157 + "processed_time": { 158 + "name": "processed_time", 159 + "type": "timestamp with time zone", 160 + "primaryKey": false, 161 + "notNull": false, 162 + "default": "now()" 163 + }, 164 + "release_mbid": { 165 + "name": "release_mbid", 166 + "type": "uuid", 167 + "primaryKey": false, 168 + "notNull": false 169 + }, 170 + "release_name": { 171 + "name": "release_name", 172 + "type": "text", 173 + "primaryKey": false, 174 + "notNull": false 175 + }, 176 + "recording_mbid": { 177 + "name": "recording_mbid", 178 + "type": "uuid", 179 + "primaryKey": false, 180 + "notNull": false 64 181 }, 65 - "author_did": { 66 - "name": "author_did", 182 + "submission_client_agent": { 183 + "name": "submission_client_agent", 67 184 "type": "text", 68 185 "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 186 + "notNull": false 71 187 }, 72 - "status": { 73 - "name": "status", 188 + "music_service_base_domain": { 189 + "name": "music_service_base_domain", 74 190 "type": "text", 75 191 "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 192 + "notNull": false 193 + } 194 + }, 195 + "indexes": {}, 196 + "foreignKeys": { 197 + "plays_release_mbid_releases_mbid_fk": { 198 + "name": "plays_release_mbid_releases_mbid_fk", 199 + "tableFrom": "plays", 200 + "tableTo": "releases", 201 + "columnsFrom": [ 202 + "release_mbid" 203 + ], 204 + "columnsTo": [ 205 + "mbid" 206 + ], 207 + "onDelete": "no action", 208 + "onUpdate": "no action" 209 + }, 210 + "plays_recording_mbid_recordings_mbid_fk": { 211 + "name": "plays_recording_mbid_recordings_mbid_fk", 212 + "tableFrom": "plays", 213 + "tableTo": "recordings", 214 + "columnsFrom": [ 215 + "recording_mbid" 216 + ], 217 + "columnsTo": [ 218 + "mbid" 219 + ], 220 + "onDelete": "no action", 221 + "onUpdate": "no action" 222 + } 223 + }, 224 + "compositePrimaryKeys": {}, 225 + "uniqueConstraints": {}, 226 + "policies": {}, 227 + "checkConstraints": {}, 228 + "isRLSEnabled": false 229 + }, 230 + "public.recordings": { 231 + "name": "recordings", 232 + "schema": "", 233 + "columns": { 234 + "mbid": { 235 + "name": "mbid", 236 + "type": "uuid", 237 + "primaryKey": true, 238 + "notNull": true 78 239 }, 79 - "created_at": { 80 - "name": "created_at", 240 + "name": { 241 + "name": "name", 81 242 "type": "text", 82 243 "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 244 + "notNull": true 245 + }, 246 + "play_count": { 247 + "name": "play_count", 248 + "type": "integer", 249 + "primaryKey": false, 250 + "notNull": false, 251 + "default": 0 252 + } 253 + }, 254 + "indexes": {}, 255 + "foreignKeys": {}, 256 + "compositePrimaryKeys": {}, 257 + "uniqueConstraints": {}, 258 + "policies": {}, 259 + "checkConstraints": {}, 260 + "isRLSEnabled": false 261 + }, 262 + "public.releases": { 263 + "name": "releases", 264 + "schema": "", 265 + "columns": { 266 + "mbid": { 267 + "name": "mbid", 268 + "type": "uuid", 269 + "primaryKey": true, 270 + "notNull": true 85 271 }, 86 - "indexed_at": { 87 - "name": "indexed_at", 272 + "name": { 273 + "name": "name", 88 274 "type": "text", 89 275 "primaryKey": false, 90 - "notNull": true, 91 - "autoincrement": false 276 + "notNull": true 277 + }, 278 + "play_count": { 279 + "name": "play_count", 280 + "type": "integer", 281 + "primaryKey": false, 282 + "notNull": false, 283 + "default": 0 92 284 } 93 285 }, 94 286 "indexes": {}, 95 287 "foreignKeys": {}, 96 288 "compositePrimaryKeys": {}, 97 289 "uniqueConstraints": {}, 98 - "checkConstraints": {} 290 + "policies": {}, 291 + "checkConstraints": {}, 292 + "isRLSEnabled": false 99 293 } 100 294 }, 101 - "views": {}, 102 295 "enums": {}, 103 - "_meta": { 104 - "schemas": {}, 105 - "tables": {}, 106 - "columns": { 107 - "\"status\".\"authorDid\"": "\"status\".\"author_did\"", 108 - "\"status\".\"createdAt\"": "\"status\".\"created_at\"", 109 - "\"status\".\"indexedAt\"": "\"status\".\"indexed_at\"" 296 + "schemas": {}, 297 + "sequences": {}, 298 + "roles": {}, 299 + "policies": {}, 300 + "views": { 301 + "public.mv_artist_play_counts": { 302 + "columns": { 303 + "mbid": { 304 + "name": "mbid", 305 + "type": "uuid", 306 + "primaryKey": true, 307 + "notNull": true 308 + }, 309 + "name": { 310 + "name": "name", 311 + "type": "text", 312 + "primaryKey": false, 313 + "notNull": true 314 + } 315 + }, 316 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" left join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" left join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" group by \"artists\".\"mbid\", \"artists\".\"name\"", 317 + "name": "mv_artist_play_counts", 318 + "schema": "public", 319 + "isExisting": false, 320 + "materialized": true 321 + }, 322 + "public.mv_global_play_count": { 323 + "columns": {}, 324 + "definition": "select count(\"uri\") as \"total_plays\", count(distinct \"did\") as \"unique_listeners\" from \"plays\"", 325 + "name": "mv_global_play_count", 326 + "schema": "public", 327 + "isExisting": false, 328 + "materialized": true 329 + }, 330 + "public.mv_recording_play_counts": { 331 + "columns": { 332 + "mbid": { 333 + "name": "mbid", 334 + "type": "uuid", 335 + "primaryKey": true, 336 + "notNull": true 337 + }, 338 + "name": { 339 + "name": "name", 340 + "type": "text", 341 + "primaryKey": false, 342 + "notNull": true 343 + } 344 + }, 345 + "definition": "select \"recordings\".\"mbid\", \"recordings\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"recordings\" left join \"plays\" on \"plays\".\"recording_mbid\" = \"recordings\".\"mbid\" group by \"recordings\".\"mbid\", \"recordings\".\"name\"", 346 + "name": "mv_recording_play_counts", 347 + "schema": "public", 348 + "isExisting": false, 349 + "materialized": true 350 + }, 351 + "public.mv_release_play_counts": { 352 + "columns": { 353 + "mbid": { 354 + "name": "mbid", 355 + "type": "uuid", 356 + "primaryKey": true, 357 + "notNull": true 358 + }, 359 + "name": { 360 + "name": "name", 361 + "type": "text", 362 + "primaryKey": false, 363 + "notNull": true 364 + } 365 + }, 366 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" left join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" group by \"releases\".\"mbid\", \"releases\".\"name\"", 367 + "name": "mv_release_play_counts", 368 + "schema": "public", 369 + "isExisting": false, 370 + "materialized": true 371 + }, 372 + "public.mv_top_artists_30days": { 373 + "columns": { 374 + "mbid": { 375 + "name": "mbid", 376 + "type": "uuid", 377 + "primaryKey": true, 378 + "notNull": true 379 + }, 380 + "name": { 381 + "name": "name", 382 + "type": "text", 383 + "primaryKey": false, 384 + "notNull": true 385 + } 386 + }, 387 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" inner join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" inner join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"artists\".\"mbid\", \"artists\".\"name\" order by count(\"plays\".\"uri\") DESC", 388 + "name": "mv_top_artists_30days", 389 + "schema": "public", 390 + "isExisting": false, 391 + "materialized": true 392 + }, 393 + "public.mv_top_releases_30days": { 394 + "columns": { 395 + "mbid": { 396 + "name": "mbid", 397 + "type": "uuid", 398 + "primaryKey": true, 399 + "notNull": true 400 + }, 401 + "name": { 402 + "name": "name", 403 + "type": "text", 404 + "primaryKey": false, 405 + "notNull": true 406 + } 407 + }, 408 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" inner join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"releases\".\"mbid\", \"releases\".\"name\" order by count(\"plays\".\"uri\") DESC", 409 + "name": "mv_top_releases_30days", 410 + "schema": "public", 411 + "isExisting": false, 412 + "materialized": true 110 413 } 111 414 }, 112 - "internal": { 113 - "indexes": {} 415 + "_meta": { 416 + "columns": {}, 417 + "schemas": {}, 418 + "tables": {} 114 419 } 115 420 }
+374 -61
packages/db/.drizzle/meta/0002_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "1123b2f8-11c2-4d28-925a-f7a30bb3bec6", 5 - "prevId": "0668ebf0-fa9b-41bc-90fa-f25992bf4b76", 2 + "id": "3f2b9825-e988-4335-8fb8-f12d54efc11f", 3 + "prevId": "1a16d013-9247-4174-beed-2db2c4b372a9", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 9 10 "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 13 14 "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 15 + "notNull": true 16 16 }, 17 - "session": { 18 - "name": "session", 17 + "name": { 18 + "name": "name", 19 19 "type": "text", 20 20 "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 23 29 } 24 30 }, 25 31 "indexes": {}, 26 32 "foreignKeys": {}, 27 33 "compositePrimaryKeys": {}, 28 34 "uniqueConstraints": {}, 29 - "checkConstraints": {} 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 30 38 }, 31 - "auth_state": { 32 - "name": "auth_state", 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 33 42 "columns": { 34 - "key": { 35 - "name": "key", 43 + "artist_mbid": { 44 + "name": "artist_mbid", 45 + "type": "uuid", 46 + "primaryKey": false, 47 + "notNull": true 48 + }, 49 + "artist_name": { 50 + "name": "artist_name", 36 51 "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 52 + "primaryKey": false, 53 + "notNull": false 40 54 }, 41 - "state": { 42 - "name": "state", 55 + "play_uri": { 56 + "name": "play_uri", 43 57 "type": "text", 44 58 "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 59 + "notNull": true 47 60 } 48 61 }, 49 62 "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 63 + "foreignKeys": { 64 + "play_to_artists_artist_mbid_artists_mbid_fk": { 65 + "name": "play_to_artists_artist_mbid_artists_mbid_fk", 66 + "tableFrom": "play_to_artists", 67 + "tableTo": "artists", 68 + "columnsFrom": [ 69 + "artist_mbid" 70 + ], 71 + "columnsTo": [ 72 + "mbid" 73 + ], 74 + "onDelete": "no action", 75 + "onUpdate": "no action" 76 + }, 77 + "play_to_artists_play_uri_plays_uri_fk": { 78 + "name": "play_to_artists_play_uri_plays_uri_fk", 79 + "tableFrom": "play_to_artists", 80 + "tableTo": "plays", 81 + "columnsFrom": [ 82 + "play_uri" 83 + ], 84 + "columnsTo": [ 85 + "uri" 86 + ], 87 + "onDelete": "no action", 88 + "onUpdate": "no action" 89 + } 90 + }, 91 + "compositePrimaryKeys": { 92 + "play_to_artists_play_uri_artist_mbid_pk": { 93 + "name": "play_to_artists_play_uri_artist_mbid_pk", 94 + "columns": [ 95 + "play_uri", 96 + "artist_mbid" 97 + ] 98 + } 99 + }, 52 100 "uniqueConstraints": {}, 53 - "checkConstraints": {} 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 54 104 }, 55 - "status": { 56 - "name": "status", 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 57 108 "columns": { 109 + "cid": { 110 + "name": "cid", 111 + "type": "text", 112 + "primaryKey": false, 113 + "notNull": true 114 + }, 115 + "did": { 116 + "name": "did", 117 + "type": "text", 118 + "primaryKey": false, 119 + "notNull": true 120 + }, 121 + "duration": { 122 + "name": "duration", 123 + "type": "integer", 124 + "primaryKey": false, 125 + "notNull": false 126 + }, 127 + "isrc": { 128 + "name": "isrc", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": false 132 + }, 133 + "music_service_base_domain": { 134 + "name": "music_service_base_domain", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "origin_url": { 140 + "name": "origin_url", 141 + "type": "text", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "played_time": { 146 + "name": "played_time", 147 + "type": "timestamp with time zone", 148 + "primaryKey": false, 149 + "notNull": false 150 + }, 151 + "processed_time": { 152 + "name": "processed_time", 153 + "type": "timestamp with time zone", 154 + "primaryKey": false, 155 + "notNull": false, 156 + "default": "now()" 157 + }, 158 + "rkey": { 159 + "name": "rkey", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": true 163 + }, 164 + "recording_mbid": { 165 + "name": "recording_mbid", 166 + "type": "uuid", 167 + "primaryKey": false, 168 + "notNull": false 169 + }, 170 + "release_mbid": { 171 + "name": "release_mbid", 172 + "type": "uuid", 173 + "primaryKey": false, 174 + "notNull": false 175 + }, 176 + "release_name": { 177 + "name": "release_name", 178 + "type": "text", 179 + "primaryKey": false, 180 + "notNull": false 181 + }, 182 + "submission_client_agent": { 183 + "name": "submission_client_agent", 184 + "type": "text", 185 + "primaryKey": false, 186 + "notNull": false 187 + }, 188 + "track_name": { 189 + "name": "track_name", 190 + "type": "text", 191 + "primaryKey": false, 192 + "notNull": true 193 + }, 58 194 "uri": { 59 195 "name": "uri", 60 196 "type": "text", 61 197 "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 198 + "notNull": true 199 + } 200 + }, 201 + "indexes": {}, 202 + "foreignKeys": { 203 + "plays_recording_mbid_recordings_mbid_fk": { 204 + "name": "plays_recording_mbid_recordings_mbid_fk", 205 + "tableFrom": "plays", 206 + "tableTo": "recordings", 207 + "columnsFrom": [ 208 + "recording_mbid" 209 + ], 210 + "columnsTo": [ 211 + "mbid" 212 + ], 213 + "onDelete": "no action", 214 + "onUpdate": "no action" 215 + }, 216 + "plays_release_mbid_releases_mbid_fk": { 217 + "name": "plays_release_mbid_releases_mbid_fk", 218 + "tableFrom": "plays", 219 + "tableTo": "releases", 220 + "columnsFrom": [ 221 + "release_mbid" 222 + ], 223 + "columnsTo": [ 224 + "mbid" 225 + ], 226 + "onDelete": "no action", 227 + "onUpdate": "no action" 228 + } 229 + }, 230 + "compositePrimaryKeys": {}, 231 + "uniqueConstraints": {}, 232 + "policies": {}, 233 + "checkConstraints": {}, 234 + "isRLSEnabled": false 235 + }, 236 + "public.recordings": { 237 + "name": "recordings", 238 + "schema": "", 239 + "columns": { 240 + "mbid": { 241 + "name": "mbid", 242 + "type": "uuid", 243 + "primaryKey": true, 244 + "notNull": true 64 245 }, 65 - "author_did": { 66 - "name": "author_did", 246 + "name": { 247 + "name": "name", 67 248 "type": "text", 68 249 "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 250 + "notNull": true 71 251 }, 72 - "status": { 73 - "name": "status", 74 - "type": "text", 252 + "play_count": { 253 + "name": "play_count", 254 + "type": "integer", 75 255 "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 256 + "notNull": false, 257 + "default": 0 258 + } 259 + }, 260 + "indexes": {}, 261 + "foreignKeys": {}, 262 + "compositePrimaryKeys": {}, 263 + "uniqueConstraints": {}, 264 + "policies": {}, 265 + "checkConstraints": {}, 266 + "isRLSEnabled": false 267 + }, 268 + "public.releases": { 269 + "name": "releases", 270 + "schema": "", 271 + "columns": { 272 + "mbid": { 273 + "name": "mbid", 274 + "type": "uuid", 275 + "primaryKey": true, 276 + "notNull": true 78 277 }, 79 - "created_at": { 80 - "name": "created_at", 278 + "name": { 279 + "name": "name", 81 280 "type": "text", 82 281 "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 282 + "notNull": true 85 283 }, 86 - "indexed_at": { 87 - "name": "indexed_at", 88 - "type": "text", 284 + "play_count": { 285 + "name": "play_count", 286 + "type": "integer", 89 287 "primaryKey": false, 90 - "notNull": true, 91 - "autoincrement": false 288 + "notNull": false, 289 + "default": 0 92 290 } 93 291 }, 94 292 "indexes": {}, 95 293 "foreignKeys": {}, 96 294 "compositePrimaryKeys": {}, 97 295 "uniqueConstraints": {}, 98 - "checkConstraints": {} 296 + "policies": {}, 297 + "checkConstraints": {}, 298 + "isRLSEnabled": false 99 299 } 100 300 }, 101 - "views": {}, 102 301 "enums": {}, 103 - "_meta": { 104 - "schemas": {}, 105 - "tables": { 106 - "\"auth_session\"": "\"atp_session\"" 302 + "schemas": {}, 303 + "sequences": {}, 304 + "roles": {}, 305 + "policies": {}, 306 + "views": { 307 + "public.mv_artist_play_counts": { 308 + "columns": { 309 + "mbid": { 310 + "name": "mbid", 311 + "type": "uuid", 312 + "primaryKey": true, 313 + "notNull": true 314 + }, 315 + "name": { 316 + "name": "name", 317 + "type": "text", 318 + "primaryKey": false, 319 + "notNull": true 320 + } 321 + }, 322 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" left join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" left join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" group by \"artists\".\"mbid\", \"artists\".\"name\"", 323 + "name": "mv_artist_play_counts", 324 + "schema": "public", 325 + "isExisting": false, 326 + "materialized": true 327 + }, 328 + "public.mv_global_play_count": { 329 + "columns": {}, 330 + "definition": "select count(\"uri\") as \"total_plays\", count(distinct \"did\") as \"unique_listeners\" from \"plays\"", 331 + "name": "mv_global_play_count", 332 + "schema": "public", 333 + "isExisting": false, 334 + "materialized": true 107 335 }, 108 - "columns": {} 336 + "public.mv_recording_play_counts": { 337 + "columns": { 338 + "mbid": { 339 + "name": "mbid", 340 + "type": "uuid", 341 + "primaryKey": true, 342 + "notNull": true 343 + }, 344 + "name": { 345 + "name": "name", 346 + "type": "text", 347 + "primaryKey": false, 348 + "notNull": true 349 + } 350 + }, 351 + "definition": "select \"recordings\".\"mbid\", \"recordings\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"recordings\" left join \"plays\" on \"plays\".\"recording_mbid\" = \"recordings\".\"mbid\" group by \"recordings\".\"mbid\", \"recordings\".\"name\"", 352 + "name": "mv_recording_play_counts", 353 + "schema": "public", 354 + "isExisting": false, 355 + "materialized": true 356 + }, 357 + "public.mv_release_play_counts": { 358 + "columns": { 359 + "mbid": { 360 + "name": "mbid", 361 + "type": "uuid", 362 + "primaryKey": true, 363 + "notNull": true 364 + }, 365 + "name": { 366 + "name": "name", 367 + "type": "text", 368 + "primaryKey": false, 369 + "notNull": true 370 + } 371 + }, 372 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" left join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" group by \"releases\".\"mbid\", \"releases\".\"name\"", 373 + "name": "mv_release_play_counts", 374 + "schema": "public", 375 + "isExisting": false, 376 + "materialized": true 377 + }, 378 + "public.mv_top_artists_30days": { 379 + "columns": { 380 + "mbid": { 381 + "name": "mbid", 382 + "type": "uuid", 383 + "primaryKey": true, 384 + "notNull": true 385 + }, 386 + "name": { 387 + "name": "name", 388 + "type": "text", 389 + "primaryKey": false, 390 + "notNull": true 391 + } 392 + }, 393 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" inner join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" inner join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"artists\".\"mbid\", \"artists\".\"name\" order by count(\"plays\".\"uri\") DESC", 394 + "name": "mv_top_artists_30days", 395 + "schema": "public", 396 + "isExisting": false, 397 + "materialized": true 398 + }, 399 + "public.mv_top_releases_30days": { 400 + "columns": { 401 + "mbid": { 402 + "name": "mbid", 403 + "type": "uuid", 404 + "primaryKey": true, 405 + "notNull": true 406 + }, 407 + "name": { 408 + "name": "name", 409 + "type": "text", 410 + "primaryKey": false, 411 + "notNull": true 412 + } 413 + }, 414 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" inner join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"releases\".\"mbid\", \"releases\".\"name\" order by count(\"plays\".\"uri\") DESC", 415 + "name": "mv_top_releases_30days", 416 + "schema": "public", 417 + "isExisting": false, 418 + "materialized": true 419 + } 109 420 }, 110 - "internal": { 111 - "indexes": {} 421 + "_meta": { 422 + "columns": {}, 423 + "schemas": {}, 424 + "tables": {} 112 425 } 113 426 }
+425 -93
packages/db/.drizzle/meta/0003_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "7710000b-44fd-4d23-a768-0117f22926c3", 5 - "prevId": "1123b2f8-11c2-4d28-925a-f7a30bb3bec6", 2 + "id": "a244bd07-cde6-4d15-8384-4a94d7cf4cb9", 3 + "prevId": "3f2b9825-e988-4335-8fb8-f12d54efc11f", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 9 10 "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 13 14 "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 15 + "notNull": true 16 16 }, 17 - "session": { 18 - "name": "session", 17 + "name": { 18 + "name": "name", 19 19 "type": "text", 20 20 "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 23 29 } 24 30 }, 25 31 "indexes": {}, 26 32 "foreignKeys": {}, 27 33 "compositePrimaryKeys": {}, 28 34 "uniqueConstraints": {}, 29 - "checkConstraints": {} 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 30 38 }, 31 - "auth_state": { 32 - "name": "auth_state", 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 33 42 "columns": { 34 - "key": { 35 - "name": "key", 43 + "artist_mbid": { 44 + "name": "artist_mbid", 45 + "type": "uuid", 46 + "primaryKey": false, 47 + "notNull": true 48 + }, 49 + "artist_name": { 50 + "name": "artist_name", 36 51 "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 52 + "primaryKey": false, 53 + "notNull": false 40 54 }, 41 - "state": { 42 - "name": "state", 55 + "play_uri": { 56 + "name": "play_uri", 43 57 "type": "text", 44 58 "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 59 + "notNull": true 47 60 } 48 61 }, 49 62 "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 63 + "foreignKeys": { 64 + "play_to_artists_artist_mbid_artists_mbid_fk": { 65 + "name": "play_to_artists_artist_mbid_artists_mbid_fk", 66 + "tableFrom": "play_to_artists", 67 + "tableTo": "artists", 68 + "columnsFrom": [ 69 + "artist_mbid" 70 + ], 71 + "columnsTo": [ 72 + "mbid" 73 + ], 74 + "onDelete": "no action", 75 + "onUpdate": "no action" 76 + }, 77 + "play_to_artists_play_uri_plays_uri_fk": { 78 + "name": "play_to_artists_play_uri_plays_uri_fk", 79 + "tableFrom": "play_to_artists", 80 + "tableTo": "plays", 81 + "columnsFrom": [ 82 + "play_uri" 83 + ], 84 + "columnsTo": [ 85 + "uri" 86 + ], 87 + "onDelete": "no action", 88 + "onUpdate": "no action" 89 + } 90 + }, 91 + "compositePrimaryKeys": { 92 + "play_to_artists_play_uri_artist_mbid_pk": { 93 + "name": "play_to_artists_play_uri_artist_mbid_pk", 94 + "columns": [ 95 + "play_uri", 96 + "artist_mbid" 97 + ] 98 + } 99 + }, 52 100 "uniqueConstraints": {}, 53 - "checkConstraints": {} 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 54 104 }, 55 - "status": { 56 - "name": "status", 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 57 108 "columns": { 109 + "cid": { 110 + "name": "cid", 111 + "type": "text", 112 + "primaryKey": false, 113 + "notNull": true 114 + }, 115 + "did": { 116 + "name": "did", 117 + "type": "text", 118 + "primaryKey": false, 119 + "notNull": true 120 + }, 121 + "duration": { 122 + "name": "duration", 123 + "type": "integer", 124 + "primaryKey": false, 125 + "notNull": false 126 + }, 127 + "isrc": { 128 + "name": "isrc", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": false 132 + }, 133 + "music_service_base_domain": { 134 + "name": "music_service_base_domain", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "origin_url": { 140 + "name": "origin_url", 141 + "type": "text", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "played_time": { 146 + "name": "played_time", 147 + "type": "timestamp with time zone", 148 + "primaryKey": false, 149 + "notNull": false 150 + }, 151 + "processed_time": { 152 + "name": "processed_time", 153 + "type": "timestamp with time zone", 154 + "primaryKey": false, 155 + "notNull": false, 156 + "default": "now()" 157 + }, 158 + "rkey": { 159 + "name": "rkey", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": true 163 + }, 164 + "recording_mbid": { 165 + "name": "recording_mbid", 166 + "type": "uuid", 167 + "primaryKey": false, 168 + "notNull": false 169 + }, 170 + "release_mbid": { 171 + "name": "release_mbid", 172 + "type": "uuid", 173 + "primaryKey": false, 174 + "notNull": false 175 + }, 176 + "release_name": { 177 + "name": "release_name", 178 + "type": "text", 179 + "primaryKey": false, 180 + "notNull": false 181 + }, 182 + "submission_client_agent": { 183 + "name": "submission_client_agent", 184 + "type": "text", 185 + "primaryKey": false, 186 + "notNull": false 187 + }, 188 + "track_name": { 189 + "name": "track_name", 190 + "type": "text", 191 + "primaryKey": false, 192 + "notNull": true 193 + }, 58 194 "uri": { 59 195 "name": "uri", 60 196 "type": "text", 61 197 "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 198 + "notNull": true 199 + } 200 + }, 201 + "indexes": {}, 202 + "foreignKeys": { 203 + "plays_recording_mbid_recordings_mbid_fk": { 204 + "name": "plays_recording_mbid_recordings_mbid_fk", 205 + "tableFrom": "plays", 206 + "tableTo": "recordings", 207 + "columnsFrom": [ 208 + "recording_mbid" 209 + ], 210 + "columnsTo": [ 211 + "mbid" 212 + ], 213 + "onDelete": "no action", 214 + "onUpdate": "no action" 64 215 }, 65 - "author_did": { 66 - "name": "author_did", 216 + "plays_release_mbid_releases_mbid_fk": { 217 + "name": "plays_release_mbid_releases_mbid_fk", 218 + "tableFrom": "plays", 219 + "tableTo": "releases", 220 + "columnsFrom": [ 221 + "release_mbid" 222 + ], 223 + "columnsTo": [ 224 + "mbid" 225 + ], 226 + "onDelete": "no action", 227 + "onUpdate": "no action" 228 + } 229 + }, 230 + "compositePrimaryKeys": {}, 231 + "uniqueConstraints": {}, 232 + "policies": {}, 233 + "checkConstraints": {}, 234 + "isRLSEnabled": false 235 + }, 236 + "public.profiles": { 237 + "name": "profiles", 238 + "schema": "", 239 + "columns": { 240 + "did": { 241 + "name": "did", 242 + "type": "text", 243 + "primaryKey": true, 244 + "notNull": true 245 + }, 246 + "display_name": { 247 + "name": "display_name", 67 248 "type": "text", 68 249 "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 250 + "notNull": true 71 251 }, 72 - "status": { 73 - "name": "status", 252 + "description": { 253 + "name": "description", 74 254 "type": "text", 75 255 "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 256 + "notNull": true 257 + }, 258 + "description_facets": { 259 + "name": "description_facets", 260 + "type": "jsonb", 261 + "primaryKey": false, 262 + "notNull": true 78 263 }, 79 - "created_at": { 80 - "name": "created_at", 264 + "avatar": { 265 + "name": "avatar", 81 266 "type": "text", 82 267 "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 268 + "notNull": true 85 269 }, 86 - "indexed_at": { 87 - "name": "indexed_at", 270 + "banner": { 271 + "name": "banner", 88 272 "type": "text", 89 273 "primaryKey": false, 90 - "notNull": true, 91 - "autoincrement": false 274 + "notNull": true 275 + }, 276 + "created_at": { 277 + "name": "created_at", 278 + "type": "timestamp", 279 + "primaryKey": false, 280 + "notNull": true 92 281 } 93 282 }, 94 283 "indexes": {}, 95 284 "foreignKeys": {}, 96 285 "compositePrimaryKeys": {}, 97 286 "uniqueConstraints": {}, 98 - "checkConstraints": {} 287 + "policies": {}, 288 + "checkConstraints": {}, 289 + "isRLSEnabled": false 99 290 }, 100 - "teal_session": { 101 - "name": "teal_session", 291 + "public.recordings": { 292 + "name": "recordings", 293 + "schema": "", 102 294 "columns": { 103 - "key": { 104 - "name": "key", 105 - "type": "text", 295 + "mbid": { 296 + "name": "mbid", 297 + "type": "uuid", 106 298 "primaryKey": true, 107 - "notNull": true, 108 - "autoincrement": false 299 + "notNull": true 109 300 }, 110 - "session": { 111 - "name": "session", 301 + "name": { 302 + "name": "name", 112 303 "type": "text", 113 304 "primaryKey": false, 114 - "notNull": true, 115 - "autoincrement": false 305 + "notNull": true 116 306 }, 117 - "provider": { 118 - "name": "provider", 307 + "play_count": { 308 + "name": "play_count", 309 + "type": "integer", 310 + "primaryKey": false, 311 + "notNull": false, 312 + "default": 0 313 + } 314 + }, 315 + "indexes": {}, 316 + "foreignKeys": {}, 317 + "compositePrimaryKeys": {}, 318 + "uniqueConstraints": {}, 319 + "policies": {}, 320 + "checkConstraints": {}, 321 + "isRLSEnabled": false 322 + }, 323 + "public.releases": { 324 + "name": "releases", 325 + "schema": "", 326 + "columns": { 327 + "mbid": { 328 + "name": "mbid", 329 + "type": "uuid", 330 + "primaryKey": true, 331 + "notNull": true 332 + }, 333 + "name": { 334 + "name": "name", 119 335 "type": "text", 120 336 "primaryKey": false, 121 - "notNull": true, 122 - "autoincrement": false 337 + "notNull": true 338 + }, 339 + "play_count": { 340 + "name": "play_count", 341 + "type": "integer", 342 + "primaryKey": false, 343 + "notNull": false, 344 + "default": 0 123 345 } 124 346 }, 125 347 "indexes": {}, 126 348 "foreignKeys": {}, 127 349 "compositePrimaryKeys": {}, 128 350 "uniqueConstraints": {}, 129 - "checkConstraints": {} 351 + "policies": {}, 352 + "checkConstraints": {}, 353 + "isRLSEnabled": false 130 354 }, 131 - "teal_user": { 132 - "name": "teal_user", 355 + "public.featured_items": { 356 + "name": "featured_items", 357 + "schema": "", 133 358 "columns": { 134 359 "did": { 135 360 "name": "did", 136 361 "type": "text", 137 362 "primaryKey": true, 138 - "notNull": true, 139 - "autoincrement": false 140 - }, 141 - "handle": { 142 - "name": "handle", 143 - "type": "text", 144 - "primaryKey": false, 145 - "notNull": true, 146 - "autoincrement": false 363 + "notNull": true 147 364 }, 148 - "email": { 149 - "name": "email", 365 + "mbid": { 366 + "name": "mbid", 150 367 "type": "text", 151 368 "primaryKey": false, 152 - "notNull": true, 153 - "autoincrement": false 369 + "notNull": true 154 370 }, 155 - "created_at": { 156 - "name": "created_at", 371 + "type": { 372 + "name": "type", 157 373 "type": "text", 158 374 "primaryKey": false, 159 - "notNull": true, 160 - "autoincrement": false 375 + "notNull": true 161 376 } 162 377 }, 163 378 "indexes": {}, 164 379 "foreignKeys": {}, 165 380 "compositePrimaryKeys": {}, 166 381 "uniqueConstraints": {}, 167 - "checkConstraints": {} 382 + "policies": {}, 383 + "checkConstraints": {}, 384 + "isRLSEnabled": false 168 385 } 169 386 }, 170 - "views": {}, 171 387 "enums": {}, 388 + "schemas": {}, 389 + "sequences": {}, 390 + "roles": {}, 391 + "policies": {}, 392 + "views": { 393 + "public.mv_artist_play_counts": { 394 + "columns": { 395 + "mbid": { 396 + "name": "mbid", 397 + "type": "uuid", 398 + "primaryKey": true, 399 + "notNull": true 400 + }, 401 + "name": { 402 + "name": "name", 403 + "type": "text", 404 + "primaryKey": false, 405 + "notNull": true 406 + } 407 + }, 408 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" left join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" left join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" group by \"artists\".\"mbid\", \"artists\".\"name\"", 409 + "name": "mv_artist_play_counts", 410 + "schema": "public", 411 + "isExisting": false, 412 + "materialized": true 413 + }, 414 + "public.mv_global_play_count": { 415 + "columns": {}, 416 + "definition": "select count(\"uri\") as \"total_plays\", count(distinct \"did\") as \"unique_listeners\" from \"plays\"", 417 + "name": "mv_global_play_count", 418 + "schema": "public", 419 + "isExisting": false, 420 + "materialized": true 421 + }, 422 + "public.mv_recording_play_counts": { 423 + "columns": { 424 + "mbid": { 425 + "name": "mbid", 426 + "type": "uuid", 427 + "primaryKey": true, 428 + "notNull": true 429 + }, 430 + "name": { 431 + "name": "name", 432 + "type": "text", 433 + "primaryKey": false, 434 + "notNull": true 435 + } 436 + }, 437 + "definition": "select \"recordings\".\"mbid\", \"recordings\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"recordings\" left join \"plays\" on \"plays\".\"recording_mbid\" = \"recordings\".\"mbid\" group by \"recordings\".\"mbid\", \"recordings\".\"name\"", 438 + "name": "mv_recording_play_counts", 439 + "schema": "public", 440 + "isExisting": false, 441 + "materialized": true 442 + }, 443 + "public.mv_release_play_counts": { 444 + "columns": { 445 + "mbid": { 446 + "name": "mbid", 447 + "type": "uuid", 448 + "primaryKey": true, 449 + "notNull": true 450 + }, 451 + "name": { 452 + "name": "name", 453 + "type": "text", 454 + "primaryKey": false, 455 + "notNull": true 456 + } 457 + }, 458 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" left join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" group by \"releases\".\"mbid\", \"releases\".\"name\"", 459 + "name": "mv_release_play_counts", 460 + "schema": "public", 461 + "isExisting": false, 462 + "materialized": true 463 + }, 464 + "public.mv_top_artists_30days": { 465 + "columns": { 466 + "mbid": { 467 + "name": "mbid", 468 + "type": "uuid", 469 + "primaryKey": true, 470 + "notNull": true 471 + }, 472 + "name": { 473 + "name": "name", 474 + "type": "text", 475 + "primaryKey": false, 476 + "notNull": true 477 + } 478 + }, 479 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" inner join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" inner join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"artists\".\"mbid\", \"artists\".\"name\" order by count(\"plays\".\"uri\") DESC", 480 + "name": "mv_top_artists_30days", 481 + "schema": "public", 482 + "isExisting": false, 483 + "materialized": true 484 + }, 485 + "public.mv_top_releases_30days": { 486 + "columns": { 487 + "mbid": { 488 + "name": "mbid", 489 + "type": "uuid", 490 + "primaryKey": true, 491 + "notNull": true 492 + }, 493 + "name": { 494 + "name": "name", 495 + "type": "text", 496 + "primaryKey": false, 497 + "notNull": true 498 + } 499 + }, 500 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" inner join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"releases\".\"mbid\", \"releases\".\"name\" order by count(\"plays\".\"uri\") DESC", 501 + "name": "mv_top_releases_30days", 502 + "schema": "public", 503 + "isExisting": false, 504 + "materialized": true 505 + } 506 + }, 172 507 "_meta": { 508 + "columns": {}, 173 509 "schemas": {}, 174 - "tables": {}, 175 - "columns": {} 176 - }, 177 - "internal": { 178 - "indexes": {} 510 + "tables": {} 179 511 } 180 512 }
+378 -207
packages/db/.drizzle/meta/0004_snapshot.json
··· 1 1 { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "639ec806-61a1-448d-a922-1935bf8f6cf3", 5 - "prevId": "7710000b-44fd-4d23-a768-0117f22926c3", 2 + "id": "01c93ffb-209e-48f7-9d79-1a4b624c2b0b", 3 + "prevId": "a244bd07-cde6-4d15-8384-4a94d7cf4cb9", 4 + "version": "7", 5 + "dialect": "postgresql", 6 6 "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 9 10 "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 13 14 "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 15 + "notNull": true 16 16 }, 17 - "session": { 18 - "name": "session", 17 + "name": { 18 + "name": "name", 19 19 "type": "text", 20 20 "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 23 29 } 24 30 }, 25 31 "indexes": {}, 26 32 "foreignKeys": {}, 27 33 "compositePrimaryKeys": {}, 28 34 "uniqueConstraints": {}, 29 - "checkConstraints": {} 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 30 38 }, 31 - "auth_state": { 32 - "name": "auth_state", 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 33 42 "columns": { 34 - "key": { 35 - "name": "key", 43 + "artist_mbid": { 44 + "name": "artist_mbid", 45 + "type": "uuid", 46 + "primaryKey": false, 47 + "notNull": true 48 + }, 49 + "artist_name": { 50 + "name": "artist_name", 36 51 "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 52 + "primaryKey": false, 53 + "notNull": false 40 54 }, 41 - "state": { 42 - "name": "state", 55 + "play_uri": { 56 + "name": "play_uri", 43 57 "type": "text", 44 58 "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 59 + "notNull": true 47 60 } 48 61 }, 49 62 "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 52 - "uniqueConstraints": {}, 53 - "checkConstraints": {} 54 - }, 55 - "follow": { 56 - "name": "follow", 57 - "columns": { 58 - "follower": { 59 - "name": "follower", 60 - "type": "text", 61 - "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 63 + "foreignKeys": { 64 + "play_to_artists_artist_mbid_artists_mbid_fk": { 65 + "name": "play_to_artists_artist_mbid_artists_mbid_fk", 66 + "tableFrom": "play_to_artists", 67 + "tableTo": "artists", 68 + "columnsFrom": [ 69 + "artist_mbid" 70 + ], 71 + "columnsTo": [ 72 + "mbid" 73 + ], 74 + "onDelete": "no action", 75 + "onUpdate": "no action" 64 76 }, 65 - "followed": { 66 - "name": "followed", 67 - "type": "text", 68 - "primaryKey": true, 69 - "notNull": true, 70 - "autoincrement": false 71 - }, 72 - "created_at": { 73 - "name": "created_at", 74 - "type": "text", 75 - "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 77 + "play_to_artists_play_uri_plays_uri_fk": { 78 + "name": "play_to_artists_play_uri_plays_uri_fk", 79 + "tableFrom": "play_to_artists", 80 + "tableTo": "plays", 81 + "columnsFrom": [ 82 + "play_uri" 83 + ], 84 + "columnsTo": [ 85 + "uri" 86 + ], 87 + "onDelete": "no action", 88 + "onUpdate": "no action" 89 + } 90 + }, 91 + "compositePrimaryKeys": { 92 + "play_to_artists_play_uri_artist_mbid_pk": { 93 + "name": "play_to_artists_play_uri_artist_mbid_pk", 94 + "columns": [ 95 + "play_uri", 96 + "artist_mbid" 97 + ] 78 98 } 79 99 }, 80 - "indexes": {}, 81 - "foreignKeys": {}, 82 - "compositePrimaryKeys": {}, 83 100 "uniqueConstraints": {}, 84 - "checkConstraints": {} 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 85 104 }, 86 - "play": { 87 - "name": "play", 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 88 108 "columns": { 89 - "uri": { 90 - "name": "uri", 109 + "cid": { 110 + "name": "cid", 91 111 "type": "text", 92 - "primaryKey": true, 93 - "notNull": true, 94 - "autoincrement": false 112 + "primaryKey": false, 113 + "notNull": true 95 114 }, 96 - "author_did": { 97 - "name": "author_did", 115 + "did": { 116 + "name": "did", 98 117 "type": "text", 99 118 "primaryKey": false, 100 - "notNull": true, 101 - "autoincrement": false 119 + "notNull": true 102 120 }, 103 - "created_at": { 104 - "name": "created_at", 105 - "type": "text", 121 + "duration": { 122 + "name": "duration", 123 + "type": "integer", 106 124 "primaryKey": false, 107 - "notNull": true, 108 - "autoincrement": false 125 + "notNull": false 109 126 }, 110 - "indexed_at": { 111 - "name": "indexed_at", 127 + "isrc": { 128 + "name": "isrc", 112 129 "type": "text", 113 130 "primaryKey": false, 114 - "notNull": true, 115 - "autoincrement": false 131 + "notNull": false 116 132 }, 117 - "track_name": { 118 - "name": "track_name", 133 + "music_service_base_domain": { 134 + "name": "music_service_base_domain", 119 135 "type": "text", 120 136 "primaryKey": false, 121 - "notNull": true, 122 - "autoincrement": false 137 + "notNull": false 123 138 }, 124 - "track_mb_id": { 125 - "name": "track_mb_id", 139 + "origin_url": { 140 + "name": "origin_url", 126 141 "type": "text", 127 142 "primaryKey": false, 128 - "notNull": false, 129 - "autoincrement": false 143 + "notNull": false 130 144 }, 131 - "recording_mb_id": { 132 - "name": "recording_mb_id", 133 - "type": "text", 145 + "played_time": { 146 + "name": "played_time", 147 + "type": "timestamp with time zone", 134 148 "primaryKey": false, 135 - "notNull": false, 136 - "autoincrement": false 149 + "notNull": false 137 150 }, 138 - "duration": { 139 - "name": "duration", 140 - "type": "integer", 151 + "processed_time": { 152 + "name": "processed_time", 153 + "type": "timestamp with time zone", 141 154 "primaryKey": false, 142 155 "notNull": false, 143 - "autoincrement": false 156 + "default": "now()" 144 157 }, 145 - "artist_name": { 146 - "name": "artist_name", 158 + "rkey": { 159 + "name": "rkey", 147 160 "type": "text", 148 161 "primaryKey": false, 149 - "notNull": true, 150 - "autoincrement": false 162 + "notNull": true 151 163 }, 152 - "artist_mb_ids": { 153 - "name": "artist_mb_ids", 154 - "type": "text", 164 + "recording_mbid": { 165 + "name": "recording_mbid", 166 + "type": "uuid", 155 167 "primaryKey": false, 156 - "notNull": false, 157 - "autoincrement": false 168 + "notNull": false 169 + }, 170 + "release_mbid": { 171 + "name": "release_mbid", 172 + "type": "uuid", 173 + "primaryKey": false, 174 + "notNull": false 158 175 }, 159 176 "release_name": { 160 177 "name": "release_name", 161 178 "type": "text", 162 179 "primaryKey": false, 163 - "notNull": false, 164 - "autoincrement": false 180 + "notNull": false 181 + }, 182 + "submission_client_agent": { 183 + "name": "submission_client_agent", 184 + "type": "text", 185 + "primaryKey": false, 186 + "notNull": false 165 187 }, 166 - "release_mb_id": { 167 - "name": "release_mb_id", 188 + "track_name": { 189 + "name": "track_name", 168 190 "type": "text", 169 191 "primaryKey": false, 170 - "notNull": false, 171 - "autoincrement": false 192 + "notNull": true 193 + }, 194 + "uri": { 195 + "name": "uri", 196 + "type": "text", 197 + "primaryKey": true, 198 + "notNull": true 199 + } 200 + }, 201 + "indexes": {}, 202 + "foreignKeys": { 203 + "plays_recording_mbid_recordings_mbid_fk": { 204 + "name": "plays_recording_mbid_recordings_mbid_fk", 205 + "tableFrom": "plays", 206 + "tableTo": "recordings", 207 + "columnsFrom": [ 208 + "recording_mbid" 209 + ], 210 + "columnsTo": [ 211 + "mbid" 212 + ], 213 + "onDelete": "no action", 214 + "onUpdate": "no action" 215 + }, 216 + "plays_release_mbid_releases_mbid_fk": { 217 + "name": "plays_release_mbid_releases_mbid_fk", 218 + "tableFrom": "plays", 219 + "tableTo": "releases", 220 + "columnsFrom": [ 221 + "release_mbid" 222 + ], 223 + "columnsTo": [ 224 + "mbid" 225 + ], 226 + "onDelete": "no action", 227 + "onUpdate": "no action" 228 + } 229 + }, 230 + "compositePrimaryKeys": {}, 231 + "uniqueConstraints": {}, 232 + "policies": {}, 233 + "checkConstraints": {}, 234 + "isRLSEnabled": false 235 + }, 236 + "public.profiles": { 237 + "name": "profiles", 238 + "schema": "", 239 + "columns": { 240 + "did": { 241 + "name": "did", 242 + "type": "text", 243 + "primaryKey": true, 244 + "notNull": true 172 245 }, 173 - "isrc": { 174 - "name": "isrc", 246 + "handle": { 247 + "name": "handle", 175 248 "type": "text", 176 249 "primaryKey": false, 177 - "notNull": false, 178 - "autoincrement": false 250 + "notNull": false 179 251 }, 180 - "origin_url": { 181 - "name": "origin_url", 252 + "display_name": { 253 + "name": "display_name", 182 254 "type": "text", 183 255 "primaryKey": false, 184 - "notNull": false, 185 - "autoincrement": false 256 + "notNull": false 186 257 }, 187 - "music_service_base_domain": { 188 - "name": "music_service_base_domain", 258 + "description": { 259 + "name": "description", 189 260 "type": "text", 190 261 "primaryKey": false, 191 - "notNull": false, 192 - "autoincrement": false 262 + "notNull": false 193 263 }, 194 - "submission_client_agent": { 195 - "name": "submission_client_agent", 264 + "description_facets": { 265 + "name": "description_facets", 266 + "type": "jsonb", 267 + "primaryKey": false, 268 + "notNull": false 269 + }, 270 + "avatar": { 271 + "name": "avatar", 196 272 "type": "text", 197 273 "primaryKey": false, 198 - "notNull": false, 199 - "autoincrement": false 274 + "notNull": false 200 275 }, 201 - "played_time": { 202 - "name": "played_time", 276 + "banner": { 277 + "name": "banner", 203 278 "type": "text", 204 279 "primaryKey": false, 205 - "notNull": false, 206 - "autoincrement": false 280 + "notNull": false 281 + }, 282 + "created_at": { 283 + "name": "created_at", 284 + "type": "timestamp", 285 + "primaryKey": false, 286 + "notNull": false 207 287 } 208 288 }, 209 289 "indexes": {}, 210 290 "foreignKeys": {}, 211 291 "compositePrimaryKeys": {}, 212 292 "uniqueConstraints": {}, 213 - "checkConstraints": {} 293 + "policies": {}, 294 + "checkConstraints": {}, 295 + "isRLSEnabled": false 214 296 }, 215 - "status": { 216 - "name": "status", 297 + "public.recordings": { 298 + "name": "recordings", 299 + "schema": "", 217 300 "columns": { 218 - "uri": { 219 - "name": "uri", 220 - "type": "text", 301 + "mbid": { 302 + "name": "mbid", 303 + "type": "uuid", 221 304 "primaryKey": true, 222 - "notNull": true, 223 - "autoincrement": false 305 + "notNull": true 224 306 }, 225 - "author_did": { 226 - "name": "author_did", 307 + "name": { 308 + "name": "name", 227 309 "type": "text", 228 310 "primaryKey": false, 229 - "notNull": true, 230 - "autoincrement": false 311 + "notNull": true 231 312 }, 232 - "status": { 233 - "name": "status", 234 - "type": "text", 313 + "play_count": { 314 + "name": "play_count", 315 + "type": "integer", 235 316 "primaryKey": false, 236 - "notNull": true, 237 - "autoincrement": false 317 + "notNull": false, 318 + "default": 0 319 + } 320 + }, 321 + "indexes": {}, 322 + "foreignKeys": {}, 323 + "compositePrimaryKeys": {}, 324 + "uniqueConstraints": {}, 325 + "policies": {}, 326 + "checkConstraints": {}, 327 + "isRLSEnabled": false 328 + }, 329 + "public.releases": { 330 + "name": "releases", 331 + "schema": "", 332 + "columns": { 333 + "mbid": { 334 + "name": "mbid", 335 + "type": "uuid", 336 + "primaryKey": true, 337 + "notNull": true 238 338 }, 239 - "created_at": { 240 - "name": "created_at", 339 + "name": { 340 + "name": "name", 241 341 "type": "text", 242 342 "primaryKey": false, 243 - "notNull": true, 244 - "autoincrement": false 343 + "notNull": true 245 344 }, 246 - "indexed_at": { 247 - "name": "indexed_at", 248 - "type": "text", 345 + "play_count": { 346 + "name": "play_count", 347 + "type": "integer", 249 348 "primaryKey": false, 250 - "notNull": true, 251 - "autoincrement": false 349 + "notNull": false, 350 + "default": 0 252 351 } 253 352 }, 254 353 "indexes": {}, 255 354 "foreignKeys": {}, 256 355 "compositePrimaryKeys": {}, 257 356 "uniqueConstraints": {}, 258 - "checkConstraints": {} 357 + "policies": {}, 358 + "checkConstraints": {}, 359 + "isRLSEnabled": false 259 360 }, 260 - "teal_session": { 261 - "name": "teal_session", 361 + "public.featured_items": { 362 + "name": "featured_items", 363 + "schema": "", 262 364 "columns": { 263 - "key": { 264 - "name": "key", 365 + "did": { 366 + "name": "did", 265 367 "type": "text", 266 368 "primaryKey": true, 267 - "notNull": true, 268 - "autoincrement": false 369 + "notNull": true 269 370 }, 270 - "session": { 271 - "name": "session", 371 + "mbid": { 372 + "name": "mbid", 272 373 "type": "text", 273 374 "primaryKey": false, 274 - "notNull": true, 275 - "autoincrement": false 375 + "notNull": true 276 376 }, 277 - "provider": { 278 - "name": "provider", 377 + "type": { 378 + "name": "type", 279 379 "type": "text", 280 380 "primaryKey": false, 281 - "notNull": true, 282 - "autoincrement": false 381 + "notNull": true 283 382 } 284 383 }, 285 384 "indexes": {}, 286 385 "foreignKeys": {}, 287 386 "compositePrimaryKeys": {}, 288 387 "uniqueConstraints": {}, 289 - "checkConstraints": {} 388 + "policies": {}, 389 + "checkConstraints": {}, 390 + "isRLSEnabled": false 391 + } 392 + }, 393 + "enums": {}, 394 + "schemas": {}, 395 + "sequences": {}, 396 + "roles": {}, 397 + "policies": {}, 398 + "views": { 399 + "public.mv_artist_play_counts": { 400 + "columns": { 401 + "mbid": { 402 + "name": "mbid", 403 + "type": "uuid", 404 + "primaryKey": true, 405 + "notNull": true 406 + }, 407 + "name": { 408 + "name": "name", 409 + "type": "text", 410 + "primaryKey": false, 411 + "notNull": true 412 + } 413 + }, 414 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" left join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" left join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" group by \"artists\".\"mbid\", \"artists\".\"name\"", 415 + "name": "mv_artist_play_counts", 416 + "schema": "public", 417 + "isExisting": false, 418 + "materialized": true 419 + }, 420 + "public.mv_global_play_count": { 421 + "columns": {}, 422 + "definition": "select count(\"uri\") as \"total_plays\", count(distinct \"did\") as \"unique_listeners\" from \"plays\"", 423 + "name": "mv_global_play_count", 424 + "schema": "public", 425 + "isExisting": false, 426 + "materialized": true 290 427 }, 291 - "teal_user": { 292 - "name": "teal_user", 428 + "public.mv_recording_play_counts": { 293 429 "columns": { 294 - "did": { 295 - "name": "did", 296 - "type": "text", 430 + "mbid": { 431 + "name": "mbid", 432 + "type": "uuid", 297 433 "primaryKey": true, 298 - "notNull": true, 299 - "autoincrement": false 434 + "notNull": true 300 435 }, 301 - "handle": { 302 - "name": "handle", 436 + "name": { 437 + "name": "name", 303 438 "type": "text", 304 439 "primaryKey": false, 305 - "notNull": true, 306 - "autoincrement": false 440 + "notNull": true 441 + } 442 + }, 443 + "definition": "select \"recordings\".\"mbid\", \"recordings\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"recordings\" left join \"plays\" on \"plays\".\"recording_mbid\" = \"recordings\".\"mbid\" group by \"recordings\".\"mbid\", \"recordings\".\"name\"", 444 + "name": "mv_recording_play_counts", 445 + "schema": "public", 446 + "isExisting": false, 447 + "materialized": true 448 + }, 449 + "public.mv_release_play_counts": { 450 + "columns": { 451 + "mbid": { 452 + "name": "mbid", 453 + "type": "uuid", 454 + "primaryKey": true, 455 + "notNull": true 307 456 }, 308 - "avatar": { 309 - "name": "avatar", 457 + "name": { 458 + "name": "name", 310 459 "type": "text", 311 460 "primaryKey": false, 312 - "notNull": true, 313 - "autoincrement": false 461 + "notNull": true 462 + } 463 + }, 464 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" left join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" group by \"releases\".\"mbid\", \"releases\".\"name\"", 465 + "name": "mv_release_play_counts", 466 + "schema": "public", 467 + "isExisting": false, 468 + "materialized": true 469 + }, 470 + "public.mv_top_artists_30days": { 471 + "columns": { 472 + "mbid": { 473 + "name": "mbid", 474 + "type": "uuid", 475 + "primaryKey": true, 476 + "notNull": true 314 477 }, 315 - "bio": { 316 - "name": "bio", 478 + "name": { 479 + "name": "name", 317 480 "type": "text", 318 481 "primaryKey": false, 319 - "notNull": false, 320 - "autoincrement": false 482 + "notNull": true 483 + } 484 + }, 485 + "definition": "select \"artists\".\"mbid\", \"artists\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"artists\" inner join \"play_to_artists\" on \"artists\".\"mbid\" = \"play_to_artists\".\"artist_mbid\" inner join \"plays\" on \"plays\".\"uri\" = \"play_to_artists\".\"play_uri\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"artists\".\"mbid\", \"artists\".\"name\" order by count(\"plays\".\"uri\") DESC", 486 + "name": "mv_top_artists_30days", 487 + "schema": "public", 488 + "isExisting": false, 489 + "materialized": true 490 + }, 491 + "public.mv_top_releases_30days": { 492 + "columns": { 493 + "mbid": { 494 + "name": "mbid", 495 + "type": "uuid", 496 + "primaryKey": true, 497 + "notNull": true 321 498 }, 322 - "created_at": { 323 - "name": "created_at", 499 + "name": { 500 + "name": "name", 324 501 "type": "text", 325 502 "primaryKey": false, 326 - "notNull": true, 327 - "autoincrement": false 503 + "notNull": true 328 504 } 329 505 }, 330 - "indexes": {}, 331 - "foreignKeys": {}, 332 - "compositePrimaryKeys": {}, 333 - "uniqueConstraints": {}, 334 - "checkConstraints": {} 506 + "definition": "select \"releases\".\"mbid\", \"releases\".\"name\", count(\"plays\".\"uri\") as \"play_count\" from \"releases\" inner join \"plays\" on \"plays\".\"release_mbid\" = \"releases\".\"mbid\" where \"plays\".\"played_time\" >= NOW() - INTERVAL '30 days' group by \"releases\".\"mbid\", \"releases\".\"name\" order by count(\"plays\".\"uri\") DESC", 507 + "name": "mv_top_releases_30days", 508 + "schema": "public", 509 + "isExisting": false, 510 + "materialized": true 335 511 } 336 512 }, 337 - "views": {}, 338 - "enums": {}, 339 513 "_meta": { 514 + "columns": {}, 340 515 "schemas": {}, 341 - "tables": {}, 342 - "columns": {} 343 - }, 344 - "internal": { 345 - "indexes": {} 516 + "tables": {} 346 517 } 347 518 }
-354
packages/db/.drizzle/meta/0005_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "42a94e7a-c4c2-4bd5-92d9-ba8a829c0704", 5 - "prevId": "639ec806-61a1-448d-a922-1935bf8f6cf3", 6 - "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 9 - "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 13 - "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 16 - }, 17 - "session": { 18 - "name": "session", 19 - "type": "text", 20 - "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 23 - } 24 - }, 25 - "indexes": {}, 26 - "foreignKeys": {}, 27 - "compositePrimaryKeys": {}, 28 - "uniqueConstraints": {}, 29 - "checkConstraints": {} 30 - }, 31 - "auth_state": { 32 - "name": "auth_state", 33 - "columns": { 34 - "key": { 35 - "name": "key", 36 - "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 40 - }, 41 - "state": { 42 - "name": "state", 43 - "type": "text", 44 - "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 47 - } 48 - }, 49 - "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 52 - "uniqueConstraints": {}, 53 - "checkConstraints": {} 54 - }, 55 - "follow": { 56 - "name": "follow", 57 - "columns": { 58 - "rel_id": { 59 - "name": "rel_id", 60 - "type": "text", 61 - "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 64 - }, 65 - "follower": { 66 - "name": "follower", 67 - "type": "text", 68 - "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 71 - }, 72 - "followed": { 73 - "name": "followed", 74 - "type": "text", 75 - "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 78 - }, 79 - "created_at": { 80 - "name": "created_at", 81 - "type": "text", 82 - "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 85 - } 86 - }, 87 - "indexes": {}, 88 - "foreignKeys": {}, 89 - "compositePrimaryKeys": {}, 90 - "uniqueConstraints": {}, 91 - "checkConstraints": {} 92 - }, 93 - "play": { 94 - "name": "play", 95 - "columns": { 96 - "uri": { 97 - "name": "uri", 98 - "type": "text", 99 - "primaryKey": true, 100 - "notNull": true, 101 - "autoincrement": false 102 - }, 103 - "author_did": { 104 - "name": "author_did", 105 - "type": "text", 106 - "primaryKey": false, 107 - "notNull": true, 108 - "autoincrement": false 109 - }, 110 - "created_at": { 111 - "name": "created_at", 112 - "type": "text", 113 - "primaryKey": false, 114 - "notNull": true, 115 - "autoincrement": false 116 - }, 117 - "indexed_at": { 118 - "name": "indexed_at", 119 - "type": "text", 120 - "primaryKey": false, 121 - "notNull": true, 122 - "autoincrement": false 123 - }, 124 - "track_name": { 125 - "name": "track_name", 126 - "type": "text", 127 - "primaryKey": false, 128 - "notNull": true, 129 - "autoincrement": false 130 - }, 131 - "track_mb_id": { 132 - "name": "track_mb_id", 133 - "type": "text", 134 - "primaryKey": false, 135 - "notNull": false, 136 - "autoincrement": false 137 - }, 138 - "recording_mb_id": { 139 - "name": "recording_mb_id", 140 - "type": "text", 141 - "primaryKey": false, 142 - "notNull": false, 143 - "autoincrement": false 144 - }, 145 - "duration": { 146 - "name": "duration", 147 - "type": "integer", 148 - "primaryKey": false, 149 - "notNull": false, 150 - "autoincrement": false 151 - }, 152 - "artist_name": { 153 - "name": "artist_name", 154 - "type": "text", 155 - "primaryKey": false, 156 - "notNull": true, 157 - "autoincrement": false 158 - }, 159 - "artist_mb_ids": { 160 - "name": "artist_mb_ids", 161 - "type": "text", 162 - "primaryKey": false, 163 - "notNull": false, 164 - "autoincrement": false 165 - }, 166 - "release_name": { 167 - "name": "release_name", 168 - "type": "text", 169 - "primaryKey": false, 170 - "notNull": false, 171 - "autoincrement": false 172 - }, 173 - "release_mb_id": { 174 - "name": "release_mb_id", 175 - "type": "text", 176 - "primaryKey": false, 177 - "notNull": false, 178 - "autoincrement": false 179 - }, 180 - "isrc": { 181 - "name": "isrc", 182 - "type": "text", 183 - "primaryKey": false, 184 - "notNull": false, 185 - "autoincrement": false 186 - }, 187 - "origin_url": { 188 - "name": "origin_url", 189 - "type": "text", 190 - "primaryKey": false, 191 - "notNull": false, 192 - "autoincrement": false 193 - }, 194 - "music_service_base_domain": { 195 - "name": "music_service_base_domain", 196 - "type": "text", 197 - "primaryKey": false, 198 - "notNull": false, 199 - "autoincrement": false 200 - }, 201 - "submission_client_agent": { 202 - "name": "submission_client_agent", 203 - "type": "text", 204 - "primaryKey": false, 205 - "notNull": false, 206 - "autoincrement": false 207 - }, 208 - "played_time": { 209 - "name": "played_time", 210 - "type": "text", 211 - "primaryKey": false, 212 - "notNull": false, 213 - "autoincrement": false 214 - } 215 - }, 216 - "indexes": {}, 217 - "foreignKeys": {}, 218 - "compositePrimaryKeys": {}, 219 - "uniqueConstraints": {}, 220 - "checkConstraints": {} 221 - }, 222 - "status": { 223 - "name": "status", 224 - "columns": { 225 - "uri": { 226 - "name": "uri", 227 - "type": "text", 228 - "primaryKey": true, 229 - "notNull": true, 230 - "autoincrement": false 231 - }, 232 - "author_did": { 233 - "name": "author_did", 234 - "type": "text", 235 - "primaryKey": false, 236 - "notNull": true, 237 - "autoincrement": false 238 - }, 239 - "status": { 240 - "name": "status", 241 - "type": "text", 242 - "primaryKey": false, 243 - "notNull": true, 244 - "autoincrement": false 245 - }, 246 - "created_at": { 247 - "name": "created_at", 248 - "type": "text", 249 - "primaryKey": false, 250 - "notNull": true, 251 - "autoincrement": false 252 - }, 253 - "indexed_at": { 254 - "name": "indexed_at", 255 - "type": "text", 256 - "primaryKey": false, 257 - "notNull": true, 258 - "autoincrement": false 259 - } 260 - }, 261 - "indexes": {}, 262 - "foreignKeys": {}, 263 - "compositePrimaryKeys": {}, 264 - "uniqueConstraints": {}, 265 - "checkConstraints": {} 266 - }, 267 - "teal_session": { 268 - "name": "teal_session", 269 - "columns": { 270 - "key": { 271 - "name": "key", 272 - "type": "text", 273 - "primaryKey": true, 274 - "notNull": true, 275 - "autoincrement": false 276 - }, 277 - "session": { 278 - "name": "session", 279 - "type": "text", 280 - "primaryKey": false, 281 - "notNull": true, 282 - "autoincrement": false 283 - }, 284 - "provider": { 285 - "name": "provider", 286 - "type": "text", 287 - "primaryKey": false, 288 - "notNull": true, 289 - "autoincrement": false 290 - } 291 - }, 292 - "indexes": {}, 293 - "foreignKeys": {}, 294 - "compositePrimaryKeys": {}, 295 - "uniqueConstraints": {}, 296 - "checkConstraints": {} 297 - }, 298 - "teal_user": { 299 - "name": "teal_user", 300 - "columns": { 301 - "did": { 302 - "name": "did", 303 - "type": "text", 304 - "primaryKey": true, 305 - "notNull": true, 306 - "autoincrement": false 307 - }, 308 - "handle": { 309 - "name": "handle", 310 - "type": "text", 311 - "primaryKey": false, 312 - "notNull": true, 313 - "autoincrement": false 314 - }, 315 - "avatar": { 316 - "name": "avatar", 317 - "type": "text", 318 - "primaryKey": false, 319 - "notNull": true, 320 - "autoincrement": false 321 - }, 322 - "bio": { 323 - "name": "bio", 324 - "type": "text", 325 - "primaryKey": false, 326 - "notNull": false, 327 - "autoincrement": false 328 - }, 329 - "created_at": { 330 - "name": "created_at", 331 - "type": "text", 332 - "primaryKey": false, 333 - "notNull": true, 334 - "autoincrement": false 335 - } 336 - }, 337 - "indexes": {}, 338 - "foreignKeys": {}, 339 - "compositePrimaryKeys": {}, 340 - "uniqueConstraints": {}, 341 - "checkConstraints": {} 342 - } 343 - }, 344 - "views": {}, 345 - "enums": {}, 346 - "_meta": { 347 - "schemas": {}, 348 - "tables": {}, 349 - "columns": {} 350 - }, 351 - "internal": { 352 - "indexes": {} 353 - } 354 - }
-356
packages/db/.drizzle/meta/0006_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "ff3fcc45-0760-4f6d-8f7c-659a1ee80ed5", 5 - "prevId": "42a94e7a-c4c2-4bd5-92d9-ba8a829c0704", 6 - "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 9 - "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 13 - "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 16 - }, 17 - "session": { 18 - "name": "session", 19 - "type": "text", 20 - "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 23 - } 24 - }, 25 - "indexes": {}, 26 - "foreignKeys": {}, 27 - "compositePrimaryKeys": {}, 28 - "uniqueConstraints": {}, 29 - "checkConstraints": {} 30 - }, 31 - "auth_state": { 32 - "name": "auth_state", 33 - "columns": { 34 - "key": { 35 - "name": "key", 36 - "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 40 - }, 41 - "state": { 42 - "name": "state", 43 - "type": "text", 44 - "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 47 - } 48 - }, 49 - "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 52 - "uniqueConstraints": {}, 53 - "checkConstraints": {} 54 - }, 55 - "follow": { 56 - "name": "follow", 57 - "columns": { 58 - "rel_id": { 59 - "name": "rel_id", 60 - "type": "text", 61 - "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 64 - }, 65 - "follower": { 66 - "name": "follower", 67 - "type": "text", 68 - "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 71 - }, 72 - "followed": { 73 - "name": "followed", 74 - "type": "text", 75 - "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 78 - }, 79 - "created_at": { 80 - "name": "created_at", 81 - "type": "text", 82 - "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 85 - } 86 - }, 87 - "indexes": {}, 88 - "foreignKeys": {}, 89 - "compositePrimaryKeys": {}, 90 - "uniqueConstraints": {}, 91 - "checkConstraints": {} 92 - }, 93 - "play": { 94 - "name": "play", 95 - "columns": { 96 - "uri": { 97 - "name": "uri", 98 - "type": "text", 99 - "primaryKey": true, 100 - "notNull": true, 101 - "autoincrement": false 102 - }, 103 - "author_did": { 104 - "name": "author_did", 105 - "type": "text", 106 - "primaryKey": false, 107 - "notNull": true, 108 - "autoincrement": false 109 - }, 110 - "created_at": { 111 - "name": "created_at", 112 - "type": "text", 113 - "primaryKey": false, 114 - "notNull": true, 115 - "autoincrement": false 116 - }, 117 - "indexed_at": { 118 - "name": "indexed_at", 119 - "type": "text", 120 - "primaryKey": false, 121 - "notNull": true, 122 - "autoincrement": false 123 - }, 124 - "track_name": { 125 - "name": "track_name", 126 - "type": "text", 127 - "primaryKey": false, 128 - "notNull": true, 129 - "autoincrement": false 130 - }, 131 - "track_mb_id": { 132 - "name": "track_mb_id", 133 - "type": "text", 134 - "primaryKey": false, 135 - "notNull": false, 136 - "autoincrement": false 137 - }, 138 - "recording_mb_id": { 139 - "name": "recording_mb_id", 140 - "type": "text", 141 - "primaryKey": false, 142 - "notNull": false, 143 - "autoincrement": false 144 - }, 145 - "duration": { 146 - "name": "duration", 147 - "type": "integer", 148 - "primaryKey": false, 149 - "notNull": false, 150 - "autoincrement": false 151 - }, 152 - "artist_names": { 153 - "name": "artist_names", 154 - "type": "text", 155 - "primaryKey": false, 156 - "notNull": false, 157 - "autoincrement": false 158 - }, 159 - "artist_mb_ids": { 160 - "name": "artist_mb_ids", 161 - "type": "text", 162 - "primaryKey": false, 163 - "notNull": false, 164 - "autoincrement": false 165 - }, 166 - "release_name": { 167 - "name": "release_name", 168 - "type": "text", 169 - "primaryKey": false, 170 - "notNull": false, 171 - "autoincrement": false 172 - }, 173 - "release_mb_id": { 174 - "name": "release_mb_id", 175 - "type": "text", 176 - "primaryKey": false, 177 - "notNull": false, 178 - "autoincrement": false 179 - }, 180 - "isrc": { 181 - "name": "isrc", 182 - "type": "text", 183 - "primaryKey": false, 184 - "notNull": false, 185 - "autoincrement": false 186 - }, 187 - "origin_url": { 188 - "name": "origin_url", 189 - "type": "text", 190 - "primaryKey": false, 191 - "notNull": false, 192 - "autoincrement": false 193 - }, 194 - "music_service_base_domain": { 195 - "name": "music_service_base_domain", 196 - "type": "text", 197 - "primaryKey": false, 198 - "notNull": false, 199 - "autoincrement": false 200 - }, 201 - "submission_client_agent": { 202 - "name": "submission_client_agent", 203 - "type": "text", 204 - "primaryKey": false, 205 - "notNull": false, 206 - "autoincrement": false 207 - }, 208 - "played_time": { 209 - "name": "played_time", 210 - "type": "text", 211 - "primaryKey": false, 212 - "notNull": false, 213 - "autoincrement": false 214 - } 215 - }, 216 - "indexes": {}, 217 - "foreignKeys": {}, 218 - "compositePrimaryKeys": {}, 219 - "uniqueConstraints": {}, 220 - "checkConstraints": {} 221 - }, 222 - "status": { 223 - "name": "status", 224 - "columns": { 225 - "uri": { 226 - "name": "uri", 227 - "type": "text", 228 - "primaryKey": true, 229 - "notNull": true, 230 - "autoincrement": false 231 - }, 232 - "author_did": { 233 - "name": "author_did", 234 - "type": "text", 235 - "primaryKey": false, 236 - "notNull": true, 237 - "autoincrement": false 238 - }, 239 - "status": { 240 - "name": "status", 241 - "type": "text", 242 - "primaryKey": false, 243 - "notNull": true, 244 - "autoincrement": false 245 - }, 246 - "created_at": { 247 - "name": "created_at", 248 - "type": "text", 249 - "primaryKey": false, 250 - "notNull": true, 251 - "autoincrement": false 252 - }, 253 - "indexed_at": { 254 - "name": "indexed_at", 255 - "type": "text", 256 - "primaryKey": false, 257 - "notNull": true, 258 - "autoincrement": false 259 - } 260 - }, 261 - "indexes": {}, 262 - "foreignKeys": {}, 263 - "compositePrimaryKeys": {}, 264 - "uniqueConstraints": {}, 265 - "checkConstraints": {} 266 - }, 267 - "teal_session": { 268 - "name": "teal_session", 269 - "columns": { 270 - "key": { 271 - "name": "key", 272 - "type": "text", 273 - "primaryKey": true, 274 - "notNull": true, 275 - "autoincrement": false 276 - }, 277 - "session": { 278 - "name": "session", 279 - "type": "text", 280 - "primaryKey": false, 281 - "notNull": true, 282 - "autoincrement": false 283 - }, 284 - "provider": { 285 - "name": "provider", 286 - "type": "text", 287 - "primaryKey": false, 288 - "notNull": true, 289 - "autoincrement": false 290 - } 291 - }, 292 - "indexes": {}, 293 - "foreignKeys": {}, 294 - "compositePrimaryKeys": {}, 295 - "uniqueConstraints": {}, 296 - "checkConstraints": {} 297 - }, 298 - "teal_user": { 299 - "name": "teal_user", 300 - "columns": { 301 - "did": { 302 - "name": "did", 303 - "type": "text", 304 - "primaryKey": true, 305 - "notNull": true, 306 - "autoincrement": false 307 - }, 308 - "handle": { 309 - "name": "handle", 310 - "type": "text", 311 - "primaryKey": false, 312 - "notNull": true, 313 - "autoincrement": false 314 - }, 315 - "avatar": { 316 - "name": "avatar", 317 - "type": "text", 318 - "primaryKey": false, 319 - "notNull": true, 320 - "autoincrement": false 321 - }, 322 - "bio": { 323 - "name": "bio", 324 - "type": "text", 325 - "primaryKey": false, 326 - "notNull": false, 327 - "autoincrement": false 328 - }, 329 - "created_at": { 330 - "name": "created_at", 331 - "type": "text", 332 - "primaryKey": false, 333 - "notNull": true, 334 - "autoincrement": false 335 - } 336 - }, 337 - "indexes": {}, 338 - "foreignKeys": {}, 339 - "compositePrimaryKeys": {}, 340 - "uniqueConstraints": {}, 341 - "checkConstraints": {} 342 - } 343 - }, 344 - "views": {}, 345 - "enums": {}, 346 - "_meta": { 347 - "schemas": {}, 348 - "tables": {}, 349 - "columns": { 350 - "\"play\".\"artist_name\"": "\"play\".\"artist_names\"" 351 - } 352 - }, 353 - "internal": { 354 - "indexes": {} 355 - } 356 - }
-356
packages/db/.drizzle/meta/0007_snapshot.json
··· 1 - { 2 - "version": "6", 3 - "dialect": "sqlite", 4 - "id": "ee14b661-c5fd-4ec3-a87a-0b19f2a4c17f", 5 - "prevId": "ff3fcc45-0760-4f6d-8f7c-659a1ee80ed5", 6 - "tables": { 7 - "atp_session": { 8 - "name": "atp_session", 9 - "columns": { 10 - "key": { 11 - "name": "key", 12 - "type": "text", 13 - "primaryKey": true, 14 - "notNull": true, 15 - "autoincrement": false 16 - }, 17 - "session": { 18 - "name": "session", 19 - "type": "text", 20 - "primaryKey": false, 21 - "notNull": true, 22 - "autoincrement": false 23 - } 24 - }, 25 - "indexes": {}, 26 - "foreignKeys": {}, 27 - "compositePrimaryKeys": {}, 28 - "uniqueConstraints": {}, 29 - "checkConstraints": {} 30 - }, 31 - "auth_state": { 32 - "name": "auth_state", 33 - "columns": { 34 - "key": { 35 - "name": "key", 36 - "type": "text", 37 - "primaryKey": true, 38 - "notNull": true, 39 - "autoincrement": false 40 - }, 41 - "state": { 42 - "name": "state", 43 - "type": "text", 44 - "primaryKey": false, 45 - "notNull": true, 46 - "autoincrement": false 47 - } 48 - }, 49 - "indexes": {}, 50 - "foreignKeys": {}, 51 - "compositePrimaryKeys": {}, 52 - "uniqueConstraints": {}, 53 - "checkConstraints": {} 54 - }, 55 - "follow": { 56 - "name": "follow", 57 - "columns": { 58 - "rel_id": { 59 - "name": "rel_id", 60 - "type": "text", 61 - "primaryKey": true, 62 - "notNull": true, 63 - "autoincrement": false 64 - }, 65 - "follower": { 66 - "name": "follower", 67 - "type": "text", 68 - "primaryKey": false, 69 - "notNull": true, 70 - "autoincrement": false 71 - }, 72 - "followed": { 73 - "name": "followed", 74 - "type": "text", 75 - "primaryKey": false, 76 - "notNull": true, 77 - "autoincrement": false 78 - }, 79 - "created_at": { 80 - "name": "created_at", 81 - "type": "text", 82 - "primaryKey": false, 83 - "notNull": true, 84 - "autoincrement": false 85 - } 86 - }, 87 - "indexes": {}, 88 - "foreignKeys": {}, 89 - "compositePrimaryKeys": {}, 90 - "uniqueConstraints": {}, 91 - "checkConstraints": {} 92 - }, 93 - "play": { 94 - "name": "play", 95 - "columns": { 96 - "rkey": { 97 - "name": "rkey", 98 - "type": "text", 99 - "primaryKey": true, 100 - "notNull": true, 101 - "autoincrement": false 102 - }, 103 - "author_did": { 104 - "name": "author_did", 105 - "type": "text", 106 - "primaryKey": false, 107 - "notNull": true, 108 - "autoincrement": false 109 - }, 110 - "created_at": { 111 - "name": "created_at", 112 - "type": "text", 113 - "primaryKey": false, 114 - "notNull": true, 115 - "autoincrement": false 116 - }, 117 - "indexed_at": { 118 - "name": "indexed_at", 119 - "type": "text", 120 - "primaryKey": false, 121 - "notNull": true, 122 - "autoincrement": false 123 - }, 124 - "track_name": { 125 - "name": "track_name", 126 - "type": "text", 127 - "primaryKey": false, 128 - "notNull": true, 129 - "autoincrement": false 130 - }, 131 - "track_mb_id": { 132 - "name": "track_mb_id", 133 - "type": "text", 134 - "primaryKey": false, 135 - "notNull": false, 136 - "autoincrement": false 137 - }, 138 - "recording_mb_id": { 139 - "name": "recording_mb_id", 140 - "type": "text", 141 - "primaryKey": false, 142 - "notNull": false, 143 - "autoincrement": false 144 - }, 145 - "duration": { 146 - "name": "duration", 147 - "type": "integer", 148 - "primaryKey": false, 149 - "notNull": false, 150 - "autoincrement": false 151 - }, 152 - "artist_names": { 153 - "name": "artist_names", 154 - "type": "text", 155 - "primaryKey": false, 156 - "notNull": false, 157 - "autoincrement": false 158 - }, 159 - "artist_mb_ids": { 160 - "name": "artist_mb_ids", 161 - "type": "text", 162 - "primaryKey": false, 163 - "notNull": false, 164 - "autoincrement": false 165 - }, 166 - "release_name": { 167 - "name": "release_name", 168 - "type": "text", 169 - "primaryKey": false, 170 - "notNull": false, 171 - "autoincrement": false 172 - }, 173 - "release_mb_id": { 174 - "name": "release_mb_id", 175 - "type": "text", 176 - "primaryKey": false, 177 - "notNull": false, 178 - "autoincrement": false 179 - }, 180 - "isrc": { 181 - "name": "isrc", 182 - "type": "text", 183 - "primaryKey": false, 184 - "notNull": false, 185 - "autoincrement": false 186 - }, 187 - "origin_url": { 188 - "name": "origin_url", 189 - "type": "text", 190 - "primaryKey": false, 191 - "notNull": false, 192 - "autoincrement": false 193 - }, 194 - "music_service_base_domain": { 195 - "name": "music_service_base_domain", 196 - "type": "text", 197 - "primaryKey": false, 198 - "notNull": false, 199 - "autoincrement": false 200 - }, 201 - "submission_client_agent": { 202 - "name": "submission_client_agent", 203 - "type": "text", 204 - "primaryKey": false, 205 - "notNull": false, 206 - "autoincrement": false 207 - }, 208 - "played_time": { 209 - "name": "played_time", 210 - "type": "text", 211 - "primaryKey": false, 212 - "notNull": false, 213 - "autoincrement": false 214 - } 215 - }, 216 - "indexes": {}, 217 - "foreignKeys": {}, 218 - "compositePrimaryKeys": {}, 219 - "uniqueConstraints": {}, 220 - "checkConstraints": {} 221 - }, 222 - "status": { 223 - "name": "status", 224 - "columns": { 225 - "uri": { 226 - "name": "uri", 227 - "type": "text", 228 - "primaryKey": true, 229 - "notNull": true, 230 - "autoincrement": false 231 - }, 232 - "author_did": { 233 - "name": "author_did", 234 - "type": "text", 235 - "primaryKey": false, 236 - "notNull": true, 237 - "autoincrement": false 238 - }, 239 - "status": { 240 - "name": "status", 241 - "type": "text", 242 - "primaryKey": false, 243 - "notNull": true, 244 - "autoincrement": false 245 - }, 246 - "created_at": { 247 - "name": "created_at", 248 - "type": "text", 249 - "primaryKey": false, 250 - "notNull": true, 251 - "autoincrement": false 252 - }, 253 - "indexed_at": { 254 - "name": "indexed_at", 255 - "type": "text", 256 - "primaryKey": false, 257 - "notNull": true, 258 - "autoincrement": false 259 - } 260 - }, 261 - "indexes": {}, 262 - "foreignKeys": {}, 263 - "compositePrimaryKeys": {}, 264 - "uniqueConstraints": {}, 265 - "checkConstraints": {} 266 - }, 267 - "teal_session": { 268 - "name": "teal_session", 269 - "columns": { 270 - "key": { 271 - "name": "key", 272 - "type": "text", 273 - "primaryKey": true, 274 - "notNull": true, 275 - "autoincrement": false 276 - }, 277 - "session": { 278 - "name": "session", 279 - "type": "text", 280 - "primaryKey": false, 281 - "notNull": true, 282 - "autoincrement": false 283 - }, 284 - "provider": { 285 - "name": "provider", 286 - "type": "text", 287 - "primaryKey": false, 288 - "notNull": true, 289 - "autoincrement": false 290 - } 291 - }, 292 - "indexes": {}, 293 - "foreignKeys": {}, 294 - "compositePrimaryKeys": {}, 295 - "uniqueConstraints": {}, 296 - "checkConstraints": {} 297 - }, 298 - "teal_user": { 299 - "name": "teal_user", 300 - "columns": { 301 - "did": { 302 - "name": "did", 303 - "type": "text", 304 - "primaryKey": true, 305 - "notNull": true, 306 - "autoincrement": false 307 - }, 308 - "handle": { 309 - "name": "handle", 310 - "type": "text", 311 - "primaryKey": false, 312 - "notNull": true, 313 - "autoincrement": false 314 - }, 315 - "avatar": { 316 - "name": "avatar", 317 - "type": "text", 318 - "primaryKey": false, 319 - "notNull": true, 320 - "autoincrement": false 321 - }, 322 - "bio": { 323 - "name": "bio", 324 - "type": "text", 325 - "primaryKey": false, 326 - "notNull": false, 327 - "autoincrement": false 328 - }, 329 - "created_at": { 330 - "name": "created_at", 331 - "type": "text", 332 - "primaryKey": false, 333 - "notNull": true, 334 - "autoincrement": false 335 - } 336 - }, 337 - "indexes": {}, 338 - "foreignKeys": {}, 339 - "compositePrimaryKeys": {}, 340 - "uniqueConstraints": {}, 341 - "checkConstraints": {} 342 - } 343 - }, 344 - "views": {}, 345 - "enums": {}, 346 - "_meta": { 347 - "schemas": {}, 348 - "tables": {}, 349 - "columns": { 350 - "\"play\".\"uri\"": "\"play\".\"rkey\"" 351 - } 352 - }, 353 - "internal": { 354 - "indexes": {} 355 - } 356 - }
+16 -37
packages/db/.drizzle/meta/_journal.json
··· 1 1 { 2 2 "version": "7", 3 - "dialect": "sqlite", 3 + "dialect": "postgresql", 4 4 "entries": [ 5 5 { 6 6 "idx": 0, 7 - "version": "6", 8 - "when": 1729467718506, 9 - "tag": "0000_same_maelstrom", 7 + "version": "7", 8 + "when": 1741812188376, 9 + "tag": "0000_perfect_war_machine", 10 10 "breakpoints": true 11 11 }, 12 12 { 13 13 "idx": 1, 14 - "version": "6", 15 - "when": 1730659321301, 16 - "tag": "0001_fresh_tana_nile", 14 + "version": "7", 15 + "when": 1741812893244, 16 + "tag": "0001_swift_maddog", 17 17 "breakpoints": true 18 18 }, 19 19 { 20 20 "idx": 2, 21 - "version": "6", 22 - "when": 1730659385717, 23 - "tag": "0002_moaning_roulette", 21 + "version": "7", 22 + "when": 1741816927440, 23 + "tag": "0002_stale_the_spike", 24 24 "breakpoints": true 25 25 }, 26 26 { 27 27 "idx": 3, 28 - "version": "6", 29 - "when": 1731093709171, 30 - "tag": "0003_sharp_medusa", 28 + "version": "7", 29 + "when": 1741927519447, 30 + "tag": "0003_worried_unicorn", 31 31 "breakpoints": true 32 32 }, 33 33 { 34 34 "idx": 4, 35 - "version": "6", 36 - "when": 1735101894454, 37 - "tag": "0004_exotic_ironclad", 38 - "breakpoints": true 39 - }, 40 - { 41 - "idx": 5, 42 - "version": "6", 43 - "when": 1735497040757, 44 - "tag": "0005_conscious_johnny_blaze", 45 - "breakpoints": true 46 - }, 47 - { 48 - "idx": 6, 49 - "version": "6", 50 - "when": 1736372650928, 51 - "tag": "0006_supreme_hairball", 52 - "breakpoints": true 53 - }, 54 - { 55 - "idx": 7, 56 - "version": "6", 57 - "when": 1736663520095, 58 - "tag": "0007_demonic_toad", 35 + "version": "7", 36 + "when": 1741929813766, 37 + "tag": "0004_furry_gravity", 59 38 "breakpoints": true 60 39 } 61 40 ]
+14 -11
packages/db/connect.ts
··· 1 - import { drizzle } from "drizzle-orm/libsql"; 2 - import { createClient } from "@libsql/client"; 1 + import { drizzle } from "drizzle-orm/postgres-js"; 2 + import postgres from "postgres"; 3 3 import * as schema from "./schema"; 4 4 import process from "node:process"; 5 5 import path from "node:path"; 6 6 7 + /// Trim a password from a db connection url 8 + function withoutPassword(url: string) { 9 + const urlObj = new URL(url); 10 + urlObj.password = "*****"; 11 + return urlObj.toString(); 12 + } 13 + 7 14 console.log( 8 - "Loading SQLite file at", 9 - path.join(process.cwd(), "./../../db.sqlite"), 15 + "Connecting to database at " + 16 + withoutPassword(process.env.DATABASE_URL ?? ""), 10 17 ); 11 18 12 - const client = createClient({ 13 - url: 14 - process.env.DATABASE_URL ?? 15 - "file:" + path.join(process.cwd(), "./../../db.sqlite"), 16 - }); 19 + const client = postgres(process.env.DATABASE_URL ?? ""); 17 20 18 - export const db = drizzle(client, { 21 + export const db = drizzle({ 22 + client, 19 23 schema: schema, 20 - casing: "snake_case", 21 24 }); 22 25 23 26 // If you need to export the type:
+2 -2
packages/db/drizzle.config.ts
··· 1 1 import { defineConfig } from "drizzle-kit"; 2 2 3 3 export default defineConfig({ 4 - dialect: "sqlite", 4 + dialect: "postgresql", 5 5 schema: "./schema.ts", 6 6 out: "./.drizzle", 7 7 casing: "snake_case", 8 8 dbCredentials: { 9 - url: process.env.DATABASE_URL ?? "./../../db.sqlite", 9 + url: process.env.DATABASE_URL || "", 10 10 }, 11 11 });
+2 -1
packages/db/package.json
··· 14 14 "@libsql/client": "^0.14.0", 15 15 "@teal/tsconfig": "workspace:*", 16 16 "drizzle-kit": "^0.30.1", 17 - "drizzle-orm": "^0.38.3" 17 + "drizzle-orm": "^0.38.3", 18 + "postgres": "^3.4.5" 18 19 }, 19 20 "devDependencies": { 20 21 "@types/node": "^20.17.6"
+156 -85
packages/db/schema.ts
··· 1 + import { sql } from "drizzle-orm"; 1 2 import { 2 - numeric, 3 - sqliteTable, 3 + pgTable, 4 4 text, 5 - customType, 5 + pgEnum, 6 + timestamp, 7 + uuid, 6 8 integer, 7 - } from "drizzle-orm/sqlite-core"; 9 + jsonb, 10 + primaryKey, 11 + foreignKey, 12 + pgMaterializedView, 13 + } from "drizzle-orm/pg-core"; 14 + import { createDeflate } from "node:zlib"; 15 + 16 + export const artists = pgTable("artists", { 17 + mbid: uuid("mbid").primaryKey(), 18 + name: text("name").notNull(), 19 + playCount: integer("play_count").default(0), 20 + }); 21 + 22 + export const plays = pgTable("plays", { 23 + cid: text("cid").notNull(), 24 + did: text("did").notNull(), 25 + duration: integer("duration"), 26 + isrc: text("isrc"), 27 + musicServiceBaseDomain: text("music_service_base_domain"), 28 + originUrl: text("origin_url"), 29 + playedTime: timestamp("played_time", { withTimezone: true }), 30 + processedTime: timestamp("processed_time", { 31 + withTimezone: true, 32 + }).defaultNow(), 33 + rkey: text("rkey").notNull(), 34 + recordingMbid: uuid("recording_mbid").references(() => recordings.mbid), 35 + releaseMbid: uuid("release_mbid").references(() => releases.mbid), 36 + releaseName: text("release_name"), 37 + submissionClientAgent: text("submission_client_agent"), 38 + trackName: text("track_name").notNull(), 39 + uri: text("uri").primaryKey(), 40 + }); 8 41 9 - // string array custom type 10 - const json = <TData>() => 11 - customType<{ data: TData; driverData: string }>({ 12 - dataType() { 13 - return "text"; 14 - }, 15 - toDriver(value: TData): string { 16 - return JSON.stringify(value); 17 - }, 18 - // handle single value (no json array) as well 19 - fromDriver(value: string): TData { 20 - if (value[0] === "[") { 21 - return JSON.parse(value); 22 - } 23 - return [value] as TData; 24 - }, 25 - })(); 42 + export const playToArtists = pgTable( 43 + "play_to_artists", 44 + { 45 + artistMbid: uuid("artist_mbid") 46 + .references(() => artists.mbid) 47 + .notNull(), 48 + artistName: text("artist_name"), 49 + playUri: text("play_uri") 50 + .references(() => plays.uri) 51 + .notNull(), 52 + }, 53 + (table) => [primaryKey({ columns: [table.playUri, table.artistMbid] })], 54 + ); 26 55 27 - // Tables 56 + export const recordings = pgTable("recordings", { 57 + mbid: uuid("mbid").primaryKey(), 58 + name: text("name").notNull(), 59 + playCount: integer("play_count").default(0), 60 + }); 28 61 29 - export const status = sqliteTable("status", { 30 - uri: text().primaryKey(), 31 - authorDid: text().notNull(), 32 - status: text().notNull(), 33 - createdAt: text().notNull(), 34 - indexedAt: text().notNull(), 62 + export const releases = pgTable("releases", { 63 + mbid: uuid("mbid").primaryKey(), 64 + name: text("name").notNull(), 65 + playCount: integer("play_count").default(0), 35 66 }); 36 67 37 - // ATP Auth Tables (oAuth) 38 - export const atProtoSession = sqliteTable("atp_session", { 39 - key: text().primaryKey(), 40 - session: text().notNull(), 68 + export const mvArtistPlayCounts = pgMaterializedView( 69 + "mv_artist_play_counts", 70 + ).as((qb) => { 71 + return qb 72 + .select({ 73 + artistMbid: artists.mbid, 74 + artistName: artists.name, 75 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 76 + }) 77 + .from(artists) 78 + .leftJoin(playToArtists, sql`${artists.mbid} = ${playToArtists.artistMbid}`) 79 + .leftJoin(plays, sql`${plays.uri} = ${playToArtists.playUri}`) 80 + .groupBy(artists.mbid, artists.name); 41 81 }); 42 82 43 - export const authState = sqliteTable("auth_state", { 44 - key: text().primaryKey(), 45 - state: text().notNull(), 83 + export const mvGlobalPlayCount = pgMaterializedView("mv_global_play_count").as( 84 + (qb) => { 85 + return qb 86 + .select({ 87 + totalPlays: sql<number>`count(${plays.uri})`.as("total_plays"), 88 + uniqueListeners: sql<number>`count(distinct ${plays.did})`.as( 89 + "unique_listeners", 90 + ), 91 + }) 92 + .from(plays); 93 + }, 94 + ); 95 + 96 + export const mvRecordingPlayCounts = pgMaterializedView( 97 + "mv_recording_play_counts", 98 + ).as((qb) => { 99 + return qb 100 + .select({ 101 + recordingMbid: recordings.mbid, 102 + recordingName: recordings.name, 103 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 104 + }) 105 + .from(recordings) 106 + .leftJoin(plays, sql`${plays.recordingMbid} = ${recordings.mbid}`) 107 + .groupBy(recordings.mbid, recordings.name); 46 108 }); 47 109 48 - export const tealSession = sqliteTable("teal_session", { 49 - key: text().primaryKey(), 50 - session: text().notNull(), 51 - provider: text().notNull(), 110 + export const mvReleasePlayCounts = pgMaterializedView( 111 + "mv_release_play_counts", 112 + ).as((qb) => { 113 + return qb 114 + .select({ 115 + releaseMbid: releases.mbid, 116 + releaseName: releases.name, 117 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 118 + }) 119 + .from(releases) 120 + .leftJoin(plays, sql`${plays.releaseMbid} = ${releases.mbid}`) 121 + .groupBy(releases.mbid, releases.name); 52 122 }); 53 123 54 - // Regular Auth Tables 55 - export const tealUser = sqliteTable("teal_user", { 56 - did: text().primaryKey(), 57 - handle: text().notNull(), 58 - avatar: text().notNull(), 59 - bio: text(), 60 - createdAt: text().notNull(), 124 + export const mvTopArtists30Days = pgMaterializedView( 125 + "mv_top_artists_30days", 126 + ).as((qb) => { 127 + return qb 128 + .select({ 129 + artistMbid: artists.mbid, 130 + artistName: artists.name, 131 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 132 + }) 133 + .from(artists) 134 + .innerJoin( 135 + playToArtists, 136 + sql`${artists.mbid} = ${playToArtists.artistMbid}`, 137 + ) 138 + .innerJoin(plays, sql`${plays.uri} = ${playToArtists.playUri}`) 139 + .where(sql`${plays.playedTime} >= NOW() - INTERVAL '30 days'`) 140 + .groupBy(artists.mbid, artists.name) 141 + .orderBy(sql`count(${plays.uri}) DESC`); 61 142 }); 62 143 63 - // follow relationship 64 - export const follow = sqliteTable("follow", { 65 - relId: text().primaryKey(), 66 - follower: text().notNull(), 67 - followed: text().notNull(), 68 - createdAt: text().notNull(), 144 + export const mvTopReleases30Days = pgMaterializedView( 145 + "mv_top_releases_30days", 146 + ).as((qb) => { 147 + return qb 148 + .select({ 149 + releaseMbid: releases.mbid, 150 + releaseName: releases.name, 151 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 152 + }) 153 + .from(releases) 154 + .innerJoin(plays, sql`${plays.releaseMbid} = ${releases.mbid}`) 155 + .where(sql`${plays.playedTime} >= NOW() - INTERVAL '30 days'`) 156 + .groupBy(releases.mbid, releases.name) 157 + .orderBy(sql`count(${plays.uri}) DESC`); 69 158 }); 70 159 71 - // play 72 - export const play = sqliteTable("play", { 73 - rkey: text().primaryKey(), 74 - authorDid: text().notNull(), 75 - createdAt: text().notNull(), 76 - indexedAt: text().notNull(), 160 + export const profiles = pgTable("profiles", { 161 + did: text("did").primaryKey(), 162 + handle: text("handle"), 163 + displayName: text("display_name"), 164 + description: text("description"), 165 + descriptionFacets: jsonb("description_facets"), 166 + // the IPLD of the image. bafy... 167 + avatar: text("avatar"), 168 + banner: text("banner"), 169 + createdAt: timestamp("created_at"), 170 + }); 77 171 78 - /** The name of the track */ 79 - trackName: text().notNull(), 80 - /** The Musicbrainz ID of the track */ 81 - trackMbId: text(), 82 - /** The Musicbrainz recording ID of the track */ 83 - recordingMbId: text(), 84 - /** The length of the track in seconds */ 85 - duration: integer(), 86 - /** The names of the artists in order of original appearance */ 87 - artistNames: json<string[]>(), 88 - /** Array of Musicbrainz artist IDs */ 89 - // type of string[] 90 - artistMbIds: json<string[]>(), 91 - /** The name of the release/album */ 92 - releaseName: text(), 93 - /** The Musicbrainz release ID */ 94 - releaseMbId: text(), 95 - /** The ISRC code associated with the recording */ 96 - isrc: text(), 97 - /** The URL associated with this track */ 98 - originUrl: text(), 99 - /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. */ 100 - musicServiceBaseDomain: text(), 101 - /** A user-agent style string specifying the user agent. e.g. fm.teal.frontend/0.0.1b */ 102 - submissionClientAgent: text(), 103 - /** The unix timestamp of when the track was played */ 104 - playedTime: text(), 172 + export const userFeaturedItems = pgTable("featured_items", { 173 + did: text("did").primaryKey(), 174 + mbid: text("mbid").notNull(), 175 + type: text("type").notNull(), 105 176 });
-19
packages/jetstring/package.json
··· 1 - { 2 - "name": "@teal/jetstring", 3 - "scripts": { 4 - "dev": "tsx watch src/index.ts | pino-pretty" 5 - }, 6 - "dependencies": { 7 - "@libsql/client": "^0.14.0", 8 - "@skyware/jetstream": "^0.2.0", 9 - "@teal/db": "workspace:*", 10 - "@teal/lexicons": "workspace:*", 11 - "@teal/tsconfig": "workspace:*", 12 - "pino-pretty": "^13.0.0" 13 - }, 14 - "devDependencies": { 15 - "tsup": "^8.3.5", 16 - "tsx": "^4.19.2", 17 - "typescript": "^5.6.3" 18 - } 19 - }
-191
packages/jetstring/src/index.ts
··· 1 - import type { Database } from "@teal/db/connect"; 2 - import { db } from "@teal/db/connect"; 3 - import { status, play } from "@teal/db/schema"; 4 - import { CommitCreateEvent, Jetstream } from "@skyware/jetstream"; 5 - 6 - import { 7 - Record as XyzStatusphereStatus, 8 - isRecord as isStatusphereStatus, 9 - } from "@teal/lexicons/src/types/xyz/statusphere/status"; 10 - 11 - import { 12 - Record as FmTealAlphaPlay, 13 - isRecord as isTealAlphaPlay, 14 - } from "@teal/lexicons/src/types/fm/teal/alpha/feed/play"; 15 - 16 - class Handler { 17 - private static instance: Handler; 18 - private constructor() {} 19 - public static getInstance(): Handler { 20 - if (!Handler.instance) { 21 - Handler.instance = new Handler(); 22 - } 23 - return Handler.instance; 24 - } 25 - 26 - handle(msg_type: string, record: CommitCreateEvent<string & {}>) { 27 - // Handle message logic here 28 - const msg = record.commit.record; 29 - console.log("Handling" + msg_type + "message:", msg); 30 - if (isStatusphereStatus(msg) && msg.$type === "xyz.statusphere.status") { 31 - if (record.commit.operation === "create") { 32 - // serialize message as xyz.statusphere.status 33 - db.insert(status) 34 - .values({ 35 - createdAt: new Date().getTime().toString(), 36 - indexedAt: new Date(record.time_us).getTime().toString(), 37 - status: msg.status, 38 - // the AT path 39 - uri: record.commit.rkey, 40 - authorDid: record.did, 41 - }) 42 - .execute(); 43 - } else { 44 - // TODO: sentry 45 - console.log( 46 - "unsupported operation for xyz.statusphere.status", 47 - record.commit.operation, 48 - ); 49 - } 50 - } else if (isTealAlphaPlay(msg) && msg.$type === "fm.teal.alpha.play") { 51 - if (record.commit.operation === "create") { 52 - // serialize message as fm.teal.alpha.play 53 - console.log(record.did); 54 - db.insert(play) 55 - .values({ 56 - createdAt: new Date().getTime().toString(), 57 - indexedAt: new Date(record.time_us).getTime().toString(), 58 - // the AT path 59 - rkey: record.commit.rkey, 60 - authorDid: record.did, 61 - artistNames: msg.artistNames, 62 - trackName: msg.trackName, 63 - artistMbIds: msg.artistMbIds || [], 64 - trackMbId: msg.trackMbId || "", 65 - duration: msg.duration || null, 66 - isrc: msg.isrc || null, 67 - musicServiceBaseDomain: msg.musicServiceBaseDomain || "local", 68 - originUrl: msg.originUrl || null, 69 - playedTime: msg.playedTime ? msg.playedTime.toString() : undefined, 70 - recordingMbId: msg.recordingMbId || null, 71 - releaseMbId: msg.releaseMbId || null, 72 - releaseName: msg.releaseName || null, 73 - submissionClientAgent: 74 - msg.submissionClientAgent || "manual/unknown", 75 - }) 76 - .execute(); 77 - } else { 78 - // TODO: sentry 79 - console.log( 80 - "unsupported operation for fm.teal.alpha.play", 81 - record.commit.operation, 82 - ); 83 - } 84 - } else { 85 - console.log("Unknown message type:", msg_type); 86 - console.log("Message:", record); 87 - } 88 - } 89 - } 90 - 91 - class Streamer { 92 - private static instance: Streamer; 93 - private jetstream: Jetstream; 94 - private handler: Handler; 95 - 96 - private wantedCollections: string[]; 97 - 98 - private constructor(wantedCollections: string[]) { 99 - this.handler = Handler.getInstance(); 100 - console.log("Creating new jetstream with collections", wantedCollections); 101 - this.jetstream = new Jetstream({ 102 - wantedCollections, 103 - }); 104 - this.wantedCollections = wantedCollections; 105 - } 106 - 107 - public static getInstance(wantedCollections?: string[]): Streamer { 108 - if (!Streamer.instance && wantedCollections) { 109 - Streamer.instance = new Streamer(wantedCollections); 110 - } else if (!Streamer.instance) { 111 - throw Error( 112 - "Wanted collections are required if instance does not exist!", 113 - ); 114 - } 115 - return Streamer.instance; 116 - } 117 - 118 - async setOnCreates() { 119 - for (const collection of this.wantedCollections) { 120 - await this.setOnCreate(collection); 121 - } 122 - } 123 - 124 - async setOnCreate(collection: string) { 125 - try { 126 - this.jetstream.onCreate(collection, (event) => { 127 - console.log("Received message:", event.commit.record); 128 - this.handleCreate(collection, event); 129 - }); 130 - } catch (error) { 131 - console.error("Error setting onCreate:", error); 132 - } 133 - console.log("Started onCreate stream for", collection); 134 - } 135 - 136 - async handleCreate( 137 - collection: string, 138 - event: CommitCreateEvent<string & {}>, 139 - ) { 140 - this.handler.handle(collection, event); 141 - } 142 - 143 - // Add method to start the streamer 144 - async start() { 145 - try { 146 - await this.setOnCreates(); 147 - this.jetstream.start(); 148 - console.log("Streamer started successfully"); 149 - } catch (error) { 150 - console.error("Error starting streamer:", error); 151 - } 152 - } 153 - } 154 - 155 - // Main function to run the application 156 - async function main() { 157 - try { 158 - const streamer = Streamer.getInstance([ 159 - "xyz.statusphere.status", 160 - "fm.teal.alpha.play", 161 - ]); 162 - await streamer.start(); 163 - 164 - // Keep the process running 165 - process.on("SIGINT", () => { 166 - console.log("Received SIGINT. Graceful shutdown..."); 167 - process.exit(0); 168 - }); 169 - 170 - process.on("SIGTERM", () => { 171 - console.log("Received SIGTERM. Graceful shutdown..."); 172 - process.exit(0); 173 - }); 174 - 175 - // Prevent the Node.js process from exiting 176 - setInterval(() => { 177 - // This empty interval keeps the process running 178 - }, 1000); 179 - 180 - console.log("Application is running. Press Ctrl+C to exit."); 181 - } catch (error) { 182 - console.error("Error in main:", error); 183 - process.exit(1); 184 - } 185 - } 186 - 187 - // Run the application 188 - main().catch((error) => { 189 - console.error("Unhandled error:", error); 190 - process.exit(1); 191 - });
-3
packages/jetstring/tsconfig.json
··· 1 - { 2 - "extends": "@teal/tsconfig/base.json" 3 - }
+13 -3
pnpm-lock.yaml
··· 264 264 version: 16.4.7 265 265 drizzle-orm: 266 266 specifier: ^0.38.3 267 - version: 0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1) 267 + version: 0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(postgres@3.4.5)(react@18.3.1) 268 268 envalid: 269 269 specifier: ^8.0.0 270 270 version: 8.0.0 ··· 325 325 version: 0.30.2 326 326 drizzle-orm: 327 327 specifier: ^0.38.3 328 - version: 0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1) 328 + version: 0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(postgres@3.4.5)(react@18.3.1) 329 + postgres: 330 + specifier: ^3.4.5 331 + version: 3.4.5 329 332 devDependencies: 330 333 '@types/node': 331 334 specifier: ^20.17.6 ··· 6232 6235 resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} 6233 6236 engines: {node: ^10 || ^12 || >=14} 6234 6237 6238 + postgres@3.4.5: 6239 + resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} 6240 + engines: {node: '>=12'} 6241 + 6235 6242 prelude-ls@1.2.1: 6236 6243 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 6237 6244 engines: {node: '>= 0.8.0'} ··· 12016 12023 transitivePeerDependencies: 12017 12024 - supports-color 12018 12025 12019 - drizzle-orm@0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1): 12026 + drizzle-orm@0.38.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(postgres@3.4.5)(react@18.3.1): 12020 12027 optionalDependencies: 12021 12028 '@libsql/client': 0.14.0 12022 12029 '@types/react': 18.3.12 12023 12030 expo-sqlite: 15.0.3(expo@52.0.27(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.1(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.6(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) 12031 + postgres: 3.4.5 12024 12032 react: 18.3.1 12025 12033 12026 12034 dunder-proto@1.0.0: ··· 14633 14641 nanoid: 3.3.8 14634 14642 picocolors: 1.1.1 14635 14643 source-map-js: 1.2.1 14644 + 14645 + postgres@3.4.5: {} 14636 14646 14637 14647 prelude-ls@1.2.1: {} 14638 14648