Files
task-stream/libs/api/tracker/infrastructure/src/lib/mongo/automations.repository.ts
Josep Piguillem 2c501a50d0 feat: uuid ids everywhere, pickers for linked tickets, @pi/ui checkboxes
- Postgres: every table has a uuid `id` (gen_random_uuid()); the event
  store keeps its relay order in `seq`, and the lookup tables their
  uniqueness in constraints of their own.
- MongoDB: `_id` is Mongo's ObjectId; `id` is the domain id (the same
  uuid as the aggregate's stream in Postgres), unique, and every query
  goes by it. Production starts from empty databases.
- Web: linking tickets and hanging one from a parent are picked from a
  searchable select (links across projects, parents within the project)
  instead of typing a key; every checkbox is @pi/ui's pi-checkbox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:16:43 +02:00

86 lines
2.3 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import {
Automation,
AutomationsRepository,
} from '@task-stream/api/tracker/application';
import { Model } from 'mongoose';
import {
AutomationDoc,
AutomationRunDoc,
ReadModels,
fromDoc,
} from './read-model.schemas';
const DUPLICATE_KEY = 11000;
@Injectable()
export class MongoAutomationsRepository extends AutomationsRepository {
constructor(
@InjectModel(ReadModels.Automation)
private readonly automations: Model<AutomationDoc>,
@InjectModel(ReadModels.AutomationRun)
private readonly runs: Model<AutomationRunDoc>,
) {
super();
}
async findByProject(projectId: string): Promise<Automation[]> {
const docs = await this.automations
.find({ projectId })
.sort({ createdAt: 1 })
.lean<AutomationDoc[]>();
return docs.map((doc) => fromDoc(doc) as Automation);
}
async findActive(projectId: string): Promise<Automation[]> {
const docs = await this.automations
.find({ projectId, active: true })
.lean<AutomationDoc[]>();
return docs.map((doc) => fromDoc(doc) as Automation);
}
async findById(id: string): Promise<Automation | null> {
const doc = await this.automations
.findOne({ id: id })
.lean<AutomationDoc>();
return doc ? (fromDoc(doc) as Automation) : null;
}
async insert({ id, ...rest }: Automation): Promise<void> {
await this.automations.create({ id, ...rest });
}
async update(
id: string,
changes: Partial<Pick<Automation, 'name' | 'active'>>,
): Promise<void> {
if (Object.keys(changes).length)
await this.automations.updateOne({ id }, { $set: changes });
}
async delete(id: string): Promise<void> {
await this.automations.deleteOne({ id });
}
async claimRun(ruleId: string, eventKey: string): Promise<boolean> {
try {
await this.runs.create({ id: `${ruleId}:${eventKey}`, at: new Date() });
return true;
} catch (error) {
if ((error as { code?: number }).code === DUPLICATE_KEY) return false;
throw error;
}
}
async recordRun(id: string, error: string | null): Promise<void> {
await this.automations.updateOne(
{ id },
{
$inc: { runs: 1 },
$set: { lastRunAt: new Date().toISOString(), lastError: error },
},
);
}
}