diff --git a/api/_shared/rate-limit.ts b/api/_shared/rate-limit.ts index f1ace30..85c03a5 100644 --- a/api/_shared/rate-limit.ts +++ b/api/_shared/rate-limit.ts @@ -1,6 +1,6 @@ import type { IncomingHttpHeaders } from 'http'; -import type { ApiResponse } from './http-types'; +import type { ApiResponse } from './http-types.js'; import recaptcha from './recaptcha.js'; const { extractClientIp } = recaptcha; diff --git a/api/booking/_lib/d1.ts b/api/booking/_lib/d1.ts index 4b137d8..0cdd9db 100644 --- a/api/booking/_lib/d1.ts +++ b/api/booking/_lib/d1.ts @@ -1,4 +1,4 @@ -import { getRuntimeEnv } from './config'; +import { getRuntimeEnv } from './config.js'; // We're on Vercel, not on Cloudflare, so we hit D1's HTTP query API rather // than binding the database. The API accepts a single SQL statement plus an diff --git a/api/booking/_lib/google-calendar.ts b/api/booking/_lib/google-calendar.ts index 4f4d653..5037bcd 100644 --- a/api/booking/_lib/google-calendar.ts +++ b/api/booking/_lib/google-calendar.ts @@ -1,4 +1,4 @@ -import { getRuntimeEnv } from './config'; +import { getRuntimeEnv } from './config.js'; // Thin wrapper around the Google Calendar v3 REST API. We avoid pulling the // `googleapis` package (heavy, slow cold-start) and stay on direct `fetch` diff --git a/api/booking/_lib/mailer.ts b/api/booking/_lib/mailer.ts index d700d7f..a1742d9 100644 --- a/api/booking/_lib/mailer.ts +++ b/api/booking/_lib/mailer.ts @@ -5,7 +5,7 @@ import { MAIL_FROM, NOTIFICATION_EMAIL, getRuntimeEnv, -} from './config'; +} from './config.js'; // Render-side helpers are kept lean and inline: no template engine, no // per-locale dispatch table, just two functions per email kind (subject + html) diff --git a/api/booking/_lib/slots.test.ts b/api/booking/_lib/slots.test.ts index 00dd606..9873088 100644 --- a/api/booking/_lib/slots.test.ts +++ b/api/booking/_lib/slots.test.ts @@ -4,7 +4,7 @@ import { buildAvailableSlots, isSlotValid, type BusyInterval, -} from './slots'; +} from './slots.js'; // All test inputs are anchored in UTC. The slots module renders them against // Europe/Zurich (UTC+1 in winter, UTC+2 in summer), so the expected wall-clock diff --git a/api/booking/_lib/slots.ts b/api/booking/_lib/slots.ts index 2b5a526..a707c95 100644 --- a/api/booking/_lib/slots.ts +++ b/api/booking/_lib/slots.ts @@ -6,7 +6,7 @@ import { MEETING_DURATION_MIN, MIN_NOTICE_MS, SLOT_GRANULARITY_MIN, -} from './config'; +} from './config.js'; // All timestamps in this module are UTC. The "9-18 Europe/Zurich" window is // resolved per-day using Intl.DateTimeFormat in BOOKING_TIMEZONE because that diff --git a/api/booking/_lib/tokens.ts b/api/booking/_lib/tokens.ts index 5d0c311..216c2d3 100644 --- a/api/booking/_lib/tokens.ts +++ b/api/booking/_lib/tokens.ts @@ -1,6 +1,6 @@ import { createHmac, randomUUID, timingSafeEqual } from 'crypto'; -import { getRuntimeEnv } from './config'; +import { getRuntimeEnv } from './config.js'; // Cancellation / reschedule links are stateless-ish: each booking gets a token // derived from `HMAC(secret, bookingId + ":" + purpose + ":" + exp + ":" + version)`. diff --git a/api/booking/availability.ts b/api/booking/availability.ts index 8c95ecb..38a535c 100644 --- a/api/booking/availability.ts +++ b/api/booking/availability.ts @@ -1,13 +1,13 @@ -import type { ApiRequest, ApiResponse } from '../_shared/http-types'; +import type { ApiRequest, ApiResponse } from '../_shared/http-types.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from '../_shared/rate-limit'; +} from '../_shared/rate-limit.js'; -import { getBusyIntervals } from './_lib/google-calendar'; -import { buildAvailableSlots } from './_lib/slots'; -import { parseAvailabilityRange } from './_lib/validators'; +import { getBusyIntervals } from './_lib/google-calendar.js'; +import { buildAvailableSlots } from './_lib/slots.js'; +import { parseAvailabilityRange } from './_lib/validators.js'; const AVAILABILITY_RATE_LIMIT = { limit: 60, diff --git a/api/booking/cancel.ts b/api/booking/cancel.ts index baadeff..b97a1f5 100644 --- a/api/booking/cancel.ts +++ b/api/booking/cancel.ts @@ -1,15 +1,15 @@ -import type { ApiRequest, ApiResponse } from '../_shared/http-types'; +import type { ApiRequest, ApiResponse } from '../_shared/http-types.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from '../_shared/rate-limit'; +} from '../_shared/rate-limit.js'; -import { getRuntimeEnv } from './_lib/config'; -import { exec, queryFirst } from './_lib/d1'; -import { deleteEvent } from './_lib/google-calendar'; -import { sendBookingCancellation } from './_lib/mailer'; -import { verifyToken } from './_lib/tokens'; +import { getRuntimeEnv } from './_lib/config.js'; +import { exec, queryFirst } from './_lib/d1.js'; +import { deleteEvent } from './_lib/google-calendar.js'; +import { sendBookingCancellation } from './_lib/mailer.js'; +import { verifyToken } from './_lib/tokens.js'; const CANCEL_RATE_LIMIT = { limit: 10, diff --git a/api/booking/create.ts b/api/booking/create.ts index dc7979c..a97fcc2 100644 --- a/api/booking/create.ts +++ b/api/booking/create.ts @@ -1,22 +1,22 @@ -import type { ApiRequest, ApiResponse } from '../_shared/http-types'; +import type { ApiRequest, ApiResponse } from '../_shared/http-types.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from '../_shared/rate-limit'; +} from '../_shared/rate-limit.js'; import recaptcha from '../_shared/recaptcha.js'; import { BOOKING_TIMEZONE, MEETING_DURATION_MIN, getRuntimeEnv, -} from './_lib/config'; -import { exec, isUniqueConstraintError, queryFirst } from './_lib/d1'; -import { createEvent, deleteEvent, getBusyIntervals } from './_lib/google-calendar'; -import { sendBookingConfirmation } from './_lib/mailer'; -import { isSlotValid } from './_lib/slots'; -import { generateToken, newBookingId } from './_lib/tokens'; -import { validateCreatePayload } from './_lib/validators'; +} from './_lib/config.js'; +import { exec, isUniqueConstraintError, queryFirst } from './_lib/d1.js'; +import { createEvent, deleteEvent, getBusyIntervals } from './_lib/google-calendar.js'; +import { sendBookingConfirmation } from './_lib/mailer.js'; +import { isSlotValid } from './_lib/slots.js'; +import { generateToken, newBookingId } from './_lib/tokens.js'; +import { validateCreatePayload } from './_lib/validators.js'; const { extractClientIp, verifyRecaptcha } = recaptcha; diff --git a/api/booking/lookup.ts b/api/booking/lookup.ts index eda2841..542b323 100644 --- a/api/booking/lookup.ts +++ b/api/booking/lookup.ts @@ -1,12 +1,12 @@ -import type { ApiRequest, ApiResponse } from '../_shared/http-types'; +import type { ApiRequest, ApiResponse } from '../_shared/http-types.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from '../_shared/rate-limit'; +} from '../_shared/rate-limit.js'; -import { queryFirst } from './_lib/d1'; -import { verifyToken } from './_lib/tokens'; +import { queryFirst } from './_lib/d1.js'; +import { verifyToken } from './_lib/tokens.js'; const LOOKUP_RATE_LIMIT = { limit: 30, diff --git a/api/booking/reschedule.ts b/api/booking/reschedule.ts index 2e629cd..9706866 100644 --- a/api/booking/reschedule.ts +++ b/api/booking/reschedule.ts @@ -1,20 +1,20 @@ -import type { ApiRequest, ApiResponse } from '../_shared/http-types'; +import type { ApiRequest, ApiResponse } from '../_shared/http-types.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from '../_shared/rate-limit'; +} from '../_shared/rate-limit.js'; import { BOOKING_TIMEZONE, MEETING_DURATION_MIN, getRuntimeEnv, -} from './_lib/config'; -import { exec, isUniqueConstraintError, queryFirst } from './_lib/d1'; -import { getBusyIntervals, updateEventTime } from './_lib/google-calendar'; -import { sendBookingConfirmation } from './_lib/mailer'; -import { isSlotValid } from './_lib/slots'; -import { generateToken, verifyToken } from './_lib/tokens'; +} from './_lib/config.js'; +import { exec, isUniqueConstraintError, queryFirst } from './_lib/d1.js'; +import { getBusyIntervals, updateEventTime } from './_lib/google-calendar.js'; +import { sendBookingConfirmation } from './_lib/mailer.js'; +import { isSlotValid } from './_lib/slots.js'; +import { generateToken, verifyToken } from './_lib/tokens.js'; const RESCHEDULE_RATE_LIMIT = { limit: 5, diff --git a/api/newsletter.ts b/api/newsletter.ts index 54a588f..8e946a9 100644 --- a/api/newsletter.ts +++ b/api/newsletter.ts @@ -1,11 +1,11 @@ import { Resend } from 'resend'; -import type { ApiRequest, ApiResponse } from './_shared/http-types'; +import type { ApiRequest, ApiResponse } from './_shared/http-types.js'; import newsletterMailer from './_shared/newsletter-mailer.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from './_shared/rate-limit'; +} from './_shared/rate-limit.js'; import recaptcha from './_shared/recaptcha.js'; const { newsletterApiErrors, sendNewsletterEmail, validateNewsletterPayload } = newsletterMailer; diff --git a/api/send.ts b/api/send.ts index 599f282..224888b 100644 --- a/api/send.ts +++ b/api/send.ts @@ -1,11 +1,11 @@ import { Resend } from 'resend'; -import type { ApiRequest, ApiResponse } from './_shared/http-types'; +import type { ApiRequest, ApiResponse } from './_shared/http-types.js'; import contactMailer from './_shared/contact-mailer.js'; import { applyRateLimitHeaders, checkRateLimit, getRateLimitIdentifier, -} from './_shared/rate-limit'; +} from './_shared/rate-limit.js'; import recaptcha from './_shared/recaptcha.js'; const { contactApiErrors, sendContactEmails, validateContactPayload } = contactMailer;