diff --git a/functions/gcloud.ts b/functions/gcloud.ts index 24913b6..e5d4c5b 100644 --- a/functions/gcloud.ts +++ b/functions/gcloud.ts @@ -79,9 +79,7 @@ async function GetAccessToken(env: Env): Promise { const signingKey = await crypto.subtle.importKey( "pkcs8", - stringToBuffer( - atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, "")) - ), + stringToBuffer(atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["sign"] @@ -107,3 +105,174 @@ async function GetAccessToken(env: Env): Promise { return ((await tokenRequest.json()) as { [k: string]: any }).access_token; } + +async function getKeyIDs( + access_token: string, + projectId: string, + keys: { partitionId: { projectId: string }; path: { kind: string }[] }[] +) { + const keyRequest = await fetch( + `https://datastore.googleapis.com/v1/projects/${projectId}:allocateIds`, + { + body: JSON.stringify({ keys }), + headers: { + authorization: `Bearer ${access_token}`, + "content-type": "application/json", + }, + method: "POST", + } + ); + + if (!keyRequest.ok) { + console.log(await keyRequest.json()); + throw new Error("Failed to allocate key IDs"); + } + + return ((await keyRequest.json()) as { keys: { [k: string]: any }[] }).keys; +} + +export async function insertLogs( + userActionMap: { [k: string]: number }, + reportId: string, + context: RequestContext +) { + const accessToken = await GetAccessToken(context.env); + const actionBaseURLs: { [k: number]: string } = { + 1: "https://portal.carcrushers.cc/view-closed-report/", + 2: "https://portal.carcrushers.cc/view-closed-report/", + 3: "", + 4: "https://portal.carcrushers.cc/game-appeals/", + }; + const actionIntegers: { [k: number]: string } = { + 1: "blacklist", + 2: "ban", + 3: "revoke", + 4: "accept_appeal", + }; + const incompleteLogKey = { + partitionId: { + projectId: context.env.DATASTORE_PROJECT, + }, + path: [ + { + kind: "log", + }, + ], + }; + const payload: { mode: string; mutations: { [k: string]: any }[] } = { + mode: "NON_TRANSACTIONAL", + mutations: [], + }; + const preAllocatedLogKeys = []; + + while (preAllocatedLogKeys.length < Object.keys(userActionMap).length) + preAllocatedLogKeys.push(incompleteLogKey); + + const keys = await getKeyIDs( + accessToken, + context.env.DATASTORE_PROJECT, + preAllocatedLogKeys + ); + + for (const [user, action] of Object.entries(userActionMap)) { + payload.mutations.push({ + insert: { + key: keys.pop(), + properties: { + action: { + stringValue: actionIntegers[action], + }, + evidence: { + stringValue: actionBaseURLs[action] + reportId, + }, + executed_at: { + integerValue: Date.now(), + }, + executor: { + stringValue: context.data.current_user.id, + }, + target: { + integerValue: user, + }, + }, + }, + }); + } + + const mutationRequest = await fetch( + `https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:commit`, + { + body: JSON.stringify(payload), + headers: { + authorization: `Bearer ${accessToken}`, + "content-type": "application/json", + }, + method: "POST", + } + ); + + if (!mutationRequest.ok) { + console.log(await mutationRequest.json()); + throw new Error("Failed to commit mutation"); + } + + return await mutationRequest.json(); +} + +export async function queryLogs(user: number, context: RequestContext) { + const accessToken = await GetAccessToken(context.env); + + const queryRequest = await fetch( + `https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:runQuery`, + { + body: JSON.stringify({ + partitionId: { + projectId: context.env.DATASTORE_PROJECT, + }, + query: { + filter: { + propertyFilter: { + op: "EQUAL", + property: { + name: "target", + }, + value: { + integerValue: user.toString(), + }, + }, + }, + kind: [ + { + name: "log", + }, + ], + }, + }), + headers: { + authorization: `Bearer ${accessToken}`, + "content-type": "application/json", + }, + method: "POST", + } + ); + + if (!queryRequest.ok) { + console.log(await queryRequest.json()); + throw new Error("Failed to query logs"); + } + + const entityResult = + ( + (await queryRequest.json()) as { + batch: { entityResults: { [k: string]: { [k: string]: any } }[] }; + } + ).batch?.entityResults ?? []; + + if (!entityResult.length) return {}; + + const entity = entityResult[0].entity.properties; + delete entity.executor; + delete entity.target; + + return entity; +}