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,
|
||||
"tag": "0008_add_default_monitoring_mode",
|
||||
"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,
|
||||
formatProfileId: null,
|
||||
monitoringMode: 'all',
|
||||
bannerUrl: null,
|
||||
description: null,
|
||||
subscriberCount: null,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
lastCheckedAt: null,
|
||||
|
|
@ -237,6 +240,9 @@ describe('YouTubeSource', () => {
|
|||
imageUrl: 'https://i.ytimg.com/vi/thumb_large.jpg',
|
||||
url: 'https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw',
|
||||
platform: 'youtube',
|
||||
bannerUrl: 'https://i.ytimg.com/vi/thumb_large.jpg',
|
||||
description: null,
|
||||
subscriberCount: null,
|
||||
});
|
||||
|
||||
// Verify yt-dlp was called with correct args
|
||||
|
|
@ -669,6 +675,9 @@ describe('SoundCloudSource', () => {
|
|||
imageUrl: 'https://i1.sndcdn.com/avatars-large.jpg',
|
||||
url: 'https://soundcloud.com/deadmau5',
|
||||
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). */
|
||||
export type CreateChannelData = Omit<
|
||||
Channel,
|
||||
'id' | 'createdAt' | 'updatedAt' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode'
|
||||
> & { monitoringMode?: Channel['monitoringMode'] };
|
||||
'id' | 'createdAt' | 'updatedAt' | 'lastCheckedAt' | 'lastCheckStatus' | 'monitoringMode' | 'bannerUrl' | 'description' | 'subscriberCount'
|
||||
> & {
|
||||
monitoringMode?: Channel['monitoringMode'];
|
||||
bannerUrl?: string | null;
|
||||
description?: string | null;
|
||||
subscriberCount?: number | null;
|
||||
};
|
||||
|
||||
/** Fields that can be updated on an existing channel. */
|
||||
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>;
|
||||
|
|
@ -39,6 +44,9 @@ export async function createChannel(
|
|||
metadata: data.metadata,
|
||||
formatProfileId: data.formatProfileId,
|
||||
monitoringMode: data.monitoringMode ?? 'all',
|
||||
bannerUrl: data.bannerUrl ?? null,
|
||||
description: data.description ?? null,
|
||||
subscriberCount: data.subscriberCount ?? null,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
|
@ -185,6 +193,9 @@ function mapRow(row: typeof channels.$inferSelect): Channel {
|
|||
metadata: row.metadata as Record<string, unknown> | null,
|
||||
formatProfileId: row.formatProfileId,
|
||||
monitoringMode: (row.monitoringMode ?? 'all') as Channel['monitoringMode'],
|
||||
bannerUrl: row.bannerUrl ?? null,
|
||||
description: row.description ?? null,
|
||||
subscriberCount: row.subscriberCount ?? null,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
lastCheckedAt: row.lastCheckedAt,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export interface CreateQueueItemData {
|
|||
/** Optional fields when updating queue item status. */
|
||||
export interface UpdateQueueItemFields {
|
||||
error?: string | null;
|
||||
errorCategory?: string | null;
|
||||
startedAt?: string | null;
|
||||
completedAt?: string | null;
|
||||
attempts?: number;
|
||||
|
|
@ -72,6 +73,7 @@ export async function getQueueItemsByStatus(
|
|||
attempts: queueItems.attempts,
|
||||
maxAttempts: queueItems.maxAttempts,
|
||||
error: queueItems.error,
|
||||
errorCategory: queueItems.errorCategory,
|
||||
startedAt: queueItems.startedAt,
|
||||
completedAt: queueItems.completedAt,
|
||||
createdAt: queueItems.createdAt,
|
||||
|
|
@ -101,6 +103,7 @@ export async function getAllQueueItems(
|
|||
attempts: queueItems.attempts,
|
||||
maxAttempts: queueItems.maxAttempts,
|
||||
error: queueItems.error,
|
||||
errorCategory: queueItems.errorCategory,
|
||||
startedAt: queueItems.startedAt,
|
||||
completedAt: queueItems.completedAt,
|
||||
createdAt: queueItems.createdAt,
|
||||
|
|
@ -154,6 +157,7 @@ export async function updateQueueItemStatus(
|
|||
};
|
||||
|
||||
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?.completedAt !== undefined) setData.completedAt = updates.completedAt;
|
||||
if (updates?.attempts !== undefined) setData.attempts = updates.attempts;
|
||||
|
|
@ -239,6 +243,7 @@ function mapRow(row: typeof queueItems.$inferSelect): QueueItem {
|
|||
attempts: row.attempts,
|
||||
maxAttempts: row.maxAttempts,
|
||||
error: row.error,
|
||||
errorCategory: row.errorCategory,
|
||||
startedAt: row.startedAt,
|
||||
completedAt: row.completedAt,
|
||||
createdAt: row.createdAt,
|
||||
|
|
@ -255,6 +260,7 @@ interface JoinedQueueRow {
|
|||
attempts: number;
|
||||
maxAttempts: number;
|
||||
error: string | null;
|
||||
errorCategory: string | null;
|
||||
startedAt: string | null;
|
||||
completedAt: string | null;
|
||||
createdAt: string;
|
||||
|
|
@ -273,6 +279,7 @@ function mapJoinedRow(row: JoinedQueueRow): QueueItem {
|
|||
attempts: row.attempts,
|
||||
maxAttempts: row.maxAttempts,
|
||||
error: row.error,
|
||||
errorCategory: row.errorCategory,
|
||||
startedAt: row.startedAt,
|
||||
completedAt: row.completedAt,
|
||||
createdAt: row.createdAt,
|
||||
|
|
|
|||
|
|
@ -28,4 +28,7 @@ export const channels = sqliteTable('channels', {
|
|||
lastCheckedAt: text('last_checked_at'), // null until first monitoring check
|
||||
lastCheckStatus: text('last_check_status'), // 'success' | 'error' | 'rate_limited'
|
||||
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),
|
||||
maxAttempts: integer('max_attempts').notNull().default(3),
|
||||
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'),
|
||||
completedAt: text('completed_at'),
|
||||
createdAt: text('created_at')
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ export async function channelRoutes(fastify: FastifyInstance): Promise<void> {
|
|||
imageUrl: metadata.imageUrl,
|
||||
metadata: null,
|
||||
formatProfileId: formatProfileId ?? null,
|
||||
bannerUrl: metadata.bannerUrl ?? null,
|
||||
description: metadata.description ?? null,
|
||||
subscriberCount: metadata.subscriberCount ?? null,
|
||||
});
|
||||
|
||||
// Notify scheduler of new channel
|
||||
|
|
|
|||
|
|
@ -47,12 +47,23 @@ export class SoundCloudSource implements PlatformSource {
|
|||
? (thumbnails[thumbnails.length - 1]?.url ?? 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 {
|
||||
name: channelName,
|
||||
platformId: uploaderId,
|
||||
imageUrl,
|
||||
url: uploaderUrl,
|
||||
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;
|
||||
|
||||
// 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
|
||||
? (thumbnails[thumbnails.length - 1]?.url ?? 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 {
|
||||
name: channelName,
|
||||
platformId: channelId,
|
||||
imageUrl,
|
||||
url: channelUrl,
|
||||
platform: Platform.YouTube,
|
||||
bannerUrl,
|
||||
description,
|
||||
subscriberCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export class YtDlpError extends Error {
|
|||
readonly stderr: string;
|
||||
readonly exitCode: number;
|
||||
readonly isRateLimit: boolean;
|
||||
readonly category: YtDlpErrorCategory;
|
||||
|
||||
constructor(message: string, stderr: string, exitCode: number) {
|
||||
super(message);
|
||||
|
|
@ -33,6 +34,7 @@ export class YtDlpError extends Error {
|
|||
this.stderr = stderr;
|
||||
this.exitCode = exitCode;
|
||||
this.isRateLimit = detectRateLimit(stderr);
|
||||
this.category = classifyYtDlpError(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,6 +285,8 @@ export type YtDlpErrorCategory =
|
|||
| 'age_restricted' // age-gated content
|
||||
| 'private' // private or removed video
|
||||
| 'network' // DNS, connection, timeout
|
||||
| 'sign_in_required' // sign-in or login required
|
||||
| 'copyright' // copyright claim or block
|
||||
| 'unknown';
|
||||
|
||||
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('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('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';
|
||||
|
||||
return 'unknown';
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ export interface PlatformSourceMetadata {
|
|||
imageUrl: string | null;
|
||||
url: string;
|
||||
platform: Platform;
|
||||
bannerUrl?: string | null;
|
||||
description?: string | null;
|
||||
subscriberCount?: number | null;
|
||||
}
|
||||
|
||||
/** Metadata for a single piece of content from a platform. */
|
||||
|
|
@ -70,6 +73,9 @@ export interface Channel {
|
|||
metadata: Record<string, unknown> | null;
|
||||
formatProfileId: number | null;
|
||||
monitoringMode: MonitoringMode;
|
||||
bannerUrl: string | null;
|
||||
description: string | null;
|
||||
subscriberCount: number | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastCheckedAt: string | null;
|
||||
|
|
@ -113,6 +119,7 @@ export interface QueueItem {
|
|||
attempts: number;
|
||||
maxAttempts: number;
|
||||
error: string | null;
|
||||
errorCategory: string | null;
|
||||
startedAt: string | null;
|
||||
completedAt: string | null;
|
||||
createdAt: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue