329 lines
8.1 KiB
TypeScript

import { getTokenProperties } from "@components/protected-route";
import { fetch } from "@tauri-apps/plugin-http";
import {
type InferOutput,
array,
null_,
literal,
nullable,
parse,
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 refreshTokenValidator = strictObject({
access: string(),
})
export const getAccessToken = async (): Promise<string> => {
let accessToken = localStorage.getItem("access")?.toString();
const refreshToken = localStorage.getItem("refresh")?.toString();
if (accessToken == null && refreshToken == null) {
throw new Error("your are not logged in")
}
const isValidAccessToken = accessToken != null && getTokenProperties(accessToken).exp.getTime() * 1000 > Date.now()
if (!isValidAccessToken) {
const newAccessToken = await fetch(getBaseRequest({
path: 'auth/refresh', method: "POST", body: JSON.stringify({
refresh: refreshToken,
})
})).then(r => r.json());
const { access } = parse(refreshTokenValidator, newAccessToken);
localStorage.setItem("access", access);
accessToken = access
}
return accessToken!
}
const getBaseAuthorizedRequest = async ({
path,
body,
method,
}: BaseRequestParams): Promise<Request> => {
const accessToken = await getAccessToken();
return new Request(`${base}/${path}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
body,
method,
});
};
export const sendImageFile = async (
imageName: string,
file: File,
): Promise<InferOutput<typeof imageValidator>> => {
const request = await 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(imageValidator, 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<void> => {
const request = await getBaseAuthorizedRequest({
path: `images/${imageID}`,
method: "DELETE",
});
await fetch(request);
}
export const deleteImageFromStack = async (listID: string, imageID: string): Promise<void> => {
const request = await getBaseAuthorizedRequest({
path: `stacks/${listID}/${imageID}`,
method: "DELETE",
});
await fetch(request);
}
export const deleteStackItem = async (
stackID: string,
schemaItemID: string,
): Promise<void> => {
const request = await getBaseAuthorizedRequest({
path: `stacks/${stackID}/${schemaItemID}`,
method: "DELETE",
});
await fetch(request);
}
export const deleteList = async (listID: string): Promise<void> => {
const request = await getBaseAuthorizedRequest({
path: `stacks/${listID}`,
method: "DELETE",
});
await fetch(request);
}
export class ImageLimitReached extends Error {
constructor() {
super();
}
}
export const sendImage = async (
imageName: string,
base64Image: string,
): Promise<InferOutput<typeof imageValidator>> => {
const request = await 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(imageValidator, res);
if (!parsedRes.success) {
console.log("Parsing issues: ", parsedRes.issues)
throw new Error(JSON.stringify(parsedRes.issues));
}
return parsedRes.output;
};
const imageValidator = strictObject({
ID: pipe(string(), uuid()),
CreatedAt: string(),
UserID: pipe(string(), uuid()),
Description: string(),
Image: null_(),
ImageName: string(),
Status: union([literal('not-started'), literal('in-progress'), literal('complete')]),
})
const userImageValidator = strictObject({
...imageValidator.entries,
ImageStacks: pipe(nullable(array(
strictObject({
ID: pipe(string(), uuid()),
ImageID: pipe(string(), uuid()),
ListID: pipe(string(), uuid()),
}),
)), transform(l => l ?? [])),
});
const stackItem = strictObject({
ID: pipe(string(), uuid()),
ImageID: pipe(string(), uuid()),
SchemaItemID: pipe(string(), uuid()),
Value: string(),
})
const stackImage = strictObject({
ID: pipe(string(), uuid()),
ImageID: pipe(string(), uuid()),
StackID: pipe(string(), uuid()),
Items: pipe(nullable(array(stackItem)), transform(l => l ?? [])),
});
const stackSchemaItem = strictObject({
ID: pipe(string(), uuid()),
StackID: pipe(string(), uuid()),
Description: string(),
Item: string(),
Value: string(),
})
const stackValidator = strictObject({
ID: pipe(string(), uuid()),
CreatedAt: string(),
UserID: pipe(string(), uuid()),
Description: string(),
Status: union([literal('not-started'), literal('in-progress'), literal('complete')]),
Name: string(),
Images: pipe(nullable(array(stackImage)), transform(l => l ?? [])),
SchemaItems: array(stackSchemaItem),
});
export type List = InferOutput<typeof stackValidator>;
const imageRequestValidator = strictObject({
UserImages: array(userImageValidator),
Stacks: array(stackValidator),
});
export type JustTheImageWhatAreTheseNames = InferOutput<
typeof userImageValidator
>[];
export const getUserImages = async (): Promise<
InferOutput<typeof imageRequestValidator>
> => {
const request = await 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("Schema error: ", parsedRes.issues)
throw new Error(JSON.stringify(parsedRes.issues));
}
return parsedRes.output;
};
export const postLogin = async (email: string): Promise<void> => {
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<InferOutput<typeof codeValidator>> => {
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("Schema error: ", 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<void> => {
const request = await 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();
}
};