Initial Version (Working relay implementing basic functionality)
This commit is contained in:
commit
aeae39df4d
15 changed files with 1272 additions and 0 deletions
33
utils/encryption.ts
Normal file
33
utils/encryption.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
||||
import { managedNonce } from "@noble/ciphers/webcrypto";
|
||||
import { decodeBase64 } from "jsr:@std/encoding/base64";
|
||||
export const encryptionKey = decodeBase64(Deno.env.get("ENCRYPTION_KEY") || "");
|
||||
|
||||
/**
|
||||
* Encrypts a given Uint8Array using the XChaCha20-Poly1305 algorithm.
|
||||
*
|
||||
* @param data - The data to be encrypted as a Uint8Array.
|
||||
* @param key - The encryption key as a Uint8Array.
|
||||
* @returns The encrypted data as a Uint8Array.
|
||||
*/
|
||||
|
||||
export function encryptUint8Array(
|
||||
data: Uint8Array,
|
||||
key: Uint8Array,
|
||||
): Uint8Array {
|
||||
return managedNonce(xchacha20poly1305)(key).encrypt(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a given Uint8Array using the XChaCha20-Poly1305 algorithm.
|
||||
*
|
||||
* @param data - The data to be decrypted as a Uint8Array.
|
||||
* @param key - The decryption key as a Uint8Array.
|
||||
* @returns The decrypted data as a Uint8Array.
|
||||
*/
|
||||
export function decryptUint8Array(
|
||||
data: Uint8Array,
|
||||
key: Uint8Array,
|
||||
): Uint8Array {
|
||||
return managedNonce(xchacha20poly1305)(key).decrypt(data);
|
||||
}
|
31
utils/files.ts
Normal file
31
utils/files.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { exists } from "jsr:@std/fs";
|
||||
|
||||
/**
|
||||
* Return the path to Eve's configuration directory.
|
||||
*
|
||||
* The configuration directory is resolved in the following order:
|
||||
* 1. The value of the `XDG_CONFIG_HOME` environment variable.
|
||||
* 2. The value of the `HOME` environment variable, with `.config` appended.
|
||||
*
|
||||
* If the resolved path does not exist, create it.
|
||||
*/
|
||||
export async function getEveConfigHome(): Promise<string> {
|
||||
const xdgConfigHome = Deno.env.get("XDG_CONFIG_HOME") ??
|
||||
`${Deno.env.get("HOME")}/.config`;
|
||||
const storagePath = `${xdgConfigHome}/arx/Eve`;
|
||||
if (!(await exists(storagePath))) {
|
||||
await Deno.mkdir(storagePath, { recursive: true });
|
||||
}
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path to the file in Eve's configuration directory.
|
||||
*
|
||||
* @param file The name of the file to return the path for.
|
||||
* @returns The path to the file in Eve's configuration directory.
|
||||
*/
|
||||
export async function getEveFilePath(file: string): Promise<string> {
|
||||
const storagePath = await getEveConfigHome();
|
||||
return `${storagePath}/${file}`;
|
||||
}
|
75
utils/logs.ts
Normal file
75
utils/logs.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import * as colors from "jsr:@std/fmt@^1.0.4/colors";
|
||||
import * as log from "jsr:@std/log";
|
||||
import { getEveFilePath } from "./files.ts";
|
||||
export * as log from "jsr:@std/log";
|
||||
|
||||
export async function setupLogger() {
|
||||
const formatLevel = (level: number): string => {
|
||||
return (
|
||||
{
|
||||
10: colors.gray("[DEBUG]"),
|
||||
20: colors.green("[INFO] "),
|
||||
30: colors.yellow("[WARN] "),
|
||||
40: colors.red("[ERROR]"),
|
||||
50: colors.bgRed("[FATAL]"),
|
||||
}[level] || `[LVL${level}]`
|
||||
);
|
||||
};
|
||||
|
||||
const levelName = (level: number): string => {
|
||||
return {
|
||||
10: "DEBUG",
|
||||
20: "INFO",
|
||||
30: "WARN",
|
||||
40: "ERROR",
|
||||
50: "FATAL",
|
||||
}[level] || `LVL${level}`;
|
||||
};
|
||||
|
||||
const formatArg = (arg: unknown): string => {
|
||||
if (typeof arg === "object") return JSON.stringify(arg);
|
||||
return String(arg);
|
||||
};
|
||||
|
||||
await log.setup({
|
||||
handlers: {
|
||||
console: new log.ConsoleHandler("DEBUG", {
|
||||
useColors: true,
|
||||
formatter: (record) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
let msg = `${colors.dim(`[${timestamp}]`)} ${
|
||||
formatLevel(record.level)
|
||||
} ${record.msg}`;
|
||||
|
||||
if (record.args.length > 0) {
|
||||
const args = record.args
|
||||
.map((arg, i) => `${colors.dim(`arg${i}:`)} ${formatArg(arg)}`)
|
||||
.join(" ");
|
||||
msg += ` ${colors.dim("|")} ${args}`;
|
||||
}
|
||||
|
||||
return msg;
|
||||
},
|
||||
}),
|
||||
file: new log.FileHandler("DEBUG", {
|
||||
filename: Deno.env.get("LOG_FILE") ||
|
||||
await getEveFilePath("eve-logs.jsonl"),
|
||||
formatter: (record) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
return JSON.stringify({
|
||||
timestamp,
|
||||
level: levelName(record.level),
|
||||
msg: record.msg,
|
||||
args: record.args,
|
||||
});
|
||||
},
|
||||
}),
|
||||
},
|
||||
loggers: {
|
||||
default: {
|
||||
level: "DEBUG",
|
||||
handlers: ["console", "file"],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
99
utils/queries.ts
Normal file
99
utils/queries.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import type { BindValue, Database } from "@db/sqlite";
|
||||
|
||||
/**
|
||||
* Construct a SQL query with placeholders for values.
|
||||
*
|
||||
* This function takes a template string and interpolates it with the given
|
||||
* values, replacing placeholders with `?`.
|
||||
*
|
||||
* @example
|
||||
* const query = sqlPartial`SELECT * FROM events WHERE id = ? OR id = ?`,
|
||||
* ['1', '2'];
|
||||
* // query = {
|
||||
* // query: 'SELECT * FROM events WHERE id = ? OR id = ?',
|
||||
* // values: ['1', '2']
|
||||
* // }
|
||||
*
|
||||
* @param {TemplateStringsArray} segments A template string
|
||||
* @param {...BindValue[]} values Values to interpolate
|
||||
* @returns {{ query: string, values: BindValue[] }} A SQL query with placeholders
|
||||
*/
|
||||
export function sqlPartial(
|
||||
segments: TemplateStringsArray,
|
||||
...values: BindValue[]
|
||||
) {
|
||||
return {
|
||||
query: segments.reduce(
|
||||
(acc, str, i) => acc + str + (i < values.length ? "?" : ""),
|
||||
"",
|
||||
),
|
||||
values: values,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SQL query with placeholders for values and return a function
|
||||
* that executes that query on a database.
|
||||
*
|
||||
* This is a convenience wrapper around `sqlPartial` and `sqlPartialRunner`.
|
||||
*
|
||||
* @example
|
||||
* const run = sql`SELECT * FROM events WHERE id = ? OR id = ?`,
|
||||
* ['1', '2'];
|
||||
* const results = run(db);
|
||||
*
|
||||
* @param {TemplateStringsArray} segments A template string
|
||||
* @param {...BindValue[]} values Values to interpolate
|
||||
* @returns {Function} A function that takes a Database and returns the query results
|
||||
*/
|
||||
export function sql(segments: TemplateStringsArray, ...values: BindValue[]) {
|
||||
return sqlPartialRunner(sqlPartial(segments, ...values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine multiple partial queries into a single query.
|
||||
*
|
||||
* This function takes any number of partial queries with values and combines
|
||||
* them into a single query.
|
||||
*
|
||||
* @example
|
||||
* const query1 = { query: 'SELECT * FROM foo', values: [] };
|
||||
* const query2 = { query: 'WHERE bar = ?', values: ['5'] };
|
||||
* const query = mixQuery(query1, query2);
|
||||
* // query = {
|
||||
* // query: 'SELECT * FROM foo WHERE bar = ?',
|
||||
* // values: ['5']
|
||||
* // }
|
||||
*
|
||||
* @param {...{ query: string, values: BindValue[] }} queries Partial queries
|
||||
* @returns {{ query: string, values: BindValue[] }} A combined query
|
||||
*/
|
||||
export function mixQuery(...queries: { query: string; values: BindValue[] }[]) {
|
||||
const { query, values } = queries.reduce(
|
||||
(acc, { query, values }) => ({
|
||||
query: `${acc.query} ${query}`,
|
||||
values: [...acc.values, ...values],
|
||||
}),
|
||||
{ query: "", values: [] },
|
||||
);
|
||||
return { query, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SQL query against a database.
|
||||
*
|
||||
* This function takes a query object containing a SQL query string with placeholders
|
||||
* and an array of values. It returns a function that, when given a Database instance,
|
||||
* prepares the query and executes it, returning all results.
|
||||
*
|
||||
* @param {Object} query An object containing the SQL query string and corresponding values
|
||||
* @returns {Function} A function that takes a Database instance and returns the query results
|
||||
*/
|
||||
|
||||
export function sqlPartialRunner(query: {
|
||||
query: string;
|
||||
values: BindValue[];
|
||||
}) {
|
||||
const run = (db: Database) => db.prepare(query.query).all(...query.values);
|
||||
return run;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue