From 2ce34b0dc8f4a9fe79843e0a59c947e17a09a0b4 Mon Sep 17 00:00:00 2001 From: snt Date: Mon, 23 Jun 2025 16:12:14 +0000 Subject: [PATCH] Initial commit --- .gitignore | 2 + README.md | 13 +++++ index.js | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 +++++ 4 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..f686bd2 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Cleaner plugin for Gancio + +This is a plugin for gancio that cleans past eventsd, this is a beta release. + +## Configuration + +Once the plugin is installed, navigate to your instance plugins tab of the admin interface. Enable the plugin and add the required data. + + +## Try it + +1. Restart your gancio instance and look at the logs for any message saying that this plugin has been loaded. + diff --git a/index.js b/index.js new file mode 100644 index 0000000..dc1030d --- /dev/null +++ b/index.js @@ -0,0 +1,161 @@ +/** + * Clean past events from gancio + */ + +const path = require('path') +const fs = require('fs/promises') +const config = require('../../config') +const notifier = require('../../../gancio_source/server/notifier') +const { Op } = require('sequelize') +const { Event, Resource, Tag, Place, Notification, APUser, EventNotification, Message, User } = require('../../../gancio_source/server/api/models/models') + + +const plugin = { + configuration: { + name: 'Cleaner', + author: 'snt', + url: 'https://git.criptomart.net/snt/gancio', + description: 'Cleans events from a specified time before now, this is a beta release of this plugin.', + settings: { + refresh_time: { + type: 'NUMBER', + hint: 'Search for past events each n hours', + required: true, + description: '1 hour?' + }, + deadline_time: { + type: 'NUMBER', + hint: 'Clean all events non recurrent with end time n hours ago.', + required: true, + description: '720 hours? events ended after this number of hours ago will be deleted.' + }, + events_number: { + type: 'NUMBER', + hint: 'Limit the number of events to be deleted each call.', + required: true, + description: '4? Do not set too much, it can overload notifications.' + }, + } + }, + + gancio: null, // { helpers, log, settings } + log: null, + settings: null, + db: null, + interval: null, + ETag: null, + + load(gancio, settings) { + plugin.gancio = gancio // contains all gancio settings, including all plugins settings + plugin.log = gancio.log // just the logger + plugin.db = gancio.db + plugin.settings = settings // this plugin settings + + plugin.apiBaseUrl = gancio.settings.baseurl + '/api' + + // TODO: could use the TaskManager? + plugin.interval = setInterval(this._tick, settings.refresh_time*1000*60*60) + plugin.log.debug("[Cleaner Plugin] loaded with params: refresh: %s -- deadline: %s -- number: %s", settings.refresh_time, settings.deadline_time, settings.events_number) + // this._tick() + }, + + unload () { + plugin.log.debug('[Cleaner Plugin] Clear interval an unload plugin') + clearInterval(plugin.interval) + }, + + onTest () { + plugin._tick() + }, + + async _tick () { + // Avoid running multiple ticks at the same time which could cause race conditions on the database + if (plugin._isTickRunning) { + plugin.log.warn('[Cleaner Plugin] _tick already in progress, skipping') + return + } + plugin.log.debug('[Cleaner Plugin] _tick started, locking it') + plugin._isTickRunning = true + + try { + if (!plugin.settings?.refresh_time) { + plugin.log.debug('[Cleaner Plugin] refresh time not set, default to 1 hour') + plugin.settings.refresh_time = 1 + plugin.interval = setInterval(this._tick, settings.refresh_time*1000*60*60) + } + if (!plugin.settings?.deadline_time) { + plugin.log.debug('[Cleaner Plugin] deadline time not set, default to 4 weeks') + plugin.settings.deadline_time = 720 + } + if (!plugin.settings?.events_number) { + plugin.log.debug('[Cleaner Plugin] events cleared not set, default to 1') + plugin.settings.events_number = 1 + } + try { + plugin.log.debug(`[Cleaner Plugin] Begin searching`) + const now = Math.floor(Date.now()) + const cut_datetime = now - plugin.settings?.deadline_time*60*60*1000 + const date_obj = new Date(cut_datetime) + plugin.log.debug("now %s -- deadline %s", now, cut_datetime) + plugin.log.info("[Cleaner Plugin] Removing old events before " + date_obj) + events = await plugin.db.models.event.findAll({ + where: { + recurrent: null, + start_datetime: { [Op.lt]: cut_datetime/1000 }, + [Op.or]: [ {end_datetime: { [Op.lt]: cut_datetime/1000 } }, { end_datetime: null } ] + }, + order: [['end_datetime', 'ASC']], + include: [{ model: Event, as:'child' }], + limit: plugin.settings.events_number, + }) + + plugin.log.warn("[Cleaner Plugin] Found %s past events and related resources.", events.length) + + if (!events.length) { return } + + for (e of events) { + end_date = new Date(e.end_datetime * 1000) + start_date = new Date(e.start_datetime * 1000) + plugin.log.info("[Cleaner Plugin] Cleaning: " + e.title + " - Rec: " + e.recurrent + " - From: " + start_date + " - To: " + end_date + " - place: " + e.placeId) + plugin.log.debug("[Cleaner Plugin] %s", JSON.stringify(e, null, "\t")) + if (e.media && e.media.length && !e.recurrent && !e.parentId) { + try { + const old_path = path.join(config.upload_path, e.media[0].url) + const old_thumb_path = path.join(config.upload_path, 'thumb', e.media[0].url) + await fs.unlink(old_thumb_path) + await fs.unlink(old_path) + plugin.log.debug("[Cleaner Plugin] removing file: " + old_path) + } catch (excep) { + plugin.log.error("[Cleaner Plugin] error removing file %s", excep.toString()) + } + } + try { + // notify local events before destroying notifications + if (!e.ap_id) { + await notifier.notifyEvent("Delete", e.id) + } + // remove related resources + await Resource.destroy({ where: { eventId: e.id }}) + // remove notifications + await EventNotification.destroy({ where: { eventId: e.id }}) + // remove event + await e.destroy() + } catch (excep) { + console.error(excep) + plugin.log.error("[Cleaner Plugin] error destroying %s", excep.toString() ) + } + }; + } catch (e) { + plugin.log.error(`[Cleaner Plugin] Error: ${String(e)}`) + } + } catch (e) { + plugin.log.error(`[Cleaner Plugin] Uncaught error in _tick: ${String(e)}`) + } finally { + plugin.log.debug('[Cleaner Plugin] _tick finished, unlocking it') + plugin._isTickRunning = false + } + } + +} + +module.exports = plugin diff --git a/package.json b/package.json new file mode 100644 index 0000000..908a18b --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "gancio-plugin-cleaner", + "version": "0.1.0", + "description": "Cleaner plugin for Gancio", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "license": "AGPL-3.0-or-later", + "dependencies": { + "sequelize": "^6.37.7" + } +}