feat: «Mi día», the few tickets I pick for today

A home panel with the tickets picked for today (10 at most), crossed out
as they get done and counted («1 de 3 terminados»), my open tickets to
add with one click, and what was left unfinished on my last day to carry
over. «☀ Mi día» on a ticket adds it or takes it out. The day is the
person's own (YYYY-MM-DD from the browser); private, no history (Mongo
`my_days`).

API: myDay(day), addToMyDay, removeFromMyDay; GET /api/me/day,
PUT/DELETE /api/me/day/{issue}. MCP: my_day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:18:10 +02:00
parent b88296c22b
commit dfb5f973f4
31 changed files with 2183 additions and 50 deletions

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import {
MyDayRecord,
MyDaysRepository,
} from '@task-stream/api/tracker/application';
import { Model } from 'mongoose';
import { ReadModels } from './read-model.schemas';
type Stored = MyDayRecord & { _id?: unknown };
const strip = ({ _id: _ignored, ...record }: Stored): MyDayRecord => ({
...record,
// Mongo leaves an emptied list out.
issueIds: record.issueIds ?? [],
});
@Injectable()
export class MongoMyDaysRepository extends MyDaysRepository {
constructor(
@InjectModel(ReadModels.MyDay) private readonly days: Model<Stored>,
) {
super();
}
async find(userId: string, day: string): Promise<MyDayRecord | null> {
const doc = await this.days.findOne({ userId, day }).lean<Stored>();
return doc ? strip(doc) : null;
}
async latestBefore(userId: string, day: string): Promise<MyDayRecord | null> {
const doc = await this.days
.findOne({ userId, day: { $lt: day } })
.sort({ day: -1 })
.lean<Stored>();
return doc ? strip(doc) : null;
}
async save(record: MyDayRecord): Promise<void> {
await this.days.updateOne(
{ userId: record.userId, day: record.day },
{ $set: record },
{ upsert: true },
);
}
}

View File

@@ -111,6 +111,7 @@ export const ReadModels = {
AlertTrace: 'AlertTrace',
TreeFollow: 'TreeFollow',
IssueReminder: 'IssueReminder',
MyDay: 'MyDay',
Sprint: 'SprintReadModel',
GitIntegration: 'GitIntegration',
OutgoingWebhook: 'OutgoingWebhook',
@@ -385,6 +386,15 @@ export const IssueReminderSchema = SchemaFactory.createForClass(
IssueReminderSchema.index({ sentAt: 1, at: 1 });
IssueReminderSchema.index({ userId: 1, issueId: 1 });
@Schema({ ...LOOSE, collection: 'my_days' })
class MyDayDocument {
@Prop({ type: String }) userId!: string;
@Prop({ type: String }) day!: string;
@Prop({ type: [String] }) issueIds!: string[];
}
export const MyDaySchema = SchemaFactory.createForClass(MyDayDocument);
MyDaySchema.index({ userId: 1, day: -1 }, { unique: true });
@Schema({ ...LOOSE, collection: 'git_integrations' })
class GitIntegrationDocument {
/** The project's id. */
@@ -492,6 +502,7 @@ export const READ_MODEL_SCHEMAS = [
{ name: ReadModels.AlertTrace, schema: AlertTraceSchema },
{ name: ReadModels.TreeFollow, schema: TreeFollowSchema },
{ name: ReadModels.IssueReminder, schema: IssueReminderSchema },
{ name: ReadModels.MyDay, schema: MyDaySchema },
{ name: ReadModels.CalendarFeed, schema: CalendarFeedSchema },
{ name: ReadModels.DashboardShare, schema: DashboardShareSchema },
{ name: ReadModels.PushSubscription, schema: PushSubscriptionSchema },

View File

@@ -17,6 +17,7 @@ import {
AlertIntegrationsRepository,
TreeFollowsRepository,
IssueRemindersRepository,
MyDaysRepository,
GitIntegrationsRepository,
OutgoingWebhooksRepository,
AutomationsRepository,
@@ -86,6 +87,7 @@ import { MongoPinsRepository } from './mongo/pins.repository';
import { MongoAlertIntegrationsRepository } from './mongo/alert-integrations.repository';
import { MongoTreeFollowsRepository } from './mongo/tree-follows.repository';
import { MongoIssueRemindersRepository } from './mongo/issue-reminders.repository';
import { MongoMyDaysRepository } from './mongo/my-days.repository';
import { MongoGitIntegrationsRepository } from './mongo/git-integrations.repository';
import { MongoOutgoingWebhooksRepository } from './mongo/outgoing-webhooks.repository';
import { HttpWebhookSender } from './webhooks/http-webhook.sender';
@@ -172,6 +174,7 @@ const PORTS = [
provide: IssueRemindersRepository,
useClass: MongoIssueRemindersRepository,
},
{ provide: MyDaysRepository, useClass: MongoMyDaysRepository },
{
provide: GitIntegrationsRepository,
useClass: MongoGitIntegrationsRepository,