// 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> { // const quiz = await kv.get([`quiz:`, quiz_id]); // if (quiz && quiz.value) { // const quizObj = quiz.value as QuizConfig; // return quizObj; // } else { // return null; // } // } export async function upsertKvSessionConfigAny( kvObjSessionConfig: KvObj, ): Promise> { 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; 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, ): Promise> { 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; 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> { 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; } 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[]> { const searchPrefix = user_id ? ["session_configs", user_id] : ["session_configs"]; const sessions: KvObj[] = []; 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 = { 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> { const kvObjSessionConfig: KvObj = { 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(kvObj: KvObj): Promise> { if (kvObj.key && kvObj.key.length > 2) { try { await kvStore.delete(kvObj.key); kvObj.ok = true; kvObj.value = null; kvObj.versionstamp = ` deleteKvObj: by ${kvObj.key[0]}, ${ kvObj.key[1] }, ${kvObj.key[2]}`; } catch (e) { console.error(e); kvObj.ok = false; kvObj.versionstamp += " deleteKvObj: Error: " + e; } } else { kvObj.ok = false; kvObj.versionstamp += " deleteKvObj: 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> { 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; 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> { const kvObj: KvObj = { 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> { const kvObj: KvObj = { 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; } 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[]> { const searchPrefix = userId ? ["user_logins:", userId] : ["user_logins:"]; const logins: KvObj[] = []; 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 = { 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, ) { 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, ) { 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, ); }