feat: set up Drizzle
This commit is contained in:
12
drizzle.config.ts
Normal file
12
drizzle.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "./src/models/schema.ts",
|
||||||
|
out: "./drizzle",
|
||||||
|
driver: "turso",
|
||||||
|
dialect: "sqlite",
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.TURSO_URL,
|
||||||
|
authToken: process.env.TURSO_AUTH_TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
14
drizzle/0000_left_reavers.sql
Normal file
14
drizzle/0000_left_reavers.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE `token` (
|
||||||
|
`device_id` text PRIMARY KEY NOT NULL,
|
||||||
|
`token` text NOT NULL,
|
||||||
|
`username` text,
|
||||||
|
`last_connected_at` text DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `watch_status` (
|
||||||
|
`device_id` text NOT NULL,
|
||||||
|
`title_id` integer NOT NULL,
|
||||||
|
`watch_status` text NOT NULL,
|
||||||
|
PRIMARY KEY(`device_id`, `title_id`),
|
||||||
|
FOREIGN KEY (`device_id`) REFERENCES `token`(`device_id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
1
drizzle/0001_purple_franklin_richards.sql
Normal file
1
drizzle/0001_purple_franklin_richards.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `watch_status` DROP COLUMN `watch_status`;
|
||||||
100
drizzle/meta/0000_snapshot.json
Normal file
100
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "4ecf912c-2ffc-4a17-924b-8694ced4d7b6",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"columns": {
|
||||||
|
"device_id": {
|
||||||
|
"name": "device_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_connected_at": {
|
||||||
|
"name": "last_connected_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(CURRENT_TIMESTAMP)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"watch_status": {
|
||||||
|
"name": "watch_status",
|
||||||
|
"columns": {
|
||||||
|
"device_id": {
|
||||||
|
"name": "device_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title_id": {
|
||||||
|
"name": "title_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"watch_status": {
|
||||||
|
"name": "watch_status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"watch_status_device_id_token_device_id_fk": {
|
||||||
|
"name": "watch_status_device_id_token_device_id_fk",
|
||||||
|
"tableFrom": "watch_status",
|
||||||
|
"tableTo": "token",
|
||||||
|
"columnsFrom": ["device_id"],
|
||||||
|
"columnsTo": ["device_id"],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"watch_status_device_id_title_id_pk": {
|
||||||
|
"columns": ["device_id", "title_id"],
|
||||||
|
"name": "watch_status_device_id_title_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
drizzle/meta/0001_snapshot.json
Normal file
93
drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "d5b8fe62-fa26-4e9b-94eb-d3d38701f620",
|
||||||
|
"prevId": "4ecf912c-2ffc-4a17-924b-8694ced4d7b6",
|
||||||
|
"tables": {
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"columns": {
|
||||||
|
"device_id": {
|
||||||
|
"name": "device_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_connected_at": {
|
||||||
|
"name": "last_connected_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(CURRENT_TIMESTAMP)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"watch_status": {
|
||||||
|
"name": "watch_status",
|
||||||
|
"columns": {
|
||||||
|
"device_id": {
|
||||||
|
"name": "device_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title_id": {
|
||||||
|
"name": "title_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"watch_status_device_id_token_device_id_fk": {
|
||||||
|
"name": "watch_status_device_id_token_device_id_fk",
|
||||||
|
"tableFrom": "watch_status",
|
||||||
|
"tableTo": "token",
|
||||||
|
"columnsFrom": ["device_id"],
|
||||||
|
"columnsTo": ["device_id"],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"watch_status_device_id_title_id_pk": {
|
||||||
|
"columns": ["device_id", "title_id"],
|
||||||
|
"name": "watch_status_device_id_title_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
drizzle/meta/_journal.json
Normal file
20
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1718106156111,
|
||||||
|
"tag": "0000_left_reavers",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1718107695989,
|
||||||
|
"tag": "0001_purple_franklin_richards",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
package.json
15
package.json
@@ -4,18 +4,23 @@
|
|||||||
"description": "API for Aniplay",
|
"description": "API for Aniplay",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:cloudflare": "wrangler dev src/index.ts --port 8080",
|
"dev:cloudflare": "TURSO_URL=http://127.0.0.1:3000 TURSO_AUTH_TOKEN=123 wrangler dev src/index.ts --port 8080",
|
||||||
"dev:server": "bun run --watch src/index.ts",
|
"dev:server": "TURSO_URL=http://127.0.0.1:3000 TURSO_AUTH_TOKEN=123 bun run --watch src/index.ts",
|
||||||
"prod:server": "bun run src/index.ts",
|
"prod:server": "bun run src/index.ts",
|
||||||
"deploy": "wrangler deploy --minify src/index.ts",
|
"deploy": "wrangler deploy --minify src/index.ts",
|
||||||
"env:generate": "bun src/scripts/generateEnv.ts",
|
"env:generate": "bun src/scripts/generateEnv.ts",
|
||||||
"env:verify": "bun src/scripts/verifyEnv.ts"
|
"env:verify": "bun src/scripts/verifyEnv.ts",
|
||||||
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"test": "bun src/testRunner.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@consumet/extensions": "github:consumet/consumet.ts#2bcd9287dc1471ed081bc23333e7629779924e0e",
|
"@consumet/extensions": "github:consumet/consumet.ts#2bcd9287dc1471ed081bc23333e7629779924e0e",
|
||||||
"@haverstack/axios-fetch-adapter": "^0.12.0",
|
"@haverstack/axios-fetch-adapter": "^0.12.0",
|
||||||
"@hono/swagger-ui": "^0.2.2",
|
"@hono/swagger-ui": "^0.2.2",
|
||||||
"@hono/zod-openapi": "^0.12.0",
|
"@hono/zod-openapi": "^0.12.0",
|
||||||
|
"@libsql/client": "^0.6.2",
|
||||||
|
"drizzle-orm": "^0.31.2",
|
||||||
"gql.tada": "^1.7.5",
|
"gql.tada": "^1.7.5",
|
||||||
"graphql-request": "^7.0.1",
|
"graphql-request": "^7.0.1",
|
||||||
"hono": "^4.3.6",
|
"hono": "^4.3.6",
|
||||||
@@ -26,11 +31,13 @@
|
|||||||
"@cloudflare/workers-types": "^4.20240403.0",
|
"@cloudflare/workers-types": "^4.20240403.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/bun": "^1.1.2",
|
"@types/bun": "^1.1.2",
|
||||||
|
"drizzle-kit": "^0.22.6",
|
||||||
"msw": "^2.3.0",
|
"msw": "^2.3.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-toml": "^2.0.1",
|
"prettier-plugin-toml": "^2.0.1",
|
||||||
"ts-morph": "^22.0.0",
|
"ts-morph": "^22.0.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"wrangler": "^3.47.0"
|
"wrangler": "^3.47.0",
|
||||||
|
"zx": "^8.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/models/db.ts
Normal file
36
src/models/db.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { createClient } from "@libsql/client";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { drizzle } from "drizzle-orm/libsql";
|
||||||
|
|
||||||
|
import type { Env } from "~/types/env";
|
||||||
|
|
||||||
|
import { tables } from "./schema";
|
||||||
|
|
||||||
|
type Db = ReturnType<typeof createDb>;
|
||||||
|
let db: Db | null = null;
|
||||||
|
|
||||||
|
export function getDb(env: Env): Db {
|
||||||
|
if (db) {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
db = createDb(env);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetDb() {
|
||||||
|
if (!db) return;
|
||||||
|
|
||||||
|
for (const table of tables) {
|
||||||
|
await db.delete(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDb(env: Env) {
|
||||||
|
const client = createClient({
|
||||||
|
url: env.TURSO_URL,
|
||||||
|
authToken: env.TURSO_AUTH_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
return drizzle(client);
|
||||||
|
}
|
||||||
30
src/models/schema.ts
Normal file
30
src/models/schema.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
integer,
|
||||||
|
primaryKey,
|
||||||
|
sqliteTable,
|
||||||
|
text,
|
||||||
|
} from "drizzle-orm/sqlite-core";
|
||||||
|
|
||||||
|
export const tokenTable = sqliteTable("token", {
|
||||||
|
deviceId: text("device_id").primaryKey(),
|
||||||
|
token: text("token").notNull(),
|
||||||
|
username: text("username"),
|
||||||
|
/** Used to determine if a device hasn't been used in a while. Should start to ignore tokens where the device hasn't connected in about a month */
|
||||||
|
lastConnectedAt: text("last_connected_at").default(sql`(CURRENT_TIMESTAMP)`),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const watchStatusTable = sqliteTable(
|
||||||
|
"watch_status",
|
||||||
|
{
|
||||||
|
deviceId: text("device_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => tokenTable.deviceId),
|
||||||
|
titleId: integer("title_id").notNull(),
|
||||||
|
},
|
||||||
|
(table) => ({
|
||||||
|
pk: primaryKey({ columns: [table.deviceId, table.titleId] }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const tables = [watchStatusTable, tokenTable];
|
||||||
26
src/models/token.ts
Normal file
26
src/models/token.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
import type { Env } from "~/types/env";
|
||||||
|
|
||||||
|
import { getDb } from "./db";
|
||||||
|
import { tokenTable } from "./schema";
|
||||||
|
|
||||||
|
export function saveToken(
|
||||||
|
env: Env,
|
||||||
|
deviceId: string,
|
||||||
|
token: string,
|
||||||
|
username: string | null,
|
||||||
|
) {
|
||||||
|
return getDb(env)
|
||||||
|
.insert(tokenTable)
|
||||||
|
.values({ deviceId, token, username })
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDeviceLastConnectedAt(env: Env, deviceId: string) {
|
||||||
|
return getDb(env)
|
||||||
|
.update(tokenTable)
|
||||||
|
.set({ lastConnectedAt: sql`(CURRENT_TIMESTAMP)` })
|
||||||
|
.where(eq(tokenTable.deviceId, deviceId))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
59
src/models/watchStatus.ts
Normal file
59
src/models/watchStatus.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { and, count, eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
import type { Env } from "~/types/env";
|
||||||
|
import { WatchStatusValues } from "~/types/title/watchStatus";
|
||||||
|
|
||||||
|
import { getDb } from "./db";
|
||||||
|
import { watchStatusTable } from "./schema";
|
||||||
|
|
||||||
|
/** If watch status is "CURRENT", the title will be added to the watch status table. Otherwise, it will be removed.
|
||||||
|
*
|
||||||
|
* @returns an object with the following properties:
|
||||||
|
* - wasAdded: whether the title was set as watching for the first time
|
||||||
|
* - wasDeleted: whether any users are still watching the title
|
||||||
|
*/
|
||||||
|
export function setWatchStatus(
|
||||||
|
env: Env,
|
||||||
|
deviceId: string,
|
||||||
|
titleId: number,
|
||||||
|
watchStatus: (typeof WatchStatusValues)[number],
|
||||||
|
) {
|
||||||
|
let dbAction;
|
||||||
|
const isSavingTitle = watchStatus === "CURRENT";
|
||||||
|
if (isSavingTitle) {
|
||||||
|
dbAction = saveTitle(env, deviceId, titleId);
|
||||||
|
} else {
|
||||||
|
dbAction = removeTitle(env, deviceId, titleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbAction
|
||||||
|
.then(() =>
|
||||||
|
getDb(env)
|
||||||
|
.select({ count: count(watchStatusTable.titleId) })
|
||||||
|
.from(watchStatusTable)
|
||||||
|
.where(eq(watchStatusTable.titleId, titleId))
|
||||||
|
.get(),
|
||||||
|
)
|
||||||
|
.then((result) => ({
|
||||||
|
wasAdded: isSavingTitle && result?.count === 1,
|
||||||
|
wasDeleted: !isSavingTitle && result?.count === 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTitle(env: Env, deviceId: string, titleId: number) {
|
||||||
|
return getDb(env)
|
||||||
|
.insert(watchStatusTable)
|
||||||
|
.values({ deviceId, titleId })
|
||||||
|
.onConflictDoNothing();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTitle(env: Env, deviceId: string, titleId: number) {
|
||||||
|
return getDb(env)
|
||||||
|
.delete(watchStatusTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(watchStatusTable.deviceId, deviceId),
|
||||||
|
eq(watchStatusTable.titleId, titleId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ compatibility_date = "2023-12-01"
|
|||||||
node_compat = true
|
node_compat = true
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
TURSO_URL = "libsql://humble-argent-silverandroid.turso.io"
|
TURSO_URL = "http://127.0.0.1:8080"
|
||||||
QSTASH_URL = "https://qstash.upstash.io/v2/publish"
|
QSTASH_URL = "https://qstash.upstash.io/v2/publish"
|
||||||
ENABLE_ANIFY = false
|
ENABLE_ANIFY = false
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user