feat: remove inactive users (scheduler)

This commit is contained in:
Sonny
2024-09-18 16:58:51 +02:00
parent b0e3bfa0f6
commit 05f067a430
13 changed files with 779 additions and 830 deletions

12
.vscode/settings.json vendored
View File

@@ -1,3 +1,13 @@
{ {
"typescript.preferences.importModuleSpecifier": "non-relative" "typescript.preferences.importModuleSpecifier": "non-relative",
/* Prefer tabs over spaces for accessibility */
"editor.insertSpaces": true,
"editor.detectIndentation": false,
/* Explorer */
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, rollup.config.mjs, tsconfig.json, eslint.config.js",
"Makefile": "*compose.yml, Dockerfile, .dockerignore, *startup.sh"
}
} }

View File

@@ -53,9 +53,10 @@ ENV PORT=$PORT
WORKDIR /app WORKDIR /app
COPY --from=production-deps /app/node_modules /app/node_modules COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app COPY --from=build /app/build /app
COPY --from=build /app/startup.sh /app/startup.sh
# Expose port # Expose port
EXPOSE $PORT EXPOSE $PORT
# Start app # Start app
CMD node bin/console.js migration:run --force && node bin/server.js CMD node bin/console.js migration:run --force && sh startup.sh

View File

@@ -2,7 +2,7 @@ dev:
@docker compose down @docker compose down
@docker compose -f dev.docker-compose.yml up -d --wait @docker compose -f dev.docker-compose.yml up -d --wait
@node ace migration:fresh @node ace migration:fresh
@pnpm run dev @./dev.startup.sh
prod: prod:
@docker compose -f dev.docker-compose.yml down @docker compose -f dev.docker-compose.yml down

View File

@@ -14,6 +14,7 @@ export default defineConfig({
() => import('@adonisjs/core/commands'), () => import('@adonisjs/core/commands'),
() => import('@adonisjs/lucid/commands'), () => import('@adonisjs/lucid/commands'),
() => import('@izzyjs/route/commands'), () => import('@izzyjs/route/commands'),
() => import('adonisjs-scheduler/commands'),
], ],
/* /*
@@ -45,6 +46,10 @@ export default defineConfig({
() => import('@adonisjs/ally/ally_provider'), () => import('@adonisjs/ally/ally_provider'),
() => import('@izzyjs/route/izzy_provider'), () => import('@izzyjs/route/izzy_provider'),
() => import('#providers/route_provider'), () => import('#providers/route_provider'),
{
file: () => import('adonisjs-scheduler/scheduler_provider'),
environment: ['console'],
},
], ],
/* /*
@@ -55,7 +60,14 @@ export default defineConfig({
| List of modules to import before starting the application. | List of modules to import before starting the application.
| |
*/ */
preloads: [() => import('#start/routes'), () => import('#start/kernel')], preloads: [
() => import('#start/routes'),
() => import('#start/kernel'),
{
file: () => import('#start/scheduler'),
environment: ['console'],
},
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@@ -4,6 +4,8 @@ import logger from '@adonisjs/core/services/logger';
import db from '@adonisjs/lucid/services/db'; import db from '@adonisjs/lucid/services/db';
import { RouteName } from '@izzyjs/route/types'; import { RouteName } from '@izzyjs/route/types';
const INACTIVE_USER_THRESHOLD = 7;
export default class UsersController { export default class UsersController {
private redirectTo: RouteName = 'auth.login'; private redirectTo: RouteName = 'auth.login';
@@ -75,4 +77,18 @@ export default class UsersController {
.withCount('collections', (q) => q.as('totalCollections')) .withCount('collections', (q) => q.as('totalCollections'))
.withCount('links', (q) => q.as('totalLinks')); .withCount('links', (q) => q.as('totalLinks'));
} }
async getAllInactiveUsers() {
const users = await this.getAllUsersWithTotalRelations();
const inactiveUsers = users.filter((user) => {
const totalLinks = Number(user.$extras.totalLinks);
const totalCollections = Number(user.$extras.totalCollections);
const isOlderThan7Days =
Math.abs(user.updatedAt.diffNow('days').days) > INACTIVE_USER_THRESHOLD;
return (totalLinks === 0 || totalCollections === 0) && isOlderThan7Days;
});
return inactiveUsers ?? [];
}
} }

View File

@@ -0,0 +1,18 @@
import UsersController from '#controllers/users_controller';
import { inject } from '@adonisjs/core';
import { BaseCommand } from '@adonisjs/core/ace';
import type { CommandOptions } from '@adonisjs/core/types/ace';
export default class RemoveInactiveUsers extends BaseCommand {
static commandName = 'remove:inactive-users';
static description = '';
static options: CommandOptions = {};
@inject()
async run(usersController: UsersController) {
const inactiveUsers = await usersController.getAllInactiveUsers();
await Promise.all(inactiveUsers.map((user) => user.delete()));
console.log(`Removed ${inactiveUsers.length} inactive users`);
}
}

6
dev.startup.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
(trap 'kill 0' SIGINT; node ace scheduler:run & pnpm run dev)
wait -n
exit $?

View File

@@ -33,7 +33,8 @@
"#tests/*": "./tests/*.js", "#tests/*": "./tests/*.js",
"#start/*": "./start/*.js", "#start/*": "./start/*.js",
"#config/*": "./config/*.js", "#config/*": "./config/*.js",
"#lib/*": "./app/lib/*.js" "#lib/*": "./app/lib/*.js",
"#routes/*": "./start/routes/*.js"
}, },
"devDependencies": { "devDependencies": {
"@adonisjs/assembler": "^7.8.2", "@adonisjs/assembler": "^7.8.2",
@@ -81,6 +82,7 @@
"@izzyjs/route": "^1.1.0-0", "@izzyjs/route": "^1.1.0-0",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@vinejs/vine": "^2.1.0", "@vinejs/vine": "^2.1.0",
"adonisjs-scheduler": "^1.0.5",
"bentocache": "^1.0.0-beta.9", "bentocache": "^1.0.0-beta.9",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"edge.js": "^6.0.2", "edge.js": "^6.0.2",

1505
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
import './routes/admin.js'; import '#routes/admin';
import './routes/app.js'; import '#routes/app';
import './routes/auth.js'; import '#routes/auth';
import './routes/collection.js'; import '#routes/collection';
import './routes/favicon.js'; import '#routes/favicon';
import './routes/link.js'; import '#routes/link';
import './routes/search.js'; import '#routes/search';
import './routes/shared_collection.js'; import '#routes/shared_collection';

3
start/scheduler.ts Normal file
View File

@@ -0,0 +1,3 @@
import scheduler from 'adonisjs-scheduler/services/main';
scheduler.command('remove:inactive-users').cron('0 20 * * *');

6
startup.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
(trap 'kill 0' SIGINT; node ace scheduler:run & pnpm start)
wait -n
exit $?