feat: allow associating an existing device ID and token with a username
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user