feat: a calendar to subscribe to, and the backlog's corners

- Each person can subscribe their dates in Google Calendar, Outlook or
  Apple Calendar: GET /api/calendar/{token}.ics (no session) brings
  their unfinished tickets with a due date (assigned or followed), the
  planned and running sprints and the planned releases of their
  projects, as all-day events linking to the web. The token is made,
  renewed or dropped under /me/calendar (and in GraphQL); in the web,
  from «Calendario» in «Mi trabajo».
- The backlog panel keeps the selected rows' background inside its
  rounded corners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 22:48:33 +02:00
parent 993f80b4b9
commit f45069a327
38 changed files with 1417 additions and 54 deletions

View File

@@ -0,0 +1,32 @@
import { Mutation, Query, Resolver } from '@nestjs/graphql';
import { User } from '@task-stream/api/identity/domain';
import { CurrentUser } from '@task-stream/api/identity/presenters';
import { CalendarService } from '@task-stream/api/tracker/application';
import { CalendarFeedGql } from './types';
/** The signed-in person's calendar subscription (iCalendar URL). */
@Resolver(() => CalendarFeedGql)
export class CalendarResolver {
constructor(private readonly calendar: CalendarService) {}
@Query(() => CalendarFeedGql, {
nullable: true,
description: 'My calendar URL, if I made one',
})
myCalendarFeed(@CurrentUser() me: User) {
return this.calendar.mine(me.id);
}
@Mutation(() => CalendarFeedGql, {
description: 'A new secret calendar URL; the previous one stops working',
})
createCalendarFeed(@CurrentUser() me: User) {
return this.calendar.create(me.id);
}
@Mutation(() => Boolean, { description: 'My calendar URL stops working' })
async revokeCalendarFeed(@CurrentUser() me: User) {
await this.calendar.revoke(me.id);
return true;
}
}

View File

@@ -625,6 +625,16 @@ export class MyNotificationsGql {
unread!: number;
}
@ObjectType('CalendarFeed')
export class CalendarFeedGql {
@Field({
description:
'Secret of the calendar URL: GET /api/calendar/{token}.ics (iCalendar, no session)',
})
token!: string;
@Field() createdAt!: string;
}
@ObjectType('NotificationSettings')
export class NotificationSettingsGql {
@Field({ description: 'Copies by e-mail' }) email!: boolean;