From ec2acedf34d0e43559acfa31c6d11f01866354ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Tue, 7 Dec 2021 08:48:08 -0700 Subject: [PATCH] Zomars/refactor emails followup (#1216) --- .env.example | 2 +- @types/ical.d.ts | 257 ++++++++++++++++++ lib/calendarClient.ts | 10 +- .../Apple/AppleCalendarAdapter.ts | 125 ++++----- .../CalDav/CalDavCalendarAdapter.ts | 117 ++++---- .../GoogleCalendarApiAdapter.ts | 25 +- package.json | 10 +- playwright/integrations.test.ts | 14 +- tsconfig.json | 1 + yarn.lock | 22 +- 10 files changed, 417 insertions(+), 166 deletions(-) create mode 100644 @types/ical.d.ts diff --git a/.env.example b/.env.example index 42c804a84e..64c3ac32cb 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ NEXT_PUBLIC_LICENSE_CONSENT='' # DATABASE_URL='postgresql://:@:/' -DATABASE_URL="postgresql://postgres:@localhost:5432/calendso?schema=public" +DATABASE_URL="postgresql://postgres:@localhost:5450/calendso" GOOGLE_API_CREDENTIALS='secret' diff --git a/@types/ical.d.ts b/@types/ical.d.ts new file mode 100644 index 0000000000..b699d05405 --- /dev/null +++ b/@types/ical.d.ts @@ -0,0 +1,257 @@ +// SPDX-FileCopyrightText: © 2019 EteSync Authors +// SPDX-License-Identifier: GPL-3.0-only +// https://github.com/mozilla-comm/ical.js/issues/367#issuecomment-568493517 +declare module "ical.js" { + function parse(input: string): any[]; + + export class helpers { + public updateTimezones(vcal: Component): Component; + } + + class Component { + public fromString(str: string): Component; + + public name: string; + + constructor(jCal: any[] | string, parent?: Component); + + public toJSON(): any[]; + + public getFirstSubcomponent(name?: string): Component | null; + public getAllSubcomponents(name?: string): Component[]; + + public getFirstPropertyValue(name?: string): T; + + public getFirstProperty(name?: string): Property; + public getAllProperties(name?: string): Property[]; + + public addProperty(property: Property): Property; + public addPropertyWithValue(name: string, value: string | number | Record): Property; + + public hasProperty(name?: string): boolean; + + public updatePropertyWithValue(name: string, value: string | number | Record): Property; + + public removeAllProperties(name?: string): boolean; + + public addSubcomponent(component: Component): Component; + } + + export class Event { + public uid: string; + public summary: string; + public startDate: Time; + public endDate: Time; + public description: string; + public location: string; + public attendees: Property[]; + /** + * The sequence value for this event. Used for scheduling. + * + * @type {number} + * @memberof Event + */ + public sequence: number; + /** + * The duration. This can be the result directly from the property, or the + * duration calculated from start date and end date. Setting the property + * will remove any `dtend` properties. + * + * @type {Duration} + * @memberof Event + */ + public duration: Duration; + /** + * The organizer value as an uri. In most cases this is a mailto: uri, + * but it can also be something else, like urn:uuid:... + */ + public organizer: string; + /** The sequence value for this event. Used for scheduling */ + public sequence: number; + /** The recurrence id for this event */ + public recurrenceId: Time; + + public component: Component; + + public constructor( + component?: Component | null, + options?: { strictExceptions: boolean; exepctions: Array } + ); + + public isRecurring(): boolean; + public iterator(startTime?: Time): RecurExpansion; + } + + export class Property { + public name: string; + public type: string; + + constructor(jCal: any[] | string, parent?: Component); + + public getFirstValue(): T; + public getValues(): T[]; + + public setParameter(name: string, value: string | string[]): void; + public setValue(value: string | Record): void; + public setValues(values: (string | Record)[]): void; + public toJSON(): any; + } + + interface TimeJsonData { + year?: number; + month?: number; + day?: number; + hour?: number; + minute?: number; + second?: number; + isDate?: boolean; + } + + export class Time { + public fromString(str: string): Time; + public fromJSDate(aDate: Date | null, useUTC: boolean): Time; + public fromData(aData: TimeJsonData): Time; + + public now(): Time; + + public isDate: boolean; + public timezone: string; + public zone: Timezone; + + public year: number; + public month: number; + public day: number; + public hour: number; + public minute: number; + public second: number; + + constructor(data?: TimeJsonData); + public compare(aOther: Time): number; + + public clone(): Time; + public convertToZone(zone: Timezone): Time; + + public adjust( + aExtraDays: number, + aExtraHours: number, + aExtraMinutes: number, + aExtraSeconds: number, + aTimeopt?: Time + ): void; + + public addDuration(aDuration: Duration): void; + public subtractDateTz(aDate: Time): Duration; + + public toUnixTime(): number; + public toJSDate(): Date; + public toJSON(): TimeJsonData; + public get icaltype(): "date" | "date-time"; + } + + export class Duration { + public weeks: number; + public days: number; + public hours: number; + public minutes: number; + public seconds: number; + public isNegative: boolean; + public icalclass: string; + public icaltype: string; + } + + export class RecurExpansion { + public complete: boolean; + public dtstart: Time; + public last: Time; + public next(): Time; + public fromData(options); + public toJSON(); + constructor(options: { + /** Start time of the event */ + dtstart: Time; + /** Component for expansion, required if not resuming. */ + component?: Component; + }); + } + + export class Timezone { + public utcTimezone: Timezone; + public localTimezone: Timezone; + public convert_time(tt: Time, fromZone: Timezone, toZone: Timezone): Time; + + public tzid: string; + public component: Component; + + constructor( + data: + | Component + | { + component: string | Component; + tzid?: string; + location?: string; + tznames?: string; + latitude?: number; + longitude?: number; + } + ); + } + + export class TimezoneService { + public get(tzid: string): Timezone | null; + public has(tzid: string): boolean; + public register(tzid: string, zone: Timezone | Component); + public remove(tzid: string): Timezone | null; + } + + export type FrequencyValues = + | "YEARLY" + | "MONTHLY" + | "WEEKLY" + | "DAILY" + | "HOURLY" + | "MINUTELY" + | "SECONDLY"; + + export enum WeekDay { + SU = 1, + MO, + TU, + WE, + TH, + FR, + SA, + } + + export class RecurData { + public freq?: FrequencyValues; + public interval?: number; + public wkst?: WeekDay; + public until?: Time; + public count?: number; + public bysecond?: number[] | number; + public byminute?: number[] | number; + public byhour?: number[] | number; + public byday?: string[] | string; + public bymonthday?: number[] | number; + public byyearday?: number[] | number; + public byweekno?: number[] | number; + public bymonth?: number[] | number; + public bysetpos?: number[] | number; + } + + export class RecurIterator { + public next(): Time; + } + + export class Recur { + constructor(data?: RecurData); + public until: Time | null; + public freq: FrequencyValues; + public count: number | null; + + public clone(): Recur; + public toJSON(): Omit & { until?: string }; + public iterator(startTime?: Time): RecurIterator; + public isByCount(): boolean; + } +} diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts index 999b208784..ee6841f624 100644 --- a/lib/calendarClient.ts +++ b/lib/calendarClient.ts @@ -19,6 +19,8 @@ import { import logger from "@lib/logger"; import { VideoCallData } from "@lib/videoClient"; +import { Ensure } from "./types/utils"; + const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] }); export type Person = { name: string; email: string; timeZone: string }; @@ -61,7 +63,7 @@ export interface CalendarEvent { paymentInfo?: PaymentInfo | null; } -export interface IntegrationCalendar extends Partial { +export interface IntegrationCalendar extends Ensure, "externalId"> { primary?: boolean; name?: string; } @@ -89,11 +91,9 @@ function getCalendarAdapterOrNull(credential: Credential): CalendarApiAdapter | case "office365_calendar": return Office365CalendarApiAdapter(credential); case "caldav_calendar": - // FIXME types wrong & type casting should not be needed - return new CalDavCalendar(credential) as never as CalendarApiAdapter; + return new CalDavCalendar(credential); case "apple_calendar": - // FIXME types wrong & type casting should not be needed - return new AppleCalendar(credential) as never as CalendarApiAdapter; + return new AppleCalendar(credential); } return null; } diff --git a/lib/integrations/Apple/AppleCalendarAdapter.ts b/lib/integrations/Apple/AppleCalendarAdapter.ts index a2c3244755..f23187f634 100644 --- a/lib/integrations/Apple/AppleCalendarAdapter.ts +++ b/lib/integrations/Apple/AppleCalendarAdapter.ts @@ -24,8 +24,6 @@ dayjs.extend(utc); const log = logger.getChildLogger({ prefix: ["[[lib] apple calendar"] }); -type EventBusyDate = Record<"start" | "end", Date>; - export class AppleCalendar implements CalendarApiAdapter { private url: string; private credentials: Record; @@ -34,7 +32,7 @@ export class AppleCalendar implements CalendarApiAdapter { constructor(credential: Credential) { const decryptedCredential = JSON.parse( - symmetricDecrypt(credential.key, process.env.CALENDSO_ENCRYPTION_KEY) + symmetricDecrypt(credential.key as string, process.env.CALENDSO_ENCRYPTION_KEY!) ); const username = decryptedCredential.username; const password = decryptedCredential.password; @@ -52,12 +50,12 @@ export class AppleCalendar implements CalendarApiAdapter { }); } - convertDate(date: string): number[] { + convertDate(date: string): [number, number, number] { return dayjs(date) .utc() .toArray() .slice(0, 6) - .map((v, i) => (i === 1 ? v + 1 : v)); + .map((v, i) => (i === 1 ? v + 1 : v)) as [number, number, number]; } getDuration(start: string, end: string): DurationObject { @@ -70,11 +68,11 @@ export class AppleCalendar implements CalendarApiAdapter { return attendees.map(({ email, name }) => ({ name, email, partstat: "NEEDS-ACTION" })); } - async createEvent(event: CalendarEvent): Promise> { + async createEvent(event: CalendarEvent) { try { const calendars = await this.listCalendars(); const uid = uuidv4(); - const { error, value: iCalString } = await createEvent({ + const { error, value: iCalString } = createEvent({ uid, startInputType: "utc", start: this.convertDate(event.startTime), @@ -86,15 +84,9 @@ export class AppleCalendar implements CalendarApiAdapter { attendees: this.getAttendees(event.attendees), }); - if (error) { - log.debug("Error creating iCalString"); - return {}; - } + if (error) throw new Error("Error creating iCalString"); - if (!iCalString) { - log.debug("Error creating iCalString"); - return {}; - } + if (!iCalString) throw new Error("Error creating iCalString"); await Promise.all( calendars.map((calendar) => { @@ -112,6 +104,9 @@ export class AppleCalendar implements CalendarApiAdapter { return { uid, id: uid, + type: "apple_calendar", + password: "", + url: "", }; } catch (reason) { console.error(reason); @@ -132,7 +127,7 @@ export class AppleCalendar implements CalendarApiAdapter { } } - const { error, value: iCalString } = await createEvent({ + const { error, value: iCalString } = createEvent({ uid, startInputType: "utc", start: this.convertDate(event.startTime), @@ -201,11 +196,7 @@ export class AppleCalendar implements CalendarApiAdapter { } } - async getAvailability( - dateFrom: string, - dateTo: string, - selectedCalendars: IntegrationCalendar[] - ): Promise { + async getAvailability(dateFrom: string, dateTo: string, selectedCalendars: IntegrationCalendar[]) { try { const selectedCalendarIds = selectedCalendars .filter((e) => e.integration === this.integrationName) @@ -229,8 +220,8 @@ export class AppleCalendar implements CalendarApiAdapter { ids.map(async (calId) => { return (await this.getEvents(calId, dateFrom, dateTo)).map((event) => { return { - start: event.startDate, - end: event.endDate, + start: event.startDate.toISOString(), + end: event.endDate.toISOString(), }; }); }) @@ -272,8 +263,8 @@ export class AppleCalendar implements CalendarApiAdapter { calId: string, dateFrom: string | null, dateTo: string | null, - objectUrls: string[] | null - ): Promise { + objectUrls?: string[] | null + ) { try { const objects = await fetchCalendarObjects({ calendar: { @@ -290,54 +281,48 @@ export class AppleCalendar implements CalendarApiAdapter { headers: this.headers, }); - const events = - objects && - objects?.length > 0 && - objects - .map((object) => { - if (object?.data) { - const jcalData = ICAL.parse(object.data); - const vcalendar = new ICAL.Component(jcalData); - const vevent = vcalendar.getFirstSubcomponent("vevent"); - const event = new ICAL.Event(vevent); + const events = objects + .filter((e) => !!e.data) + .map((object) => { + const jcalData = ICAL.parse(object.data); + const vcalendar = new ICAL.Component(jcalData); + const vevent = vcalendar.getFirstSubcomponent("vevent"); + const event = new ICAL.Event(vevent); - const calendarTimezone = vcalendar.getFirstSubcomponent("vtimezone") - ? vcalendar.getFirstSubcomponent("vtimezone").getFirstPropertyValue("tzid") - : ""; + const calendarTimezone = + vcalendar.getFirstSubcomponent("vtimezone")?.getFirstPropertyValue("tzid") || ""; - const startDate = calendarTimezone - ? dayjs(event.startDate).tz(calendarTimezone) - : new Date(event.startDate.toUnixTime() * 1000); - const endDate = calendarTimezone - ? dayjs(event.endDate).tz(calendarTimezone) - : new Date(event.endDate.toUnixTime() * 1000); + const startDate = calendarTimezone + ? dayjs(event.startDate.toJSDate()).tz(calendarTimezone) + : new Date(event.startDate.toUnixTime() * 1000); + const endDate = calendarTimezone + ? dayjs(event.endDate.toJSDate()).tz(calendarTimezone) + : new Date(event.endDate.toUnixTime() * 1000); - return { - uid: event.uid, - etag: object.etag, - url: object.url, - summary: event.summary, - description: event.description, - location: event.location, - sequence: event.sequence, - startDate, - endDate, - duration: { - weeks: event.duration.weeks, - days: event.duration.days, - hours: event.duration.hours, - minutes: event.duration.minutes, - seconds: event.duration.seconds, - isNegative: event.duration.isNegative, - }, - organizer: event.organizer, - attendees: event.attendees.map((a) => a.getValues()), - recurrenceId: event.recurrenceId, - timezone: calendarTimezone, - }; - } - }) - .filter((e) => e != null); + return { + uid: event.uid, + etag: object.etag, + url: object.url, + summary: event.summary, + description: event.description, + location: event.location, + sequence: event.sequence, + startDate, + endDate, + duration: { + weeks: event.duration.weeks, + days: event.duration.days, + hours: event.duration.hours, + minutes: event.duration.minutes, + seconds: event.duration.seconds, + isNegative: event.duration.isNegative, + }, + organizer: event.organizer, + attendees: event.attendees.map((a) => a.getValues()), + recurrenceId: event.recurrenceId, + timezone: calendarTimezone, + }; + }); return events; } catch (reason) { diff --git a/lib/integrations/CalDav/CalDavCalendarAdapter.ts b/lib/integrations/CalDav/CalDavCalendarAdapter.ts index 511c9cb0dc..dc3e02ac93 100644 --- a/lib/integrations/CalDav/CalDavCalendarAdapter.ts +++ b/lib/integrations/CalDav/CalDavCalendarAdapter.ts @@ -24,8 +24,6 @@ dayjs.extend(utc); const log = logger.getChildLogger({ prefix: ["[lib] caldav"] }); -type EventBusyDate = Record<"start" | "end", Date>; - export class CalDavCalendar implements CalendarApiAdapter { private url: string; private credentials: Record; @@ -34,7 +32,7 @@ export class CalDavCalendar implements CalendarApiAdapter { constructor(credential: Credential) { const decryptedCredential = JSON.parse( - symmetricDecrypt(credential.key, process.env.CALENDSO_ENCRYPTION_KEY) + symmetricDecrypt(credential.key as string, process.env.CALENDSO_ENCRYPTION_KEY!) ); const username = decryptedCredential.username; const url = decryptedCredential.url; @@ -53,12 +51,12 @@ export class CalDavCalendar implements CalendarApiAdapter { }); } - convertDate(date: string): number[] { + convertDate(date: string): [number, number, number] { return dayjs(date) .utc() .toArray() .slice(0, 6) - .map((v, i) => (i === 1 ? v + 1 : v)); + .map((v, i) => (i === 1 ? v + 1 : v)) as [number, number, number]; } getDuration(start: string, end: string): DurationObject { @@ -71,15 +69,14 @@ export class CalDavCalendar implements CalendarApiAdapter { return attendees.map(({ email, name }) => ({ name, email, partstat: "NEEDS-ACTION" })); } - async createEvent(event: CalendarEvent): Promise> { + async createEvent(event: CalendarEvent) { try { const calendars = await this.listCalendars(); const uid = uuidv4(); - const { error, value: iCalString } = await createEvent({ + const { error, value: iCalString } = createEvent({ uid, startInputType: "utc", - // FIXME types start: this.convertDate(event.startTime), duration: this.getDuration(event.startTime, event.endTime), title: event.title, @@ -89,15 +86,9 @@ export class CalDavCalendar implements CalendarApiAdapter { attendees: this.getAttendees(event.attendees), }); - if (error) { - log.debug("Error creating iCalString"); - return {}; - } + if (error) throw new Error("Error creating iCalString"); - if (!iCalString) { - log.debug("Error creating iCalString"); - return {}; - } + if (!iCalString) throw new Error("Error creating iCalString"); await Promise.all( calendars.map((calendar) => { @@ -115,6 +106,9 @@ export class CalDavCalendar implements CalendarApiAdapter { return { uid, id: uid, + type: "caldav_calendar", + password: "", + url: "", }; } catch (reason) { log.error(reason); @@ -138,7 +132,6 @@ export class CalDavCalendar implements CalendarApiAdapter { const { error, value: iCalString } = await createEvent({ uid, startInputType: "utc", - // FIXME - types wrong start: this.convertDate(event.startTime), duration: this.getDuration(event.startTime, event.endTime), title: event.title, @@ -205,12 +198,7 @@ export class CalDavCalendar implements CalendarApiAdapter { } } - // FIXME - types wrong - async getAvailability( - dateFrom: string, - dateTo: string, - selectedCalendars: IntegrationCalendar[] - ): Promise { + async getAvailability(dateFrom: string, dateTo: string, selectedCalendars: IntegrationCalendar[]) { try { const selectedCalendarIds = selectedCalendars .filter((e) => e.integration === this.integrationName) @@ -235,8 +223,8 @@ export class CalDavCalendar implements CalendarApiAdapter { ids.map(async (calId) => { return (await this.getEvents(calId, dateFrom, dateTo)).map((event) => { return { - start: event.startDate, - end: event.endDate, + start: event.startDate.toISOString(), + end: event.endDate.toISOString(), }; }); }) @@ -274,7 +262,7 @@ export class CalDavCalendar implements CalendarApiAdapter { } } - async getEvents(calId: string, dateFrom: string | null, dateTo: string | null): Promise { + async getEvents(calId: string, dateFrom: string | null, dateTo: string | null) { try { const objects = await fetchCalendarObjects({ calendar: { @@ -295,50 +283,47 @@ export class CalDavCalendar implements CalendarApiAdapter { } const events = objects + .filter((e) => !!e.data) .map((object) => { - if (object?.data) { - const jcalData = ICAL.parse(object.data); - const vcalendar = new ICAL.Component(jcalData); - const vevent = vcalendar.getFirstSubcomponent("vevent"); - const event = new ICAL.Event(vevent); + const jcalData = ICAL.parse(object.data); + const vcalendar = new ICAL.Component(jcalData); + const vevent = vcalendar.getFirstSubcomponent("vevent"); + const event = new ICAL.Event(vevent); - const calendarTimezone = vcalendar.getFirstSubcomponent("vtimezone") - ? vcalendar.getFirstSubcomponent("vtimezone").getFirstPropertyValue("tzid") - : ""; + const calendarTimezone = + vcalendar.getFirstSubcomponent("vtimezone")?.getFirstPropertyValue("tzid") || ""; - const startDate = calendarTimezone - ? dayjs(event.startDate).tz(calendarTimezone) - : new Date(event.startDate.toUnixTime() * 1000); - const endDate = calendarTimezone - ? dayjs(event.endDate).tz(calendarTimezone) - : new Date(event.endDate.toUnixTime() * 1000); + const startDate = calendarTimezone + ? dayjs(event.startDate.toJSDate()).tz(calendarTimezone) + : new Date(event.startDate.toUnixTime() * 1000); + const endDate = calendarTimezone + ? dayjs(event.endDate.toJSDate()).tz(calendarTimezone) + : new Date(event.endDate.toUnixTime() * 1000); - return { - uid: event.uid, - etag: object.etag, - url: object.url, - summary: event.summary, - description: event.description, - location: event.location, - sequence: event.sequence, - startDate, - endDate, - duration: { - weeks: event.duration.weeks, - days: event.duration.days, - hours: event.duration.hours, - minutes: event.duration.minutes, - seconds: event.duration.seconds, - isNegative: event.duration.isNegative, - }, - organizer: event.organizer, - attendees: event.attendees.map((a) => a.getValues()), - recurrenceId: event.recurrenceId, - timezone: calendarTimezone, - }; - } - }) - .filter((e) => e != null); + return { + uid: event.uid, + etag: object.etag, + url: object.url, + summary: event.summary, + description: event.description, + location: event.location, + sequence: event.sequence, + startDate, + endDate, + duration: { + weeks: event.duration.weeks, + days: event.duration.days, + hours: event.duration.hours, + minutes: event.duration.minutes, + seconds: event.duration.seconds, + isNegative: event.duration.isNegative, + }, + organizer: event.organizer, + attendees: event.attendees.map((a) => a.getValues()), + recurrenceId: event.recurrenceId, + timezone: calendarTimezone, + }; + }); return events; } catch (reason) { diff --git a/lib/integrations/GoogleCalendar/GoogleCalendarApiAdapter.ts b/lib/integrations/GoogleCalendar/GoogleCalendarApiAdapter.ts index 306b595a7f..b9395bd803 100644 --- a/lib/integrations/GoogleCalendar/GoogleCalendarApiAdapter.ts +++ b/lib/integrations/GoogleCalendar/GoogleCalendarApiAdapter.ts @@ -7,7 +7,7 @@ import { CalendarApiAdapter, CalendarEvent, IntegrationCalendar } from "@lib/cal import prisma from "@lib/prisma"; export interface ConferenceData { - createRequest: calendar_v3.Schema$CreateConferenceRequest; + createRequest?: calendar_v3.Schema$CreateConferenceRequest; } const googleAuth = (credential: Credential) => { @@ -91,7 +91,19 @@ export const GoogleCalendarApiAdapter = (credential: Credential): CalendarApiAda if (err) { reject(err); } - resolve(Object.values(apires.data.calendars).flatMap((item) => item["busy"])); + let result: Prisma.PromiseReturnType = []; + if (apires?.data.calendars) { + result = Object.values(apires.data.calendars).reduce((c, i) => { + i.busy?.forEach((busyTime) => { + c.push({ + start: busyTime.start || "", + end: busyTime.end || "", + }); + }); + return c; + }, [] as typeof result); + } + resolve(result); } ); }) @@ -146,7 +158,14 @@ export const GoogleCalendarApiAdapter = (credential: Credential): CalendarApiAda console.error("There was an error contacting google calendar service: ", err); return reject(err); } - return resolve(event.data); + return resolve({ + ...event.data, + id: event.data.id || "", + hangoutLink: event.data.hangoutLink || "", + type: "google_calendar", + password: "", + url: "", + }); } ); }) diff --git a/package.json b/package.json index c0079dafd2..66586f3f0a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "db-migrate": "yarn prisma migrate dev", "db-seed": "yarn ts-node scripts/seed.ts", "db-nuke": "docker-compose down --volumes --remove-orphans", - "dx": "cross-env BASE_URL=http://localhost:3000 JWT_SECRET=secret DATABASE_URL=postgresql://postgres:@localhost:5450/calendso run-s db-up db-migrate db-seed dev", + "db-setup": "run-s db-up db-migrate db-seed", + "db-reset": "run-s db-nuke db-setup", + "dx": "env-cmd run-s db-setup dev", "test": "jest", "test-playwright": "jest --config jest.playwright.config.js", "test-codegen": "yarn playwright codegen http://localhost:3000", @@ -91,8 +93,8 @@ "react-use-intercom": "1.4.0", "short-uuid": "^4.2.0", "stripe": "^8.191.0", - "tsdav": "^1.1.5", "superjson": "1.8.0", + "tsdav": "^1.1.5", "tslog": "^3.2.1", "uuid": "^8.3.2", "zod": "^3.8.2" @@ -117,7 +119,7 @@ "@typescript-eslint/parser": "^4.33.0", "autoprefixer": "^10.3.1", "babel-jest": "^27.3.1", - "cross-env": "^7.0.3", + "env-cmd": "10.1.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", @@ -149,4 +151,4 @@ "prisma format" ] } -} \ No newline at end of file +} diff --git a/playwright/integrations.test.ts b/playwright/integrations.test.ts index 8ea3ffec33..2bca930d58 100644 --- a/playwright/integrations.test.ts +++ b/playwright/integrations.test.ts @@ -1,4 +1,3 @@ -import dayjs from "dayjs"; import { kont } from "kont"; import { loginProvider } from "./lib/loginProvider"; @@ -35,14 +34,11 @@ describe("webhooks", () => { // page contains the url await expect(page).toHaveSelector(`text='${webhookReceiver.url}'`); - // --- go to tomorrow in the pro user's "30min"-event - const tomorrow = dayjs().add(1, "day"); - const tomorrowFormatted = tomorrow.format("YYYY-MM-DDZZ"); - - await page.goto(`http://localhost:3000/pro/30min?date=${encodeURIComponent(tomorrowFormatted)}`); - - // click first time available - await page.click("[data-testid=time]"); + // --- Book the first available day next month in the pro user's "30min"-event + await page.goto(`http://localhost:3000/pro/30min`); + await page.click('[data-testid="incrementMonth"]'); + await page.click('[data-testid="day"]'); + await page.click('[data-testid="time"]'); // --- fill form await page.fill('[name="name"]', "Test Testson"); diff --git a/tsconfig.json b/tsconfig.json index 84a7cd0884..4a5418130e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -43,6 +43,7 @@ }, "include": [ "next-env.d.ts", + "@types/*.d.ts", "**/*.ts", "**/*.tsx" ], diff --git a/yarn.lock b/yarn.lock index 20e594756d..0e2607b9d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3152,6 +3152,11 @@ commander@^3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + commander@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" @@ -3273,13 +3278,6 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - cross-fetch@3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" @@ -3298,7 +3296,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3702,6 +3700,14 @@ enquirer@^2.3.5, enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" +env-cmd@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-10.1.0.tgz#c7f5d3b550c9519f137fdac4dd8fb6866a8c8c4b" + integrity sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA== + dependencies: + commander "^4.0.0" + cross-spawn "^7.0.0" + errno@^0.1.1, errno@~0.1.1: version "0.1.8" resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"