Compare commits
10 Commits
aaadc4f4d6
...
29086b57fd
Author | SHA1 | Date | |
---|---|---|---|
29086b57fd | |||
0e16979232 | |||
|
b5f39cdecc | ||
|
9e20733ff8 | ||
|
b542768e9e | ||
|
a18a442485 | ||
|
ef231be351 | ||
|
c24590c90f | ||
|
0a08c131cc | ||
|
c82e2aaf2c |
4
.env.example
Normal file
4
.env.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
DATABASE_URL=postgres://user:password@127.0.0.1:5432/postgres
|
||||||
|
REDIS_URL=redis://127.0.0.1:6379/
|
||||||
|
PORT=8080
|
||||||
|
HOST=127.0.0.1
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"es2021": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": "xo",
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"extends": ["xo-typescript"],
|
|
||||||
"files": ["*.ts", "*.tsx"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// double quotes
|
|
||||||
"@typescript-eslint/quotes": "double"
|
|
||||||
}
|
|
||||||
}
|
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
env
|
env
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
dist
|
||||||
|
.env
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["samverschueren.linter-xo"]
|
||||||
|
}
|
39
README.md
39
README.md
@ -1,3 +1,38 @@
|
|||||||
# fastify-drizzle-quick-start
|
# fastify-jsdoc-quick-start
|
||||||
|
|
||||||
Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM
|
Quick start for Fastify, JSDoc, ESlint, Prettier and DrizzleORM
|
||||||
|
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Comes equipped with
|
||||||
|
|
||||||
|
- Dockerfile including postgres and redis
|
||||||
|
- JSDoc type annotations
|
||||||
|
- XO
|
||||||
|
- Prettier
|
||||||
|
- DrizzleORM
|
||||||
|
- Fastify
|
||||||
|
- Zod
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Docker](https://docs.docker.com/get-docker/)
|
||||||
|
- [Yarn](https://yarnpkg.com/getting-started/install)
|
||||||
|
- [NodeJS](https://nodejs.org/en/download/)
|
||||||
|
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
1. Clone the repo
|
||||||
|
2. Run `yarn install`
|
||||||
|
3. Run `docker-compose up -d` (-d to detach from the terminal)
|
||||||
|
4. Run `yarn dev` to start the dev server
|
||||||
|
5. Make your changes
|
||||||
|
6. Run `yarn lint` to lint your code
|
||||||
|
7. Run `yarn start` to start the production server
|
||||||
|
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
|
||||||
|
Originally cloned from https://github.com/Looskie/fastify-drizzle-quick-start and transformed to use JSDoc over Typescript
|
12
drizzle.config.js
Normal file
12
drizzle.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as dotenv from 'dotenv'
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
"out": "./src/db/migrations",
|
||||||
|
"schema": "./src/db/schemas.js",
|
||||||
|
"driver": "pg",
|
||||||
|
"dbCredentials": {
|
||||||
|
"connectionString": process.env.DATABASE_URL
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"out": "./src/db/migrations",
|
|
||||||
"schema": "./src/db/schemas.ts"
|
|
||||||
}
|
|
70
package.json
70
package.json
@ -2,38 +2,66 @@
|
|||||||
"name": "fastify-drizzle-quick-start",
|
"name": "fastify-drizzle-quick-start",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM",
|
"description": "Quick start for Fastify, TypeScript, ESlint, Prettier and DrizzleORM",
|
||||||
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "https://github.com/Looskie/fastify-drizzle-quick-start.git",
|
"repository": "https://github.com/Looskie/fastify-drizzle-quick-start.git",
|
||||||
"author": "Cody Miller <50378828+Looskie@users.noreply.github.com>",
|
"author": "Cody Miller <50378828+Looskie@users.noreply.github.com>",
|
||||||
"license": "none",
|
"license": "none",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts",
|
"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",
|
||||||
|
"start": "node src/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cli-color": "^2.0.2",
|
"@types/cli-color": "^2.0.6",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.10.4",
|
||||||
"@types/pg": "^8.10.2",
|
"@types/pg": "^8.10.9",
|
||||||
"@typescript-eslint/eslint-plugin": ">=5.57.0",
|
|
||||||
"@typescript-eslint/parser": ">=5.57.0",
|
|
||||||
"cli-color": "^2.0.3",
|
"cli-color": "^2.0.3",
|
||||||
"dotenv": "^16.1.4",
|
"dotenv": "^16.3.1",
|
||||||
"drizzle-kit": "^0.18.1",
|
"drizzle-kit": "^0.20.6",
|
||||||
"eslint": ">=8.0.0",
|
"prettier": "^3.1.1",
|
||||||
"eslint-config-xo": "^0.43.1",
|
"tsc-alias": "^1.8.8",
|
||||||
"eslint-config-xo-typescript": "^0.57.0",
|
|
||||||
"prettier": "^2.8.8",
|
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
"typescript": ">=4.4"
|
"typescript": "^5.3.3",
|
||||||
|
"xo": "^0.56.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.3.0",
|
"@fastify/cors": "^8.4.2",
|
||||||
"drizzle-orm": "^0.26.5",
|
"@fastify/oauth2": "^7.8.0",
|
||||||
"fastify": "^4.18.0",
|
"drizzle-orm": "^0.29.1",
|
||||||
"pg": "^8.11.0",
|
"fastify": "^4.25.0",
|
||||||
"redis": "^4.6.7",
|
"fastify-plugin": "^4.5.1",
|
||||||
"zod": "^3.21.4"
|
"googleapis": "^134.0.0",
|
||||||
|
"pg": "^8.11.3",
|
||||||
|
"redis": "^4.6.11",
|
||||||
|
"simple-get": "^4.0.1",
|
||||||
|
"tsc": "^2.0.4",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"rules": {
|
||||||
|
"import/extensions": "off",
|
||||||
|
"@typescript-eslint/quotes": [
|
||||||
|
"error",
|
||||||
|
"double"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/indent": [
|
||||||
|
"error",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"@typescript-eslint/object-curly-spacing": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"n/prefer-global/process": "off",
|
||||||
|
"@typescript-eslint/no-extraneous-class": "off",
|
||||||
|
"arrow-parens": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-floating-promises": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
src/db/index.js
Normal file
41
src/db/index.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import * as schema from "./schemas.js";
|
||||||
|
import { env, Logger } from "../utils/index.js";
|
||||||
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||||
|
import pgpkg from 'pg';
|
||||||
|
const { Pool } = pgpkg;
|
||||||
|
|
||||||
|
/** @type {ReturnType<typeof drizzle<typeof schema>>} */
|
||||||
|
export let db;
|
||||||
|
|
||||||
|
export const initDb = async () => {
|
||||||
|
const pool = await new Pool({
|
||||||
|
connectionString: env.DATABASE_URL,
|
||||||
|
idleTimeoutMillis: 20000
|
||||||
|
})
|
||||||
|
.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, {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
|
||||||
|
// await migrate(db, {
|
||||||
|
// migrationsFolder: "./src/db/migrations",
|
||||||
|
// })
|
||||||
|
// .then(() => {
|
||||||
|
// Logger.info("INIT", "Migrated database");
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// Logger.error("INIT", `Failed to migrate database ${String(error)}`);
|
||||||
|
// throw new Error(`Failed to migrate database ${String(error)}`);
|
||||||
|
// });
|
||||||
|
};
|
@ -1,36 +0,0 @@
|
|||||||
import * as schema from "@api/db/schemas";
|
|
||||||
import { env, Logger } from "@api/utils";
|
|
||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
|
||||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
||||||
import { Pool } from "pg";
|
|
||||||
|
|
||||||
export let db: ReturnType<typeof drizzle<typeof schema>>;
|
|
||||||
|
|
||||||
export const initDb = async () => {
|
|
||||||
const pool = await new Pool({
|
|
||||||
connectionString: env.DATABASE_URL,
|
|
||||||
})
|
|
||||||
.connect()
|
|
||||||
.then((client) => {
|
|
||||||
Logger.info("INIT", "Connected to database");
|
|
||||||
|
|
||||||
return client;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
Logger.error("INIT", `Failed to connect to database ${String(err)}}`);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
db = drizzle(pool);
|
|
||||||
|
|
||||||
await migrate(db, {
|
|
||||||
migrationsFolder: "./src/db/migrations",
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
Logger.info("INIT", "Migrated database");
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
Logger.error("INIT", `Failed to migrate database ${String(err)}`);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
};
|
|
43
src/db/migrations/0000_huge_ares.sql
Normal file
43
src/db/migrations/0000_huge_ares.sql
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "sessions" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"user_id" uuid,
|
||||||
|
"google_code" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "sites" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"user_id" uuid,
|
||||||
|
"name" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "users" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"google_id" text,
|
||||||
|
"name" text,
|
||||||
|
"email" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "articles" ADD CONSTRAINT "articles_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "sites"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("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 "users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
@ -1,5 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS "users" (
|
|
||||||
"id" serial PRIMARY KEY NOT NULL,
|
|
||||||
"full_name" text,
|
|
||||||
"phone" varchar(256)
|
|
||||||
);
|
|
6
src/db/migrations/0001_slim_xavin.sql
Normal file
6
src/db/migrations/0001_slim_xavin.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE "sessions" ADD COLUMN "google_access_token" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" ADD COLUMN "google_refresh_token" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" ADD COLUMN "expires_at" date;--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD COLUMN "google_refresh_token" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sessions" DROP COLUMN IF EXISTS "google_code";--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" DROP COLUMN IF EXISTS "google_id";
|
1
src/db/migrations/0002_small_forge.sql
Normal file
1
src/db/migrations/0002_small_forge.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "users" ADD COLUMN "channel_id" text;
|
1
src/db/migrations/0003_elite_falcon.sql
Normal file
1
src/db/migrations/0003_elite_falcon.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "users" DROP COLUMN IF EXISTS "google_refresh_token";
|
1
src/db/migrations/0004_daffy_ironclad.sql
Normal file
1
src/db/migrations/0004_daffy_ironclad.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "users" ADD COLUMN "google_id" text;
|
1
src/db/migrations/0005_tricky_sheva_callister.sql
Normal file
1
src/db/migrations/0005_tricky_sheva_callister.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "users" ADD COLUMN "uploads_playlist_id" text;
|
1
src/db/migrations/0006_wide_the_hunter.sql
Normal file
1
src/db/migrations/0006_wide_the_hunter.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "sessions" ALTER COLUMN "expires_at" SET DATA TYPE timestamp;
|
@ -1,35 +1,178 @@
|
|||||||
{
|
{
|
||||||
|
"id": "b6cb5336-be20-4e96-861f-fff166b013c7",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "pg",
|
"dialect": "pg",
|
||||||
"id": "478200dd-a614-41dd-9cd1-f6368405417e",
|
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"tables": {
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_code": {
|
||||||
|
"name": "google_code",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
"users": {
|
"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": {
|
"google_id": {
|
||||||
"name": "full_name",
|
"name": "google_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
"phone": {
|
"name": {
|
||||||
"name": "phone",
|
"name": "name",
|
||||||
"type": "varchar(256)",
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
"compositePrimaryKeys": {}
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
"enums": {},
|
||||||
|
197
src/db/migrations/meta/0001_snapshot.json
Normal file
197
src/db/migrations/meta/0001_snapshot.json
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"id": "de9d771e-4272-49d2-bcc7-6eb5291bcd51",
|
||||||
|
"prevId": "b6cb5336-be20-4e96-861f-fff166b013c7",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "date",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
203
src/db/migrations/meta/0002_snapshot.json
Normal file
203
src/db/migrations/meta/0002_snapshot.json
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"id": "ef238b19-05f5-4be9-90f8-56f4a08bb576",
|
||||||
|
"prevId": "de9d771e-4272-49d2-bcc7-6eb5291bcd51",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "date",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
197
src/db/migrations/meta/0003_snapshot.json
Normal file
197
src/db/migrations/meta/0003_snapshot.json
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"id": "d7ae7ea1-7218-4141-a9a9-35395e42e9bd",
|
||||||
|
"prevId": "ef238b19-05f5-4be9-90f8-56f4a08bb576",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "date",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
203
src/db/migrations/meta/0004_snapshot.json
Normal file
203
src/db/migrations/meta/0004_snapshot.json
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"id": "130129fa-d718-4be8-8ffe-f285dce85195",
|
||||||
|
"prevId": "d7ae7ea1-7218-4141-a9a9-35395e42e9bd",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "date",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"google_id": {
|
||||||
|
"name": "google_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
209
src/db/migrations/meta/0005_snapshot.json
Normal file
209
src/db/migrations/meta/0005_snapshot.json
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"id": "4c4cea1e-1ffe-4736-b045-7dadd7e22244",
|
||||||
|
"prevId": "130129fa-d718-4be8-8ffe-f285dce85195",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "date",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"google_id": {
|
||||||
|
"name": "google_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"uploads_playlist_id": {
|
||||||
|
"name": "uploads_playlist_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
209
src/db/migrations/meta/0006_snapshot.json
Normal file
209
src/db/migrations/meta/0006_snapshot.json
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
{
|
||||||
|
"id": "3ec0762e-c27b-4b7f-b4a6-4db07400a5ae",
|
||||||
|
"prevId": "4c4cea1e-1ffe-4736-b045-7dadd7e22244",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"articles": {
|
||||||
|
"name": "articles",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"source_video_id": {
|
||||||
|
"name": "source_video_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"articles_site_id_sites_id_fk": {
|
||||||
|
"name": "articles_site_id_sites_id_fk",
|
||||||
|
"tableFrom": "articles",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_access_token": {
|
||||||
|
"name": "google_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_refresh_token": {
|
||||||
|
"name": "google_refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_user_id_users_id_fk": {
|
||||||
|
"name": "sites_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"google_id": {
|
||||||
|
"name": "google_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"name": "channel_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"uploads_playlist_id": {
|
||||||
|
"name": "uploads_playlist_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,51 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1686356934306,
|
"when": 1713532681066,
|
||||||
"tag": "0000_plain_killmonger",
|
"tag": "0000_huge_ares",
|
||||||
"breakpoints": false
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713547912112,
|
||||||
|
"tag": "0001_slim_xavin",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713549800799,
|
||||||
|
"tag": "0002_small_forge",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713550262815,
|
||||||
|
"tag": "0003_elite_falcon",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713550343102,
|
||||||
|
"tag": "0004_daffy_ironclad",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713551541682,
|
||||||
|
"tag": "0005_tricky_sheva_callister",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 6,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1713614011867,
|
||||||
|
"tag": "0006_wide_the_hunter",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
31
src/db/schemas.js
Normal file
31
src/db/schemas.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { date, foreignKey, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const users = pgTable("users", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
google_id: text("google_id"),
|
||||||
|
name: text("name"),
|
||||||
|
email: text("email"),
|
||||||
|
channel_id: text("channel_id"),
|
||||||
|
uploads_playlist_id: text("uploads_playlist_id")
|
||||||
|
});
|
||||||
|
|
||||||
|
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 const site = pgTable("sites", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
user_id: uuid("user_id").references(() => users.id),
|
||||||
|
name: text("name")
|
||||||
|
})
|
||||||
|
|
||||||
|
export const article = pgTable("articles", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
site_id: uuid("site_id").references(() => site.id),
|
||||||
|
content: text("content"),
|
||||||
|
source_video_id: text("source_video_id")
|
||||||
|
})
|
@ -1,7 +0,0 @@
|
|||||||
import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const users = pgTable("users", {
|
|
||||||
id: serial("id").primaryKey(),
|
|
||||||
fullName: text("full_name"),
|
|
||||||
phone: varchar("phone", { length: 256 }),
|
|
||||||
});
|
|
21
src/fastify.d.ts
vendored
Normal file
21
src/fastify.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {OAuth2Namespace} from "@fastify/oauth2";
|
||||||
|
import type { db } from "./db";
|
||||||
|
import type { Redis } from "./utils";
|
||||||
|
|
||||||
|
declare module "fastify" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
interface FastifyRequest {
|
||||||
|
// db: typeof db;
|
||||||
|
// redis: typeof Redis;
|
||||||
|
session?: {
|
||||||
|
id: string,
|
||||||
|
user_id: string,
|
||||||
|
google_access_token: string,
|
||||||
|
google_refresh_token: string,
|
||||||
|
expires_at: Date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface FastifyInstance {
|
||||||
|
googleOAuth2: OAuth2Namespace
|
||||||
|
}
|
||||||
|
}
|
77
src/index.js
Normal file
77
src/index.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { initDb } from "./db/index.js";
|
||||||
|
import { channelRoutes, authRoutes } from "./routes/index.js";
|
||||||
|
import { env, Logger, Redis } from "./utils/index.js";
|
||||||
|
import fastify from "fastify";
|
||||||
|
import { middleware } from "./modules/middleware.js";
|
||||||
|
import oauth from '@fastify/oauth2';
|
||||||
|
import { videoRoutes } from "./routes/videos.js";
|
||||||
|
|
||||||
|
const API_VERSION = "v1";
|
||||||
|
|
||||||
|
export const main = async () => {
|
||||||
|
const server = fastify({
|
||||||
|
bodyLimit: 1_000_000,
|
||||||
|
trustProxy: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await initDb();
|
||||||
|
// await Redis.initialize();
|
||||||
|
|
||||||
|
server.register(middleware);
|
||||||
|
server.register(import("@fastify/cors"), {
|
||||||
|
maxAge: 600,
|
||||||
|
origin: true,
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
server.register(oauth, {
|
||||||
|
name: 'googleOAuth2',
|
||||||
|
scope: ['https://www.googleapis.com/auth/youtube.readonly', 'https://www.googleapis.com/auth/youtube.force-ssl', "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"],
|
||||||
|
credentials: {
|
||||||
|
client: {
|
||||||
|
id: '103003963446-6m80lqbe4bkgjv1nq5ihmju0bb6agkkg.apps.googleusercontent.com',
|
||||||
|
secret: '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'
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
// facebook redirect here after the user login
|
||||||
|
callbackUri: 'http://localhost:3000/auth/google/callback'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
server.register(channelRoutes, {
|
||||||
|
prefix: `/`,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.register(videoRoutes, {
|
||||||
|
prefix: `/videos`,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.register(authRoutes, {
|
||||||
|
prefix: `/auth`,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.get("/hello", (req, res) => {
|
||||||
|
res.send("world");
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen({ host: env.HOST, port: env.PORT }, (error, address) => {
|
||||||
|
if (error) {
|
||||||
|
Logger.error("INIT", error.message);
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info("INIT", `Server listening at ${address}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
40
src/index.ts
40
src/index.ts
@ -1,40 +0,0 @@
|
|||||||
import { initDb } from "@api/db";
|
|
||||||
import { testRoutes } from "@api/routes";
|
|
||||||
import { env, Logger, Redis } from "@api/utils";
|
|
||||||
import fastify from "fastify";
|
|
||||||
|
|
||||||
const API_VERSION = "v1";
|
|
||||||
|
|
||||||
export const main = async () => {
|
|
||||||
const server = fastify({
|
|
||||||
bodyLimit: 1_000_000,
|
|
||||||
trustProxy: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await initDb();
|
|
||||||
await Redis.initialize();
|
|
||||||
|
|
||||||
server.register(import("@fastify/cors"), {
|
|
||||||
maxAge: 600,
|
|
||||||
origin: true,
|
|
||||||
credentials: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
server.register(testRoutes, {
|
|
||||||
prefix: `/${API_VERSION}/test`,
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen({ host: env.HOST, port: env.PORT }, (err, address) => {
|
|
||||||
if (err) {
|
|
||||||
Logger.error("INIT", err.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.info("INIT", `Server listening at ${address}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return server;
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
44
src/modules/middleware.js
Normal file
44
src/modules/middleware.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
|
||||||
|
import fp from "fastify-plugin";
|
||||||
|
import { Redis } from "../utils/index.js";
|
||||||
|
import { db } from "../db/index.js";
|
||||||
|
import { sessions } from "../db/schemas.js";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
const middleware = fp(
|
||||||
|
/**
|
||||||
|
* @param {FastifyInstance} fastify
|
||||||
|
* @param {unknown} _options
|
||||||
|
*/
|
||||||
|
async (fastify, _options) => {
|
||||||
|
fastify.addHook("onRequest", async (request) => {
|
||||||
|
// request.redis = Redis;
|
||||||
|
// request.db = db;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const authMiddleware = fp(
|
||||||
|
async (fastify, _options) => {
|
||||||
|
fastify.addHook("preValidation", async (request, response) => {
|
||||||
|
try {
|
||||||
|
if (!request.headers.authorization || !request.headers.authorization.startsWith("Bearer")) {
|
||||||
|
response.status(401).send("Missing authentication token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = request.headers.authorization.split(" ")[1];
|
||||||
|
const session = await db.select().from(sessions).where(eq(sessions.id, token));
|
||||||
|
if(session.length == 0) {
|
||||||
|
response.status(401).send("Invalid authentication token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log(token);
|
||||||
|
// console.log(session);
|
||||||
|
request.session = session[0];
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export { middleware, authMiddleware };
|
68
src/routes/auth.js
Normal file
68
src/routes/auth.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import sget from 'simple-get';
|
||||||
|
import { createAuthToken as createSession } from '../utils/token.js';
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
import { getChannelInfo, getUserInfo } from '../utils/youtube.js';
|
||||||
|
import { db } from '../db/index.js';
|
||||||
|
import { users as usersTable } from '../db/schemas.js';
|
||||||
|
import { userInfo } from 'os';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {FastifyInstance} fastify
|
||||||
|
* @param {unknown} _
|
||||||
|
* @param {() => void} done
|
||||||
|
*/
|
||||||
|
export const authRoutes = (fastify, _, done) => {
|
||||||
|
fastify.get("/google/callback", async (request, response) => {
|
||||||
|
try {
|
||||||
|
const { token } = await fastify.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);
|
||||||
|
|
||||||
|
const user_info = await getUserInfo(token.access_token);
|
||||||
|
if (!user_info.verified_email) {
|
||||||
|
response.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)).then(x=>x[0]);
|
||||||
|
let user;
|
||||||
|
|
||||||
|
if (existing_user) {
|
||||||
|
user = existing_user;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
user = await db.insert(usersTable).values({
|
||||||
|
name: user_info.name,
|
||||||
|
google_id: user_info.id,
|
||||||
|
email: user_info.email,
|
||||||
|
channel_id: channel_info.id,
|
||||||
|
uploads_playlist_id: channel_info.contentDetails.relatedPlaylists.uploads
|
||||||
|
}).returning({ id: usersTable.id }).then(x=>x[0]);
|
||||||
|
|
||||||
|
if (user.length == 0) {
|
||||||
|
response.status(400).send({ success: false, message: "Problem when creating user account" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await createSession(user.id, {
|
||||||
|
access_token: token.access_token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
expires_at: new Date(token.expires_at)
|
||||||
|
});
|
||||||
|
|
||||||
|
response.send({
|
||||||
|
token: session.session_id
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
response.send({ success: false, message: e.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
};
|
36
src/routes/channels.js
Normal file
36
src/routes/channels.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/** @typedef {import("fastify").FastifyInstance} FastifyInstance */
|
||||||
|
|
||||||
|
import { authMiddleware } from "../modules/middleware.js";
|
||||||
|
import { 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 {
|
||||||
|
console.log(request.session);
|
||||||
|
|
||||||
|
const { token } = await fastify.googleOAuth2.getNewAccessTokenUsingRefreshToken({
|
||||||
|
refresh_token: request.session.google_refresh_token,
|
||||||
|
expires_at: request.session.expires_at
|
||||||
|
});
|
||||||
|
console.log(token);
|
||||||
|
const channel = await getChannelInfo(token.access_token);
|
||||||
|
|
||||||
|
response.send({
|
||||||
|
success: true,
|
||||||
|
channel
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
};
|
2
src/routes/index.js
Normal file
2
src/routes/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./channels.js";
|
||||||
|
export * from "./auth.js";
|
@ -1 +0,0 @@
|
|||||||
export * from "./test";
|
|
@ -1,11 +0,0 @@
|
|||||||
import type { FastifyInstance } from "fastify";
|
|
||||||
|
|
||||||
export const testRoutes = (fastify: FastifyInstance, _: unknown, done: () => void) => {
|
|
||||||
fastify.get("/", async (_req, res) => {
|
|
||||||
res.send({
|
|
||||||
hello: "world",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
};
|
|
61
src/routes/videos.js
Normal file
61
src/routes/videos.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/** @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 { getCaptionText, getNewToken, getVideoCaptions, 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 getNewToken(fastify.googleOAuth2, request.session, {});
|
||||||
|
const [user] = await db.select().from(users).where(eq(users.id, request.session.user_id));
|
||||||
|
const videos = await getVideosFromPlaylist(token.access_token, user.uploads_playlist_id);
|
||||||
|
|
||||||
|
response.send({
|
||||||
|
success: true,
|
||||||
|
videos
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.get("/captions/:video_id", async (request, response) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const token = await getNewToken(fastify.googleOAuth2, request.session, {});
|
||||||
|
const captions_list = await getVideoCaptions(token.access_token, request.params.video_id);
|
||||||
|
|
||||||
|
const caption = captions_list.filter(x => x.snippet.language === "en");
|
||||||
|
|
||||||
|
if (caption.length === 0) {
|
||||||
|
response.send({
|
||||||
|
success: false,
|
||||||
|
message: "Couldn't find caption"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const caption_text = await getCaptionText(token.access_token, caption[0].id);
|
||||||
|
|
||||||
|
response.send({
|
||||||
|
captions_info: caption,
|
||||||
|
captions: caption_text
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
done();
|
||||||
|
};
|
@ -1,11 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
|
||||||
const env_schema = z.object({
|
const envSchema = z.object({
|
||||||
DATABASE_URL: z.string().default("postgres://user:password@127.0.0.1:5432/postgres"),
|
DATABASE_URL: z.string().default("postgres://user:password@127.0.0.1:5432/postgres"),
|
||||||
REDIS_URL: z.string().default("redis://127.0.0.1:6379/"),
|
REDIS_URL: z.string().default("redis://127.0.0.1:6379/"),
|
||||||
PORT: z.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"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const env = env_schema.parse(process.env);
|
export const env = envSchema.parse(process.env);
|
3
src/utils/index.js
Normal file
3
src/utils/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./env.js";
|
||||||
|
export * from "./logger.js";
|
||||||
|
export * from "./redis.js";
|
@ -1,3 +0,0 @@
|
|||||||
export * from "./env";
|
|
||||||
export * from "./logger";
|
|
||||||
export * from "./redis";
|
|
32
src/utils/logger.js
Normal file
32
src/utils/logger.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import clc from "cli-color";
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} prefix
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
static info(prefix, message) {
|
||||||
|
console.log(`[${clc.cyan(prefix)}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} prefix
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
static error(prefix, message) {
|
||||||
|
console.log(`[${clc.red(prefix)}] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} prefix
|
||||||
|
* @param {string} message
|
||||||
|
*/
|
||||||
|
static success(prefix, message) {
|
||||||
|
console.log(`[${clc.green(prefix)}] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Logger };
|
@ -1,17 +0,0 @@
|
|||||||
import clc from "cli-color";
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
public static info(prefix: string, message: string) {
|
|
||||||
console.log(`[${clc.cyan(prefix)}] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static error(prefix: string, message: string) {
|
|
||||||
console.log(`[${clc.red(prefix)}] ${message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static success(prefix: string, message: string) {
|
|
||||||
console.log(`[${clc.green(prefix)}] ${message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Logger };
|
|
91
src/utils/redis.js
Normal file
91
src/utils/redis.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import * as redis from "redis";
|
||||||
|
import { env, Logger } from "./index.js";
|
||||||
|
|
||||||
|
/** @typedef {"session" | "visits"} RedisPrefixes */
|
||||||
|
/** @typedef {`${RedisPrefixes}:${string | number}` | RedisPrefixes} RedisKey */
|
||||||
|
|
||||||
|
class Redis {
|
||||||
|
/**
|
||||||
|
* @type {ReturnType<typeof redis.createClient>}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
static redis;
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
static async initialize() {
|
||||||
|
this.redis = redis.createClient({
|
||||||
|
url: env.REDIS_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.redis.on("error", (error) => {
|
||||||
|
Logger.error("INIT", "Failed to connect to redis " + String(error));
|
||||||
|
throw new Error("Failed to connect to redis");
|
||||||
|
});
|
||||||
|
|
||||||
|
this.redis.on("connect", () => {
|
||||||
|
Logger.info("INIT", "Connected to redis");
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.redis.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RedisKey} key Key of the value to set
|
||||||
|
* @param {string} value Value to set
|
||||||
|
* @param {?redis.SetOptions} options Options for the value to set
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await Redis.set("session:user_xxx", new Date().toISOString());
|
||||||
|
* await Redis.set("visits", "1");
|
||||||
|
*/
|
||||||
|
static async set(key, value, options) {
|
||||||
|
await this.redis.set(key, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {RedisKey} key
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
static async incr(key) {
|
||||||
|
await this.redis.incr(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {RedisKey} key
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
static async decr(key) {
|
||||||
|
await this.redis.decr(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* @param {RedisKey} key Key of the value to get
|
||||||
|
* @example
|
||||||
|
* const value = await Redis.get("session:user_xxx");
|
||||||
|
* const visits = await Redis.get("visits");
|
||||||
|
*
|
||||||
|
* @returns {T | undefined}
|
||||||
|
*/
|
||||||
|
static async get(key) {
|
||||||
|
return this.redis.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {RedisKey} key Key of the value to delete
|
||||||
|
* @public
|
||||||
|
* @example
|
||||||
|
* await Redis.del("session:user_xxx");
|
||||||
|
* await Redis.del("visits");
|
||||||
|
*/
|
||||||
|
static async del(key) {
|
||||||
|
await this.redis.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Redis };
|
@ -1,39 +0,0 @@
|
|||||||
import * as redis from "redis";
|
|
||||||
import { env, Logger } from "@api/utils";
|
|
||||||
|
|
||||||
type RedisPrefixes = "session";
|
|
||||||
|
|
||||||
class Redis {
|
|
||||||
public static redis: ReturnType<typeof redis.createClient>;
|
|
||||||
|
|
||||||
public static async initialize() {
|
|
||||||
this.redis = redis.createClient({
|
|
||||||
url: env.REDIS_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.redis.on("error", (err) => {
|
|
||||||
Logger.error("INIT", "Failed to connect to redis " + String(err));
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.redis.on("connect", () => {
|
|
||||||
Logger.info("INIT", "Connected to redis");
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.redis.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async set(type: RedisPrefixes, key: string, value: string, options?: redis.SetOptions) {
|
|
||||||
await this.redis.set(`${type}:${key}`, value, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async get<T>(type: RedisPrefixes, key: string) {
|
|
||||||
return this.redis.get(`${type}:${key}`) as T | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async del(type: RedisPrefixes, key: string) {
|
|
||||||
await this.redis.del(`${type}:${key}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Redis, type RedisPrefixes };
|
|
29
src/utils/token.js
Normal file
29
src/utils/token.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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 createAuthToken(user_id, {
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
expires_at
|
||||||
|
}) {
|
||||||
|
const token = await db.insert(sessions).values({
|
||||||
|
user_id,
|
||||||
|
google_access_token: access_token,
|
||||||
|
google_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
|
||||||
|
}
|
||||||
|
}
|
112
src/utils/youtube.js
Normal file
112
src/utils/youtube.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { db } from '../db/index.js';
|
||||||
|
import { sessions } from '../db/schemas.js';
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
const service = google.youtube("v3");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} access_token
|
||||||
|
* @param {string} video_id
|
||||||
|
*
|
||||||
|
* @returns {Promise<import("googleapis").youtube_v3.Schema$Caption[]>}
|
||||||
|
*/
|
||||||
|
export async function getVideoCaptions(access_token, video_id) {
|
||||||
|
const captions = await service.captions.list({
|
||||||
|
access_token,
|
||||||
|
videoId: video_id,
|
||||||
|
part: "id,snippet"
|
||||||
|
}).then(x=>x.data.items);
|
||||||
|
|
||||||
|
return captions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} access_token
|
||||||
|
* @param {string} caption_id
|
||||||
|
*
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
export async function getCaptionText(access_token, caption_id) {
|
||||||
|
const caption_text = await service.captions.download({
|
||||||
|
id: caption_id,
|
||||||
|
part: "snippet",
|
||||||
|
tfmt: "srt",
|
||||||
|
tlang: "en",
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer " + access_token
|
||||||
|
}
|
||||||
|
}).then(res=>res.data).then(x=>x.text());
|
||||||
|
console.log(caption_text)
|
||||||
|
return caption_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} access_token
|
||||||
|
* @param {string} playlist_id
|
||||||
|
*
|
||||||
|
* @returns {Promise<import("googleapis").youtube_v3.Schema$PlaylistItem[]>}
|
||||||
|
*/
|
||||||
|
export async function getVideosFromPlaylist(access_token, playlist_id) {
|
||||||
|
const videos = await service.playlistItems.list({
|
||||||
|
access_token,
|
||||||
|
part: "id,snippet",
|
||||||
|
playlistId: playlist_id,
|
||||||
|
maxResults: 10
|
||||||
|
}).then(res => res);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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/oauth2').OAuth2Namespace} oauth
|
||||||
|
* @param {import('@fastify/oauth2').Token} session
|
||||||
|
*
|
||||||
|
* @returns {Promise<import('@fastify/oauth2').OAuth2Token>}
|
||||||
|
*/
|
||||||
|
export async function getNewToken(oauth, session) {
|
||||||
|
const {token} = await oauth.getNewAccessTokenUsingRefreshToken(session, {});
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
240
test.md
Normal file
240
test.md
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
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
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"module": "ESNext",
|
|
||||||
"target": "ESNext",
|
|
||||||
"strict": true,
|
|
||||||
"useUnknownInCatchVariables": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"exactOptionalPropertyTypes": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@api/*": ["src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user