mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat: remove inactive users (scheduler)
This commit is contained in:
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
|
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
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -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
|
||||||
|
|||||||
14
adonisrc.ts
14
adonisrc.ts
@@ -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'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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 ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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",
|
"#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
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/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
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