mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-10-17 05:14:19 +02:00
Adds a user-facing trigger to rebuild the Kiwix library index from the ZIM files currently on disk. Covers the sideload case: a user copies a .zim onto the box (USB, SSH, network share) outside NOMAD's download flow, and Kiwix has no way to discover it without regenerating the library index. Reuses the existing native KiwixLibraryService.rebuildFromDisk() (which also extracts embedded favicons natively), so no kiwix-tools container and no icon-patch step are needed. In library mode (--monitorLibrary) kiwix-serve hot-reloads the XML automatically; only legacy glob-mode containers are restarted. - KiwixLibraryService.rebuildFromDisk now returns the book count; adds getBookCount() for the before/after delta - ZimService.rescanLibrary() orchestrates rebuild + legacy restart - POST /api/zim/rescan-library + ZimController.rescanLibrary - "Rescan Library" button on the Content Manager page (shown when Kiwix is installed) with a success toast reporting books found Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import { ZimService } from '#services/zim_service'
|
|
import {
|
|
assertNotPrivateUrl,
|
|
downloadCategoryTierValidator,
|
|
filenameParamValidator,
|
|
remoteDownloadWithMetadataValidator,
|
|
selectWikipediaValidator,
|
|
} from '#validators/common'
|
|
import { addCustomLibraryValidator, browseLibraryValidator, idParamValidator, listRemoteZimValidator } from '#validators/zim'
|
|
import { inject } from '@adonisjs/core'
|
|
import type { HttpContext } from '@adonisjs/core/http'
|
|
|
|
@inject()
|
|
export default class ZimController {
|
|
constructor(private zimService: ZimService) {}
|
|
|
|
async list({}: HttpContext) {
|
|
return await this.zimService.list()
|
|
}
|
|
|
|
async listRemote({ request }: HttpContext) {
|
|
const payload = await request.validateUsing(listRemoteZimValidator)
|
|
const { start = 0, count = 12, query } = payload
|
|
return await this.zimService.listRemote({ start, count, query })
|
|
}
|
|
|
|
async downloadRemote({ request }: HttpContext) {
|
|
const payload = await request.validateUsing(remoteDownloadWithMetadataValidator)
|
|
assertNotPrivateUrl(payload.url)
|
|
const { filename, jobId } = await this.zimService.downloadRemote(payload.url, payload.metadata)
|
|
|
|
return {
|
|
message: 'Download started successfully',
|
|
filename,
|
|
jobId,
|
|
url: payload.url,
|
|
}
|
|
}
|
|
|
|
async listCuratedCategories({}: HttpContext) {
|
|
return await this.zimService.listCuratedCategories()
|
|
}
|
|
|
|
async downloadCategoryTier({ request }: HttpContext) {
|
|
const payload = await request.validateUsing(downloadCategoryTierValidator)
|
|
const resources = await this.zimService.downloadCategoryTier(
|
|
payload.categorySlug,
|
|
payload.tierSlug
|
|
)
|
|
|
|
return {
|
|
message: 'Download started successfully',
|
|
categorySlug: payload.categorySlug,
|
|
tierSlug: payload.tierSlug,
|
|
resources,
|
|
}
|
|
}
|
|
|
|
async rescanLibrary({}: HttpContext) {
|
|
const result = await this.zimService.rescanLibrary()
|
|
return {
|
|
message: 'Kiwix library rescanned',
|
|
...result,
|
|
}
|
|
}
|
|
|
|
async delete({ request, response }: HttpContext) {
|
|
const payload = await request.validateUsing(filenameParamValidator)
|
|
|
|
try {
|
|
await this.zimService.delete(payload.params.filename)
|
|
} catch (error) {
|
|
if (error.message === 'not_found') {
|
|
return response.status(404).send({
|
|
message: `ZIM file with key ${payload.params.filename} not found`,
|
|
})
|
|
}
|
|
throw error // Re-throw any other errors and let the global error handler catch
|
|
}
|
|
|
|
return {
|
|
message: 'ZIM file deleted successfully',
|
|
}
|
|
}
|
|
|
|
// Wikipedia selector endpoints
|
|
|
|
async getWikipediaState({}: HttpContext) {
|
|
return this.zimService.getWikipediaState()
|
|
}
|
|
|
|
async selectWikipedia({ request }: HttpContext) {
|
|
const payload = await request.validateUsing(selectWikipediaValidator)
|
|
return this.zimService.selectWikipedia(payload.optionId)
|
|
}
|
|
|
|
// Custom library endpoints
|
|
|
|
async listCustomLibraries({}: HttpContext) {
|
|
return this.zimService.listCustomLibraries()
|
|
}
|
|
|
|
async addCustomLibrary({ request, response }: HttpContext) {
|
|
const payload = await request.validateUsing(addCustomLibraryValidator)
|
|
assertNotPrivateUrl(payload.base_url)
|
|
try {
|
|
const source = await this.zimService.addCustomLibrary(payload.name, payload.base_url)
|
|
return { message: 'Custom library added', library: source }
|
|
} catch (error) {
|
|
if (error.message === 'Maximum of 10 custom libraries allowed') {
|
|
return response.status(400).send({ message: error.message })
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async removeCustomLibrary({ request, response }: HttpContext) {
|
|
const payload = await request.validateUsing(idParamValidator)
|
|
try {
|
|
await this.zimService.removeCustomLibrary(payload.params.id)
|
|
return { message: 'Custom library removed' }
|
|
} catch (error) {
|
|
if (error.message === 'Custom library not found') {
|
|
return response.status(404).send({ message: error.message })
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async browseLibrary({ request, response }: HttpContext) {
|
|
const payload = await request.validateUsing(browseLibraryValidator)
|
|
try {
|
|
return await this.zimService.browseLibraryUrl(payload.url)
|
|
} catch (error) {
|
|
if (error.message?.includes('loopback or link-local')) {
|
|
return response.status(400).send({ message: error.message })
|
|
}
|
|
return response.status(502).send({
|
|
message: 'Could not fetch directory listing from the provided URL',
|
|
})
|
|
}
|
|
}
|
|
}
|