317 lines
7.8 KiB
TypeScript
317 lines
7.8 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 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: array(stackImage),
|
|
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();
|
|
}
|
|
};
|