From a0e9061352f2adbafa4e84b1bed3cd92df0c815d Mon Sep 17 00:00:00 2001 From: garrettmills Date: Mon, 20 Jan 2025 21:50:40 -0500 Subject: [PATCH] Clean up index.ts and implement basic http server --- index.ts | 16 ++++++++-- src/bones/Unit.ts | 65 ++++++++++++++++++++++++++++++++++++++++ src/config.ts | 1 + src/http/server.ts | 11 ------- src/types.ts | 1 + src/units/http-server.ts | 34 +++++++++++++++++++++ src/units/threads.ts | 38 +++++++++++++++++++++++ 7 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 src/bones/Unit.ts delete mode 100644 src/http/server.ts create mode 100644 src/units/http-server.ts create mode 100644 src/units/threads.ts diff --git a/index.ts b/index.ts index 8acae9a..52eaf1d 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,17 @@ -import {ensureDirectoriesExist} from "./src/config.ts"; -import {refreshThreadsEntirely} from "./src/threads/refresh.ts"; +import {config, ensureDirectoriesExist} from "./src/config.ts"; +import {UnitManager} from "./src/bones/Unit.ts"; +import {ThreadRefresher} from "./src/units/threads.ts"; +import {HttpServer} from "./src/units/http-server.ts"; ;(async () => { await ensureDirectoriesExist() - await refreshThreadsEntirely() + + const manager = new UnitManager() + + await manager.addUnit(new ThreadRefresher) + if ( config.http.enabled ) { + await manager.addUnit(new HttpServer) + } + + await manager.run() })() diff --git a/src/bones/Unit.ts b/src/bones/Unit.ts new file mode 100644 index 0000000..ee0b1fd --- /dev/null +++ b/src/bones/Unit.ts @@ -0,0 +1,65 @@ +import type {Maybe} from "./types.ts"; + +export abstract class Unit { + public abstract up(): Promise + public abstract down(): Promise +} + +export class UnitManager { + private state: 'init' | 'starting' | 'running' | 'stopping' | 'stopped' = 'init' + private pendingUnits: Unit[] = [] + private runningUnits: Unit[] = [] + + public async addUnit(unit: Unit): Promise { + this.pendingUnits.push(unit) + } + + public getState(): typeof this.state { + return this.state + } + + public async run(): Promise { + await this.start() + console.log('Press Ctrl-C to stop.') + await new Promise(res => { + process.on('SIGINT', () => res()) + }) + await this.stop() + } + + public async start(): Promise { + this.state = 'starting' + + let unit: Maybe + while ( unit = this.pendingUnits.shift() ) { + try { + await unit.up() + } catch (e) { + console.error('Error when starting unit') + console.error(e) + await this.stop() + throw e + } + + this.runningUnits.push(unit) + } + + this.state = 'running' + } + + public async stop(): Promise { + this.state = 'stopping' + + let unit: Maybe + while ( unit = this.runningUnits.shift() ) { + try { + await unit.down() + } catch (e) { + console.error('Error while stopping:') + console.error(e) + } + } + + this.state = 'stopped' + } +} diff --git a/src/config.ts b/src/config.ts index 3c02eea..ed5807b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,6 +3,7 @@ import {castCommentsConfig, type CommentsConfig} from "./types.ts"; const maybeConfig: any = { mail: { + refreshIntervalInSeconds: process.env.CHROUS_REFRESH_INTERVAL_IN_SECONDS || 60, imap: { host: process.env.CHORUS_IMAP_HOST, port: process.env.CHORUS_IMAP_PORT || 993, diff --git a/src/http/server.ts b/src/http/server.ts deleted file mode 100644 index bbcd84f..0000000 --- a/src/http/server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {config} from "../config.ts"; - -export async function runHttpServer() { - Bun.serve({ - hostname: config.http.address, - port: config.http.port, - fetch: async request => { - - }, - }) -} diff --git a/src/types.ts b/src/types.ts index 4bfabd7..6f658d4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,7 @@ import {z} from "zod"; const commentsConfigSchema = z.object({ mail: z.object({ + refreshIntervalInSeconds: z.number({ coerce: true }), imap: z.object({ host: z.string(), port: z.number({ coerce: true }), diff --git a/src/units/http-server.ts b/src/units/http-server.ts new file mode 100644 index 0000000..a84404a --- /dev/null +++ b/src/units/http-server.ts @@ -0,0 +1,34 @@ +import {Unit} from "../bones/Unit.ts"; +import type {Maybe} from "../bones"; +import {config} from "../config.ts"; + +export class HttpServer extends Unit { + private server: Maybe> + + async up(): Promise { + this.server = Bun.serve({ + hostname: config.http.address, + port: config.http.port, + fetch: async request => { + const path = `${config.dirs.data}${new URL(request.url).pathname}` + const file = Bun.file(path) + return new Response(file) + }, + error: () => new Response( + JSON.stringify({ success: false, message: 'Not found.' }), + { + status: 404, + headers: { + 'Content-Type': 'application/json', + }, + } + ) + }) + console.log(`Listening on ${config.http.address}:${config.http.port}`) + } + + async down(): Promise { + console.log('Stopping server...') + await this.server?.stop?.(true) + } +} diff --git a/src/units/threads.ts b/src/units/threads.ts new file mode 100644 index 0000000..af35b07 --- /dev/null +++ b/src/units/threads.ts @@ -0,0 +1,38 @@ +import {Unit} from "../bones/Unit.ts"; +import type {Maybe} from "../bones"; +import {undefined} from "zod"; +import {refreshThreadsEntirely} from "../threads/refresh.ts"; +import {config} from "../config.ts"; + +export class ThreadRefresher extends Unit { + private handle: Maybe + private running: boolean = false + + async up(): Promise { + this.handle = setInterval(async () => { + if ( this.running ) { + return; + } + + this.running = true + try { + console.log('Refreshing threads...') + await refreshThreadsEntirely() + } catch (e) { + this.running = false + throw e + } + + console.log('Done.') + this.running = false + }, config.mail.refreshIntervalInSeconds * 1000) + } + + async down(): Promise { + if ( this.handle ) { + console.log('Stopping refresh interval...') + clearInterval(this.handle) + console.log('Done.') + } + } +}