Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hono/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import albums from '~/hono/albums'
import openList from '~/hono/storage/open-list.ts'
import daily from '~/hono/daily'
import tasks from '~/hono/tasks'
import preprocessTasks from '~/hono/preprocess-tasks'
import backup from '~/hono/backup'
import { HTTPException } from 'hono/http-exception'
import { sessionMiddleware } from '~/hono/_lib/context'
Expand All @@ -31,6 +32,7 @@ route.route('/albums', albums)
route.route('/storage/open-list', openList)
route.route('/daily', daily)
route.route('/tasks', tasks)
route.route('/preprocess-tasks', preprocessTasks)
route.route('/backup', backup)

export default route
147 changes: 147 additions & 0 deletions hono/preprocess-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'server-only'

import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'

import {
cancelPreprocessTaskRun,
createPreprocessTaskRun,
getPreprocessTaskPreviewCount,
getPreprocessTaskRunDetail,
kickPreprocessTaskRun,
listPreprocessTaskRuns,
tickPreprocessTaskRuns,
} from '~/server/tasks/image-preprocess-service'
import { ADMIN_TASK_KEY_PREPROCESS_IMAGES, normalizePreprocessTaskScope } from '~/types/admin-tasks'
import { ok } from '~/hono/_lib/response'
import { badRequest, conflict, notFound, serverError } from '~/hono/_lib/errors'

const app = new Hono()

function ensureTaskKey(taskKey: unknown) {
if (taskKey !== ADMIN_TASK_KEY_PREPROCESS_IMAGES) {
throw badRequest('Unsupported task key')
}
return taskKey
}

function getScopeFromQuery(query: Record<string, string | undefined>) {
return normalizePreprocessTaskScope({ force: query.force === 'true' })
}

function getScopeFromBody(body: Record<string, unknown> | null) {
return normalizePreprocessTaskScope(body?.scope)
}

function rethrowTaskError(error: unknown): never {
if (error instanceof HTTPException) {
throw error
}

const message = error instanceof Error ? error.message : 'Task request failed'

if (message === 'Another preprocess task is already active') {
throw conflict(message)
}

if (message === 'No images matched the selected filters' || message === 'Variant storage backend is not configured') {
throw badRequest(message)
}

throw serverError(message, error)
}

app.get('/preview-count', async (c) => {
try {
const scope = getScopeFromQuery(c.req.query())
const data = await getPreprocessTaskPreviewCount(scope)
return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.get('/runs', async (c) => {
try {
const data = await listPreprocessTaskRuns()
return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.get('/runs/:id', async (c) => {
try {
const data = await getPreprocessTaskRunDetail(c.req.param('id'))

if (!data) {
throw notFound('Task run not found')
}

return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.post('/runs', async (c) => {
const body = await c.req.json<Record<string, unknown>>().catch(() => null)

if (!body || typeof body !== 'object' || Array.isArray(body)) {
throw badRequest('Invalid task request body')
}

try {
ensureTaskKey(body.taskKey)
const scope = getScopeFromBody(body)
const data = await createPreprocessTaskRun(scope)

if (!data) {
throw conflict('Task system is busy, please retry shortly')
}

return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.post('/runs/:id/kick', async (c) => {
try {
const data = await kickPreprocessTaskRun(c.req.param('id'))

if (!data) {
throw notFound('Task run not found')
}

return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.post('/runs/:id/cancel', async (c) => {
try {
const data = await cancelPreprocessTaskRun(c.req.param('id'))

if (!data) {
throw notFound('Task run not found')
}

return ok(c, data)
} catch (error) {
rethrowTaskError(error)
}
})

app.post('/tick', async (c) => {
try {
const data = await tickPreprocessTaskRuns()
return ok(c, data)
} catch (error) {
console.error('Preprocess task tick failed:', error)
rethrowTaskError(error)
}
})

export default app
Loading