Init backend, frontend and DB schema

This commit is contained in:
2024-08-03 10:44:42 +02:00
parent ddd7f7221b
commit 777038e0b4
23 changed files with 1917 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import { RequestId } from "common";
import { Context, Option as O } from "effect";
export interface RequestInterface {
readonly requestId: RequestId;
readonly method: string;
readonly path: readonly string[];
readonly query: { readonly [_: string]: string };
readonly headers: { readonly [_: string]: string };
readonly ip: O.Option<string>;
}
export class Request extends Context.Tag("Request")<Request, RequestInterface>() { }

View File

@@ -0,0 +1,107 @@
import { Sha256 } from "common";
import { Context, Data, Effect, Layer } from "effect";
import { NoSuchElementException } from "effect/Cause";
import { constant, flow } from "effect/Function";
import path from "node:path";
export class StorageError extends Data.TaggedError("StorageError")<{ cause: unknown }> { }
export interface StorageInterface {
/**
* @param sha256 SHA-256 of blob to read
* @returns Blob, if exists
*/
readonly read: (sha256: Sha256) => Effect.Effect<Uint8Array, NoSuchElementException | StorageError>;
/**
* @param data Blob to write
* @param sha256 Precomputed SHA-256, trusted to be accurate if provided
* @returns Precomputed SHA-256, if provided, internally computed SHA-256
* otherwise
*/
readonly write: (data: Uint8Array, sha256?: Sha256) => Effect.Effect<Sha256, StorageError>;
}
export class Storage extends Context.Tag("Storage")<Storage, StorageInterface>() { }
/**
* Storage implementation that uses the filesystem.
*/
export const StorageFilesystem = (baseDir: string = ".") => {
const sha256toFilePath = (sha256: Sha256): string => {
const sha256hex = Buffer.from(sha256).toString("hex");
return path.join(
baseDir,
sha256hex.slice(0, 2),
sha256hex.slice(2, 4),
sha256hex.slice(4),
);
}
return Layer.succeed(Storage, Object.freeze<StorageInterface>({
read: (sha256) => {
const filePath = sha256toFilePath(sha256);
return Effect.gen(function* () {
const arrayBuffer = yield* Effect.tryPromise({
try: () => Bun.file(filePath).arrayBuffer(),
catch: (error) => new StorageError({ cause: error }),
});
const array = new Uint8Array(arrayBuffer);
return array;
});
},
write: (data, sha256) => Effect.gen(function* () {
if (sha256 === undefined) {
sha256 = Sha256.make(new Uint8Array(Bun.SHA256.byteLength));
Bun.SHA256.hash(data, sha256);
}
const filePath = sha256toFilePath(sha256);
yield* Effect.tryPromise({
try: () => Bun.write(filePath, data),
catch: (error) => new StorageError({ cause: error }),
});
return sha256;
}),
}));
};
const sha256ToBase64 = (sha256: Sha256): string => {
return Buffer.from(sha256).toString("base64");
}
/**
* Storage that keeps all data in memory.
*/
export const StorageMemory = Layer.sync(Storage, () => {
const map = new Map<string, Uint8Array>();
return Object.freeze<StorageInterface>({
read: flow(
sha256ToBase64,
_ => map.get(_),
Effect.fromNullable,
),
write: (data, sha256) => Effect.sync(() => {
if (sha256 === undefined) {
sha256 = Sha256.make(new Uint8Array(Bun.SHA256.byteLength));
Bun.SHA256.hash(data, sha256);
}
map.set(sha256ToBase64(sha256), data);
return sha256;
}),
});
});
/**
* Storage that discards any writes silently and always fails to read.
*/
export const StorageEmpty = Layer.succeed(Storage, Object.freeze<StorageInterface>({
read: constant(Effect.fail(new NoSuchElementException())),
write: (data, sha256) => Effect.sync(() => {
if (sha256 === undefined) {
sha256 = Sha256.make(new Uint8Array(Bun.SHA256.byteLength));
Bun.SHA256.hash(data, sha256);
}
return sha256;
}),
}));