feat: set up Drizzle

This commit is contained in:
2024-06-12 09:29:58 -04:00
parent 5843dfdeb2
commit 06bb8f65fb
13 changed files with 403 additions and 5 deletions

36
src/models/db.ts Normal file
View 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
View 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
View 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
View 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),
),
);
}