From 75ffc3ed8ef0832e94e472dd40cc15649b5c24e4 Mon Sep 17 00:00:00 2001 From: Pavlo Kulyk Date: Mon, 16 Feb 2026 13:19:18 +0200 Subject: [PATCH 1/4] add documentation for auto-remove-plugin --- .../tutorial/08-Plugins/23-auto-remove.md | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md diff --git a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md new file mode 100644 index 00000000..dea66f94 --- /dev/null +++ b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md @@ -0,0 +1,313 @@ +# Many2Many + +This plugin lets you manage many-to-many relationships. + +## Installation + +Install the plugin: + +```bash +npm i @adminforth/many2many +``` + +## Setting up + +Let's say we want to implement a relationship where every apartment can have many realtors and each realtor can have many apartments. +We'll also need a junction resource to connect realtors and apartments. + +First, create the `realtors` table and the junction table `realtorsAparts`: + + +```ts title="./schema.prisma" +//diff-add +model realtors { + //diff-add + id String @id + //diff-add + name String + //diff-add + surname String + //diff-add +} +//diff-add +model realtorsAparts { + //diff-add + id String @id + //diff-add + realtorId String + //diff-add + apartmentId String + //diff-add +} +``` + +Migrate the Prisma schema: + +```bash +npm run makemigration -- --name add-realtors-and-realtorsAparts; npm run migrate:local +``` + +Now create a resource for the realtors: + +```ts title="./resources/realtors.ts" +import { AdminForthDataTypes, AdminForthResourceInput } from 'adminforth'; + +export default { + dataSource: 'maindb', + table: 'realtors', + resourceId: 'realtors', + label: 'Realtors', + recordLabel: (r) => ` ${r.name}`, + columns: [ + { + name: 'id', + type: AdminForthDataTypes.STRING, + label: 'Identifier', + showIn: { + list: false, + edit: false, + create: false, + }, + primaryKey: true, + fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7), + }, + { + name: 'name', + required: true, + showIn: { all: true }, + type: AdminForthDataTypes.STRING, + maxLength: 255, + minLength: 3, + }, + { + name: "surname", + required: true, + showIn: { all: true }, + type: AdminForthDataTypes.STRING, + maxLength: 100, + minLength: 3, + } + ], + options: { + listPageSize: 12, + allowedActions: { + edit: true, + delete: true, + show: true, + filter: true, + }, + }, +} as AdminForthResourceInput; +``` + +And one for the junction table `realtorsAparts`: + +```ts title="./resources/realtorsAparts.ts" +import { AdminForthDataTypes, AdminForthResourceInput } from 'adminforth'; + +export default { + dataSource: 'maindb', + table: 'realtorsAparts', + resourceId: 'realtorsAparts', + label: 'Realtors-aparts', + columns: [ + { + name: 'id', + type: AdminForthDataTypes.STRING, + label: 'Identifier', + showIn: { + list: false, + edit: false, + create: false, + }, + primaryKey: true, + fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7), + }, + { + name: 'realtorId', + foreignResource: { + resourceId: 'realtors', + searchableFields: ['name'], + searchIsCaseSensitive: true + } + }, + { + name: 'apartmentId', + foreignResource: { + resourceId: 'aparts', + searchableFields: ['title'], + searchIsCaseSensitive: true + } + }, + ], + options: { + listPageSize: 12, + allowedActions: { + edit: true, + delete: true, + show: true, + filter: true, + }, + }, +} as AdminForthResourceInput; +``` + +Now add the plugin resources to the main config file: + +```ts title="./index.ts" + +//diff-add +import realtorsResource from './resources/realtors.js'; +//diff-add +import realtorsApartsResource from './resources/realtorsAparts.js'; + + ... + + +dataSources: [ + { + id: 'maindb', + url: `${process.env.DATABASE_URL}` + }, + ], + resources: [ + ... + //diff-add + realtorsResource, + //diff-add + realtorsApartsResource + ] + + ... + + menu: [ + + ... + //diff-add + { + //diff-add + label: 'Realtors', + //diff-add + resourceId: 'realtors' + //diff-add + }, + //diff-add + { + //diff-add + label: 'Realtors-aparts', + //diff-add + resourceId: 'realtorsAparts' + //diff-add + }, + + ... + + ] + +``` + +Finally, add the plugin to the `apartments` resource: + + +```ts title="./resources/apartments.ts" +//diff-add +import Many2ManyPlugin from '@adminforth/many2many'; + + ... + + plugins: [ + ... + + //diff-add + new Many2ManyPlugin({ + //diff-add + linkedResourceId: 'realtors', + //diff-add + }) + + ... + ] + + ... + +``` + + +The plugin is set up. Create some records in the `realtors` table: +![alt text](Many2Many-1.png) +Now, when creating an apartment, you can select (link) one or more realtors. +![alt text](Many2Many-2.png) +After saving the record, rows in the junction table are created automatically: +![alt text](Many2Many-3.png) + + +## Disable automatic cleanup of the junction table +By default, when you delete a realtor or an apartment, all related rows in the junction table are deleted automatically. To avoid this behavior, add: + +```ts + + ... + + new Many2ManyPlugin({ + linkedResourceId: 'realtors', + //diff-add + dontDeleteJunctionRecords: true, // prevents automatic deletion of related junction rows + }) + + ... + +``` + +## Making editable fields for the both resources + +There might be cases, when you want to make fields editable on both resources. For these cases you can just create second instance of plugin on the second resource and it will allow you to use this plugin from the both resources. + +```ts title="./resources/apartments.ts" +//diff-add +import Many2ManyPlugin from '@adminforth/many2many'; + + ... + + plugins: [ + ... + + //diff-add + new Many2ManyPlugin({ + //diff-add + linkedResourceId: 'realtors', + //diff-add + }) + + ... + ] + + ... + +``` + + +and + + +```ts title="./resources/realtors.ts" +//diff-add +import Many2ManyPlugin from '@adminforth/many2many'; + + ... + + plugins: [ + ... + + //diff-add + new Many2ManyPlugin({ + //diff-add + linkedResourceId: 'aparts', + //diff-add + }) + + ... + ] + + ... + +``` \ No newline at end of file From 289d977a7249e10d95d34721b6f778c1212c02fc Mon Sep 17 00:00:00 2001 From: Pavlo Kulyk Date: Mon, 16 Feb 2026 13:24:18 +0200 Subject: [PATCH 2/4] fix: change documentation for auto-remove plugin --- .../tutorial/08-Plugins/23-auto-remove.md | 367 ++++-------------- 1 file changed, 79 insertions(+), 288 deletions(-) diff --git a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md index dea66f94..6ed57c95 100644 --- a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md +++ b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md @@ -1,313 +1,104 @@ -# Many2Many +# Auto Remove Plugin -This plugin lets you manage many-to-many relationships. +This plugin removes records from resources based on **count-based** or **time-based** rules. -## Installation +It is designed for cleaning up: -Install the plugin: +* old records +* logs +* demo/test data +* temporary entities -```bash -npm i @adminforth/many2many -``` - -## Setting up - -Let's say we want to implement a relationship where every apartment can have many realtors and each realtor can have many apartments. -We'll also need a junction resource to connect realtors and apartments. - -First, create the `realtors` table and the junction table `realtorsAparts`: - - -```ts title="./schema.prisma" -//diff-add -model realtors { - //diff-add - id String @id - //diff-add - name String - //diff-add - surname String - //diff-add -} -//diff-add -model realtorsAparts { - //diff-add - id String @id - //diff-add - realtorId String - //diff-add - apartmentId String - //diff-add -} -``` - -Migrate the Prisma schema: - -```bash -npm run makemigration -- --name add-realtors-and-realtorsAparts; npm run migrate:local -``` - -Now create a resource for the realtors: - -```ts title="./resources/realtors.ts" -import { AdminForthDataTypes, AdminForthResourceInput } from 'adminforth'; +--- -export default { - dataSource: 'maindb', - table: 'realtors', - resourceId: 'realtors', - label: 'Realtors', - recordLabel: (r) => ` ${r.name}`, - columns: [ - { - name: 'id', - type: AdminForthDataTypes.STRING, - label: 'Identifier', - showIn: { - list: false, - edit: false, - create: false, - }, - primaryKey: true, - fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7), - }, - { - name: 'name', - required: true, - showIn: { all: true }, - type: AdminForthDataTypes.STRING, - maxLength: 255, - minLength: 3, - }, - { - name: "surname", - required: true, - showIn: { all: true }, - type: AdminForthDataTypes.STRING, - maxLength: 100, - minLength: 3, - } - ], - options: { - listPageSize: 12, - allowedActions: { - edit: true, - delete: true, - show: true, - filter: true, - }, - }, -} as AdminForthResourceInput; -``` - -And one for the junction table `realtorsAparts`: +## Instalation -```ts title="./resources/realtorsAparts.ts" -import { AdminForthDataTypes, AdminForthResourceInput } from 'adminforth'; +To install the plugin: -export default { - dataSource: 'maindb', - table: 'realtorsAparts', - resourceId: 'realtorsAparts', - label: 'Realtors-aparts', - columns: [ - { - name: 'id', - type: AdminForthDataTypes.STRING, - label: 'Identifier', - showIn: { - list: false, - edit: false, - create: false, - }, - primaryKey: true, - fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7), - }, - { - name: 'realtorId', - foreignResource: { - resourceId: 'realtors', - searchableFields: ['name'], - searchIsCaseSensitive: true - } - }, - { - name: 'apartmentId', - foreignResource: { - resourceId: 'aparts', - searchableFields: ['title'], - searchIsCaseSensitive: true - } - }, - ], - options: { - listPageSize: 12, - allowedActions: { - edit: true, - delete: true, - show: true, - filter: true, - }, - }, -} as AdminForthResourceInput; +```bash +npm install @adminforth/auto-remove ``` -Now add the plugin resources to the main config file: - -```ts title="./index.ts" - -//diff-add -import realtorsResource from './resources/realtors.js'; -//diff-add -import realtorsApartsResource from './resources/realtorsAparts.js'; - - ... - - -dataSources: [ - { - id: 'maindb', - url: `${process.env.DATABASE_URL}` - }, - ], - resources: [ - ... - //diff-add - realtorsResource, - //diff-add - realtorsApartsResource - ] - - ... - - menu: [ - - ... - //diff-add - { - //diff-add - label: 'Realtors', - //diff-add - resourceId: 'realtors' - //diff-add - }, - //diff-add - { - //diff-add - label: 'Realtors-aparts', - //diff-add - resourceId: 'realtorsAparts' - //diff-add - }, - - ... - - ] - +Import it into your resource: +```bash +import AutoRemovePlugin from '../../plugins/adminforth-auto-remove/index.js'; ``` -Finally, add the plugin to the `apartments` resource: - - -```ts title="./resources/apartments.ts" -//diff-add -import Many2ManyPlugin from '@adminforth/many2many'; - - ... - - plugins: [ - ... - - //diff-add - new Many2ManyPlugin({ - //diff-add - linkedResourceId: 'realtors', - //diff-add - }) - - ... - ] - - ... +## Plugin Options +```ts +export interface PluginOptions { + createdAtField: string; + + /** + * - count-based: Delete items > maxItems + * - time-based: Delete age > maxAge + */ + mode: AutoRemoveMode; + + /** + * for count-based mode (100', '1k', '10k', '1m') + */ + keepAtLeast?: HumanNumber; + + /** + * Minimum number of items to always keep in count-based mode. + * This acts as a safety threshold together with `keepAtLeast`. + * Example formats: '100', '1k', '10k', '1m'. + * + * Validation ensures that minItemsKeep <= keepAtLeast. + */ + minItemsKeep?: HumanNumber; + + /** + * Max age of item for time-based mode ('1d', '7d', '1mon', '1y') + */ + deleteOlderThan?: HumanDuration; + + /** + * Interval for running cleanup (e.g. '1h', '1d') + * Default '1d' + */ + interval?: HumanDuration; +} ``` +--- +## Usage +To use the plugin, add it to your resource file. Here's an example: -The plugin is set up. Create some records in the `realtors` table: -![alt text](Many2Many-1.png) -Now, when creating an apartment, you can select (link) one or more realtors. -![alt text](Many2Many-2.png) -After saving the record, rows in the junction table are created automatically: -![alt text](Many2Many-3.png) - - -## Disable automatic cleanup of the junction table -By default, when you delete a realtor or an apartment, all related rows in the junction table are deleted automatically. To avoid this behavior, add: - +for count-based mode ```ts - - ... - - new Many2ManyPlugin({ - linkedResourceId: 'realtors', - //diff-add - dontDeleteJunctionRecords: true, // prevents automatic deletion of related junction rows - }) - - ... - +new AutoRemovePlugin({ + createdAtField: 'created_at', + mode: 'count-based', + keepAtLeast: '200', + interval: '1s', + minItemsKeep: '180', + }), ``` -## Making editable fields for the both resources - -There might be cases, when you want to make fields editable on both resources. For these cases you can just create second instance of plugin on the second resource and it will allow you to use this plugin from the both resources. - -```ts title="./resources/apartments.ts" -//diff-add -import Many2ManyPlugin from '@adminforth/many2many'; - - ... - - plugins: [ - ... - - //diff-add - new Many2ManyPlugin({ - //diff-add - linkedResourceId: 'realtors', - //diff-add - }) - - ... - ] - - ... - +for time-based mode +```ts +new AutoRemovePlugin({ + createdAtField: 'created_at', + mode: 'time-based', + deleteOlderThan: '3min', + interval: '5s', + }), ``` +--- -and - - -```ts title="./resources/realtors.ts" -//diff-add -import Many2ManyPlugin from '@adminforth/many2many'; - - ... - - plugins: [ - ... +## Result +After running **AutoRemovePlugin**, old or excess records are deleted automatically: - //diff-add - new Many2ManyPlugin({ - //diff-add - linkedResourceId: 'aparts', - //diff-add - }) +- **Count-based mode:** keeps the newest `keepAtLeast` records, deletes older ones. + Example: `keepAtLeast = 500` → table with 650 records deletes 150 oldest. - ... - ] +- **Time-based mode:** deletes records older than `deleteOlderThan`. + Example: `deleteOlderThan = '7d'` → removes records older than 7 days. - ... +- **Manual cleanup:** `POST /plugin/{pluginInstanceId}/cleanup`, returns `{ "ok": true }`. -``` \ No newline at end of file +Logs show how many records were removed per run. \ No newline at end of file From ba90a019c455cee9a8489a57c844a91c07c28384 Mon Sep 17 00:00:00 2001 From: Pavlo Kulyk Date: Thu, 19 Feb 2026 16:51:30 +0200 Subject: [PATCH 3/4] fix: increase intervals in usage example --- .../docs/tutorial/08-Plugins/23-auto-remove.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md index 6ed57c95..01b0ad43 100644 --- a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md +++ b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md @@ -73,7 +73,7 @@ new AutoRemovePlugin({ createdAtField: 'created_at', mode: 'count-based', keepAtLeast: '200', - interval: '1s', + interval: '30d', minItemsKeep: '180', }), ``` @@ -83,8 +83,8 @@ for time-based mode new AutoRemovePlugin({ createdAtField: 'created_at', mode: 'time-based', - deleteOlderThan: '3min', - interval: '5s', + deleteOlderThan: '1y', + interval: '30ds', }), ``` From 4db00842f6f755039a3a3c0db2b1199071ab8f7a Mon Sep 17 00:00:00 2001 From: Pavlo Kulyk Date: Fri, 20 Feb 2026 09:21:38 +0200 Subject: [PATCH 4/4] fix: correct interval value in time-based mode example for auto-remove plugin --- .../documentation/docs/tutorial/08-Plugins/23-auto-remove.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md index 01b0ad43..703d2f62 100644 --- a/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md +++ b/adminforth/documentation/docs/tutorial/08-Plugins/23-auto-remove.md @@ -84,7 +84,7 @@ new AutoRemovePlugin({ createdAtField: 'created_at', mode: 'time-based', deleteOlderThan: '1y', - interval: '30ds', + interval: '30d', }), ```