#!/usr/bin/env node import { Either, getRequiredEnvVars, LogTraceable, LogMetricTraceable, Metric, TraceUtil, } from '@emprespresso/pengueno'; import { Infisical, type SecureNote } from '@emprespresso/ci_worker'; import { CoolifyWebhookJob } from '@emprespresso/ci_model'; const eitherJob = getRequiredEnvVars(['webhookUrl']).mapRight( (baseArgs) => { type: 'coolify_webhook.js', arguments: baseArgs, }, ); const eitherVault = Infisical.getConfigFromEnvironment().mapRight((config) => new Infisical(config)); const webhookMetric = Metric.fromName('coolify_webhook.trigger').asResult(); const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('coolify_webhook')); await LogMetricTraceable.ofLogTraceable(_logJob) .flatMap(TraceUtil.withMetricTrace(webhookMetric)) .peek((tEitherJob) => tEitherJob.trace.trace('starting coolify webhook trigger! (⑅˘꒳˘)')) .map((tEitherJob) => tEitherJob.get().flatMapAsync(async (job) => { tEitherJob.trace.trace('checking vault configuration...'); return eitherVault.flatMapAsync(async (vault) => { tEitherJob.trace.trace('unlocking vault...'); const eitherKey = await vault.unlock(tEitherJob); eitherKey.fold( (err) => tEitherJob.trace.trace(`failed to unlock vault: ${err.message || err}`), () => tEitherJob.trace.trace('unlocked vault :3'), ); return eitherKey.mapRight((key) => ({ job, key, vault })); }); }), ) .map(async (tEitherJobVault) => { const eitherJobVault = await tEitherJobVault.get(); return eitherJobVault.flatMapAsync(async ({ job, key, vault }) => { tEitherJobVault.trace.trace('fetching coolify secret from vault...'); const eitherSecret = await vault.fetchSecret(tEitherJobVault, key, 'coolify'); await vault.lock(tEitherJobVault, key); eitherSecret.fold( (err: Error) => tEitherJobVault.trace.trace(`failed to fetch coolify secret: ${err.message || err}`), () => tEitherJobVault.trace.trace('successfully retrieved coolify webhook secret'), ); return eitherSecret.mapRight(({ notes }: SecureNote) => ({ job, webhookSecret: notes })); }); }) .map(async (tEitherJobAndSecret) => { const eitherJobAndSecret = await tEitherJobAndSecret.get(); return eitherJobAndSecret.flatMapAsync(async ({ job, webhookSecret }) => { tEitherJobAndSecret.trace.trace(`triggering webhook at ${job.arguments.webhookUrl} (◕ᴗ◕✿)`); return Either.fromFailableAsync(() => fetch(job.arguments.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${webhookSecret}`, }, body: JSON.stringify({}), }).then(async (response) => { if (!response.ok) { const errorText = await response.text(); tEitherJobAndSecret.trace.trace( `webhook failed with status ${response.status}: ${errorText}`, ); throw new Error(`webhook request failed with status ${response.status}: ${errorText}`); } return response.text(); }), ).then((e) => { e.fold( (err) => tEitherJobAndSecret.trace.trace(`webhook error: ${err.message || err}`), (responseText) => tEitherJobAndSecret.trace.trace(`webhook response: ${responseText}`), ); return e; }); }); }) .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(webhookMetric))) .peek( TraceUtil.promiseify((tEitherResult) => tEitherResult.get().fold( (err) => tEitherResult.trace.trace(`oh nyoo webhook failed :(( ${err.message || err}`), () => tEitherResult.trace.trace('webhook triggered successfully! (◕ᴗ◕✿)'), ), ), ) .map(async (tEitherJob) => { const eitherJob = await tEitherJob.get(); return eitherJob.fold( (e) => Promise.reject(e), () => Promise.resolve(0), ); }) .get();