feat: «Recordármelo», a reminder of a ticket only for me
On a ticket, «Recordármelo» offers in an hour, this evening, tomorrow or
next Monday at 9, in a week, or any date and time, with an optional note.
The ticket shows the waiting one («Te lo recordaré el mié 21 a las 9:00»)
to change or drop. Every minute a scheduler tells the ones whose time
came as a REMINDER notification (bell, push, e-mail), once, and forgets
those whose ticket went to the trash or out of reach. One per person and
ticket, private, no history (Mongo `issue_reminders`).
API: setReminder / clearReminder, Issue.myReminder, myReminders;
PUT/DELETE /api/issues/{issue}/reminder, GET /api/me/reminders. MCP:
remind_me.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import {
|
||||
IssueReminder,
|
||||
IssueRemindersRepository,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
import { Model } from 'mongoose';
|
||||
import { ReadModels } from './read-model.schemas';
|
||||
|
||||
type Stored = IssueReminder & { _id?: unknown };
|
||||
|
||||
const strip = ({ _id: _ignored, ...reminder }: Stored): IssueReminder =>
|
||||
reminder;
|
||||
|
||||
@Injectable()
|
||||
export class MongoIssueRemindersRepository extends IssueRemindersRepository {
|
||||
constructor(
|
||||
@InjectModel(ReadModels.IssueReminder)
|
||||
private readonly reminders: Model<Stored>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async save(reminder: IssueReminder): Promise<void> {
|
||||
await this.remove(reminder.userId, reminder.issueId);
|
||||
await this.reminders.create(reminder);
|
||||
}
|
||||
|
||||
async waitingFor(
|
||||
userId: string,
|
||||
issueId: string,
|
||||
): Promise<IssueReminder | null> {
|
||||
const doc = await this.reminders
|
||||
.findOne({ userId, issueId, sentAt: null })
|
||||
.lean<Stored>();
|
||||
return doc ? strip(doc) : null;
|
||||
}
|
||||
|
||||
async waitingOf(userId: string): Promise<IssueReminder[]> {
|
||||
const docs = await this.reminders
|
||||
.find({ userId, sentAt: null })
|
||||
.sort({ at: 1 })
|
||||
.lean<Stored[]>();
|
||||
return docs.map(strip);
|
||||
}
|
||||
|
||||
async remove(userId: string, issueId: string): Promise<void> {
|
||||
await this.reminders.deleteMany({ userId, issueId, sentAt: null });
|
||||
}
|
||||
|
||||
async due(now: string, limit: number): Promise<IssueReminder[]> {
|
||||
const docs = await this.reminders
|
||||
.find({ sentAt: null, at: { $lte: now } })
|
||||
.sort({ at: 1 })
|
||||
.limit(limit)
|
||||
.lean<Stored[]>();
|
||||
return docs.map(strip);
|
||||
}
|
||||
|
||||
async markSent(id: string, at: string): Promise<void> {
|
||||
await this.reminders.updateOne({ id }, { $set: { sentAt: at } });
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ export const ReadModels = {
|
||||
AlertIntegration: 'AlertIntegration',
|
||||
AlertTrace: 'AlertTrace',
|
||||
TreeFollow: 'TreeFollow',
|
||||
IssueReminder: 'IssueReminder',
|
||||
Sprint: 'SprintReadModel',
|
||||
GitIntegration: 'GitIntegration',
|
||||
OutgoingWebhook: 'OutgoingWebhook',
|
||||
@@ -370,6 +371,20 @@ export const TreeFollowSchema =
|
||||
SchemaFactory.createForClass(TreeFollowDocument);
|
||||
TreeFollowSchema.index({ userId: 1, issueId: 1 }, { unique: true });
|
||||
|
||||
@Schema({ ...LOOSE, collection: 'issue_reminders' })
|
||||
class IssueReminderDocument {
|
||||
@Prop({ type: String, required: true, unique: true }) id!: string;
|
||||
@Prop({ type: String }) userId!: string;
|
||||
@Prop({ type: String }) issueId!: string;
|
||||
@Prop({ type: String }) at!: string;
|
||||
@Prop({ type: String, default: null }) sentAt!: string | null;
|
||||
}
|
||||
export const IssueReminderSchema = SchemaFactory.createForClass(
|
||||
IssueReminderDocument,
|
||||
);
|
||||
IssueReminderSchema.index({ sentAt: 1, at: 1 });
|
||||
IssueReminderSchema.index({ userId: 1, issueId: 1 });
|
||||
|
||||
@Schema({ ...LOOSE, collection: 'git_integrations' })
|
||||
class GitIntegrationDocument {
|
||||
/** The project's id. */
|
||||
@@ -476,6 +491,7 @@ export const READ_MODEL_SCHEMAS = [
|
||||
{ name: ReadModels.AlertIntegration, schema: AlertIntegrationSchema },
|
||||
{ name: ReadModels.AlertTrace, schema: AlertTraceSchema },
|
||||
{ name: ReadModels.TreeFollow, schema: TreeFollowSchema },
|
||||
{ name: ReadModels.IssueReminder, schema: IssueReminderSchema },
|
||||
{ name: ReadModels.CalendarFeed, schema: CalendarFeedSchema },
|
||||
{ name: ReadModels.DashboardShare, schema: DashboardShareSchema },
|
||||
{ name: ReadModels.PushSubscription, schema: PushSubscriptionSchema },
|
||||
|
||||
Reference in New Issue
Block a user