Add phone playback reporting and pause sharing to the API

Phones that read system media sessions report playback themselves:
- POST /me/devices issues a per-phone token that can only call
  POST /devices/now-playing; revocable, hashed at rest, 10 per user
- Reports name the app ("via Spotify"); unknown apps use the OS label
- Push-reported playback expires after 9 minutes of silence, since
  phones send a heartbeat every 4 minutes while playing

Users can pause sharing (PUT /me/sharing: 1h, 8h, indefinitely). While
paused, connections aren't polled, phone reports are dropped, nothing is
added to history, and the current track switches to "played … ago".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
2026-10-03 17:37:30 -04:00
co-authored by Claude Opus 5.5
parent 6317004004
commit 70fc7efbbe
24 changed files with 2500 additions and 7 deletions
+14
View File
@@ -0,0 +1,14 @@
CREATE TABLE "devices" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"token_hash" text NOT NULL,
"platform" text NOT NULL,
"name" varchar(100) NOT NULL,
"last_seen_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "devices_token_hash_unique" UNIQUE("token_hash")
);
--> statement-breakpoint
ALTER TABLE "now_playing" ADD COLUMN "source_app" text;--> statement-breakpoint
ALTER TABLE "devices" ADD CONSTRAINT "devices_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "devices_user_idx" ON "devices" USING btree ("user_id");
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "sharing_paused_until" timestamp with time zone;
+978
View File
@@ -0,0 +1,978 @@
{
"id": "362955f3-fb83-46da-ab8e-e73eeaf805eb",
"prevId": "fb3fef85-3893-4f8d-b4b9-3fab12952d4e",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.connections": {
"name": "connections",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"external_user_id": {
"name": "external_user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_enc": {
"name": "access_token_enc",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token_enc": {
"name": "refresh_token_enc",
"type": "text",
"primaryKey": false,
"notNull": false
},
"token_expires_at": {
"name": "token_expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'active'"
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_polled_at": {
"name": "last_polled_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"meta": {
"name": "meta",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"webhook_secret": {
"name": "webhook_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"connections_user_provider_idx": {
"name": "connections_user_provider_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "provider",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"connections_user_id_users_id_fk": {
"name": "connections_user_id_users_id_fk",
"tableFrom": "connections",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"connections_webhook_secret_unique": {
"name": "connections_webhook_secret_unique",
"nullsNotDistinct": false,
"columns": [
"webhook_secret"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.devices": {
"name": "devices",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"platform": {
"name": "platform",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true
},
"last_seen_at": {
"name": "last_seen_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"devices_user_idx": {
"name": "devices_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"devices_user_id_users_id_fk": {
"name": "devices_user_id_users_id_fk",
"tableFrom": "devices",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"devices_token_hash_unique": {
"name": "devices_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.featured_songs": {
"name": "featured_songs",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "varchar(280)",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"featured_songs_user_id_users_id_fk": {
"name": "featured_songs_user_id_users_id_fk",
"tableFrom": "featured_songs",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"featured_songs_track_id_tracks_id_fk": {
"name": "featured_songs_track_id_tracks_id_fk",
"tableFrom": "featured_songs",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.follows": {
"name": "follows",
"schema": "",
"columns": {
"follower_id": {
"name": "follower_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"followee_id": {
"name": "followee_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"pinned_at": {
"name": "pinned_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"follows_followee_idx": {
"name": "follows_followee_idx",
"columns": [
{
"expression": "followee_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"follows_follower_id_users_id_fk": {
"name": "follows_follower_id_users_id_fk",
"tableFrom": "follows",
"tableTo": "users",
"columnsFrom": [
"follower_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"follows_followee_id_users_id_fk": {
"name": "follows_followee_id_users_id_fk",
"tableFrom": "follows",
"tableTo": "users",
"columnsFrom": [
"followee_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"follows_follower_id_followee_id_pk": {
"name": "follows_follower_id_followee_id_pk",
"columns": [
"follower_id",
"followee_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.listens": {
"name": "listens",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": true
},
"started_at": {
"name": "started_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"listens_user_started_idx": {
"name": "listens_user_started_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "started_at",
"isExpression": false,
"asc": false,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"listens_user_id_users_id_fk": {
"name": "listens_user_id_users_id_fk",
"tableFrom": "listens",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"listens_track_id_tracks_id_fk": {
"name": "listens_track_id_tracks_id_fk",
"tableFrom": "listens",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.now_playing": {
"name": "now_playing",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": true
},
"source_app": {
"name": "source_app",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_playing": {
"name": "is_playing",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"progress_ms": {
"name": "progress_ms",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"started_at": {
"name": "started_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"now_playing_track_idx": {
"name": "now_playing_track_idx",
"columns": [
{
"expression": "track_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"now_playing_user_id_users_id_fk": {
"name": "now_playing_user_id_users_id_fk",
"tableFrom": "now_playing",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"now_playing_track_id_tracks_id_fk": {
"name": "now_playing_track_id_tracks_id_fk",
"tableFrom": "now_playing",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"sessions_user_idx": {
"name": "sessions_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.track_links": {
"name": "track_links",
"schema": "",
"columns": {
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"service": {
"name": "service",
"type": "text",
"primaryKey": false,
"notNull": true
},
"external_id": {
"name": "external_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"match_type": {
"name": "match_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"track_links_external_idx": {
"name": "track_links_external_idx",
"columns": [
{
"expression": "service",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "external_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"track_links_track_id_tracks_id_fk": {
"name": "track_links_track_id_tracks_id_fk",
"tableFrom": "track_links",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"track_links_track_id_service_pk": {
"name": "track_links_track_id_service_pk",
"columns": [
"track_id",
"service"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tracks": {
"name": "tracks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"isrc": {
"name": "isrc",
"type": "varchar(12)",
"primaryKey": false,
"notNull": false
},
"match_key": {
"name": "match_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"artist": {
"name": "artist",
"type": "text",
"primaryKey": false,
"notNull": true
},
"album": {
"name": "album",
"type": "text",
"primaryKey": false,
"notNull": false
},
"duration_ms": {
"name": "duration_ms",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"artwork_url": {
"name": "artwork_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"links_resolved_at": {
"name": "links_resolved_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"tracks_isrc_idx": {
"name": "tracks_isrc_idx",
"columns": [
{
"expression": "isrc",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"tracks_match_key_idx": {
"name": "tracks_match_key_idx",
"columns": [
{
"expression": "match_key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(320)",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"bio": {
"name": "bio",
"type": "varchar(280)",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "varchar(500)",
"primaryKey": false,
"notNull": false
},
"preferred_service": {
"name": "preferred_service",
"type": "text",
"primaryKey": false,
"notNull": false
},
"plan": {
"name": "plan",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"plan_expires_at": {
"name": "plan_expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
},
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+984
View File
@@ -0,0 +1,984 @@
{
"id": "96e26cc1-ef9f-4bdb-9889-54a20705d43f",
"prevId": "362955f3-fb83-46da-ab8e-e73eeaf805eb",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.connections": {
"name": "connections",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"external_user_id": {
"name": "external_user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_enc": {
"name": "access_token_enc",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token_enc": {
"name": "refresh_token_enc",
"type": "text",
"primaryKey": false,
"notNull": false
},
"token_expires_at": {
"name": "token_expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'active'"
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_polled_at": {
"name": "last_polled_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"meta": {
"name": "meta",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"webhook_secret": {
"name": "webhook_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"connections_user_provider_idx": {
"name": "connections_user_provider_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "provider",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"connections_user_id_users_id_fk": {
"name": "connections_user_id_users_id_fk",
"tableFrom": "connections",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"connections_webhook_secret_unique": {
"name": "connections_webhook_secret_unique",
"nullsNotDistinct": false,
"columns": [
"webhook_secret"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.devices": {
"name": "devices",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"token_hash": {
"name": "token_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"platform": {
"name": "platform",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true
},
"last_seen_at": {
"name": "last_seen_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"devices_user_idx": {
"name": "devices_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"devices_user_id_users_id_fk": {
"name": "devices_user_id_users_id_fk",
"tableFrom": "devices",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"devices_token_hash_unique": {
"name": "devices_token_hash_unique",
"nullsNotDistinct": false,
"columns": [
"token_hash"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.featured_songs": {
"name": "featured_songs",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"comment": {
"name": "comment",
"type": "varchar(280)",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"featured_songs_user_id_users_id_fk": {
"name": "featured_songs_user_id_users_id_fk",
"tableFrom": "featured_songs",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"featured_songs_track_id_tracks_id_fk": {
"name": "featured_songs_track_id_tracks_id_fk",
"tableFrom": "featured_songs",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.follows": {
"name": "follows",
"schema": "",
"columns": {
"follower_id": {
"name": "follower_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"followee_id": {
"name": "followee_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"pinned_at": {
"name": "pinned_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"follows_followee_idx": {
"name": "follows_followee_idx",
"columns": [
{
"expression": "followee_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"follows_follower_id_users_id_fk": {
"name": "follows_follower_id_users_id_fk",
"tableFrom": "follows",
"tableTo": "users",
"columnsFrom": [
"follower_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"follows_followee_id_users_id_fk": {
"name": "follows_followee_id_users_id_fk",
"tableFrom": "follows",
"tableTo": "users",
"columnsFrom": [
"followee_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"follows_follower_id_followee_id_pk": {
"name": "follows_follower_id_followee_id_pk",
"columns": [
"follower_id",
"followee_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.listens": {
"name": "listens",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": true
},
"started_at": {
"name": "started_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"listens_user_started_idx": {
"name": "listens_user_started_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "started_at",
"isExpression": false,
"asc": false,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"listens_user_id_users_id_fk": {
"name": "listens_user_id_users_id_fk",
"tableFrom": "listens",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"listens_track_id_tracks_id_fk": {
"name": "listens_track_id_tracks_id_fk",
"tableFrom": "listens",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.now_playing": {
"name": "now_playing",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": true
},
"source_app": {
"name": "source_app",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_playing": {
"name": "is_playing",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"progress_ms": {
"name": "progress_ms",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"started_at": {
"name": "started_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"now_playing_track_idx": {
"name": "now_playing_track_idx",
"columns": [
{
"expression": "track_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"now_playing_user_id_users_id_fk": {
"name": "now_playing_user_id_users_id_fk",
"tableFrom": "now_playing",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"now_playing_track_id_tracks_id_fk": {
"name": "now_playing_track_id_tracks_id_fk",
"tableFrom": "now_playing",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"sessions_user_idx": {
"name": "sessions_user_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.track_links": {
"name": "track_links",
"schema": "",
"columns": {
"track_id": {
"name": "track_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"service": {
"name": "service",
"type": "text",
"primaryKey": false,
"notNull": true
},
"external_id": {
"name": "external_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"match_type": {
"name": "match_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"track_links_external_idx": {
"name": "track_links_external_idx",
"columns": [
{
"expression": "service",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "external_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"track_links_track_id_tracks_id_fk": {
"name": "track_links_track_id_tracks_id_fk",
"tableFrom": "track_links",
"tableTo": "tracks",
"columnsFrom": [
"track_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"track_links_track_id_service_pk": {
"name": "track_links_track_id_service_pk",
"columns": [
"track_id",
"service"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.tracks": {
"name": "tracks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"isrc": {
"name": "isrc",
"type": "varchar(12)",
"primaryKey": false,
"notNull": false
},
"match_key": {
"name": "match_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true
},
"artist": {
"name": "artist",
"type": "text",
"primaryKey": false,
"notNull": true
},
"album": {
"name": "album",
"type": "text",
"primaryKey": false,
"notNull": false
},
"duration_ms": {
"name": "duration_ms",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"artwork_url": {
"name": "artwork_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"links_resolved_at": {
"name": "links_resolved_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"tracks_isrc_idx": {
"name": "tracks_isrc_idx",
"columns": [
{
"expression": "isrc",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"tracks_match_key_idx": {
"name": "tracks_match_key_idx",
"columns": [
{
"expression": "match_key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(320)",
"primaryKey": false,
"notNull": true
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true
},
"display_name": {
"name": "display_name",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"bio": {
"name": "bio",
"type": "varchar(280)",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "varchar(500)",
"primaryKey": false,
"notNull": false
},
"preferred_service": {
"name": "preferred_service",
"type": "text",
"primaryKey": false,
"notNull": false
},
"sharing_paused_until": {
"name": "sharing_paused_until",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"plan": {
"name": "plan",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"plan_expires_at": {
"name": "plan_expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
},
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+14
View File
@@ -29,6 +29,20 @@
"when": 1791057194458,
"tag": "0003_small_taskmaster",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1791058721937,
"tag": "0004_late_proudstar",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1791059998457,
"tag": "0005_cooing_thena",
"breakpoints": true
}
]
}