import { fetch } from "@tauri-apps/plugin-http"; import { type InferOutput, array, literal, null_, nullable, pipe, safeParse, strictObject, string, transform, union, uuid, } from "valibot"; type BaseRequestParams = Partial<{ path: string; body: RequestInit["body"]; method: "GET" | "POST" | "DELETE"; }>; // export const base = "https://haystack.johncosta.tech"; export const base = "http://localhost:3040"; const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => { return new Request(`${base}/${path}`, { body, method, }); }; const getBaseAuthorizedRequest = ({ path, body, method, }: BaseRequestParams): Request => { return new Request(`${base}/${path}`, { headers: { Authorization: `Bearer ${localStorage.getItem("access")?.toString()}`, }, body, method, }); }; const sendImageResponseValidator = strictObject({ ID: pipe(string(), uuid()), ImageID: pipe(string(), uuid()), UserID: pipe(string(), uuid()), Status: string(), }); export const sendImageFile = async ( imageName: string, file: File, ): Promise> => { const request = getBaseAuthorizedRequest({ path: `images/${imageName}`, body: file, method: "POST", }); request.headers.set("Content-Type", "application/oclet-stream"); const res = await fetch(request).then((res) => res.json()); const parsedRes = safeParse(sendImageResponseValidator, res); if (!parsedRes.success) { console.log(parsedRes.issues) throw new Error(JSON.stringify(parsedRes.issues)); } return parsedRes.output; }; export const deleteImage = async ( imageID: string ): Promise => { const request = getBaseAuthorizedRequest({ path: `images/${imageID}`, method: "DELETE", }); await fetch(request); } export const deleteImageFromStack = async (listID: string, imageID: string): Promise => { const request = getBaseAuthorizedRequest({ path: `stacks/${listID}/${imageID}`, method: "DELETE", }); await fetch(request); } export class ImageLimitReached extends Error { constructor() { super(); } } export const sendImage = async ( imageName: string, base64Image: string, ): Promise> => { const request = getBaseAuthorizedRequest({ path: `images/${imageName}`, body: base64Image, method: "POST", }); request.headers.set("Content-Type", "application/base64"); const rawRes = await fetch(request); if (!rawRes.ok && rawRes.status == 429) { throw new ImageLimitReached() } const res = await rawRes.json(); const parsedRes = safeParse(sendImageResponseValidator, res); if (!parsedRes.success) { console.log(parsedRes.issues) throw new Error(JSON.stringify(parsedRes.issues)); } return parsedRes.output; }; const imageMetaValidator = strictObject({ ID: pipe(string(), uuid()), ImageName: string(), Description: string(), Image: null_(), }); const userImageValidator = strictObject({ ID: pipe(string(), uuid()), CreatedAt: pipe(string()), ImageID: pipe(string(), uuid()), UserID: pipe(string(), uuid()), Image: strictObject({ ...imageMetaValidator.entries, ImageLists: pipe(nullable(array( strictObject({ ID: pipe(string(), uuid()), ImageID: pipe(string(), uuid()), ListID: pipe(string(), uuid()), }), )), transform(l => l ?? [])), }), }); const userProcessingImageValidator = strictObject({ ID: pipe(string(), uuid()), ImageID: pipe(string(), uuid()), UserID: pipe(string(), uuid()), Image: imageMetaValidator, Status: union([ literal("not-started"), literal("in-progress"), literal("complete"), ]), }); const listValidator = strictObject({ ID: pipe(string(), uuid()), UserID: pipe(string(), uuid()), CreatedAt: pipe(string()), Name: string(), Description: nullable(string()), Images: pipe( nullable( array( strictObject({ ID: pipe(string(), uuid()), ImageID: pipe(string(), uuid()), ListID: pipe(string(), uuid()), Items: array( strictObject({ ID: pipe(string(), uuid()), ImageID: pipe(string(), uuid()), SchemaItemID: pipe(string(), uuid()), Value: string(), }), ), }), ), ), transform((n) => n ?? []), ), Schema: strictObject({ ID: pipe(string(), uuid()), ListID: pipe(string(), uuid()), SchemaItems: array( strictObject({ ID: pipe(string(), uuid()), SchemaID: pipe(string(), uuid()), Item: string(), Value: nullable(string()), Description: string(), }), ), }), }); export type List = InferOutput; const imageRequestValidator = strictObject({ userImages: array(userImageValidator), processingImages: array(userProcessingImageValidator), lists: array(listValidator), }); export type JustTheImageWhatAreTheseNames = InferOutput< typeof userImageValidator >[]; export const getUserImages = async (): Promise< InferOutput > => { const request = getBaseAuthorizedRequest({ path: "images" }); const res = await fetch(request).then((res) => res.json()); console.log("Backend response: ", res); const parsedRes = safeParse(imageRequestValidator, res); if (!parsedRes.success) { console.log(parsedRes.issues) throw new Error(JSON.stringify(parsedRes.issues)); } return parsedRes.output; }; export const postLogin = async (email: string): Promise => { const request = getBaseRequest({ path: "auth/login", body: JSON.stringify({ email }), method: "POST", }); await fetch(request); }; const codeValidator = strictObject({ access: string(), refresh: string(), }); export const postCode = async ( email: string, code: string, ): Promise> => { const request = getBaseRequest({ path: "auth/code", body: JSON.stringify({ email, code }), method: "POST", }); const res = await fetch(request).then((res) => res.json()); const parsedRes = safeParse(codeValidator, res); if (!parsedRes.success) { console.log(parsedRes.issues) throw new Error(JSON.stringify(parsedRes.issues)); } return parsedRes.output; }; export class ReachedListLimit extends Error { constructor() { super(); } } export const createList = async ( title: string, description: string, ): Promise => { const request = getBaseAuthorizedRequest({ path: "stacks", method: "POST", body: JSON.stringify({ title, description }), }); request.headers.set("Content-Type", "application/json"); const res = await fetch(request); if (!res.ok && res.status == 429) { throw new ReachedListLimit(); } };