integratedhub/utils/database.ts
open-swe-dev-andrinoid[bot] 146dca155c Apply patch [skip ci]
2026-01-22 02:04:08 +00:00

595 lines
17 KiB
TypeScript

// Connect to a KV instance await Deno.openKv("https://api.deno.com/databases/05af0e7a-a39f-44c8-a6fe-f52bacede61c/connect");
//const kv = await Deno.openKv("https://api.deno.com/databases/35aded84-9959-4723-9906-13380079a56c/connect");
//const kv = await openKv("https://api.deno.com/databases/05af0e7a-a39f-44c8-a6fe-f52bacede61c/connect");
//import { kv } from "npm:deno/kv";
//const kv = await Deno.open("",);
//import { openKv } from "npm:@deno/kv";
import { cloneObjectExcept, jsonMerge, KvObj, SessionConfig } from "./types.ts";
//import { bucket } from "./gcs_client.ts";
import { kvStore } from "../modules/KvCacheStore.ts";
//export let REDIRECT_URI = "https://studymylanguage.com/api/callback";
//export let REDIRECT_URI = "http://localhost:8000/api/callback";
export const REDIRECT_URI = Deno.env.get("REDIRECT_URL") + "/api/callback";
export interface GoogleUser {
id: string;
email: string;
verified_email: boolean;
name: string;
given_name: string;
family_name: string;
picture: string;
}
export interface AccessTokenResponse {
owner_id: string; // TODO: Make this required to be unuqe for each row in the table
access_token: string;
token_type: string;
expires_in?: number;
refresh_token?: string;
scope?: string;
}
const isDeploy = Deno.env.get("DENO_DEPLOYMENT_ID") !== undefined;
if (!isDeploy) {
Deno.env.set("APP_KEY", "12345678901123456789011234567890");
Deno.env.set(
"DENO_KV_ACCESS_TOKEN",
"ddp_2ZM4j8SSkfe13gPlMJhEaFjr23UxDJ1JR2N1",
);
//REDIRECT_URI = "http://localhost:8000/api/callback";
//REDIRECT_URI = "https://devcontainertest.agreeablebay-91c47758.centralus.azurecontainerapps.io/api/callback";
//REDIRECT_URI = "https://studymylanguage.com/api/callback";
}
//export
// let openKv = {};
// if (!isDeploy) {
// const module = await import("npm:@deno/kv");
// openKv = module.openKv;
// }
//const kv = await openKv("./mydata.db"); // Path to your local database file
// const getKv = async (isDeploy: boolean) => {
// return isDeploy && Deno.openKv ? await Deno.openKv() : await openKv(
// "https://api.deno.com/databases/35aded84-9959-4723-9906-13380079a56c/connect",
// );
// };
function keyValue(prefix: string, key: string) {
return [prefix, key]; //, Date.now(), crypto.randomUUID()];
}
// export async function setLoginEventState(
// stateId: string,
// loginState: LoginEventState,
// ) {
// await kv.set(keyValue(`LoginEventState:`, stateId), loginState);
// }
// export async function listLoginEventState() {
// const iter = kv.list({ prefix: ["LoginEventState:"] });
// //console.log("iter ", JSON.stringify(iter));
// const keys = [];
// for await (const res of iter) {
// keys.push(res);
// }
// return keys;
// }
// export async function getLoginEventState(stateId: string) {
// const result = await kv.get(keyValue(`LoginEventState:`, stateId));
// if (result && result.value) {
// return result.value as LoginEventState;
// }
// return null;
// }
export async function deleteLoginEventState(stateId: string) {
//await kv.delete(keyValue(`LoginEventState:`, stateId));
await kvStore.delete([stateId]);
}
// Function to get a list of file names in the bucket and store them in Deno KV
export async function listFilesAndStoreInKv() {
try {
// Get the files in the bucket
// const [files] = await bucket.getFiles();
//console.log("got files from bucket ", files.length);
// Extract the file names
// const fileNames = files.map((file: { name: string }) => file.name);
// await setAllBlobFileListInKv(fileNames);
} catch (error) {
console.error("Error listing files or storing in KV:", error);
throw error;
}
}
export async function setAllBlobFileListInKv(
fileNames: string[],
value?: boolean,
) {
// Store each file name in the KV database
for (const fileName of fileNames) {
// Use the file name as the key and store a simple value, such as true
//console.log("GoogleImages: ", fileName);
await kvStore.set(
keyValue(`GoogleImages:`, `${fileName}`),
value !== undefined ? value : true,
);
}
}
export async function doesBlobImageExistInKv(fileName: string) {
try {
// Construct the key used to store the file name
const key = [`GoogleImages:`, fileName];
// Retrieve the value associated with the key
const result = await kvStore.get(key);
// Check if the value exists
return result; //?.value !== null ? result.value : false;
} catch (error) {
console.error("Error checking file existence in KV:", error);
return null;
}
}
export async function getAllImageFiles() {
const iter = kvStore.list({ prefix: ["GoogleImages:"] });
//console.log("iter ", JSON.stringify(iter));
const keys = [];
for await (const res of iter) {
keys.push(res);
}
return keys;
}
export async function addBlobImageFileInKv(fileName: string) {
try {
// Construct the key used to store the file name
await kvStore.set(keyValue(`GoogleImages:`, fileName), true);
} catch (error) {
console.error("Error setting file in KV:", error);
throw error;
}
}
export async function deleteBlobImageFileInKv(fileName: string) {
try {
// Construct the key used to store the file name
await kvStore.delete(keyValue(`GoogleImages:`, fileName));
} catch (error) {
console.error("Error deleting file in KV:", error);
throw error;
}
}
// export async function getKvQuizAny(quiz_id: string): Promise<KvObj<QuizConfig>> {
// const quiz = await kv.get<QuizConfig>([`quiz:`, quiz_id]);
// if (quiz && quiz.value) {
// const quizObj = quiz.value as QuizConfig;
// return quizObj;
// } else {
// return null;
// }
// }
export async function upsertKvSessionConfigAny(
kvObjSessionConfig: KvObj<SessionConfig>,
): Promise<KvObj<SessionConfig>> {
if (
kvObjSessionConfig.key && kvObjSessionConfig.key[1] &&
kvObjSessionConfig.key[2] && kvObjSessionConfig.value
) {
try {
// if (kvObjSessionConfig.value.quiz_configs.length < 1) {
// throw new Error("WHY NO VALUE quiz_configs !!");
// }
//console.log("before",kvObjSessionConfig.value.quiz_configs.length);
await kvStore.set(
kvObjSessionConfig.key,
kvObjSessionConfig.value,
);
// Create a successful result object
const result = {
ok: true,
key: kvObjSessionConfig.key,
value: kvObjSessionConfig.value,
versionstamp: "upsertKvSessionConfigAny: success",
} as KvObj<SessionConfig>;
const clone = cloneObjectExcept(result, ["key", "value"], [
cloneObjectExcept(kvObjSessionConfig.key),
cloneObjectExcept(kvObjSessionConfig.value),
]);
//console.log("clone key",clone.key);
//console.log("after",clone.value?.quiz_configs.length);
return clone;
} catch (e) {
console.error(e);
kvObjSessionConfig.versionstamp += " upsertKvSessionConfigAny: Error: " +
e;
}
} else {
kvObjSessionConfig.versionstamp +=
" upsertKvSessionConfigAny: Input parameter bad.";
}
kvObjSessionConfig.ok = false;
return kvObjSessionConfig;
}
export async function setKvSessionConfigAny(
kvObjSessionConfig: KvObj<SessionConfig>,
): Promise<KvObj<SessionConfig>> {
if (
kvObjSessionConfig.key && kvObjSessionConfig.key[1] &&
kvObjSessionConfig.key[2] && kvObjSessionConfig.value
) {
try {
const result = await kvStore.get(kvObjSessionConfig.key);
if (result) {
// Since KvCacheStore doesn't return versionstamps, we'll skip the version check
// and always merge the data
const mergeValue = jsonMerge(
result,
kvObjSessionConfig.value,
) as SessionConfig;
// if (kvObjSessionConfig.value.quiz_configs.length < 1) {
// throw new Error("WHY NO VALUE quiz_configs !!");
// }
await kvStore.set(kvObjSessionConfig.key, mergeValue);
// Create a successful result object
const res = {
ok: true,
key: kvObjSessionConfig.key,
value: mergeValue,
versionstamp: "setKvSessionConfigAny: success",
} as KvObj<SessionConfig>;
const clone = cloneObjectExcept(res, ["key", "value"], [
cloneObjectExcept(kvObjSessionConfig.key),
mergeValue,
]);
return clone;
} else {
kvObjSessionConfig.versionstamp +=
" setKvSessionConfigAny: Database get value falure.";
}
} catch (e) {
console.error(e);
kvObjSessionConfig.versionstamp += " setKvSessionConfigAny: Error: " + e;
}
} else {
kvObjSessionConfig.versionstamp +=
" setKvSessionConfigAny: Input parameter bad.";
}
kvObjSessionConfig.ok = false;
return kvObjSessionConfig;
}
export async function getKvSessionConfig(
user_id: string,
device_id: string,
): Promise<KvObj<SessionConfig>> {
const kvObjSessionConfig = {
ok: false,
key: ["session_configs", user_id, device_id],
versionstamp: " getKvSessionConfig: Bad input parameters.",
value: null,
};
if (user_id && device_id) {
try {
const result = await kvStore.get(kvObjSessionConfig.key);
//console.log(JSON.stringify(result));
if (result) {
return {
ok: true,
key: kvObjSessionConfig.key,
value: result as SessionConfig,
versionstamp: "getKvSessionConfig: success",
} as KvObj<SessionConfig>;
} else {
kvObjSessionConfig.versionstamp =
" getKvSessionConfig: Database get value falure.";
}
} catch (e) {
console.error(e);
kvObjSessionConfig.versionstamp = " getKvSessionConfig: Error: " + e;
}
}
return kvObjSessionConfig;
}
export async function getAllKvSessionConfigs(
user_id?: string,
): Promise<KvObj<SessionConfig>[]> {
const searchPrefix = user_id
? ["session_configs", user_id]
: ["session_configs"];
const sessions: KvObj<SessionConfig>[] = [];
try {
const iter = kvStore.list({ prefix: searchPrefix }) as AsyncIterable<
{ key: Deno.KvKey; value: SessionConfig; versionstamp?: string }
>;
for await (const resEntry of iter) {
const res: KvObj<SessionConfig> = {
key: [
resEntry.key[0].toString(),
resEntry.key[1].toString(),
resEntry.key[2].toString(),
],
ok: true,
value: resEntry.value,
versionstamp: resEntry.versionstamp || "",
};
//console.log(res.key);
if (resEntry.value) {
res.ok = true;
} else {
res.ok = false;
res.versionstamp +=
" getAllKvSessionConfigs: Database get value falure.";
}
sessions.push(res);
}
} catch (e) {
console.error(e);
}
return sessions;
}
export async function deleteKvSessionConfig(
user_id: string,
device_id: string,
): Promise<KvObj<SessionConfig>> {
const kvObjSessionConfig: KvObj<SessionConfig> = {
ok: false,
key: ["session_configs", user_id, device_id],
versionstamp: " deleteKvSessionConfig: by user_id & device_id",
value: null,
};
if (user_id && device_id) {
try {
await kvStore.delete(kvObjSessionConfig.key);
kvObjSessionConfig.ok = true;
} catch (e) {
console.error(e);
kvObjSessionConfig.versionstamp = " deleteKvSessionConfig: Error: " + e;
}
} else {
kvObjSessionConfig.versionstamp =
" deleteKvSessionConfig: Input parameter bad.";
}
return kvObjSessionConfig;
}
export async function deleteKvObj<T>(kvObj: KvObj<T>): Promise<KvObj<T>> {
if (kvObj.key && kvObj.key.length > 2) {
try {
await kvStore.delete(kvObj.key);
kvObj.ok = true;
kvObj.value = null;
kvObj.versionstamp = ` deleteKvObj<T>: by ${kvObj.key[0]}, ${
kvObj.key[1]
}, ${kvObj.key[2]}`;
} catch (e) {
console.error(e);
kvObj.ok = false;
kvObj.versionstamp += " deleteKvObj<T>: Error: " + e;
}
} else {
kvObj.ok = false;
kvObj.versionstamp += " deleteKvObj<T>: Input parameter bad.";
}
return kvObj;
}
export interface UserConnection {
started_on: number;
connection_id: string;
}
export async function initUserDeviceLogin(
userId: string,
deviceId: string,
connection: UserConnection,
): Promise<KvObj<UserConnection>> {
const kvObj = {
ok: false,
key: ["user_logins:", userId, deviceId],
value: connection,
versionstamp: " initUserDeviceLogin: Input parameter bad.",
};
if (userId && deviceId && connection && connection.connection_id) {
try {
await kvStore.set(kvObj.key, connection);
// Create a successful result object
const result = {
ok: true,
key: kvObj.key,
value: connection,
versionstamp: "initUserDeviceLogin: success",
} as KvObj<UserConnection>;
const clone = cloneObjectExcept(result, ["key", "value"], [
cloneObjectExcept(kvObj.key),
cloneObjectExcept(kvObj.value),
]);
return clone;
} catch (e) {
console.error(e);
kvObj.versionstamp = " initUserDeviceLogin: Error: " + e;
}
}
return kvObj;
}
export async function deleteUserDeviceLogin(
userId: string,
deviceId: string,
): Promise<KvObj<UserConnection>> {
const kvObj: KvObj<UserConnection> = {
ok: false,
key: ["user_logins:", userId, deviceId],
value: null,
versionstamp: " deleteUserDeviceLogin: Input parameter bad.",
};
if (userId && deviceId) {
try {
await kvStore.delete(kvObj.key);
kvObj.ok = true;
kvObj.versionstamp = "1";
return kvObj;
} catch (e) {
console.error(e);
kvObj.versionstamp = " deleteUserDeviceLogin: Error: " + e;
}
}
return kvObj;
}
export async function getUserDeviceLogin(
userId: string,
deviceId: string,
): Promise<KvObj<UserConnection>> {
const kvObj: KvObj<UserConnection> = {
ok: false,
key: ["user_logins:", userId, deviceId],
versionstamp: " getUserDeviceLogin: Input parameter bad.",
value: null,
};
if (userId && deviceId) {
try {
const result = await kvStore.get(kvObj.key);
if (result) {
return {
ok: true,
key: kvObj.key,
value: result as UserConnection,
versionstamp: "getUserDeviceLogin: success",
} as KvObj<UserConnection>;
} else {
kvObj.versionstamp = " getUserDeviceLogin: Database get value falure.";
}
} catch (e) {
console.error(e);
kvObj.versionstamp = " getUserDeviceLogin: Error: " + e;
}
}
return kvObj;
}
export async function getAllUserDeviceLogin(
userId?: string,
): Promise<KvObj<UserConnection>[]> {
const searchPrefix = userId ? ["user_logins:", userId] : ["user_logins:"];
const logins: KvObj<UserConnection>[] = [];
try {
const iter = kvStore.list({ prefix: searchPrefix }) as AsyncIterable<
{ key: Deno.KvKey; value: UserConnection; versionstamp?: string }
>;
for await (const resEntry of iter) {
const res: KvObj<UserConnection> = {
key: searchPrefix,
ok: true,
value: resEntry.value,
versionstamp: resEntry.versionstamp || "",
};
if (resEntry.value) {
res.ok = true;
} else {
res.ok = false;
res.versionstamp +=
" getAllUserDeviceLogin: Database get value falure.";
}
logins.push(res);
}
} catch (e) {
console.error(e);
}
return logins;
}
export async function upsertUser(user: GoogleUser) {
if (user && user.id.length > 0) {
await kvStore.set([`users`, user.id], user);
}
}
export async function getUser(userId: string) {
const user = await kvStore.get([`users`, `${userId}`]);
return user ? user : null;
}
export async function getAllUsers() {
const iter = kvStore.list({ prefix: ["users"] });
//console.log("iter ", JSON.stringify(iter));
const keys: { key: Deno.KvKey; value: GoogleUser }[] = [];
for await (const res of iter) {
const userRes = res as { key: Deno.KvKey; value: GoogleUser };
keys.push(userRes);
}
//console.log("lists ", JSON.stringify(keys));
const users: GoogleUser[] = [];
for (const key of keys) {
const userObj = key.value as GoogleUser;
//const quiz = key.value as QuizData;// await kv.get([key.value as string]);
if (userObj) {
users.push(userObj); //.value as QuizData);
}
}
return users;
}
export async function recordTelemetry(
quiz_id: string,
userId: string,
data: Record<string, unknown>,
) {
const telemetryId = crypto.randomUUID();
await kvStore.set(keyValue(`telemetry:`, `${telemetryId}`), {
quiz_id,
user_id: userId,
data,
recorded_at: new Date().toISOString(),
});
}
export async function submitQuizResults(
quiz_id: string,
userId: string,
results: Record<string, unknown>,
) {
const resultId = crypto.randomUUID();
await kvStore.set(keyValue(`quiz_result:`, `${resultId}`), {
quiz_id,
user_id: userId,
results,
submitted_at: new Date().toISOString(),
});
}
export async function getDbToken(userId: string) {
const token = await kvStore.get([`user_tokens`, `${userId}`]);
return token ? token : null;
}
export async function upsertAccessToken(tokenResponse: AccessTokenResponse) {
await kvStore.set(
keyValue(`user_tokens`, `${tokenResponse.owner_id}`),
tokenResponse,
);
}