mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-17 15:01:42 +08:00
fix(frontend): make backenddata more robust
This commit is contained in:
parent
27bda171e1
commit
9072d02090
@ -20,27 +20,9 @@ export default defineNuxtModule({
|
||||
},
|
||||
setup(moduleOptions, nuxt) {
|
||||
nuxt.hook("prepare:types", async () => {
|
||||
let state = {} as BackendData;
|
||||
try {
|
||||
const data = await fs.readFile(moduleOptions.path, "utf8");
|
||||
state = JSON.parse(data);
|
||||
} catch {
|
||||
// File doesn't exist, create folder
|
||||
await fs.mkdir("./generated", { recursive: true });
|
||||
}
|
||||
const state = await loadState(moduleOptions.path);
|
||||
|
||||
if (
|
||||
// Skip regeneration if within TTL...
|
||||
state?.meta?.lastGenerated &&
|
||||
new Date(state.meta.lastGenerated).getTime() + moduleOptions.ttl > Date.now() &&
|
||||
// ...but only if the API URL is the same
|
||||
state.meta.apiUrl === moduleOptions.serverUrl &&
|
||||
// ...and the version is the same
|
||||
state.meta.version === moduleOptions.version
|
||||
) {
|
||||
backendDataLog("Not generating backend data since its still valid, lastGenerated was", new Date(state.meta.lastGenerated));
|
||||
return;
|
||||
}
|
||||
if (!needsRefresh(state, moduleOptions.ttl, moduleOptions.serverUrl, moduleOptions.version)) return;
|
||||
|
||||
backendDataLog("Generating backend data...");
|
||||
state.meta = {
|
||||
@ -49,48 +31,91 @@ export default defineNuxtModule({
|
||||
version: moduleOptions.version,
|
||||
};
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
headers: {
|
||||
"user-agent": `Hangar Backend Data Generator (bots@papermc.io)`,
|
||||
},
|
||||
baseURL: moduleOptions.serverUrl + "/api/internal/data",
|
||||
});
|
||||
axiosInstance.interceptors.response.use(undefined, (err) => {
|
||||
if (axios.isAxiosError(err)) {
|
||||
if (err.response?.status === 404) {
|
||||
backendDataLog("Couldn't load " + err.request?.path + ", skipping");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
try {
|
||||
await loadData(state, axiosInstance);
|
||||
|
||||
backendDataLog("state", state);
|
||||
|
||||
await fs.writeFile(moduleOptions.path, JSON.stringify(state));
|
||||
|
||||
backendDataLog("Backend data generated!");
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err)) {
|
||||
const transformedError = {
|
||||
code: err?.code,
|
||||
requestUrl: err?.request?.path,
|
||||
status: err?.response?.status,
|
||||
data: err?.response?.data,
|
||||
};
|
||||
backendDataLog("Axios error while generating backend data:", transformedError);
|
||||
} else {
|
||||
backendDataLog("Unknown error while generating backend data:", err);
|
||||
}
|
||||
await fs.writeFile(moduleOptions.path, JSON.stringify({}));
|
||||
}
|
||||
await generateBackendData(state, moduleOptions.path);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
async function generateBackendData(state: BackendData, path: string, retry = true) {
|
||||
const axiosInstance = prepareAxios(state.meta.apiUrl);
|
||||
|
||||
try {
|
||||
await loadData(state, axiosInstance);
|
||||
|
||||
backendDataLog("state", state);
|
||||
|
||||
await fs.writeFile(path, JSON.stringify(state));
|
||||
|
||||
backendDataLog("Backend data generated!");
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err)) {
|
||||
const transformedError = {
|
||||
code: err?.code,
|
||||
requestUrl: err?.request?.path,
|
||||
status: err?.response?.status,
|
||||
data: err?.response?.data,
|
||||
};
|
||||
backendDataLog("Axios error while generating backend data:", transformedError);
|
||||
} else {
|
||||
backendDataLog("Unknown error while generating backend data:", err);
|
||||
}
|
||||
if (retry) {
|
||||
backendDataLog("Try running against production...");
|
||||
state.meta.apiUrl = "https://hangar.papermc.io";
|
||||
await generateBackendData(state, path, false);
|
||||
} else {
|
||||
await fs.writeFile(path, JSON.stringify({}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadState(path: string): Promise<BackendData> {
|
||||
let state = {} as BackendData;
|
||||
try {
|
||||
const data = await fs.readFile(path, "utf8");
|
||||
state = JSON.parse(data);
|
||||
} catch {
|
||||
// File doesn't exist, create folder
|
||||
await fs.mkdir("./generated", { recursive: true });
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function needsRefresh(state: BackendData, ttl: number, serverUrl: string, version: number) {
|
||||
if (
|
||||
// Skip regeneration if within TTL...
|
||||
state?.meta?.lastGenerated &&
|
||||
new Date(state.meta.lastGenerated).getTime() + ttl > Date.now() &&
|
||||
// ...but only if the API URL is the same
|
||||
state.meta.apiUrl === serverUrl &&
|
||||
// ...and the version is the same
|
||||
state.meta.version === version
|
||||
) {
|
||||
backendDataLog("Not generating backend data since its still valid, lastGenerated was", new Date(state.meta.lastGenerated));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function prepareAxios(serverUrl: string): AxiosInstance {
|
||||
const axiosInstance = axios.create({
|
||||
headers: {
|
||||
"user-agent": `Hangar Backend Data Generator (bots@papermc.io)`,
|
||||
},
|
||||
baseURL: serverUrl + "/api/internal/data",
|
||||
});
|
||||
axiosInstance.interceptors.response.use(undefined, (err) => {
|
||||
if (axios.isAxiosError(err)) {
|
||||
if (err.response?.status === 404) {
|
||||
backendDataLog("Couldn't load " + err.request?.path + ", skipping");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
return axiosInstance;
|
||||
}
|
||||
|
||||
async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
|
||||
const [
|
||||
projectCategories,
|
||||
|
@ -6,10 +6,10 @@ import { IPlatform, IProjectCategory, IPrompt } from "hangar-internal";
|
||||
import backendData from "~/generated/backendData.json";
|
||||
import { Option } from "~/lib/types/components/ui/InputAutocomplete";
|
||||
|
||||
const typedBackendData = backendData as unknown as BackendData;
|
||||
const typedBackendData = { ...backendData } as unknown as BackendData;
|
||||
|
||||
// convert to bigint
|
||||
const permissionResult = (typedBackendData.permissions as unknown as IPermission[]).map(({ value, frontendName, permission }) => ({
|
||||
const permissionResult = (typedBackendData.permissions as unknown as IPermission[])?.map(({ value, frontendName, permission }) => ({
|
||||
value,
|
||||
frontendName,
|
||||
permission: BigInt("0b" + permission),
|
||||
@ -22,7 +22,7 @@ typedBackendData.platforms = convertToMap(typedBackendData.platforms as unknown
|
||||
typedBackendData.prompts = convertToMap(typedBackendData.prompts as unknown as IPrompt[], (value) => value.name);
|
||||
|
||||
// main export
|
||||
export const useBackendData = { ...typedBackendData } as BackendData;
|
||||
export const useBackendData = typedBackendData;
|
||||
|
||||
// helpers
|
||||
export const useVisibleCategories = computed<IProjectCategory[]>(() =>
|
||||
@ -33,7 +33,7 @@ export const useVisiblePlatforms = computed(() => (useBackendData.platforms ? [.
|
||||
export const useLicenseOptions = computed<Option[]>(() => useBackendData.licenses.map<Option>((l) => ({ value: l, text: l })));
|
||||
export const useCategoryOptions = computed<Option[]>(() => useVisibleCategories.value.map<Option>((c) => ({ value: c.apiName, text: c.title })));
|
||||
|
||||
function convertToMap<E, T>(values: T[], toStringFunc: (value: T) => string): Map<E, T> {
|
||||
function convertToMap<E, T>(values: T[] = [], toStringFunc: (value: T) => string): Map<E, T> {
|
||||
const map = new Map<E, T>();
|
||||
for (const value of values) {
|
||||
const key: E = toStringFunc(value) as unknown as E;
|
||||
|
Loading…
Reference in New Issue
Block a user