fix(frontend): make backenddata more robust

This commit is contained in:
MiniDigger | Martin 2023-01-03 09:35:41 +01:00
parent 27bda171e1
commit 9072d02090
2 changed files with 87 additions and 62 deletions

View File

@ -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,

View File

@ -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;