Skip to content

Commit

Permalink
Less-dynamic module system (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
lynn authored Jan 5, 2025
1 parent ea518a0 commit bacc29b
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 334 deletions.
17 changes: 8 additions & 9 deletions config/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ exit_on_module_load_error: true
# passwords
password_rounds: 8

# please note that modules are loaded in the given order. below are
# given sensible defaults, but you are welcome to use your own values.
# below are sensible defaults, but you are welcome to use your own values.
modules: {}
# modules/housekeep.js: {}
### updating the database from foreign sources; `sources.yml` contains
### the module-specific configuration
### reading from and saving to a disk file
# modules/disk.js:
# enabled: true
Expand All @@ -30,13 +32,6 @@ modules: {}
##### prophylactic. 3600_000 (every hour) is good; 86400_000 (every
##### full day) is, for all intents and purposes, good too
# backup_interval: 1800_000
### refreshing a few variables after loading the database
# modules/housekeep.js: {}
### updating the database from foreign sources; `sources.yml` contains
### the module-specific configuration
# modules/update.js:
# enabled: true
# update_interval: 300_000
### removing sufficiently downvoted entries
# modules/cleanup.js:
# enabled: true
Expand All @@ -49,3 +44,7 @@ modules: {}
# modules/announce.js:
# enabled: true
# hook: 'https://discordapp.com/api/webooks/here/goes/your/webhook'
### refreshing a few variables after loading the database
# modules/update.js:
# enabled: true
# update_interval: 300_000
14 changes: 14 additions & 0 deletions core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ actions.vote = guard(
e.votes[uname] = i.vote;
e.score += i.vote - old_vote;
ret(good({ entry: present(e, uname) }));

const cleanup = config.modules['modules/cleanup.js'];
if (cleanup.enabled) {
const culpable = !cleanup.users || cleanup.users.includes(e.user);
const bad = e.score <= cleanup.vote_threshold;
if (culpable && bad) {
call(
{ action: 'remove', id: e.id },
() => console.log(`-- ${e.head} weeded out`),
e.user,
);
}
}

emitter.emit('vote', e, uname);
},
);
Expand Down
16 changes: 15 additions & 1 deletion core/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,21 @@ export interface ToaduaConfig {
*/
password_rounds: number;

modules: Record<string, object>;
modules: {
'modules/disk.js'?: {
enabled: boolean;
save_interval: number;
backup_interval: number;
};
'modules/housekeep.js'?: Record<string, never>;
'modules/update.js'?: { enabled: boolean; save_interval: number };
'modules/cleanup.js'?: {
enabled: boolean;
vote_threshold: number;
users?: string[];
};
'modules/announce.js'?: { enabled: boolean; hook: string };
};
}

const toaduaPath = getToaduaPath();
Expand Down
93 changes: 59 additions & 34 deletions core/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import * as fs from 'node:fs';
import * as argparse from 'argparse';
import * as commons from './commons.js';

import { HousekeepModule } from '../modules/housekeep.js';
import { DiskModule } from '../modules/disk.js';
import { UpdateModule } from '../modules/update.js';
import { AnnounceModule } from '../modules/announce.js';

const argparser = new argparse.ArgumentParser({
description: 'Toaq dictionary',
add_help: true,
Expand Down Expand Up @@ -36,6 +41,7 @@ console.log(`starting up v${VERSION}...`);
import * as http from 'node:http';
import * as api from './api.js';
import type { Socket } from 'node:net';
import type { EventEmitter } from 'node:stream';

const fourohfour = static_handler('frontend/404.html', 'text/html', 404);
const routes = {
Expand Down Expand Up @@ -169,37 +175,64 @@ function handler(r, s_) {
}
}

const modules: Record<string, any> = {};
class ToaduaModules {
private housekeep?: HousekeepModule;
private disk?: DiskModule;
private announce?: AnnounceModule;
private update?: UpdateModule;

async function load_modules(data: commons.ToaduaConfig): Promise<void> {
for (const path of Object.keys(data.modules)) {
if (!modules[path]) {
try {
modules[path] = { ...(await import(`./../${path}`)), path };
} catch (e) {
if (config.exit_on_module_load_error) throw e;
console.log(`error when loading module '${path}': ${e.stack}`);
delete modules[path];
}
constructor(
private store: commons.Store,
private config: commons.ToaduaConfig,
private emitter: EventEmitter,
) {
const housekeepConfig = config.modules['modules/housekeep.js'];
if (housekeepConfig) {
this.housekeep = new HousekeepModule();
}
}
for (const path in modules) {
const new_options = data.modules[path];
// note that when an entry in the module table is removed,
// `new_options === undefined`. this is all right
if (JSON.stringify(new_options) !== JSON.stringify(modules[path].options)) {
modules[path].options = new_options;
console.log(`changing state for module '${path}'`);
try {
modules[path].state_change.call(new_options);
} catch (e) {
console.log(`error for module '${path}': ${e.stack}`);
}

const diskConfig = config.modules['modules/disk.js'];
if (diskConfig) {
this.disk = new DiskModule(
diskConfig.save_interval,
diskConfig.backup_interval,
);
}

const announceConfig = config.modules['modules/announce.js'];
if (announceConfig) {
this.announce = new AnnounceModule(
announceConfig.enabled,
announceConfig.hook,
);
}

const updateConfig = config.modules['modules/update.js'];
if (updateConfig) {
this.update = new UpdateModule(
updateConfig.enabled,
updateConfig.save_interval,
this.announce,
);
}
}

public up(): void {
this.housekeep?.up(this.store, this.config);
this.disk?.up(this.store);
this.announce?.up(this.emitter);
this.update?.up(this.store);
}

public down(): void {
// The other modules don't need to do anything special when Toadua is
// going down:
this.disk?.down(this.store);
}
}

await load_modules(config);
const modules = new ToaduaModules(commons.store, config, commons.emitter);
modules.up();

const server = http.createServer(handler);
const connections: Socket[] = [];
Expand All @@ -223,15 +256,7 @@ function bye(error) {
for (const connection of connections) {
connection.destroy();
}
for (const [path, _] of Object.entries(modules).reverse()) {
try {
_.state_change.call(null);
} catch (e) {
console.log(
`ignoring state change error for module '${path}': ${e.stack}`,
);
}
}
modules.down();
process.exitCode = 0;
}

Expand Down
Loading

0 comments on commit bacc29b

Please sign in to comment.