feat(downloads): add remote download workers

This commit is contained in:
saltbo
2026-06-03 02:21:50 -04:00
parent cf12806902
commit 53076d7873
53 changed files with 15539 additions and 14 deletions
+25
View File
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { createTestApp } from '../test/setup'
describe('downloader OpenAPI', () => {
it('serves downloader documentation from the real app route', async () => {
const { app } = await createTestApp({ DOWNLOAD_TOKEN_SECRET: 'test-download-token-secret' })
const res = await app.request('/api/openapi/downloader.json')
expect(res.status).toBe(200)
const doc = (await res.json()) as { paths: Record<string, unknown> }
expect(Object.keys(doc.paths)).toEqual(
expect.arrayContaining([
'/api/auth/device/code',
'/api/auth/device/token',
'/api/download-tasks',
'/api/download-tasks/{id}',
'/api/downloader/heartbeat',
'/api/admin/downloaders',
'/api/admin/downloaders/{id}',
'/api/objects',
'/api/objects/{id}',
]),
)
})
})
+165
View File
@@ -0,0 +1,165 @@
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'
import { createMatterSchema } from '@shared/schemas'
import downloadTasks from '../routes/download-tasks'
import downloaders, { downloaderSelfRoute } from '../routes/downloaders'
const errorSchema = z.object({ error: z.string() }).openapi('ErrorResponse')
const deviceCodeRequestSchema = z
.object({
client_id: z.string(),
scope: z.string(),
})
.openapi('DeviceCodeRequest')
const deviceCodeSchema = z
.object({
device_code: z.string(),
user_code: z.string(),
verification_uri: z.string(),
verification_uri_complete: z.string(),
expires_in: z.number().int(),
interval: z.number().int(),
})
.openapi('DeviceCode')
const deviceTokenRequestSchema = z
.object({
grant_type: z.string(),
device_code: z.string(),
client_id: z.string(),
})
.openapi('DeviceTokenRequest')
const deviceTokenSchema = z
.object({
access_token: z.string(),
token_type: z.string(),
expires_in: z.number().int(),
scope: z.string(),
})
.openapi('DeviceToken')
const objectDraftSchema = z
.object({
id: z.string(),
name: z.string(),
uploadUrl: z.string(),
})
.openapi('ObjectDraft')
const confirmObjectRequestSchema = z
.object({
action: z.enum(['confirm']),
})
.openapi('ConfirmObjectRequest')
function jsonResponse(schema: z.ZodType, description: string) {
return {
content: {
'application/json': {
schema,
},
},
description,
}
}
function mountBetterAuthDeviceRoutes(app: OpenAPIHono) {
app.openapi(
createRoute({
method: 'post',
path: '/api/auth/device/code',
request: {
body: {
content: { 'application/json': { schema: deviceCodeRequestSchema } },
required: true,
},
},
responses: {
200: jsonResponse(deviceCodeSchema, 'Device authorization code'),
},
}),
(c) => c.json({} as z.infer<typeof deviceCodeSchema>, 200),
)
app.openapi(
createRoute({
method: 'post',
path: '/api/auth/device/token',
request: {
body: {
content: { 'application/json': { schema: deviceTokenRequestSchema } },
required: true,
},
},
responses: {
200: jsonResponse(deviceTokenSchema, 'Device access token'),
400: jsonResponse(errorSchema, 'Device login error'),
},
}),
(c) => c.json({} as z.infer<typeof deviceTokenSchema>, 200),
)
}
function mountObjectUploadRoutes(app: OpenAPIHono) {
app.openapi(
createRoute({
method: 'post',
path: '/api/objects',
request: {
body: {
content: { 'application/json': { schema: createMatterSchema } },
required: true,
},
},
responses: {
200: jsonResponse(objectDraftSchema, 'Object draft with upload URL'),
201: jsonResponse(objectDraftSchema, 'Object draft with upload URL'),
403: jsonResponse(errorSchema, 'Forbidden'),
409: jsonResponse(errorSchema, 'Name conflict'),
},
}),
(c) => c.json({} as z.infer<typeof objectDraftSchema>, 200),
)
app.openapi(
createRoute({
method: 'patch',
path: '/api/objects/{id}',
request: {
params: z.object({ id: z.string() }),
body: {
content: { 'application/json': { schema: confirmObjectRequestSchema } },
required: true,
},
},
responses: {
200: jsonResponse(objectDraftSchema, 'Confirmed object'),
403: jsonResponse(errorSchema, 'Forbidden'),
404: jsonResponse(errorSchema, 'Not found'),
},
}),
(c) => c.json({} as z.infer<typeof objectDraftSchema>, 200),
)
}
export function createDownloaderOpenAPIApp() {
const app = new OpenAPIHono()
mountBetterAuthDeviceRoutes(app)
app.route('/api/download-tasks', downloadTasks)
app.route('/api/downloader', downloaderSelfRoute)
app.route('/api/admin/downloaders', downloaders)
mountObjectUploadRoutes(app)
return app
}
export function downloaderOpenAPIDocument() {
return createDownloaderOpenAPIApp().getOpenAPIDocument({
openapi: '3.0.0',
info: {
title: 'ZPan Downloader API',
version: '0.1.0',
},
})
}