- 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>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectModel } from '@nestjs/mongoose';
|
|
import { NotificationsRepository } from '@task-stream/api/tracker/application';
|
|
import {
|
|
NotificationReadModel,
|
|
NotificationSettingsReadModel,
|
|
} from '@task-stream/api/tracker/domain';
|
|
import { Model } from 'mongoose';
|
|
import {
|
|
fromDoc,
|
|
NotificationDoc,
|
|
NotificationSettingsDoc,
|
|
ReadModels,
|
|
} from './read-model.schemas';
|
|
|
|
@Injectable()
|
|
export class MongoNotificationsRepository extends NotificationsRepository {
|
|
constructor(
|
|
@InjectModel(ReadModels.Notification)
|
|
private readonly notifications: Model<NotificationDoc>,
|
|
@InjectModel(ReadModels.NotificationSettings)
|
|
private readonly settingsDocs: Model<NotificationSettingsDoc>,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
async insert({ id, ...rest }: NotificationReadModel): Promise<boolean> {
|
|
const result = await this.notifications.updateOne(
|
|
{ id },
|
|
{ $setOnInsert: rest },
|
|
{ upsert: true },
|
|
);
|
|
return result.upsertedCount > 0;
|
|
}
|
|
|
|
async findForUser(
|
|
userId: string,
|
|
options: { unreadOnly: boolean; limit: number },
|
|
): Promise<NotificationReadModel[]> {
|
|
const docs = await this.notifications
|
|
.find({ userId, ...(options.unreadOnly ? { readAt: null } : {}) })
|
|
.sort({ createdAt: -1 })
|
|
.limit(options.limit)
|
|
.lean<NotificationDoc[]>();
|
|
return docs.map((doc) => fromDoc(doc) as NotificationReadModel);
|
|
}
|
|
|
|
countUnread(userId: string): Promise<number> {
|
|
return this.notifications.countDocuments({ userId, readAt: null });
|
|
}
|
|
|
|
async markRead(userId: string, ids: string[] | null): Promise<void> {
|
|
await this.notifications.updateMany(
|
|
{ userId, readAt: null, ...(ids ? { id: { $in: ids } } : {}) },
|
|
{ $set: { readAt: new Date().toISOString() } },
|
|
);
|
|
}
|
|
|
|
async settings(userId: string): Promise<NotificationSettingsReadModel> {
|
|
const doc = await this.settingsDocs
|
|
.findOne({ id: userId })
|
|
.lean<NotificationSettingsDoc>();
|
|
return { userId, email: doc?.email ?? false };
|
|
}
|
|
|
|
async saveSettings(settings: NotificationSettingsReadModel): Promise<void> {
|
|
await this.settingsDocs.updateOne(
|
|
{ id: settings.userId },
|
|
{ $set: { email: settings.email } },
|
|
{ upsert: true },
|
|
);
|
|
}
|
|
}
|