diff --git a/src/backend/common/database/drizzle/drizzleTypes.ts b/src/backend/common/database/drizzle/drizzleTypes.ts index d2edea03..8872023d 100644 --- a/src/backend/common/database/drizzle/drizzleTypes.ts +++ b/src/backend/common/database/drizzle/drizzleTypes.ts @@ -17,8 +17,8 @@ export type PlayInputNew = typeof playInputs.$inferInsert; export type PlayInputSelect = typeof playInputs.$inferSelect; export type PlaySelect = typeof plays.$inferSelect; -export type PlaySelectRel = ModelWithRelations; export type PlaySelectWithQueueStates = GenericRelationResult<'plays', 'queueStates'>; +export type PlayWith = GenericRelationResult<'plays', K>; export type PlayNew = typeof plays.$inferInsert; @@ -43,59 +43,6 @@ export type WhereClause = RelationsFilter = Pick, 'gt' | 'gte' | 'eq' | 'lt' | 'lte' | 'ne'> export type CompareOpKey = keyof CompareOp; - - -/** - * Based on https://github.com/drizzle-team/drizzle-orm/issues/695#issuecomment-3133969178 - */ - -// Helper type to find the tsName corresponding to a given dbName in TSchema -type FindTsNameByDbName = { - [K in keyof TSchema]: TSchema[K] extends { - // updated dbName -> name - name: TDbNameToFind; - } - ? K - : TDbNameToFind; -}[keyof TSchema]; - -// Helper type to find the dbName corresponding to a given tsName in TSchema -type FindDbNameByTsName = { - [K in keyof Schema]: Schema[K] extends TTable ? K : never; -}[keyof Schema]; - -/** - * Utility type to infer the model type for a given table name from the schema. - * Handles nested relations recursively. - * Uses referencedTableName (dbName) and FindTsNameByDbName helper. - */ -export type ModelWithRelationsFromName< - TTableName extends keyof TSchema, -> = InferSelectModel & { - [K in keyof TSchema[TTableName]['relations']]?: TSchema[TTableName]['relations'][K] extends infer TRelation - // updated referencedTableName -> targetTableName - ? TRelation extends { targetTableName: infer TRefDbName extends string } - ? FindTsNameByDbName extends infer TRefTsName extends - keyof TSchema - ? TRelation extends Many - ? ModelWithRelationsFromName[] - : ModelWithRelationsFromName | null - : never - : never - : never; -}; - -/** - * Utility type to infer the model type for a given table from the schema. - * Handles nested relations recursively. - * Uses referencedTableName (dbName) and FindDbNameByTsName helper. - */ -export type ModelWithRelations = - FindDbNameByTsName extends infer TTableName extends keyof TSchema - ? ModelWithRelationsFromName - : never; - - // all relations are are now fully typed and optional //type FullPlay = ModelWithRelations; diff --git a/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/migration.sql b/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/migration.sql new file mode 100644 index 00000000..b1a04ae0 --- /dev/null +++ b/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/migration.sql @@ -0,0 +1,17 @@ +CREATE TABLE `plays_historical` ( + `id` integer PRIMARY KEY, + `uid` text(200), + `componentId` integer, + `playedAt` number, + `seenAt` number, + `play` text NOT NULL, + `playHash` text, + `mbidIdentifier` text, + CONSTRAINT `fk_plays_historical_componentId_components_id_fk` FOREIGN KEY (`componentId`) REFERENCES `components`(`id`) ON UPDATE CASCADE ON DELETE CASCADE +); +--> statement-breakpoint +ALTER TABLE `components` ADD `lastReadyAt` number;--> statement-breakpoint +ALTER TABLE `components` ADD `lastActiveAt` number;--> statement-breakpoint +CREATE INDEX `play_historical_component_id_idx` ON `plays_historical` (`componentId`);--> statement-breakpoint +CREATE UNIQUE INDEX `play_historical_uid_idx` ON `plays_historical` (`uid`);--> statement-breakpoint +CREATE INDEX `play_historical_playedAt_idx` ON `plays_historical` (`playedAt`); \ No newline at end of file diff --git a/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/snapshot.json b/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/snapshot.json new file mode 100644 index 00000000..3a7e8b10 --- /dev/null +++ b/src/backend/common/database/drizzle/migrations/20260515170311_nostalgic_venom/snapshot.json @@ -0,0 +1,1094 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "195d210b-d216-4980-8d74-fa3ac3abfcd2", + "prevIds": [ + "68ef2e2f-c7b2-41bb-9487-2d49bd04d70d" + ], + "ddl": [ + { + "name": "component_migrations", + "entityType": "tables" + }, + { + "name": "components", + "entityType": "tables" + }, + { + "name": "jobs", + "entityType": "tables" + }, + { + "name": "play_inputs", + "entityType": "tables" + }, + { + "name": "plays", + "entityType": "tables" + }, + { + "name": "plays_historical", + "entityType": "tables" + }, + { + "name": "play_queue_states", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentId", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "success", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "error", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "attemptedAt", + "entityType": "columns", + "table": "component_migrations" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "components" + }, + { + "type": "text(200)", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "uid", + "entityType": "columns", + "table": "components" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "mode", + "entityType": "columns", + "table": "components" + }, + { + "type": "text(50)", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "components" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "components" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "countLive", + "entityType": "columns", + "table": "components" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "countNonLive", + "entityType": "columns", + "table": "components" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "createdAt", + "entityType": "columns", + "table": "components" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "lastReadyAt", + "entityType": "columns", + "table": "components" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "lastActiveAt", + "entityType": "columns", + "table": "components" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentFromId", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentToId", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text(50)", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "'idle'", + "generated": null, + "name": "status", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "retries", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "error", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "transformOptions", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "initialParameters", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "cursor", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "total", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "imported", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "scrobbled", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "createdAt", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updatedAt", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "completedAt", + "entityType": "columns", + "table": "jobs" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "play_inputs" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playId", + "entityType": "columns", + "table": "play_inputs" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "play_inputs" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "play", + "entityType": "columns", + "table": "play_inputs" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "createdAt", + "entityType": "columns", + "table": "play_inputs" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text(30)", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "uid", + "entityType": "columns", + "table": "plays" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentId", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "error", + "entityType": "columns", + "table": "plays" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playedAt", + "entityType": "columns", + "table": "plays" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seenAt", + "entityType": "columns", + "table": "plays" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updatedAt", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "play", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "state", + "entityType": "columns", + "table": "plays" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parentId", + "entityType": "columns", + "table": "plays" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "jobId", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playHash", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "mbidIdentifier", + "entityType": "columns", + "table": "plays" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "compacted", + "entityType": "columns", + "table": "plays" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "text(200)", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "uid", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentId", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playedAt", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "number", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seenAt", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "play", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playHash", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "mbidIdentifier", + "entityType": "columns", + "table": "plays_historical" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "playId", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "componentId", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "text(50)", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "queueName", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "'queued'", + "generated": null, + "name": "queueStatus", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": "0", + "generated": null, + "name": "retries", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "error", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "createdAt", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "type": "number", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updatedAt", + "entityType": "columns", + "table": "play_queue_states" + }, + { + "columns": [ + "componentId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_component_migrations_componentId_components_id_fk", + "entityType": "fks", + "table": "component_migrations" + }, + { + "columns": [ + "componentFromId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_jobs_componentFromId_components_id_fk", + "entityType": "fks", + "table": "jobs" + }, + { + "columns": [ + "componentToId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_jobs_componentToId_components_id_fk", + "entityType": "fks", + "table": "jobs" + }, + { + "columns": [ + "playId" + ], + "tableTo": "plays", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_play_inputs_playId_plays_id_fk", + "entityType": "fks", + "table": "play_inputs" + }, + { + "columns": [ + "componentId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_plays_componentId_components_id_fk", + "entityType": "fks", + "table": "plays" + }, + { + "columns": [ + "parentId" + ], + "tableTo": "plays", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_plays_parentId_plays_id_fk", + "entityType": "fks", + "table": "plays" + }, + { + "columns": [ + "jobId" + ], + "tableTo": "jobs", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_plays_jobId_jobs_id_fk", + "entityType": "fks", + "table": "plays" + }, + { + "columns": [ + "componentId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_plays_historical_componentId_components_id_fk", + "entityType": "fks", + "table": "plays_historical" + }, + { + "columns": [ + "playId" + ], + "tableTo": "plays", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_play_queue_states_playId_plays_id_fk", + "entityType": "fks", + "table": "play_queue_states" + }, + { + "columns": [ + "componentId" + ], + "tableTo": "components", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_play_queue_states_componentId_components_id_fk", + "entityType": "fks", + "table": "play_queue_states" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "component_migrations_pk", + "table": "component_migrations", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "components_pk", + "table": "components", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobs_pk", + "table": "jobs", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "play_inputs_pk", + "table": "play_inputs", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "plays_pk", + "table": "plays", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "plays_historical_pk", + "table": "plays_historical", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "play_queue_states_pk", + "table": "play_queue_states", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "uid", + "isExpression": false + }, + { + "value": "mode", + "isExpression": false + }, + { + "value": "type", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "uid_mode_type_idx", + "entityType": "indexes", + "table": "components" + }, + { + "columns": [ + { + "value": "playId", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "play_input_id_idx", + "entityType": "indexes", + "table": "play_inputs" + }, + { + "columns": [ + { + "value": "parentId", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_parent_id_idx", + "entityType": "indexes", + "table": "plays" + }, + { + "columns": [ + { + "value": "componentId", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_component_id_idx", + "entityType": "indexes", + "table": "plays" + }, + { + "columns": [ + { + "value": "uid", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "play_uid_idx", + "entityType": "indexes", + "table": "plays" + }, + { + "columns": [ + { + "value": "playedAt", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_playedAt_idx", + "entityType": "indexes", + "table": "plays" + }, + { + "columns": [ + { + "value": "seenAt", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_seenAt_idx", + "entityType": "indexes", + "table": "plays" + }, + { + "columns": [ + { + "value": "componentId", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_historical_component_id_idx", + "entityType": "indexes", + "table": "plays_historical" + }, + { + "columns": [ + { + "value": "uid", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "play_historical_uid_idx", + "entityType": "indexes", + "table": "plays_historical" + }, + { + "columns": [ + { + "value": "playedAt", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_historical_playedAt_idx", + "entityType": "indexes", + "table": "plays_historical" + }, + { + "columns": [ + { + "value": "playId", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "play_queue_state_id_idx", + "entityType": "indexes", + "table": "play_queue_states" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/src/backend/common/database/drizzle/repositories/PlayRepository.ts b/src/backend/common/database/drizzle/repositories/PlayRepository.ts index a1c80d7a..777a664f 100644 --- a/src/backend/common/database/drizzle/repositories/PlayRepository.ts +++ b/src/backend/common/database/drizzle/repositories/PlayRepository.ts @@ -4,7 +4,7 @@ import { loggerNoop } from "../../../MaybeLogger.js"; import { ErrorLike, PlayObject, TA_CLOSE, TA_DEFAULT_ACCURACY, TA_EXACT, TemporalAccuracy } from "../../../../../core/Atomic.js"; import { generateInputEntity, generatePlayEntity, PlayEntityOpts, hydratePlaySelect, PlayHydateOptions } from "../entityUtils.js"; import { playInputs, plays, queueStates, relations } from "../schema/schema.js"; -import { PlayNew, PlaySelect, PlayInputNew, FindWhere, FindMany, CompareOpKey, QueueStateSelect, PlayInputSelect, PlaySelectRel, FindWith, PlaySelectWithQueueStates, WhereClause } from "../drizzleTypes.js";; +import { PlayNew, PlaySelect, PlayInputNew, FindWhere, FindMany, QueueStateSelect, FindWith, PlaySelectWithQueueStates, WhereClause, PlayWith } from "../drizzleTypes.js";; import { MarkOptional, MarkRequired, PathValue } from "ts-essentials"; import { genGroupIdStrFromPlay, removeEmptyArrays, removeUndefinedKeys } from "../../../../utils.js"; import dayjs, { Dayjs } from "dayjs"; @@ -236,7 +236,7 @@ export class DrizzlePlayRepository extends DrizzleBaseRepository<'plays'> { return results.map(identifierExtractor[identifier]); } - findPlaysPaginated = async (args: QueryPlaysOpts, opts: HydrateOpts & ComponentConstrainedRepoOpts = {}): Promise> => { + findPlaysPaginated = async >(args: QueryPlaysOpts, opts: HydrateOpts & ComponentConstrainedRepoOpts = {}): Promise> => { const { limit = 100, offset = 0, @@ -664,7 +664,7 @@ export class DrizzlePlayRepository extends DrizzleBaseRepository<'plays'> { }) } - public getTemporallyClosePlays = async (play: PlayObject, opts: {states?: PlaySelect['state'][], bufferTime?: number} & { with?: WithPlayRelation[] } & ComponentConstrainedRepoOpts = {}): Promise => { + public getTemporallyClosePlays = async (play: PlayObject, opts: {states?: PlaySelect['state'][], bufferTime?: number} & { with?: WithPlayRelation[] } & ComponentConstrainedRepoOpts = {}): Promise[]> => { const { componentId = this.componentId, bufferTime, @@ -688,7 +688,7 @@ export class DrizzlePlayRepository extends DrizzleBaseRepository<'plays'> { return ((await this.db.query.plays.findMany({ where, with: buildPlayWith(qWith) - })) as PlaySelectRel[]).map(x => ({...x, play: hydratePlaySelect(x)})); + })) as PlayWith<'queueStates'>[]).map(x => ({...x, play: hydratePlaySelect(x)})); } } diff --git a/src/backend/common/database/drizzle/schema/schema.ts b/src/backend/common/database/drizzle/schema/schema.ts index a681b26c..240bc270 100644 --- a/src/backend/common/database/drizzle/schema/schema.ts +++ b/src/backend/common/database/drizzle/schema/schema.ts @@ -99,6 +99,21 @@ export const playInputs = sqliteTable("play_inputs", { uniqueIndex('play_input_id_idx').on(table.playId) ]); +export const playsHistorical = sqliteTable("plays_historical", { + id: integer().primaryKey(), + uid: text({ length: 200 }).unique(), + componentId: integer().references(() => components.id, {onDelete: 'cascade', onUpdate: 'cascade'}), + playedAt: DayjsTimestamp('playedAt'), + seenAt: DayjsTimestamp('seenAt'), + play: PlayJson('play').notNull(), + playHash: text(), + mbidIdentifier: text() +}, (table) => [ + index("play_historical_component_id_idx").on(table.componentId), + uniqueIndex("play_historical_uid_idx").on(table.uid), + index("play_historical_playedAt_idx").on(table.playedAt) +]); + // export const playParentRelations = defineRelations({plays}, (r) => ({ // plays: { // parent: r.one.plays({ @@ -160,7 +175,9 @@ export const components = sqliteTable("components", { countLive: integer().notNull().default(0), // number of discovered/scrobbled plays from backlog/jobs countNonLive: integer().notNull().default(0), - createdAt: DayjsTimestamp('createdAt').$defaultFn(() => dayjs()) + createdAt: DayjsTimestamp('createdAt').$defaultFn(() => dayjs()), + lastReadyAt: DayjsTimestamp('lastReadyAt'), + lastActiveAt: DayjsTimestamp('lastActiveAt') }, (table) => [ uniqueIndex('uid_mode_type_idx').on(table.uid,table.mode,table.type) @@ -194,7 +211,7 @@ export const jobs = sqliteTable("jobs", { completedAt: DayjsTimestamp('completedAt') }); -const playRelations = defineRelations({ plays, queueStates, playInputs, components, jobs, componentMigrations }, (r) => ({ +const playRelations = defineRelations({ plays, queueStates, playInputs, components, jobs, componentMigrations,playsHistorical }, (r) => ({ plays: { queueStates: r.many.queueStates(), input: r.one.playInputs({ @@ -218,6 +235,12 @@ const playRelations = defineRelations({ plays, queueStates, playInputs, componen optional: true }) }, + playsHistorical: { + component: r.one.components({ + from: r.playsHistorical.componentId, + to: r.components.id, + }), + }, queueStates: { play: r.one.plays({ from: r.queueStates.playId, @@ -230,6 +253,7 @@ const playRelations = defineRelations({ plays, queueStates, playInputs, componen }, components: { plays: r.many.plays(), + playsHistorical: r.many.plays(), queueStates: r.many.queueStates(), migrations: r.many.componentMigrations(), }, @@ -250,6 +274,8 @@ export const getConfigByTableName = (name: T) => { switch(name) { case 'plays': return plays; + case 'playsHistorical': + return playsHistorical; case 'components': return components; case 'playInputs':