feat: Added banner_url, description, subscriber_count columns with Driz…
- "src/db/schema/channels.ts" - "drizzle/0010_special_ghost_rider.sql" - "src/types/index.ts" - "src/sources/youtube.ts" - "src/sources/soundcloud.ts" - "src/db/repositories/channel-repository.ts" - "src/server/routes/channel.ts" - "src/__tests__/sources.test.ts" GSD-Task: S01/T01
This commit is contained in:
parent
0bf63fed9c
commit
6a5402ce8d
16 changed files with 2232 additions and 4 deletions
1
drizzle/0009_many_carlie_cooper.sql
Normal file
1
drizzle/0009_many_carlie_cooper.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `queue_items` ADD `error_category` text;
|
||||||
3
drizzle/0010_special_ghost_rider.sql
Normal file
3
drizzle/0010_special_ghost_rider.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE `channels` ADD `banner_url` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `channels` ADD `description` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE `channels` ADD `subscriber_count` integer;
|
||||||
976
drizzle/meta/0009_snapshot.json
Normal file
976
drizzle/meta/0009_snapshot.json
Normal file
|
|
@ -0,0 +1,976 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "eaac3184-0b4a-45d4-b2a9-da09dbd4bd56",
|
||||||
|
"prevId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
|
||||||
|
"tables": {
|
||||||
|
"channels": {
|
||||||
|
"name": "channels",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"name": "platform",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_id": {
|
||||||
|
"name": "platform_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitoring_enabled": {
|
||||||
|
"name": "monitoring_enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"check_interval": {
|
||||||
|
"name": "check_interval",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 360
|
||||||
|
},
|
||||||
|
"image_url": {
|
||||||
|
"name": "image_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"format_profile_id": {
|
||||||
|
"name": "format_profile_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"last_checked_at": {
|
||||||
|
"name": "last_checked_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_check_status": {
|
||||||
|
"name": "last_check_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitoring_mode": {
|
||||||
|
"name": "monitoring_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'all'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"channels_format_profile_id_format_profiles_id_fk": {
|
||||||
|
"name": "channels_format_profile_id_format_profiles_id_fk",
|
||||||
|
"tableFrom": "channels",
|
||||||
|
"tableTo": "format_profiles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"format_profile_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"content_items": {
|
||||||
|
"name": "content_items",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_content_id": {
|
||||||
|
"name": "platform_content_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content_type": {
|
||||||
|
"name": "content_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"name": "duration",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"name": "file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_size": {
|
||||||
|
"name": "file_size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"name": "format",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"quality_metadata": {
|
||||||
|
"name": "quality_metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'monitored'"
|
||||||
|
},
|
||||||
|
"thumbnail_url": {
|
||||||
|
"name": "thumbnail_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_at": {
|
||||||
|
"name": "published_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"downloaded_at": {
|
||||||
|
"name": "downloaded_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitored": {
|
||||||
|
"name": "monitored",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"content_items_channel_id_channels_id_fk": {
|
||||||
|
"name": "content_items_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "content_items",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"format_profiles": {
|
||||||
|
"name": "format_profiles",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"video_resolution": {
|
||||||
|
"name": "video_resolution",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"audio_codec": {
|
||||||
|
"name": "audio_codec",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"audio_bitrate": {
|
||||||
|
"name": "audio_bitrate",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"container_format": {
|
||||||
|
"name": "container_format",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_default": {
|
||||||
|
"name": "is_default",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"subtitle_languages": {
|
||||||
|
"name": "subtitle_languages",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"embed_subtitles": {
|
||||||
|
"name": "embed_subtitles",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"download_history": {
|
||||||
|
"name": "download_history",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"event_type": {
|
||||||
|
"name": "event_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"name": "details",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"download_history_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "download_history_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "download_history",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"download_history_channel_id_channels_id_fk": {
|
||||||
|
"name": "download_history_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "download_history",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"content_playlist": {
|
||||||
|
"name": "content_playlist",
|
||||||
|
"columns": {
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"playlist_id": {
|
||||||
|
"name": "playlist_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"content_playlist_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "content_playlist_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "content_playlist",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"content_playlist_playlist_id_playlists_id_fk": {
|
||||||
|
"name": "content_playlist_playlist_id_playlists_id_fk",
|
||||||
|
"tableFrom": "content_playlist",
|
||||||
|
"tableTo": "playlists",
|
||||||
|
"columnsFrom": [
|
||||||
|
"playlist_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"content_playlist_content_item_id_playlist_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"content_item_id",
|
||||||
|
"playlist_id"
|
||||||
|
],
|
||||||
|
"name": "content_playlist_content_item_id_playlist_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_settings": {
|
||||||
|
"name": "notification_settings",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"on_grab": {
|
||||||
|
"name": "on_grab",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"on_download": {
|
||||||
|
"name": "on_download",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"on_failure": {
|
||||||
|
"name": "on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"platform_settings": {
|
||||||
|
"name": "platform_settings",
|
||||||
|
"columns": {
|
||||||
|
"platform": {
|
||||||
|
"name": "platform",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"default_format_profile_id": {
|
||||||
|
"name": "default_format_profile_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"check_interval": {
|
||||||
|
"name": "check_interval",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 360
|
||||||
|
},
|
||||||
|
"concurrency_limit": {
|
||||||
|
"name": "concurrency_limit",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 2
|
||||||
|
},
|
||||||
|
"subtitle_languages": {
|
||||||
|
"name": "subtitle_languages",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"grab_all_enabled": {
|
||||||
|
"name": "grab_all_enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"grab_all_order": {
|
||||||
|
"name": "grab_all_order",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'newest'"
|
||||||
|
},
|
||||||
|
"scan_limit": {
|
||||||
|
"name": "scan_limit",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 100
|
||||||
|
},
|
||||||
|
"rate_limit_delay": {
|
||||||
|
"name": "rate_limit_delay",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 1000
|
||||||
|
},
|
||||||
|
"default_monitoring_mode": {
|
||||||
|
"name": "default_monitoring_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'all'"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"platform_settings_default_format_profile_id_format_profiles_id_fk": {
|
||||||
|
"name": "platform_settings_default_format_profile_id_format_profiles_id_fk",
|
||||||
|
"tableFrom": "platform_settings",
|
||||||
|
"tableTo": "format_profiles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"default_format_profile_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"playlists": {
|
||||||
|
"name": "playlists",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_playlist_id": {
|
||||||
|
"name": "platform_playlist_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"name": "position",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"playlists_channel_id_channels_id_fk": {
|
||||||
|
"name": "playlists_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "playlists",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"queue_items": {
|
||||||
|
"name": "queue_items",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'pending'"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"name": "priority",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"attempts": {
|
||||||
|
"name": "attempts",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"max_attempts": {
|
||||||
|
"name": "max_attempts",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 3
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"name": "error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"error_category": {
|
||||||
|
"name": "error_category",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"name": "started_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"completed_at": {
|
||||||
|
"name": "completed_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"queue_items_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "queue_items_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "queue_items",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"system_config": {
|
||||||
|
"name": "system_config",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
997
drizzle/meta/0010_snapshot.json
Normal file
997
drizzle/meta/0010_snapshot.json
Normal file
|
|
@ -0,0 +1,997 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "2032ed4f-0e7d-4a3c-9e00-96716084f3f6",
|
||||||
|
"prevId": "eaac3184-0b4a-45d4-b2a9-da09dbd4bd56",
|
||||||
|
"tables": {
|
||||||
|
"channels": {
|
||||||
|
"name": "channels",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"name": "platform",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_id": {
|
||||||
|
"name": "platform_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitoring_enabled": {
|
||||||
|
"name": "monitoring_enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"check_interval": {
|
||||||
|
"name": "check_interval",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 360
|
||||||
|
},
|
||||||
|
"image_url": {
|
||||||
|
"name": "image_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"format_profile_id": {
|
||||||
|
"name": "format_profile_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"last_checked_at": {
|
||||||
|
"name": "last_checked_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_check_status": {
|
||||||
|
"name": "last_check_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitoring_mode": {
|
||||||
|
"name": "monitoring_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'all'"
|
||||||
|
},
|
||||||
|
"banner_url": {
|
||||||
|
"name": "banner_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"subscriber_count": {
|
||||||
|
"name": "subscriber_count",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"channels_format_profile_id_format_profiles_id_fk": {
|
||||||
|
"name": "channels_format_profile_id_format_profiles_id_fk",
|
||||||
|
"tableFrom": "channels",
|
||||||
|
"tableTo": "format_profiles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"format_profile_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"content_items": {
|
||||||
|
"name": "content_items",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_content_id": {
|
||||||
|
"name": "platform_content_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content_type": {
|
||||||
|
"name": "content_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"name": "duration",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_path": {
|
||||||
|
"name": "file_path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"file_size": {
|
||||||
|
"name": "file_size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"name": "format",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"quality_metadata": {
|
||||||
|
"name": "quality_metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'monitored'"
|
||||||
|
},
|
||||||
|
"thumbnail_url": {
|
||||||
|
"name": "thumbnail_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"published_at": {
|
||||||
|
"name": "published_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"downloaded_at": {
|
||||||
|
"name": "downloaded_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"monitored": {
|
||||||
|
"name": "monitored",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"content_items_channel_id_channels_id_fk": {
|
||||||
|
"name": "content_items_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "content_items",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"format_profiles": {
|
||||||
|
"name": "format_profiles",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"video_resolution": {
|
||||||
|
"name": "video_resolution",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"audio_codec": {
|
||||||
|
"name": "audio_codec",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"audio_bitrate": {
|
||||||
|
"name": "audio_bitrate",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"container_format": {
|
||||||
|
"name": "container_format",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_default": {
|
||||||
|
"name": "is_default",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"subtitle_languages": {
|
||||||
|
"name": "subtitle_languages",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"embed_subtitles": {
|
||||||
|
"name": "embed_subtitles",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"download_history": {
|
||||||
|
"name": "download_history",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"event_type": {
|
||||||
|
"name": "event_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"name": "details",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"download_history_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "download_history_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "download_history",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"download_history_channel_id_channels_id_fk": {
|
||||||
|
"name": "download_history_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "download_history",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"content_playlist": {
|
||||||
|
"name": "content_playlist",
|
||||||
|
"columns": {
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"playlist_id": {
|
||||||
|
"name": "playlist_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"content_playlist_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "content_playlist_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "content_playlist",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"content_playlist_playlist_id_playlists_id_fk": {
|
||||||
|
"name": "content_playlist_playlist_id_playlists_id_fk",
|
||||||
|
"tableFrom": "content_playlist",
|
||||||
|
"tableTo": "playlists",
|
||||||
|
"columnsFrom": [
|
||||||
|
"playlist_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"content_playlist_content_item_id_playlist_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"content_item_id",
|
||||||
|
"playlist_id"
|
||||||
|
],
|
||||||
|
"name": "content_playlist_content_item_id_playlist_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"notification_settings": {
|
||||||
|
"name": "notification_settings",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"on_grab": {
|
||||||
|
"name": "on_grab",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"on_download": {
|
||||||
|
"name": "on_download",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"on_failure": {
|
||||||
|
"name": "on_failure",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"platform_settings": {
|
||||||
|
"name": "platform_settings",
|
||||||
|
"columns": {
|
||||||
|
"platform": {
|
||||||
|
"name": "platform",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"default_format_profile_id": {
|
||||||
|
"name": "default_format_profile_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"check_interval": {
|
||||||
|
"name": "check_interval",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 360
|
||||||
|
},
|
||||||
|
"concurrency_limit": {
|
||||||
|
"name": "concurrency_limit",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 2
|
||||||
|
},
|
||||||
|
"subtitle_languages": {
|
||||||
|
"name": "subtitle_languages",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"grab_all_enabled": {
|
||||||
|
"name": "grab_all_enabled",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"grab_all_order": {
|
||||||
|
"name": "grab_all_order",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'newest'"
|
||||||
|
},
|
||||||
|
"scan_limit": {
|
||||||
|
"name": "scan_limit",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 100
|
||||||
|
},
|
||||||
|
"rate_limit_delay": {
|
||||||
|
"name": "rate_limit_delay",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 1000
|
||||||
|
},
|
||||||
|
"default_monitoring_mode": {
|
||||||
|
"name": "default_monitoring_mode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'all'"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"platform_settings_default_format_profile_id_format_profiles_id_fk": {
|
||||||
|
"name": "platform_settings_default_format_profile_id_format_profiles_id_fk",
|
||||||
|
"tableFrom": "platform_settings",
|
||||||
|
"tableTo": "format_profiles",
|
||||||
|
"columnsFrom": [
|
||||||
|
"default_format_profile_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"playlists": {
|
||||||
|
"name": "playlists",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"platform_playlist_id": {
|
||||||
|
"name": "platform_playlist_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"name": "position",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"playlists_channel_id_channels_id_fk": {
|
||||||
|
"name": "playlists_channel_id_channels_id_fk",
|
||||||
|
"tableFrom": "playlists",
|
||||||
|
"tableTo": "channels",
|
||||||
|
"columnsFrom": [
|
||||||
|
"channel_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"queue_items": {
|
||||||
|
"name": "queue_items",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"content_item_id": {
|
||||||
|
"name": "content_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'pending'"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"name": "priority",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"attempts": {
|
||||||
|
"name": "attempts",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"max_attempts": {
|
||||||
|
"name": "max_attempts",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 3
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"name": "error",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"error_category": {
|
||||||
|
"name": "error_category",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"name": "started_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"completed_at": {
|
||||||
|
"name": "completed_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"queue_items_content_item_id_content_items_id_fk": {
|
||||||
|
"name": "queue_items_content_item_id_content_items_id_fk",
|
||||||
|
"tableFrom": "queue_items",
|
||||||
|
"tableTo": "content_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"content_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"system_config": {
|
||||||
|
"name": "system_config",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(datetime('now'))"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,6 +64,20 @@
|
||||||
"when": 1774839000000,
|
"when": 1774839000000,
|
||||||
"tag": "0008_add_default_monitoring_mode",
|
"tag": "0008_add_default_monitoring_mode",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1775192114394,
|
||||||
|
"tag": "0009_many_carlie_cooper",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 10,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1775196046744,
|
||||||
|
"tag": "0010_special_ghost_rider",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +135,9 @@ function makeChannel(overrides: Partial<Channel> = {}): Channel {
|
||||||
metadata: null,
|
metadata: null,
|
||||||
formatProfileId: null,
|
formatProfileId: null,
|
||||||
monitoringMode: 'all',
|
monitoringMode: 'all',
|
||||||
|
bannerUrl: null,
|
||||||
|
description: null,
|
||||||
|
subscriberCount: null,
|
||||||
createdAt: '2024-01-01T00:00:00Z',
|
createdAt: '2024-01-01T00:00:00Z',
|
||||||
updatedAt: '2024-01-01T00:00:00Z',
|
updatedAt: '2024-01-01T00:00:00Z',
|
||||||
lastCheckedAt: null,
|
lastCheckedAt: null,
|
||||||
|
|
@ -237,6 +240,9 @@ describe('YouTubeSource', () => {
|
||||||
imageUrl: 'https://i.ytimg.com/vi/thumb_large.jpg',
|
imageUrl: 'https://i.ytimg.com/vi/thumb_large.jpg',
|
||||||
url: 'https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw',
|
url: 'https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw',
|
||||||
platform: 'youtube',
|
platform: 'youtube',
|
||||||
|
bannerUrl: 'https://i.ytimg.com/vi/thumb_large.jpg',
|
||||||
|
description: null,
|
||||||
|
subscriberCount: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify yt-dlp was called with correct args
|
// Verify yt-dlp was called with correct args
|
||||||
|
|
@ -669,6 +675,9 @@ describe('SoundCloudSource', () => {
|
||||||
imageUrl: 'https://i1.sndcdn.com/avatars-large.jpg',
|
imageUrl: 'https://i1.sndcdn.com/avatars-large.jpg',
|
||||||
url: 'https://soundcloud.com/deadmau5',
|
url: 'https://soundcloud.com/deadmau5',
|
||||||
platform: 'soundcloud',
|
platform: 'soundcloud',
|
||||||
|
bannerUrl: null,
|
||||||
|
description: null,
|
||||||
|
subscriberCount: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
161
src/__tests__/yt-dlp-classification.test.ts
Normal file
161
src/__tests__/yt-dlp-classification.test.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
classifyYtDlpError,
|
||||||
|
YtDlpError,
|
||||||
|
type YtDlpErrorCategory,
|
||||||
|
} from '../sources/yt-dlp';
|
||||||
|
|
||||||
|
describe('classifyYtDlpError', () => {
|
||||||
|
// ── rate_limit ──
|
||||||
|
|
||||||
|
it('classifies HTTP 429 as rate_limit', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: HTTP Error 429: Too Many Requests')).toBe('rate_limit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "too many requests" as rate_limit', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: too many requests, please retry later')).toBe('rate_limit');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── format_unavailable ──
|
||||||
|
|
||||||
|
it('classifies "requested format" as format_unavailable', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: requested format not available')).toBe('format_unavailable');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "format is not available" as format_unavailable', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: format is not available')).toBe('format_unavailable');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── geo_blocked ──
|
||||||
|
|
||||||
|
it('classifies geo-restriction as geo_blocked', () => {
|
||||||
|
expect(
|
||||||
|
classifyYtDlpError('ERROR: Video not available in your country')
|
||||||
|
).toBe('geo_blocked');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "geo" keyword as geo_blocked', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: geo-restricted content')).toBe('geo_blocked');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── age_restricted ──
|
||||||
|
|
||||||
|
it('classifies age-restricted content', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: age restricted video')).toBe('age_restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies age verify as age_restricted', () => {
|
||||||
|
expect(classifyYtDlpError('Sign in to confirm your age. This video may be inappropriate for some users. Verify your age')).toBe('age_restricted');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── private ──
|
||||||
|
|
||||||
|
it('classifies private video', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: Private video. Sign in if you\'ve been granted access')).toBe('private');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "video unavailable"', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: Video unavailable')).toBe('private');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "been removed"', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: This video has been removed by the uploader')).toBe('private');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── sign_in_required ──
|
||||||
|
|
||||||
|
it('classifies "sign in" as sign_in_required', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: Sign in to confirm you are not a bot')).toBe('sign_in_required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "login required" as sign_in_required', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: This video requires login required authentication')).toBe('sign_in_required');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── copyright ──
|
||||||
|
|
||||||
|
it('classifies "copyright" keyword', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: This video contains content from UMG, who has blocked it on copyright grounds')).toBe('copyright');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies "blocked...claim" pattern', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: Video blocked due to a claim by Sony Music')).toBe('copyright');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── network ──
|
||||||
|
|
||||||
|
it('classifies connection error as network', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: unable to download webpage: connection refused')).toBe('network');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies timeout as network', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: timed out')).toBe('network');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('classifies urlopen error as network', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: <urlopen error [Errno -2] Name or service not known>')).toBe('network');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── unknown ──
|
||||||
|
|
||||||
|
it('returns unknown for empty string', () => {
|
||||||
|
expect(classifyYtDlpError('')).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns unknown for unrecognized error', () => {
|
||||||
|
expect(classifyYtDlpError('ERROR: Something completely unexpected happened')).toBe('unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Priority / first-match-wins ──
|
||||||
|
|
||||||
|
it('first match wins when multiple signals present', () => {
|
||||||
|
// Contains both '429' (rate_limit) and 'connection' (network) — rate_limit is checked first
|
||||||
|
const result = classifyYtDlpError('ERROR: 429 connection refused');
|
||||||
|
expect(result).toBe('rate_limit');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('YtDlpError.category', () => {
|
||||||
|
it('auto-populates category from stderr in constructor', () => {
|
||||||
|
const err = new YtDlpError(
|
||||||
|
'yt-dlp failed',
|
||||||
|
'ERROR: HTTP Error 429: Too Many Requests',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
expect(err.category).toBe('rate_limit');
|
||||||
|
expect(err.isRateLimit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets category to unknown for unrecognized errors', () => {
|
||||||
|
const err = new YtDlpError('yt-dlp failed', 'some weird error', 1);
|
||||||
|
expect(err.category).toBe('unknown');
|
||||||
|
expect(err.isRateLimit).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets sign_in_required category', () => {
|
||||||
|
const err = new YtDlpError(
|
||||||
|
'yt-dlp failed',
|
||||||
|
'ERROR: Sign in to confirm you are not a bot',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
expect(err.category).toBe('sign_in_required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets copyright category', () => {
|
||||||
|
const err = new YtDlpError(
|
||||||
|
'yt-dlp failed',
|
||||||
|
'ERROR: blocked on copyright grounds',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
expect(err.category).toBe('copyright');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves all existing YtDlpError properties', () => {
|
||||||
|
const err = new YtDlpError('msg', 'stderr text', 42);
|
||||||
|
expect(err.name).toBe('YtDlpError');
|
||||||
|
expect(err.message).toBe('msg');
|
||||||
|
expect(err.stderr).toBe('stderr text');
|
||||||
|
expect(err.exitCode).toBe(42);
|
||||||
|
expect(err.category).toBe('unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -9,12 +9,17 @@ import type { Channel, Platform, MonitoringMode } from '../../types/index';
|
||||||
/** Fields needed to create a new channel (auto-generated fields excluded). */
|
/** Fields needed to create a new channel (auto-generated fields excluded). */
|
||||||
export type CreateChannelData = Omit<
|
export type CreateChannelData = Omit<
|
||||||
Channel,
|
Channel,
|
||||||
'id' | 'createdAt' | 'updatedAt' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode'
|
'id' | 'createdAt' | 'updatedAt' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode' | 'bannerUrl' | 'description' | 'subscriberCount'
|
||||||
> & { monitoringMode?: Channel['monitoringMode'] };
|
> & {
|
||||||
|
monitoringMode?: Channel['monitoringMode'];
|
||||||
|
bannerUrl?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
subscriberCount?: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
/** Fields that can be updated on an existing channel. */
|
/** Fields that can be updated on an existing channel. */
|
||||||
export type UpdateChannelData = Partial<
|
export type UpdateChannelData = Partial<
|
||||||
Pick<Channel, 'name' | 'checkInterval' | 'monitoringEnabled' | 'imageUrl' | 'formatProfileId' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode'>
|
Pick<Channel, 'name' | 'checkInterval' | 'monitoringEnabled' | 'imageUrl' | 'formatProfileId' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode' | 'bannerUrl' | 'description' | 'subscriberCount'>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type Db = LibSQLDatabase<typeof schema>;
|
type Db = LibSQLDatabase<typeof schema>;
|
||||||
|
|
@ -39,6 +44,9 @@ export async function createChannel(
|
||||||
metadata: data.metadata,
|
metadata: data.metadata,
|
||||||
formatProfileId: data.formatProfileId,
|
formatProfileId: data.formatProfileId,
|
||||||
monitoringMode: data.monitoringMode ?? 'all',
|
monitoringMode: data.monitoringMode ?? 'all',
|
||||||
|
bannerUrl: data.bannerUrl ?? null,
|
||||||
|
description: data.description ?? null,
|
||||||
|
subscriberCount: data.subscriberCount ?? null,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|
@ -185,6 +193,9 @@ function mapRow(row: typeof channels.$inferSelect): Channel {
|
||||||
metadata: row.metadata as Record<string, unknown> | null,
|
metadata: row.metadata as Record<string, unknown> | null,
|
||||||
formatProfileId: row.formatProfileId,
|
formatProfileId: row.formatProfileId,
|
||||||
monitoringMode: (row.monitoringMode ?? 'all') as Channel['monitoringMode'],
|
monitoringMode: (row.monitoringMode ?? 'all') as Channel['monitoringMode'],
|
||||||
|
bannerUrl: row.bannerUrl ?? null,
|
||||||
|
description: row.description ?? null,
|
||||||
|
subscriberCount: row.subscriberCount ?? null,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
updatedAt: row.updatedAt,
|
updatedAt: row.updatedAt,
|
||||||
lastCheckedAt: row.lastCheckedAt,
|
lastCheckedAt: row.lastCheckedAt,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export interface CreateQueueItemData {
|
||||||
/** Optional fields when updating queue item status. */
|
/** Optional fields when updating queue item status. */
|
||||||
export interface UpdateQueueItemFields {
|
export interface UpdateQueueItemFields {
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
errorCategory?: string | null;
|
||||||
startedAt?: string | null;
|
startedAt?: string | null;
|
||||||
completedAt?: string | null;
|
completedAt?: string | null;
|
||||||
attempts?: number;
|
attempts?: number;
|
||||||
|
|
@ -72,6 +73,7 @@ export async function getQueueItemsByStatus(
|
||||||
attempts: queueItems.attempts,
|
attempts: queueItems.attempts,
|
||||||
maxAttempts: queueItems.maxAttempts,
|
maxAttempts: queueItems.maxAttempts,
|
||||||
error: queueItems.error,
|
error: queueItems.error,
|
||||||
|
errorCategory: queueItems.errorCategory,
|
||||||
startedAt: queueItems.startedAt,
|
startedAt: queueItems.startedAt,
|
||||||
completedAt: queueItems.completedAt,
|
completedAt: queueItems.completedAt,
|
||||||
createdAt: queueItems.createdAt,
|
createdAt: queueItems.createdAt,
|
||||||
|
|
@ -101,6 +103,7 @@ export async function getAllQueueItems(
|
||||||
attempts: queueItems.attempts,
|
attempts: queueItems.attempts,
|
||||||
maxAttempts: queueItems.maxAttempts,
|
maxAttempts: queueItems.maxAttempts,
|
||||||
error: queueItems.error,
|
error: queueItems.error,
|
||||||
|
errorCategory: queueItems.errorCategory,
|
||||||
startedAt: queueItems.startedAt,
|
startedAt: queueItems.startedAt,
|
||||||
completedAt: queueItems.completedAt,
|
completedAt: queueItems.completedAt,
|
||||||
createdAt: queueItems.createdAt,
|
createdAt: queueItems.createdAt,
|
||||||
|
|
@ -154,6 +157,7 @@ export async function updateQueueItemStatus(
|
||||||
};
|
};
|
||||||
|
|
||||||
if (updates?.error !== undefined) setData.error = updates.error;
|
if (updates?.error !== undefined) setData.error = updates.error;
|
||||||
|
if (updates?.errorCategory !== undefined) setData.errorCategory = updates.errorCategory;
|
||||||
if (updates?.startedAt !== undefined) setData.startedAt = updates.startedAt;
|
if (updates?.startedAt !== undefined) setData.startedAt = updates.startedAt;
|
||||||
if (updates?.completedAt !== undefined) setData.completedAt = updates.completedAt;
|
if (updates?.completedAt !== undefined) setData.completedAt = updates.completedAt;
|
||||||
if (updates?.attempts !== undefined) setData.attempts = updates.attempts;
|
if (updates?.attempts !== undefined) setData.attempts = updates.attempts;
|
||||||
|
|
@ -239,6 +243,7 @@ function mapRow(row: typeof queueItems.$inferSelect): QueueItem {
|
||||||
attempts: row.attempts,
|
attempts: row.attempts,
|
||||||
maxAttempts: row.maxAttempts,
|
maxAttempts: row.maxAttempts,
|
||||||
error: row.error,
|
error: row.error,
|
||||||
|
errorCategory: row.errorCategory,
|
||||||
startedAt: row.startedAt,
|
startedAt: row.startedAt,
|
||||||
completedAt: row.completedAt,
|
completedAt: row.completedAt,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
|
|
@ -255,6 +260,7 @@ interface JoinedQueueRow {
|
||||||
attempts: number;
|
attempts: number;
|
||||||
maxAttempts: number;
|
maxAttempts: number;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
errorCategory: string | null;
|
||||||
startedAt: string | null;
|
startedAt: string | null;
|
||||||
completedAt: string | null;
|
completedAt: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|
@ -273,6 +279,7 @@ function mapJoinedRow(row: JoinedQueueRow): QueueItem {
|
||||||
attempts: row.attempts,
|
attempts: row.attempts,
|
||||||
maxAttempts: row.maxAttempts,
|
maxAttempts: row.maxAttempts,
|
||||||
error: row.error,
|
error: row.error,
|
||||||
|
errorCategory: row.errorCategory,
|
||||||
startedAt: row.startedAt,
|
startedAt: row.startedAt,
|
||||||
completedAt: row.completedAt,
|
completedAt: row.completedAt,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,7 @@ export const channels = sqliteTable('channels', {
|
||||||
lastCheckedAt: text('last_checked_at'), // null until first monitoring check
|
lastCheckedAt: text('last_checked_at'), // null until first monitoring check
|
||||||
lastCheckStatus: text('last_check_status'), // 'success' | 'error' | 'rate_limited'
|
lastCheckStatus: text('last_check_status'), // 'success' | 'error' | 'rate_limited'
|
||||||
monitoringMode: text('monitoring_mode').notNull().default('all'), // 'all' | 'future' | 'existing' | 'none'
|
monitoringMode: text('monitoring_mode').notNull().default('all'), // 'all' | 'future' | 'existing' | 'none'
|
||||||
|
bannerUrl: text('banner_url'),
|
||||||
|
description: text('description'),
|
||||||
|
subscriberCount: integer('subscriber_count'),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export const queueItems = sqliteTable('queue_items', {
|
||||||
attempts: integer('attempts').notNull().default(0),
|
attempts: integer('attempts').notNull().default(0),
|
||||||
maxAttempts: integer('max_attempts').notNull().default(3),
|
maxAttempts: integer('max_attempts').notNull().default(3),
|
||||||
error: text('error'),
|
error: text('error'),
|
||||||
|
errorCategory: text('error_category'), // rate_limit|format_unavailable|geo_blocked|age_restricted|private|network|sign_in_required|copyright|unknown
|
||||||
startedAt: text('started_at'),
|
startedAt: text('started_at'),
|
||||||
completedAt: text('completed_at'),
|
completedAt: text('completed_at'),
|
||||||
createdAt: text('created_at')
|
createdAt: text('created_at')
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,9 @@ export async function channelRoutes(fastify: FastifyInstance): Promise<void> {
|
||||||
imageUrl: metadata.imageUrl,
|
imageUrl: metadata.imageUrl,
|
||||||
metadata: null,
|
metadata: null,
|
||||||
formatProfileId: formatProfileId ?? null,
|
formatProfileId: formatProfileId ?? null,
|
||||||
|
bannerUrl: metadata.bannerUrl ?? null,
|
||||||
|
description: metadata.description ?? null,
|
||||||
|
subscriberCount: metadata.subscriberCount ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify scheduler of new channel
|
// Notify scheduler of new channel
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,23 @@ export class SoundCloudSource implements PlatformSource {
|
||||||
? (thumbnails[thumbnails.length - 1]?.url ?? null)
|
? (thumbnails[thumbnails.length - 1]?.url ?? null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Extract enrichment metadata (limited availability on SoundCloud)
|
||||||
|
const description = typeof data.description === 'string' ? data.description : null;
|
||||||
|
const subscriberCount = typeof data.channel_follower_count === 'number'
|
||||||
|
? data.channel_follower_count
|
||||||
|
: typeof data.uploader_follower_count === 'number'
|
||||||
|
? data.uploader_follower_count
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: channelName,
|
name: channelName,
|
||||||
platformId: uploaderId,
|
platformId: uploaderId,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
url: uploaderUrl,
|
url: uploaderUrl,
|
||||||
platform: Platform.SoundCloud,
|
platform: Platform.SoundCloud,
|
||||||
|
bannerUrl: null, // SoundCloud doesn't provide banner URLs via yt-dlp
|
||||||
|
description,
|
||||||
|
subscriberCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,35 @@ export class YouTubeSource implements PlatformSource {
|
||||||
url;
|
url;
|
||||||
|
|
||||||
// Pick the best thumbnail — yt-dlp returns an array sorted by quality
|
// Pick the best thumbnail — yt-dlp returns an array sorted by quality
|
||||||
const thumbnails = data.thumbnails as Array<{ url?: string }> | undefined;
|
const thumbnails = data.thumbnails as Array<{ url?: string; width?: number }> | undefined;
|
||||||
const imageUrl = thumbnails?.length
|
const imageUrl = thumbnails?.length
|
||||||
? (thumbnails[thumbnails.length - 1]?.url ?? null)
|
? (thumbnails[thumbnails.length - 1]?.url ?? null)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Extract enrichment metadata
|
||||||
|
const description = typeof data.description === 'string' ? data.description : null;
|
||||||
|
const subscriberCount = typeof data.channel_follower_count === 'number'
|
||||||
|
? data.channel_follower_count
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Banner: try channel_banner_url first, then look for wide thumbnails (>=1024px)
|
||||||
|
let bannerUrl: string | null = null;
|
||||||
|
if (typeof data.channel_banner_url === 'string') {
|
||||||
|
bannerUrl = data.channel_banner_url;
|
||||||
|
} else if (thumbnails?.length) {
|
||||||
|
const wideThumbnail = thumbnails.find((t) => (t.width ?? 0) >= 1024);
|
||||||
|
if (wideThumbnail?.url) bannerUrl = wideThumbnail.url;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: channelName,
|
name: channelName,
|
||||||
platformId: channelId,
|
platformId: channelId,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
url: channelUrl,
|
url: channelUrl,
|
||||||
platform: Platform.YouTube,
|
platform: Platform.YouTube,
|
||||||
|
bannerUrl,
|
||||||
|
description,
|
||||||
|
subscriberCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export class YtDlpError extends Error {
|
||||||
readonly stderr: string;
|
readonly stderr: string;
|
||||||
readonly exitCode: number;
|
readonly exitCode: number;
|
||||||
readonly isRateLimit: boolean;
|
readonly isRateLimit: boolean;
|
||||||
|
readonly category: YtDlpErrorCategory;
|
||||||
|
|
||||||
constructor(message: string, stderr: string, exitCode: number) {
|
constructor(message: string, stderr: string, exitCode: number) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
@ -33,6 +34,7 @@ export class YtDlpError extends Error {
|
||||||
this.stderr = stderr;
|
this.stderr = stderr;
|
||||||
this.exitCode = exitCode;
|
this.exitCode = exitCode;
|
||||||
this.isRateLimit = detectRateLimit(stderr);
|
this.isRateLimit = detectRateLimit(stderr);
|
||||||
|
this.category = classifyYtDlpError(stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,6 +285,8 @@ export type YtDlpErrorCategory =
|
||||||
| 'age_restricted' // age-gated content
|
| 'age_restricted' // age-gated content
|
||||||
| 'private' // private or removed video
|
| 'private' // private or removed video
|
||||||
| 'network' // DNS, connection, timeout
|
| 'network' // DNS, connection, timeout
|
||||||
|
| 'sign_in_required' // sign-in or login required
|
||||||
|
| 'copyright' // copyright claim or block
|
||||||
| 'unknown';
|
| 'unknown';
|
||||||
|
|
||||||
export function classifyYtDlpError(stderr: string): YtDlpErrorCategory {
|
export function classifyYtDlpError(stderr: string): YtDlpErrorCategory {
|
||||||
|
|
@ -293,6 +297,8 @@ export function classifyYtDlpError(stderr: string): YtDlpErrorCategory {
|
||||||
if (lower.includes('not available in your country') || lower.includes('geo')) return 'geo_blocked';
|
if (lower.includes('not available in your country') || lower.includes('geo')) return 'geo_blocked';
|
||||||
if (lower.includes('age') && (lower.includes('restricted') || lower.includes('verify'))) return 'age_restricted';
|
if (lower.includes('age') && (lower.includes('restricted') || lower.includes('verify'))) return 'age_restricted';
|
||||||
if (lower.includes('private video') || lower.includes('video unavailable') || lower.includes('been removed')) return 'private';
|
if (lower.includes('private video') || lower.includes('video unavailable') || lower.includes('been removed')) return 'private';
|
||||||
|
if (lower.includes('sign in') || lower.includes('login required')) return 'sign_in_required';
|
||||||
|
if (lower.includes('copyright') || /blocked.*claim/.test(lower)) return 'copyright';
|
||||||
if (lower.includes('unable to download') || lower.includes('connection') || lower.includes('timed out') || lower.includes('urlopen error')) return 'network';
|
if (lower.includes('unable to download') || lower.includes('connection') || lower.includes('timed out') || lower.includes('urlopen error')) return 'network';
|
||||||
|
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ export interface PlatformSourceMetadata {
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
url: string;
|
url: string;
|
||||||
platform: Platform;
|
platform: Platform;
|
||||||
|
bannerUrl?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
subscriberCount?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Metadata for a single piece of content from a platform. */
|
/** Metadata for a single piece of content from a platform. */
|
||||||
|
|
@ -70,6 +73,9 @@ export interface Channel {
|
||||||
metadata: Record<string, unknown> | null;
|
metadata: Record<string, unknown> | null;
|
||||||
formatProfileId: number | null;
|
formatProfileId: number | null;
|
||||||
monitoringMode: MonitoringMode;
|
monitoringMode: MonitoringMode;
|
||||||
|
bannerUrl: string | null;
|
||||||
|
description: string | null;
|
||||||
|
subscriberCount: number | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
lastCheckedAt: string | null;
|
lastCheckedAt: string | null;
|
||||||
|
|
@ -113,6 +119,7 @@ export interface QueueItem {
|
||||||
attempts: number;
|
attempts: number;
|
||||||
maxAttempts: number;
|
maxAttempts: number;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
errorCategory: string | null;
|
||||||
startedAt: string | null;
|
startedAt: string | null;
|
||||||
completedAt: string | null;
|
completedAt: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue