diff --git a/src/components/color-theme/ColorThemePopoverButton.tsx b/src/components/color-theme/ColorThemePopoverButton.tsx
index 2430b07..aedb471 100644
--- a/src/components/color-theme/ColorThemePopoverButton.tsx
+++ b/src/components/color-theme/ColorThemePopoverButton.tsx
@@ -14,6 +14,7 @@ import { Slider } from "../ui/slider";
import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
import { EnableCustomVideoPlayerToggle } from "./EnableCustomVideoPlayerToggle";
import { ShowShortcutsToggle } from "./ShowShortcutsToggle";
+import { ShowArticleStyleToggle } from "./ShowArticleStyleToggle";
function getCssVariable(name: string) {
const value = window
@@ -208,6 +209,8 @@ export function ColorThemePopoverButton({
+
+
>
)}
@@ -229,6 +232,8 @@ export function ColorThemeDropdownSidebar({
+
+
);
}
diff --git a/src/components/color-theme/ShowArticleStyleToggle.tsx b/src/components/color-theme/ShowArticleStyleToggle.tsx
new file mode 100644
index 0000000..3175cd5
--- /dev/null
+++ b/src/components/color-theme/ShowArticleStyleToggle.tsx
@@ -0,0 +1,32 @@
+import { Label } from "../ui/label";
+import { useFlagState } from "~/lib/hooks/useFlagState";
+import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
+
+export const ShowArticleStyleToggle = () => {
+ const [articleStyle, setArticleStyle] = useFlagState("ARTICLE_STYLE");
+
+ return (
+
+
+
+
+ Simplified
+
+
+ Full
+
+
+
+ );
+};
diff --git a/src/lib/hooks/useFlagState.ts b/src/lib/hooks/useFlagState.ts
index bfce0ac..e02c5ec 100644
--- a/src/lib/hooks/useFlagState.ts
+++ b/src/lib/hooks/useFlagState.ts
@@ -14,6 +14,10 @@ const LOCAL_STORAGE_FLAGS = {
key: "serial-flag-display-inline-shortcuts",
schema: z.enum(["show-shortcuts", "hide-shortcuts"]),
},
+ ARTICLE_STYLE: {
+ key: "serial-flag-article-style",
+ schema: z.enum(["simplified", "full"]),
+ },
} as const;
type FlagName = keyof typeof LOCAL_STORAGE_FLAGS;
type FlagSchema
= (typeof LOCAL_STORAGE_FLAGS)[T]["schema"];
@@ -43,6 +47,7 @@ const flagsAtom = atom({
parseFlagLocalStorageValue("CUSTOM_VIDEO_PLAYER") ?? "serial",
INLINE_SHORTCUTS:
parseFlagLocalStorageValue("INLINE_SHORTCUTS") ?? "show-shortcuts",
+ ARTICLE_STYLE: parseFlagLocalStorageValue("ARTICLE_STYLE") ?? "simplified",
} as FlagsState);
export function useFlagState(key: TKey) {
diff --git a/src/server/api/routers/feedItemRouter.ts b/src/server/api/routers/feedItemRouter.ts
index 972a830..19634f4 100644
--- a/src/server/api/routers/feedItemRouter.ts
+++ b/src/server/api/routers/feedItemRouter.ts
@@ -58,6 +58,7 @@ export const feedItemRouter = createTRPCRouter({
return {
feedId: feed.id,
contentId: item.id,
+ content: item.content ?? "",
title: item.title ?? "",
author: item.author ?? "",
thumbnail: item.thumbnail ?? "",
diff --git a/src/server/api/routers/feedRouter.ts b/src/server/api/routers/feedRouter.ts
index ba5b89a..05f742a 100644
--- a/src/server/api/routers/feedRouter.ts
+++ b/src/server/api/routers/feedRouter.ts
@@ -102,6 +102,7 @@ export const feedRouter = createTRPCRouter({
name: channel.title,
platform: "youtube",
url: channel.feedUrl,
+ imageUrl: "",
}));
if (!feedsToAdd.length) return;
diff --git a/src/server/db/migrations/0014_stormy_redwing.sql b/src/server/db/migrations/0014_stormy_redwing.sql
new file mode 100644
index 0000000..da6cb81
--- /dev/null
+++ b/src/server/db/migrations/0014_stormy_redwing.sql
@@ -0,0 +1 @@
+ALTER TABLE `serial_feed` ADD `image_url` text(512) DEFAULT '' NOT NULL;
\ No newline at end of file
diff --git a/src/server/db/migrations/0015_funny_joshua_kane.sql b/src/server/db/migrations/0015_funny_joshua_kane.sql
new file mode 100644
index 0000000..f124ca2
--- /dev/null
+++ b/src/server/db/migrations/0015_funny_joshua_kane.sql
@@ -0,0 +1 @@
+ALTER TABLE `serial_feed_item` ADD `content` text DEFAULT '' NOT NULL;
\ No newline at end of file
diff --git a/src/server/db/migrations/0016_sloppy_glorian.sql b/src/server/db/migrations/0016_sloppy_glorian.sql
new file mode 100644
index 0000000..9bedc5d
--- /dev/null
+++ b/src/server/db/migrations/0016_sloppy_glorian.sql
@@ -0,0 +1 @@
+ALTER TABLE `serial_feed_item` ADD `id` text;
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/0014_snapshot.json b/src/server/db/migrations/meta/0014_snapshot.json
new file mode 100644
index 0000000..a7dcb77
--- /dev/null
+++ b/src/server/db/migrations/meta/0014_snapshot.json
@@ -0,0 +1,844 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "2327bf29-c06f-4617-807b-a29686a80108",
+ "prevId": "1a5f1fcf-f8ba-4db1-9ebc-d1193494ee01",
+ "tables": {
+ "serial_account": {
+ "name": "serial_account",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_account_user_id_serial_user_id_fk": {
+ "name": "serial_account_user_id_serial_user_id_fk",
+ "tableFrom": "serial_account",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_content_categories": {
+ "name": "serial_content_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "content_categories_name_idx": {
+ "name": "content_categories_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_categories": {
+ "name": "serial_feed_categories",
+ "columns": {
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_feed_categories_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_categories_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_feed_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_feed_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_categories_feed_id_category_id_pk": {
+ "columns": [
+ "feed_id",
+ "category_id"
+ ],
+ "name": "serial_feed_categories_feed_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_item": {
+ "name": "serial_feed_item",
+ "columns": {
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content_id": {
+ "name": "content_id",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnail": {
+ "name": "thumbnail",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "is_watched": {
+ "name": "is_watched",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_watch_later": {
+ "name": "is_watch_later",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "posted_at": {
+ "name": "posted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_item_feed_id_idx": {
+ "name": "feed_item_feed_id_idx",
+ "columns": [
+ "feed_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "serial_feed_item_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_item_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_item",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_item_feed_id_content_id_pk": {
+ "columns": [
+ "feed_id",
+ "content_id"
+ ],
+ "name": "serial_feed_item_feed_id_content_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed": {
+ "name": "serial_feed",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'youtube'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_name_idx": {
+ "name": "feed_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_session": {
+ "name": "serial_session",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_session_token_unique": {
+ "name": "serial_session_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "serial_session_user_id_serial_user_id_fk": {
+ "name": "serial_session_user_id_serial_user_id_fk",
+ "tableFrom": "serial_session",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user": {
+ "name": "serial_user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_user_email_unique": {
+ "name": "serial_user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user_config": {
+ "name": "serial_user_config",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "light_hsl": {
+ "name": "light_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "dark_hsl": {
+ "name": "dark_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_verification": {
+ "name": "serial_verification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_view_categories": {
+ "name": "serial_view_categories",
+ "columns": {
+ "view_id": {
+ "name": "view_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_view_categories_view_id_serial_views_id_fk": {
+ "name": "serial_view_categories_view_id_serial_views_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_views",
+ "columnsFrom": [
+ "view_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_view_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_view_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_view_categories_view_id_category_id_pk": {
+ "columns": [
+ "view_id",
+ "category_id"
+ ],
+ "name": "serial_view_categories_view_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_views": {
+ "name": "serial_views",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "days_window": {
+ "name": "days_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "read_status": {
+ "name": "read_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": -1
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'horizontal'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "view_name_idx": {
+ "name": "view_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/0015_snapshot.json b/src/server/db/migrations/meta/0015_snapshot.json
new file mode 100644
index 0000000..d09f1fc
--- /dev/null
+++ b/src/server/db/migrations/meta/0015_snapshot.json
@@ -0,0 +1,852 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "f9c2162a-fb4f-4976-b211-21e6f3edfae1",
+ "prevId": "2327bf29-c06f-4617-807b-a29686a80108",
+ "tables": {
+ "serial_account": {
+ "name": "serial_account",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_account_user_id_serial_user_id_fk": {
+ "name": "serial_account_user_id_serial_user_id_fk",
+ "tableFrom": "serial_account",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_content_categories": {
+ "name": "serial_content_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "content_categories_name_idx": {
+ "name": "content_categories_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_categories": {
+ "name": "serial_feed_categories",
+ "columns": {
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_feed_categories_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_categories_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_feed_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_feed_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_categories_feed_id_category_id_pk": {
+ "columns": [
+ "feed_id",
+ "category_id"
+ ],
+ "name": "serial_feed_categories_feed_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_item": {
+ "name": "serial_feed_item",
+ "columns": {
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content_id": {
+ "name": "content_id",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnail": {
+ "name": "thumbnail",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "is_watched": {
+ "name": "is_watched",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_watch_later": {
+ "name": "is_watch_later",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "posted_at": {
+ "name": "posted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_item_feed_id_idx": {
+ "name": "feed_item_feed_id_idx",
+ "columns": [
+ "feed_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "serial_feed_item_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_item_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_item",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_item_feed_id_content_id_pk": {
+ "columns": [
+ "feed_id",
+ "content_id"
+ ],
+ "name": "serial_feed_item_feed_id_content_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed": {
+ "name": "serial_feed",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'youtube'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_name_idx": {
+ "name": "feed_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_session": {
+ "name": "serial_session",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_session_token_unique": {
+ "name": "serial_session_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "serial_session_user_id_serial_user_id_fk": {
+ "name": "serial_session_user_id_serial_user_id_fk",
+ "tableFrom": "serial_session",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user": {
+ "name": "serial_user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_user_email_unique": {
+ "name": "serial_user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user_config": {
+ "name": "serial_user_config",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "light_hsl": {
+ "name": "light_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "dark_hsl": {
+ "name": "dark_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_verification": {
+ "name": "serial_verification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_view_categories": {
+ "name": "serial_view_categories",
+ "columns": {
+ "view_id": {
+ "name": "view_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_view_categories_view_id_serial_views_id_fk": {
+ "name": "serial_view_categories_view_id_serial_views_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_views",
+ "columnsFrom": [
+ "view_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_view_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_view_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_view_categories_view_id_category_id_pk": {
+ "columns": [
+ "view_id",
+ "category_id"
+ ],
+ "name": "serial_view_categories_view_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_views": {
+ "name": "serial_views",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "days_window": {
+ "name": "days_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "read_status": {
+ "name": "read_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": -1
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'horizontal'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "view_name_idx": {
+ "name": "view_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/0016_snapshot.json b/src/server/db/migrations/meta/0016_snapshot.json
new file mode 100644
index 0000000..8d70aa1
--- /dev/null
+++ b/src/server/db/migrations/meta/0016_snapshot.json
@@ -0,0 +1,859 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "c4f42446-335e-46ec-a111-0876c059416f",
+ "prevId": "f9c2162a-fb4f-4976-b211-21e6f3edfae1",
+ "tables": {
+ "serial_account": {
+ "name": "serial_account",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_account_user_id_serial_user_id_fk": {
+ "name": "serial_account_user_id_serial_user_id_fk",
+ "tableFrom": "serial_account",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_content_categories": {
+ "name": "serial_content_categories",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "content_categories_name_idx": {
+ "name": "content_categories_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_categories": {
+ "name": "serial_feed_categories",
+ "columns": {
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_feed_categories_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_categories_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_feed_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_feed_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_feed_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_categories_feed_id_category_id_pk": {
+ "columns": [
+ "feed_id",
+ "category_id"
+ ],
+ "name": "serial_feed_categories_feed_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed_item": {
+ "name": "serial_feed_item",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content_id": {
+ "name": "content_id",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thumbnail": {
+ "name": "thumbnail",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "is_watched": {
+ "name": "is_watched",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_watch_later": {
+ "name": "is_watch_later",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "posted_at": {
+ "name": "posted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_item_feed_id_idx": {
+ "name": "feed_item_feed_id_idx",
+ "columns": [
+ "feed_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "serial_feed_item_feed_id_serial_feed_id_fk": {
+ "name": "serial_feed_item_feed_id_serial_feed_id_fk",
+ "tableFrom": "serial_feed_item",
+ "tableTo": "serial_feed",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_feed_item_feed_id_content_id_pk": {
+ "columns": [
+ "feed_id",
+ "content_id"
+ ],
+ "name": "serial_feed_item_feed_id_content_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_feed": {
+ "name": "serial_feed",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'youtube'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "feed_name_idx": {
+ "name": "feed_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_session": {
+ "name": "serial_session",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_session_token_unique": {
+ "name": "serial_session_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "serial_session_user_id_serial_user_id_fk": {
+ "name": "serial_session_user_id_serial_user_id_fk",
+ "tableFrom": "serial_session",
+ "tableTo": "serial_user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user": {
+ "name": "serial_user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "serial_user_email_unique": {
+ "name": "serial_user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_user_config": {
+ "name": "serial_user_config",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "light_hsl": {
+ "name": "light_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "dark_hsl": {
+ "name": "dark_hsl",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_verification": {
+ "name": "serial_verification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_view_categories": {
+ "name": "serial_view_categories",
+ "columns": {
+ "view_id": {
+ "name": "view_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "serial_view_categories_view_id_serial_views_id_fk": {
+ "name": "serial_view_categories_view_id_serial_views_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_views",
+ "columnsFrom": [
+ "view_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "serial_view_categories_category_id_serial_content_categories_id_fk": {
+ "name": "serial_view_categories_category_id_serial_content_categories_id_fk",
+ "tableFrom": "serial_view_categories",
+ "tableTo": "serial_content_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "serial_view_categories_view_id_category_id_pk": {
+ "columns": [
+ "view_id",
+ "category_id"
+ ],
+ "name": "serial_view_categories_view_id_category_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "serial_views": {
+ "name": "serial_views",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "days_window": {
+ "name": "days_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "read_status": {
+ "name": "read_status",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": -1
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "text(16)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'horizontal'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "view_name_idx": {
+ "name": "view_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json
index 5ac5910..ec216cb 100644
--- a/src/server/db/migrations/meta/_journal.json
+++ b/src/server/db/migrations/meta/_journal.json
@@ -99,6 +99,27 @@
"when": 1745184500672,
"tag": "0013_brainy_logan",
"breakpoints": true
+ },
+ {
+ "idx": 14,
+ "version": "6",
+ "when": 1756339571169,
+ "tag": "0014_stormy_redwing",
+ "breakpoints": true
+ },
+ {
+ "idx": 15,
+ "version": "6",
+ "when": 1756340471840,
+ "tag": "0015_funny_joshua_kane",
+ "breakpoints": true
+ },
+ {
+ "idx": 16,
+ "version": "6",
+ "when": 1756349707304,
+ "tag": "0016_sloppy_glorian",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts
index cb91824..d9b30da 100644
--- a/src/server/db/schema.ts
+++ b/src/server/db/schema.ts
@@ -20,6 +20,7 @@ import {
VIEW_READ_STATUS,
viewReadStatusSchema,
} from "./constants";
+import { createId } from "@paralleldrive/cuid2";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
@@ -92,6 +93,7 @@ export const feeds = sqliteTable(
userId: text("user_id").notNull().default(""),
name: text("name", { length: 256 }).notNull().default(""),
url: text("url", { length: 512 }).notNull().default(""),
+ imageUrl: text("image_url", { length: 512 }).notNull().default(""),
platform: text("platform", { length: 256 }).notNull().default("youtube"),
createdAt: integer("created_at", { mode: "timestamp" })
.$default(() => new Date())
@@ -102,7 +104,7 @@ export const feeds = sqliteTable(
},
(example) => [index("feed_name_idx").on(example.name)],
);
-export const platformsSchema = z.enum(["youtube", "peertube"]);
+export const platformsSchema = z.enum(["youtube", "peertube", "website"]);
export type FeedPlatform = z.infer;
export const feedsSchema = createSelectSchema(feeds).merge(
@@ -115,12 +117,14 @@ export type DatabaseFeed = typeof feeds.$inferSelect;
export const feedItems = sqliteTable(
"feed_item",
{
+ id: text("id").$defaultFn(() => createId()),
feedId: integer("feed_id").references(() => feeds.id),
contentId: text("content_id", { length: 512 }).notNull(),
title: text("title", { length: 512 }).notNull(),
author: text("author", { length: 512 }).notNull(),
url: text("url", { length: 512 }).notNull(),
thumbnail: text("thumbnail", { length: 512 }).notNull().default(""),
+ content: text("content").notNull().default(""),
isWatched: integer("is_watched", { mode: "boolean" })
.notNull()
.default(false),
diff --git a/src/server/rss/fetchFeeds.ts b/src/server/rss/fetchFeeds.ts
index d7e0b23..bb52733 100644
--- a/src/server/rss/fetchFeeds.ts
+++ b/src/server/rss/fetchFeeds.ts
@@ -1,6 +1,7 @@
import type { DatabaseFeed } from "../db/schema";
import { fetchPeerTubeFeedData } from "./parsers/peertube";
import { fetchUnknownRssFeed } from "./parsers/unknown";
+import { fetchWebsiteFeedData } from "./parsers/website";
import {
fetchYouTubeFeedData,
fetchYouTubeFeedDetails,
@@ -53,6 +54,9 @@ export async function fetchFeedData(
if (feed.platform === "peertube") {
return fetchPeerTubeFeedData(feed);
}
+ if (feed.platform === "website") {
+ return fetchWebsiteFeedData(feed);
+ }
return null;
}),
)
diff --git a/src/server/rss/new-feed/fetchNewFeedDetails.ts b/src/server/rss/new-feed/fetchNewFeedDetails.ts
deleted file mode 100644
index 42b201d..0000000
--- a/src/server/rss/new-feed/fetchNewFeedDetails.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { fetchYouTubeFeedDetails } from "../parsers/youtube";
-import { type NewFeedDetails } from "../types";
-
-export async function fetchNewFeedDetails(
- url: string,
-): Promise {
- if (url.includes("youtube.com")) {
- return fetchYouTubeFeedDetails(url);
- }
- return null;
-}
diff --git a/src/server/rss/parsers/unknown.ts b/src/server/rss/parsers/unknown.ts
index 1ebbfa4..0837076 100644
--- a/src/server/rss/parsers/unknown.ts
+++ b/src/server/rss/parsers/unknown.ts
@@ -1,5 +1,6 @@
import type { NewFeedDetails } from "../types";
import { getPeerTubeFeedIfMatches } from "./peertube";
+import { getWebsiteFeedIfMatches } from "./website";
export async function fetchUnknownRssFeed(
url: string,
@@ -11,6 +12,9 @@ export async function fetchUnknownRssFeed(
const peerTubeFeed = await getPeerTubeFeedIfMatches(text);
if (peerTubeFeed) return peerTubeFeed;
+ const websiteFeed = await getWebsiteFeedIfMatches(text, url);
+ if (websiteFeed) return websiteFeed;
+
return null;
} catch (e) {
console.error(e);
diff --git a/src/server/rss/parsers/website.ts b/src/server/rss/parsers/website.ts
new file mode 100644
index 0000000..40c2278
--- /dev/null
+++ b/src/server/rss/parsers/website.ts
@@ -0,0 +1,114 @@
+import Parser from "rss-parser";
+import { z } from "zod";
+import type { DatabaseFeed } from "~/server/db/schema";
+import { isWithinDays } from "../rssUtils";
+import type { NewFeedDetails, RSSContent, RSSFeed } from "../types";
+
+const parser = new Parser({
+ customFields: {
+ item: [],
+ },
+});
+
+export const websiteItemSchema = z.object({
+ creator: z.string(),
+ title: z.string(),
+ link: z.string(),
+ pubDate: z.string().optional(),
+ "content:encoded": z.string().optional(),
+ content: z.string(),
+ contentSnippet: z.string().optional(),
+ guid: z.string(),
+ isoDate: z.string().optional(),
+ updated: z.string().optional(),
+});
+
+export const websiteSchema = z.object({
+ items: websiteItemSchema.array(),
+ image: z
+ .object({
+ link: z.string(),
+ url: z.string(),
+ title: z.string(),
+ })
+ .optional(),
+ title: z.string(),
+ description: z.string().optional(),
+ generator: z.string().optional(),
+ link: z.string(),
+ lastBuildDate: z.string().optional(),
+ ttl: z.string().optional(),
+});
+
+export async function getWebsiteFeedIfMatches(
+ rssString: string,
+ url: string,
+): Promise {
+ const rssData = await parser.parseString(rssString); //as unknown as RSSPeerTubeData;
+
+ console.log(rssData);
+ const {
+ data: websiteData,
+ success: websiteSuccess,
+ error,
+ } = websiteSchema.safeParse(rssData);
+
+ console.log(error?.flatten().formErrors);
+ console.log(error?.flatten().fieldErrors);
+
+ if (websiteSuccess) {
+ return {
+ url: url,
+ platform: "website",
+ name: websiteData.title,
+ imageUrl: websiteData.image?.url,
+ };
+ } else {
+ console.error(error);
+ }
+
+ return null;
+}
+
+export async function fetchWebsiteFeedData(
+ feed: DatabaseFeed,
+): Promise {
+ try {
+ const feedResponse = await fetch(feed.url);
+ const text = await feedResponse.text();
+ const rssData = await parser.parseString(text);
+
+ const data = websiteSchema.parse(rssData);
+
+ const itemPromises = data.items
+ .filter((item) =>
+ isWithinDays(item?.pubDate || item?.isoDate || item?.updated || "", 60),
+ )
+ .map(async (item) => {
+ const idParts = item.guid.split("/");
+ const id = idParts[idParts.length - 1];
+
+ if (!id) return null;
+
+ return {
+ id,
+ title: item.title,
+ publishedDate: item?.pubDate || item?.isoDate || item?.updated || "",
+ url: item.link,
+ author: item.creator,
+ content: item["content:encoded"],
+ } satisfies RSSContent;
+ });
+
+ return {
+ id: feed.id,
+ title: data.title,
+ url: data.link,
+ items: (await Promise.all(itemPromises)).filter(Boolean),
+ };
+ } catch (e) {
+ console.error("Error fetching website feed data for URL =", feed.url);
+ console.error(e);
+ return null;
+ }
+}
diff --git a/src/server/rss/validateFeedUrl.ts b/src/server/rss/validateFeedUrl.ts
index fcb7305..91cb34e 100644
--- a/src/server/rss/validateFeedUrl.ts
+++ b/src/server/rss/validateFeedUrl.ts
@@ -1,13 +1,30 @@
-const SUPPORTED_IF_INCLUDES = [
+import type { FeedPlatform } from "../db/schema";
+
+const YOUTUBE_URL_SEGMENTS = [
"https://youtube.com/@",
"https://www.youtube.com/@",
"https://youtube.com/channel/",
"https://www.youtube.com/channel/",
"https://www.youtube.com/feeds/videos.xml?channel_id=",
- "/feeds/videos.xml?accountId=", // PeerTube
- "/feeds/videos.xml?videoChannelId=", // PeerTube
];
-export function validateFeedUrl(url: string) {
- return SUPPORTED_IF_INCLUDES.some((supported) => url.includes(supported));
+const PEERTUBE_URL_SEGMENTS = [
+ "/feeds/videos.xml?accountId=",
+ "/feeds/videos.xml?videoChannelId=",
+];
+
+export const FEED_PLATFORM_LABEL_MAP = {
+ website: "Website",
+ youtube: "YouTube",
+ peertube: "PeerTube",
+} as const satisfies Record;
+
+export function getAssumedFeedPlatform(url: string): FeedPlatform {
+ if (YOUTUBE_URL_SEGMENTS.some((supported) => url.includes(supported))) {
+ return "youtube";
+ }
+ if (PEERTUBE_URL_SEGMENTS.some((supported) => url.includes(supported))) {
+ return "peertube";
+ }
+ return "website";
}
diff --git a/src/server/scripts/addIdsToFeedItems.ts b/src/server/scripts/addIdsToFeedItems.ts
new file mode 100644
index 0000000..be00eed
--- /dev/null
+++ b/src/server/scripts/addIdsToFeedItems.ts
@@ -0,0 +1,35 @@
+import "dotenv/config";
+
+import * as schema from "~/server/db/schema";
+import { eq, isNull } from "drizzle-orm";
+import { db } from "../db";
+
+import { createId } from "@paralleldrive/cuid2";
+
+async function migrate() {
+ let feedItemsMigrated = 0;
+
+ await db.transaction(async (tx) => {
+ const feedItems = await tx
+ .select()
+ .from(schema.feedItems)
+ .where(isNull(schema.feedItems.id))
+ .all();
+
+ return await Promise.all(
+ feedItems.map(async (feedItem) => {
+ feedItemsMigrated += 1;
+ return await tx
+ .update(schema.feedItems)
+ .set({
+ id: createId(),
+ })
+ .where(eq(schema.feedItems.contentId, feedItem.contentId));
+ }),
+ );
+ });
+
+ console.log(`Migrated ${feedItemsMigrated} rows!`);
+}
+
+migrate();