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

nat magic

+592 -47
+9 -31
apps/aqua/src/xrpc/actor/getTopAlbums.ts
··· 1 1 import { TealContext } from "@/ctx"; 2 - import { db, plays, profiles } from "@teal/db"; 2 + import { db, mvTopReleasesPerUser30Days, profiles } from "@teal/db"; 3 3 import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/actor/getTopAlbums"; 4 4 import { eq } from "drizzle-orm"; 5 5 ··· 35 35 36 36 profile = profile[0]; 37 37 38 - const playsQuery = await db 38 + const topReleases30Days = await db 39 39 .select() 40 - .from(plays) 41 - .where(eq(plays.did, profile.did)) 42 - 43 - .limit(100); 44 - 45 - const albums = playsQuery.map((play) => { 46 - return { 47 - albumName: play.releaseName, 48 - // TODO: okay so this isn't in the db ?! 49 - albumArtist: play.releaseName, 50 - // TODO: see how its implemented on frontend 51 - // albumArt: play., 52 - albumReleaseMBID: play.releaseMbid, 53 - }; 54 - }); 55 - 56 - // TODO: idk this probably sucks, i'm going to bed im tired 57 - const albumCounts = albums.reduce((acc: Record<string, number>, album) => { 58 - if (album.albumName && album.albumArtist) { 59 - acc[album.albumName] = (acc[album.albumName] || 0) + 1; 60 - } 61 - return acc; 62 - }, {}); 63 - const sortedAlbums = Object.entries(albumCounts).sort((a, b) => b[1] - a[1]); 64 - const topAlbums = sortedAlbums.slice(0, Number(params.limit) || 10); 40 + .from(mvTopReleasesPerUser30Days) 41 + .where(eq(mvTopReleasesPerUser30Days.userDid, profile.did)) 42 + .limit(Number(params.limit) ?? 10); 65 43 66 44 const res: OutputSchema = { 67 45 actor: { ··· 74 52 createdAt: profile.createdAt?.toISOString(), 75 53 }, 76 54 // TODO: actually implement this 77 - albums: topAlbums.map(([albumName]) => ({ 78 - albumName, 79 - albumArtist: "", // TODO: Get actual artist name 55 + albums: topReleases30Days.map((release) => ({ 56 + albumName: release.releaseName, 57 + albumArtist: release.releaseName, 80 58 albumArt: undefined, 81 - albumReleaseMBID: undefined, 59 + albumReleaseMBID: release.releaseMbid, 82 60 })), 83 61 }; 84 62
+1
packages/db/.drizzle/0006_stale_romulus.sql
··· 1 + CREATE MATERIALIZED VIEW "public"."mv_top_releases_per_user_30days" AS (select "plays"."did", "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 "plays"."did", "releases"."mbid", "releases"."name" order by count("plays"."uri") DESC);
+545
packages/db/.drizzle/meta/0006_snapshot.json
··· 1 + { 2 + "id": "43a5b818-6a83-4577-b28f-ae7ee3028402", 3 + "prevId": "c4b1bdd0-5fea-44e4-b753-4f25193b9c87", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.artists": { 8 + "name": "artists", 9 + "schema": "", 10 + "columns": { 11 + "mbid": { 12 + "name": "mbid", 13 + "type": "uuid", 14 + "primaryKey": true, 15 + "notNull": true 16 + }, 17 + "name": { 18 + "name": "name", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true 22 + }, 23 + "play_count": { 24 + "name": "play_count", 25 + "type": "integer", 26 + "primaryKey": false, 27 + "notNull": false, 28 + "default": 0 29 + } 30 + }, 31 + "indexes": {}, 32 + "foreignKeys": {}, 33 + "compositePrimaryKeys": {}, 34 + "uniqueConstraints": {}, 35 + "policies": {}, 36 + "checkConstraints": {}, 37 + "isRLSEnabled": false 38 + }, 39 + "public.play_to_artists": { 40 + "name": "play_to_artists", 41 + "schema": "", 42 + "columns": { 43 + "artist_mbid": { 44 + "name": "artist_mbid", 45 + "type": "uuid", 46 + "primaryKey": false, 47 + "notNull": true 48 + }, 49 + "artist_name": { 50 + "name": "artist_name", 51 + "type": "text", 52 + "primaryKey": false, 53 + "notNull": false 54 + }, 55 + "play_uri": { 56 + "name": "play_uri", 57 + "type": "text", 58 + "primaryKey": false, 59 + "notNull": true 60 + } 61 + }, 62 + "indexes": {}, 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 + }, 100 + "uniqueConstraints": {}, 101 + "policies": {}, 102 + "checkConstraints": {}, 103 + "isRLSEnabled": false 104 + }, 105 + "public.plays": { 106 + "name": "plays", 107 + "schema": "", 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 + }, 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 245 + }, 246 + "handle": { 247 + "name": "handle", 248 + "type": "text", 249 + "primaryKey": false, 250 + "notNull": false 251 + }, 252 + "display_name": { 253 + "name": "display_name", 254 + "type": "text", 255 + "primaryKey": false, 256 + "notNull": false 257 + }, 258 + "description": { 259 + "name": "description", 260 + "type": "text", 261 + "primaryKey": false, 262 + "notNull": false 263 + }, 264 + "description_facets": { 265 + "name": "description_facets", 266 + "type": "jsonb", 267 + "primaryKey": false, 268 + "notNull": false 269 + }, 270 + "avatar": { 271 + "name": "avatar", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "banner": { 277 + "name": "banner", 278 + "type": "text", 279 + "primaryKey": false, 280 + "notNull": false 281 + }, 282 + "created_at": { 283 + "name": "created_at", 284 + "type": "timestamp with time zone", 285 + "primaryKey": false, 286 + "notNull": false 287 + } 288 + }, 289 + "indexes": {}, 290 + "foreignKeys": {}, 291 + "compositePrimaryKeys": {}, 292 + "uniqueConstraints": {}, 293 + "policies": {}, 294 + "checkConstraints": {}, 295 + "isRLSEnabled": false 296 + }, 297 + "public.recordings": { 298 + "name": "recordings", 299 + "schema": "", 300 + "columns": { 301 + "mbid": { 302 + "name": "mbid", 303 + "type": "uuid", 304 + "primaryKey": true, 305 + "notNull": true 306 + }, 307 + "name": { 308 + "name": "name", 309 + "type": "text", 310 + "primaryKey": false, 311 + "notNull": true 312 + }, 313 + "play_count": { 314 + "name": "play_count", 315 + "type": "integer", 316 + "primaryKey": 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 338 + }, 339 + "name": { 340 + "name": "name", 341 + "type": "text", 342 + "primaryKey": false, 343 + "notNull": true 344 + }, 345 + "play_count": { 346 + "name": "play_count", 347 + "type": "integer", 348 + "primaryKey": false, 349 + "notNull": false, 350 + "default": 0 351 + } 352 + }, 353 + "indexes": {}, 354 + "foreignKeys": {}, 355 + "compositePrimaryKeys": {}, 356 + "uniqueConstraints": {}, 357 + "policies": {}, 358 + "checkConstraints": {}, 359 + "isRLSEnabled": false 360 + }, 361 + "public.featured_items": { 362 + "name": "featured_items", 363 + "schema": "", 364 + "columns": { 365 + "did": { 366 + "name": "did", 367 + "type": "text", 368 + "primaryKey": true, 369 + "notNull": true 370 + }, 371 + "mbid": { 372 + "name": "mbid", 373 + "type": "text", 374 + "primaryKey": false, 375 + "notNull": true 376 + }, 377 + "type": { 378 + "name": "type", 379 + "type": "text", 380 + "primaryKey": false, 381 + "notNull": true 382 + } 383 + }, 384 + "indexes": {}, 385 + "foreignKeys": {}, 386 + "compositePrimaryKeys": {}, 387 + "uniqueConstraints": {}, 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 427 + }, 428 + "public.mv_recording_play_counts": { 429 + "columns": { 430 + "mbid": { 431 + "name": "mbid", 432 + "type": "uuid", 433 + "primaryKey": true, 434 + "notNull": true 435 + }, 436 + "name": { 437 + "name": "name", 438 + "type": "text", 439 + "primaryKey": 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 456 + }, 457 + "name": { 458 + "name": "name", 459 + "type": "text", 460 + "primaryKey": 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 477 + }, 478 + "name": { 479 + "name": "name", 480 + "type": "text", 481 + "primaryKey": 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 498 + }, 499 + "name": { 500 + "name": "name", 501 + "type": "text", 502 + "primaryKey": false, 503 + "notNull": true 504 + } 505 + }, 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 511 + }, 512 + "public.mv_top_releases_per_user_30days": { 513 + "columns": { 514 + "did": { 515 + "name": "did", 516 + "type": "text", 517 + "primaryKey": false, 518 + "notNull": true 519 + }, 520 + "mbid": { 521 + "name": "mbid", 522 + "type": "uuid", 523 + "primaryKey": true, 524 + "notNull": true 525 + }, 526 + "name": { 527 + "name": "name", 528 + "type": "text", 529 + "primaryKey": false, 530 + "notNull": true 531 + } 532 + }, 533 + "definition": "select \"plays\".\"did\", \"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 \"plays\".\"did\", \"releases\".\"mbid\", \"releases\".\"name\" order by count(\"plays\".\"uri\") DESC", 534 + "name": "mv_top_releases_per_user_30days", 535 + "schema": "public", 536 + "isExisting": false, 537 + "materialized": true 538 + } 539 + }, 540 + "_meta": { 541 + "columns": {}, 542 + "schemas": {}, 543 + "tables": {} 544 + } 545 + }
+7
packages/db/.drizzle/meta/_journal.json
··· 43 43 "when": 1742260281562, 44 44 "tag": "0005_plain_vulture", 45 45 "breakpoints": true 46 + }, 47 + { 48 + "idx": 6, 49 + "version": "7", 50 + "when": 1747022393536, 51 + "tag": "0006_stale_romulus", 52 + "breakpoints": true 46 53 } 47 54 ] 48 55 }
+30 -16
packages/db/schema.ts
··· 1 1 import { sql } from "drizzle-orm"; 2 2 import { 3 + integer, 4 + jsonb, 5 + pgMaterializedView, 3 6 pgTable, 7 + primaryKey, 4 8 text, 5 - pgEnum, 6 9 timestamp, 7 10 uuid, 8 - integer, 9 - jsonb, 10 - primaryKey, 11 - foreignKey, 12 - pgMaterializedView, 13 11 } from "drizzle-orm/pg-core"; 14 - import { createDeflate } from "node:zlib"; 15 12 16 13 export const artists = pgTable("artists", { 17 14 mbid: uuid("mbid").primaryKey(), ··· 50 47 .references(() => plays.uri) 51 48 .notNull(), 52 49 }, 53 - (table) => [primaryKey({ columns: [table.playUri, table.artistMbid] })], 50 + (table) => [primaryKey({ columns: [table.playUri, table.artistMbid] })] 54 51 ); 55 52 56 53 export const recordings = pgTable("recordings", { ··· 66 63 }); 67 64 68 65 export const mvArtistPlayCounts = pgMaterializedView( 69 - "mv_artist_play_counts", 66 + "mv_artist_play_counts" 70 67 ).as((qb) => { 71 68 return qb 72 69 .select({ ··· 86 83 .select({ 87 84 totalPlays: sql<number>`count(${plays.uri})`.as("total_plays"), 88 85 uniqueListeners: sql<number>`count(distinct ${plays.did})`.as( 89 - "unique_listeners", 86 + "unique_listeners" 90 87 ), 91 88 }) 92 89 .from(plays); 93 - }, 90 + } 94 91 ); 95 92 96 93 export const mvRecordingPlayCounts = pgMaterializedView( 97 - "mv_recording_play_counts", 94 + "mv_recording_play_counts" 98 95 ).as((qb) => { 99 96 return qb 100 97 .select({ ··· 108 105 }); 109 106 110 107 export const mvReleasePlayCounts = pgMaterializedView( 111 - "mv_release_play_counts", 108 + "mv_release_play_counts" 112 109 ).as((qb) => { 113 110 return qb 114 111 .select({ ··· 121 118 .groupBy(releases.mbid, releases.name); 122 119 }); 123 120 121 + export const mvTopReleasesPerUser30Days = pgMaterializedView( 122 + "mv_top_releases_per_user_30days" 123 + ).as((qb) => { 124 + return qb 125 + .select({ 126 + userDid: plays.did, 127 + releaseMbid: releases.mbid, 128 + releaseName: releases.name, 129 + playCount: sql<number>`count(${plays.uri})`.as("play_count"), 130 + }) 131 + .from(releases) 132 + .innerJoin(plays, sql`${plays.releaseMbid} = ${releases.mbid}`) 133 + .where(sql`${plays.playedTime} >= NOW() - INTERVAL '30 days'`) 134 + .groupBy(plays.did, releases.mbid, releases.name) 135 + .orderBy(sql`count(${plays.uri}) DESC`); 136 + }); 137 + 124 138 export const mvTopArtists30Days = pgMaterializedView( 125 - "mv_top_artists_30days", 139 + "mv_top_artists_30days" 126 140 ).as((qb) => { 127 141 return qb 128 142 .select({ ··· 133 147 .from(artists) 134 148 .innerJoin( 135 149 playToArtists, 136 - sql`${artists.mbid} = ${playToArtists.artistMbid}`, 150 + sql`${artists.mbid} = ${playToArtists.artistMbid}` 137 151 ) 138 152 .innerJoin(plays, sql`${plays.uri} = ${playToArtists.playUri}`) 139 153 .where(sql`${plays.playedTime} >= NOW() - INTERVAL '30 days'`) ··· 142 156 }); 143 157 144 158 export const mvTopReleases30Days = pgMaterializedView( 145 - "mv_top_releases_30days", 159 + "mv_top_releases_30days" 146 160 ).as((qb) => { 147 161 return qb 148 162 .select({