feat: allow associating an existing device ID and token with a username

This commit is contained in:
2024-06-16 08:24:28 -04:00
parent e8aebac6d4
commit f9d7a6bbd2
4 changed files with 112 additions and 7 deletions

View File

@@ -195,6 +195,64 @@ describe("requests the /token route", () => {
expect(row).toBeUndefined(); expect(row).toBeUndefined();
}); });
it("associating a username with a token, should succeed", async () => {
await db
.insert(deviceTokensTable)
.values({ deviceId: "123", token: "123" });
const res = await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
token: "123",
deviceId: "123",
username: "aniplay",
}),
});
expect(res.json()).resolves.toEqual({ success: true });
expect(res.status).toBe(200);
});
it("associating a username with a token should update existing entry", async () => {
const minimumTimestamp = DateTime.now();
await db
.insert(deviceTokensTable)
.values({ deviceId: "123", token: "123" });
await app.request("/token", {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
token: "123",
deviceId: "123",
username: "aniplay",
}),
});
const row = await db
.select()
.from(deviceTokensTable)
.where(eq(deviceTokensTable.deviceId, "123"))
.get();
expect(row).toEqual({
deviceId: "123",
token: "123",
username: "aniplay",
lastConnectedAt: expect.any(String),
});
// since SQL timestamp doesn't support milliseconds, compare to nearest second
expect(
+DateTime.fromSQL(row!.lastConnectedAt!, { zone: "utc" }).startOf(
"second",
),
).toBeGreaterThanOrEqual(+minimumTimestamp.startOf("second"));
});
it("token is invalid, should fail", async () => { it("token is invalid, should fail", async () => {
mock.module("src/libs/fcm/verifyFcmToken", () => ({ mock.module("src/libs/fcm/verifyFcmToken", () => ({
verifyFcmToken: () => false, verifyFcmToken: () => false,

View File

@@ -69,11 +69,7 @@ app.openapi(route, async (c) => {
await saveToken(env(c, "workerd"), deviceId, token, username); await saveToken(env(c, "workerd"), deviceId, token, username);
} catch (error) { } catch (error) {
// when token already exists in the database if (error.message === "Token already exists in the database") {
if (
error.code === "SQLITE_CONSTRAINT" &&
error.message.includes("device_tokens.token")
) {
return c.json(ErrorResponse, 412); return c.json(ErrorResponse, 412);
} }

View File

@@ -11,7 +11,9 @@ export const deviceTokensTable = sqliteTable("device_tokens", {
token: text("token").notNull().unique(), token: text("token").notNull().unique(),
username: text("username"), 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. */ /** 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)`), lastConnectedAt: text("last_connected_at")
.default(sql`(CURRENT_TIMESTAMP)`)
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
}); });
export const watchStatusTable = sqliteTable( export const watchStatusTable = sqliteTable(

View File

@@ -1,4 +1,4 @@
import { eq, sql } from "drizzle-orm"; import { eq, or, sql } from "drizzle-orm";
import type { Env } from "~/types/env"; import type { Env } from "~/types/env";
@@ -10,6 +10,42 @@ export function saveToken(
deviceId: string, deviceId: string,
token: string, token: string,
username: string | null, username: string | null,
) {
return getDb(env)
.select()
.from(deviceTokensTable)
.where(
or(
eq(deviceTokensTable.deviceId, deviceId),
eq(deviceTokensTable.token, token),
),
)
.then((existingTokens) => {
const existingToken = existingTokens.find(
({ token: existingToken, deviceId: existingDeviceId }) =>
existingToken === token || existingDeviceId === deviceId,
);
if (!existingToken) {
return insertToken(env, deviceId, token, username);
}
if (
existingToken.token === token &&
!existingToken.username &&
!username
) {
throw new Error("Token already exists in the database");
}
return updateToken(env, deviceId, token, username);
});
}
function insertToken(
env: Env,
deviceId: string,
token: string,
username: string | null,
) { ) {
return getDb(env) return getDb(env)
.insert(deviceTokensTable) .insert(deviceTokensTable)
@@ -17,6 +53,19 @@ export function saveToken(
.run(); .run();
} }
function updateToken(
env: Env,
deviceId: string,
token: string,
username: string | null,
) {
return getDb(env)
.update(deviceTokensTable)
.set({ token, username })
.where(eq(deviceTokensTable.deviceId, deviceId))
.run();
}
export function updateDeviceLastConnectedAt(env: Env, deviceId: string) { export function updateDeviceLastConnectedAt(env: Env, deviceId: string) {
return getDb(env) return getDb(env)
.update(deviceTokensTable) .update(deviceTokensTable)