A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz

work in progress

+630 -12
+6 -1
apps/api/lexicons/playlist/insertDirectory.json
··· 8 8 "parameters": { 9 9 "type": "params", 10 10 "required": [ 11 - "uri" 11 + "uri", 12 + "directory" 12 13 ], 13 14 "properties": { 14 15 "uri": { ··· 19 20 "directory": { 20 21 "type": "string", 21 22 "description": "The directory (id) to insert into the playlist" 23 + }, 24 + "position": { 25 + "type": "integer", 26 + "description": "The position in the playlist to insert the directory at, if not specified, the directory will be appended" 22 27 } 23 28 } 24 29 }
+6 -1
apps/api/lexicons/playlist/insertFiles.json
··· 8 8 "parameters": { 9 9 "type": "params", 10 10 "required": [ 11 - "uri" 11 + "uri", 12 + "files" 12 13 ], 13 14 "properties": { 14 15 "uri": { ··· 22 23 "type": "string", 23 24 "description": "List of file (id) to insert into the playlist" 24 25 } 26 + }, 27 + "position": { 28 + "type": "integer", 29 + "description": "The position in the playlist to insert the files at, if not specified, files will be appended" 25 30 } 26 31 } 27 32 }
+8
apps/api/lexicons/playlist/startPlaylist.json
··· 15 15 "type": "string", 16 16 "description": "The URI of the playlist to start", 17 17 "format": "at-uri" 18 + }, 19 + "shuffle": { 20 + "type": "boolean", 21 + "description": "Whether to shuffle the playlist when starting it" 22 + }, 23 + "position": { 24 + "type": "integer", 25 + "description": "The position in the playlist to start from, if not specified, starts from the beginning" 18 26 } 19 27 } 20 28 }
+5 -1
apps/api/pkl/defs/playlist/insertDirectory.pkl
··· 8 8 description = "Insert a directory into a playlist" 9 9 parameters { 10 10 type = "params" 11 - required = List("uri") 11 + required = List("uri", "directory") 12 12 properties { 13 13 ["uri"] = new StringType { 14 14 type = "string" ··· 18 18 ["directory"] = new StringType { 19 19 type = "string" 20 20 description = "The directory (id) to insert into the playlist" 21 + } 22 + ["position"] = new IntegerType { 23 + type = "integer" 24 + description = "The position in the playlist to insert the directory at, if not specified, the directory will be appended" 21 25 } 22 26 } 23 27 }
+5 -1
apps/api/pkl/defs/playlist/insertFiles.pkl
··· 8 8 description = "Insert files into a playlist" 9 9 parameters { 10 10 type = "params" 11 - required = List("uri") 11 + required = List("uri", "files") 12 12 properties { 13 13 ["uri"] = new StringType { 14 14 type = "string" ··· 21 21 type = "string" 22 22 description = "List of file (id) to insert into the playlist" 23 23 } 24 + } 25 + ["position"] = new IntegerType { 26 + type = "integer" 27 + description = "The position in the playlist to insert the files at, if not specified, files will be appended" 24 28 } 25 29 } 26 30 }
+8
apps/api/pkl/defs/playlist/startPlaylist.pkl
··· 15 15 description = "The URI of the playlist to start" 16 16 format = "at-uri" 17 17 } 18 + ["shuffle"] = new BooleanType { 19 + type = "boolean" 20 + description = "Whether to shuffle the playlist when starting it" 21 + } 22 + ["position"] = new IntegerType { 23 + type = "integer" 24 + description = "The position in the playlist to start from, if not specified, starts from the beginning" 25 + } 18 26 } 19 27 } 20 28 }
+5 -1
apps/api/pkl/schema/lexicon.pkl
··· 17 17 minimum: Int? 18 18 } 19 19 20 + class BooleanType extends BaseType { 21 + type: "boolean" 22 + } 23 + 20 24 class Blob extends BaseType { 21 25 type: "blob" 22 26 accept: List<String> ··· 31 35 class Params { 32 36 type: "params" 33 37 required: List<String>? 34 - properties: Mapping<String, StringType | IntegerType | Blob | Ref | Array> 38 + properties: Mapping<String, StringType | IntegerType | BooleanType | Blob | Ref | Array> 35 39 } 36 40 37 41 class ObjectType extends BaseType {
+21 -2
apps/api/src/lexicon/lexicons.ts
··· 2344 2344 description: 'Insert a directory into a playlist', 2345 2345 parameters: { 2346 2346 type: 'params', 2347 - required: ['uri'], 2347 + required: ['uri', 'directory'], 2348 2348 properties: { 2349 2349 uri: { 2350 2350 type: 'string', ··· 2354 2354 directory: { 2355 2355 type: 'string', 2356 2356 description: 'The directory (id) to insert into the playlist', 2357 + }, 2358 + position: { 2359 + type: 'integer', 2360 + description: 2361 + 'The position in the playlist to insert the directory at, if not specified, the directory will be appended', 2357 2362 }, 2358 2363 }, 2359 2364 }, ··· 2369 2374 description: 'Insert files into a playlist', 2370 2375 parameters: { 2371 2376 type: 'params', 2372 - required: ['uri'], 2377 + required: ['uri', 'files'], 2373 2378 properties: { 2374 2379 uri: { 2375 2380 type: 'string', ··· 2382 2387 type: 'string', 2383 2388 description: 'List of file (id) to insert into the playlist', 2384 2389 }, 2390 + }, 2391 + position: { 2392 + type: 'integer', 2393 + description: 2394 + 'The position in the playlist to insert the files at, if not specified, files will be appended', 2385 2395 }, 2386 2396 }, 2387 2397 }, ··· 2514 2524 type: 'string', 2515 2525 description: 'The URI of the playlist to start', 2516 2526 format: 'at-uri', 2527 + }, 2528 + shuffle: { 2529 + type: 'boolean', 2530 + description: 'Whether to shuffle the playlist when starting it', 2531 + }, 2532 + position: { 2533 + type: 'integer', 2534 + description: 2535 + 'The position in the playlist to start from, if not specified, starts from the beginning', 2517 2536 }, 2518 2537 }, 2519 2538 },
+3 -1
apps/api/src/lexicon/types/app/rocksky/playlist/insertDirectory.ts
··· 12 12 /** The URI of the playlist to start */ 13 13 uri: string 14 14 /** The directory (id) to insert into the playlist */ 15 - directory?: string 15 + directory: string 16 + /** The position in the playlist to insert the directory at, if not specified, the directory will be appended */ 17 + position?: number 16 18 } 17 19 18 20 export type InputSchema = undefined
+3 -1
apps/api/src/lexicon/types/app/rocksky/playlist/insertFiles.ts
··· 11 11 export interface QueryParams { 12 12 /** The URI of the playlist to start */ 13 13 uri: string 14 - files?: string[] 14 + files: string[] 15 + /** The position in the playlist to insert the files at, if not specified, files will be appended */ 16 + position?: number 15 17 } 16 18 17 19 export type InputSchema = undefined
+4
apps/api/src/lexicon/types/app/rocksky/playlist/startPlaylist.ts
··· 11 11 export interface QueryParams { 12 12 /** The URI of the playlist to start */ 13 13 uri: string 14 + /** Whether to shuffle the playlist when starting it */ 15 + shuffle?: boolean 16 + /** The position in the playlist to start from, if not specified, starts from the beginning */ 17 + position?: number 14 18 } 15 19 16 20 export type InputSchema = undefined
+2
apps/api/src/schema/index.ts
··· 16 16 import playlistTracks from "./playlist-tracks"; 17 17 import playlists from "./playlists"; 18 18 import profileShouts from "./profile-shouts"; 19 + import queueTracks from "./queue-tracks"; 19 20 import scrobbles from "./scrobbles"; 20 21 import shoutLikes from "./shout-likes"; 21 22 import shoutReports from "./shout-reports"; ··· 62 63 googleDrivePaths, 63 64 dropbox, 64 65 googleDrive, 66 + queueTracks, 65 67 };
+24
apps/api/src/schema/queue-tracks.ts
··· 1 + import { InferInsertModel, InferSelectModel } from "drizzle-orm"; 2 + import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import tracks from "./tracks"; 4 + import users from "./users"; 5 + 6 + const queueTracks = pgTable("queue_tracks", { 7 + id: text("xata_id").primaryKey(), 8 + userId: text("user_id") 9 + .notNull() 10 + .references(() => users.id), 11 + trackId: text("track_id") 12 + .notNull() 13 + .references(() => tracks.id), 14 + position: integer("position").notNull(), 15 + fileUri: text("file_uri").notNull(), 16 + version: integer("xata_version").default(0).notNull(), 17 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 18 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 19 + }); 20 + 21 + export type SelectQueueTrack = InferSelectModel<typeof queueTracks>; 22 + export type InsertQueueTrack = InferInsertModel<typeof queueTracks>; 23 + 24 + export default queueTracks;
+57
apps/api/src/xrpc/app/rocksky/player/addItemsToQueue.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/player/addItemsToQueue"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const addItemsToQueue = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + handleAddItemsToQueue, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.player.addItemsToQueue({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(addItemsToQueue(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const handleAddItemsToQueue = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.queueTracks).execute(); 46 + // Logic to add items to the queue would go here 47 + }); 48 + }, 49 + catch: (err) => { 50 + console.error(err); 51 + return {}; 52 + }, 53 + }); 54 + 55 + const presentation = (): Effect.Effect<{}, never> => { 56 + return Effect.sync(() => ({})); 57 + };
+59
apps/api/src/xrpc/app/rocksky/player/getPlaybackQueue.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/player/getPlaybackQueue"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const getPlaybackQueue = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + retrieve, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.player.getPlaybackQueue({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(getPlaybackQueue(params, auth)); 29 + return { 30 + encoding: "application/json", 31 + body: {}, 32 + }; 33 + }, 34 + }); 35 + } 36 + 37 + const retrieve = ({ 38 + params, 39 + ctx, 40 + did, 41 + }: { 42 + params: QueryParams; 43 + ctx: Context; 44 + did?: string; 45 + }) => 46 + Effect.tryPromise({ 47 + try: async () => { 48 + await ctx.db.select().from(tables.queueTracks).execute(); 49 + // Logic to retrieve the playback queue would go here 50 + }, 51 + catch: (err) => { 52 + console.error(err); 53 + return {}; 54 + }, 55 + }); 56 + 57 + const presentation = (): Effect.Effect<{}, never> => { 58 + return Effect.sync(() => ({})); 59 + };
+57
apps/api/src/xrpc/app/rocksky/player/playDirectory.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/player/playFile"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const playDirectory = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + handlePlayDirectory, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.player.playDirectory({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(playDirectory(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const handlePlayDirectory = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.queueTracks).execute(); 46 + // Logic to play the directory would go here 47 + }); 48 + }, 49 + catch: (err) => { 50 + console.error(err); 51 + return {}; 52 + }, 53 + }); 54 + 55 + const presentation = (): Effect.Effect<{}, never> => { 56 + return Effect.sync(() => ({})); 57 + };
+57
apps/api/src/xrpc/app/rocksky/player/playFile.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/player/playFile"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const playFile = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + handlePlayFile, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.player.playFile({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(playFile(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const handlePlayFile = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.queueTracks).execute(); 46 + // Logic to play the file would go here 47 + }); 48 + }, 49 + catch: (err) => { 50 + console.error(err); 51 + return {}; 52 + }, 53 + }); 54 + 55 + const presentation = (): Effect.Effect<{}, never> => { 56 + return Effect.sync(() => ({})); 57 + };
+54
apps/api/src/xrpc/app/rocksky/playlist/createPlaylist.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/playlist/createPlaylist"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const createPlaylist = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + create, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.playlist.createPlaylist({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(createPlaylist(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const create = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.select().from(tables.playlists).execute(); 45 + }, 46 + catch: (err) => { 47 + console.error(err); 48 + return {}; 49 + }, 50 + }); 51 + 52 + const presentation = (): Effect.Effect<{}, never> => { 53 + return Effect.sync(() => ({})); 54 + };
+56
apps/api/src/xrpc/app/rocksky/playlist/insertDirectory.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/playlist/insertDirectory"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const insertDirectory = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + insert, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.playlist.insertDirectory({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(insertDirectory(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const insert = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.playlists).execute(); 46 + }); 47 + }, 48 + catch: (err) => { 49 + console.error(err); 50 + return {}; 51 + }, 52 + }); 53 + 54 + const presentation = (): Effect.Effect<{}, never> => { 55 + return Effect.sync(() => ({})); 56 + };
+57
apps/api/src/xrpc/app/rocksky/playlist/insertFiles.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/playlist/insertFiles"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const insertFiles = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + insert, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.playlist.insertFiles({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(insertFiles(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const insert = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.playlists).execute(); 46 + // Logic to insert files into the playlist would go here 47 + }); 48 + }, 49 + catch: (err) => { 50 + console.error(err); 51 + return {}; 52 + }, 53 + }); 54 + 55 + const presentation = (): Effect.Effect<{}, never> => { 56 + return Effect.sync(() => ({})); 57 + };
+55
apps/api/src/xrpc/app/rocksky/playlist/removePlaylist.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/playlist/removePlaylist"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const removePlaylist = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + remove, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.playlist.removePlaylist({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(removePlaylist(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const remove = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.select().from(tables.playlists).execute(); 45 + // Logic to remove the playlist would go here 46 + }, 47 + catch: (err) => { 48 + console.error(err); 49 + return {}; 50 + }, 51 + }); 52 + 53 + const presentation = (): Effect.Effect<{}, never> => { 54 + return Effect.sync(() => ({})); 55 + };
+57
apps/api/src/xrpc/app/rocksky/playlist/startPlaylist.ts
··· 1 + import { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { Context } from "context"; 3 + import { Effect, pipe } from "effect"; 4 + import { Server } from "lexicon"; 5 + import { QueryParams } from "lexicon/types/app/rocksky/playlist/startPlaylist"; 6 + import tables from "schema"; 7 + 8 + export default function (server: Server, ctx: Context) { 9 + const startPlaylist = (params, auth: HandlerAuth) => 10 + pipe( 11 + { 12 + params, 13 + ctx, 14 + did: auth.credentials?.did, 15 + }, 16 + start, 17 + Effect.flatMap(presentation), 18 + Effect.retry({ times: 3 }), 19 + Effect.timeout("10 seconds"), 20 + Effect.catchAll((err) => { 21 + console.error(err); 22 + return Effect.succeed({}); 23 + }) 24 + ); 25 + server.app.rocksky.playlist.startPlaylist({ 26 + auth: ctx.authVerifier, 27 + handler: async ({ params, auth }) => { 28 + const result = await Effect.runPromise(startPlaylist(params, auth)); 29 + }, 30 + }); 31 + } 32 + 33 + const start = ({ 34 + params, 35 + ctx, 36 + did, 37 + }: { 38 + params: QueryParams; 39 + ctx: Context; 40 + did?: string; 41 + }) => 42 + Effect.tryPromise({ 43 + try: async () => { 44 + await ctx.db.transaction(async (tx) => { 45 + await tx.select().from(tables.playlists).execute(); 46 + // Logic to start the playlist would go here 47 + }); 48 + }, 49 + catch: (err) => { 50 + console.error(err); 51 + return {}; 52 + }, 53 + }); 54 + 55 + const presentation = (): Effect.Effect<{}, never> => { 56 + return Effect.sync(() => ({})); 57 + };
+21 -3
apps/api/src/xrpc/index.ts
··· 1 1 import { Context } from "context"; 2 2 import { Server } from "lexicon"; 3 - import spotifyPause from "./app/rocksky//spotify/pause"; 4 - import spotifyPlay from "./app/rocksky//spotify/play"; 5 - import spotifySeek from "./app/rocksky//spotify/seek"; 6 3 import getActorAlbums from "./app/rocksky/actor/getActorAlbums"; 7 4 import getActorArtists from "./app/rocksky/actor/getActorArtists"; 8 5 import getActorLovedSongs from "./app/rocksky/actor/getActorLovedSongs"; ··· 35 32 import dislikeSong from "./app/rocksky/like/dislikeSong"; 36 33 import likeShout from "./app/rocksky/like/likeShout"; 37 34 import likeSong from "./app/rocksky/like/likeSong"; 35 + import addItemsToQueue from "./app/rocksky/player/addItemsToQueue"; 38 36 import getCurrentlyPlaying from "./app/rocksky/player/getCurrentlyPlaying"; 37 + import getPlaybackQueue from "./app/rocksky/player/getPlaybackQueue"; 39 38 import next from "./app/rocksky/player/next"; 40 39 import pause from "./app/rocksky/player/pause"; 41 40 import play from "./app/rocksky/player/play"; 41 + import playDirectory from "./app/rocksky/player/playDirectory"; 42 + import playFile from "./app/rocksky/player/playFile"; 42 43 import previous from "./app/rocksky/player/previous"; 43 44 import seek from "./app/rocksky/player/seek"; 45 + import createPlaylist from "./app/rocksky/playlist/createPlaylist"; 44 46 import getPlaylist from "./app/rocksky/playlist/getPlaylist"; 45 47 import getPlaylists from "./app/rocksky/playlist/getPlaylists"; 48 + import insertDirectory from "./app/rocksky/playlist/insertDirectory"; 49 + import insertFiles from "./app/rocksky/playlist/insertFiles"; 50 + import removePlaylist from "./app/rocksky/playlist/removePlaylist"; 51 + import startPlaylist from "./app/rocksky/playlist/startPlaylist"; 46 52 import createScrobble from "./app/rocksky/scrobble/createScrobble"; 47 53 import getScrobble from "./app/rocksky/scrobble/getScrobble"; 48 54 import getScrobbles from "./app/rocksky/scrobble/getScrobbles"; ··· 60 66 import getSongs from "./app/rocksky/song/getSongs"; 61 67 import spotifyGetCurrentlyPlaying from "./app/rocksky/spotify/getCurrentlyPlaying"; 62 68 import spotifyNext from "./app/rocksky/spotify/next"; 69 + import spotifyPause from "./app/rocksky/spotify/pause"; 70 + import spotifyPlay from "./app/rocksky/spotify/play"; 63 71 import spotifyPrevious from "./app/rocksky/spotify/previous"; 72 + import spotifySeek from "./app/rocksky/spotify/seek"; 64 73 import getStats from "./app/rocksky/stats/getStats"; 65 74 66 75 export default function (server: Server, ctx: Context) { ··· 127 136 spotifyPlay(server, ctx); 128 137 getStats(server, ctx); 129 138 createSong(server, ctx); 139 + addItemsToQueue(server, ctx); 140 + getPlaybackQueue(server, ctx); 141 + playDirectory(server, ctx); 142 + playFile(server, ctx); 143 + createPlaylist(server, ctx); 144 + insertDirectory(server, ctx); 145 + insertFiles(server, ctx); 146 + removePlaylist(server, ctx); 147 + startPlaylist(server, ctx); 130 148 131 149 return server; 132 150 }