Podcast workflow: assign lanes by hand, match by season, unique keys for unseasoned episodes; drop old checklist data file; v2.16.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac1b06f945
commit
900a3efee3
6 files changed
+69
-698
No files matched your search
@@ -55,3 +55,7 @@ data/study-community.json
|
|||||||
data/analytics-events.json
|
data/analytics-events.json
|
||||||
data/admin-reply-history.json
|
data/admin-reply-history.json
|
||||||
data/episode-plays.json
|
data/episode-plays.json
|
||||||
|
|
||||||
|
# Episode workflow progress — lives only in the server data volume
|
||||||
|
data/podcast-workflow.json
|
||||||
|
data/nuggets-workflow.json
|
||||||
@@ -1,681 +0,0 @@
|
|||||||
{
|
|
||||||
"checklist": {
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"id": "verify_script",
|
|
||||||
"label": "Verify Script",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "read_script",
|
|
||||||
"label": "Read Script",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "record",
|
|
||||||
"label": "Record",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "edit",
|
|
||||||
"label": "Edit",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mix",
|
|
||||||
"label": "Mix",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "video_script",
|
|
||||||
"label": "Run Video Conversion Script",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post_spotify",
|
|
||||||
"label": "Post on Spotify",
|
|
||||||
"phase": "pre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "update_website",
|
|
||||||
"label": "Update Website",
|
|
||||||
"phase": "post"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "send_email",
|
|
||||||
"label": "Send Email",
|
|
||||||
"phase": "post"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"episodes": [
|
|
||||||
{
|
|
||||||
"id": "titus-11",
|
|
||||||
"series": "Titus",
|
|
||||||
"episodeNumber": 11,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "titus-12",
|
|
||||||
"series": "Titus",
|
|
||||||
"episodeNumber": 12,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "titus-13",
|
|
||||||
"series": "Titus",
|
|
||||||
"episodeNumber": 13,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "titus-14",
|
|
||||||
"series": "Titus",
|
|
||||||
"episodeNumber": 14,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "titus-15",
|
|
||||||
"series": "Titus",
|
|
||||||
"episodeNumber": 15,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-1",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 1,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-2",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 2,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-3",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 3,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-4",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 4,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-5",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 5,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-6",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 6,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-7",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 7,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-8",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 8,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-9",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 9,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-10",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 10,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-11",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 11,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-12",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 12,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-13",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 13,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-14",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 14,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-15",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 15,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-16",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 16,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-17",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 17,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-18",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 18,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-19",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 19,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-20",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 20,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-21",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 21,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-22",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 22,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-23",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 23,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-24",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 24,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-25",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 25,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-26",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 26,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "colossians-27",
|
|
||||||
"series": "Colossians",
|
|
||||||
"episodeNumber": 27,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "episode-mpb66z1c",
|
|
||||||
"series": "Titus -10",
|
|
||||||
"episodeNumber": 0,
|
|
||||||
"title": "",
|
|
||||||
"datePublished": "",
|
|
||||||
"expanded": false,
|
|
||||||
"tasks": {
|
|
||||||
"verify_script": false,
|
|
||||||
"read_script": false,
|
|
||||||
"record": false,
|
|
||||||
"edit": false,
|
|
||||||
"mix": false,
|
|
||||||
"video_script": false,
|
|
||||||
"post_spotify": false,
|
|
||||||
"update_website": false,
|
|
||||||
"send_email": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"updatedAt": "2026-05-18T12:18:35.130Z"
|
|
||||||
}
|
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"version": "2.15.2",
|
"version": "2.16.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"version": "2.15.2",
|
"version": "2.16.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.1135.0",
|
"@aws-sdk/client-s3": "^3.1135.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.15.2",
|
"version": "2.16.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ const STEPS = ['readScript', 'record', 'edit', 'upload']
|
|||||||
async function load() {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(await readFile(WORKFLOW_FILE, 'utf8'))
|
const data = JSON.parse(await readFile(WORKFLOW_FILE, 'utf8'))
|
||||||
return { planned: Array.isArray(data.planned) ? data.planned : [], items: data.items ?? {} }
|
return { planned: Array.isArray(data.planned) ? data.planned : [], items: data.items ?? {}, laneOverrides: data.laneOverrides ?? {} }
|
||||||
} catch {
|
} catch {
|
||||||
return { planned: [], items: {} }
|
return { planned: [], items: {}, laneOverrides: {} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +37,9 @@ function laneFor(season) {
|
|||||||
return 'other'
|
return 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normTitle = t => String(t ?? '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()
|
||||||
|
const plannedKey = p => p.key ?? scriptKey(p.season, p.episodeNumber)
|
||||||
|
|
||||||
function toIsoDate(pubDate) {
|
function toIsoDate(pubDate) {
|
||||||
const d = new Date(pubDate)
|
const d = new Date(pubDate)
|
||||||
return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10)
|
return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10)
|
||||||
@@ -65,17 +68,25 @@ export function register(app) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (const p of wf.planned) {
|
for (const p of wf.planned) {
|
||||||
const key = scriptKey(p.season, p.episodeNumber)
|
const key = plannedKey(p)
|
||||||
const prev = byKey.get(key)
|
const prev = byKey.get(key)
|
||||||
upsert(key, { season: p.season, episodeNumber: p.episodeNumber, title: prev?.title || p.title || '', planned: true })
|
upsert(key, { season: p.season, episodeNumber: p.episodeNumber, title: prev?.title || p.title || '', planned: true, plannedLane: p.lane })
|
||||||
}
|
}
|
||||||
for (const item of feed) {
|
for (const item of feed) {
|
||||||
const hasNum = /^\d+$/.test(item.episode)
|
const hasNum = /^\d+$/.test(item.episode)
|
||||||
const key = hasNum ? scriptKey(item.season, item.episode) : `t:${item.title}`
|
let key
|
||||||
|
if (item.season && hasNum) {
|
||||||
|
key = scriptKey(item.season, item.episode)
|
||||||
|
} else {
|
||||||
|
// No season in the feed: pair with a script/planned episode of the same title, else keep it on its own.
|
||||||
|
const t = normTitle(item.title)
|
||||||
|
const match = [...byKey.values()].find(e => !e.released && normTitle(e.title) === t)
|
||||||
|
key = match ? match.key : `t:${t}`
|
||||||
|
}
|
||||||
const prev = byKey.get(key)
|
const prev = byKey.get(key)
|
||||||
upsert(key, {
|
upsert(key, {
|
||||||
season: item.season,
|
season: item.season || prev?.season || '',
|
||||||
episodeNumber: hasNum ? item.episode : '',
|
episodeNumber: hasNum ? item.episode : (prev?.episodeNumber ?? ''),
|
||||||
title: item.title || prev?.title || '',
|
title: item.title || prev?.title || '',
|
||||||
released: true,
|
released: true,
|
||||||
releaseDate: toIsoDate(item.pubDate),
|
releaseDate: toIsoDate(item.pubDate),
|
||||||
@@ -85,7 +96,8 @@ export function register(app) {
|
|||||||
|
|
||||||
const items = [...byKey.values()].map(e => ({
|
const items = [...byKey.values()].map(e => ({
|
||||||
...e,
|
...e,
|
||||||
lane: laneFor(e.season),
|
lane: wf.laneOverrides[e.key] ?? e.plannedLane ?? laneFor(e.season),
|
||||||
|
laneOverride: wf.laneOverrides[e.key] ?? '',
|
||||||
released: Boolean(e.released),
|
released: Boolean(e.released),
|
||||||
state: { ...emptyState(), ...(wf.items[e.key] ?? {}) },
|
state: { ...emptyState(), ...(wf.items[e.key] ?? {}) },
|
||||||
}))
|
}))
|
||||||
@@ -101,10 +113,12 @@ export function register(app) {
|
|||||||
res.status(400).json({ message: 'Episode number must be a whole number.' })
|
res.status(400).json({ message: 'Episode number must be a whole number.' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const lane = ['A', 'B'].includes(req.body?.lane) ? req.body.lane : 'other'
|
||||||
const wf = await load()
|
const wf = await load()
|
||||||
const key = scriptKey(season, episodeNumber)
|
// Without a season number the key must still be unique per lane.
|
||||||
if (!wf.planned.some(p => scriptKey(p.season, p.episodeNumber) === key)) {
|
const key = season ? scriptKey(season, episodeNumber) : `${lane}-p${episodeNumber}`
|
||||||
wf.planned.push({ season, episodeNumber, title })
|
if (!wf.planned.some(p => plannedKey(p) === key)) {
|
||||||
|
wf.planned.push({ key, season, episodeNumber, title, lane })
|
||||||
await save(wf)
|
await save(wf)
|
||||||
}
|
}
|
||||||
res.json({ ok: true, key })
|
res.json({ ok: true, key })
|
||||||
@@ -112,8 +126,23 @@ export function register(app) {
|
|||||||
|
|
||||||
app.delete('/api/admin/podcast-workflow/planned/:key', requireAdminAuth, async (req, res) => {
|
app.delete('/api/admin/podcast-workflow/planned/:key', requireAdminAuth, async (req, res) => {
|
||||||
const wf = await load()
|
const wf = await load()
|
||||||
wf.planned = wf.planned.filter(p => scriptKey(p.season, p.episodeNumber) !== req.params.key)
|
wf.planned = wf.planned.filter(p => plannedKey(p) !== req.params.key)
|
||||||
delete wf.items[req.params.key]
|
delete wf.items[req.params.key]
|
||||||
|
delete wf.laneOverrides[req.params.key]
|
||||||
|
await save(wf)
|
||||||
|
res.json({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assign an episode to a lane by hand ('auto' clears the override and goes back to season matching).
|
||||||
|
app.put('/api/admin/podcast-workflow/:key/lane', requireAdminAuth, async (req, res) => {
|
||||||
|
const lane = req.body?.lane
|
||||||
|
if (!['A', 'B', 'other', 'auto'].includes(lane)) {
|
||||||
|
res.status(400).json({ message: 'Lane must be A, B, other or auto.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const wf = await load()
|
||||||
|
if (lane === 'auto') delete wf.laneOverrides[req.params.key]
|
||||||
|
else wf.laneOverrides[req.params.key] = lane
|
||||||
await save(wf)
|
await save(wf)
|
||||||
res.json({ ok: true })
|
res.json({ ok: true })
|
||||||
})
|
})
|
||||||
|
|||||||
+21
-2
@@ -1816,6 +1816,7 @@ interface PodcastWorkflowItem {
|
|||||||
episodeNumber: string
|
episodeNumber: string
|
||||||
title: string
|
title: string
|
||||||
lane: 'A' | 'B' | 'other'
|
lane: 'A' | 'B' | 'other'
|
||||||
|
laneOverride?: '' | 'A' | 'B' | 'other'
|
||||||
released: boolean
|
released: boolean
|
||||||
releaseDate?: string
|
releaseDate?: string
|
||||||
spotifyUrl?: string
|
spotifyUrl?: string
|
||||||
@@ -1874,7 +1875,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
const r = await fetch('/api/admin/podcast-workflow/planned', {
|
const r = await fetch('/api/admin/podcast-workflow/planned', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ season: laneSeason ? String(laneSeason) : '', episodeNumber: newNumber, title: newTitle }),
|
body: JSON.stringify({ season: laneSeason ? String(laneSeason) : '', episodeNumber: newNumber, title: newTitle, lane }),
|
||||||
})
|
})
|
||||||
const d = await r.json()
|
const d = await r.json()
|
||||||
if (!r.ok) { setError(d.message ?? 'Could not add episode.'); return }
|
if (!r.ok) { setError(d.message ?? 'Could not add episode.'); return }
|
||||||
@@ -1882,6 +1883,15 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
await reload()
|
await reload()
|
||||||
setSelectedKey(d.key)
|
setSelectedKey(d.key)
|
||||||
}
|
}
|
||||||
|
const setLaneFor = async (key: string, value: string) => {
|
||||||
|
await fetch(`/api/admin/podcast-workflow/${encodeURIComponent(key)}/lane`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ lane: value }),
|
||||||
|
})
|
||||||
|
setSelectedKey('')
|
||||||
|
reload()
|
||||||
|
}
|
||||||
const removePlanned = async (key: string) => {
|
const removePlanned = async (key: string) => {
|
||||||
if (!confirm('Remove this planned episode and its progress?')) return
|
if (!confirm('Remove this planned episode and its progress?')) return
|
||||||
await fetch(`/api/admin/podcast-workflow/planned/${encodeURIComponent(key)}`, { method: 'DELETE' })
|
await fetch(`/api/admin/podcast-workflow/planned/${encodeURIComponent(key)}`, { method: 'DELETE' })
|
||||||
@@ -1932,7 +1942,7 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{lane !== 'other' && !laneSeason && (
|
{lane !== 'other' && !laneSeason && (
|
||||||
<p className="admin-stats-note">Set this lane’s Podcast Season Number in Current Series so its episodes can be matched.</p>
|
<p className="admin-stats-note">Set this lane’s Podcast Season Number in Current Series to match episodes automatically, or assign episodes to this lane by hand.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loading ? <p className="admin-stats-note">Loading…</p> : (
|
{loading ? <p className="admin-stats-note">Loading…</p> : (
|
||||||
@@ -1970,6 +1980,15 @@ function PodcastWorkflowAdmin({ laneA, laneB }: { laneA: { title: string; season
|
|||||||
{selected.hasScript ? 'Script uploaded.' : 'No script yet.'} This moves to Released on its own once it shows up in the feed.
|
{selected.hasScript ? 'Script uploaded.' : 'No script yet.'} This moves to Released on its own once it shows up in the feed.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
<div className="admin-field" style={{ maxWidth: 360, margin: '8px 0 12px' }}>
|
||||||
|
<label htmlFor="pw-lane">Lane</label>
|
||||||
|
<select id="pw-lane" value={selected.laneOverride || 'auto'} onChange={e => setLaneFor(selected.key, e.target.value)}>
|
||||||
|
<option value="auto">Automatic (by season number{selected.season ? ` ${selected.season}` : ''})</option>
|
||||||
|
<option value="A">Lane A — {laneA.title || 'Primary series'}</option>
|
||||||
|
<option value="B">Lane B — {laneB.title || 'Second series'}</option>
|
||||||
|
<option value="other">Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<ol style={{ paddingLeft: 0, listStyle: 'none', margin: 0 }}>
|
<ol style={{ paddingLeft: 0, listStyle: 'none', margin: 0 }}>
|
||||||
{PODCAST_STEP_DEFS.map((s, idx) => {
|
{PODCAST_STEP_DEFS.map((s, idx) => {
|
||||||
const completedAt = selected.state.steps[s.key]
|
const completedAt = selected.state.steps[s.key]
|
||||||
|
|||||||
Reference in new issue
Block a user