82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
/**
|
|
* MetaMesh Plugin: still-extractor
|
|
*
|
|
* ============================================================================
|
|
* PLUGIN FILE ACCESS ARCHITECTURE - WebDAV
|
|
* ============================================================================
|
|
*
|
|
* File access via WebDAV, resolved per request from the calling meta-core's
|
|
* /urls -> webdavUrlInternal (WEBDAV_URL overrides):
|
|
* - Read media files: GET /webdav/watch/... or /webdav/test/...
|
|
* - Write output: PUT /webdav/plugin/still-extractor/...
|
|
* - Temp: Local /cache/temp mount (frame written before WebDAV upload)
|
|
*
|
|
* SECURITY: Plugins use WebDAV for all file access. No /files mount needed.
|
|
*
|
|
* ============================================================================
|
|
*/
|
|
|
|
import Fastify from 'fastify';
|
|
import type { HealthResponse, ProcessRequest, ProcessResponse, CallbackPayload, ConfigureRequest, ConfigureResponse } from './types.js';
|
|
import { manifest, process as processFile, configure } from './plugin.js';
|
|
|
|
const app = Fastify({ logger: true });
|
|
let ready = false;
|
|
|
|
app.get('/health', async (): Promise<HealthResponse> => ({
|
|
status: 'healthy',
|
|
ready,
|
|
version: manifest.version
|
|
}));
|
|
|
|
app.get('/manifest', async () => manifest);
|
|
|
|
app.post<{ Body: ConfigureRequest }>('/configure', async (request): Promise<ConfigureResponse> => {
|
|
try {
|
|
configure(request.body.config || {});
|
|
console.log(`[${manifest.id}] Configuration updated`);
|
|
return { status: 'ok' };
|
|
} catch (error) {
|
|
console.error(`[${manifest.id}] Configuration error:`, error);
|
|
return { status: 'error', error: error instanceof Error ? error.message : String(error) };
|
|
}
|
|
});
|
|
|
|
app.post<{ Body: ProcessRequest }>('/process', async (request, reply) => {
|
|
const { taskId, cid, filePath, callbackUrl, metaCoreUrl } = request.body;
|
|
|
|
if (!taskId || !cid || !filePath || !callbackUrl || !metaCoreUrl) {
|
|
return reply.send({ status: 'rejected', error: 'Missing required fields' } as ProcessResponse);
|
|
}
|
|
|
|
// Process asynchronously and send callback when done
|
|
processFile(request.body, async (payload: CallbackPayload) => {
|
|
try {
|
|
await fetch(callbackUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
} catch (error) {
|
|
console.error(`[${manifest.id}] Callback error:`, error);
|
|
}
|
|
}).catch((error) => {
|
|
console.error(`[${manifest.id}] Process error:`, error);
|
|
});
|
|
|
|
return reply.send({ status: 'accepted' } as ProcessResponse);
|
|
});
|
|
|
|
const port = parseInt(process.env.PORT || '8080', 10);
|
|
|
|
app.listen({ port, host: '0.0.0.0' }).then(() => {
|
|
ready = true;
|
|
console.log(`[${manifest.id}] Listening on port ${port}`);
|
|
});
|
|
|
|
process.on('SIGTERM', async () => {
|
|
ready = false;
|
|
await app.close();
|
|
process.exit(0);
|
|
});
|