chore: init

This commit is contained in:
DevMiner 2024-08-11 03:51:22 +02:00
commit 320715f3e7
174 changed files with 42083 additions and 0 deletions

51
web/src/api/auth.ts Normal file
View file

@ -0,0 +1,51 @@
import {HttpClient, HttpClientRequest, HttpClientResponse,} from "@effect/platform";
import * as Schema from "@effect/schema/Schema";
import {Effect, Schedule} from "effect";
import {OTLP_TRACE_PROPAGATION} from "../env.ts";
import {APIResponse, BASE_URL, FailedAPIResponse} from "./response.ts";
export const UserId = Schema.UUID.pipe(Schema.brand("UserId"));
export type UserId = Schema.Schema.Type<typeof UserId>;
export class UserInfo extends Schema.Class<UserInfo>("UserInfo")({
id: UserId,
username: Schema.String,
}) {
}
export const fetchUserInfo = HttpClientRequest.get(
`${BASE_URL}/api/auth/authn/@me`,
).pipe(
HttpClient.fetch,
HttpClientResponse.schemaBodyJsonScoped(APIResponse(UserInfo)),
Effect.filterOrFail(
(res) => res.ok,
(res) => (res as FailedAPIResponse).error,
),
Effect.retry({times: 3, schedule: Schedule.exponential(250, 2)}),
Effect.map((d) => d.data),
Effect.either,
Effect.withSpan("fetchUserInfo"),
HttpClient.withTracerPropagation(OTLP_TRACE_PROPAGATION),
);
export class UserCreation extends Schema.Class<UserCreation>("UserCreation")({
username: Schema.String,
}) {
}
const createUserBody = HttpClientRequest.schemaBody(UserCreation);
export const createUser = (data: UserCreation) =>
HttpClientRequest.post(`${BASE_URL}/api/app/users/`).pipe(
createUserBody(data),
Effect.andThen(HttpClient.fetch),
HttpClientResponse.schemaBodyJsonScoped(APIResponse(UserInfo)),
Effect.filterOrFail(
(res) => res.ok,
(res) => (res as FailedAPIResponse).error,
),
Effect.map((d) => d.data),
Effect.either,
Effect.withSpan("createUser", {attributes: {data: data}}),
HttpClient.withTracerPropagation(OTLP_TRACE_PROPAGATION),
);

28
web/src/api/index.ts Normal file
View file

@ -0,0 +1,28 @@
import {layer} from "@effect/opentelemetry/WebSdk";
import {OTLPTraceExporter} from "@opentelemetry/exporter-trace-otlp-http";
import {BatchSpanProcessor} from "@opentelemetry/sdk-trace-base";
import {Layer, ManagedRuntime} from "effect";
import {OTLP_ENDPOINT, OTLP_TRACE_PROPAGATION} from "../env.ts";
export const WebSdkLive = OTLP_TRACE_PROPAGATION
? layer(() => ({
resource: {serviceName: "Web UI"},
spanProcessor: new BatchSpanProcessor(
new OTLPTraceExporter({url: OTLP_ENDPOINT}),
),
}))
: Layer.empty;
if (OTLP_ENDPOINT) {
console.log("OpenTelemetry initialized with OTLP endpoint", OTLP_ENDPOINT);
if (OTLP_TRACE_PROPAGATION) {
console.log("OpenTelemetry initialized with trace propagation");
} else {
console.warn("OpenTelemetry initialized without trace propagation");
}
} else {
console.warn("OpenTelemetry not initialized because no OTLP endpoint is set");
}
export const runtime = ManagedRuntime.make(WebSdkLive);

40
web/src/api/notes.ts Normal file
View file

@ -0,0 +1,40 @@
import {HttpClient, HttpClientRequest, HttpClientResponse,} from "@effect/platform";
import * as Schema from "@effect/schema/Schema";
import {Effect} from "effect";
import {OTLP_TRACE_PROPAGATION} from "../env.ts";
import {APIResponse, BASE_URL, FailedAPIResponse} from "./response.ts";
export const NoteId = Schema.UUID.pipe(Schema.brand("UserId"));
export type NoteId = Schema.Schema.Type<typeof NoteId>;
export const NoteVisibility = Schema.Literal("public", "private", "direct");
export type NoteVisibility = Schema.Schema.Type<typeof NoteVisibility>;
export class Note extends Schema.Class<Note>("Note")({
id: NoteId,
visibility: NoteVisibility,
}) {
}
export class NoteCreation extends Schema.Class<NoteCreation>("NoteCreation")({
content: Schema.String,
visibility: Schema.optionalWith(NoteVisibility, {default: () => "public"}),
mentions: Schema.optional(Schema.Array(Schema.String)),
}) {
}
const createNoteBody = HttpClientRequest.schemaBody(NoteCreation);
export const createNote = (data: NoteCreation) =>
HttpClientRequest.post(`${BASE_URL}/api/app/notes/`).pipe(
createNoteBody(data),
Effect.andThen(HttpClient.fetch),
HttpClientResponse.schemaBodyJsonScoped(APIResponse(Note)),
Effect.filterOrFail(
(res) => res.ok,
(res) => (res as FailedAPIResponse).error,
),
Effect.map((d) => d.data),
Effect.either,
Effect.withSpan("createNote", {attributes: {data: data}}),
HttpClient.withTracerPropagation(OTLP_TRACE_PROPAGATION),
);

42
web/src/api/response.ts Normal file
View file

@ -0,0 +1,42 @@
import * as Schema from "@effect/schema/Schema";
export const BASE_URL = import.meta.env.VITE_BASE_URL ?? "INVALID";
export class APIError extends Schema.Class<APIError>("APIError")({
status_code: Schema.Number,
description: Schema.String,
metadata: Schema.optional(
Schema.Record({key: Schema.String, value: Schema.Any}),
),
}) {
_tag = "APIError" as const;
static toString(error: APIError) {
return `${error.status_code}: ${error.description}${error.metadata ? JSON.stringify(error.metadata) : ""}`;
}
override toString() {
return APIError.toString(this);
}
get message() {
return this.toString();
}
}
export const FailedAPIResponse = Schema.Struct({
ok: Schema.Literal(false),
error: APIError,
data: Schema.Null,
});
export type FailedAPIResponse = Schema.Schema.Type<typeof FailedAPIResponse>;
export const APIResponse = <A, I>(data: Schema.Schema<A, I>) =>
Schema.Union(
Schema.Struct({
ok: Schema.Literal(true),
data: data,
error: Schema.Null,
}),
FailedAPIResponse,
);