- 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>
86 lines
2.3 KiB
TypeScript
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 },
|
|
},
|
|
);
|
|
}
|
|
}
|