596 lines
17 KiB
TypeScript
596 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,
|
|
);
|
|
}
|
|
|