diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cddd16..90f3084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ All notable changes to this project are documented in this file. +## [1.118.1] - 2026-09-07 + +### Fixed +- **Der Build prüft wieder Typen.** `typescript.ignoreBuildErrors` steht auf `false`. + Damit ist ein erfolgreicher Build endlich wieder ein Beleg dafür, dass der Code + übersetzbar ist — bisher war er es nicht. +- **Notion-Anbindung war defekt.** Der Code rief `notion.databases.query()` auf; in + `@notionhq/client` v5 gibt es diese Methode nicht mehr, der Aufruf lief zur Laufzeit in + ein "is not a function". Umgestellt auf `dataSources.query()`, die Suche filtert jetzt + auf `data_source`. **Ungetestet** (kein Notion-Zugang), und gespeicherte Verbindungen + enthalten alte `database_id`s — betroffene Nutzer müssen Notion neu verbinden. +- `WeeklyView.tsx` importierte `isAllDayEvent` weiterhin aus `format`, obwohl die Funktion + in v1.118.0 nach `date-utils` gewandert war. Wäre zur Laufzeit gescheitert. +- Export-Helfer (`EXPORT_FIELDS`, `isoWeek`, `weekLabel`, `csvCell` …) liegen jetzt in + `src/lib/export-fields.ts`. Next.js erlaubt in einer `route.ts` nur Route-Handler als + Exporte; zudem importierte `SettingsSidebar` sie direkt aus der Route und zog damit + einen Server-Handler in den Client-Graphen. +- `push-notifications.ts`: `applicationServerKey` braucht seit TypeScript 5.7 einen Cast + auf `BufferSource`. +- Typdeklaration für `web-push` in `src/types/web-push.d.ts` ergänzt — als lokale Datei + statt als Abhängigkeit, weil `npm install` im Projektordner nicht läuft. + +### Changed +- `tsconfig.json`: `include` von `**/*` auf `src/**/*` eingegrenzt. **Das war die Ursache + dafür, dass der Typecheck über 30 Minuten lief** — nicht die Größe von `WeeklyView.tsx`, + wie zuvor vermutet. Der vollständige Lauf dauert jetzt **19 Sekunden**. + ## [1.118.0] - 2026-09-07 ### Added diff --git a/TODO.md b/TODO.md index 4911011..2662630 100644 --- a/TODO.md +++ b/TODO.md @@ -104,11 +104,13 @@ Anleitung siehe [`docs/secret-rotation.md`](docs/secret-rotation.md). - [x] **Schritt 1:** `eslint.ignoreDuringBuilds` in `next.config.js` auf `false` - [x] Gemeldete ESLint-Fehler abarbeiten — es waren nur **2** (`prefer-const` in `SettingsSidebar.tsx`), verifiziert: `next lint` endet jetzt mit Exit 0 -- [ ] **Schritt 2:** `npm run type-check` laufen lassen, Fehlerzahl notieren -- [ ] Fehler abarbeiten — modulweise, nicht auf einmal -- [ ] **Schritt 3:** `typescript.ignoreBuildErrors` auf `false` +- [x] **Schritt 2:** `npm run type-check` — es waren **7 Fehler**, alle behoben +- [x] **Schritt 3:** `typescript.ignoreBuildErrors` auf `false` — Build prüft jetzt Typen +- [x] `tsconfig.json`: `include` von `**/*` auf `src/**/*` eingegrenzt. **Das war die + Ursache der Langsamkeit**, nicht die Dateigröße: Der Lauf dauert jetzt **19 Sekunden** + statt über 30 Minuten. Meine gegenteilige Vermutung war falsch. - [ ] **3 Dateien mit `@ts-nocheck`** aufspüren und entschärfen — die überspringen den - Typecheck vollständig, `ignoreBuildErrors: false` würde sie gar nicht erfassen + Typecheck vollständig, auch mit `ignoreBuildErrors: false` - [ ] `any`-Stellen reduzieren — ESLint zählt **470** `no-explicit-any` — fortlaufend, kein Blocker - [ ] **209** ungenutzte Variablen (`no-unused-vars`) aufräumen — fortlaufend - [ ] **11** unvollständige Hook-Abhängigkeiten (`react-hooks/exhaustive-deps`) prüfen — @@ -254,6 +256,25 @@ F1. Kommt etwas anderes heraus, wurde es längst getauscht und du kannst F1 abha --- +## Block H — Aus dem scharfen Typecheck + +### H1. Notion-Anbindung war defekt *(migriert, aber ungetestet)* +- [ ] Migration praktisch prüfen (braucht Notion-Zugang) +- [ ] Betroffene Nutzer informieren: Notion-Verbindung muss neu eingerichtet werden + +**Warum:** Der Code rief `notion.databases.query()` auf. Die Methode gibt es in +`@notionhq/client` v5 nicht mehr (nur noch retrieve/create/update) — der Aufruf lief zur +Laufzeit in ein "is not a function". **Die Notion-Anbindung war damit schlicht kaputt**, und +das ist niemandem aufgefallen, weil der Build Typfehler verschluckte. + +Umgestellt auf `dataSources.query()`; die Suche filtert jetzt auf `data_source` statt +`database`. Das ist in sich konsistent, aber **mangels Notion-Zugang nicht erprobt**. + +⚠️ **Gespeicherte Verbindungen enthalten alte `database_id`s. Das sind keine +`data_source_id`s** — betroffene Nutzer müssen Notion einmal neu verbinden. + +--- + ## Block G — Infrastruktur (Supabase-LXC 201) ### G1. Container läuft privilegiert *(Sicherheitsthema)* diff --git a/next.config.js b/next.config.js index 3aaa26d..838d40a 100644 --- a/next.config.js +++ b/next.config.js @@ -9,7 +9,10 @@ const nextConfig = { ignoreDuringBuilds: false, }, typescript: { - ignoreBuildErrors: true, + // Aus: Der Build bricht bei Typfehlern ab. Bis v1.118.1 stand hier true — dadurch + // liefen mehrfach fehlende Importe durch, die zur Laufzeit ReferenceError geworfen + // haetten, waehrend der Build "erfolgreich" meldete. + ignoreBuildErrors: false, }, optimizeFonts: false, env: { diff --git a/package-lock.json b/package-lock.json index dc91b5a..6bbe7bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "my-weekly-todo-list", - "version": "1.118.0", + "version": "1.118.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "my-weekly-todo-list", - "version": "1.118.0", + "version": "1.118.1", "license": "MIT", "dependencies": { "@auth/prisma-adapter": "^2.11.1", diff --git a/package.json b/package.json index 70311c2..7a14a28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.118.0", + "version": "1.118.1", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/api/user/export-pdf/route.ts b/src/app/api/user/export-pdf/route.ts index 8fe77de..608c60b 100644 --- a/src/app/api/user/export-pdf/route.ts +++ b/src/app/api/user/export-pdf/route.ts @@ -9,7 +9,7 @@ import { import { EXPORT_FIELDS, ExportFieldKey, fetchExportData, weekLabel, fmtDate, priorityLabel, priorityColor, -} from '../export/route'; +} from '@/lib/export-fields'; // ── PDF styles ───────────────────────────────────────────────────────────── diff --git a/src/app/api/user/export/route.ts b/src/app/api/user/export/route.ts index f94effd..a740322 100644 --- a/src/app/api/user/export/route.ts +++ b/src/app/api/user/export/route.ts @@ -2,162 +2,8 @@ import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/lib/auth"; import { prisma } from '@/lib/prisma'; - -// ── Field registry ───────────────────────────────────────────────────────── -export type ExportFieldKey = - | 'week' - | 'weekGoal' - | 'completedDate' - | 'scheduledDate' - | 'createdDate' - | 'project' - | 'list' - | 'priority' - | 'url' - | 'notes' - | 'description' - | 'delegatedTo' - | 'delegationNote' - | 'startTime' - | 'kanbanStage'; - -export const EXPORT_FIELDS: { - key: ExportFieldKey; - labelDe: string; - labelEn: string; - defaultOn: boolean; - csvOnly?: boolean; // not a table column, only used as a section in PDF/CSV -}[] = [ - { key: 'week', labelDe: 'Kalenderwoche', labelEn: 'Calendar week', defaultOn: true, csvOnly: true }, - { key: 'weekGoal', labelDe: 'Wochenziel', labelEn: 'Goal of the week', defaultOn: true, csvOnly: true }, - { key: 'completedDate', labelDe: 'Erledigt am', labelEn: 'Completed', defaultOn: true }, - { key: 'scheduledDate', labelDe: 'Geplant für', labelEn: 'Scheduled for', defaultOn: true }, - { key: 'createdDate', labelDe: 'Erstellt am', labelEn: 'Created', defaultOn: false }, - { key: 'project', labelDe: 'Projekt', labelEn: 'Project', defaultOn: true }, - { key: 'list', labelDe: 'Liste', labelEn: 'List', defaultOn: true }, - { key: 'priority', labelDe: 'Priorität', labelEn: 'Priority', defaultOn: true }, - { key: 'url', labelDe: 'Link / URL', labelEn: 'Link / URL', defaultOn: false }, - { key: 'notes', labelDe: 'Notiz', labelEn: 'Notes', defaultOn: false }, - { key: 'description', labelDe: 'Beschreibung', labelEn: 'Description', defaultOn: false }, - { key: 'delegatedTo', labelDe: 'Delegiert an', labelEn: 'Delegated to', defaultOn: false }, - { key: 'delegationNote', labelDe: 'Delegationsnotiz', labelEn: 'Delegation note', defaultOn: false }, - { key: 'startTime', labelDe: 'Uhrzeit', labelEn: 'Start time', defaultOn: false }, - { key: 'kanbanStage', labelDe: 'Kanban-Stage', labelEn: 'Kanban stage', defaultOn: false }, -]; - -// ── Shared helpers (also used by export-pdf route) ───────────────────────── - -export function isoWeek(date: Date): string { - const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); - const dayNum = d.getUTCDay() || 7; - d.setUTCDate(d.getUTCDate() + 4 - dayNum); - const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); - const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); - return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; -} - -export function weekMondaySunday(weekKey: string): { mon: Date; sun: Date } { - const [year, w] = weekKey.split('-W'); - const d = new Date(Date.UTC(parseInt(year), 0, 1)); - d.setUTCDate(d.getUTCDate() + (parseInt(w) - 1) * 7 - (d.getUTCDay() || 7) + 1); - const sun = new Date(d); sun.setUTCDate(d.getUTCDate() + 6); - return { mon: d, sun }; -} - -export function weekLabel(weekKey: string, de: boolean): string { - const [, w] = weekKey.split('-W'); - const year = weekKey.split('-W')[0]; - const { mon, sun } = weekMondaySunday(weekKey); - const fmt = (x: Date) => x.toLocaleDateString(de ? 'de-DE' : 'en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' }); - return de - ? `KW ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})` - : `Week ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})`; -} - -export function fmtDate(d: Date | null | undefined, de: boolean): string { - if (!d) return ''; - return de - ? d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) - : d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); -} - -export function priorityLabel(task: any, de: boolean): string { - if (task.urgency != null && task.importance != null) { - if (task.urgency && task.importance) return de ? 'Sofort erledigen' : 'Do now'; - if (!task.urgency && task.importance) return de ? 'Planen' : 'Schedule'; - if (task.urgency && !task.importance) return de ? 'Delegieren' : 'Delegate'; - return de ? 'Eliminieren' : 'Eliminate'; - } - if (task.priority) return task.priority; - return ''; -} - -export function priorityColor(task: any): string { - if (task.urgency != null && task.importance != null) { - if (task.urgency && task.importance) return '#ef4444'; - if (!task.urgency && task.importance) return '#3b82f6'; - if (task.urgency && !task.importance) return '#f97316'; - return '#9ca3af'; - } - const map: Record = { A: '#ef4444', B: '#f97316', C: '#eab308', D: '#3b82f6', E: '#9ca3af' }; - return task.priority ? (map[task.priority] || '#6b7280') : '#6b7280'; -} - -function csvCell(value: string): string { - return `"${(value ?? '').replace(/"/g, '""').replace(/\n/g, ' ')}"`; -} - -const DEFAULT_GOAL = 'your goal of this week'; - -// ── Fetch shared export data (also used by PDF route) ────────────────────── - -export async function fetchExportData(userId: string, startDate: string | null, endDate: string | null) { - const where: any = { userId, completed: true, deletedAt: null }; - if (startDate || endDate) { - where.updatedAt = {}; - if (startDate) where.updatedAt.gte = new Date(startDate); - if (endDate) { - const end = new Date(endDate); - end.setHours(23, 59, 59, 999); - where.updatedAt.lte = end; - } - } - - const [tasks, goals] = await Promise.all([ - prisma.task.findMany({ - where, - orderBy: { updatedAt: 'desc' }, - include: { - project: { select: { name: true, color: true } }, - somedayList: { select: { title: true } }, - }, - }), - prisma.weeklyGoal.findMany({ - where: { userId }, - select: { weekStart: true, text: true }, - }), - ]); - - // Build week → goal map (weekStart is always Monday) - const goalByWeek = new Map(); - for (const g of goals) { - if (g.text && g.text !== DEFAULT_GOAL) { - goalByWeek.set(isoWeek(g.weekStart), g.text); - } - } - - // Group tasks by ISO week - const byWeek = new Map(); - for (const task of tasks) { - const key = isoWeek(task.updatedAt); - if (!byWeek.has(key)) byWeek.set(key, []); - byWeek.get(key)!.push(task); - } - - return { byWeek, goalByWeek }; -} - -// ── CSV route ────────────────────────────────────────────────────────────── +import { EXPORT_FIELDS, fetchExportData, weekLabel, fmtDate, priorityLabel, csvCell } from '@/lib/export-fields'; +import type { ExportFieldKey } from '@/lib/export-fields'; export async function GET(request: Request) { const session = await getServerSession(authOptions); diff --git a/src/components/SettingsSidebar.tsx b/src/components/SettingsSidebar.tsx index 1079599..3b49157 100644 --- a/src/components/SettingsSidebar.tsx +++ b/src/components/SettingsSidebar.tsx @@ -10,7 +10,7 @@ import { ViewStyle, KanbanStage, Task } from "./WeeklyView"; import { AVAILABLE_FONTS, isCustomFont } from "../lib/fontConstants"; import { translations } from "../lib/weeklyViewTranslations"; import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants"; -import { EXPORT_FIELDS, type ExportFieldKey } from "../app/api/user/export/route"; +import { EXPORT_FIELDS, type ExportFieldKey } from "@/lib/export-fields"; import { ArrowLeftRight, Briefcase, diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 56225e7..7b45c7f 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -105,9 +105,9 @@ import { } from "@/lib/weekly/device-settings"; import type { DeviceViewSettingKey } from "@/lib/weekly/device-settings"; import { - stripHtml, DEFAULT_SOMEDAY_SLOT_COUNT, getSomedaySlotCount, - isAllDayEvent, invertColor, + stripHtml, DEFAULT_SOMEDAY_SLOT_COUNT, getSomedaySlotCount, invertColor, } from "@/lib/weekly/format"; +import { isAllDayEvent } from "@/lib/weekly/date-utils"; // Types import UserMenu from "./UserMenu"; diff --git a/src/lib/export-fields.ts b/src/lib/export-fields.ts new file mode 100644 index 0000000..e2beb59 --- /dev/null +++ b/src/lib/export-fields.ts @@ -0,0 +1,170 @@ +/** + * Felddefinitionen und Helfer für den Datenexport. + * + * Lagen bis v1.118.1 in `app/api/user/export/route.ts`. Next.js erlaubt in einer route.ts + * nur Route-Handler als Exporte — alles andere lässt die generierte Typprüfung auflaufen + * ("does not satisfy the constraint"). Ausserdem importierte SettingsSidebar EXPORT_FIELDS + * aus der Route und zog damit einen Server-Handler in den Client-Graphen. + */ + +import { NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from "@/lib/auth"; +import { prisma } from '@/lib/prisma'; + +// ── Field registry ───────────────────────────────────────────────────────── + +export type ExportFieldKey = + | 'week' + | 'weekGoal' + | 'completedDate' + | 'scheduledDate' + | 'createdDate' + | 'project' + | 'list' + | 'priority' + | 'url' + | 'notes' + | 'description' + | 'delegatedTo' + | 'delegationNote' + | 'startTime' + | 'kanbanStage'; + +export const EXPORT_FIELDS: { + key: ExportFieldKey; + labelDe: string; + labelEn: string; + defaultOn: boolean; + csvOnly?: boolean; // not a table column, only used as a section in PDF/CSV +}[] = [ + { key: 'week', labelDe: 'Kalenderwoche', labelEn: 'Calendar week', defaultOn: true, csvOnly: true }, + { key: 'weekGoal', labelDe: 'Wochenziel', labelEn: 'Goal of the week', defaultOn: true, csvOnly: true }, + { key: 'completedDate', labelDe: 'Erledigt am', labelEn: 'Completed', defaultOn: true }, + { key: 'scheduledDate', labelDe: 'Geplant für', labelEn: 'Scheduled for', defaultOn: true }, + { key: 'createdDate', labelDe: 'Erstellt am', labelEn: 'Created', defaultOn: false }, + { key: 'project', labelDe: 'Projekt', labelEn: 'Project', defaultOn: true }, + { key: 'list', labelDe: 'Liste', labelEn: 'List', defaultOn: true }, + { key: 'priority', labelDe: 'Priorität', labelEn: 'Priority', defaultOn: true }, + { key: 'url', labelDe: 'Link / URL', labelEn: 'Link / URL', defaultOn: false }, + { key: 'notes', labelDe: 'Notiz', labelEn: 'Notes', defaultOn: false }, + { key: 'description', labelDe: 'Beschreibung', labelEn: 'Description', defaultOn: false }, + { key: 'delegatedTo', labelDe: 'Delegiert an', labelEn: 'Delegated to', defaultOn: false }, + { key: 'delegationNote', labelDe: 'Delegationsnotiz', labelEn: 'Delegation note', defaultOn: false }, + { key: 'startTime', labelDe: 'Uhrzeit', labelEn: 'Start time', defaultOn: false }, + { key: 'kanbanStage', labelDe: 'Kanban-Stage', labelEn: 'Kanban stage', defaultOn: false }, +]; + +// ── Shared helpers (also used by export-pdf route) ───────────────────────── + +export function isoWeek(date: Date): string { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; +} + +export function weekMondaySunday(weekKey: string): { mon: Date; sun: Date } { + const [year, w] = weekKey.split('-W'); + const d = new Date(Date.UTC(parseInt(year), 0, 1)); + d.setUTCDate(d.getUTCDate() + (parseInt(w) - 1) * 7 - (d.getUTCDay() || 7) + 1); + const sun = new Date(d); sun.setUTCDate(d.getUTCDate() + 6); + return { mon: d, sun }; +} + +export function weekLabel(weekKey: string, de: boolean): string { + const [, w] = weekKey.split('-W'); + const year = weekKey.split('-W')[0]; + const { mon, sun } = weekMondaySunday(weekKey); + const fmt = (x: Date) => x.toLocaleDateString(de ? 'de-DE' : 'en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' }); + return de + ? `KW ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})` + : `Week ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})`; +} + +export function fmtDate(d: Date | null | undefined, de: boolean): string { + if (!d) return ''; + return de + ? d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) + : d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); +} + +export function priorityLabel(task: any, de: boolean): string { + if (task.urgency != null && task.importance != null) { + if (task.urgency && task.importance) return de ? 'Sofort erledigen' : 'Do now'; + if (!task.urgency && task.importance) return de ? 'Planen' : 'Schedule'; + if (task.urgency && !task.importance) return de ? 'Delegieren' : 'Delegate'; + return de ? 'Eliminieren' : 'Eliminate'; + } + if (task.priority) return task.priority; + return ''; +} + +export function priorityColor(task: any): string { + if (task.urgency != null && task.importance != null) { + if (task.urgency && task.importance) return '#ef4444'; + if (!task.urgency && task.importance) return '#3b82f6'; + if (task.urgency && !task.importance) return '#f97316'; + return '#9ca3af'; + } + const map: Record = { A: '#ef4444', B: '#f97316', C: '#eab308', D: '#3b82f6', E: '#9ca3af' }; + return task.priority ? (map[task.priority] || '#6b7280') : '#6b7280'; +} + +export function csvCell(value: string): string { + return `"${(value ?? '').replace(/"/g, '""').replace(/\n/g, ' ')}"`; +} + +const DEFAULT_GOAL = 'your goal of this week'; + +// ── Fetch shared export data (also used by PDF route) ────────────────────── + +export async function fetchExportData(userId: string, startDate: string | null, endDate: string | null) { + const where: any = { userId, completed: true, deletedAt: null }; + if (startDate || endDate) { + where.updatedAt = {}; + if (startDate) where.updatedAt.gte = new Date(startDate); + if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + where.updatedAt.lte = end; + } + } + + const [tasks, goals] = await Promise.all([ + prisma.task.findMany({ + where, + orderBy: { updatedAt: 'desc' }, + include: { + project: { select: { name: true, color: true } }, + somedayList: { select: { title: true } }, + }, + }), + prisma.weeklyGoal.findMany({ + where: { userId }, + select: { weekStart: true, text: true }, + }), + ]); + + // Build week → goal map (weekStart is always Monday) + const goalByWeek = new Map(); + for (const g of goals) { + if (g.text && g.text !== DEFAULT_GOAL) { + goalByWeek.set(isoWeek(g.weekStart), g.text); + } + } + + // Group tasks by ISO week + const byWeek = new Map(); + for (const task of tasks) { + const key = isoWeek(task.updatedAt); + if (!byWeek.has(key)) byWeek.set(key, []); + byWeek.get(key)!.push(task); + } + + return { byWeek, goalByWeek }; +} + +// ── CSV route ────────────────────────────────────────────────────────────── diff --git a/src/lib/notion-calendar.ts b/src/lib/notion-calendar.ts index 085b7cc..39191d7 100644 --- a/src/lib/notion-calendar.ts +++ b/src/lib/notion-calendar.ts @@ -20,19 +20,37 @@ export interface NotionEvent { } /** - * Find all databases shared with the integration + * ACHTUNG — Migration auf @notionhq/client v5, ungetestet. + * + * Der bisherige Code rief `notion.databases.query()` auf. Diese Methode gibt es in SDK v5 + * nicht mehr (nur noch retrieve/create/update), der Aufruf lief also zur Laufzeit in ein + * "is not a function". Die Notion-Anbindung war damit defekt — aufgefallen erst, als der + * Typecheck scharf geschaltet wurde. + * + * Umgestellt auf `dataSources.query()`. Beides ist konsistent: Die Suche liefert jetzt + * data sources, deren `id` direkt als `data_source_id` taugt. + * + * WICHTIG: Bereits gespeicherte Notion-Verbindungen enthalten alte database_ids. Die sind + * KEINE data_source_ids — betroffene Nutzer müssen ihre Notion-Verbindung einmal neu + * einrichten. Mangels Notion-Zugang ist nichts davon praktisch erprobt. + */ + +/** + * Find all data sources shared with the integration */ export async function getUserDatabases(accessToken: string): Promise { const notion = new Client({ auth: accessToken }); + // SDK v5: Was frueher "database" hiess, heisst jetzt "data source". Der alte Wert + // 'database' ist kein gueltiger Filterwert mehr. const response = await notion.search({ - filter: { property: 'object', value: 'database' }, + filter: { property: 'object', value: 'data_source' }, page_size: 50, }); const databases: NotionDatabase[] = []; for (const result of response.results) { - if (result.object !== 'database') continue; + if (result.object !== 'data_source') continue; const db = result as any; const title = db.title?.map((t: any) => t.plain_text).join('') || 'Untitled'; @@ -79,8 +97,8 @@ export async function getUpcomingEvents( let startCursor: string | undefined; while (hasMore) { - const response = await notion.databases.query({ - database_id: databaseId, + const response = await notion.dataSources.query({ + data_source_id: databaseId, filter: { and: [ { diff --git a/src/lib/push-notifications.ts b/src/lib/push-notifications.ts index b48d638..5b5b904 100644 --- a/src/lib/push-notifications.ts +++ b/src/lib/push-notifications.ts @@ -55,7 +55,10 @@ export async function subscribeToPush( try { const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(vapidPublicKey), + // Cast noetig: TypeScript typisiert Uint8Array seit 5.7 als Uint8Array, + // BufferSource verlangt aber ArrayBuffer. urlBase64ToUint8Array liefert immer einen + // echten ArrayBuffer, der SharedArrayBuffer-Fall kann hier nicht auftreten. + applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) as BufferSource, }); return subscription; } catch (err) { diff --git a/src/types/web-push.d.ts b/src/types/web-push.d.ts new file mode 100644 index 0000000..6e1255d --- /dev/null +++ b/src/types/web-push.d.ts @@ -0,0 +1,27 @@ +/** + * Minimale Typdeklaration für `web-push`. + * + * Das Paket liefert keine eigenen Typen, und `@types/web-push` als Abhängigkeit aufzunehmen + * scheitert daran, dass `npm install` im Projektordner nicht läuft (CIFS-Freigabe ohne + * Symlink-Unterstützung). Deklariert ist nur, was das Projekt tatsächlich benutzt. + */ +declare module "web-push" { + export interface PushSubscription { + endpoint: string; + keys: { p256dh: string; auth: string }; + } + + export interface SendResult { + statusCode: number; + body: string; + headers: Record; + } + + export function setVapidDetails(subject: string, publicKey: string, privateKey: string): void; + export function sendNotification( + subscription: PushSubscription, + payload?: string | Buffer | null, + options?: Record, + ): Promise; + export function generateVAPIDKeys(): { publicKey: string; privateKey: string }; +} diff --git a/tsconfig.json b/tsconfig.json index 3648563..cf574ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,8 +31,8 @@ }, "include": [ "next-env.d.ts", - "**/*.ts", - "**/*.tsx", + "src/**/*.ts", + "src/**/*.tsx", ".next/types/**/*.ts" ], "exclude": [