Compare commits

..

137 Commits

Author SHA1 Message Date
bb8b532afc dashboard should only show finished articles 2024-08-28 15:39:35 +02:00
cf20c4c725 updated AI model to gpt-4o 2024-08-28 15:35:25 +02:00
647d6fc6cb comment out auto_publish code 2024-08-26 20:37:23 +02:00
1f10d5fc95 remove free 200 tokens 2024-08-25 19:04:28 +02:00
18f1f2afed remove channel info stuff 2024-08-25 18:56:36 +02:00
557ea7ede5 removed youtube scope 2024-08-25 18:39:24 +02:00
e222cec776 made contact email optional 2024-08-25 15:02:35 +02:00
a458d20679 fixed mail 2024-08-24 13:24:00 +02:00
d61b0a767a removed scope 2024-08-14 15:50:44 +02:00
895fb12140 bugfix 2024-08-14 15:47:41 +02:00
661f93350f fix 2024-08-10 23:53:55 +02:00
f75783569e cookies for ytdl 2024-08-10 23:43:15 +02:00
d56fc025f2 added yarn.lock 2024-08-10 17:09:25 +02:00
bb78997490 revert captions to yt auto-gen 2024-08-09 20:08:39 +02:00
ac57317a55 possible maybe fix 2024-07-24 23:18:12 +02:00
ab207c8ec6 kill me 2024-07-22 22:40:32 +02:00
1fefda4322 update ytdl 2024-07-22 22:37:36 +02:00
50566ba4ef bump node version 2024-07-22 21:00:58 +02:00
4c7d3e44e3 decrease tokens per month 2024-07-21 17:39:34 +02:00
600186ae42 cookies 2024-07-21 17:13:01 +02:00
8ff80c76f3 kill me 2024-07-21 15:52:28 +02:00
4c401c469f aghh 2024-07-21 15:51:47 +02:00
797bc86e19 update fix maybe 2024-07-21 15:51:09 +02:00
eed90d8bc0 updates 2024-07-21 15:37:23 +02:00
8c26d78afb billing 2024-07-08 01:39:46 +02:00
4248cadbf0 payment updates innit 2024-07-07 09:06:46 +02:00
3564d6c763 finalized contact email 2024-06-25 21:34:52 +02:00
94bd46a1f9 updates n shit 2024-06-24 18:59:11 +02:00
3e4ae8c1df contact email + route 2024-06-24 10:34:30 +02:00
3af979bf42 fixed disconnecting 2024-06-24 09:38:49 +02:00
583e1f4705 fixed signups 2024-06-22 11:51:21 +02:00
b4bae7460f added queue 2024-06-22 01:12:21 +02:00
e2fa79c3d1 added source_id to queue 2024-06-21 16:33:05 +02:00
cf0d463e46 fix again lol 2024-06-21 16:27:13 +02:00
6081f56b85 fix 2024-06-21 16:25:25 +02:00
fca37022d7 returning queue on blog endpoint 2024-06-19 20:03:51 +02:00
3bf83041ff updated db 2024-06-19 20:03:30 +02:00
ba5e89e892 removed unnecessary code 2024-06-19 19:27:03 +02:00
fbdabf0a39 fix 2024-06-14 10:49:45 +02:00
e8325bce03 fix epoch 2024-06-14 10:45:51 +02:00
7d5326af79 agag 2024-06-14 10:43:39 +02:00
c85c075d1d pubsub_expiry date to timestamp 2024-06-14 10:38:21 +02:00
2d40082ed9 fixx 2024-06-14 10:34:50 +02:00
d720a7f836 fixxx 2024-06-14 10:33:58 +02:00
778972d926 fix 2024-06-14 10:33:01 +02:00
ad35def57b maybe 2024-06-14 10:30:54 +02:00
72ede98188 added lease_seconds to db 2024-06-14 10:29:07 +02:00
c4ef4b35c4 update prompt 2024-06-13 22:57:25 +02:00
4ae120d2ce fix webhook 2024-06-13 22:33:40 +02:00
9ad2437a79 cut early if no video 2024-06-13 22:28:33 +02:00
b66e27d49b debug 2024-06-13 22:25:19 +02:00
b96c2d6546 added sentry 2024-06-13 22:16:17 +02:00
666af9b4db updated prompt 2024-06-13 22:16:11 +02:00
dfe23fffcc bugfix 2024-06-13 14:54:39 +02:00
e6a8ef5f9e added logic for auto-publish 2024-06-13 14:45:43 +02:00
8412cbc572 fix xml parser 2024-06-13 14:45:06 +02:00
b4c44e7622 fix createBlogFromCaptions function default values 2024-06-13 14:44:55 +02:00
bfe1c4fe0a bro wtf 2024-06-13 14:14:50 +02:00
84a9673759 fixed verification 2024-06-13 12:14:29 +02:00
5208119964 fixed parser and webhook 2024-06-13 12:05:19 +02:00
33aadc41d0 bugfix 2024-06-13 11:34:50 +02:00
4c0101b981 bugfix 2024-06-13 11:32:57 +02:00
58be9ad126 new content parser 2024-06-13 11:27:17 +02:00
83db716fcb bugfix 2024-06-13 10:58:14 +02:00
bec8892735 webhooks 2024-06-13 10:29:49 +02:00
ffec31e4d4 set max db connections to 1 2024-06-11 22:18:04 +02:00
2b699899df fix 2024-06-11 22:14:05 +02:00
abac46ceae debug 2024-06-11 22:12:55 +02:00
1aec9dc089 v3 prompt 2024-06-11 21:58:48 +02:00
1b82a3ba48 updated prompt 2024-06-11 21:48:24 +02:00
72fec82ff6 oopsie daisy 2024-06-11 21:37:50 +02:00
ffdf58e818 fix maybe 2024-06-11 21:33:41 +02:00
23ce45e16f pubsubhubbub test 2024-06-11 21:11:18 +02:00
7f72636af2 fix logout 2024-06-11 18:36:10 +02:00
edc3822936 temp tokens for testers 2024-06-10 21:23:53 +02:00
3da2300226 thing 2024-06-10 21:11:17 +02:00
c53d4f6e84 new captions system 2024-06-10 00:14:41 +02:00
f1bbf852e2 bugfix 2024-06-08 21:08:59 +02:00
1a8b81cb53 edge-case 2024-06-08 20:43:13 +02:00
6377de28d1 update 2024-06-08 15:20:24 +02:00
39918459ab tx 2024-06-08 15:12:39 +02:00
b5424fc83b debug 2024-06-08 15:02:37 +02:00
434cf71010 db config 2024-06-08 15:00:11 +02:00
b3d7e4ee5d fuck me 2024-06-08 14:53:45 +02:00
bcff5c0787 ff 2024-06-08 14:43:35 +02:00
4c8fdcc84e move over to neon db 2024-06-08 14:36:42 +02:00
818de51743 hhhh 2024-06-08 14:31:04 +02:00
fce1dbe5b4 update connection 2024-06-08 09:36:52 +02:00
0fbc03f1ed maybe??? 2024-06-07 22:43:16 +02:00
15ee0deb66 agaega 2024-06-07 22:41:26 +02:00
67b7849b39 fix error code 2024-06-07 22:27:46 +02:00
ab00a416e8 fix 2024-06-07 22:23:30 +02:00
34fe0dc034 fix 2024-06-07 22:00:08 +02:00
de8099aa20 removing id from db update 2024-06-07 21:37:59 +02:00
48bae83521 added nullable params to website 2024-06-07 21:34:52 +02:00
89f44aad7d fixes 2024-06-07 17:18:44 +02:00
3871396339 fixfixfix 2024-06-07 15:27:28 +02:00
fba83c3943 gonna kms 2024-06-07 15:26:20 +02:00
555ade8285 wtf I'm dumb 2024-06-07 15:21:49 +02:00
3808aaa8da fa 2024-06-07 15:20:33 +02:00
2c8a97fbd0 aefa 2024-06-07 15:12:47 +02:00
948651b398 af 2024-06-07 15:10:51 +02:00
09205ea28d fea 2024-06-07 15:06:52 +02:00
86e8fb64ee idk at this point bro 2024-06-07 14:39:26 +02:00
57404f7def fix 2024-06-07 14:38:09 +02:00
096b4f64d4 debug 2024-06-07 14:35:02 +02:00
54ed89cdd8 idk maybe 2024-06-07 14:33:19 +02:00
9ebaece0a2 dafok 2024-06-07 14:31:46 +02:00
c333b9aaec bro wtf 2024-06-07 14:26:59 +02:00
0484c1dfc7 aefaeff 2024-06-07 12:39:11 +02:00
f8af4a5df7 calling certs service only when domain is changed 2024-06-02 10:47:01 +02:00
d80df18f4f removed debug script 2024-06-02 10:32:48 +02:00
159770e793 maybe?? 2024-06-02 10:30:56 +02:00
8c892f42e9 I'm tired... ok 2024-06-02 02:38:40 +02:00
a9260b2867 ughhh 2024-06-02 02:36:39 +02:00
768afb9d06 idk 2024-05-30 15:24:47 +02:00
b4cb2be739 updated prompt and shit 2024-05-30 15:21:55 +02:00
86b43e5811 added coalesce to totalViews 2024-05-30 12:24:10 +02:00
2915123f71 updated env vars 2024-05-30 09:17:59 +02:00
a51529042e wtf I have no idea 2024-05-29 23:08:37 +02:00
565ac26123 redirect 2024-05-29 22:17:41 +02:00
375f32dafb readded migration script 2024-05-29 22:07:10 +02:00
7ede9a8f8a fix imports 2024-05-29 21:55:32 +02:00
0887e16dcb bruh 2024-05-29 21:47:16 +02:00
95675b90bf dockerfile 2024-05-29 21:38:01 +02:00
ac54af5610 lotsa shit 2024-05-29 21:35:18 +02:00
5247feb40a faded as a hoe 2024-05-14 00:08:00 +02:00
d373a8c26e lots o shit 2024-05-12 22:05:06 +02:00
02278a99d9 fixed GET article route 2024-05-07 18:54:45 +02:00
561404faf1 world 2024-05-07 18:38:55 +02:00
6cb3d035e6 ... 2024-05-06 18:53:30 +02:00
933db8828b bds 2024-05-02 12:57:43 +02:00
00fb06aaad ara 2024-04-28 20:58:31 +02:00
6b47ffaf8c progress init 2024-04-26 18:40:30 +02:00
08fff603b5 update 2024-04-24 22:49:59 +02:00
adeb965f3e updated example 2024-04-22 00:47:22 +02:00
29086b57fd initial commit 2024-04-22 00:41:38 +02:00
65 changed files with 23391 additions and 1349 deletions

View File

@ -2,3 +2,17 @@ DATABASE_URL=postgres://user:password@127.0.0.1:5432/postgres
REDIS_URL=redis://127.0.0.1:6379/ REDIS_URL=redis://127.0.0.1:6379/
PORT=8080 PORT=8080
HOST=127.0.0.1 HOST=127.0.0.1
PUBLIC_API_URL=http://localhost:3000
FRONTEND_URL=http://localhost:3002
SITES_HOST=http://localhost:3001
GOOGLE_CLIENT_ID=
GOOGLE_SECRET=
OPENAI_TOKEN=
CERTS_URL=
STRIPE_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_SUBSCRIPTION_PRICE_ID=

21
Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM node:20-alpine
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+) and yarn.lock because we are using yarn
COPY package*.json yarn.lock ./
# Run yarn without generating a yarn.lock file
RUN yarn --pure-lockfile
# Bundle app source
COPY . ./
# Use the port used by our server.js configuration
EXPOSE 3000
# This will run `yarn start` when the docker image is ran
CMD [ "yarn", "start" ]

12
drizzle.config.js Normal file
View File

@ -0,0 +1,12 @@
import * as dotenv from 'dotenv'
dotenv.config()
export default {
"out": "./src/db/migrations",
"schema": "./src/db/schemas.js",
"dbCredentials": {
"url": process.env.DATABASE_URL
},
"dialect": "postgresql"
}

View File

@ -1,4 +0,0 @@
{
"out": "./src/db/migrations",
"schema": "./src/db/schemas.ts"
}

10518
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,20 @@
"license": "none", "license": "none",
"scripts": { "scripts": {
"lint": "xo", "lint": "xo",
"dev": "tsx watch --clear-screen=false src/index.ts", "dev": "tsx watch --clear-screen=false src/index.js",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"migrate": "drizzle-kit generate:pg", "migrate": "drizzle-kit generate:pg",
"build": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"start": "node src/index.js" "start": "node src/index.js"
}, },
"devDependencies": { "devDependencies": {
"@types/cli-color": "^2.0.6", "@types/cli-color": "^2.0.6",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/pg": "^8.10.9", "@types/pg": "^8.10.9",
"@types/stripe-v3": "^3.1.33",
"cli-color": "^2.0.3", "cli-color": "^2.0.3",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"drizzle-kit": "^0.20.6", "drizzle-kit": "^0.22.4",
"pg": "^8.12.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"tsc-alias": "^1.8.8", "tsc-alias": "^1.8.8",
"tsx": "^3.12.7", "tsx": "^3.12.7",
@ -29,13 +30,25 @@
"xo": "^0.56.0" "xo": "^0.56.0"
}, },
"dependencies": { "dependencies": {
"@distube/ytdl-core": "^4.14.4",
"@fastify/cookie": "^9.3.1",
"@fastify/cors": "^8.4.2", "@fastify/cors": "^8.4.2",
"drizzle-orm": "^0.29.1", "@fastify/multipart": "^8.2.0",
"@fastify/oauth2": "^7.8.0",
"@neondatabase/serverless": "^0.9.3",
"@sentry/node": "^8.9.2",
"drizzle-orm": "^0.31.1",
"fastify": "^4.25.0", "fastify": "^4.25.0",
"fastify-plugin": "^4.5.1", "fastify-plugin": "^4.5.1",
"pg": "^8.11.3", "fastify-raw-body": "^4.3.0",
"googleapis": "^134.0.0",
"nodemailer": "^6.9.14",
"openai": "^4.38.5",
"redis": "^4.6.11", "redis": "^4.6.11",
"simple-get": "^4.0.1",
"stripe": "^15.7.0",
"tsc": "^2.0.4", "tsc": "^2.0.4",
"xml2js": "^0.6.2",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"xo": { "xo": {

View File

@ -2,39 +2,38 @@ import * as schema from "./schemas.js";
import { env, Logger } from "../utils/index.js"; import { env, Logger } from "../utils/index.js";
import { drizzle } from "drizzle-orm/node-postgres"; import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator"; import { migrate } from "drizzle-orm/node-postgres/migrator";
import pgpkg from 'pg'; import pg from 'pg'
const { Pool } = pgpkg; const { Pool } = pg
/** @type {ReturnType<typeof drizzle<typeof schema>>} */ /** @type {ReturnType<typeof drizzle<typeof schema>>} */
export let db; export let db;
export const initDb = async () => { export const initDb = async () => {
const pool = await new Pool({ const client = new Pool({
connectionString: env.DATABASE_URL, connectionString: env.DATABASE_URL,
}) keepAlive: true
.connect()
.then((client) => {
Logger.info("INIT", "Connected to database");
return client;
})
.catch((error) => {
Logger.error("INIT", `Failed to connect to database ${String(error)}}`);
throw new Error(`Failed to connect to database ${String(error)}`);
}); });
db = drizzle(pool, { client.on('error', e => {
console.error('Database error', e);
client = null;
});
await client.connect();
db = drizzle(client, {
schema, schema,
}); });
await migrate(db, {
migrationsFolder: "./src/db/migrations", // await migrate(db, {
}) // migrationsFolder: "./src/db/migrations",
.then(() => { // })
Logger.info("INIT", "Migrated database"); // .then(() => {
}) // Logger.info("INIT", "Migrated database");
.catch((error) => { // })
Logger.error("INIT", `Failed to migrate database ${String(error)}`); // .catch((error) => {
throw new Error(`Failed to migrate database ${String(error)}`); // Logger.error("INIT", `Failed to migrate database ${String(error)}`);
}); // throw new Error(`Failed to migrate database ${String(error)}`);
// });
}; };

View File

@ -0,0 +1,99 @@
-- Current sql file was generated after introspecting the database
-- If you want to run this migration please uncomment this code before executing migrations
/*
DO $$ BEGIN
CREATE TYPE "public"."subscription_tier" AS ENUM('free', 'basic', 'pro', 'enterprise');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "signups" (
"email" text NOT NULL,
"site_id" uuid NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"source" text
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text,
"email" text,
"channel_id" text,
"google_id" text,
"uploads_playlist_id" text,
"subscription_tier" "subscription_tier" DEFAULT 'free' NOT NULL,
"tokens" integer DEFAULT 0 NOT NULL,
"stripe_id" text
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "sites" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid,
"name" text,
"primary_color_hex" varchar(6) DEFAULT 'c4ced4'::character varying NOT NULL,
"secondary_color_hex" varchar(6) DEFAULT '27251f'::character varying NOT NULL,
"text_color_hex" varchar(6) DEFAULT 'ffffff'::character varying NOT NULL,
"title" text DEFAULT 'The best blog in the world!',
"subtitle" text DEFAULT 'Some extra info about the best blog in the world!',
"domain" text,
"send_freebie" boolean DEFAULT false,
"freebie_name" text DEFAULT '',
"freebie_url" text DEFAULT '',
"freebie_text" text DEFAULT '',
"freebie_image_url" text DEFAULT '',
"subdomain_slug" text,
"social_medias" jsonb
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "sessions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid,
"google_access_token" text,
"google_refresh_token" text,
"expires_at" timestamp
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "articles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"site_id" uuid,
"content" text,
"source_video_id" text,
"title" text,
"seo_slug" text,
"created_at" timestamp DEFAULT now(),
"is_public" boolean DEFAULT false,
"views" integer DEFAULT 0,
"seo_title" text,
"seo_description" text,
"excerp" text
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "waitlist" (
"email" text
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "signups" ADD CONSTRAINT "signups_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "sites" ADD CONSTRAINT "sites_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "articles" ADD CONSTRAINT "articles_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
*/

View File

@ -1,5 +0,0 @@
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
"full_name" text,
"phone" varchar(256)
);

View File

@ -0,0 +1,5 @@
ALTER TABLE "sites" ALTER COLUMN "primary_color_hex" SET DEFAULT 'c4ced4';--> statement-breakpoint
ALTER TABLE "sites" ALTER COLUMN "secondary_color_hex" SET DEFAULT '27251f';--> statement-breakpoint
ALTER TABLE "sites" ALTER COLUMN "text_color_hex" SET DEFAULT 'ffffff';--> statement-breakpoint
ALTER TABLE "sites" ALTER COLUMN "domain" SET DEFAULT null;--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "domainUniqueIndex" ON "sites" USING btree ((lower("domain"))) WHERE "sites"."domain" IS NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE "sites" ALTER COLUMN "domain" SET DEFAULT NULL;

View File

@ -0,0 +1,2 @@
DROP INDEX IF EXISTS "domainUniqueIndex";--> statement-breakpoint
ALTER TABLE "sites" ADD CONSTRAINT "domainUniqueIndex" UNIQUE NULLS NOT DISTINCT("domain");

View File

@ -0,0 +1,2 @@
ALTER TABLE "sites" DROP CONSTRAINT "domainUniqueIndex";--> statement-breakpoint
ALTER TABLE "sites" ADD CONSTRAINT "domainUniqueIndex" UNIQUE("domain");

View File

@ -0,0 +1,2 @@
ALTER TABLE "sites" DROP CONSTRAINT "domainUniqueIndex";--> statement-breakpoint
ALTER TABLE "sites" ADD CONSTRAINT "sites_domain_unique" UNIQUE("domain");

View File

@ -0,0 +1,2 @@
ALTER TABLE "users" ALTER COLUMN "tokens" SET DEFAULT 200;--> statement-breakpoint
ALTER TABLE "sites" ADD COLUMN "auto_publish" boolean DEFAULT false;

View File

@ -0,0 +1 @@
ALTER TABLE "sites" ADD COLUMN "pubsub_expiry" date;

View File

@ -0,0 +1 @@
ALTER TABLE "sites" ALTER COLUMN "pubsub_expiry" SET DATA TYPE timestamp;

View File

@ -0,0 +1,7 @@
DO $$ BEGIN
CREATE TYPE "public"."article_status" AS ENUM('queued', 'transcribing', 'generating', 'done');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "articles" ADD COLUMN "status" "article_status" DEFAULT 'done';

View File

@ -0,0 +1 @@
ALTER TABLE "articles" ALTER COLUMN "status" SET DEFAULT 'queued';

View File

@ -0,0 +1 @@
ALTER TYPE "article_status" ADD VALUE 'error';

View File

@ -0,0 +1,3 @@
ALTER TABLE "sites" ALTER COLUMN "freebie_name" SET DEFAULT 'Our newsletter';--> statement-breakpoint
ALTER TABLE "sites" ADD COLUMN "use_contact_page" boolean DEFAULT false;--> statement-breakpoint
ALTER TABLE "sites" ADD COLUMN "contact_email" text;

View File

@ -0,0 +1,2 @@
ALTER TABLE "sites" ADD COLUMN "use_about_page" boolean DEFAULT true;--> statement-breakpoint
ALTER TABLE "sites" ADD COLUMN "about_text" text;

View File

@ -0,0 +1,2 @@
ALTER TABLE "users" ADD COLUMN "subscribed_until" date DEFAULT '1970-01-01';--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "tokens_claimed" date;

View File

@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "trial_used" boolean DEFAULT false;

View File

@ -1,42 +1,452 @@
{ {
"version": "5", "id": "00000000-0000-0000-0000-000000000000",
"dialect": "pg", "prevId": "",
"id": "478200dd-a614-41dd-9cd1-f6368405417e", "version": "7",
"prevId": "00000000-0000-0000-0000-000000000000", "dialect": "postgresql",
"tables": { "tables": {
"users": { "public.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": true,
"default": "now()"
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"signups_site_id_sites_id_fk": {
"name": "signups_site_id_sites_id_fk",
"tableFrom": "signups",
"tableTo": "sites",
"schemaTo": "public",
"columnsFrom": [
"site_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.users": {
"name": "users", "name": "users",
"schema": "", "schema": "",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "serial", "type": "uuid",
"primaryKey": true, "primaryKey": true,
"notNull": true "notNull": true,
"default": "gen_random_uuid()"
}, },
"full_name": { "name": {
"name": "full_name", "name": "name",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"phone": { "email": {
"name": "phone", "name": "email",
"type": "varchar(256)", "type": "text",
"primaryKey": false,
"notNull": false
},
"channel_id": {
"name": "channel_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"google_id": {
"name": "google_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"uploads_playlist_id": {
"name": "uploads_playlist_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"stripe_id": {
"name": "stripe_id",
"type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {} "compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'::character varying"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'::character varying"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'::character varying"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"sites_user_id_users_id_fk": {
"name": "sites_user_id_users_id_fk",
"tableFrom": "sites",
"tableTo": "users",
"schemaTo": "public",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.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",
"schemaTo": "public",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.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()"
},
"is_public": {
"name": "is_public",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"articles_site_id_sites_id_fk": {
"name": "articles_site_id_sites_id_fk",
"tableFrom": "articles",
"tableTo": "sites",
"schemaTo": "public",
"columnsFrom": [
"site_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"values": [
"free",
"basic",
"pro",
"enterprise"
],
"schema": "public"
} }
}, },
"enums": {},
"schemas": {}, "schemas": {},
"_meta": { "_meta": {
"schemas": {}, "schemas": {},
"tables": {}, "tables": {},
"columns": {} "columns": {}
},
"internal": {
"tables": {}
} }
} }

View File

@ -0,0 +1,463 @@
{
"id": "4bc82cd1-68f2-4b03-bdc9-058a4f3ff395",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": null
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"domainUniqueIndex": {
"name": "domainUniqueIndex",
"columns": [
{
"expression": "(lower(\"domain\"))",
"asc": true,
"isExpression": true,
"nulls": "last"
}
],
"isUnique": true,
"where": "\"sites\".\"domain\" IS NOT NULL",
"concurrently": false,
"method": "btree",
"with": {}
}
},
"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": {}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,463 @@
{
"id": "ca4576ea-6a09-4a8d-afe2-ad00dd56505f",
"prevId": "4bc82cd1-68f2-4b03-bdc9-058a4f3ff395",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"domainUniqueIndex": {
"name": "domainUniqueIndex",
"columns": [
{
"expression": "(lower(\"domain\"))",
"asc": true,
"isExpression": true,
"nulls": "last"
}
],
"isUnique": true,
"where": "\"sites\".\"domain\" IS NOT NULL",
"concurrently": false,
"method": "btree",
"with": {}
}
},
"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": {}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,454 @@
{
"id": "a8542e87-bade-4eae-b740-8ca9d8429118",
"prevId": "ca4576ea-6a09-4a8d-afe2-ad00dd56505f",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"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": {
"domainUniqueIndex": {
"name": "domainUniqueIndex",
"nullsNotDistinct": true,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,454 @@
{
"id": "3332ba7a-6f32-4ee9-8c5a-61dfbe163bda",
"prevId": "a8542e87-bade-4eae-b740-8ca9d8429118",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"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": {
"domainUniqueIndex": {
"name": "domainUniqueIndex",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,454 @@
{
"id": "e3391040-c733-41df-ab35-ddb8c0bbec80",
"prevId": "3332ba7a-6f32-4ee9-8c5a-61dfbe163bda",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,461 @@
{
"id": "00d3bd2f-2c64-4026-982e-e67ce4a45a11",
"prevId": "e3391040-c733-41df-ab35-ddb8c0bbec80",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": 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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,467 @@
{
"id": "77f7612a-c9c8-4843-b0b6-09842cd9cff7",
"prevId": "00d3bd2f-2c64-4026-982e-e67ce4a45a11",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "date",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,467 @@
{
"id": "d4c8325d-683a-4d6d-bcf7-bf16892332cc",
"prevId": "77f7612a-c9c8-4843-b0b6-09842cd9cff7",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,485 @@
{
"id": "805686b7-a39a-4dd8-b119-37d73e86d686",
"prevId": "d4c8325d-683a-4d6d-bcf7-bf16892332cc",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'done'"
}
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,485 @@
{
"id": "9e95f80e-79e7-4af0-9a8e-327fb358ec26",
"prevId": "805686b7-a39a-4dd8-b119-37d73e86d686",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
}
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,486 @@
{
"id": "1b69d63a-4517-42bc-aa1f-0ae6ed4ac685",
"prevId": "9e95f80e-79e7-4af0-9a8e-327fb358ec26",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done",
"error"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,499 @@
{
"id": "dcc6e5bd-8fea-4462-8f49-5cfc09408ab7",
"prevId": "1b69d63a-4517-42bc-aa1f-0ae6ed4ac685",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Our newsletter'"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"use_contact_page": {
"name": "use_contact_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"contact_email": {
"name": "contact_email",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done",
"error"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,512 @@
{
"id": "de65dcf5-f6de-434b-80b6-ba199df3a96a",
"prevId": "dcc6e5bd-8fea-4462-8f49-5cfc09408ab7",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Our newsletter'"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"use_contact_page": {
"name": "use_contact_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"contact_email": {
"name": "contact_email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"use_about_page": {
"name": "use_about_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"about_text": {
"name": "about_text",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done",
"error"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,525 @@
{
"id": "674d04c7-a853-4f20-98ac-b92e33d6d017",
"prevId": "de65dcf5-f6de-434b-80b6-ba199df3a96a",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Our newsletter'"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"use_contact_page": {
"name": "use_contact_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"contact_email": {
"name": "contact_email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"use_about_page": {
"name": "use_about_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"about_text": {
"name": "about_text",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"subscribed_until": {
"name": "subscribed_until",
"type": "date",
"primaryKey": false,
"notNull": false,
"default": "'1970-01-01'"
},
"tokens_claimed": {
"name": "tokens_claimed",
"type": "date",
"primaryKey": false,
"notNull": false
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done",
"error"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,532 @@
{
"id": "5df89ef6-dcab-4661-87f4-ad81b88bf4f4",
"prevId": "674d04c7-a853-4f20-98ac-b92e33d6d017",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.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": false
},
"views": {
"name": "views",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"seo_title": {
"name": "seo_title",
"type": "text",
"primaryKey": false,
"notNull": false
},
"seo_description": {
"name": "seo_description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"excerp": {
"name": "excerp",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "article_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": false,
"default": "'queued'"
},
"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": {}
},
"public.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": {}
},
"public.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
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"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": {}
},
"public.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
},
"primary_color_hex": {
"name": "primary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'c4ced4'"
},
"secondary_color_hex": {
"name": "secondary_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'27251f'"
},
"text_color_hex": {
"name": "text_color_hex",
"type": "varchar(6)",
"primaryKey": false,
"notNull": true,
"default": "'ffffff'"
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'The best blog in the world!'"
},
"subtitle": {
"name": "subtitle",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Some extra info about the best blog in the world!'"
},
"domain": {
"name": "domain",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "NULL"
},
"send_freebie": {
"name": "send_freebie",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"freebie_name": {
"name": "freebie_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'Our newsletter'"
},
"freebie_url": {
"name": "freebie_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_text": {
"name": "freebie_text",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"freebie_image_url": {
"name": "freebie_image_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "''"
},
"subdomain_slug": {
"name": "subdomain_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"social_medias": {
"name": "social_medias",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"auto_publish": {
"name": "auto_publish",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"pubsub_expiry": {
"name": "pubsub_expiry",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"use_contact_page": {
"name": "use_contact_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"contact_email": {
"name": "contact_email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"use_about_page": {
"name": "use_about_page",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": true
},
"about_text": {
"name": "about_text",
"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": {
"sites_domain_unique": {
"name": "sites_domain_unique",
"nullsNotDistinct": false,
"columns": [
"domain"
]
}
}
},
"public.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
},
"stripe_id": {
"name": "stripe_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
},
"subscription_tier": {
"name": "subscription_tier",
"type": "subscription_tier",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"subscribed_until": {
"name": "subscribed_until",
"type": "date",
"primaryKey": false,
"notNull": false,
"default": "'1970-01-01'"
},
"tokens_claimed": {
"name": "tokens_claimed",
"type": "date",
"primaryKey": false,
"notNull": false
},
"trial_used": {
"name": "trial_used",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"tokens": {
"name": "tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 200
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"public.article_status": {
"name": "article_status",
"schema": "public",
"values": [
"queued",
"transcribing",
"generating",
"done",
"error"
]
},
"public.subscription_tier": {
"name": "subscription_tier",
"schema": "public",
"values": [
"free",
"basic",
"pro",
"enterprise"
]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -4,10 +4,115 @@
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "5", "version": "7",
"when": 1686356934306, "when": 1717763053901,
"tag": "0000_plain_killmonger", "tag": "0000_gorgeous_amazoness",
"breakpoints": false "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1717763195953,
"tag": "0001_big_sugar_man",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1717793012414,
"tag": "0002_perpetual_norman_osborn",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1717849103779,
"tag": "0003_organic_dark_beast",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1717852643512,
"tag": "0004_lively_frank_castle",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1717852812991,
"tag": "0005_smiling_pandemic",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1718267048369,
"tag": "0006_happy_red_wolf",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1718353625117,
"tag": "0007_early_living_mummy",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1718354167998,
"tag": "0008_smart_captain_cross",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1718818623333,
"tag": "0009_wooden_weapon_omega",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1718818704691,
"tag": "0010_complex_malice",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1719007763731,
"tag": "0011_normal_thunderball",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1719133327271,
"tag": "0012_magenta_captain_marvel",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1719134353536,
"tag": "0013_lucky_vulture",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1719389425401,
"tag": "0014_tan_bill_hollister",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1719403126188,
"tag": "0015_nervous_victor_mancha",
"breakpoints": true
} }
] ]
} }

View File

@ -1,7 +1,94 @@
import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm";
import { boolean, date, integer, jsonb, pgEnum, pgTable, text, timestamp, unique, uniqueIndex, uuid, varchar } from "drizzle-orm/pg-core";
export const subscription_enum = pgEnum("subscription_tier", ["free", "basic", "pro", "enterprise"])
export const article_status_enum = pgEnum("article_status", ["queued", "transcribing", "generating", "done", "error"])
export const users = pgTable("users", { export const users = pgTable("users", {
id: serial("id").primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
fullName: text("full_name"), google_id: text("google_id"),
phone: varchar("phone", { length: 256 }), stripe_id: text("stripe_id"),
name: text("name"),
email: text("email"),
channel_id: text("channel_id"),
uploads_playlist_id: text("uploads_playlist_id"),
subscription_tier: subscription_enum("subscription_tier").default("free").notNull(),
subscribed_until: date("subscribed_until").default(`1970-01-01`),
tokens_claimed: date("tokens_claimed"),
trial_used: boolean("trial_used").default(false),
tokens: integer("tokens").notNull()
});
export const sessions = pgTable("sessions", {
id: uuid("id").defaultRandom().primaryKey(),
user_id: uuid("user_id").references(() => users.id),
access_token: text("google_access_token"),
refresh_token: text("google_refresh_token"),
expires_at: timestamp("expires_at")
});
export function makeid(length) {
let result = '';
const characters = 'abcdefghijklmnopqrstuvwxyz';
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
export const sites = pgTable("sites", {
id: uuid("id").defaultRandom().primaryKey(),
user_id: uuid("user_id").references(() => users.id),
name: text("name"),
primary_color_hex: varchar("primary_color_hex", { length: 6 }).default("c4ced4").notNull(),
secondary_color_hex: varchar("secondary_color_hex", { length: 6 }).default('27251f').notNull(),
text_color_hex: varchar("text_color_hex", { length: 6 }).default('ffffff').notNull(),
title: text("title").default("The best blog in the world!"),
subtitle: text("subtitle").default("Some extra info about the best blog in the world!"),
domain: text("domain").default(sql`NULL`).unique(),
use_freebie: boolean("send_freebie").default(false),
freebie_title: text("freebie_name").default("Our newsletter"),
freebie_url: text("freebie_url").default(""),
freebie_text: text("freebie_text").default(""),
freebie_image_url: text("freebie_image_url").default(""),
subdomain_slug: text("subdomain_slug").$defaultFn(() => {
return makeid(10);
}),
social_medias: jsonb("social_medias"),
auto_publish: boolean("auto_publish").default(false),
pubsub_expiry: timestamp("pubsub_expiry"),
use_contact_page: boolean("use_contact_page").default(false),
contact_email: text("contact_email"),
use_about_page: boolean("use_about_page").default(true),
about_text: text("about_text")
});
export const articles = pgTable("articles", {
id: uuid("id").defaultRandom().primaryKey(),
site_id: uuid("site_id").references(() => sites.id),
content: text("content"),
source_video_id: text("source_video_id"),
title: text("title"),
seo_slug: text("seo_slug"),
is_public: boolean("is_public").default(false),
views: integer("views").default(0),
meta_title: text("seo_title"),
meta_desc: text("seo_description"),
excerp: text("excerp"),
status: article_status_enum("status").default("queued"),
created_at: timestamp("created_at").defaultNow()
})
export const signups = pgTable("signups", {
email: text("email").notNull(),
site_id: uuid("site_id").references(() => sites.id).notNull(),
source: text("source"),
created_at: timestamp("created_at").defaultNow().notNull()
});
export const waitlist = pgTable("waitlist", {
email: text("email")
}); });

15
src/fastify.d.ts vendored
View File

@ -1,10 +1,21 @@
import {OAuth2Namespace} from "@fastify/oauth2";
import type { db } from "./db"; import type { db } from "./db";
import type { Redis } from "./utils"; import type { Redis } from "./utils";
declare module "fastify" { declare module "fastify" {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface FastifyRequest { interface FastifyRequest {
db: typeof db; // db: typeof db;
redis: typeof Redis; // redis: typeof Redis;
session?: {
id: string,
user_id: string,
access_token: string,
refresh_token: string,
expires_at: Date
}
}
interface FastifyInstance {
googleOAuth2: OAuth2Namespace
} }
} }

View File

@ -1,19 +1,49 @@
import { initDb } from "./db/index.js"; import { initDb } from "./db/index.js";
import { testRoutes } from "./routes/index.js"; import { channelRoutes, authRoutes, videoRoutes, meRoutes, blogRoutes, dashboardRoutes, webhookRoutes } from "./routes/index.js";
import { env, Logger, Redis } from "./utils/index.js"; import { env, Logger } from "./utils/index.js";
import fastify from "fastify"; import fastify from "fastify";
import { middleware } from "./modules/middleware.js"; import { middleware } from "./modules/middleware.js";
import oauth from '@fastify/oauth2';
import fastifyCookie from "@fastify/cookie";
import xml2js from 'xml2js';
import './utils/sentry.js';
// import fastifyMultipart from "@fastify/multipart";
import * as Sentry from "@sentry/node";
import rawBody from 'fastify-raw-body';
const API_VERSION = "v1"; const API_VERSION = "v1";
export const main = async () => { export const main = async () => {
const server = fastify({ const server = fastify({
bodyLimit: 1_000_000, bodyLimit: 1_000_000,
trustProxy: true, trustProxy: true,
// logger: true
}); });
Sentry.setupFastifyErrorHandler(server);
await initDb(); await initDb();
await Redis.initialize(); // await Redis.initialize();
// server.register(fastifyMultipart, {
// // attachFieldsToBody: true,
// });
server.register(fastifyCookie, {
secret: "my-secret", // for cookies signature
hook: 'preParsing', // set to false to disable cookie autoparsing or set autoparsing on any of the following hooks: 'onRequest', 'preParsing', 'preHandler', 'preValidation'. default: 'onRequest'
parseOptions: {}, // options for parsing cookies
});
server.register(rawBody, {
field: 'rawBody', // change the default request.rawBody property name
global: false, // add the rawBody to every request. **Default true**
encoding: 'utf8', // set it to false to set rawBody as a Buffer **Default utf8**
runFirst: true, // get the body before any preParsing hook change/uncompress it. **Default false**
routes: [], // array of routes, **`global`** will be ignored, wildcard routes not supported
jsonContentTypes: [], // array of content-types to handle as JSON. **Default ['application/json']**
})
server.register(middleware); server.register(middleware);
server.register(import("@fastify/cors"), { server.register(import("@fastify/cors"), {
@ -22,11 +52,66 @@ export const main = async () => {
credentials: true, credentials: true,
}); });
// Routes server.register(oauth, {
server.register(testRoutes, { name: 'googleOAuth2',
prefix: `/${API_VERSION}/test`, scope: ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"],
credentials: {
client: {
id: env.GOOGLE_CLIENT_ID,
secret: env.GOOGLE_SECRET
},
auth: oauth.GOOGLE_CONFIGURATION
},
callbackUriParams: {
// custom query param that will be passed to callbackUri
access_type: 'offline', // will tell Google to send a refreshToken too
},
pkce: 'S256',
// register a fastify url to start the redirect flow
startRedirectPath: '/auth/google',
// google redirect here after the user login
callbackUri: `${env.PUBLIC_API_URL}/auth/google/callback`
}); });
server.addContentTypeParser(['text/xml', 'application/xml', 'application/atom+xml'], { parseAs: 'string' }, function (request, payload, done) {
xml2js.parseString(payload, function (err, body) {
done(err, body)
})
})
// Routes
server.register(channelRoutes, {
prefix: `/channels`,
});
server.register(videoRoutes, {
prefix: `/videos`,
});
server.register(authRoutes, {
prefix: `/auth`,
});
server.register(meRoutes, {
prefix: `/me`
});
server.register(blogRoutes, {
prefix: `/blog`
});
server.register(dashboardRoutes, {
prefix: `/dashboard`
});
server.register(webhookRoutes, {
prefix: `/webhooks`
});
server.get("/hello", (req, res) => {
res.send({ message: "world", cookies: req.cookies });
})
server.listen({ host: env.HOST, port: env.PORT }, (error, address) => { server.listen({ host: env.HOST, port: env.PORT }, (error, address) => {
if (error) { if (error) {
Logger.error("INIT", error.message); Logger.error("INIT", error.message);

View File

@ -2,17 +2,48 @@
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import { Redis } from "../utils/index.js"; import { Redis } from "../utils/index.js";
import { db } from "../db/index.js"; import { db } from "../db/index.js";
import { sessions } from "../db/schemas.js";
import { eq } from "drizzle-orm";
const middleware = fp( export const middleware = fp(
/** /**
* @param {FastifyInstance} fastify * @param {FastifyInstance} fastify
* @param {unknown} _options * @param {unknown} _options
*/ */
async (fastify, _options) => { async (fastify, _options) => {
fastify.addHook("onRequest", async (request) => { fastify.addHook("onRequest", async (request) => {
request.redis = Redis; // request.redis = Redis;
request.db = db; // request.db = db;
}); });
}); });
export { middleware }; export const authMiddlewareFn = async (request, response) => {
try {
// if (!request.headers.authorization || !request.headers.authorization.startsWith("Bearer")) {
if (!request.cookies.token) {
response.status(401).send({ success: false, message: "Missing authentication token" });
return false;
}
// const token = request.headers.authorization.split(" ")[1];
const token = request.cookies.token;
const session = await db.select().from(sessions).where(eq(sessions.id, token));
if (session.length == 0) {
response.status(401).send({ success: false, message: "Invalid authentication token" });
return false;
}
// console.log(token);
// console.log(session);
request.session = session[0];
return true;
} catch (e) {
console.log(e);
}
}
export const authMiddleware = fp(
async (fastify, _options) => {
fastify.addHook("preValidation", authMiddlewareFn);
}
)

145
src/routes/auth.js Normal file
View File

@ -0,0 +1,145 @@
import { createSession as createSession } from '../utils/token.js';
import { getUserInfo } from '../utils/youtube.js';
import { db } from '../db/index.js';
import { sessions, sites, users, users as usersTable } from '../db/schemas.js';
import { eq } from 'drizzle-orm';
import { env } from '../utils/env.js';
import { authMiddleware, authMiddlewareFn } from '../modules/middleware.js';
import { createCustomer } from '../utils/stripe.js';
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const authRoutes = (fastify, _, done) => {
fastify.get("/google/callback", async (request, reply) => {
let token = "";
try {
/** @type {{token: import('@fastify/oauth2').Token}} */
const token_response = await fastify.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);
token = token_response.token;
const user_info = await getUserInfo(token.access_token);
if (!user_info.verified_email) {
reply.status(400).send({ success: false, message: "Provider email is not verified" });
return;
}
//const channel_info = await getChannelInfo(token.access_token);
const [existing_user] = await db.select().from(usersTable).where(eq(usersTable.google_id, user_info.id));
let user;
let session_id;
await db.transaction(async tx => {
try {
if (existing_user) {
user = existing_user;
if (token.refresh_token) {
await tx.update(sessions).set({
refresh_token: token.refresh_token
}).where(eq(sessions.user_id, user.id));
}
}
else {
const customer = await createCustomer(user_info.email, user_info.name);
[user] = await tx.insert(usersTable).values({
name: user_info.name,
google_id: user_info.id,
email: user_info.email,
// channel_id: channel_info.id,
channel_id: "TEMP_DISABLED",
// uploads_playlist_id: channel_info.contentDetails.relatedPlaylists.uploads,
uploads_playlist_id: "TEMP_DISABLED",
stripe_id: customer.id
}).returning({ id: usersTable.id });
// console.log("USER MADE")
// console.log("user", user);
await tx.insert(sites).values({
name: `${user_info.name}'s Website`,
user_id: user.id
});
console.log("SITE MADE")
if (user.length == 0) {
reply.status(400).send({ success: false, message: "Problem when creating user account" });
return;
}
}
let existing_session = await tx.select().from(sessions).where(eq(sessions.user_id, user.id));
if (!existing_session || existing_session.length === 0) {
let session_info = await createSession(user.id, {
access_token: token.access_token,
refresh_token: token.refresh_token,
expires_at: new Date(token.expires_at)
}, tx);
console.log("SESSION MADE")
session_id = session_info.session_id;
}
else {
let session_info = await tx.select().from(users).leftJoin(sessions, eq(sessions.user_id, users.id)).where(eq(users.google_id, user_info.id));
if (!session_info || session_info.length == 0) {
reply.status(400).send({ success: false, message: "Problem when creating user account" });
await tx.rollback();
return;
}
console.log("session_info", session_info);
session_id = session_info[0].sessions.id
}
} catch (e) {
console.log(e);
await tx.rollback();
return;
}
})
reply.setCookie("token", session_id, {
httpOnly: true,
path: "/",
sameSite: false,
maxAge: 1000 * 60 * 60 * 24 * 7,
domain: "api.youpage.ai"
});
reply.redirect(env.FRONTEND_URL + "/auth?token=" + session_id);
// response.send({
// token: session_id
// });
return;
} catch (e) {
if(e.message === "no_channel") {
reply.status(400).send("Your account does not have a youtube channel. Please make one.")
}
// fastify.googleOAuth2.revokeToken(token, "refresh_token");
console.log(e);
reply.send({ success: false, message: "There was a problem when making your account." });
return;
}
});
fastify.post("/logout", {
preValidation: authMiddlewareFn
}, async (req, reply) => {
try {
await db.delete(sessions).where(eq(sessions.id, req.session.id));
// await fastify.googleOAuth2.revokeToken(req.session, "refresh_token", {
// "content-type": "application/json"
// });
console.log("revoked")
// console.log(await res.json());
reply.send({
success: true
});
} catch (e) {
console.log(e);
}
})
done();
};

214
src/routes/blog.js Normal file
View File

@ -0,0 +1,214 @@
import { and, desc, eq, not, or, sql } from "drizzle-orm";
import { db } from "../db/index.js";
import { authMiddlewareFn } from "../modules/middleware.js";
import { articles, articles as articlesTable, signups as signupsTable, sites, users } from "../db/schemas.js";
import { sendContactEmail, sendFreebie } from "../utils/email.js";
/**
*
* @param {import("fastify").FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const blogRoutes = (fastify, _, done) => {
fastify.get("/", {
schema: {
querystring: {
mine: {
type: "boolean",
},
blog_id: {
type: "string"
},
offset: {
type: "number"
}
}
}
}, async (request, response) => {
try {
const mine = request.query.mine ? request.query.mine : false;
const blog_id = request.query.blog_id;
if (!mine && blog_id === "") {
response.status(400).send({
success: false,
message: "Request can either have \"mine\" set to true or provide a blog_id"
});
return;
}
let clause;
let site;
if (mine) {
if (!(await authMiddlewareFn(request, response))) return;
try {
[site] = await db.select().from(sites).where(and(eq(sites.user_id, request.session.user_id)));
} catch (e) {
console.log(e);
}
}
else {
let referer = new URL(request.headers.origin).host;
try {
[site] = await db.select().from(sites).where(or(eq(sites.domain, referer), eq(sites.subdomain_slug, referer.split(".")[0])));
} catch (e) {
console.log(e);
}
}
if (!site) {
response.status(400).send({
success: false,
message: "Site not found"
});
return;
}
clause = eq(articlesTable.site_id, site.id);
if (!mine) {
clause = and(eq(articlesTable.site_id, site.id), eq(articlesTable.is_public, true));
}
// const access_token = await getAccessToken(fastify, request);
// const channel = await getChannelInfo(access_token);
const results = await db.select({
id: articlesTable.id,
title: articlesTable.title,
seo_slug: articlesTable.seo_slug,
views: articlesTable.views,
is_public: articlesTable.is_public,
created_at: articlesTable.created_at,
}).from(articlesTable).where(and(eq(articlesTable.status, "done"), clause)).limit(10).offset(request.query.offset || 0).orderBy(desc(articlesTable.created_at));
const [{ total }] = await db.select({
total: sql`COUNT(*)`
}).from(articlesTable).where(clause);
response.send({
success: true,
articles: results,
total_articles: total,
site
});
} catch (e) {
console.log(e);
response.status(500).send({
success: false,
message: "Unexpected error"
});
}
});
fastify.get("/article", {
schema: {
querystring: {
id: {
type: "string"
},
slug: {
type: "string"
}
}
}
}, async (req, reply) => {
try {
const [result] = await db.select().from(articlesTable)
.where((req.query.id ? eq(articlesTable.id, req.query.id) : eq(articlesTable.seo_slug, req.query.slug)));
if (!req.session && result) {
await db.update(articlesTable).set({
views: sql`${articles.views} + 1`
}).where(eq(articlesTable.seo_slug, req.query.slug));
}
reply.send({
success: true,
article: result
});
return;
} catch (e) {
console.log(e);
}
});
fastify.post("/signup", {
schema: {
body: {
site_id: {
type: "string"
},
email: {
type: "string",
format: "email"
},
source: {
type: "string"
}
}
}
}, async (req, reply) => {
const inserted = await db.insert(signupsTable).values({
email: req.body.email,
site_id: req.body.site_id,
source: req.body.source
});
if (inserted.rowCount != 1) {
reply.status(400).send({
success: false,
message: "Problem when signing up email"
});
return;
}
await sendFreebie(req.body.email, req.body.site_id)
reply.send({
success: true
});
});
fastify.post("/contact", {
body: {
email: {
type: "string",
format: "email"
},
subject: {
type: "string"
},
content: {
type: "string"
},
blog_id: {
type: "string"
}
}
}, async (req, reply) => {
const [blog] = await db.select().from(sites).where(eq(sites.id, req.body.blog_id));
if(!blog || !blog.contact_email) {
return reply.code(400).send({
success: false,
code: "invalid_blog"
});
}
try {
await sendContactEmail(blog.contact_email, req.body.email, req.body.subject, req.body.content);
} catch (e) {
console.log(e);
return reply.code(500).send({
success: false,
code: "problem_sending"
});
}
return reply.send({
success: true
});
});
done();
};

31
src/routes/channels.js Normal file
View File

@ -0,0 +1,31 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
import { authMiddleware } from "../modules/middleware.js";
import { getAccessToken, getChannelInfo } from "../utils/youtube.js";
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const channelRoutes = (fastify, _, done) => {
fastify.register(authMiddleware);
fastify.get("/", async (request, response) => {
try {
const access_token = await getAccessToken(fastify, request);
const channel = await getChannelInfo(access_token);
response.send({
success: true,
channel
})
} catch (e) {
console.log(e);
}
});
done();
};

466
src/routes/dashboard.js Normal file
View File

@ -0,0 +1,466 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
import { and, desc, eq, getTableColumns, inArray, notInArray, sql, not } from "drizzle-orm";
import { db } from "../db/index.js";
import { articles, articles as articlesTable, signups as signupsTable, sites, users } from "../db/schemas.js";
import { authMiddleware, authMiddlewareFn } from "../modules/middleware.js";
import { jsonToCsv, createBlogFromCaptions, createArticleSlug, getVideoById, env, getWhisperCaptions, getVideoDetails, getVideoWithCaptions } from "../utils/index.js";
import { escape } from "querystring";
const websubVerifyToken = "FQNI4Suzih";
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const dashboardRoutes = (fastify, _, done) => {
fastify.register(authMiddleware);
fastify.get("/", async (req, response) => {
try {
const [{ site_id, user_id }] = await db.select({
site_id: sites.id,
user_id: sites.user_id
}).from(sites).where(eq(sites.user_id, req.session.user_id));
const recentArticles = await db.select({
title: articlesTable.title,
views: articlesTable.views,
created_at: articlesTable.created_at
}).from(articlesTable)
.where(eq(articlesTable.site_id, site_id))
.orderBy(desc(articlesTable.created_at)).limit(5);
const recentSignups = await db.select({
email: signupsTable.email,
created_at: signupsTable.created_at
}).from(signupsTable)
.where(eq(signupsTable.site_id, site_id))
.orderBy(desc(signupsTable.created_at)).limit(8);
const [{ totalArticles }] = await db.select({ totalArticles: sql`count(*)` }).from(articlesTable).where(and(eq(articlesTable.site_id, site_id), eq(articlesTable.status, "done")));
const [{ totalViews }] = await db.select({ totalViews: sql`COALESCE(sum(${articlesTable.views}), 0)` }).from(articlesTable).where(eq(articlesTable.site_id, site_id));
const [{ totalEmails }] = await db.select({ totalEmails: sql`count(*)` }).from(signupsTable).where(eq(signupsTable.site_id, site_id));
response.send({
success: true,
recentArticles,
recentSignups,
totalArticles,
totalEmails,
totalViews
});
return;
} catch (e) {
console.log(e);
response.status(500).send({
success: false,
message: "Failed to get dashboard data",
log: e.message
});
return;
}
});
fastify.get("/email-export", async (req, response) => {
});
fastify.get("/signups", 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)).orderBy(desc(signupsTable.created_at));
reply.send({
success: true,
signups
});
});
fastify.get("/signups/export", async (req, reply) => {
try {
const [{ site_id }] = await db.select({
site_id: sites.id
}).from(sites).where(eq(sites.user_id, req.session.user_id));
const signups = await db.select({
email: signupsTable.email,
source: sql`COALESCE(source, 'Home')`,
created_at: signupsTable.created_at
}).from(signupsTable).where(eq(signupsTable.site_id, site_id)).orderBy(desc(signupsTable.created_at));
let result = jsonToCsv(signups);
reply.send({
success: true,
data: result
});
return;
} catch (e) {
console.log(e);
}
});
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));
}
reply.send({
success: true
});
return;
});
fastify.delete("/article", {
schema: {
querystring: {
type: "object",
properties: {
id: {
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(and(eq(sites.user_id, req.session.user_id), eq(articles.id, req.query.id)));
if (!article) {
reply.status(404).send({
success: false,
message: "This article does not exist."
});
return;
}
await db.delete(articles).where(eq(articles.id, article.id));
reply.send({
success: true
});
return;
});
fastify.post("/create", {
schema: {
body: {
type: "object",
required: ["video_id"],
properties: {
video_id: {
type: "string",
},
length: {
type: "number",
},
format: {
type: "string"
},
faq: {
type: "boolean"
},
}
},
}
}, async (req, reply) => {
let article;
try {
const [{ tokens }] = await db.select({
tokens: users.tokens
}).from(users).where(eq(users.id, req.session.user_id));
if (tokens < 1) {
reply.send({
success: false,
code: "insufficient_tokens",
message: "Insufficient tokens"
});
return;
}
// const access_token = await getAccessToken(fastify, req);
let urlRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
let match = req.body.video_id.match(urlRegex);
if (!match) {
reply.status(400).send({
success: false,
message: "Invalid Youtube URL"
});
return;
}
const site = await db.select().from(sites).where(eq(sites.user_id, req.session.user_id));
// const video_data = await getVideoDetails(req.body.video_id);
const video_data = await getVideoWithCaptions(req.body.video_id);
reply.send({
success: true
});
article = (await db.insert(articlesTable).values({
title: video_data.title,
source_video_id: match[2],
status: "transcribing",
site_id: site[0].id,
is_public: false
}).returning({ id: articlesTable.id }))[0];
// video_data.captions = await getWhisperCaptions(req.body.video_id);
// const video_data = await getVideoById(access_token, req.body.video_id);
await db.update(articlesTable).set({
status: "generating"
}).where(eq(articlesTable.id, article.id));
const blog_content_json = await createBlogFromCaptions(video_data.captions, { title: video_data.title, description: video_data.description }, req.body);
// TODO: once I add multiple sites per user, this should come from the client
await db.update(articlesTable).set({
title: blog_content_json.title,
content: blog_content_json.content,
meta_title: blog_content_json.meta_title,
meta_desc: blog_content_json.meta_desc,
excerp: blog_content_json.excerp,
seo_slug: createArticleSlug(blog_content_json.title),
is_public: false,
status: "done"
}).where(eq(articlesTable.id, article.id));
await db.update(users).set({
tokens: tokens - 1
}).where(eq(users.id, req.session.user_id));
} catch (e) {
console.log(e);
if(e.message == "no_captions") {
reply.status(400).send({
success: false,
message: "This video does not have captions!"
});
}
article ? await db.update(articlesTable).set({
status: "error"
}).where(eq(articlesTable.id, article.id)) : "";
let message = "problem_creating_article";
reply.status(500).send({
success: false,
message: message
})
}
});
fastify.get("/queue", async (req, reply) => {
try {
const site_id = (await db.select({ site_id: sites.id }).from(sites).where(eq(sites.user_id, req.session.user_id)))[0].site_id;
if (!site_id) throw new Error("Could not find site_id");
const queue = await db.select({
id: articlesTable.id,
title: articlesTable.title,
status: articlesTable.status,
created_at: articlesTable.created_at,
source_id: articlesTable.source_video_id
}).from(articlesTable)
.where(and(eq(articlesTable.site_id, site_id), notInArray(articles.status, ["done", "error"])))
.orderBy(desc(articlesTable.created_at));
reply.send({
success: true,
queue
});
} catch (err) {
reply.status(500).send({
success: false,
message: err.message
})
}
});
fastify.post("/recreate", {
schema: {
body: {
type: "object",
required: ["video_id"],
properties: {
video_id: {
type: "string",
},
length: {
type: "number",
},
format: {
type: "string"
},
faq: {
type: "boolean"
},
}
},
}
}, async (req, reply) => {
});
fastify.put("/website", {
schema: {
body: {
type: "object",
properties: {
id: {
type: "string"
},
name: {
type: "string"
},
primary_color_hex: {
type: "string",
maxLength: 6,
minLength: 6
},
secondary_color_hex: {
type: "string",
maxLength: 6,
minLength: 6
},
text_color_hex: {
type: "string",
maxLength: 6,
minLength: 6
},
use_freebie: {
type: "boolean"
},
freebie_title: {
type: ["string", "null"]
},
freebie_url: {
type: ["string", "null"],
format: "uri"
},
freebie_image_url: {
type: ["string", "null"],
format: "uri"
},
freebie_text: {
type: ["string", "null"]
},
title: {
type: "string",
},
subtitle: {
type: "string",
},
domain: {
type: ["string", "null"]
},
auto_publish: {
type: "boolean"
},
use_contact_page: {
type: "boolean"
},
contact_email: {
type: ["string", "null"],
format: "email"
}
},
required: ["id"]
}
},
preValidation: authMiddlewareFn
}, async (req, reply) => {
try {
const [site] = await db.select().from(sites).where(eq(sites.id, req.body.id));
if (site.user_id !== req.session.user_id) {
reply.status(403).send({
success: false
});
return;
}
if (env.CERTS_URL && site.domain !== req.body.domain) {
await fetch(`http://${env.CERTS_URL}/provision`, {
method: "POST",
body: JSON.stringify({
domain: req.body.domain
})
});
}
// if (site.auto_publish !== req.body.auto_publish) {
// const [user] = await db.select().from(users).where(eq(users.id, req.session.user_id));
// if (!user) throw new Error("Problem getting user");
// let form = new FormData();
// form.set("hub.callback", env.PUBLIC_API_URL + "/webhooks/youtube");
// form.set("hub.topic", "https://www.youtube.com/xml/feeds/videos.xml?channel_id=" + user.channel_id);
// form.set("hub.verify", "async");
// form.set("hub.mode", req.body.auto_publish ? "subscribe" : "unsubscribe");
// form.set("hub.verify_token", websubVerifyToken);
// await fetch("https://pubsubhubbub.appspot.com/subscribe", {
// method: "POST",
// body: form
// });
// }
const data = structuredClone(req.body);
delete data.id;
await db.update(sites).set(data).where(eq(sites.id, req.body.id));
return {
success: true
}
} catch (e) {
console.log(e);
}
});
done();
};

View File

@ -1 +1,7 @@
export * from "./test.js"; export * from "./auth.js";
export * from "./blog.js"
export * from "./channels.js";
export * from "./dashboard.js"
export * from "./me.js";
export * from "./videos.js";
export * from "./webhook.js";

87
src/routes/me.js Normal file
View File

@ -0,0 +1,87 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { users } from "../db/schemas.js";
import { authMiddleware } from "../modules/middleware.js";
import { createCheckout, getBillingDashboard } from "../utils/stripe.js";
import { env } from "../utils/env.js";
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const meRoutes = (fastify, _, done) => {
fastify.register(authMiddleware);
fastify.get("/", async (request, response) => {
try {
const user = await db.select().from(users).where(eq(users.id, request.session.user_id));
if (typeof user == typeof Error) {
response.status(400).send({
success: false,
message: "User not found"
});
return;
}
response.send({
success: true,
user: user[0],
is_subscribed: new Date(user[0].subscribed_until) > new Date(),
});
} catch (e) {
response.status(400).send({
success: false,
message: "User not found",
log: e.message
});
return;
}
});
fastify.get("/billing", async (request, response) => {
try {
const [user] = await db.select().from(users).where(eq(users.id, request.session.user_id));
const billing_session_url = await getBillingDashboard(user.stripe_id, env.FRONTEND_URL);
response.redirect(billing_session_url);
return;
} catch (e) {
response.status(400).send({
success: false,
message: "User not found",
log: e.message
});
return;
}
});
fastify.get("/subscribe", async (req, reply) => {
try {
const [user] = await db.select().from(users).where(eq(users.id, req.session.user_id));
if (!user) throw new Error("user not found");
if(new Date(user.subscribed_until) > new Date()) return reply.redirect(303, "/me/billing");
console.log(new Date(user.subscribed_until).getTime())
const checkout_url = await createCheckout(env.STRIPE_SUBSCRIPTION_PRICE_ID, user.stripe_id, {
user_id: user.id
}, {
...(!user.trial_used ? {trial_end: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000)} : {})
});
reply.redirect(303, checkout_url);
} catch (e) {
reply.status(400).send({
success: false,
message: "User not found",
log: e.message
});
return;
}
});
done();
};

View File

@ -1,21 +0,0 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const testRoutes = (fastify, _, done) => {
fastify.get("/", async (request, response) => {
const visits = Number(await request.redis.get("visits"));
request.redis.incr("visits");
response.send({
hello: "world",
visits,
});
});
done();
};

33
src/routes/videos.js Normal file
View File

@ -0,0 +1,33 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { users } from "../db/schemas.js";
import { authMiddleware } from "../modules/middleware.js";
import { getAccessToken, getVideosFromPlaylist } from "../utils/youtube.js";
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const videoRoutes = (fastify, _, done) => {
fastify.register(authMiddleware);
fastify.get("/", async (request, response) => {
try {
const token = await getAccessToken(fastify, request);
const [user] = await db.select().from(users).where(eq(users.id, request.session.user_id));
const videos = await getVideosFromPlaylist(token, user.uploads_playlist_id);
response.send({
success: true,
videos
});
} catch (e) {
console.log(e);
}
});
done();
};

110
src/routes/webhook.js Normal file
View File

@ -0,0 +1,110 @@
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { articles, sites, users } from "../db/schemas.js";
import { getVideoWithCaptions } from "../utils/youtube.js";
import { createBlogFromCaptions } from "../utils/ai.js";
import { createArticleSlug } from "../utils/index.js";
import { handleWebhook } from "../utils/stripe.js";
/**
*
* @param {FastifyInstance} fastify
* @param {unknown} _
* @param {() => void} done
*/
export const webhookRoutes = (fastify, _, done) => {
fastify.post("/stripe", {
config: {
rawBody: true
},
handler: async (req, reply) => {
try {
await handleWebhook(req.rawBody, req.headers["stripe-signature"]);
reply.status(200).send({});
} catch (e) {
console.log(e);
reply.status(400).send({
success: false,
message: "Error validating purchase",
log: e.message
});
return;
}
}
});
fastify.get("/youtube", async (req, reply) => {
// Check if the request contains the 'hub.challenge' query parameter
if (req.query["hub.challenge"] && req.query["hub.verify_token"] === "FQNI4Suzih" && req.query["hub.topic"].startsWith("https://www.youtube.com/xml/feeds/videos.xml?channel_id=")) {
if (req.query["hub.mode" === "unsubscribe"]) return reply.code(200).send();
const channel = req.query["hub.topic"].replace("https://www.youtube.com/xml/feeds/videos.xml?channel_id=", "");
const [user] = await db.select().from(users).where(eq(users.channel_id, channel));
if (!user) return reply.code(400).send("Invalid user");
await db.update(sites).set({
pubsub_expiry: new Date(new Date().getTime() + (parseInt(req.query["hub.lease_seconds"] || 172800) * 1000))
}).where(eq(sites.user_id, user.id));
// Respond with the challenge to verify the subscription
console.log(req.query)
console.log("verifying...", req.query["hub.challenge"]);
return reply.send(req.query["hub.challenge"]);
} else {
// Handle other cases or errors
return reply.code(404).send("Not found");
}
});
fastify.post("/youtube", async (req, reply) => {
const { headers, body } = req;
const contentType = headers['content-type'];
// Check if the content type is 'application/atom+xml'
if (contentType === 'application/atom+xml') {
try {
// Parse the XML payload
console.log(JSON.stringify(body))
const feed = body["feed"];
// Example processing: log the video IDs of new videos
const entry = feed.entry[0];
if (!entry) return reply.code(200).send();
const [{ users: user, sites: site }] = await db.select().from(users).leftJoin(sites, eq(users.id, sites.user_id)).where(eq(users.channel_id, entry["yt:channelId"][0]));
if (!user || !site) throw new Error("User not found");
if (user.tokens < 3) throw new Error("Not enough tokens");
const videoId = entry["yt:videoId"][0];
const videoURL = `https://youtu.be/${videoId}`;
reply.code(200).send();
const video_data = await getVideoWithCaptions(videoURL);
const blog_content_json = await createBlogFromCaptions(video_data.captions, { title: video_data.title, description: video_data.description });
await db.insert(articles).values({
site_id: site.id,
title: blog_content_json.title,
content: blog_content_json.content,
meta_title: blog_content_json.meta_title,
meta_desc: blog_content_json.meta_desc,
excerp: blog_content_json.excerp,
source_video_id: videoId,
seo_slug: createArticleSlug(blog_content_json.title),
is_public: false
}).returning({ id: articles.id });
await db.update(users).set({
tokens: user.tokens - 3
}).where(eq(users.id, user.id));
// Respond with a success status
} catch (e) {
console.log(e)
return reply.code(500).send(e);
}
} else {
// Respond with an error status if the content type is not expected
return reply.code(400).send("Bad Request");
}
})
done();
};

218
src/utils/ai.js Normal file
View File

@ -0,0 +1,218 @@
const defaultModel = '@hf/mistralai/mistral-7b-instruct-v0.2';
import OpenAI, { toFile } from "openai";
import { env } from "./env.js";
import ytdl from "@distube/ytdl-core";
import { PassThrough } from "stream";
const openai = new OpenAI({
apiKey: env.OPENAI_TOKEN
});
async function cf_prompt(prompt, model = defaultModel) {
const options = {
method: 'POST',
headers: { Authorization: 'Bearer oJh-qUnkPmsaaE7mfor617dasBMnH_t9QFkSc5L2' },
body: '{ "messages": [{ "role": "system", "content": "You are a friendly assistant" }, { "role": "user", "content": "Why is pizza so good" }]}'
};
const res = await fetch('https://api.cloudflare.com/client/v4/accounts/f79c2f6c3ee16c813cbc853bc7e16166/ai/run/@hf/mistralai/mistral-7b-instruct-v0.2', options)
.then(response => response.json())
.catch(err => console.error(err));
console.log(res);
return res;
}
/**
*
* @param {import("openai/resources/index.mjs").ChatCompletionMessageParam[]} prompt
* @param {{model: string, is_json: boolean}} opts
* @returns
*/
async function promptGPT(prompt, { model, is_json } = { model: "gpt-3.5-turbo", is_json: true }) {
// return JSON.stringify({ "title": "Tech News Update: TikTok Ban, Snapdragon X Series CPUs, Tesla Troubles, and More", "body": "I was thinking about starting this video by singing the song Tik Tok by Kesha but I don't think anyone waking up in the morning feeling like P Diddy is a good vibe right now. The United States has officially passed a law banning TikTok next year if certain conditions aren't met. This ban is part of a larger foreign aid package in support of Ukraine and Israel. TikTok has vowed to fight the law in court, calling it an unconstitutional suppression of its American users' freedom of speech. In other tech news, Qualcomm has unveiled its upcoming lineup of Snapdragon X Series CPUs, including some confusing names. Tesla is facing troubles with its Cybertruck not being waterproof. And Google has delayed its plan to phase out third-party tracking cookies yet again. Stay tuned for more tech updates on TechLink!" });
openai.chat.completions.create({
model: model,
messages: prompt,
})
const options = {
method: 'POST',
headers: { Authorization: `Bearer ${env.OPENAI_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
"model": "gpt-3.5-turbo",
// "max_tokens": 4096,
...(is_json ? { "response_format": { "type": "json_object" } } : {}),
"messages": prompt
})
};
console.log("prompting gpt-3.5-turbo...")
const res = await fetch('https://api.openai.com/v1/chat/completions', options)
.then(response => {
return response.json();
})
.catch(err => console.log(err));
console.log(res);
return res.choices[0].message.content;
}
/**
*
* @param {string} captions
* @param {{length: number, language: string, format: "summary"|"listicle"|"product review"|"tutorial", tone: "professional"|"informal"|"informational"}} param1
*
* @returns {Promise<{title: string, body: string, seo_friendly_slug: string}>}
*/
export async function createBlogFromCaptions(captions, {
title,
description
}, options = {}) {
const {
length,
language,
format,
tone,
faq
} = Object.assign({ length: 700, language: "English", format: "summary", tone: "informal", faq: false }, options);
// 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, "body": string, "seo_friendly_slug": string}. Do not, under any circumstance, include the title inside the body, it should only be reserved for the body of the article. Use markdown to format the article. Use "\\n" to add line-breaks. \nHere is the transcript: `;
const basePrompt = [{
role: "system",
content: `# Role: SEO Content Specialist
## Profile:
- Author: Youpage.ai
- Version: 1.0
- Language: English
- Description: An SEO Content Specialist is an expert in creating high-quality, factually correct, and search engine optimized content. This role involves transforming transcripts, drafts, or raw content into polished articles that rank well on search engines and engage readers.
### Skill:
1. Expertise in SEO best practices.
2. Knowledge of Markdown formatting and how to use it to improve readability.
3. Proficiency in content writing and editing.
4. Strong understanding of keyword research and usage.
5. Ability to maintain factual accuracy.
6. Knowledge of news article formatting and structure.
## Goals:
1. Create a high-quality news article from the provided transcript.
2. Ensure the article is SEO optimized.
3. Maintain factual accuracy throughout the article.
4. Enhance readability and engagement.
5. Use relevant keywords effectively.
## Constraints VERY IMPORTANT:
1. Do not alter the factual content from the transcript.
2. Follow SEO best practices.
3. Ensure the article is engaging and readable.
4. Adhere to news article formatting.
5. Limit the use of adjectives. Try to use precise nouns and verbs instead.
6. Use unique descriptions and avoid repeating the same phrases.
7. Vary the vocabulary to avoid repetition.
8. Keep sentences concise to maintain pace and tension.
9. Ensure a consistent perspective and unique character voice.
10. Do NOT use this sentence structure: [[Subject] [Predicate] [Object] [Appositive] [Prepositional Phrase] [Relative Clause]]
11. Do NOT use these phrases: delve, Cosmic, cosmos, heady, void, stars, testament, hum, palpable, symphony, tapestry, woven, complemented, grim, I can't deny, reminder, stark, contrast, storm, tempest, a far cry, an echo, navigate, sheepish.
## OutputFormat:
1. Do not include a title at the top of the article.
2. VERY IMPORTANT! Use markdown to add formatting to the article and make it easier to read.
3. Length: The article should be roughly ${length || 1200} words.
${faq ? "4. FAQ Section: Add a FAQ section at the end of the article to address common questions." : ""}
## Workflow:
1. First, analyze the provided transcript to understand the key points and factual content.
2. Then, conduct keyword research to identify relevant and high-ranking keywords for the article.
3. Next, draft a compelling headline and introduction that include the main keyword.
4. After that, write the body of the article, ensuring it is factually correct, well-structured, and around {{word_count}} words in length.
5. Conclude the article with a strong closing and ensure all SEO elements are in place, including meta description and alt text for any images.
${faq ? "6. Finally, add an FAQ section at the end of the article to address common questions related to the topic." : ""}
## Initialization:
As an SEO Content Specialist, you must follow the specified rules and communicate in English. Respond only with the article content.`
}, {
role: "user",
content: `# Information:
## Title
${title}
## Transcript
${captions}`
}];
let response = await openai.chat.completions.create({
model: "gpt-4o",
messages: basePrompt
});
console.log("first response", response.choices[0].message.content)
let final = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [...basePrompt, { "role": "assistant", "content": response.choices[0].message.content }, { "role": "user", "content": "Respond only in JSON with the following format: { meta_title: string, meta_desc: string, slug: string, excerp: string, title: string }" }],
response_format: { type: "json_object" }
})
console.log("second response", final.choices[0].message.content)
let result = Object.assign(JSON.parse(final.choices[0].message.content), {
content: response.choices[0].message.content,
})
return result;
}
function wordsToTokens(n) {
return Math.ceil(n / 0.75);
}
export async function getWhisperCaptions(video_url) {
const audio = await getYouTubeAudioBuffer(video_url);
const file = await toFile(audio, "audio.mp3");
const res = await openai.audio.transcriptions.create({
file: file,
model: "whisper-1",
}).catch(x => console.log(x));
return res.text;
}
/**
* Fetches the audio data from a YouTube video URL and returns it as a buffer.
* @param {string} videoUrl - The YouTube video URL.
* @returns {Promise<Buffer>} A promise that resolves with the audio data as a buffer.
*/
function getYouTubeAudioBuffer(videoUrl) {
return new Promise((resolve, reject) => {
if(!videoUrl) reject();
// Download audio from YouTube
const audioStream = ytdl(videoUrl, { filter: "audioonly", quality: "lowestaudio" });
// Create a PassThrough stream to hold the audio data in memory
const passThrough = new PassThrough();
// Pipe the audio stream to the PassThrough stream
audioStream.pipe(passThrough);
// Collect the audio data into a buffer
let audioDataBuffer = [];
// Listen for data events on the PassThrough stream
passThrough.on('data', chunk => {
audioDataBuffer.push(chunk);
});
// Listen for end event to indicate the stream has finished
passThrough.on('end', () => {
// Convert the collected chunks into a single Buffer
const audioBufferData = Buffer.concat(audioDataBuffer);
// Resolve the promise with the audio data buffer
resolve(audioBufferData);
});
// Listen for error event on the PassThrough stream
passThrough.on('error', err => {
// Reject the promise if an error occurs
reject(err);
});
});
}

50
src/utils/email.js Normal file
View File

@ -0,0 +1,50 @@
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { sites } from "../db/schemas.js";
import nodemailer from 'nodemailer';
const TOKEN = "a0cc97f4f856d6c16d70bb5984e32cef";
const ENDPOINT = "https://send.api.mailtrap.io/";
const default_sender = "mailtrap@demomailtrap.com";
var client = nodemailer.createTransport({
host: "live.smtp.mailtrap.io",
port: 587,
auth: {
user: "api",
pass: "a0cc97f4f856d6c16d70bb5984e32cef"
}
});
export async function sendContactEmail(recipient, sender, subject, body) {
client.sendMail({
from: default_sender,
to: recipient,
subject: `YouPage.ai: ${subject}`,
text: `You got a new message from your Youpage.ai blog!\nSender: ${sender}\nMessage: ${body}`
}).catch(console.error);
}
export async function sendFreebie(recipient, blog_id) {
const [blog] = await db.select().from(sites).where(eq(sites.id, blog_id));
if (!blog) throw new Error("Invalid site");
if (!blog.use_freebie || !blog.freebie_url) return;
const sender = default_sender;
const recipients = [
{
email: recipient,
}
];
client
.sendMail({
from: sender,
to: recipients,
subject: `Your freebie from ${blog.name}`,
text: `Thank you for subscribing to ${blog.name}! Your freebie is linked below.\n ${blog.freebie_url}`,
}).catch(console.error);
}

View File

@ -6,6 +6,16 @@ const envSchema = z.object({
REDIS_URL: z.string().default("redis://127.0.0.1:6379/"), REDIS_URL: z.string().default("redis://127.0.0.1:6379/"),
PORT: z.coerce.number().default(8080), PORT: z.coerce.number().default(8080),
HOST: z.string().default("127.0.0.1"), HOST: z.string().default("127.0.0.1"),
PUBLIC_API_URL: z.string(),
FRONTEND_URL: z.string(),
SITES_HOST: z.string(),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_SECRET: z.string(),
OPENAI_TOKEN: z.string(),
CERTS_URL: z.string().nullable().default(null),
STRIPE_KEY: z.string(),
STRIPE_WEBHOOK_SECRET: z.string(),
STRIPE_SUBSCRIPTION_PRICE_ID: z.string(),
}); });
export const env = envSchema.parse(process.env); export const env = envSchema.parse(process.env);

View File

@ -1,3 +1,75 @@
export * from "./env.js"; export * from "./env.js";
export * from "./logger.js"; export * from "./logger.js";
export * from "./redis.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;
}
/**
* Convert a JSON object to a valid CSV string
* @param {JSON[]} jsonData
* @returns {string} CSV Data
*/
export function jsonToCsv(jsonData) {
// Extract headers from the first object in the JSON array
const headers = Object.keys(jsonData[0]);
// Join headers to form the first line of CSV
let csv = headers.map(header => `"${header}"`).join(',') + '\n';
// Loop through each object in the JSON array
for (let obj of jsonData) {
// Extract values corresponding to each header
const values = headers.map(header => {
let value = obj[header];
// Check if the value contains a comma, if so, wrap it in double quotes
if (typeof value === 'string' && value.includes(',')) {
value = `"${value}"`;
}
return value;
});
// Join values with comma separator
csv += values.join(',') + '\n';
}
return csv;
}

6
src/utils/sentry.js Normal file
View File

@ -0,0 +1,6 @@
import * as Sentry from "@sentry/node";
// Ensure to call this before importing any other modules!
Sentry.init({
dsn: "https://0092593709894b0eaec1702e0136594b@glitchtip.omersabic.com/4",
});

107
src/utils/stripe.js Normal file
View File

@ -0,0 +1,107 @@
import loadStripe from 'stripe';
import { env } from './env.js';
import { db } from '../db/index.js';
import { users } from '../db/schemas.js';
import { eq, sql } from 'drizzle-orm';
const stripe = await loadStripe(env.STRIPE_KEY);
/**
*
* @param {string} email
* @param {string} name
* @returns {import('stripe-v3').customer.create}
*/
export async function createCustomer(email, name) {
return (await stripe.customers.create({
email: email,
name: name,
}));
}
/**
*
* @param {string} stripe_id
*/
export async function getBillingDashboard(stripe_id, return_url) {
const session = await stripe.billingPortal.sessions.create({
customer: stripe_id,
return_url
});
return session.url;
}
export async function createCheckout(price_id, customer_id, metadata = {}, subscription_data = {}) {
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customer_id,
line_items: [
{
price: price_id,
quantity: 1
},
],
metadata,
subscription_data,
success_url: env.FRONTEND_URL + '/payments/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: env.FRONTEND_URL,
});
return session.url;
}
export async function handleWebhook(body, signature) {
let data;
let eventType;
// Check if webhook signing is configured.
const webhookSecret = env.STRIPE_WEBHOOK_SECRET;
if (webhookSecret) {
// Retrieve the event by verifying the signature using the raw body and secret.
let event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
webhookSecret
);
} catch (err) {
console.log(err);
console.log(`⚠️ Webhook signature verification failed.`);
throw new Error("Verification failed")
}
// Extract the object from the event.
data = event.data;
eventType = event.type;
} else {
// Webhook signing is recommended, but if the secret is not configured in `config.js`,
// retrieve the event data directly from the request body.
data = body.data;
eventType = body.type;
}
switch (eventType) {
case 'invoice.paid':
console.log("invoice paid!")
const [user] = await db.select().from(users).where(eq(users.stripe_id, data.object.customer));
console.log(data);
if (!user) throw new Error("Failed to get user ID");
let currentDate = new Date();
let subscriptionEndDate = new Date(currentDate);
if(user.trial_used) {
subscriptionEndDate.setMonth(currentDate.getMonth() + 1);
} else {
subscriptionEndDate.setDate(currentDate.getDate() + 14);
}
await db.update(users).set({
subscribed_until: subscriptionEndDate,
subscription_tier: "basic",
tokens: sql`${users.tokens} + 25`,
trial_used: true
}).where(eq(users.stripe_id, data.object.customer));
break;
default:
break;
}
}

44
src/utils/token.js Normal file
View File

@ -0,0 +1,44 @@
import { eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { sessions } from "../db/schemas.js";
/**
*
* @param {string} user_id User to assign the auth token to
* @param {{access_token: string, refresh_token: string, expires_at: Date}} googleAuth
*
* @returns {Promise<{session_id: string}>} Auth token
*/
export async function createSession(user_id, {
access_token,
refresh_token,
expires_at,
}, tx = db) {
try {
const [existing_session] = await tx.select().from(sessions).where(eq(sessions.user_id, user_id));
if (existing_session) {
return {
session_id: existing_session.id
}
}
const token = await tx.insert(sessions).values({
user_id,
access_token: access_token,
refresh_token: refresh_token,
expires_at
}).returning({ id: sessions.id });
if (token.length == 0) {
throw new Error("Error creating auth token");
}
return {
session_id: token[0].id
}
} catch (e) {
console.log(e);
throw e;
}
}

520
src/utils/youtube.js Normal file
View File

@ -0,0 +1,520 @@
import { eq } from 'drizzle-orm';
import { db } from '../db/index.js';
import { sessions } from '../db/schemas.js';
import { google } from 'googleapis';
import ytdl from "@distube/ytdl-core";
import { getWhisperCaptions } from './ai.js';
import { articles as articlesTable } from "../db/schemas.js";
import * as xml2js from 'xml2js';
const service = google.youtube("v3");
const agent = ytdl.createAgent([
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678493,
"hostOnly": false,
"httpOnly": false,
"name": "__Secure-1PAPISID",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "Ydllg_a0ziZ2IRkW/AxTe18r6nUqk7BEbQ",
"id": 1
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678415,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-1PSID",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "g.a000mwiFpDXHv-mE1JbzAv29Ye5X1D7V-zT1tEGx1_bd46Kree9yTMpJEGK4nnajwLM5d0hj7gACgYKAYUSARUSFQHGX2MiVlSE8GgO3Vmj3YT3n8k6WhoVAUF8yKoU4xPY7CM6zb7j98zdaVV70076",
"id": 2
},
{
"domain": ".youtube.com",
"expirationDate": 1754861761.153214,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-1PSIDCC",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "AKEyXzXzGpG2-NgDLDoR0-TXlyJVUQJQ_hzs2wEvAZmPy5VTxaOIXg4QrrR3N_v5EWUqhQ4q",
"id": 3
},
{
"domain": ".youtube.com",
"expirationDate": 1754861754.678387,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-1PSIDTS",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "sidts-CjIBUFGohzuXoAbjBYmkalmpToyVGhPcAMyp-r3IgiyU7WlYnluERYQWrB-zOvpwcD5F_RAA",
"id": 4
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678506,
"hostOnly": false,
"httpOnly": false,
"name": "__Secure-3PAPISID",
"path": "/",
"sameSite": "no_restriction",
"secure": true,
"session": false,
"storeId": "1",
"value": "Ydllg_a0ziZ2IRkW/AxTe18r6nUqk7BEbQ",
"id": 5
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678427,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-3PSID",
"path": "/",
"sameSite": "no_restriction",
"secure": true,
"session": false,
"storeId": "1",
"value": "g.a000mwiFpDXHv-mE1JbzAv29Ye5X1D7V-zT1tEGx1_bd46Kree9y4tCIp7vBEBU5tBLxdPiklQACgYKAVgSARUSFQHGX2MiCFYG1JWlH9dAu8gduZa6dRoVAUF8yKpMaD9-H5JRU8a-L-_Za4wd0076",
"id": 6
},
{
"domain": ".youtube.com",
"expirationDate": 1754861761.153238,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-3PSIDCC",
"path": "/",
"sameSite": "no_restriction",
"secure": true,
"session": false,
"storeId": "1",
"value": "AKEyXzUy-4CY7B0Gu-XHjrZVVpAWPNJNET5PQOlbRbmaa_ecvp7AS8Nwf5gbDTfL47zj0Dez3Q",
"id": 7
},
{
"domain": ".youtube.com",
"expirationDate": 1754861754.678402,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-3PSIDTS",
"path": "/",
"sameSite": "no_restriction",
"secure": true,
"session": false,
"storeId": "1",
"value": "sidts-CjIBUFGohzuXoAbjBYmkalmpToyVGhPcAMyp-r3IgiyU7WlYnluERYQWrB-zOvpwcD5F_RAA",
"id": 8
},
{
"domain": ".youtube.com",
"expirationDate": 1757453723.663338,
"hostOnly": false,
"httpOnly": true,
"name": "__Secure-YEC",
"path": "/",
"sameSite": "lax",
"secure": true,
"session": false,
"storeId": "1",
"value": "CgtVakdtMnJrZ2VwRSi_ut-1BjIKCgJERRIEEgAgKQ%3D%3D",
"id": 9
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678463,
"hostOnly": false,
"httpOnly": false,
"name": "APISID",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "PzwvCMFIwi-WXHNK/Aa7aeSu6UGGiR3Xw-",
"id": 10
},
{
"domain": ".youtube.com",
"expirationDate": 1723326359,
"hostOnly": false,
"httpOnly": false,
"name": "CONSISTENCY",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "AKreu9uoPRzjfNbvjscAvYa4AnZ7yXSd9GkhniF8n1uodA2SI5_UawxviB_l5efAOS7TIHbrzFvCUM3riEdLFWvDQ0So_SGThxdrORfZ0vAZzDn30MIgE0ddEa_h-aBhNzX4ispknt4SY4E76opMTGsu",
"id": 11
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678441,
"hostOnly": false,
"httpOnly": true,
"name": "HSID",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "An0F5SiZ_NJH4_Rx4",
"id": 12
},
{
"domain": ".youtube.com",
"expirationDate": 1757885756.324828,
"hostOnly": false,
"httpOnly": true,
"name": "LOGIN_INFO",
"path": "/",
"sameSite": "no_restriction",
"secure": true,
"session": false,
"storeId": "1",
"value": "AFmmF2swRQIgQD5euCkX56qyeRocOC192DF6siCwmZiG5RFdXIsIOKQCIQDVYMUXa9fafpFC4gJAi32F-LsqO7a5ptBfytQ0ooI-7g:QUQ3MjNmd1RmUlRfd25hTUFDT214TF9pcHlXcEh5ZHAzc2d0Rjc2WjZVUEJCRW1KSV9QRTlBZXpTLXA1QXFtdExyMzFncVdteTE4N1pMWk9yUnhTVU8taF93eEZlRm95UTdubHJ3emt4SVBYeVNjQ0dDREtnb2JWeHhHeU1HYy1xVlhlZTU4V3g3TnEyMm9uWDZzTzlBZHhjSG81dXZ3WnJn",
"id": 13
},
{
"domain": ".youtube.com",
"expirationDate": 1739136959.800355,
"hostOnly": false,
"httpOnly": true,
"name": "NID",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "516=aE2sFI_rh_Rh7q0T-GD9vzWm4pf6Uak1iFjvzBFuRr8wxfSmJEDS6NzXpXKLLNKi_ba-NaxkIxkfhKJ9I3ODz9TmFnuTwQVvGbSordWz2DI3IvtXEygFNEGTTW1WuKBOHO-WXjdM2nuQ4b7d3KWiDttthySlnE6l0vur98aENPo",
"id": 14
},
{
"domain": ".youtube.com",
"expirationDate": 1757885758.099494,
"hostOnly": false,
"httpOnly": false,
"name": "PREF",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "f6=40000000&tz=Europe.Berlin",
"id": 15
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678475,
"hostOnly": false,
"httpOnly": false,
"name": "SAPISID",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "Ydllg_a0ziZ2IRkW/AxTe18r6nUqk7BEbQ",
"id": 16
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678333,
"hostOnly": false,
"httpOnly": false,
"name": "SID",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "g.a000mwiFpDXHv-mE1JbzAv29Ye5X1D7V-zT1tEGx1_bd46Kree9yaWb-XRQACGvVe52ZscToXgACgYKAc4SARUSFQHGX2MipK_BxY_xGkd82ZuR_-usghoVAUF8yKoD-HK1Ms_BeaLioA-9grr10076",
"id": 17
},
{
"domain": ".youtube.com",
"expirationDate": 1754861761.153147,
"hostOnly": false,
"httpOnly": false,
"name": "SIDCC",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "AKEyXzVET2aVXEooYcLzVZcKqrNU4ugR61hBJSb9_Kg9YQSdRmMSiwZNQZEHDNA0spao7jlE",
"id": 18
},
{
"domain": ".youtube.com",
"expirationDate": 1757453733.489585,
"hostOnly": false,
"httpOnly": false,
"name": "SOCS",
"path": "/",
"sameSite": "lax",
"secure": true,
"session": false,
"storeId": "1",
"value": "CAESFggDEgk2NjA1NTYzMjIaBWVuLUdCIAEaBgiA29q1Bg",
"id": 19
},
{
"domain": ".youtube.com",
"expirationDate": 1757885754.678452,
"hostOnly": false,
"httpOnly": true,
"name": "SSID",
"path": "/",
"sameSite": "unspecified",
"secure": true,
"session": false,
"storeId": "1",
"value": "AMLcUMa2BaB8t4bo0",
"id": 20
},
{
"domain": ".youtube.com",
"expirationDate": 1723325764,
"hostOnly": false,
"httpOnly": false,
"name": "ST-hcbf8d",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "session_logininfo=AFmmF2swRQIgQD5euCkX56qyeRocOC192DF6siCwmZiG5RFdXIsIOKQCIQDVYMUXa9fafpFC4gJAi32F-LsqO7a5ptBfytQ0ooI-7g%3AQUQ3MjNmd1RmUlRfd25hTUFDT214TF9pcHlXcEh5ZHAzc2d0Rjc2WjZVUEJCRW1KSV9QRTlBZXpTLXA1QXFtdExyMzFncVdteTE4N1pMWk9yUnhTVU8taF93eEZlRm95UTdubHJ3emt4SVBYeVNjQ0dDREtnb2JWeHhHeU1HYy1xVlhlZTU4V3g3TnEyMm9uWDZzTzlBZHhjSG81dXZ3WnJn",
"id": 21
},
{
"domain": ".youtube.com",
"expirationDate": 1723325763,
"hostOnly": false,
"httpOnly": false,
"name": "ST-l3hjtt",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "session_logininfo=AFmmF2swRQIgQD5euCkX56qyeRocOC192DF6siCwmZiG5RFdXIsIOKQCIQDVYMUXa9fafpFC4gJAi32F-LsqO7a5ptBfytQ0ooI-7g%3AQUQ3MjNmd1RmUlRfd25hTUFDT214TF9pcHlXcEh5ZHAzc2d0Rjc2WjZVUEJCRW1KSV9QRTlBZXpTLXA1QXFtdExyMzFncVdteTE4N1pMWk9yUnhTVU8taF93eEZlRm95UTdubHJ3emt4SVBYeVNjQ0dDREtnb2JWeHhHeU1HYy1xVlhlZTU4V3g3TnEyMm9uWDZzTzlBZHhjSG81dXZ3WnJn",
"id": 22
},
{
"domain": ".youtube.com",
"expirationDate": 1723325763,
"hostOnly": false,
"httpOnly": false,
"name": "ST-tladcw",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "session_logininfo=AFmmF2swRQIgQD5euCkX56qyeRocOC192DF6siCwmZiG5RFdXIsIOKQCIQDVYMUXa9fafpFC4gJAi32F-LsqO7a5ptBfytQ0ooI-7g%3AQUQ3MjNmd1RmUlRfd25hTUFDT214TF9pcHlXcEh5ZHAzc2d0Rjc2WjZVUEJCRW1KSV9QRTlBZXpTLXA1QXFtdExyMzFncVdteTE4N1pMWk9yUnhTVU8taF93eEZlRm95UTdubHJ3emt4SVBYeVNjQ0dDREtnb2JWeHhHeU1HYy1xVlhlZTU4V3g3TnEyMm9uWDZzTzlBZHhjSG81dXZ3WnJn",
"id": 23
},
{
"domain": ".youtube.com",
"expirationDate": 1723325764,
"hostOnly": false,
"httpOnly": false,
"name": "ST-xuwub9",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "1",
"value": "session_logininfo=AFmmF2swRQIgQD5euCkX56qyeRocOC192DF6siCwmZiG5RFdXIsIOKQCIQDVYMUXa9fafpFC4gJAi32F-LsqO7a5ptBfytQ0ooI-7g%3AQUQ3MjNmd1RmUlRfd25hTUFDT214TF9pcHlXcEh5ZHAzc2d0Rjc2WjZVUEJCRW1KSV9QRTlBZXpTLXA1QXFtdExyMzFncVdteTE4N1pMWk9yUnhTVU8taF93eEZlRm95UTdubHJ3emt4SVBYeVNjQ0dDREtnb2JWeHhHeU1HYy1xVlhlZTU4V3g3TnEyMm9uWDZzTzlBZHhjSG81dXZ3WnJn",
"id": 24
}
]);
export async function getVideoById(access_token, video_id) {
const video = await service.videos.list({
access_token,
id: video_id,
part: "snippet"
}).then(x => x.data.items[0].snippet);
return video;
}
/**
*
* @param {string} access_token
* @param {string} playlist_id
*
* @returns {Promise<import("googleapis").youtube_v3.Schema$PlaylistItem[]>}
*/
export async function getVideosFromPlaylist(access_token, playlist_id) {
if (!access_token) return [];
let videos;
try {
videos = await service.playlistItems.list({
access_token,
part: "id,snippet",
playlistId: playlist_id,
maxResults: 10
}).then(res => res)
} catch (e) {
return [];
}
console.log(videos.status);
if (videos.status > 299 || videos.status < 200) {
return [];
}
return videos.data.items;
}
/**
*
* @param {string} access_token
*
* @returns {Promise<import("googleapis").youtube_v3.Schema$Channel>}
*/
export async function getChannelInfo(access_token) {
const channel = await service.channels.list({
access_token,
part: "id,contentDetails,snippet",
mine: true,
maxResults: 1
}).then(res => res);
if (!channel.data.items[0]) {
throw new Error("no_channel");
}
return channel.data.items[0];
}
/** @typedef {{name: string, email: string, isVerified: boolean}} UserInfo */
/**
*
* @param {string} access_token
*
* @returns {Promise<UserInfo>}
*/
export async function getUserInfo(access_token) {
const url = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
// const url = "https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses";
const response = await fetch(url, {
headers: {
"Authorization": `Bearer ${access_token}`
}
})
const data = await response.json();
return data
}
/**
*
* @param {import('fastify').FastifyInstance} fastify
* @param {import('fastify').FastifyRequest} request
*
* @returns {Promise<string>}
*/
export async function getAccessToken(fastify, request) {
// TODO: Move to cache instead of postgres
// const [cachedToken] = await db.select().from(sessions).where(eq(sessions.id, request.session.id));
let access_token = request.session.access_token;
if ((new Date().getTime() + 10) > request.session.expires_at) {
/** @type {import('@fastify/oauth2').Token} */
const { token } = await fastify.googleOAuth2.getNewAccessTokenUsingRefreshToken(request.session).catch(e => ({ token: null }));
if (!token) return ""
access_token = token.access_token;
await db.update(sessions).set({
expires_at: token.expires_at,
access_token: token.access_token
}).where(eq(sessions.user_id, request.session.user_id));
}
return access_token;
}
export async function getVideoDetails(video_url) {
if (!(ytdl.validateURL(video_url))) throw new Error("Invalid Youtube URL");
const info = await ytdl.getBasicInfo(video_url, {agent});
return {
title: info.videoDetails.title,
description: info.videoDetails.description
}
}
export function parseTextFromCaptions(caption_text) {
let text_content = "";
const captionEntries = caption_text.split(/\n\n/);
for (const entry of captionEntries) {
const lines = entry.trim().split('\n');
if (lines.length >= 2 && !lines[1].includes('-->')) {
text_content += lines.slice(1).join(' ').trim() + ' ';
}
}
return text_content
}
function parseXMLCaptions(xmlString) {
return new Promise((resolve, reject) => {
const parser = new xml2js.Parser();
parser.parseString(xmlString, (err, result) => {
if (err) {
reject(err);
} else {
const textElements = result.transcript.text;
let captionText = textElements.map(element => element._).join('\n');
resolve(captionText.trim());
}
});
});
}
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
export async function getVideoWithCaptions(video_url) {
const info = await ytdl.getInfo(video_url, {agent});
const tracks = info
.player_response.captions
.playerCaptionsTracklistRenderer.captionTracks;
if (!tracks || tracks.length < 1) throw new Error("no_captions");
const track = tracks.find(t => t.languageCode == "en");
if(!track) throw new Error("no_captions");
const captions = await (await fetch(`${track.baseUrl}&fmt=srv1`)).text();
return {
title: info.videoDetails.title,
description: info.videoDetails.description,
captions: await parseXMLCaptions(captions)
}
}

324
test.md Normal file
View File

@ -0,0 +1,324 @@
last week AMD launched their new
graphics cards and I already reviewed
the RX 7900 XTX so today I'm going to
talk about this RX 7900 XT now I did not
have a chance to test the reference card
but I do have this tough gaming model
from Asus and while I do think that AMD
has done a great job at creating
high-end graphics cards and bringing
some well needed competition to the
market I also think that there are
plenty of things that still need a bit
of work and that should be improved so
I'm going to take a bit of a different
approach today and I'm going to talk
about my issues with this 7900 XT let's
go this video is brought to you by
Corsair and their brand new Dominator
Platinum RGB memory these super fast
ddr5 6000 Hertz memory kits are
specifically made for ryzen 7000 series
CPUs they feature a stylish aluminum
heat spreader with dhx technology that
keeps them nice and cool under load
offering a smooth and stable performance
with a lot out of room for overclocking
and they come with 12 customizable
kapelix LEDs that you can control with
their IQ software and easily sync up
with your other coarser components check
them out using the links in the
description below one of the first and
most obvious issues I had with this card
is its name the RX 7900 XT sounds very
similar to the 7900 XTX even though when
we look at the specs you can see that
they are vastly different the XCX has
more cores higher clock speeds more
memory and a bigger memory bus than the
XT and I don't think that removing One X
at the end of the product name is enough
to distinguish and obviously lower tier
card from a higher tier one it just
causes a lot of confusion especially for
the less informed consumers that might
think that they are buying a very
similar product for a hundred dollars
less which is definitely not the case
here now Nvidia received a lot of
criticism for their 4086 16 gig and 4080
12 gig cards and AMD here is pretty much
doing the same thing the XT is more than
15 slower than the xdx and they should
have called it a 7800 XT or at least a
7900 without any X's just to make it a
bit more obvious to everyone and that
brings me to my next issue the relative
performance because the XT is priced so
closely to the XTX its performance
should also be at a close distance
compared to the xdx and that is
definitely not the case here so the xt's
recommended price is only 10 lower than
the xdx but when it comes to Performance
the XTX is 16 faster than the XD and
keep in mind I was using the relatively
fast third-party version of the XT so
for 100 more you can get a card that is
16 faster and if you already decided to
spend 900 on a GPU upgrading to dxdx for
a hundred dollars more just seems like a
logical choice and that pressure from
the relative performance is a bit of a
shame because in absolute terms the XT
is a pretty powerful chip at 1440p
resolution it shows a great High refresh
rate experience in every game I've
tested and most games will easily run at
144 FPS or above but even the heaviest
titles like Dying Light 2 and cyberpunk
2077 for example will get pretty close
to that at 4K resolution the XC does
well offering that 4K 120 experience in
lighter titles and 60-ish FPS or above
in heavier games amd's FSR has also been
adopted by a lot of game developers so
for the titles that do support it this
7900 XT is actually a very capable 4K
gaming card as well but should you buy a
much stronger XTX for 100 more
absolutely now the next issue I had was
performance inconsistency when testing
Call of Duty Modern Warfare we were
getting really inconsistent results so
one moment the XT would be running at
around 2400 megahertz showing FPS
numbers that kind of fall in line with
other cards but during some other runs
it would suddenly boost much higher
somewhere closer to 2 900 megahertz
which would result in a much higher
average FPS and which would put it above
the RTX 4090 and the 7900 XDS and there
was absolutely no logic behind it so it
was the same system same settings no
updates in between the CPU wasn't
throttling or anything else that we can
think of actually so every time we
reboot the PC it would act differently
for no clear reason now in this
particular game it doesn't really matter
as much because in the worst case
scenario it was close to an RTX 4090 and
in the best case scenario it was
actually beating it which is definitely
not bad for a GPU that costs half as
much but seeing this inconsistent
Behavior always causes doubt whether
everything is working well as it should
and what kind of an impact does it have
in titles where this card was not doing
as well but AMD has been on top of this
since I mentioned it and they are trying
to figure it out but there are no clear
answers just yet now hopefully they will
come up with a driver update that will
stabilize this situation a bit more now
my next issue is a really noticeable one
coil line in my review of the XTX I
mentioned that my reference card had
some coil line now coil wine is mostly
sample specific and then it is often
specific to certain games or super high
FPS gameplay but the XT here had a
noticeable amount of coal wine regularly
and since the launch a lot of reviewers
as well as people who bought their cards
frequently mentioned coil wine too both
in reference cards as well as third
party cards so this tough gaming from
Asus doesn't always whine but it does
sit pretty frequently and since the
sound often changes depending on what's
going on with your PC it is way more
noticeable than if it was just a
consistent tone I did manage to test the
thermal and noise performance at one
point where it wasn't whining and this
oversized tough cooler performed
extremely well barely going over 50
degrees with almost no noise at all and
even though this should be a great
cooler design it is only quiet sometimes
the other times it is just super whiny
and given the fact that these reports
are so widespread with all sorts of
Radeon cards it just feels like this is
something that AMD really needs to work
on my next issue is the power
consumption especially in idle when
having multiple monitors connected to
your cart if I just connect a single
monitor power consumption is completely
fine with a single watt of a difference
between the 4080 and this 7900 XD but
when connecting two monitors so I had
one 4K 120 hertz one and one 4K 60hz one
the power consumption on the XT went up
by a lot it was 15 watts higher than the
4080 and I would say that is a
significant change now depending on how
much you pay for your electricity and
how many hours you have your monitors on
per day this could really add up quickly
now supposedly this should get fixed
with the next driver so I will put an
update in the comment under this video
once I learn more about this
but perhaps the biggest issue right now
is the supply and pricing before these
cards launch there were plenty of posts
saying that there should be plenty of
stock and that the prices should be as
promised but in reality the supply so
far hasn't been as good it will vary per
region but if I look at the Netherlands
I haven't seen a single xdx listed at
any reputable retailer and even the XT
which had more critical reviews overall
has been really hard to find most shops
I spoke to told me that they have no
clear indication of stock on any Radeon
cards at this point and the couple shops
that did list the XT seem to have very
limited stock and they're selling them
at very high prices so right now the
cheapest one I can find is at 1300 Euros
which is 250 euros over the MSRP and
only about 150 Euros less than a 4080
and a 4080 is faster has stronger Ray
tracing performance and it is more power
efficient and I think all this was much
worse because of the hype that followed
the AMD launch and the belief that there
might actually be some stock or that the
prices might actually stay down anyway
the launch was definitely a bit shaky
but it is important to remember that
most of the things I mentioned today are
absolutely fixable and that was the
reason of making this video because I
know know that AMD cares about criticism
and that they do want to fix things for
the end users at least the things that
can be fixed because as I said before
the absolute performance of this 7900 XT
is quite good and the only way to fix
their relative performance is to lower
the price because if it gets
significantly cheaper I'm sure that most
of us just would not care about the
confusing name business or the fact that
it is boosting a bit higher one day and
then a bit lower the next now the coil
wine is a bit more complicated to fix
especially for the cars that already
love the factory but for future batches
that too is definitely fixable and they
definitely should fix it because the GPU
Market is so competitive and even a
single downside like coil wine for
example can make a big difference at the
end of the day now that is all I have
for today I hope this video was
interesting enough let me know in the
comments down below what are your
thoughts about the XD and if you want to
make sure not to miss any of my future
videos try to click that subscribe
button bye guys and see in the next one
Prompt 0: Split text : Pass 2k characters in to the context and it will return a json with the required text
```
You are a personal assistant of mine. I need you to take youtube captions and split them into 1500-2000 character blocks. The blocks should all be self-enclosing. Meaning no context from the 1st block is needed in the 2nd block etc. for it to make sense. Omit any sponsorships and mentions of "this video was brought by".
VERY IMPORTANT: Respond with a JSON compatible array of strings.
```
Prompt 1: Extract main points
```
<system>
You are my personal assistant. I have a youtube video that needs to be broken down into the main points. Please take the captions I have given you and extract the main talking points along with quotes from the script. Send only a list of points with a quote attached to each point. Do not mention "The reviewer" or "You", only say full statements. Ignore any sponsorships or "this video was brought to you by" statements. Those should be excluded from the list of talking points. The talking points returned should look like this:
* Main talking point (Quote: "Quote from the captions")
</system>
<captions>
a part of the captions
</captions>
<response>
```
Prompt 2: Combine into blog mistral-7b-instruct-0.2
```
You are a copywriter. I just got my personal assistant to take the main talking points and quotes from a youtube video of mine. Take these main talking points and write an engaging blog article.
<talkingpoints>
all talking points from previous prompt
</talkingpoints>
<response>
```
Thoughs: The response was generally pretty OK. Got like 60% on quillbot AI detector.
Prompt 2 v2: Caption directly into blog
```
You are a copywriter. Your job is to write blog articles for all of my youtube videos to help with SEO and channel growth. I provide you with SRT captions from my youtube videos and you return an *engaging* blog article I can post to my website. You are allowed to use markdown for formatting. Make sure you do not mention any sponsors or "video was brough to you by" in the blog article.
```
Thoughts: Very robotic, didn't like it. Also tended to imagine stuff.
Prompt 2 v3: Main talking points
```
Take the main points and quotes from my YouTube video, picked out by my assistant, and turn them into a fun and informative blog post. The topic can change depending on the video. Keep it easy to read and relatable, like chatting with a friend. You can use markdown if it helps. The aim is to make content that feels natural and connects well with my audience, while still getting across the main ideas from the video.
```
full response. Model used: mistral-7b-instruct-v0.2
Title: My Honest Take on the AMD Radeon RX 7900 XT: A Powerful Card with Room for Improvement
Last week, AMD made waves in the tech world with the launch of their new graphics cards, specifically the RX 7900 XTX. As a tech enthusiast and content creator, I was excited to get my hands on the latest offering from AMD. However, due to unforeseen circumstances, I wasn't able to test the reference card. Instead, I've had the opportunity to review the Asus model of the RX 7900 XT.
Before we dive into the specifics of my review, I want to acknowledge AMD's success in creating high-end graphics cards and bringing some much-needed competition to the market. The graphics card landscape has been dominated by Nvidia for quite some time, and AMD's entry into the high-end market is a welcome change.
That being said, I also think that there are plenty of things that still need improvement with the RX 7900 XT. One of the most significant issues is the card's name and the confusion it causes for consumers. The XT model shares a similar name to the XTX, despite significant differences in specs. The XT has fewer cores, lower clock speeds, less memory, and a smaller memory bus than the XTX. AMD is pretty much doing the same thing Nvidia did with their controversial 4086 16 gig and 4080 12 gig cards. For 100 more, you can get a card that is 16 faster.
Another issue I've encountered is the inconsistent performance of the RX 7900 XT. In Call of Duty Modern Warfare, for instance, the card's performance was inconsistent, with one moment showing FPS numbers that align with other cards, and the next moment boosting much higher, outperforming the RTX 4090 and RTX 7900 XT. Every time we rebooted the PC, the XT acted differently for no clear reason. Seeing this inconsistent behavior always causes doubt whether everything is working well as it should.
Additionally, the ASUS TUF Gaming Radeon RX 7900 XT graphics card can produce coil whine, which is more noticeable due to its variable sound. This TUF Gaming from Asus doesn't always whine, but it does sit pretty frequently. And since the sound often changes depending on what's going on with your PC, it is way more noticeable than if it was just a consistent tone.
The oversized cooler on the TUF Gaming RX 7900 XT performs well when it's not whining, maintaining temperatures below 50 degrees with minimal noise. I managed to test the thermal and noise performance at one point where it wasn't whining, and this oversized Tough Cooler performed extremely well, barely going over 50 degrees with almost no noise at all.
However, there are a few other issues that need addressing. Power consumption is higher when using multiple monitors with the Radeon RX 7900 XT, leading to increased electricity costs. When connecting two monitors, the power consumption on the XT went up by a lot, 15 watts higher than the 4080.
The launch of the Radeon RX 7900 XT was definitely a bit shaky, but it is important to remember that most of the issues I've mentioned today are absolutely fixable. The only way to fix their relative performance is to lower the price. Coil whine is a bit more complicated to fix, especially for the cards that already left the factory. But for future batches, that too is definitely fixable.
In conclusion, the AMD Radeon RX 7900 XT is a powerful card with room for improvement. It offers a great high refresh rate experience at 1440p resolution and is capable of 4K gaming with FSR support. However, the inconsistent performance, coil whine, and power consumption issues are significant concerns that need to be addressed. AMD has the potential to create a truly competitive high-end graphics card, and I'm excited to see how they Title: My Honest Take on the AMD Radeon RX 7900 XT: A Powerful Card with Room for Improvement
Last week, AMD made waves in the tech world with the launch of their new graphics cards, specifically the RX 7900 XTX. As a tech enthusiast and content creator, I was excited to get my hands on the latest offering from AMD. However, due to unforeseen circumstances, I wasn't able to test the reference card. Instead, I've had the opportunity to review the Asus model of the RX 7900 XT.
Before we dive into the specifics of my review, I want to acknowledge AMD's success in creating high-end graphics cards and bringing some much-needed competition to the market. The graphics card landscape has been dominated by Nvidia for quite some time, and AMD's entry into the high-end market is a welcome change.
That being said, I also think that there are plenty of things that still need improvement with the RX 7900 XT. One of the most significant issues is the card's name and the confusion it causes for consumers. The XT model shares a similar name to the XTX, despite significant differences in specs. The XT has fewer cores, lower clock speeds, less memory, and a smaller memory bus than the XTX. AMD is pretty much doing the same thing Nvidia did with their controversial 4086 16 gig and 4080 12 gig cards. For 100 more, you can get a card that is 16 faster.
Another issue I've encountered is the inconsistent performance of the RX 7900 XT. In Call of Duty Modern Warfare, for instance, the card's performance was inconsistent, with one moment showing FPS numbers that align with other cards, and the next moment boosting much higher, outperforming the RTX 4090 and RTX 7900 XT. Every time we rebooted the PC, the XT acted differently for no clear reason. Seeing this inconsistent behavior always causes doubt whether everything is working well as it should.
Additionally, the ASUS TUF Gaming Radeon RX 7900 XT graphics card can produce coil whine, which is more noticeable due to its variable sound. This TUF Gaming from Asus doesn't always whine, but it does sit pretty frequently. And since the sound often changes depending on what's going on with your PC, it is way more noticeable than if it was just a consistent tone.
The oversized cooler on the TUF Gaming RX 7900 XT performs well when it's not whining, maintaining temperatures below 50 degrees with minimal noise. I managed to test the thermal and noise performance at one point where it wasn't whining, and this oversized Tough Cooler performed extremely well, barely going over 50 degrees with almost no noise at all.
However, there are a few other issues that need addressing. Power consumption is higher when using multiple monitors with the Radeon RX 7900 XT, leading to increased electricity costs. When connecting two monitors, the power consumption on the XT went up by a lot, 15 watts higher than the 4080.
The launch of the Radeon RX 7900 XT was definitely a bit shaky, but it is important to remember that most of the issues I've mentioned today are absolutely fixable. The only way to fix their relative performance is to lower the price. Coil whine is a bit more complicated to fix, especially for the cards that already left the factory. But for future batches, that too is definitely fixable.
In conclusion, the AMD Radeon RX 7900 XT is a powerful card with room for improvement. It offers a great high refresh rate experience at 1440p resolution and is capable of 4K gaming with FSR support. However, the inconsistent performance, coil whine, and power consumption issues are significant concerns that need to be addressed. AMD has the potential to create a truly competitive high-end graphics card, and I'm excited to see how they will address these issues moving forward.

2
test.txt Normal file

File diff suppressed because one or more lines are too long

3478
yarn.lock

File diff suppressed because it is too large Load Diff