mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 11:12:29 +00:00
feat(torrent): api.js Fastify routes + stream mount (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
7aaa8d5409
commit
fd6809b873
59
packages/secubox-torrent/lxc/app/api.js
Normal file
59
packages/secubox-torrent/lxc/app/api.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
// SecuBox-Deb :: secubox-torrent :: api.js — CyberMind https://cybermind.fr
|
||||
//
|
||||
// Fastify control API: torrent lifecycle (add/list/keep/remove) + status/health,
|
||||
// plus the /stream mount that hands off to the Range streaming handler.
|
||||
|
||||
import Fastify from 'fastify';
|
||||
import path from 'node:path';
|
||||
import { handleStream } from './stream.js';
|
||||
|
||||
export function buildApi({ engine, library, diskFreeBytes }) {
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
app.get('/api/v1/torrent/health', async () => ({ status: 'ok' }));
|
||||
app.get('/api/v1/torrent/status', async () => ({
|
||||
active: engine.client.torrents.length, disk_free: diskFreeBytes(), webrtc: engine.webrtc !== false }));
|
||||
|
||||
app.post('/api/v1/torrent/add', async (req, reply) => {
|
||||
const magnet = (req.body || {}).magnet;
|
||||
if (!magnet) return reply.code(400).send({ error: 'magnet required' });
|
||||
let meta;
|
||||
try { meta = await engine.add(magnet); }
|
||||
catch (e) { return reply.code(504).send({ error: e.message }); }
|
||||
library.add({ infohash: meta.infohash, name: meta.name, magnet,
|
||||
path: path.join(engine.downloadDir, 'tmp', meta.infohash) });
|
||||
return meta;
|
||||
});
|
||||
|
||||
app.get('/api/v1/torrent/list', async () =>
|
||||
library.list().map(r => ({ ...r, stats: engine.stats(r.infohash) })));
|
||||
|
||||
app.get('/api/v1/torrent/files/:infohash', async (req, reply) => {
|
||||
const t = engine.get(req.params.infohash);
|
||||
if (!t) return reply.code(404).send({ error: 'not found' });
|
||||
return t.files.map((f, i) => ({ idx: i, name: f.name, length: f.length }));
|
||||
});
|
||||
|
||||
app.post('/api/v1/torrent/keep/:infohash', async (req, reply) => {
|
||||
const ih = req.params.infohash;
|
||||
if (!library.get(ih)) return reply.code(404).send({ error: 'not found' });
|
||||
library.keep(ih, path.join(engine.downloadDir, 'library', ih));
|
||||
return { status: 'kept' };
|
||||
});
|
||||
|
||||
app.post('/api/v1/torrent/remove/:infohash', async (req) => {
|
||||
engine.remove(req.params.infohash, { deleteData: true });
|
||||
library.remove(req.params.infohash);
|
||||
return { status: 'removed' };
|
||||
});
|
||||
|
||||
app.get('/stream/:infohash/:fileIdx', (req, reply) => {
|
||||
library.touch(req.params.infohash);
|
||||
return handleStream(engine, req, reply.raw)
|
||||
.then(() => { reply.hijack(); });
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
45
packages/secubox-torrent/lxc/app/api.test.js
Normal file
45
packages/secubox-torrent/lxc/app/api.test.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
// SecuBox-Deb :: secubox-torrent :: api.test.js — CyberMind https://cybermind.fr
|
||||
//
|
||||
// Unit tests for the Fastify control API. Uses fastify .inject (no network),
|
||||
// the Engine with FakeWebTorrent, and an in-memory Library.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { buildApi } from './api.js';
|
||||
import { Library } from './library.js';
|
||||
import { Engine } from './engine.js';
|
||||
import { FakeWebTorrent } from './fakes.js';
|
||||
|
||||
function build() {
|
||||
const engine = new Engine({ WebTorrentCtor: FakeWebTorrent, downloadDir: '/tmp', maxActive: 5, webrtc: true });
|
||||
const library = new Library(':memory:');
|
||||
return buildApi({ engine, library, diskFreeBytes: () => 10 * 1e9 });
|
||||
}
|
||||
|
||||
test('health returns ok', async () => {
|
||||
const app = build();
|
||||
const r = await app.inject({ method: 'GET', url: '/api/v1/torrent/health' });
|
||||
assert.equal(r.statusCode, 200);
|
||||
assert.equal(r.json().status, 'ok');
|
||||
});
|
||||
|
||||
test('add then list returns the torrent', async () => {
|
||||
const app = build();
|
||||
await app.inject({ method: 'POST', url: '/api/v1/torrent/add',
|
||||
payload: { magnet: 'magnet:?xt=urn:btih:' + 'a'.repeat(40) } });
|
||||
const r = await app.inject({ method: 'GET', url: '/api/v1/torrent/list' });
|
||||
assert.equal(r.json().length, 1);
|
||||
assert.equal(r.json()[0].kept, 0);
|
||||
});
|
||||
|
||||
test('keep flips kept=1', async () => {
|
||||
const app = build();
|
||||
await app.inject({ method: 'POST', url: '/api/v1/torrent/add',
|
||||
payload: { magnet: 'magnet:?xt=urn:btih:' + 'a'.repeat(40) } });
|
||||
const r = await app.inject({ method: 'POST', url: '/api/v1/torrent/keep/' + 'a'.repeat(40) });
|
||||
assert.equal(r.statusCode, 200);
|
||||
const list = await app.inject({ method: 'GET', url: '/api/v1/torrent/list' });
|
||||
assert.equal(list.json()[0].kept, 1);
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
"dependencies": {
|
||||
"@roamhq/wrtc": "^0.8.0",
|
||||
"better-sqlite3": "^11.3.0",
|
||||
"fastify": "^4.28.1",
|
||||
"fastify": "^4.29.1",
|
||||
"webtorrent": "^2.5.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@
|
|||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": { "node": ">=18" },
|
||||
"scripts": { "test": "node --test" },
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"webtorrent": "^2.5.1",
|
||||
"@roamhq/wrtc": "^0.8.0",
|
||||
"fastify": "^4.28.1",
|
||||
"better-sqlite3": "^11.3.0"
|
||||
"better-sqlite3": "^11.3.0",
|
||||
"fastify": "^4.29.1",
|
||||
"webtorrent": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user