Le développement initial du plugin “Cleanup Unviewed Videos” pour PeerTube (disponible sur le Github de Worteks) avait été évoqué dans un article précédent. Dans la continuité, cet article traite de l’évolution de son architecture en vue de transférer une partie du traitement côté serveur.
Cette évolution permet une recherche plus efficace des vidéos éligibles à la suppression du fait d’un accès direct à la base de données.
Accès à l’API interne
La fonction en charge d’enregistrer le plugin dans PeerTube doit être enrichie de nouveaux attributs permettant l’accès à l’API interne de PeerTube :
async function register({
getRouter,
peertubeHelpers,
registerSetting, // Cet attribut était déjà importé
settingsManager
}) {
const database = peertubeHelpers.database;
const logger = peertubeHelpers.logger;
const router = getRouter();
const videos = peertubeHelpers.videos;
// ...
});
Les attributs qui sont exposés aux plugins sont listés sur la page de documentation.
Interactions avec le routeur
Pour que le plugin puisse être piloté depuis l’interface de PeerTube, il faut qu’il déclare des terminaisons d’API HTTP. Nous allons déclarer deux routes :
- récupération des vidéos éligibles à la suppression
- suppression d’une vidéo
Le routeur utilisé par PeerTube est celui du framework Express. La déclaration des deux routes se fait de la sorte :
router.get("/", async (req, res) => {
// ...
res.status(200).json(data);
});
router.delete("/:id", async (req, res) => {
// ...
res.status(204).json();
});
Les routes sont alors accessibles via les requêtes HTTP suivantes :
GET /plugins/<plugin-name>/routerDELETE /plugins/<plugin-name>/router/:id
Accès aux paramètres du plugin
La recherche des vidéos éligibles à une suppression est basée sur des critères d’âge exprimés en années, mois et jours. Ces critères sont passés en paramètres GET de la requête. Il est possible d’anticiper des requêtes incomplètes dont certains paramètres seraient manquants. On peut alors augmenter la robustesse du code en chargeant la valeur par défaut pour chacun des arguments manquants. (Note : on aurait pu décider de leur donner la valeur de “0” par défaut, mais ça aurait été trop simpliste).
router.get("/", async (req, res) => {
const [years, months, days] = await Promise.all(
["years", "months", "days"].map(async (field) => {
const req_value = parseInt(req.query[field], 10);
return !isNaN(req_value)
? req_value
: parseInt(
`${await settingsManager.getSetting(`default-${field}-old`)}`,
10,
);
}),
);
// ...
});
Dans sa version initiale, le plugin expose un paramètre permettant de désactiver
la suppression (à des fins de test). La prise en compte de ce paramètre doit
maintenant se faire dans le code de traitement de la route
DELETE /plugins/<plugin-name>/router/:id.
const enable_deletion = await settingsManager.getSetting("enable-deletion");
if (enable_deletion) {
// Actually delete video
} else {
// Write a log message
}
Accès à la base de données
PeerTube interagit avec la base de données via Sequelize.
Les plugins peuvent accéder à cette bibliothèque via peertubeHelpers.database.
Cette méthode d’accès permet d’exécuter des requêtes au format SQL sur la base de données. Cependant, des limitations ou des particularités imposent les précautions suivantes.
Il n’est pas possible d’accéder aux commodités de haut niveau de l’ORM :
Par ailleurs, Sequelize nomme les tables en traduction camelCase de la classe TypeScript associée. Ceci a comme conséquence que les tables ne seront pas reconnues directement par le shell PostgreSQL et devront être entourées de doubles-quotes :
SELECT * FROM videoView; -- ERROR: relation "videoview" does not exist
SELECT * FROM "videoView"; -- OK
Enfin, les plugins n’ont pas accès à suffisamment de fonctionnalités de protection anti-injection (bind-parameters, date-ranges, etc.).
Les besoins du plugin nécessitent l’intervention d’opérateurs de calculs de date
afin de traduire l’âge spécifié en date. La requête que le plugin va exécuter ne
pourra pas être sécurisée par l’ORM et nous devons nous assurer nous-mêmes
qu’elle ne permettra pas d’injection. Une conversion en entier base 10 a été
insérée dans sa mise en forme. Le résultat de cette conversion sera soit une
succession de chiffres, soit NaN. Ni l’un ni l’autre ne seront vecteurs
d’injection.
const [results, _] = await database.query(
`SELECT "videoId" as id, name, url, "createdAt"
FROM (
SELECT "videoId", video.name, video.url, video."createdAt", MAX("endDate") AS last_viewed
FROM "videoView"
LEFT JOIN video ON "videoId" = video.id
GROUP BY "videoId", video.name, video.url, video."createdAt"
)
WHERE last_viewed < CURRENT_DATE
- interval \'${parseInt(years, 10)} years\'
- interval \'${parseInt(months, 10)} months\'
- interval \'${parseInt(days, 10)} days\'
`,
);
Suppression d’une vidéo
Parmi les fonctions d’aide exposées aux plugins par PeerTube, la fonction
removeVideo permet la suppression d’une vidéo et de tous les éléments qui y
sont associés :
await peertubeHelpers.videos.removeVideo(req.params.id);
Conclusion
L’ajout de fonctionnalités back-end à ce plugin représente un important gain d’efficacité. De plus, il permet d’en rendre l’architecture plus classique.
Les fonctionnalités offertes aux plugins par PeerTube permettent de traiter des cas simples sans trop de difficulté. Cependant, la documentation étant assez rudimentaire, il est nécessaire d’avoir un bagage TypeScript/Javascript suffisant pour comprendre la façon dont les interfaces de l’API interne à PeerTube doivent être utilisées.
Une évolution prochaine de ce plugin (ou une approche initiale pour un prochain plugin) pourrait être de le développer en TypeScript, ce qui apporterait un gain en ergonomie dans le processus de développement (au détriment de la simplicité de déploiement actuelle).