You've already forked AstralRinth
forked from didirus/AstralRinth
refactor: Huge pyro servers composable cleanup (#3745)
* refactor: start refactor of pyro servers module-based class * refactor: finish modules * refactor: start on type checking + matching api * refactor: finish pyro servers composable refactor * refactor: pyro -> modrinth * fix: import not refactored * fix: broken power action enums * fix: remove pyro mentions * fix: lint * refactor: fix option pages * fix: error renames * remove empty pyro-servers.ts file --------- Signed-off-by: IMB11 <hendersoncal117@gmail.com> Co-authored-by: Prospector <prospectordev@gmail.com>
This commit is contained in:
265
apps/frontend/src/composables/servers/modrinth-servers.ts
Normal file
265
apps/frontend/src/composables/servers/modrinth-servers.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { ModrinthServerError } from "@modrinth/utils";
|
||||
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
||||
import { useServersFetch } from "./servers-fetch.ts";
|
||||
|
||||
import {
|
||||
GeneralModule,
|
||||
ContentModule,
|
||||
BackupsModule,
|
||||
NetworkModule,
|
||||
StartupModule,
|
||||
WSModule,
|
||||
FSModule,
|
||||
} from "./modules/index.ts";
|
||||
|
||||
export function handleError(err: any) {
|
||||
if (err instanceof ModrinthServerError && err.v1Error) {
|
||||
addNotification({
|
||||
title: err.v1Error?.context ?? `An error occurred`,
|
||||
type: "error",
|
||||
text: err.v1Error.description,
|
||||
errorCode: err.v1Error.error,
|
||||
});
|
||||
} else {
|
||||
addNotification({
|
||||
title: "An error occurred",
|
||||
type: "error",
|
||||
text: err.message ?? (err.data ? err.data.description : err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ModrinthServer {
|
||||
readonly serverId: string;
|
||||
private errors: Partial<Record<ModuleName, ModuleError>> = {};
|
||||
|
||||
readonly general: GeneralModule;
|
||||
readonly content: ContentModule;
|
||||
readonly backups: BackupsModule;
|
||||
readonly network: NetworkModule;
|
||||
readonly startup: StartupModule;
|
||||
readonly ws: WSModule;
|
||||
readonly fs: FSModule;
|
||||
|
||||
constructor(serverId: string) {
|
||||
this.serverId = serverId;
|
||||
|
||||
this.general = new GeneralModule(this);
|
||||
this.content = new ContentModule(this);
|
||||
this.backups = new BackupsModule(this);
|
||||
this.network = new NetworkModule(this);
|
||||
this.startup = new StartupModule(this);
|
||||
this.ws = new WSModule(this);
|
||||
this.fs = new FSModule(this);
|
||||
}
|
||||
|
||||
async createMissingFolders(path: string): Promise<void> {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
const folders = path.split("/");
|
||||
let currentPath = "";
|
||||
|
||||
for (const folder of folders) {
|
||||
currentPath += "/" + folder;
|
||||
try {
|
||||
await this.fs.createFileOrFolder(currentPath, "directory");
|
||||
} catch {
|
||||
// Folder might already exist, ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchConfigFile(fileName: string): Promise<any> {
|
||||
return await useServersFetch(`servers/${this.serverId}/config/${fileName}`);
|
||||
}
|
||||
|
||||
constructServerProperties(properties: any): string {
|
||||
let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`;
|
||||
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
if (typeof value === "object") {
|
||||
fileContent += `${key}=${JSON.stringify(value)}\n`;
|
||||
} else if (typeof value === "boolean") {
|
||||
fileContent += `${key}=${value ? "true" : "false"}\n`;
|
||||
} else {
|
||||
fileContent += `${key}=${value}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
async processImage(iconUrl: string | undefined): Promise<string | undefined> {
|
||||
const sharedImage = useState<string | undefined>(`server-icon-${this.serverId}`);
|
||||
|
||||
if (sharedImage.value) {
|
||||
return sharedImage.value;
|
||||
}
|
||||
|
||||
try {
|
||||
const auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`);
|
||||
try {
|
||||
const fileData = await useServersFetch(`/download?path=/server-icon-original.png`, {
|
||||
override: auth,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
if (fileData instanceof Blob && import.meta.client) {
|
||||
const dataURL = await new Promise<string>((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
canvas.width = 512;
|
||||
canvas.height = 512;
|
||||
ctx?.drawImage(img, 0, 0, 512, 512);
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
sharedImage.value = dataURL;
|
||||
resolve(dataURL);
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(fileData);
|
||||
});
|
||||
return dataURL;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ModrinthServerError && error.statusCode === 404 && iconUrl) {
|
||||
// Handle external icon processing
|
||||
try {
|
||||
const response = await fetch(iconUrl);
|
||||
if (!response.ok) throw new Error("Failed to fetch icon");
|
||||
const file = await response.blob();
|
||||
const originalFile = new File([file], "server-icon-original.png", {
|
||||
type: "image/png",
|
||||
});
|
||||
|
||||
if (import.meta.client) {
|
||||
const dataURL = await new Promise<string>((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
canvas.width = 64;
|
||||
canvas.height = 64;
|
||||
ctx?.drawImage(img, 0, 0, 64, 64);
|
||||
canvas.toBlob(async (blob) => {
|
||||
if (blob) {
|
||||
const scaledFile = new File([blob], "server-icon.png", { type: "image/png" });
|
||||
await useServersFetch(`/create?path=/server-icon.png&type=file`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
body: scaledFile,
|
||||
override: auth,
|
||||
});
|
||||
await useServersFetch(`/create?path=/server-icon-original.png&type=file`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
body: originalFile,
|
||||
override: auth,
|
||||
});
|
||||
}
|
||||
}, "image/png");
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
sharedImage.value = dataURL;
|
||||
resolve(dataURL);
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(file);
|
||||
});
|
||||
return dataURL;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process external icon:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process server icon:", error);
|
||||
}
|
||||
|
||||
sharedImage.value = undefined;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async refresh(
|
||||
modules: ModuleName[] = [],
|
||||
options?: {
|
||||
preserveConnection?: boolean;
|
||||
preserveInstallState?: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
const modulesToRefresh =
|
||||
modules.length > 0
|
||||
? modules
|
||||
: (["general", "content", "backups", "network", "startup", "ws", "fs"] as ModuleName[]);
|
||||
|
||||
for (const module of modulesToRefresh) {
|
||||
try {
|
||||
switch (module) {
|
||||
case "general": {
|
||||
if (options?.preserveConnection) {
|
||||
const currentImage = this.general.image;
|
||||
const currentMotd = this.general.motd;
|
||||
const currentStatus = this.general.status;
|
||||
|
||||
await this.general.fetch();
|
||||
|
||||
if (currentImage) {
|
||||
this.general.image = currentImage;
|
||||
}
|
||||
if (currentMotd) {
|
||||
this.general.motd = currentMotd;
|
||||
}
|
||||
if (options.preserveInstallState && currentStatus === "installing") {
|
||||
this.general.status = "installing";
|
||||
}
|
||||
} else {
|
||||
await this.general.fetch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "content":
|
||||
await this.content.fetch();
|
||||
break;
|
||||
case "backups":
|
||||
await this.backups.fetch();
|
||||
break;
|
||||
case "network":
|
||||
await this.network.fetch();
|
||||
break;
|
||||
case "startup":
|
||||
await this.startup.fetch();
|
||||
break;
|
||||
case "ws":
|
||||
await this.ws.fetch();
|
||||
break;
|
||||
case "fs":
|
||||
await this.fs.fetch();
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors[module] = {
|
||||
error:
|
||||
error instanceof ModrinthServerError
|
||||
? error
|
||||
: new ModrinthServerError("Unknown error", undefined, error as Error),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get moduleErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
}
|
||||
|
||||
export const useModrinthServers = async (
|
||||
serverId: string,
|
||||
includedModules: ModuleName[] = ["general"],
|
||||
) => {
|
||||
const server = new ModrinthServer(serverId);
|
||||
await server.refresh(includedModules);
|
||||
return reactive(server);
|
||||
};
|
||||
Reference in New Issue
Block a user