You've already forked AstralRinth
forked from didirus/AstralRinth
Merge tag 'v0.10.16' into beta
This commit is contained in:
@@ -11,12 +11,35 @@ export class BackupsModule extends ServerModule {
|
||||
}
|
||||
|
||||
async create(backupName: string): Promise<string> {
|
||||
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
|
||||
method: 'POST',
|
||||
body: { name: backupName },
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
return response.id
|
||||
const tempId = `temp-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
||||
const tempBackup: Backup = {
|
||||
id: tempId,
|
||||
name: backupName,
|
||||
created_at: new Date().toISOString(),
|
||||
locked: false,
|
||||
automated: false,
|
||||
interrupted: false,
|
||||
ongoing: true,
|
||||
task: { create: { progress: 0, state: 'ongoing' } },
|
||||
}
|
||||
this.data.push(tempBackup)
|
||||
|
||||
try {
|
||||
const response = await useServersFetch<{ id: string }>(`servers/${this.serverId}/backups`, {
|
||||
method: 'POST',
|
||||
body: { name: backupName },
|
||||
})
|
||||
|
||||
const backup = this.data.find((b) => b.id === tempId)
|
||||
if (backup) {
|
||||
backup.id = response.id
|
||||
}
|
||||
|
||||
return response.id
|
||||
} catch (error) {
|
||||
this.data = this.data.filter((b) => b.id !== tempId)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async rename(backupId: string, newName: string): Promise<void> {
|
||||
@@ -24,35 +47,47 @@ export class BackupsModule extends ServerModule {
|
||||
method: 'POST',
|
||||
body: { name: newName },
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async delete(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async restore(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
const backup = this.data.find((b) => b.id === backupId)
|
||||
if (backup) {
|
||||
if (!backup.task) backup.task = {}
|
||||
backup.task.restore = { progress: 0, state: 'ongoing' }
|
||||
}
|
||||
|
||||
try {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/restore`, {
|
||||
method: 'POST',
|
||||
})
|
||||
} catch (error) {
|
||||
if (backup?.task?.restore) {
|
||||
delete backup.task.restore
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async lock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/lock`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async unlock(backupId: string): Promise<void> {
|
||||
await useServersFetch(`servers/${this.serverId}/backups/${backupId}/unlock`, {
|
||||
method: 'POST',
|
||||
})
|
||||
await this.fetch() // Refresh this module
|
||||
await this.fetch()
|
||||
}
|
||||
|
||||
async retry(backupId: string): Promise<void> {
|
||||
@@ -71,4 +106,87 @@ export class BackupsModule extends ServerModule {
|
||||
async getAutoBackup(): Promise<AutoBackupSettings> {
|
||||
return await useServersFetch(`servers/${this.serverId}/autobackup`)
|
||||
}
|
||||
|
||||
downloadBackup(
|
||||
backupId: string,
|
||||
backupName: string,
|
||||
): {
|
||||
promise: Promise<void>
|
||||
onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) => void
|
||||
cancel: () => void
|
||||
} {
|
||||
const progressSubject = new EventTarget()
|
||||
const abortController = new AbortController()
|
||||
|
||||
const downloadPromise = new Promise<void>((resolve, reject) => {
|
||||
const auth = this.server.general?.node
|
||||
if (!auth?.instance || !auth?.token) {
|
||||
reject(new Error('Missing authentication credentials'))
|
||||
return
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
xhr.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const progress = e.loaded / e.total
|
||||
progressSubject.dispatchEvent(
|
||||
new CustomEvent('progress', {
|
||||
detail: { loaded: e.loaded, total: e.total, progress },
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// progress = -1 to indicate indeterminate size
|
||||
progressSubject.dispatchEvent(
|
||||
new CustomEvent('progress', {
|
||||
detail: { loaded: e.loaded, total: 0, progress: -1 },
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
const blob = xhr.response
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${backupName}.zip`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
window.URL.revokeObjectURL(url)
|
||||
resolve()
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Download failed with status ${xhr.status}`))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => reject(new Error('Download failed'))
|
||||
xhr.onabort = () => reject(new Error('Download cancelled'))
|
||||
|
||||
xhr.open(
|
||||
'GET',
|
||||
`https://${auth.instance}/modrinth/v0/backups/${backupId}/download?auth=${auth.token}`,
|
||||
)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.send()
|
||||
|
||||
abortController.signal.addEventListener('abort', () => xhr.abort())
|
||||
})
|
||||
|
||||
return {
|
||||
promise: downloadPromise,
|
||||
onProgress: (cb: (p: { loaded: number; total: number; progress: number }) => void) => {
|
||||
progressSubject.addEventListener('progress', ((e: CustomEvent) => {
|
||||
cb(e.detail)
|
||||
}) as EventListener)
|
||||
},
|
||||
cancel: () => abortController.abort(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface ServersFetchOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||
contentType?: string
|
||||
body?: Record<string, any>
|
||||
version?: number
|
||||
version?: number | 'internal'
|
||||
override?: {
|
||||
url?: string
|
||||
token?: string
|
||||
@@ -30,7 +30,7 @@ export async function useServersFetch<T>(
|
||||
'[Modrinth Servers] Cannot fetch without auth',
|
||||
10000,
|
||||
)
|
||||
throw new ModrinthServerError('Missing auth token', 401, error, module)
|
||||
throw new ModrinthServerError('Missing auth token', 401, error, module, undefined, undefined)
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -52,7 +52,14 @@ export async function useServersFetch<T>(
|
||||
'[Modrinth Servers] Circuit breaker open - too many recent failures',
|
||||
503,
|
||||
)
|
||||
throw new ModrinthServerError('Service temporarily unavailable', 503, error, module)
|
||||
throw new ModrinthServerError(
|
||||
'Service temporarily unavailable',
|
||||
503,
|
||||
error,
|
||||
module,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
|
||||
if (now - lastFailureTime.value > 30000) {
|
||||
@@ -69,7 +76,14 @@ export async function useServersFetch<T>(
|
||||
'[Modrinth Servers] Cannot fetch without base url. Make sure to set a PYRO_BASE_URL in environment variables',
|
||||
10001,
|
||||
)
|
||||
throw new ModrinthServerError('Configuration error: Missing PYRO_BASE_URL', 500, error, module)
|
||||
throw new ModrinthServerError(
|
||||
'Configuration error: Missing PYRO_BASE_URL',
|
||||
500,
|
||||
error,
|
||||
module,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
|
||||
const versionString = `v${version}`
|
||||
@@ -82,7 +96,9 @@ export async function useServersFetch<T>(
|
||||
? `https://${newOverrideUrl}/${path.replace(/^\//, '')}`
|
||||
: version === 0
|
||||
? `${base}/modrinth/v${version}/${path.replace(/^\//, '')}`
|
||||
: `${base}/v${version}/${path.replace(/^\//, '')}`
|
||||
: version === 'internal'
|
||||
? `${base}/_internal/${path.replace(/^\//, '')}`
|
||||
: `${base}/v${version}/${path.replace(/^\//, '')}`
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'User-Agent': 'Modrinth/1.0 (https://modrinth.com)',
|
||||
@@ -177,6 +193,7 @@ export async function useServersFetch<T>(
|
||||
fetchError,
|
||||
module,
|
||||
v1Error,
|
||||
error.data,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -198,6 +215,8 @@ export async function useServersFetch<T>(
|
||||
undefined,
|
||||
fetchError,
|
||||
module,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -210,7 +229,14 @@ export async function useServersFetch<T>(
|
||||
statusCode,
|
||||
lastError,
|
||||
)
|
||||
throw new ModrinthServerError('Maximum retry attempts reached', statusCode, pyroError, module)
|
||||
throw new ModrinthServerError(
|
||||
'Maximum retry attempts reached',
|
||||
statusCode,
|
||||
pyroError,
|
||||
module,
|
||||
undefined,
|
||||
lastError.data,
|
||||
)
|
||||
}
|
||||
|
||||
const fetchError = new ModrinthServersFetchError(
|
||||
@@ -218,5 +244,12 @@ export async function useServersFetch<T>(
|
||||
undefined,
|
||||
lastError || undefined,
|
||||
)
|
||||
throw new ModrinthServerError('Maximum retry attempts reached', undefined, fetchError, module)
|
||||
throw new ModrinthServerError(
|
||||
'Maximum retry attempts reached',
|
||||
undefined,
|
||||
fetchError,
|
||||
module,
|
||||
undefined,
|
||||
undefined,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user