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

View File

@@ -13,4 +13,4 @@ DB_PASSWORD=my-links-pwd
DB_DATABASE=my-links
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CLIENT_CALLBACK_URL=http://localhost:3333/auth/callback
GOOGLE_CLIENT_CALLBACK_URL=http://localhost:3333/auth/callback

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
COPY --from=production-deps /app/node_modules /app/node_modules
COPY --from=build /app/build /app
COPY --from=build /app/startup.sh /app/startup.sh
# Expose port
EXPOSE $PORT
# 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 -f dev.docker-compose.yml up -d --wait
@node ace migration:fresh
@pnpm run dev
@./dev.startup.sh
prod:
@docker compose -f dev.docker-compose.yml down

View File

@@ -14,6 +14,7 @@ export default defineConfig({
() => import('@adonisjs/core/commands'),
() => import('@adonisjs/lucid/commands'),
() => import('@izzyjs/route/commands'),
() => import('adonisjs-scheduler/commands'),
],
/*
@@ -45,6 +46,10 @@ export default defineConfig({
() => import('@adonisjs/ally/ally_provider'),
() => import('@izzyjs/route/izzy_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.
|
*/
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 { RouteName } from '@izzyjs/route/types';
const INACTIVE_USER_THRESHOLD = 7;
export default class UsersController {
private redirectTo: RouteName = 'auth.login';
@@ -75,4 +77,18 @@ export default class UsersController {
.withCount('collections', (q) => q.as('totalCollections'))
.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",
"#start/*": "./start/*.js",
"#config/*": "./config/*.js",
"#lib/*": "./app/lib/*.js"
"#lib/*": "./app/lib/*.js",
"#routes/*": "./start/routes/*.js"
},
"devDependencies": {
"@adonisjs/assembler": "^7.8.2",
@@ -81,6 +82,7 @@
"@izzyjs/route": "^1.1.0-0",
"@tanstack/react-table": "^8.20.5",
"@vinejs/vine": "^2.1.0",
"adonisjs-scheduler": "^1.0.5",
"bentocache": "^1.0.0-beta.9",
"dayjs": "^1.11.13",
"edge.js": "^6.0.2",
@@ -124,4 +126,4 @@
"volta": {
"node": "22.2.0"
}
}
}

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/app.js';
import './routes/auth.js';
import './routes/collection.js';
import './routes/favicon.js';
import './routes/link.js';
import './routes/search.js';
import './routes/shared_collection.js';
import '#routes/admin';
import '#routes/app';
import '#routes/auth';
import '#routes/collection';
import '#routes/favicon';
import '#routes/link';
import '#routes/search';
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 $?