feat: create scheduler to run cron jobs and setup periodic repo update

This commit is contained in:
Nicolas Meienberger 2023-02-12 18:43:13 +01:00 committed by Nicolas Meienberger
parent 90115b149f
commit 747fee006d
5 changed files with 52 additions and 28 deletions

View file

@ -1,5 +1,5 @@
{
"watch": ["server.ts"],
"watch": ["server.ts", "src/server"],
"exec": "ts-node --project tsconfig.server.json server.ts",
"ext": "js ts"
}

View file

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"prisma:pull": "prisma db pull",
"test": "dotenv -e .env.test -- ts-node ./run-migration.ts && jest --colors",
"test": "dotenv -e .env.test -- jest --colors",
"test:client": "jest --colors --selectProjects client --",
"test:server": "jest --colors --selectProjects server --",
"postinstall": "prisma generate",
@ -45,7 +45,6 @@
"redis": "^4.3.1",
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"remark-mdx": "^2.1.1",
"sass": "^1.55.0",
"semver": "^7.3.7",
"sharp": "0.30.7",
@ -86,6 +85,7 @@
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "13.1.1",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^27.1.7",
"eslint-plugin-jsdoc": "^39.6.9",

View file

@ -2,20 +2,21 @@ import path from 'path';
import pg from 'pg';
import { migrate } from '@runtipi/postgres-migrations';
import { Logger } from './src/server/core/Logger';
import { getConfig } from './src/server/core/TipiConfig';
export const runPostgresMigrations = async (dbName?: string) => {
Logger.info('Starting database migration');
const { POSTGRES_HOST, POSTGRES_DBNAME, POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_PORT = 5432 } = process.env;
const { postgresHost, postgresDatabase, postgresUsername, postgresPassword, postgresPort } = getConfig();
Logger.info('Connecting to database', POSTGRES_DBNAME, 'on', POSTGRES_HOST, 'as', POSTGRES_USERNAME, 'on port', POSTGRES_PORT);
Logger.info(`Connecting to database ${postgresDatabase} on ${postgresHost} as ${postgresUsername} on port ${postgresPort}`);
const client = new pg.Client({
user: POSTGRES_USERNAME,
host: POSTGRES_HOST,
database: dbName || POSTGRES_DBNAME,
password: POSTGRES_PASSWORD,
port: Number(POSTGRES_PORT),
user: postgresUsername,
host: postgresHost,
database: dbName || postgresDatabase,
password: postgresPassword,
port: Number(postgresPort),
});
await client.connect();
@ -44,5 +45,3 @@ export const runPostgresMigrations = async (dbName?: string) => {
Logger.info('Migration complete');
await client.end();
};
runPostgresMigrations();

View file

@ -32,13 +32,16 @@ nextApp.prepare().then(async () => {
// Run database migrations
await runPostgresMigrations();
// startJobs();
setConfig('status', 'RUNNING');
// Clone and update apps repo
await EventDispatcher.dispatchEventAsync('clone_repo', [getConfig().appsRepoUrl]);
await EventDispatcher.dispatchEventAsync('update_repo', [getConfig().appsRepoUrl]);
// Scheduled events
EventDispatcher.scheduleEvent({ type: 'update_repo', args: [getConfig().appsRepoUrl], cronExpression: '*/30 * * * *' });
EventDispatcher.scheduleEvent({ type: 'system_info', args: [], cronExpression: '* * * * *' });
appService.startAllApps();
Logger.info(`> Server listening at http://localhost:${port} as ${dev ? 'development' : process.env.NODE_ENV}`);

View file

@ -1,4 +1,5 @@
/* eslint-disable vars-on-top */
import cron from 'node-cron';
import fs from 'fs-extra';
import { Logger } from '../Logger';
import { getConfig } from '../TipiConfig';
@ -27,7 +28,14 @@ type SystemEvent = {
creationDate: Date;
};
type EventStatusTypes = 'running' | 'success' | 'error' | 'waiting';
const EVENT_STATUS = {
RUNNING: 'running',
SUCCESS: 'success',
ERROR: 'error',
WAITING: 'waiting',
} as const;
type EventStatus = typeof EVENT_STATUS[keyof typeof EVENT_STATUS]; // 'running' | 'success' | 'error' | 'waiting';
const WATCH_FILE = '/runtipi/state/events';
@ -60,7 +68,8 @@ class EventDispatcher {
/**
* Generate a random task id
* @returns - Random id
*
* @returns {string} id - Randomly generated id
*/
static generateId() {
return Math.random().toString(36).substring(2, 9);
@ -125,10 +134,11 @@ class EventDispatcher {
/**
* Check event status
* @param id - Event id
* @returns - Event status
*
* @param {string} id - Event id
* @returns {EventStatus} - Event status
*/
private getEventStatus(id: string): EventStatusTypes {
private getEventStatus(id: string): EventStatus {
const event = this.queue.find((e) => e.id === id);
if (!event) {
@ -148,16 +158,17 @@ class EventDispatcher {
return 'waiting';
}
const status = line.split(' ')[2] as EventStatusTypes;
const status = line.split(' ')[2] as EventStatus;
return status;
}
/**
* Dispatch an event to the queue
* @param type - Event type
* @param args - Event arguments
* @returns - Event object
*
* @param {EventType} type - Event type
* @param {[string]} args - Event arguments
* @returns {SystemEvent} event - Event object
*/
public dispatchEvent(type: EventType, args?: string[]): SystemEvent {
const event: SystemEvent = {
@ -173,10 +184,12 @@ class EventDispatcher {
}
/**
* Clear event from queue
* @param id - Event id
* Clears an event from the queue
*
* @param {SystemEvent} event - The event to clear
* @param {EventStatus} status - The status to consider the event to
*/
private clearEvent(event: SystemEvent, status: EventStatusTypes = 'success') {
private clearEvent(event: SystemEvent, status: EventStatus = 'success') {
this.queue = this.queue.filter((e) => e.id !== event.id);
if (fs.existsSync(`/app/logs/${event.id}.log`)) {
const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
@ -192,8 +205,9 @@ class EventDispatcher {
/**
* Dispatch an event to the queue and wait for it to finish
* @param type - Event type
* @param args - Event arguments
*
* @param {EventType} type - Event type
* @param {[string[]]} args - Event arguments
* @returns - Promise that resolves when the event is done
*/
public async dispatchEventAsync(type: EventType, args?: string[]): Promise<{ success: boolean; stdout?: string }> {
@ -231,6 +245,14 @@ class EventDispatcher {
EventDispatcher.instance = null;
fs.writeFileSync(WATCH_FILE, '');
}
public scheduleEvent(params: { type: EventType; args?: string[]; cronExpression: string }) {
const { type, args, cronExpression } = params;
cron.schedule(cronExpression, async () => {
this.dispatchEvent(type, args);
});
}
}
export const EventDispatcherInstance = global.EventDispatcher || EventDispatcher.getInstance();