diff --git a/src/db/migrations/0008_jittery_piledriver.sql b/src/db/migrations/0008_jittery_piledriver.sql new file mode 100644 index 0000000..a33f905 --- /dev/null +++ b/src/db/migrations/0008_jittery_piledriver.sql @@ -0,0 +1 @@ +ALTER TABLE "articles" ADD COLUMN "created_at" timestamp; \ No newline at end of file diff --git a/src/db/migrations/0009_opposite_slayback.sql b/src/db/migrations/0009_opposite_slayback.sql new file mode 100644 index 0000000..e2e1cb3 --- /dev/null +++ b/src/db/migrations/0009_opposite_slayback.sql @@ -0,0 +1 @@ +ALTER TABLE "articles" ALTER COLUMN "created_at" SET DEFAULT now(); \ No newline at end of file diff --git a/src/db/migrations/0010_same_famine.sql b/src/db/migrations/0010_same_famine.sql new file mode 100644 index 0000000..1d36722 --- /dev/null +++ b/src/db/migrations/0010_same_famine.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS "signups" ( + "email" text NOT NULL, + "site_id" uuid NOT NULL, + "created_at" timestamp DEFAULT now() +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "signups" ADD CONSTRAINT "signups_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "sites"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/src/db/migrations/0011_serious_tenebrous.sql b/src/db/migrations/0011_serious_tenebrous.sql new file mode 100644 index 0000000..ec054dd --- /dev/null +++ b/src/db/migrations/0011_serious_tenebrous.sql @@ -0,0 +1 @@ +ALTER TABLE "articles" ADD COLUMN "is_public" boolean; \ No newline at end of file diff --git a/src/db/migrations/0012_nervous_penance.sql b/src/db/migrations/0012_nervous_penance.sql new file mode 100644 index 0000000..714431b --- /dev/null +++ b/src/db/migrations/0012_nervous_penance.sql @@ -0,0 +1 @@ +ALTER TABLE "articles" ALTER COLUMN "is_public" SET DEFAULT true; \ No newline at end of file diff --git a/src/db/migrations/meta/0008_snapshot.json b/src/db/migrations/meta/0008_snapshot.json new file mode 100644 index 0000000..1bde36b --- /dev/null +++ b/src/db/migrations/meta/0008_snapshot.json @@ -0,0 +1,227 @@ +{ + "id": "8177d5c3-dd38-41fc-83ae-a544aa8dc9a4", + "prevId": "10737e50-695f-41a3-94b4-e34439850a06", + "version": "5", + "dialect": "pg", + "tables": { + "articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/0009_snapshot.json b/src/db/migrations/meta/0009_snapshot.json new file mode 100644 index 0000000..0e72f4e --- /dev/null +++ b/src/db/migrations/meta/0009_snapshot.json @@ -0,0 +1,228 @@ +{ + "id": "35af811f-9d91-4524-b269-c7e3f8eb0ce8", + "prevId": "8177d5c3-dd38-41fc-83ae-a544aa8dc9a4", + "version": "5", + "dialect": "pg", + "tables": { + "articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/0010_snapshot.json b/src/db/migrations/meta/0010_snapshot.json new file mode 100644 index 0000000..29bc35b --- /dev/null +++ b/src/db/migrations/meta/0010_snapshot.json @@ -0,0 +1,271 @@ +{ + "id": "55d4edf3-4ec8-4c89-ae81-2c9199efea11", + "prevId": "35af811f-9d91-4524-b269-c7e3f8eb0ce8", + "version": "5", + "dialect": "pg", + "tables": { + "articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "signups": { + "name": "signups", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signups_site_id_sites_id_fk": { + "name": "signups_site_id_sites_id_fk", + "tableFrom": "signups", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/0011_snapshot.json b/src/db/migrations/meta/0011_snapshot.json new file mode 100644 index 0000000..202cbae --- /dev/null +++ b/src/db/migrations/meta/0011_snapshot.json @@ -0,0 +1,277 @@ +{ + "id": "bd993656-45ee-4b6d-9875-e47a5674f46e", + "prevId": "55d4edf3-4ec8-4c89-ae81-2c9199efea11", + "version": "5", + "dialect": "pg", + "tables": { + "articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "signups": { + "name": "signups", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signups_site_id_sites_id_fk": { + "name": "signups_site_id_sites_id_fk", + "tableFrom": "signups", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/0012_snapshot.json b/src/db/migrations/meta/0012_snapshot.json new file mode 100644 index 0000000..e5f933a --- /dev/null +++ b/src/db/migrations/meta/0012_snapshot.json @@ -0,0 +1,278 @@ +{ + "id": "06039986-7308-452a-9a12-12c317b3f75b", + "prevId": "bd993656-45ee-4b6d-9875-e47a5674f46e", + "version": "5", + "dialect": "pg", + "tables": { + "articles": { + "name": "articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_video_id": { + "name": "source_video_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seo_slug": { + "name": "seo_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "articles_site_id_sites_id_fk": { + "name": "articles_site_id_sites_id_fk", + "tableFrom": "articles", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "google_access_token": { + "name": "google_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_refresh_token": { + "name": "google_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "signups": { + "name": "signups", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "site_id": { + "name": "site_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signups_site_id_sites_id_fk": { + "name": "signups_site_id_sites_id_fk", + "tableFrom": "signups", + "tableTo": "sites", + "columnsFrom": [ + "site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "sites": { + "name": "sites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sites_user_id_users_id_fk": { + "name": "sites_user_id_users_id_fk", + "tableFrom": "sites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploads_playlist_id": { + "name": "uploads_playlist_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 022e498..367b22c 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -57,6 +57,41 @@ "when": 1714378789934, "tag": "0007_familiar_thor_girl", "breakpoints": true + }, + { + "idx": 8, + "version": "5", + "when": 1714739422527, + "tag": "0008_jittery_piledriver", + "breakpoints": true + }, + { + "idx": 9, + "version": "5", + "when": 1714739443139, + "tag": "0009_opposite_slayback", + "breakpoints": true + }, + { + "idx": 10, + "version": "5", + "when": 1714739875253, + "tag": "0010_same_famine", + "breakpoints": true + }, + { + "idx": 11, + "version": "5", + "when": 1714856795946, + "tag": "0011_serious_tenebrous", + "breakpoints": true + }, + { + "idx": 12, + "version": "5", + "when": 1714859887333, + "tag": "0012_nervous_penance", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schemas.js b/src/db/schemas.js index bb0da93..98dcfe0 100644 --- a/src/db/schemas.js +++ b/src/db/schemas.js @@ -1,4 +1,4 @@ -import { date, foreignKey, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { boolean, date, foreignKey, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; export const users = pgTable("users", { id: uuid("id").defaultRandom().primaryKey(), @@ -15,13 +15,13 @@ export const sessions = pgTable("sessions", { access_token: text("google_access_token"), refresh_token: text("google_refresh_token"), expires_at: timestamp("expires_at") -}) +}); export const sites = pgTable("sites", { id: uuid("id").defaultRandom().primaryKey(), user_id: uuid("user_id").references(() => users.id), name: text("name") -}) +}); export const articles = pgTable("articles", { id: uuid("id").defaultRandom().primaryKey(), @@ -29,5 +29,13 @@ export const articles = pgTable("articles", { content: text("content"), source_video_id: text("source_video_id"), title: text("title"), - seo_slug: text("seo_slug") -}) \ No newline at end of file + seo_slug: text("seo_slug"), + is_public: boolean("is_public").default(true), + created_at: timestamp("created_at").defaultNow() +}); + +export const signups = pgTable("signups", { + email: text("email").notNull(), + site_id: uuid("site_id").references(() => sites.id).notNull(), + created_at: timestamp("created_at").defaultNow() +}); \ No newline at end of file diff --git a/src/routes/blog.js b/src/routes/blog.js index c028637..d1a4aee 100644 --- a/src/routes/blog.js +++ b/src/routes/blog.js @@ -1,9 +1,10 @@ -import { eq } from "drizzle-orm"; +import { eq, getTableColumns, or } from "drizzle-orm"; import { db } from "../db/index.js"; import { authMiddleware, authMiddlewareFn } from "../modules/middleware.js"; import { getAccessToken, getCaptionText, getChannelInfo, getVideoCaptions, parseTextFromCaptions } from "../utils/youtube.js"; -import { articles as articlesTable, sites } from "../db/schemas.js"; +import { articles, articles as articlesTable, signups as signupsTable, sites, users } from "../db/schemas.js"; import { createBlogFromCaptions } from "../utils/ai.js"; +import { createArticleSlug } from "../utils/index.js"; /** * @@ -15,7 +16,7 @@ export const blogRoutes = (fastify, _, done) => { fastify.get("/", async (request, response) => { try { - const mine = request.query.mine != 'false' || true; + const mine = request.query.mine != 'false' ?? true; const blog_id = request.query.blog_id; if (!mine && !blog_id) { response.send({ @@ -24,30 +25,93 @@ export const blogRoutes = (fastify, _, done) => { }) } let clause; - + let site; if (mine) { if (!(await authMiddlewareFn(request, response))) return; - clause = eq(sites.user_id, request.session.user_id); + [site] = await db.select().from(sites).where(eq(sites.user_id, request.session.user_id)); + } + else { + [site] = await db.select().from(sites).where(eq(sites.id, request.query.blog_id)); } + + clause = eq(articlesTable.site_id, site.id); if (mine == false) { - clause = eq(sites.user_id, request.query.blog_id); + clause = eq(articlesTable.site_id, request.query.blog_id); } // const access_token = await getAccessToken(fastify, request); // const channel = await getChannelInfo(access_token); - - const articles = await db.select().from(articlesTable).leftJoin(sites, articlesTable.site_id == sites.id).where(clause); - + const results = await db.select({ id: articlesTable.id, title: articlesTable.title, seo_slug: articlesTable.seo_slug }).from(articlesTable).where(clause).limit(10); + // console.log(articles) response.send({ success: true, - articles + articles: results, + site, }); } catch (e) { console.log(e); } }); + fastify.get("/article", { + schema: { + querystring: { + id: { + type: "string" + } + } + } + }, async (req, reply) => { + const [result] = await db.select().from(articlesTable).where(or(eq(articlesTable.seo_slug, req.query.id), eq(articlesTable.id, req.query.id))); + + reply.send({ + success: true, + article: result + }); + }); + + fastify.put("/article", { + schema: { + body: { + type: "object", + properties: { + id: { + type: "string" + }, + is_public: { + type: "boolean" + }, + content: { + type: "string" + }, + title: { + type: "string" + }, + }, + required: ["id"] + } + } + }, async (req, reply) => { + const [article] = await db.select(getTableColumns(articles)).from(articles).leftJoin(sites, eq(sites.id, articles.site_id)).where(eq(sites.user_id, req.session.user_id)); + + if (!article) { + reply.status(404).send({ + success: false, + message: "This article does not exist." + }); + return; + } + + if (Object.keys(req.body).length > 1) { + await db.update(articles).set(JSON.parse(JSON.stringify({ ...req.body, id: undefined }))).where(eq(articles.id, req.body.id)).limit(1); + } + reply.send({ + success: true + }); + return; + }); + fastify.post("/create", { schema: { body: { @@ -74,30 +138,78 @@ export const blogRoutes = (fastify, _, done) => { const captions = await getVideoCaptions(access_token, req.body.video_id); const preferred_caption_id = captions.find(x => x.snippet.language == 'en').id; + reply.send({ + success: true + }); + const caption_body = await getCaptionText(access_token, preferred_caption_id); const caption_text = parseTextFromCaptions(caption_body).substring(28); - console.log(caption_text); const blog_content = await createBlogFromCaptions(caption_text, req.body); + const blog_content_json = JSON.parse(blog_content); // TODO: once I add multiple sites per user, this should come from the client const site = await db.select().from(sites).where(eq(sites.user_id, req.session.user_id)); - const article = await db.insert(articlesTable).values({ + await db.insert(articlesTable).values({ site_id: site[0].id, - content: blog_content, - source_video_id: req.body.video_id + title: blog_content_json.title, + content: blog_content_json.content, + source_video_id: req.body.video_id, + seo_slug: createArticleSlug(blog_content_json.title) }).returning({ id: articlesTable.id }); - - reply.send({ - success: true, - article_id: article[0].id - }); } catch (e) { console.log(e); } - }) + }); - fastify.register(authMiddleware); + fastify.get("/signups", { + preValidation: authMiddlewareFn + }, async (req, reply) => { + const site = await db.select().from(sites).where(eq(sites.user_id, req.session.user_id)); + + if (site[0]?.user_id != req.session.user_id) { + reply.status(403).send({ success: false }); + return; + } + + const signups = await db.select().from(signupsTable).where(eq(signupsTable.site_id, site[0].id)); + + reply.send({ + success: true, + signups + }); + }); + + fastify.post("/signup", { + schema: { + body: { + site_id: { + type: "string" + }, + email: { + type: "string", + format: "email" + } + } + } + }, async (req, reply) => { + const inserted = await db.insert(signupsTable).values({ + email: req.body.email, + site_id: req.body.site_id + }); + + if (inserted.rowCount != 1) { + reply.status(400).send({ + success: false, + message: "Problem when signing up email" + }); + return; + } + + reply.send({ + success: true + }); + }); done(); }; diff --git a/src/utils/ai.js b/src/utils/ai.js index 3fdad80..4b60e08 100644 --- a/src/utils/ai.js +++ b/src/utils/ai.js @@ -23,13 +23,14 @@ async function cf_prompt(prompt, model = defaultModel) { return res; } -async function promptGPT(prompt, model = "gpt-3-turbo") { +async function promptGPT(prompt, {model = "gpt-3.5-turbo", max_tokens = 1024}) { const options = { method: 'POST', - headers: { Authorization: 'Bearer pk-hevZzgTruHUMITZDvBqcIaLXWYtkxuTLkQkYecEfszfNHNBT', "Content-Type": "application/json" }, + headers: { Authorization: 'Bearer sk-proj-P3cSwYJjZgnSRzil9vsAT3BlbkFJ9mqryDKqGJ2p0rm3z557', "Content-Type": "application/json" }, body: JSON.stringify({ - "model": "pai-001-light", - "max_tokens": 512, + "model": "gpt-3.5-turbo", + "max_tokens": max_tokens, + "response_format": { "type": "json_object" }, "messages": [ { "role": "user", @@ -39,8 +40,8 @@ async function promptGPT(prompt, model = "gpt-3-turbo") { }) }; - console.log("prompting pai-001...") - const res = await fetch('https://api.pawan.krd/pai-001/v1/chat/completions', options) + console.log("prompting gpt-3.5-turbo...") + const res = await fetch('https://api.openai.com/v1/chat/completions', options) .then(response => { return response.json(); }) @@ -62,9 +63,16 @@ export async function createBlogFromCaptions(captions, { format, tone } = {length: 500, language: "English", format: "summary", tone: "informal"}) { - const prompt = `Please convert the following video transcript into a blog post. The approximate length should be around ${length || 500} characters, written in ${language || "English"}. The desired format of the blog post is a ${format || "summary"}. Please ensure the blog post has a ${tone || "informal"} tone throughout. Use markdown to format the article. You must always respond in the following json fromat: {"title": string, "content": string, "seo_friendly_slug": string}. \nHere is the transcript: ` - - const result = await promptGPT(prompt + captions); + // const prompt = `Convert the following video transcript into a blog post. The approximate length should be around ${length || 500} characters, written in ${language || "English"}. The desired format of the blog post is a ${format || "summary"}. Please ensure the blog post has a ${tone || "informal"} tone throughout. Use markdown to format the article. You must always respond in the following json fromat: {"title": string, "content": string, "seo_friendly_slug": string}. \nHere is the transcript: ` + const prompt = `Convert the following video transcript into an engaging blog post. You must always respond in the following json fromat: {"title": string, "content": string, "seo_friendly_slug": string}. Do not include the title inside the content, it should only be reserved for the body of the article. Use markdown to format the article. \nHere is the transcript: `; + + const result = await promptGPT(prompt + captions, { + length: wordsToTokens(length) + }); console.log(result); return result; +} + +function wordsToTokens(n) { + return Math.ceil(n/0.75); } \ No newline at end of file diff --git a/src/utils/index.js b/src/utils/index.js index a064837..361466a 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,3 +1,45 @@ export * from "./env.js"; export * from "./logger.js"; export * from "./redis.js"; +export * from "./ai.js"; +export * from "./token.js"; +export * from "./youtube.js"; + + +/** + * + * @param {string} title + * @returns {string} + */ +export function createArticleSlug(title) { + const maxLength = 20; + const slug = title.toLowerCase().replace(/[^a-z0-9\s-]/g, '').trim().replace(/\s+/g, '-'); + const randomChars = "-" + generateRandomChars(6); + + // If slug is shorter than or equal to 12 characters, return it directly + if (slug.length <= maxLength) { + return slug + randomChars; + } + + // Find the last space within the first 12 characters + const lastSpaceIndex = slug.substring(0, maxLength).lastIndexOf('-'); + + // If no space found, truncate slug to 12 characters + const truncatedSlug = lastSpaceIndex !== -1 ? slug.substring(0, lastSpaceIndex) : slug.substring(0, maxLength); + + // Generate 6 random characters + + // Concatenate the truncated slug and random characters + const finalSlug = truncatedSlug + randomChars; + + return finalSlug; +} + +function generateRandomChars(length) { + let result = ''; + const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; +}