feat: set up Drizzle
This commit is contained in:
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user