add some types & functions to manage zipline

This commit is contained in:
Stef-00012 2025-01-21 21:08:45 +01:00
parent ed5c91435d
commit 370194a486
No known key found for this signature in database
GPG key ID: 28BE9A9E4EF0E6BF
20 changed files with 3170 additions and 97 deletions

35
functions/util.ts Normal file
View file

@ -0,0 +1,35 @@
import mimetypesJSON from "@/assets/mimetypes.json";
import type { Mimetypes } from "@/types/mimetypes";
const mimetypes = mimetypesJSON as Mimetypes;
export function generateRandomString(): string {
return Math.random().toString(36).substring(2, 6);
}
export function guessMimetype(
mimetype: keyof Mimetypes,
): Mimetypes[keyof Mimetypes] {
if (!mimetype) return "so";
const mime = mimetypes[mimetype];
if (!mime) return "so";
return mime;
}
export function convertToBlob(data: string): Blob {
const base64Data = data.split(",")[1];
const mimetype = data.split(":")[1].split(";").shift();
const string = atob(base64Data);
const length = string.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = string.charCodeAt(i);
}
const blob = new Blob([bytes], { type: mimetype || "image/png" });
return blob;
}

View file

@ -1,79 +0,0 @@
import * as db from "@/functions/database";
import type { APIRecentFiles, APIStats, APIUser } from "@/types/zipline";
import axios from "axios";
export async function getUser() {
const token = db.get("token")
const url = db.get("url")
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user`, {
headers: {
Authorization: token
}
})
return res.data.user as APIUser;
} catch(e) {
return null;
}
}
export async function getRecentFiles() {
const token = db.get("token")
const url = db.get("url")
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/recent`, {
headers: {
Authorization: token
}
})
return res.data as APIRecentFiles;
} catch(e) {
return null;
}
}
export async function getStats() {
const token = db.get("token")
const url = db.get("url")
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/stats`, {
headers: {
Authorization: token
}
})
return res.data as APIStats;
} catch(e) {
return null;
}
}
export async function getUserAvatar() {
const token = db.get("token")
const url = db.get("url")
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/avatar`, {
headers: {
Authorization: token
}
})
return res.data as string;
} catch(e) {
return null;
}
}

View file

@ -0,0 +1,65 @@
import * as db from "@/functions/database";
import type { APIExports } from "@/types/zipline";
import axios from "axios";
// GET /api/user/export
export async function getUserExports(): Promise<APIExports | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/export`, {
headers: {
Authorization: token,
},
});
return res.data.user;
} catch (e) {
return null;
}
}
// POST /api/user/export
export async function createUserExport(): Promise<{ running: boolean } | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.post(`${url}/api/user/export`, null, {
headers: {
Authorization: token,
},
});
return res.data.user;
} catch (e) {
return null;
}
}
// DELETE /api/user/export?id=[id]
export async function deleteUserExport(id: string): Promise<{ deleted: boolean } | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/user/export?id=${id}`, {
headers: {
Authorization: token,
},
});
return res.data.user;
} catch (e) {
return null;
}
}

257
functions/zipline/files.ts Normal file
View file

@ -0,0 +1,257 @@
import * as db from "@/functions/database";
import type { APIFile, APIFiles, APISettings } from "@/types/zipline";
import axios from "axios";
import { getSettings } from "@/functions/zipline/settings";
import bytesFn, { BytesOptions } from "bytes";
import { convertToBlob, generateRandomString, guessMimetype } from "../util";
import type { Mimetypes } from "@/types/mimetypes";
// GET /api/user/files
export async function getFiles(page: number): Promise<APIFiles | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const params = new URLSearchParams({
page: String(page),
});
try {
const res = await axios.get(`${url}/api/user/files?${params}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// GET /api/user/files/[id]
export async function getFile(id: string): Promise<APIFile | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/files/${id}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// DELETE /api/user/files/[id]
export async function deleteFile(id: string): Promise<APIFile | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/user/files/${id}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
interface UpdateFileTagsOptions {
add?: Array<string>;
remove?: Array<string>;
}
// PATCH /api/user/files/[id]
export async function updateFileTags(
id: string,
options: UpdateFileTagsOptions = {},
): Promise<APIFile | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const file = await getFile(id);
let newTags = (file?.tags || []).map((tag) => tag.id);
if (options.remove)
newTags = newTags.filter((tag) => !options.remove?.includes(tag));
if (options.add) newTags.push(...options.add);
const res = await axios.patch(
`${url}/api/user/files/${id}`,
{
tags: newTags,
},
{
headers: {
Authorization: token,
},
},
);
return res.data;
} catch (e) {
return null;
}
}
interface EditFileOptions {
originalName?: string;
maxViews?: number;
password?: string;
type?: string;
favorite?: boolean;
}
// PATCH /api/user/files/[id]
export async function editFile(
id: string,
options: EditFileOptions = {},
): Promise<APIFile | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.patch(`${url}/api/user/files/${id}`, options, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
interface UploadFileOptions {
text?: boolean;
maxViews?: number;
compression?: number;
format?: APISettings["filesDefaultFormat"];
password?: string;
filename?: string;
folder?: string;
overrideDomain?: string;
originalName?: boolean;
expiresAt?: Date;
}
// POST /api/upload
export async function uploadToZipline(
file: string,
options: UploadFileOptions = {},
) {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const blob = convertToBlob(file);
const settings = await getSettings();
const chunksMax = bytesFn(settings?.chunksMax || "95mb") || 95 * 1024 * 1024;
const chunksSize =
bytesFn(settings?.chunksSize || "25mb") || 25 * 1024 * 1024;
const headers: {
[key: string]: string;
} = {};
headers.Authorization = token;
headers["X-Zipline-Format"] = options.format?.toLowerCase() || "random";
if (options.compression)
headers["X-Zipline-Image-Compression-Percent"] = String(
options.compression,
);
if (options.maxViews)
headers["X-Zipline-Max-Views"] = String(options.maxViews);
if (options.password) headers["X-Zipline-Password"] = options.password;
if (options.folder) headers["X-Zipline-Folder"] = options.folder;
if (options.overrideDomain)
headers["X-Zipline-Domain"] = options.overrideDomain;
if (options.expiresAt)
headers["X-Zipline-Deletes-At"] = `date=${options.expiresAt.toISOString()}`;
if (options.originalName) headers["X-Zipline-Original-Name"] = "true";
if (options.filename) headers["X-Zipline-Filename"] = options.filename;
if (blob.size < chunksMax) {
const filename = `${new Date().toISOString()}.${guessMimetype(blob.type as keyof Mimetypes) || "png"}`;
const formData = new FormData();
if (options.text)
formData.append("file", blob, `${new Date().toISOString()}.txt`);
else formData.append("file", blob, filename);
try {
const res = await axios.post(`${url}/api/upload`, formData, {
headers,
});
const data = await res.data;
const fileUrl = data?.files?.[0]?.url;
if (fileUrl) return url;
return null;
} catch (e) {
return null;
}
} else {
const numberOfChunks = Math.ceil(blob.size / chunksSize);
const identifier = generateRandomString();
const filename = `${new Date().toISOString()}.${(await guessMimetype(blob.type as keyof Mimetypes)) || "png"}`;
for (let i = numberOfChunks - 1; i >= 0; i--) {
const chunkId = numberOfChunks - i;
const start = i * chunksSize;
const end = Math.min(start + chunksSize, blob.size);
const chunk = blob.slice(start, end);
const formData = new FormData();
formData.append("file", chunk, filename);
headers["Content-Range"] = `bytes ${start}-${end - 1}/${blob.size}`;
headers["X-Zipline-P-Filename"] = filename;
headers["X-Zipline-P-Lastchunk"] = i === 0 ? "true" : "false";
headers["X-Zipline-P-Identifier"] = identifier;
headers["X-Zipline-P-Mimetype"] = blob.type;
try {
const response = await axios.post(`${url}/api/upload`, formData, {
headers,
});
const data = await response.data;
if (data.files?.length > 0) return data.files?.[0]?.url;
} catch (e) {
return null;
}
}
}
}

View file

@ -0,0 +1,147 @@
import * as db from "@/functions/database";
import type {
APIFoldersNoIncl,
APIFolders,
APIFolder,
} from "@/types/zipline";
import axios from "axios";
// GET /api/user/folders
export async function getFolders<T extends boolean | undefined = undefined>(
noIncl?: T,
): Promise<T extends true ? APIFoldersNoIncl | null : APIFolders | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const params = new URLSearchParams();
if (noIncl) params.append("noincl", "true");
try {
const res = await axios.get(`${url}/api/user/folders?${params}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// POST /api/user/folders
export async function createFolder(name: string, isPublic = false): Promise<APIFolder | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.post(`${url}/api/user/folders`, {
name,
isPublic
}, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// PATCH /api/user/folders/[id]
export async function editFolder(id: string, isPublic: boolean): Promise<APIFolder | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.patch(`${url}/api/user/folders/${id}`, {
isPublic
}, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// DELETE /api/user/folders/[id]
export async function deleteFolder(id: string): Promise<APIFolder | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/user/folders/${id}`, {
headers: {
Authorization: token,
},
data: {
delete: "folder"
}
});
return res.data;
} catch (e) {
return null;
}
}
// DELETE /api/user/folders/[folderId]
export async function removeFileFromFolder(folderId: string, fileId: string): Promise<APIFolder | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/user/folders/${folderId}`, {
headers: {
Authorization: token,
},
data: {
delete: "file",
id: fileId
}
});
return res.data;
} catch (e) {
return null;
}
}
// POST /api/user/folders/[folderId]
export async function addFileToFolder(folderId: string, fileId: string): Promise<APIFolder | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.post(`${url}/api/user/folders/${folderId}`, {
id: fileId
}, {
headers: {
Authorization: token,
}
});
return res.data;
} catch (e) {
return null;
}
}

View file

@ -0,0 +1,25 @@
import * as db from "@/functions/database";
import type {
APIInvites,
} from "@/types/zipline";
import axios from "axios";
// GET /api/auth/invites
export async function getInvites(): Promise<APIInvites | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/auth/invites`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}

View file

@ -0,0 +1,47 @@
import * as db from "@/functions/database";
import type {
APISettings,
} from "@/types/zipline";
import axios from "axios";
// GET /api/server/settings
export async function getSettings(): Promise<APISettings | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/server/settings`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// PATCH /api/server/settings
export async function updateSettings(
settings: Partial<APISettings> = {},
): Promise<APISettings | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.patch(`${url}/api/server/settings`, settings, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}

View file

@ -0,0 +1,54 @@
import * as db from "@/functions/database";
import type {
APIUserStats,
APIStats,
} from "@/types/zipline";
import axios from "axios";
// GET /api/stats
export async function getStats(
from?: string,
to?: string,
): Promise<APIStats | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const params = new URLSearchParams();
if (from) params.append("from", from);
if (to) params.append("to", to);
try {
const res = await axios.get(`${url}/api/stats?${params}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// GET /api/user/stats
export async function getUserStats(): Promise<APIUserStats | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/stats`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}

117
functions/zipline/urls.ts Normal file
View file

@ -0,0 +1,117 @@
import * as db from "@/functions/database";
import type {
APIURLs,
APIURL,
} from "@/types/zipline";
import axios from "axios";
// GET /user/urls
export async function getURLs(): Promise<APIURLs | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/urls`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
interface CreateURLParams {
destination: string;
vanity?: string;
maxView?: number;
}
// POST /api/user/urls
export async function createURL({
destination,
vanity,
maxView,
}: CreateURLParams): Promise<{
url: string;
} | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const headers: { [key: string]: string | number } = {};
if (maxView) headers["X-Zipline-Max-Views"] = maxView;
try {
const res = await axios.post(
`${url}/api/user/urls`,
{
destination,
vanity,
},
{
headers: {
Authorization: token,
...headers,
},
},
);
return res.data;
} catch (e) {
return null;
}
}
// DELETE /api/user/urls/[id]
export async function deleteURL(id: string): Promise<APIURL | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/user/urls/${id}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
interface editURLOptions {
destination?: string;
maxViews?: number;
password?: string;
}
// PATCH /api/user/urls/[id]
export async function editURL(
id: string,
options: editURLOptions = {},
): Promise<APIURL | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.patch(`${url}/api/user/urls/${id}`, options, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}

66
functions/zipline/user.ts Normal file
View file

@ -0,0 +1,66 @@
import * as db from "@/functions/database";
import type {
APIRecentFiles,
APISelfUser,
} from "@/types/zipline";
import axios from "axios";
// GET /api/user
export async function getUser(): Promise<APISelfUser | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user`, {
headers: {
Authorization: token,
},
});
return res.data.user;
} catch (e) {
return null;
}
}
// GET /api/user/recent
export async function getRecentFiles(): Promise<APIRecentFiles | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/recent`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// GET /api/user/avatar
export async function getUserAvatar(): Promise<string | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.get(`${url}/api/user/avatar`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}

141
functions/zipline/users.ts Normal file
View file

@ -0,0 +1,141 @@
import * as db from "@/functions/database";
import type {
APIUsersNoIncl,
APIUsers,
APIUser,
APIUserQuota,
} from "@/types/zipline";
import axios from "axios";
// GET /api/users(?noincl=true)
export async function getUsers<T extends boolean | undefined = undefined>(
noIncl?: T,
): Promise<T extends true ? APIUsersNoIncl | null : APIUsers | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
const params = new URLSearchParams();
if (noIncl) params.append("noincl", "true");
try {
const res = await axios.get(`${url}/api/users?${params}`, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}
// POST /api/users
export async function createUser(
username: string,
password: string,
role: APIUser["role"],
avatar?: string,
) {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.post(
`${url}/api/users`,
{
username,
password,
role,
avatar,
},
{
headers: {
Authorization: token,
},
},
);
return res.data;
} catch (e) {
return null;
}
}
// DELETE /api/users/[id]
export async function deleteUser(
id: string,
deleteData = false,
): Promise<APIUser | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.delete(`${url}/api/users/${id}`, {
headers: {
Authorization: token,
},
data: {
delete: deleteData,
},
});
return res.data;
} catch (e) {
return null;
}
}
// PATCH /api/users/[id]
export async function editUser(
id: string,
options: Partial<
Omit<
APIUser,
| "id"
| "createdAt"
| "updatedAt"
| "role"
| "view"
| "oauthProviders"
| "totpSecret"
| "passkeys"
| "sessions"
| "quota"
> & {
role?: Exclude<APIUser["role"], "SUPERADMIN">;
quota?: Partial<
Omit<
APIUserQuota,
"id" | "createdAt" | "updatedAt" | "filesQuota" | "userId"
> & {
filesType?: APIUserQuota["filesQuota"];
}
>;
}
> = {},
): Promise<APIUser | null> {
const token = db.get("token");
const url = db.get("url");
if (!url || !token) return null;
try {
const res = await axios.patch(`${url}/api/users/${id}`, options, {
headers: {
Authorization: token,
},
});
return res.data;
} catch (e) {
return null;
}
}