Owners and admins turn on a secret URL per project (Settings →
Integrations), POST /api/webhooks/alerts/{project}/{token}, that Sentry,
Uptime Kuma, Prometheus Alertmanager, Grafana or any tool sending JSON
with a title can call. A new problem opens a bug as TaskStream (label
«alerta», chosen type and priority); while that bug is open, the same
fingerprint goes to it (a comment at most every 30 minutes) and
«resolved» is said on it; once it is closed, a new bug names the old
one. Alerts of a project are handled one at a time, so two at once open
one bug, and a bug not yet projected still counts as open. Unknown
project, alerts off or a bad token all answer 403.
TaskStream may now comment as the system actor, like it already opened
tickets for public proposals. Stored in Mongo (alert_integrations,
alert_traces), not in the event store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectModel } from '@nestjs/mongoose';
|
|
import {
|
|
AlertIntegrationConfig,
|
|
AlertIntegrationsRepository,
|
|
AlertTrace,
|
|
} from '@task-stream/api/tracker/application';
|
|
import { Model } from 'mongoose';
|
|
import { ReadModels } from './read-model.schemas';
|
|
|
|
type Stored<T> = T & { _id?: unknown };
|
|
|
|
@Injectable()
|
|
export class MongoAlertIntegrationsRepository extends AlertIntegrationsRepository {
|
|
constructor(
|
|
@InjectModel(ReadModels.AlertIntegration)
|
|
private readonly configs: Model<Stored<AlertIntegrationConfig>>,
|
|
@InjectModel(ReadModels.AlertTrace)
|
|
private readonly traces: Model<Stored<AlertTrace>>,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
async find(projectId: string): Promise<AlertIntegrationConfig | null> {
|
|
const doc = await this.configs
|
|
.findOne({ projectId })
|
|
.lean<Stored<AlertIntegrationConfig>>();
|
|
if (!doc) return null;
|
|
const { _id: _ignored, ...config } = doc;
|
|
return config;
|
|
}
|
|
|
|
async save(config: AlertIntegrationConfig): Promise<void> {
|
|
await this.configs.updateOne(
|
|
{ projectId: config.projectId },
|
|
{ $set: config },
|
|
{ upsert: true },
|
|
);
|
|
}
|
|
|
|
async remove(projectId: string): Promise<void> {
|
|
// The traces stay: turned on again, repeats still find their bug.
|
|
await this.configs.deleteOne({ projectId });
|
|
}
|
|
|
|
async trace(
|
|
projectId: string,
|
|
fingerprint: string,
|
|
): Promise<AlertTrace | null> {
|
|
const doc = await this.traces
|
|
.findOne({ projectId, fingerprint })
|
|
.lean<Stored<AlertTrace>>();
|
|
if (!doc) return null;
|
|
const { _id: _ignored, ...trace } = doc;
|
|
return trace;
|
|
}
|
|
|
|
async saveTrace(trace: AlertTrace): Promise<void> {
|
|
await this.traces.updateOne(
|
|
{ projectId: trace.projectId, fingerprint: trace.fingerprint },
|
|
{ $set: trace },
|
|
{ upsert: true },
|
|
);
|
|
}
|
|
}
|