From 1379c9c706b89bcd44dd2ce7ecbc7587399967d8 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 21 Oct 2022 11:45:07 +0100 Subject: [PATCH] Server: Allow enabling and disabling tasks --- packages/server/schema.sqlite | Bin 360448 -> 339968 bytes packages/server/src/app.ts | 12 +-- .../migrations/20221020143305_task_states.ts | 17 +++++ packages/server/src/models/BaseModel.ts | 2 +- packages/server/src/models/TaskStateModel.ts | 56 ++++++++++++++ packages/server/src/models/factory.ts | 5 ++ packages/server/src/routes/admin/tasks.ts | 70 +++++++++++------ .../server/src/services/TaskService.test.ts | 48 ++++++++++-- packages/server/src/services/TaskService.ts | 71 ++++++++---------- .../server/src/services/database/types.ts | 29 +++++++ packages/server/src/tools/generateTypes.ts | 4 + packages/server/src/utils/setupAppContext.ts | 2 +- packages/server/src/utils/setupTaskService.ts | 7 +- .../server/src/views/admin/tasks.mustache | 2 + 14 files changed, 246 insertions(+), 79 deletions(-) create mode 100644 packages/server/src/migrations/20221020143305_task_states.ts create mode 100644 packages/server/src/models/TaskStateModel.ts diff --git a/packages/server/schema.sqlite b/packages/server/schema.sqlite index 852ceac167d331a515d3aac64ef53f1cf4b6042d..aefe885035545c4f48ceac65c0ce4cf37f3e459a 100644 GIT binary patch delta 1718 zcmcIkU2Ixq7(NFMrEp-dS=&+m4g+nwF*qF9Di<1{sil+#3N5a<1SmbDP&llGZnbeK z%$Y2^P?GQBBqqk%dEt^RI%uHQi%xI6n)6DHX0cb@QqyE!R4wL>!l!` zw-I2h*DPpglHg`f;)!e#of$Qp1FlGUp zYEEYckJOw46Od>)SEs=l&FfHS1W^sgo&q0gI0yaUj>ZJpKCq+V{Ph8emdiK_2AfC@ zX0%+!5pYAxRgr+Nv|I)bes9LaW#S|A>O1biR6fRdzvY*RdnynFXG8Dc%Wm!CLK681&|b@uA^vemtAlc3 zPuAUD`~s@#M-NS_M$zxH-Q={}cgYh!yKE1vxKci^-MttO``KR~plZD30N@(!wbdPT zPkTt6^^&8!h7&bB%KIa7$dOg=@m}Ind-0tja)i6nn)C_p&`YiFVtq|s_45yWjWf9t zhK(q^UW(Sk<)3ikCSD@zd&K_s*@Z6T7;>NAd9q`-N6Fbf0 zJ!aQ7u`g@>5TD2EKoIQNw3Yp96Foi=nP=$bNS@AIjKtD;deLsBB9>-wwI~zzIQ3|; zFUic(%PqZ?>$Az($P$xWSfX2)AgkLzUtMj9{6`xlvJ4Sz;iE)9_nVxJ4qR0ij|hA8 zdh{BakIkf8E4QQsh62OR$zfvB>GBT$uUNefdt(nZ319{&$-2ZwBD%Q5Fi9rCep<#( zu#PGIE9|W@c9ebcH8zA@#fs9J_ZtjD)z`X;VquR$fnMiB19T>qOy{_HqUQY;dy1}? Nh{hdO@Oua5e*x^*X~X~k delta 2186 zcmcIlZ%i9y7{8ahQrc453HCPVpX=y!brV{kWoS^K{2ee_V1SLGchHV@lva9_F-R;o zof&>un!GV*jB%{^VIMSVSt!xXDAA9)#ZQZ7kwu-!B9WNj5~qpnd!=Qp?Aw~A`Q>?j z&+q*`&-3OHt zve`&38Ox!p)PV5i+Jn$qS|#8bJq(ss8dIE02PqNuqC`|>s;V-X%_g(OWUV&2raa69 zTXC8NDGm>pN}m;_&^z!(8bSD=RF7g1>dQ^?140*Iq>Msn4t`j62%%Z{eL6b> z@0SUBXJEOy9A&3rr`m{67@k#I5n|z0bqzuxxS@UyAum*Gst_85eH!6h7rdZ3f?^GD zRU`1~;O`p2ffW{N1y@#R(LRHCHIUW{*=l%RD>yL0kF^yjTLIU#g1_fsp$=83{)M$^ z^bZ`-p$gS~__hwIsQ!YV=um}f6aJKrZN#aIl@W`@Gd9}RJkZu@t7&xjz4o)_zR5bq zJ>d&9_=BU2-y3A73{{N5Y%;VvO>A3esHwl2nHU@&cfM9*Zf^A)Ypu2)W7z(-nxMOG z;&@=l?+6dD2k<&cZf$AungGQHmi%JavfO@KfhUQ}8?OJ-Ab45v#B6y6x|@c&Esn{? zREMeCqk_V(VCycXxQlGf-&Uw{`;SN8yVT^jS;m-7XV~j*J#9PHGj8$D&iMLr7v$#h zcUFo0g*zR@=kF<0xyg@7t=PeyWh*pT3#x3P=^1u#tjlp~gc%DtXB{0)tt~8n>ju!s zb_@WWk(;}+4({pp(1}GG;)^mWA*1ttcI`2;m9O%i;8lD4PBA4W>@*?Y%-eIR$u44x z$`w1l)svbDxqu!l3$ZAj499QEs3lp{l|jZIxG6pdmub(r?KX=>EVgbr6*`G6Cb^O^)G;@=v^74UOHFPUIwTw&G| zV);9LxSFR=;v)F1ydD@6-+fAi^3Ag2n4go2NsAtE3SYqOnMo-NB`%-a6%P0&!^nmZ z>*w`X!P7mtNHi4;2*w0z+MRKbljV~~NHCX?XCGE(dy%LC^^{Fz_1JAGWfU??H_l6U|j zAxyynEXWXQlMJ**e-W4BA&%NVBbIZ`q_RyOq_V z6rg3J6>Lk;#&g45p_tU`!Fh4!o(hd;9rrzYV-bZPcH$c4_HZ(n_s?}u&WHN(Vwo>6 z?VDnze8GT=U+=|B;6=GYnT@f@To~PFeSwd4p*IrW{R`0WlnZ|e7-LZ?^}k$}MYnXZ Jn^bJE;eYIU^I`x1 diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 750d4c27e..fb370df85 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -285,18 +285,18 @@ async function main() { appLogger().info('Connection check:', connectionCheckLogInfo); const ctx = app.context as AppContext; - await setupAppContext(ctx, env, connectionCheck.connection, appLogger); - - await initializeJoplinUtils(config(), ctx.joplinBase.models, ctx.joplinBase.services.mustache); - if (config().database.autoMigration) { appLogger().info('Auto-migrating database...'); - await migrateLatest(ctx.joplinBase.db); - appLogger().info('Latest migration:', await latestMigration(ctx.joplinBase.db)); + await migrateLatest(connectionCheck.connection); + appLogger().info('Latest migration:', await latestMigration(connectionCheck.connection)); } else { appLogger().info('Skipped database auto-migration.'); } + await setupAppContext(ctx, env, connectionCheck.connection, appLogger); + + await initializeJoplinUtils(config(), ctx.joplinBase.models, ctx.joplinBase.services.mustache); + appLogger().info('Performing main storage check...'); appLogger().info(await storageConnectionCheck(config().storageDriver, ctx.joplinBase.db, ctx.joplinBase.models)); diff --git a/packages/server/src/migrations/20221020143305_task_states.ts b/packages/server/src/migrations/20221020143305_task_states.ts new file mode 100644 index 000000000..7013f54c4 --- /dev/null +++ b/packages/server/src/migrations/20221020143305_task_states.ts @@ -0,0 +1,17 @@ +import { Knex } from 'knex'; +import { DbConnection } from '../db'; + +export async function up(db: DbConnection): Promise { + await db.schema.createTable('task_states', (table: Knex.CreateTableBuilder) => { + table.increments('id').unique().primary().notNullable(); + table.integer('task_id').unique().notNullable(); + table.specificType('running', 'smallint').defaultTo(0).notNullable(); + table.specificType('enabled', 'smallint').defaultTo(1).notNullable(); + table.bigInteger('updated_time').notNullable(); + table.bigInteger('created_time').notNullable(); + }); +} + +export async function down(db: DbConnection): Promise { + await db.schema.dropTable('task_states'); +} diff --git a/packages/server/src/models/BaseModel.ts b/packages/server/src/models/BaseModel.ts index 363129fae..63808abef 100644 --- a/packages/server/src/models/BaseModel.ts +++ b/packages/server/src/models/BaseModel.ts @@ -357,7 +357,7 @@ export default abstract class BaseModel { return toSave; } - public async loadByIds(ids: string[], options: LoadOptions = {}): Promise { + public async loadByIds(ids: string[] | number[], options: LoadOptions = {}): Promise { if (!ids.length) return []; ids = unique(ids); return this.db(this.tableName).select(options.fields || this.defaultFields).whereIn('id', ids); diff --git a/packages/server/src/models/TaskStateModel.ts b/packages/server/src/models/TaskStateModel.ts new file mode 100644 index 000000000..024915d89 --- /dev/null +++ b/packages/server/src/models/TaskStateModel.ts @@ -0,0 +1,56 @@ +import { TaskId, TaskState } from '../services/database/types'; +import BaseModel from './BaseModel'; + +export default class TaskStateModel extends BaseModel { + + public get tableName(): string { + return 'task_states'; + } + + protected hasUuid(): boolean { + return false; + } + + public async loadByTaskId(taskId: TaskId): Promise { + return this.db(this.tableName).where('task_id', '=', taskId).first(); + } + + public async loadByTaskIds(taskIds: TaskId[]): Promise { + return this.db(this.tableName).whereIn('task_id', taskIds); + } + + public async init(taskId: TaskId) { + const taskState: TaskState = await this.loadByTaskId(taskId); + if (taskState) return taskState; + + return this.save({ + task_id: taskId, + enabled: 1, + running: 0, + }); + } + + public async start(taskId: TaskId) { + const state = await this.loadByTaskId(taskId); + if (state.running) throw new Error(`Task is already running: ${taskId}`); + await this.save({ id: state.id, running: 1 }); + } + + public async stop(taskId: TaskId) { + const state = await this.loadByTaskId(taskId); + if (!state.running) throw new Error(`Task is not running: ${taskId}`); + await this.save({ id: state.id, running: 0 }); + } + + public async enable(taskId: TaskId, enabled: boolean = true) { + const state = await this.loadByTaskId(taskId); + if (state.enabled && enabled) throw new Error(`Task is already enabled: ${taskId}`); + if (!state.enabled && !enabled) throw new Error(`Task is already disabled: ${taskId}`); + await this.save({ id: state.id, enabled: enabled ? 1 : 0 }); + } + + public async disable(taskId: TaskId) { + await this.enable(taskId, false); + } + +} diff --git a/packages/server/src/models/factory.ts b/packages/server/src/models/factory.ts index 94b3a6221..40850bf29 100644 --- a/packages/server/src/models/factory.ts +++ b/packages/server/src/models/factory.ts @@ -76,6 +76,7 @@ import LockModel from './LockModel'; import StorageModel from './StorageModel'; import UserDeletionModel from './UserDeletionModel'; import BackupItemModel from './BackupItemModel'; +import TaskStateModel from './TaskStateModel'; export type NewModelFactoryHandler = (db: DbConnection)=> Models; @@ -175,6 +176,10 @@ export class Models { return new BackupItemModel(this.db_, this.newModelFactory, this.config_); } + public taskState() { + return new TaskStateModel(this.db_, this.newModelFactory, this.config_); + } + } export default function newModelFactory(db: DbConnection, config: Config): Models { diff --git a/packages/server/src/routes/admin/tasks.ts b/packages/server/src/routes/admin/tasks.ts index c8f925e98..c2e792377 100644 --- a/packages/server/src/routes/admin/tasks.ts +++ b/packages/server/src/routes/admin/tasks.ts @@ -11,44 +11,63 @@ import { formatDateTime } from '../../utils/time'; import { createCsrfTag } from '../../utils/csrf'; import { RunType } from '../../services/TaskService'; import { NotificationKey } from '../../models/NotificationModel'; -import { NotificationLevel } from '../../services/database/types'; +import { NotificationLevel, TaskId } from '../../services/database/types'; const prettyCron = require('prettycron'); const router: Router = new Router(RouteType.Web); router.post('admin/tasks', async (_path: SubPath, ctx: AppContext) => { + interface FormFields { + startTaskButton: string; + enableTaskButton: string; + disableTaskButton: string; + } + const user = ctx.joplin.owner; if (!user.is_admin) throw new ErrorForbidden(); const taskService = ctx.joplin.services.tasks; - const fields: any = await bodyFields(ctx.req); + const fields = await bodyFields(ctx.req); + + const taskIds: TaskId[] = []; + + for (const k of Object.keys(fields)) { + if (k.startsWith('checkbox_')) { + taskIds.push(Number(k.substr(9))); + } + } + + const errors: Error[] = []; if (fields.startTaskButton) { - const errors: Error[] = []; - - for (const k of Object.keys(fields)) { - if (k.startsWith('checkbox_')) { - const taskId = Number(k.substr(9)); - try { - void taskService.runTask(taskId, RunType.Manual); - } catch (error) { - errors.push(error); - } + for (const taskId of taskIds) { + try { + void taskService.runTask(taskId, RunType.Manual); + } catch (error) { + errors.push(error); } } - - if (errors.length) { - await ctx.joplin.models.notification().add( - user.id, - NotificationKey.Any, - NotificationLevel.Error, - `Some tasks could not be started: ${errors.join('. ')}` - ); + } else if (fields.enableTaskButton || fields.disableTaskButton) { + for (const taskId of taskIds) { + try { + await taskService.enableTask(taskId, !!fields.enableTaskButton); + } catch (error) { + errors.push(error); + } } } else { throw new ErrorBadRequest('Invalid action'); } + if (errors.length) { + await ctx.joplin.models.notification().add( + user.id, + NotificationKey.Any, + NotificationLevel.Error, + `Some operations could not be performed: ${errors.join('. ')}` + ); + } + return redirect(ctx, makeUrl(UrlType.Tasks)); }); @@ -57,11 +76,12 @@ router.get('admin/tasks', async (_path: SubPath, ctx: AppContext) => { if (!user.is_admin) throw new ErrorForbidden(); const taskService = ctx.joplin.services.tasks; + const states = await ctx.joplin.models.taskState().loadByTaskIds(taskService.taskIds); const taskRows: Row[] = []; for (const [taskIdString, task] of Object.entries(taskService.tasks)) { const taskId = Number(taskIdString); - const state = taskService.taskState(taskId); + const state = states.find(s => s.task_id === taskId); const events = await taskService.taskLastEvents(taskId); taskRows.push({ @@ -80,6 +100,9 @@ router.get('admin/tasks', async (_path: SubPath, ctx: AppContext) => { value: task.schedule, hint: prettyCron.toString(task.schedule), }, + { + value: yesOrNo(state.enabled), + }, { value: yesOrNo(state.running), }, @@ -111,6 +134,10 @@ router.get('admin/tasks', async (_path: SubPath, ctx: AppContext) => { name: 'schedule', label: 'Schedule', }, + { + name: 'enabled', + label: 'Enabled', + }, { name: 'running', label: 'Running', @@ -134,7 +161,6 @@ router.get('admin/tasks', async (_path: SubPath, ctx: AppContext) => { postUrl: makeUrl(UrlType.Tasks), csrfTag: await createCsrfTag(ctx), }, - // cssFiles: ['index/tasks'], }; }); diff --git a/packages/server/src/services/TaskService.test.ts b/packages/server/src/services/TaskService.test.ts index 23ba02ebf..da82cea4f 100644 --- a/packages/server/src/services/TaskService.test.ts +++ b/packages/server/src/services/TaskService.test.ts @@ -38,7 +38,7 @@ describe('TaskService', function() { schedule: '', }; - service.registerTask(task); + await service.registerTask(task); expect(service.tasks[123456]).toBeTruthy(); await expectThrow(async () => service.registerTask(task)); @@ -47,6 +47,8 @@ describe('TaskService', function() { test('should run a task', async function() { const service = newService(); + let taskStarted = false; + let waitToFinish = true; let finishTask = false; let taskHasRan = false; @@ -56,7 +58,11 @@ describe('TaskService', function() { id: taskId, description: '', run: async (_models: Models) => { + taskStarted = true; + const iid = setInterval(() => { + if (waitToFinish) return; + if (finishTask) { clearInterval(iid); taskHasRan = true; @@ -66,25 +72,57 @@ describe('TaskService', function() { schedule: '', }; - service.registerTask(task); + await service.registerTask(task); - expect(service.taskState(taskId).running).toBe(false); + expect((await service.taskState(taskId)).running).toBe(0); const startTime = new Date(); void service.runTask(taskId, RunType.Manual); - expect(service.taskState(taskId).running).toBe(true); + while (!taskStarted) { + await msleep(1); + } + + expect((await service.taskState(taskId)).running).toBe(1); + waitToFinish = false; while (!taskHasRan) { await msleep(1); finishTask = true; } - expect(service.taskState(taskId).running).toBe(false); + expect((await service.taskState(taskId)).running).toBe(0); const events = await service.taskLastEvents(taskId); expect(events.taskStarted.created_time).toBeGreaterThanOrEqual(startTime.getTime()); expect(events.taskCompleted.created_time).toBeGreaterThan(startTime.getTime()); }); + test('should not run if task is disabled', async function() { + const service = newService(); + + let taskHasRan = false; + + const taskId = 123456; + + const task: Task = { + id: taskId, + description: '', + run: async (_models: Models) => { + taskHasRan = true; + }, + schedule: '', + }; + + await service.registerTask(task); + + await service.runTask(taskId, RunType.Manual); + expect(taskHasRan).toBe(true); + + taskHasRan = false; + await models().taskState().disable(task.id); + await service.runTask(taskId, RunType.Manual); + expect(taskHasRan).toBe(false); + }); + }); diff --git a/packages/server/src/services/TaskService.ts b/packages/server/src/services/TaskService.ts index 7bd3415cb..8bdef6ecf 100644 --- a/packages/server/src/services/TaskService.ts +++ b/packages/server/src/services/TaskService.ts @@ -2,25 +2,14 @@ import Logger from '@joplin/lib/Logger'; import { Models } from '../models/factory'; import { Config, Env } from '../utils/types'; import BaseService from './BaseService'; -import { Event, EventType } from './database/types'; +import { Event, EventType, TaskId, TaskState } from './database/types'; import { Services } from './types'; import { _ } from '@joplin/lib/locale'; +import { ErrorNotFound } from '../utils/errors'; const cron = require('node-cron'); const logger = Logger.create('TaskService'); -export enum TaskId { - DeleteExpiredTokens = 1, - UpdateTotalSizes = 2, - HandleOversizedAccounts = 3, - HandleBetaUserEmails = 4, - HandleFailedPaymentSubscriptions = 5, - DeleteExpiredSessions = 6, - CompressOldChanges = 7, - ProcessUserDeletions = 8, - AutoAddDisabledAccountsForDeletion = 9, -} - export enum RunType { Scheduled = 1, Manual = 2, @@ -60,14 +49,6 @@ export interface Task { export type Tasks = Record; -interface TaskState { - running: boolean; -} - -const defaultTaskState: TaskState = { - running: false, -}; - interface TaskEvents { taskStarted: Event; taskCompleted: Event; @@ -76,7 +57,6 @@ interface TaskEvents { export default class TaskService extends BaseService { private tasks_: Tasks = {}; - private taskStates_: Record = {}; private services_: Services; public constructor(env: Env, models: Models, config: Config, services: Services) { @@ -84,23 +64,32 @@ export default class TaskService extends BaseService { this.services_ = services; } - public registerTask(task: Task) { + public async registerTask(task: Task) { if (this.tasks_[task.id]) throw new Error(`Already a task with this ID: ${task.id}`); this.tasks_[task.id] = task; - this.taskStates_[task.id] = { ...defaultTaskState }; + await this.models.taskState().init(task.id); } - public registerTasks(tasks: Task[]) { - for (const task of tasks) this.registerTask(task); + public async registerTasks(tasks: Task[]) { + for (const task of tasks) await this.registerTask(task); } public get tasks(): Tasks { return this.tasks_; } - public taskState(id: TaskId): TaskState { - if (!this.taskStates_[id]) throw new Error(`No such task: ${id}`); - return this.taskStates_[id]; + public get taskIds(): TaskId[] { + return Object.keys(this.tasks_).map(s => Number(s)); + } + + public async taskStates(ids: TaskId[]): Promise { + return this.models.taskState().loadByTaskIds(ids); + } + + public async taskState(id: TaskId): Promise { + const r = await this.taskStates([id]); + if (!r.length) throw new ErrorNotFound(`No such task: ${id}`); + return r[0]; } public async taskLastEvents(id: TaskId): Promise { @@ -122,16 +111,16 @@ export default class TaskService extends BaseService { public async runTask(id: TaskId, runType: RunType) { const displayString = this.taskDisplayString(id); - const state = this.taskState(id); - if (state.running) throw new Error(`Already running: ${displayString}`); + const taskState = await this.models.taskState().loadByTaskId(id); + if (!taskState.enabled) { + logger.info(`Not running ${displayString} because the tasks is disabled`); + return; + } + + await this.models.taskState().start(id); const startTime = Date.now(); - this.taskStates_[id] = { - ...this.taskStates_[id], - running: true, - }; - await this.models.event().create(EventType.TaskStarted, id.toString()); try { @@ -141,16 +130,16 @@ export default class TaskService extends BaseService { logger.error(`On ${displayString}`, error); } - this.taskStates_[id] = { - ...this.taskStates_[id], - running: false, - }; - + await this.models.taskState().stop(id); await this.models.event().create(EventType.TaskCompleted, id.toString()); logger.info(`Completed ${this.taskDisplayString(id)} in ${Date.now() - startTime}ms`); } + public async enableTask(taskId: TaskId, enabled: boolean = true) { + await this.models.taskState().enable(taskId, enabled); + } + public async runInBackground() { for (const [taskId, task] of Object.entries(this.tasks_)) { if (!task.schedule) continue; diff --git a/packages/server/src/services/database/types.ts b/packages/server/src/services/database/types.ts index 10c6ee7ba..7358c6c8c 100644 --- a/packages/server/src/services/database/types.ts +++ b/packages/server/src/services/database/types.ts @@ -111,6 +111,20 @@ interface DatabaseTables { [key: string]: DatabaseTable; } +export enum TaskId { + // Don't re-use any of these numbers, always add to it, as the ID is used in + // the database + DeleteExpiredTokens = 1, + UpdateTotalSizes = 2, + HandleOversizedAccounts = 3, + HandleBetaUserEmails = 4, + HandleFailedPaymentSubscriptions = 5, + DeleteExpiredSessions = 6, + CompressOldChanges = 7, + ProcessUserDeletions = 8, + AutoAddDisabledAccountsForDeletion = 9, +} + // AUTO-GENERATED-TYPES // Auto-generated using `yarn run generate-types` export interface Session extends WithDates, WithUuid { @@ -300,6 +314,13 @@ export interface BackupItem extends WithCreatedDate { content?: Buffer; } +export interface TaskState extends WithDates { + id?: number; + task_id?: TaskId; + running?: number; + enabled?: number; +} + export const databaseSchema: DatabaseTables = { sessions: { id: { type: 'string' }, @@ -504,5 +525,13 @@ export const databaseSchema: DatabaseTables = { content: { type: 'any' }, created_time: { type: 'string' }, }, + task_states: { + id: { type: 'number' }, + task_id: { type: 'number' }, + running: { type: 'number' }, + enabled: { type: 'number' }, + updated_time: { type: 'string' }, + created_time: { type: 'string' }, + }, }; // AUTO-GENERATED-TYPES diff --git a/packages/server/src/tools/generateTypes.ts b/packages/server/src/tools/generateTypes.ts index c08bf1b1f..d8193fba5 100644 --- a/packages/server/src/tools/generateTypes.ts +++ b/packages/server/src/tools/generateTypes.ts @@ -36,6 +36,7 @@ const config = { 'main.users': 'WithDates, WithUuid', 'main.events': 'WithUuid', 'main.user_deletions': 'WithDates', + 'main.task_states': 'WithDates', 'main.backup_items': 'WithCreatedDate', }, }; @@ -65,6 +66,7 @@ const propertyTypes: Record = { 'user_deletions.scheduled_time': 'number', 'users.disabled_time': 'number', 'backup_items.content': 'Buffer', + 'task_states.task_id': 'TaskId', }; function insertContentIntoFile(filePath: string, markerOpen: string, markerClose: string, contentToInsert: string): void { @@ -158,6 +160,8 @@ async function main() { content += `export const databaseSchema: DatabaseTables = {\n${tableStrings.join('\n')}\n};`; insertContentIntoFile(dbFilePath, fileReplaceWithinMarker, fileReplaceWithinMarker, content); + + console.info(`Types have been updated in ${dbFilePath}`); } main().catch(error => { diff --git a/packages/server/src/utils/setupAppContext.ts b/packages/server/src/utils/setupAppContext.ts index 0e33cc34f..9b2fd0d62 100644 --- a/packages/server/src/utils/setupAppContext.ts +++ b/packages/server/src/utils/setupAppContext.ts @@ -20,7 +20,7 @@ async function setupServices(env: Env, models: Models, config: Config): Promise< tasks: null, }; - output.tasks = setupTaskService(env, models, config, output), + output.tasks = await setupTaskService(env, models, config, output), await output.mustache.loadPartials(); diff --git a/packages/server/src/utils/setupTaskService.ts b/packages/server/src/utils/setupTaskService.ts index 58f2707d2..2e71fa076 100644 --- a/packages/server/src/utils/setupTaskService.ts +++ b/packages/server/src/utils/setupTaskService.ts @@ -1,9 +1,10 @@ import { Models } from '../models/factory'; -import TaskService, { Task, TaskId, taskIdToLabel } from '../services/TaskService'; +import { TaskId } from '../services/database/types'; +import TaskService, { Task, taskIdToLabel } from '../services/TaskService'; import { Services } from '../services/types'; import { Config, Env } from './types'; -export default function(env: Env, models: Models, config: Config, services: Services): TaskService { +export default async function(env: Env, models: Models, config: Config, services: Services): Promise { const taskService = new TaskService(env, models, config, services); let tasks: Task[] = [ @@ -80,7 +81,7 @@ export default function(env: Env, models: Models, config: Config, services: Serv ]); } - taskService.registerTasks(tasks); + await taskService.registerTasks(tasks); return taskService; } diff --git a/packages/server/src/views/admin/tasks.mustache b/packages/server/src/views/admin/tasks.mustache index 924dc981e..1ec0656ee 100644 --- a/packages/server/src/views/admin/tasks.mustache +++ b/packages/server/src/views/admin/tasks.mustache @@ -7,5 +7,7 @@
+ +
\ No newline at end of file