mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 14:43:24 +00:00
feat: remove inactive users (scheduler)
This commit is contained in:
@@ -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
12
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
2
Makefile
2
Makefile
@@ -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
|
||||
|
||||
14
adonisrc.ts
14
adonisrc.ts
@@ -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'],
|
||||
},
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -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 ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
18
commands/remove_inactive_users.ts
Normal file
18
commands/remove_inactive_users.ts
Normal 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
6
dev.startup.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
(trap 'kill 0' SIGINT; node ace scheduler:run & pnpm run dev)
|
||||
|
||||
wait -n
|
||||
exit $?
|
||||
@@ -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
1505
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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
3
start/scheduler.ts
Normal 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
6
startup.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
(trap 'kill 0' SIGINT; node ace scheduler:run & pnpm start)
|
||||
|
||||
wait -n
|
||||
exit $?
|
||||
Reference in New Issue
Block a user