@open-mercato/core 0.6.6-develop.6339.1.193c6c7c71 → 0.6.6-develop.6344.1.c4b17c07c4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/catalog/components/products/ProductComplianceSection.js +1 -1
  3. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +2 -2
  4. package/dist/modules/catalog/data/validators.js +1 -1
  5. package/dist/modules/catalog/data/validators.js.map +2 -2
  6. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +1 -1
  7. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +2 -2
  8. package/dist/modules/communication_channels/lib/route-mutation-guard.js +19 -29
  9. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +4 -4
  11. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  12. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
  13. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  14. package/dist/modules/integrations/api/[id]/credentials/route.js +14 -4
  15. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  16. package/dist/modules/integrations/lib/credentials-masking.js +50 -0
  17. package/dist/modules/integrations/lib/credentials-masking.js.map +7 -0
  18. package/dist/modules/notifications/lib/routeHelpers.js +18 -25
  19. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  20. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +1 -1
  21. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  22. package/package.json +7 -7
  23. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +1 -1
  24. package/src/modules/catalog/data/validators.ts +1 -1
  25. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +1 -1
  26. package/src/modules/communication_channels/lib/route-mutation-guard.ts +32 -32
  27. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +4 -4
  28. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
  29. package/src/modules/integrations/api/[id]/credentials/route.ts +18 -4
  30. package/src/modules/integrations/lib/credentials-masking.ts +95 -0
  31. package/src/modules/notifications/lib/routeHelpers.ts +18 -26
  32. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +1 -1
@@ -175,7 +175,7 @@ function CalendarScreen() {
175
175
  const typeOptions = React.useMemo(() => {
176
176
  const values = new Set(Object.keys(typeLabels));
177
177
  for (const item of items) values.add(item.interactionType);
178
- return [...values].sort().map((value) => ({ value, label: typeLabels[value] ?? value }));
178
+ return [...values].sort((a, b) => a.localeCompare(b)).map((value) => ({ value, label: typeLabels[value] ?? value }));
179
179
  }, [items, typeLabels]);
180
180
  const ownerOptions = React.useMemo(() => {
181
181
  const participantNames = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/CalendarScreen.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { matchFeature } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { countByCategory } from '../../lib/calendar/categories'\nimport { findConflicts } from '../../lib/calendar/conflicts'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { AgendaList } from './AgendaList'\nimport { CalendarFooter } from './CalendarFooter'\nimport { CalendarHeader } from './CalendarHeader'\nimport { CalendarTabs } from './CalendarTabs'\nimport { CalendarToolbar } from './CalendarToolbar'\nimport { MonthGrid } from './MonthGrid'\nimport { ShortcutsDialog } from './ShortcutsDialog'\nimport { TimeGrid } from './TimeGrid'\nimport { UpcomingCards } from './UpcomingCards'\nimport { CalendarSettingsModal } from './CalendarSettingsModal'\nimport { useCalendarPreferences } from './useCalendarPreferences'\nimport { MAX_WINDOW_ITEMS, useCalendarItems } from './useCalendarItems'\nimport type {\n CalendarFiltersValue,\n CalendarItem,\n CalendarRangePreset,\n CalendarTab,\n CalendarView,\n UpcomingCard,\n} from './types'\n\nconst CalendarEventEditor = dynamic(\n () => import('./CalendarEventEditor').then((mod) => mod.CalendarEventEditor),\n { ssr: false },\n)\n\nconst SEARCH_DEBOUNCE_MS = 200\nconst PHONE_BREAKPOINT_PX = 640\nconst HIGHLIGHT_CLEAR_MS = 3000\nconst DEFAULT_AGENDA_HORIZON_DAYS = 7\nconst UPCOMING_CARDS_COUNT = 4\nconst EMPTY_FILTERS: CalendarFiltersValue = { types: [], status: null, ownerUserId: null }\n\ntype EditorState = { open: boolean; mode: 'create' | 'edit'; item: CalendarItem | null }\n\nconst MANAGE_FEATURE = 'customers.interactions.manage'\n\nfunction useCanManageInteractions(): boolean {\n const [canManage, setCanManage] = React.useState(false)\n React.useEffect(() => {\n const controller = new AbortController()\n let cancelled = false\n apiCall<{ granted?: unknown[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n body: JSON.stringify({ features: [MANAGE_FEATURE] }),\n })\n .then((call) => {\n if (cancelled || !call.ok) return\n const granted = Array.isArray(call.result?.granted)\n ? call.result.granted.map((feature) => String(feature))\n : []\n setCanManage(granted.some((grantedFeature) => matchFeature(MANAGE_FEATURE, grantedFeature)))\n })\n .catch(() => {\n if (!cancelled && !controller.signal.aborted) setCanManage(false)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [])\n return canManage\n}\n\nfunction buildTimezoneLabel(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n const offsetMinutes = -new Date().getTimezoneOffset()\n const sign = offsetMinutes >= 0 ? '+' : '-'\n const absolute = Math.abs(offsetMinutes)\n const hours = Math.floor(absolute / 60)\n const minutes = absolute % 60\n const offset = minutes > 0 ? `${sign}${hours}:${String(minutes).padStart(2, '0')}` : `${sign}${hours}`\n return `${timeZone} (GMT${offset})`\n}\n\nfunction resolveJoinUrl(location: string | null): string | null {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n}\n\nfunction asEditableItem(item: CalendarItem): CalendarItem {\n return item.isRecurringOccurrence ? { ...item, id: item.raw.id } : item\n}\n\nexport function CalendarScreen() {\n const t = useT()\n const [view, setView] = React.useState<CalendarView>('week')\n const [anchor, setAnchor] = React.useState<Date>(() => new Date())\n const [agendaHorizonDays, setAgendaHorizonDays] = React.useState(DEFAULT_AGENDA_HORIZON_DAYS)\n const [preset, setPreset] = React.useState<CalendarRangePreset | null>('thisWeek')\n\n React.useEffect(() => {\n if (window.innerWidth >= PHONE_BREAKPOINT_PX) return\n setView('day')\n setPreset(null)\n }, [])\n const [tab, setTab] = React.useState<CalendarTab>('all')\n const [searchText, setSearchText] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filters, setFilters] = React.useState<CalendarFiltersValue>(EMPTY_FILTERS)\n const [editor, setEditor] = React.useState<EditorState>({ open: false, mode: 'create', item: null })\n const [editorMounted, setEditorMounted] = React.useState(false)\n const [createRange, setCreateRange] = React.useState<{ start: Date; end: Date } | null>(null)\n const [shortcutsOpen, setShortcutsOpen] = React.useState(false)\n const [settingsOpen, setSettingsOpen] = React.useState(false)\n const [highlightItemId, setHighlightItemId] = React.useState<string | null>(null)\n const [hasLoadedOnce, setHasLoadedOnce] = React.useState(false)\n const { preferences, setPreferences, hydrated: preferencesHydrated, userId: currentUserId } = useCalendarPreferences()\n\n const range = React.useMemo(\n () => getVisibleRange(view, anchor, agendaHorizonDays),\n [view, anchor, agendaHorizonDays],\n )\n const { items, isLoading, error, truncated, typeLabels, typeColors, refetch } = useCalendarItems(range)\n\n React.useEffect(() => {\n if (!isLoading) setHasLoadedOnce(true)\n }, [isLoading])\n\n React.useEffect(() => {\n const timer = window.setTimeout(() => setDebouncedSearch(searchText), SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [searchText])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const timer = window.setTimeout(() => setHighlightItemId(null), HIGHLIGHT_CLEAR_MS)\n return () => window.clearTimeout(timer)\n }, [highlightItemId])\n\n const visibleItems = React.useMemo(\n () => items.filter((item) => item.end > range.from && item.start < range.to),\n [items, range],\n )\n\n const searchedItems = React.useMemo(() => {\n const query = debouncedSearch.trim().toLowerCase()\n if (!query) return visibleItems\n return visibleItems.filter((item) => {\n if (item.title.toLowerCase().includes(query)) return true\n if (item.location && item.location.toLowerCase().includes(query)) return true\n const rawBody = (item.raw as { body?: unknown }).body\n if (typeof rawBody === 'string' && rawBody.toLowerCase().includes(query)) return true\n return item.participants.some((participant) =>\n (participant.name ?? '').toLowerCase().includes(query),\n )\n })\n }, [visibleItems, debouncedSearch])\n\n const baseItems = React.useMemo(\n () =>\n searchedItems.filter((item) => {\n if (filters.types.length > 0 && !filters.types.includes(item.interactionType)) return false\n if (filters.status && item.status !== filters.status) return false\n if (filters.ownerUserId && item.ownerUserId !== filters.ownerUserId) return false\n if (!preferences.showCrmActivities && item.category !== 'meeting' && item.category !== 'event') return false\n return true\n }),\n [searchedItems, filters, preferences.showCrmActivities],\n )\n\n const tabCounts = React.useMemo(() => countByCategory(baseItems), [baseItems])\n\n const viewItems = React.useMemo(() => {\n if (tab === 'meetings') return baseItems.filter((item) => item.category === 'meeting')\n if (tab === 'events') return baseItems.filter((item) => item.category === 'event')\n return baseItems\n }, [baseItems, tab])\n\n const conflictMap = React.useMemo(\n () => findConflicts(baseItems, { scope: preferences.conflictScope, currentUserId }),\n [baseItems, preferences.conflictScope, currentUserId],\n )\n const conflictIds = React.useMemo(() => new Set(conflictMap.keys()), [conflictMap])\n\n const upcomingCards = React.useMemo<UpcomingCard[]>(() => {\n const now = new Date()\n const nowMs = now.getTime()\n return baseItems\n .filter((item) => item.start.getTime() >= nowMs)\n .sort((first, second) => first.start.getTime() - second.start.getTime())\n .slice(0, UPCOMING_CARDS_COUNT)\n .map((item) => {\n const conflictCount = preferences.conflictWarnings ? (conflictMap.get(item.id)?.length ?? 0) : 0\n const kind: UpcomingCard['kind'] =\n item.status === 'canceled'\n ? 'cancelled'\n : conflictCount > 0\n ? 'conflicted'\n : isSameDay(item.start, now)\n ? 'today'\n : 'future'\n return { item, kind, conflictCount }\n })\n }, [baseItems, conflictMap, preferences.conflictWarnings])\n\n const typeOptions = React.useMemo(() => {\n const values = new Set<string>(Object.keys(typeLabels))\n for (const item of items) values.add(item.interactionType)\n return [...values]\n .sort()\n .map((value) => ({ value, label: typeLabels[value] ?? value }))\n }, [items, typeLabels])\n\n const ownerOptions = React.useMemo(() => {\n const participantNames = new Map<string, string>()\n for (const item of items) {\n for (const participant of item.participants) {\n if (participant.name && !participantNames.has(participant.userId)) {\n participantNames.set(participant.userId, participant.name)\n }\n }\n }\n const owners = new Map<string, string>()\n for (const item of items) {\n if (!item.ownerUserId || owners.has(item.ownerUserId)) continue\n owners.set(item.ownerUserId, participantNames.get(item.ownerUserId) ?? item.ownerUserId)\n }\n return [...owners.entries()]\n .map(([value, label]) => ({ value, label }))\n .sort((first, second) => first.label.localeCompare(second.label))\n }, [items])\n\n const timezoneLabel = React.useMemo(() => buildTimezoneLabel(), [])\n\n const canManage = useCanManageInteractions()\n\n const openCreateEditor = React.useCallback(() => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n }, [canManage])\n\n const openEditEditor = React.useCallback(\n (item: CalendarItem) => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'edit', item: asEditableItem(item) })\n },\n [canManage],\n )\n\n const handleCreateRange = React.useCallback(\n (start: Date, end: Date) => {\n if (!canManage) return\n setCreateRange({ start, end })\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n },\n [canManage],\n )\n\n const seedActivityTypes = React.useMemo(() => {\n const seen = new Set<string>()\n const labels: string[] = []\n for (const value of Object.keys(typeLabels)) {\n const label = typeLabels[value] ?? value\n if (seen.has(label)) continue\n seen.add(label)\n labels.push(label)\n }\n return labels\n }, [typeLabels])\n\n const handleToday = React.useCallback(() => {\n setAnchor(new Date())\n setPreset(null)\n }, [])\n\n const handlePresetChange = React.useCallback((next: CalendarRangePreset) => {\n setPreset(next)\n setAnchor(new Date())\n if (next === 'thisWeek') {\n setView('week')\n } else if (next === 'thisMonth') {\n setView('month')\n } else {\n setView('agenda')\n setAgendaHorizonDays(next === 'next30' ? 30 : DEFAULT_AGENDA_HORIZON_DAYS)\n }\n }, [])\n\n const handleAnchorChange = React.useCallback((date: Date) => {\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleViewChange = React.useCallback((next: CalendarView) => {\n setView(next)\n setPreset(null)\n }, [])\n\n const handleTimeGridNavigate = React.useCallback((deltaDays: number) => {\n setAnchor((current) => addDays(current, deltaDays))\n setPreset(null)\n }, [])\n\n const handleDayOpen = React.useCallback((date: Date) => {\n setView('day')\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleSeeConflict = React.useCallback((item: CalendarItem) => {\n setView('week')\n setAnchor(item.start)\n setPreset(null)\n setHighlightItemId(item.id)\n }, [])\n\n const handleJoin = React.useCallback((item: CalendarItem) => {\n const url = resolveJoinUrl(item.location)\n if (url) window.open(url, '_blank', 'noopener')\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-calendar-cancel',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const handleCancelItem = React.useCallback(\n async (item: CalendarItem) => {\n const interactionId = item.raw.id\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(item.updatedAt), () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: interactionId, status: 'canceled' }),\n }),\n ),\n mutationPayload: {\n operation: 'cancelCalendarEvent',\n interactionId,\n interactionType: item.interactionType,\n },\n context: {\n formId: 'customers-calendar-cancel',\n resourceKind: 'customers.interaction',\n resourceId: interactionId,\n retryLastMutation,\n },\n })\n flash(t('customers.calendar.cards.cancelSuccess', 'Event cancelled'), 'success')\n refetch()\n } catch (err) {\n // An optimistic-lock 409 is surfaced as the persistent conflict bar by\n // useGuardedMutation (surfaceRecordConflict) \u2014 don't re-flash.\n if (extractOptimisticLockConflict(err)) return\n flash(t('customers.calendar.cards.cancelError', 'Failed to cancel event'), 'error')\n }\n },\n [refetch, retryLastMutation, runMutation, t],\n )\n\n const focusSearch = React.useCallback(() => {\n const node = document.querySelector('[data-calendar-search]')\n if (node instanceof HTMLInputElement) node.focus()\n else if (node instanceof HTMLElement) node.querySelector('input')?.focus()\n }, [])\n\n const editorOpen = editor.open\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented) return\n if (event.metaKey || event.ctrlKey || event.altKey) return\n const target = event.target\n if (target instanceof HTMLElement) {\n const tag = target.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) return\n }\n if (editorOpen) return\n switch (event.key) {\n case 't':\n case 'T':\n event.preventDefault()\n setAnchor(new Date())\n setPreset(null)\n break\n case 'd':\n case 'D':\n event.preventDefault()\n setView('day')\n setPreset(null)\n break\n case 'w':\n case 'W':\n event.preventDefault()\n setView('week')\n setPreset(null)\n break\n case 'm':\n case 'M':\n event.preventDefault()\n setView('month')\n setPreset(null)\n break\n case 'a':\n case 'A':\n event.preventDefault()\n setView('agenda')\n setPreset(null)\n break\n case 'n':\n case 'N':\n event.preventDefault()\n openCreateEditor()\n break\n case '/':\n event.preventDefault()\n focusSearch()\n break\n case '?':\n event.preventDefault()\n setShortcutsOpen((open) => !open)\n break\n case 'Escape':\n setHighlightItemId(null)\n break\n default:\n break\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [editorOpen, focusSearch, openCreateEditor])\n\n const showInitialLoading = (isLoading && !hasLoadedOnce) || !preferencesHydrated\n\n let viewArea: React.ReactNode\n if (error) {\n viewArea = (\n <ErrorMessage\n label={t('customers.calendar.errors.loadFailed', 'Failed to load calendar events.')}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={refetch}>\n {t('customers.calendar.errors.retry', 'Retry')}\n </Button>\n }\n />\n )\n } else if (showInitialLoading) {\n viewArea = <LoadingMessage label={t('customers.calendar.loading', 'Loading calendar\u2026')} />\n } else if (view === 'month') {\n viewArea = (\n <MonthGrid anchor={anchor} items={viewItems} onItemClick={openEditEditor} onDayOpen={handleDayOpen} />\n )\n } else if (view === 'agenda') {\n viewArea = (\n <AgendaList\n anchor={anchor}\n horizonDays={agendaHorizonDays}\n items={viewItems}\n typeLabels={typeLabels}\n onItemClick={openEditEditor}\n />\n )\n } else {\n viewArea = (\n <TimeGrid\n days={view === 'day' ? 1 : 7}\n anchor={anchor}\n items={viewItems}\n conflictIds={conflictIds}\n showWeekends={preferences.showWeekends}\n showConflicts={preferences.conflictWarnings}\n aiSummaries={preferences.aiSummaries}\n canManage={canManage}\n highlightItemId={highlightItemId}\n onItemClick={openEditEditor}\n onJoin={handleJoin}\n onNavigate={handleTimeGridNavigate}\n onCreate={canManage ? openCreateEditor : undefined}\n onCreateRange={canManage ? handleCreateRange : undefined}\n />\n )\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col gap-4\">\n <CalendarHeader view={view} anchor={anchor} onNewEvent={canManage ? openCreateEditor : undefined} />\n <CalendarToolbar\n view={view}\n anchor={anchor}\n range={range}\n preset={preset}\n search={searchText}\n filters={filters}\n typeOptions={typeOptions}\n ownerOptions={ownerOptions}\n onToday={handleToday}\n onPresetChange={handlePresetChange}\n onAnchorChange={handleAnchorChange}\n onSearchChange={setSearchText}\n onFiltersChange={setFilters}\n onOpenSettings={() => setSettingsOpen(true)}\n />\n <UpcomingCards\n cards={upcomingCards}\n canManage={canManage}\n onJoin={handleJoin}\n onSeeConflict={handleSeeConflict}\n onOpen={openEditEditor}\n onEdit={openEditEditor}\n onCancel={handleCancelItem}\n />\n <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n <CalendarTabs\n tab={tab}\n counts={tabCounts}\n view={view}\n onTabChange={setTab}\n onViewChange={handleViewChange}\n />\n {truncated ? (\n <p className=\"text-xs text-muted-foreground\" role=\"status\">\n {t('customers.calendar.notice.truncated', 'Showing first {count} items for this range.', {\n count: MAX_WINDOW_ITEMS,\n })}\n </p>\n ) : null}\n <div className=\"flex min-h-[560px] flex-1 flex-col [&>*]:flex-1\">{viewArea}</div>\n </div>\n <div className=\"hidden md:block\">\n <CalendarFooter timezoneLabel={timezoneLabel} onOpenShortcuts={() => setShortcutsOpen(true)} />\n </div>\n <ShortcutsDialog open={shortcutsOpen} onOpenChange={setShortcutsOpen} />\n <CalendarSettingsModal\n open={settingsOpen}\n preferences={preferences}\n seedActivityTypes={seedActivityTypes}\n onOpenChange={setSettingsOpen}\n onSave={(next) => {\n setPreferences(next)\n flash(t('customers.calendar.settings.saved', 'Calendar settings saved'), 'success')\n }}\n />\n {editorMounted ? (\n <CalendarEventEditor\n open={editor.open}\n mode={editor.mode}\n item={editor.item}\n defaultDate={anchor}\n defaultRange={createRange}\n typeLabels={typeLabels}\n typeColors={typeColors}\n surfacedTypes={preferences.activityTypes}\n eventCategories={preferences.eventCategories}\n conflictScope={preferences.conflictScope}\n currentUserId={currentUserId}\n onOpenChange={(open) => setEditor((current) => ({ ...current, open }))}\n onSaved={refetch}\n />\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport { addDays } from 'date-fns/addDays'\nimport { isSameDay } from 'date-fns/isSameDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { matchFeature } from '@open-mercato/shared/lib/auth/featureMatch'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { countByCategory } from '../../lib/calendar/categories'\nimport { findConflicts } from '../../lib/calendar/conflicts'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { AgendaList } from './AgendaList'\nimport { CalendarFooter } from './CalendarFooter'\nimport { CalendarHeader } from './CalendarHeader'\nimport { CalendarTabs } from './CalendarTabs'\nimport { CalendarToolbar } from './CalendarToolbar'\nimport { MonthGrid } from './MonthGrid'\nimport { ShortcutsDialog } from './ShortcutsDialog'\nimport { TimeGrid } from './TimeGrid'\nimport { UpcomingCards } from './UpcomingCards'\nimport { CalendarSettingsModal } from './CalendarSettingsModal'\nimport { useCalendarPreferences } from './useCalendarPreferences'\nimport { MAX_WINDOW_ITEMS, useCalendarItems } from './useCalendarItems'\nimport type {\n CalendarFiltersValue,\n CalendarItem,\n CalendarRangePreset,\n CalendarTab,\n CalendarView,\n UpcomingCard,\n} from './types'\n\nconst CalendarEventEditor = dynamic(\n () => import('./CalendarEventEditor').then((mod) => mod.CalendarEventEditor),\n { ssr: false },\n)\n\nconst SEARCH_DEBOUNCE_MS = 200\nconst PHONE_BREAKPOINT_PX = 640\nconst HIGHLIGHT_CLEAR_MS = 3000\nconst DEFAULT_AGENDA_HORIZON_DAYS = 7\nconst UPCOMING_CARDS_COUNT = 4\nconst EMPTY_FILTERS: CalendarFiltersValue = { types: [], status: null, ownerUserId: null }\n\ntype EditorState = { open: boolean; mode: 'create' | 'edit'; item: CalendarItem | null }\n\nconst MANAGE_FEATURE = 'customers.interactions.manage'\n\nfunction useCanManageInteractions(): boolean {\n const [canManage, setCanManage] = React.useState(false)\n React.useEffect(() => {\n const controller = new AbortController()\n let cancelled = false\n apiCall<{ granted?: unknown[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n body: JSON.stringify({ features: [MANAGE_FEATURE] }),\n })\n .then((call) => {\n if (cancelled || !call.ok) return\n const granted = Array.isArray(call.result?.granted)\n ? call.result.granted.map((feature) => String(feature))\n : []\n setCanManage(granted.some((grantedFeature) => matchFeature(MANAGE_FEATURE, grantedFeature)))\n })\n .catch(() => {\n if (!cancelled && !controller.signal.aborted) setCanManage(false)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [])\n return canManage\n}\n\nfunction buildTimezoneLabel(): string {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n const offsetMinutes = -new Date().getTimezoneOffset()\n const sign = offsetMinutes >= 0 ? '+' : '-'\n const absolute = Math.abs(offsetMinutes)\n const hours = Math.floor(absolute / 60)\n const minutes = absolute % 60\n const offset = minutes > 0 ? `${sign}${hours}:${String(minutes).padStart(2, '0')}` : `${sign}${hours}`\n return `${timeZone} (GMT${offset})`\n}\n\nfunction resolveJoinUrl(location: string | null): string | null {\n const trimmed = location?.trim() ?? ''\n if (!trimmed) return null\n if (/^https?:\\/\\//i.test(trimmed)) return trimmed\n if (/^www\\./i.test(trimmed)) return `https://${trimmed}`\n return null\n}\n\nfunction asEditableItem(item: CalendarItem): CalendarItem {\n return item.isRecurringOccurrence ? { ...item, id: item.raw.id } : item\n}\n\nexport function CalendarScreen() {\n const t = useT()\n const [view, setView] = React.useState<CalendarView>('week')\n const [anchor, setAnchor] = React.useState<Date>(() => new Date())\n const [agendaHorizonDays, setAgendaHorizonDays] = React.useState(DEFAULT_AGENDA_HORIZON_DAYS)\n const [preset, setPreset] = React.useState<CalendarRangePreset | null>('thisWeek')\n\n React.useEffect(() => {\n if (window.innerWidth >= PHONE_BREAKPOINT_PX) return\n setView('day')\n setPreset(null)\n }, [])\n const [tab, setTab] = React.useState<CalendarTab>('all')\n const [searchText, setSearchText] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filters, setFilters] = React.useState<CalendarFiltersValue>(EMPTY_FILTERS)\n const [editor, setEditor] = React.useState<EditorState>({ open: false, mode: 'create', item: null })\n const [editorMounted, setEditorMounted] = React.useState(false)\n const [createRange, setCreateRange] = React.useState<{ start: Date; end: Date } | null>(null)\n const [shortcutsOpen, setShortcutsOpen] = React.useState(false)\n const [settingsOpen, setSettingsOpen] = React.useState(false)\n const [highlightItemId, setHighlightItemId] = React.useState<string | null>(null)\n const [hasLoadedOnce, setHasLoadedOnce] = React.useState(false)\n const { preferences, setPreferences, hydrated: preferencesHydrated, userId: currentUserId } = useCalendarPreferences()\n\n const range = React.useMemo(\n () => getVisibleRange(view, anchor, agendaHorizonDays),\n [view, anchor, agendaHorizonDays],\n )\n const { items, isLoading, error, truncated, typeLabels, typeColors, refetch } = useCalendarItems(range)\n\n React.useEffect(() => {\n if (!isLoading) setHasLoadedOnce(true)\n }, [isLoading])\n\n React.useEffect(() => {\n const timer = window.setTimeout(() => setDebouncedSearch(searchText), SEARCH_DEBOUNCE_MS)\n return () => window.clearTimeout(timer)\n }, [searchText])\n\n React.useEffect(() => {\n if (!highlightItemId) return\n const timer = window.setTimeout(() => setHighlightItemId(null), HIGHLIGHT_CLEAR_MS)\n return () => window.clearTimeout(timer)\n }, [highlightItemId])\n\n const visibleItems = React.useMemo(\n () => items.filter((item) => item.end > range.from && item.start < range.to),\n [items, range],\n )\n\n const searchedItems = React.useMemo(() => {\n const query = debouncedSearch.trim().toLowerCase()\n if (!query) return visibleItems\n return visibleItems.filter((item) => {\n if (item.title.toLowerCase().includes(query)) return true\n if (item.location && item.location.toLowerCase().includes(query)) return true\n const rawBody = (item.raw as { body?: unknown }).body\n if (typeof rawBody === 'string' && rawBody.toLowerCase().includes(query)) return true\n return item.participants.some((participant) =>\n (participant.name ?? '').toLowerCase().includes(query),\n )\n })\n }, [visibleItems, debouncedSearch])\n\n const baseItems = React.useMemo(\n () =>\n searchedItems.filter((item) => {\n if (filters.types.length > 0 && !filters.types.includes(item.interactionType)) return false\n if (filters.status && item.status !== filters.status) return false\n if (filters.ownerUserId && item.ownerUserId !== filters.ownerUserId) return false\n if (!preferences.showCrmActivities && item.category !== 'meeting' && item.category !== 'event') return false\n return true\n }),\n [searchedItems, filters, preferences.showCrmActivities],\n )\n\n const tabCounts = React.useMemo(() => countByCategory(baseItems), [baseItems])\n\n const viewItems = React.useMemo(() => {\n if (tab === 'meetings') return baseItems.filter((item) => item.category === 'meeting')\n if (tab === 'events') return baseItems.filter((item) => item.category === 'event')\n return baseItems\n }, [baseItems, tab])\n\n const conflictMap = React.useMemo(\n () => findConflicts(baseItems, { scope: preferences.conflictScope, currentUserId }),\n [baseItems, preferences.conflictScope, currentUserId],\n )\n const conflictIds = React.useMemo(() => new Set(conflictMap.keys()), [conflictMap])\n\n const upcomingCards = React.useMemo<UpcomingCard[]>(() => {\n const now = new Date()\n const nowMs = now.getTime()\n return baseItems\n .filter((item) => item.start.getTime() >= nowMs)\n .sort((first, second) => first.start.getTime() - second.start.getTime())\n .slice(0, UPCOMING_CARDS_COUNT)\n .map((item) => {\n const conflictCount = preferences.conflictWarnings ? (conflictMap.get(item.id)?.length ?? 0) : 0\n const kind: UpcomingCard['kind'] =\n item.status === 'canceled'\n ? 'cancelled'\n : conflictCount > 0\n ? 'conflicted'\n : isSameDay(item.start, now)\n ? 'today'\n : 'future'\n return { item, kind, conflictCount }\n })\n }, [baseItems, conflictMap, preferences.conflictWarnings])\n\n const typeOptions = React.useMemo(() => {\n const values = new Set<string>(Object.keys(typeLabels))\n for (const item of items) values.add(item.interactionType)\n return [...values]\n .sort((a, b) => a.localeCompare(b))\n .map((value) => ({ value, label: typeLabels[value] ?? value }))\n }, [items, typeLabels])\n\n const ownerOptions = React.useMemo(() => {\n const participantNames = new Map<string, string>()\n for (const item of items) {\n for (const participant of item.participants) {\n if (participant.name && !participantNames.has(participant.userId)) {\n participantNames.set(participant.userId, participant.name)\n }\n }\n }\n const owners = new Map<string, string>()\n for (const item of items) {\n if (!item.ownerUserId || owners.has(item.ownerUserId)) continue\n owners.set(item.ownerUserId, participantNames.get(item.ownerUserId) ?? item.ownerUserId)\n }\n return [...owners.entries()]\n .map(([value, label]) => ({ value, label }))\n .sort((first, second) => first.label.localeCompare(second.label))\n }, [items])\n\n const timezoneLabel = React.useMemo(() => buildTimezoneLabel(), [])\n\n const canManage = useCanManageInteractions()\n\n const openCreateEditor = React.useCallback(() => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n }, [canManage])\n\n const openEditEditor = React.useCallback(\n (item: CalendarItem) => {\n if (!canManage) return\n setCreateRange(null)\n setEditorMounted(true)\n setEditor({ open: true, mode: 'edit', item: asEditableItem(item) })\n },\n [canManage],\n )\n\n const handleCreateRange = React.useCallback(\n (start: Date, end: Date) => {\n if (!canManage) return\n setCreateRange({ start, end })\n setEditorMounted(true)\n setEditor({ open: true, mode: 'create', item: null })\n },\n [canManage],\n )\n\n const seedActivityTypes = React.useMemo(() => {\n const seen = new Set<string>()\n const labels: string[] = []\n for (const value of Object.keys(typeLabels)) {\n const label = typeLabels[value] ?? value\n if (seen.has(label)) continue\n seen.add(label)\n labels.push(label)\n }\n return labels\n }, [typeLabels])\n\n const handleToday = React.useCallback(() => {\n setAnchor(new Date())\n setPreset(null)\n }, [])\n\n const handlePresetChange = React.useCallback((next: CalendarRangePreset) => {\n setPreset(next)\n setAnchor(new Date())\n if (next === 'thisWeek') {\n setView('week')\n } else if (next === 'thisMonth') {\n setView('month')\n } else {\n setView('agenda')\n setAgendaHorizonDays(next === 'next30' ? 30 : DEFAULT_AGENDA_HORIZON_DAYS)\n }\n }, [])\n\n const handleAnchorChange = React.useCallback((date: Date) => {\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleViewChange = React.useCallback((next: CalendarView) => {\n setView(next)\n setPreset(null)\n }, [])\n\n const handleTimeGridNavigate = React.useCallback((deltaDays: number) => {\n setAnchor((current) => addDays(current, deltaDays))\n setPreset(null)\n }, [])\n\n const handleDayOpen = React.useCallback((date: Date) => {\n setView('day')\n setAnchor(date)\n setPreset(null)\n }, [])\n\n const handleSeeConflict = React.useCallback((item: CalendarItem) => {\n setView('week')\n setAnchor(item.start)\n setPreset(null)\n setHighlightItemId(item.id)\n }, [])\n\n const handleJoin = React.useCallback((item: CalendarItem) => {\n const url = resolveJoinUrl(item.location)\n if (url) window.open(url, '_blank', 'noopener')\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-calendar-cancel',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const handleCancelItem = React.useCallback(\n async (item: CalendarItem) => {\n const interactionId = item.raw.id\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(item.updatedAt), () =>\n apiCallOrThrow('/api/customers/interactions', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: interactionId, status: 'canceled' }),\n }),\n ),\n mutationPayload: {\n operation: 'cancelCalendarEvent',\n interactionId,\n interactionType: item.interactionType,\n },\n context: {\n formId: 'customers-calendar-cancel',\n resourceKind: 'customers.interaction',\n resourceId: interactionId,\n retryLastMutation,\n },\n })\n flash(t('customers.calendar.cards.cancelSuccess', 'Event cancelled'), 'success')\n refetch()\n } catch (err) {\n // An optimistic-lock 409 is surfaced as the persistent conflict bar by\n // useGuardedMutation (surfaceRecordConflict) \u2014 don't re-flash.\n if (extractOptimisticLockConflict(err)) return\n flash(t('customers.calendar.cards.cancelError', 'Failed to cancel event'), 'error')\n }\n },\n [refetch, retryLastMutation, runMutation, t],\n )\n\n const focusSearch = React.useCallback(() => {\n const node = document.querySelector('[data-calendar-search]')\n if (node instanceof HTMLInputElement) node.focus()\n else if (node instanceof HTMLElement) node.querySelector('input')?.focus()\n }, [])\n\n const editorOpen = editor.open\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.defaultPrevented) return\n if (event.metaKey || event.ctrlKey || event.altKey) return\n const target = event.target\n if (target instanceof HTMLElement) {\n const tag = target.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) return\n }\n if (editorOpen) return\n switch (event.key) {\n case 't':\n case 'T':\n event.preventDefault()\n setAnchor(new Date())\n setPreset(null)\n break\n case 'd':\n case 'D':\n event.preventDefault()\n setView('day')\n setPreset(null)\n break\n case 'w':\n case 'W':\n event.preventDefault()\n setView('week')\n setPreset(null)\n break\n case 'm':\n case 'M':\n event.preventDefault()\n setView('month')\n setPreset(null)\n break\n case 'a':\n case 'A':\n event.preventDefault()\n setView('agenda')\n setPreset(null)\n break\n case 'n':\n case 'N':\n event.preventDefault()\n openCreateEditor()\n break\n case '/':\n event.preventDefault()\n focusSearch()\n break\n case '?':\n event.preventDefault()\n setShortcutsOpen((open) => !open)\n break\n case 'Escape':\n setHighlightItemId(null)\n break\n default:\n break\n }\n }\n window.addEventListener('keydown', handleKeyDown)\n return () => window.removeEventListener('keydown', handleKeyDown)\n }, [editorOpen, focusSearch, openCreateEditor])\n\n const showInitialLoading = (isLoading && !hasLoadedOnce) || !preferencesHydrated\n\n let viewArea: React.ReactNode\n if (error) {\n viewArea = (\n <ErrorMessage\n label={t('customers.calendar.errors.loadFailed', 'Failed to load calendar events.')}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={refetch}>\n {t('customers.calendar.errors.retry', 'Retry')}\n </Button>\n }\n />\n )\n } else if (showInitialLoading) {\n viewArea = <LoadingMessage label={t('customers.calendar.loading', 'Loading calendar\u2026')} />\n } else if (view === 'month') {\n viewArea = (\n <MonthGrid anchor={anchor} items={viewItems} onItemClick={openEditEditor} onDayOpen={handleDayOpen} />\n )\n } else if (view === 'agenda') {\n viewArea = (\n <AgendaList\n anchor={anchor}\n horizonDays={agendaHorizonDays}\n items={viewItems}\n typeLabels={typeLabels}\n onItemClick={openEditEditor}\n />\n )\n } else {\n viewArea = (\n <TimeGrid\n days={view === 'day' ? 1 : 7}\n anchor={anchor}\n items={viewItems}\n conflictIds={conflictIds}\n showWeekends={preferences.showWeekends}\n showConflicts={preferences.conflictWarnings}\n aiSummaries={preferences.aiSummaries}\n canManage={canManage}\n highlightItemId={highlightItemId}\n onItemClick={openEditEditor}\n onJoin={handleJoin}\n onNavigate={handleTimeGridNavigate}\n onCreate={canManage ? openCreateEditor : undefined}\n onCreateRange={canManage ? handleCreateRange : undefined}\n />\n )\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col gap-4\">\n <CalendarHeader view={view} anchor={anchor} onNewEvent={canManage ? openCreateEditor : undefined} />\n <CalendarToolbar\n view={view}\n anchor={anchor}\n range={range}\n preset={preset}\n search={searchText}\n filters={filters}\n typeOptions={typeOptions}\n ownerOptions={ownerOptions}\n onToday={handleToday}\n onPresetChange={handlePresetChange}\n onAnchorChange={handleAnchorChange}\n onSearchChange={setSearchText}\n onFiltersChange={setFilters}\n onOpenSettings={() => setSettingsOpen(true)}\n />\n <UpcomingCards\n cards={upcomingCards}\n canManage={canManage}\n onJoin={handleJoin}\n onSeeConflict={handleSeeConflict}\n onOpen={openEditEditor}\n onEdit={openEditEditor}\n onCancel={handleCancelItem}\n />\n <div className=\"flex min-h-0 flex-1 flex-col gap-3\">\n <CalendarTabs\n tab={tab}\n counts={tabCounts}\n view={view}\n onTabChange={setTab}\n onViewChange={handleViewChange}\n />\n {truncated ? (\n <p className=\"text-xs text-muted-foreground\" role=\"status\">\n {t('customers.calendar.notice.truncated', 'Showing first {count} items for this range.', {\n count: MAX_WINDOW_ITEMS,\n })}\n </p>\n ) : null}\n <div className=\"flex min-h-[560px] flex-1 flex-col [&>*]:flex-1\">{viewArea}</div>\n </div>\n <div className=\"hidden md:block\">\n <CalendarFooter timezoneLabel={timezoneLabel} onOpenShortcuts={() => setShortcutsOpen(true)} />\n </div>\n <ShortcutsDialog open={shortcutsOpen} onOpenChange={setShortcutsOpen} />\n <CalendarSettingsModal\n open={settingsOpen}\n preferences={preferences}\n seedActivityTypes={seedActivityTypes}\n onOpenChange={setSettingsOpen}\n onSave={(next) => {\n setPreferences(next)\n flash(t('customers.calendar.settings.saved', 'Calendar settings saved'), 'success')\n }}\n />\n {editorMounted ? (\n <CalendarEventEditor\n open={editor.open}\n mode={editor.mode}\n item={editor.item}\n defaultDate={anchor}\n defaultRange={createRange}\n typeLabels={typeLabels}\n typeColors={typeColors}\n surfacedTypes={preferences.activityTypes}\n eventCategories={preferences.eventCategories}\n conflictScope={preferences.conflictScope}\n currentUserId={currentUserId}\n onOpenChange={(open) => setEditor((current) => ({ ...current, open }))}\n onSaved={refetch}\n />\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,10 @@ import {
11
11
  isCredentialsEncryptionUnavailableError
12
12
  } from "../../../lib/credentials-service.js";
13
13
  import { collectCredentialUrlValidationErrors } from "../../../lib/credentials-field-validation.js";
14
+ import {
15
+ maskSecretCredentials,
16
+ mergeMaskedSecretCredentials
17
+ } from "../../../lib/credentials-masking.js";
14
18
  import {
15
19
  resolveUserFeatures,
16
20
  runIntegrationMutationGuardAfterSuccess,
@@ -60,10 +64,13 @@ async function GET(req, ctx) {
60
64
  }
61
65
  throw error;
62
66
  }
67
+ const schema = credentialsService.getSchema(integration.id);
68
+ const { credentials, secretFieldsConfigured } = maskSecretCredentials(schema, values ?? {});
63
69
  return NextResponse.json({
64
70
  integrationId: integration.id,
65
- schema: credentialsService.getSchema(integration.id),
66
- credentials: values ?? {},
71
+ schema,
72
+ credentials,
73
+ secretFieldsConfigured,
67
74
  updatedAt: updatedAt?.toISOString() ?? null
68
75
  });
69
76
  }
@@ -116,6 +123,7 @@ async function PUT(req, ctx) {
116
123
  }
117
124
  const credentialsService = container.resolve("integrationCredentialsService");
118
125
  const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
126
+ const schema = credentialsService.getSchema(integration.id);
119
127
  try {
120
128
  const currentUpdatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope);
121
129
  enforceCommandOptimisticLock({
@@ -134,7 +142,7 @@ async function PUT(req, ctx) {
134
142
  throw error;
135
143
  }
136
144
  const credentialFieldErrors = collectCredentialUrlValidationErrors(
137
- credentialsService.getSchema(integration.id),
145
+ schema,
138
146
  payloadData.credentials
139
147
  );
140
148
  if (Object.keys(credentialFieldErrors).length > 0) {
@@ -144,7 +152,9 @@ async function PUT(req, ctx) {
144
152
  );
145
153
  }
146
154
  try {
147
- await credentialsService.save(integration.id, payloadData.credentials, scope);
155
+ const existing = await credentialsService.resolve(integration.id, scope);
156
+ const credentialsToSave = mergeMaskedSecretCredentials(schema, payloadData.credentials, existing ?? {});
157
+ await credentialsService.save(integration.id, credentialsToSave, scope);
148
158
  } catch (error) {
149
159
  if (isCredentialsEncryptionUnavailableError(error)) {
150
160
  return NextResponse.json({ error: "Integration credentials encryption is unavailable" }, { status: 503 });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/integrations/api/%5Bid%5D/credentials/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { emitIntegrationsEvent } from '../../../events'\nimport { saveCredentialsSchema } from '../../../data/validators'\nimport {\n isCredentialsEncryptionUnavailableError,\n type CredentialsService,\n} from '../../../lib/credentials-service'\nimport { collectCredentialUrlValidationErrors } from '../../../lib/credentials-field-validation'\nimport {\n resolveUserFeatures,\n runIntegrationMutationGuardAfterSuccess,\n runIntegrationMutationGuards,\n} from '../../guards'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get or save integration credentials',\n}\n\nfunction resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }): Promise<{ id?: string } | undefined> | { id?: string } | undefined {\n if (!ctx.params) return undefined\n if (typeof (ctx.params as Promise<unknown>).then === 'function') {\n return ctx.params as Promise<{ id?: string }>\n }\n return ctx.params as { id?: string }\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n let values: Record<string, unknown> | null\n let updatedAt: Date | null\n try {\n values = await credentialsService.resolve(integration.id, scope)\n updatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n return NextResponse.json({\n integrationId: integration.id,\n schema: credentialsService.getSchema(integration.id),\n credentials: values ?? {},\n updatedAt: updatedAt?.toISOString() ?? null,\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = saveCredentialsSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid credentials payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runIntegrationMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsedBody.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n\n let payloadData = parsedBody.data\n if (guardResult.modifiedPayload) {\n const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }\n const reparsed = saveCredentialsSchema.safeParse(mergedPayload)\n if (!reparsed.success) {\n return NextResponse.json({ error: 'Invalid credentials payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })\n }\n payloadData = reparsed.data\n }\n\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n try {\n const currentUpdatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope)\n enforceCommandOptimisticLock({\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n const credentialFieldErrors = collectCredentialUrlValidationErrors(\n credentialsService.getSchema(integration.id),\n payloadData.credentials,\n )\n if (Object.keys(credentialFieldErrors).length > 0) {\n return NextResponse.json(\n { error: 'Invalid credentials payload', details: { fieldErrors: credentialFieldErrors } },\n { status: 422 },\n )\n }\n\n try {\n await credentialsService.save(integration.id, payloadData.credentials, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n await emitIntegrationsEvent('integrations.credentials.updated', {\n integrationId: integration.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({ ok: true })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { emitIntegrationsEvent } from '../../../events'\nimport { saveCredentialsSchema } from '../../../data/validators'\nimport {\n isCredentialsEncryptionUnavailableError,\n type CredentialsService,\n} from '../../../lib/credentials-service'\nimport { collectCredentialUrlValidationErrors } from '../../../lib/credentials-field-validation'\nimport {\n maskSecretCredentials,\n mergeMaskedSecretCredentials,\n} from '../../../lib/credentials-masking'\nimport {\n resolveUserFeatures,\n runIntegrationMutationGuardAfterSuccess,\n runIntegrationMutationGuards,\n} from '../../guards'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get or save integration credentials',\n}\n\nfunction resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }): Promise<{ id?: string } | undefined> | { id?: string } | undefined {\n if (!ctx.params) return undefined\n if (typeof (ctx.params as Promise<unknown>).then === 'function') {\n return ctx.params as Promise<{ id?: string }>\n }\n return ctx.params as { id?: string }\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n let values: Record<string, unknown> | null\n let updatedAt: Date | null\n try {\n values = await credentialsService.resolve(integration.id, scope)\n updatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n const schema = credentialsService.getSchema(integration.id)\n const { credentials, secretFieldsConfigured } = maskSecretCredentials(schema, values ?? {})\n\n return NextResponse.json({\n integrationId: integration.id,\n schema,\n credentials,\n secretFieldsConfigured,\n updatedAt: updatedAt?.toISOString() ?? null,\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = saveCredentialsSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid credentials payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runIntegrationMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsedBody.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n\n let payloadData = parsedBody.data\n if (guardResult.modifiedPayload) {\n const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }\n const reparsed = saveCredentialsSchema.safeParse(mergedPayload)\n if (!reparsed.success) {\n return NextResponse.json({ error: 'Invalid credentials payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })\n }\n payloadData = reparsed.data\n }\n\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n const schema = credentialsService.getSchema(integration.id)\n\n try {\n const currentUpdatedAt = await credentialsService.resolveUpdatedAt(integration.id, scope)\n enforceCommandOptimisticLock({\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n current: currentUpdatedAt,\n request: req,\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n const credentialFieldErrors = collectCredentialUrlValidationErrors(\n schema,\n payloadData.credentials,\n )\n if (Object.keys(credentialFieldErrors).length > 0) {\n return NextResponse.json(\n { error: 'Invalid credentials payload', details: { fieldErrors: credentialFieldErrors } },\n { status: 422 },\n )\n }\n\n try {\n // Secret fields are returned masked on GET; when the client round-trips the\n // mask sentinel it means \"unchanged\", so restore the existing stored secret\n // instead of overwriting it with the placeholder.\n const existing = await credentialsService.resolve(integration.id, scope)\n const credentialsToSave = mergeMaskedSecretCredentials(schema, payloadData.credentials, existing ?? {})\n await credentialsService.save(integration.id, credentialsToSave, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n await emitIntegrationsEvent('integrations.credentials.updated', {\n integrationId: integration.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({ ok: true })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,50 @@
1
+ const SECRET_CREDENTIAL_FIELD_TYPES = /* @__PURE__ */ new Set([
2
+ "secret",
3
+ "oauth",
4
+ "ssh_keypair"
5
+ ]);
6
+ const MASKED_SECRET_VALUE = "__om_secret_unchanged__";
7
+ function isSecretField(type) {
8
+ return SECRET_CREDENTIAL_FIELD_TYPES.has(type);
9
+ }
10
+ function hasPresentValue(value) {
11
+ if (value === void 0 || value === null) return false;
12
+ if (typeof value === "string") return value.length > 0;
13
+ if (typeof value === "object") return Object.keys(value).length > 0;
14
+ return true;
15
+ }
16
+ function maskSecretCredentials(schema, values) {
17
+ const credentials = { ...values };
18
+ const secretFieldsConfigured = {};
19
+ for (const field of schema?.fields ?? []) {
20
+ if (!isSecretField(field.type)) continue;
21
+ const configured = hasPresentValue(credentials[field.key]);
22
+ secretFieldsConfigured[field.key] = configured;
23
+ if (configured) {
24
+ credentials[field.key] = MASKED_SECRET_VALUE;
25
+ } else {
26
+ delete credentials[field.key];
27
+ }
28
+ }
29
+ return { credentials, secretFieldsConfigured };
30
+ }
31
+ function mergeMaskedSecretCredentials(schema, incoming, existing) {
32
+ const merged = { ...incoming };
33
+ for (const field of schema?.fields ?? []) {
34
+ if (!isSecretField(field.type)) continue;
35
+ if (merged[field.key] !== MASKED_SECRET_VALUE) continue;
36
+ if (hasPresentValue(existing[field.key])) {
37
+ merged[field.key] = existing[field.key];
38
+ } else {
39
+ delete merged[field.key];
40
+ }
41
+ }
42
+ return merged;
43
+ }
44
+ export {
45
+ MASKED_SECRET_VALUE,
46
+ SECRET_CREDENTIAL_FIELD_TYPES,
47
+ maskSecretCredentials,
48
+ mergeMaskedSecretCredentials
49
+ };
50
+ //# sourceMappingURL=credentials-masking.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/integrations/lib/credentials-masking.ts"],
4
+ "sourcesContent": ["import type {\n CredentialFieldType,\n IntegrationCredentialsSchema,\n} from '@open-mercato/shared/modules/integrations/types'\n\n/**\n * Credential field types whose stored value is a secret (API keys, OAuth client\n * secrets/tokens, SSH private keys). These MUST never be returned in plaintext\n * from the credentials API \u2014 they are masked on read and treated as write-only.\n */\nexport const SECRET_CREDENTIAL_FIELD_TYPES: ReadonlySet<CredentialFieldType> = new Set<CredentialFieldType>([\n 'secret',\n 'oauth',\n 'ssh_keypair',\n])\n\n/**\n * Opaque sentinel returned in place of a configured secret value. The client\n * round-trips this value back on save when the user did not change the field;\n * the PUT handler then preserves the existing stored secret instead of writing\n * the sentinel. Chosen to be extremely unlikely to collide with a real secret.\n */\nexport const MASKED_SECRET_VALUE = '__om_secret_unchanged__'\n\nfunction isSecretField(type: CredentialFieldType): boolean {\n return SECRET_CREDENTIAL_FIELD_TYPES.has(type)\n}\n\nfunction hasPresentValue(value: unknown): boolean {\n if (value === undefined || value === null) return false\n if (typeof value === 'string') return value.length > 0\n if (typeof value === 'object') return Object.keys(value as Record<string, unknown>).length > 0\n return true\n}\n\nexport type MaskSecretCredentialsResult = {\n credentials: Record<string, unknown>\n secretFieldsConfigured: Record<string, boolean>\n}\n\n/**\n * Replace every configured secret-typed field with an opaque sentinel so the\n * decrypted plaintext never reaches the API response (and therefore the\n * browser, devtools, proxies, or editable DOM inputs). Non-secret config fields\n * pass through unchanged. A `secretFieldsConfigured` map reports which secret\n * fields currently hold a value without exposing it.\n */\nexport function maskSecretCredentials(\n schema: IntegrationCredentialsSchema | undefined,\n values: Record<string, unknown>,\n): MaskSecretCredentialsResult {\n const credentials: Record<string, unknown> = { ...values }\n const secretFieldsConfigured: Record<string, boolean> = {}\n\n for (const field of schema?.fields ?? []) {\n if (!isSecretField(field.type)) continue\n const configured = hasPresentValue(credentials[field.key])\n secretFieldsConfigured[field.key] = configured\n if (configured) {\n credentials[field.key] = MASKED_SECRET_VALUE\n } else {\n delete credentials[field.key]\n }\n }\n\n return { credentials, secretFieldsConfigured }\n}\n\n/**\n * Reverse of {@link maskSecretCredentials} for the save path. When the client\n * submits the mask sentinel for a secret field it means \"leave it unchanged\":\n * restore the existing stored secret, or drop the field entirely when nothing\n * was previously stored (so the literal sentinel is never persisted). Any other\n * value (including an empty string, which clears the secret) is written as-is.\n */\nexport function mergeMaskedSecretCredentials(\n schema: IntegrationCredentialsSchema | undefined,\n incoming: Record<string, unknown>,\n existing: Record<string, unknown>,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...incoming }\n\n for (const field of schema?.fields ?? []) {\n if (!isSecretField(field.type)) continue\n if (merged[field.key] !== MASKED_SECRET_VALUE) continue\n\n if (hasPresentValue(existing[field.key])) {\n merged[field.key] = existing[field.key]\n } else {\n delete merged[field.key]\n }\n }\n\n return merged\n}\n"],
5
+ "mappings": "AAUO,MAAM,gCAAkE,oBAAI,IAAyB;AAAA,EAC1G;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAQM,MAAM,sBAAsB;AAEnC,SAAS,cAAc,MAAoC;AACzD,SAAO,8BAA8B,IAAI,IAAI;AAC/C;AAEA,SAAS,gBAAgB,OAAyB;AAChD,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,SAAS;AACrD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,KAAgC,EAAE,SAAS;AAC7F,SAAO;AACT;AAcO,SAAS,sBACd,QACA,QAC6B;AAC7B,QAAM,cAAuC,EAAE,GAAG,OAAO;AACzD,QAAM,yBAAkD,CAAC;AAEzD,aAAW,SAAS,QAAQ,UAAU,CAAC,GAAG;AACxC,QAAI,CAAC,cAAc,MAAM,IAAI,EAAG;AAChC,UAAM,aAAa,gBAAgB,YAAY,MAAM,GAAG,CAAC;AACzD,2BAAuB,MAAM,GAAG,IAAI;AACpC,QAAI,YAAY;AACd,kBAAY,MAAM,GAAG,IAAI;AAAA,IAC3B,OAAO;AACL,aAAO,YAAY,MAAM,GAAG;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,uBAAuB;AAC/C;AASO,SAAS,6BACd,QACA,UACA,UACyB;AACzB,QAAM,SAAkC,EAAE,GAAG,SAAS;AAEtD,aAAW,SAAS,QAAQ,UAAU,CAAC,GAAG;AACxC,QAAI,CAAC,cAAc,MAAM,IAAI,EAAG;AAChC,QAAI,OAAO,MAAM,GAAG,MAAM,oBAAqB;AAE/C,QAAI,gBAAgB,SAAS,MAAM,GAAG,CAAC,GAAG;AACxC,aAAO,MAAM,GAAG,IAAI,SAAS,MAAM,GAAG;AAAA,IACxC,OAAO;AACL,aAAO,OAAO,MAAM,GAAG;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,10 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { resolveRequestContext } from "@open-mercato/shared/lib/api/context";
3
3
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
- import {
5
- runCrudMutationGuardAfterSuccess,
6
- validateCrudMutationGuard
7
- } from "@open-mercato/shared/lib/crud/mutation-guard";
4
+ import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
8
5
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
6
  import { resolveNotificationService } from "./notificationService.js";
10
7
  const NOTIFICATION_RESOURCE_KIND = "notifications.notification";
@@ -41,30 +38,26 @@ async function resolveNotificationContext(req) {
41
38
  };
42
39
  }
43
40
  async function runGuardedNotificationWrite(container, scope, req, options, write) {
44
- const guardScope = {
45
- tenantId: scope.tenantId,
46
- organizationId: scope.organizationId,
47
- userId: scope.userId ?? "",
48
- resourceKind: options.resourceKind,
49
- resourceId: options.resourceId ?? "",
50
- operation: options.operation,
51
- requestMethod: req.method,
52
- requestHeaders: req.headers
53
- };
54
- const guardResult = await validateCrudMutationGuard(container, {
55
- ...guardScope,
56
- mutationPayload: options.payload ?? null
41
+ const guarded = await runRouteMutationGuards({
42
+ container,
43
+ req,
44
+ auth: {
45
+ userId: scope.userId ?? "",
46
+ tenantId: scope.tenantId,
47
+ organizationId: scope.organizationId
48
+ },
49
+ input: {
50
+ resourceKind: options.resourceKind,
51
+ resourceId: options.resourceId ?? null,
52
+ operation: options.operation,
53
+ mutationPayload: options.payload ?? null
54
+ }
57
55
  });
58
- if (guardResult && !guardResult.ok) {
59
- return { ok: false, response: Response.json(guardResult.body, { status: guardResult.status }) };
56
+ if (!guarded.ok) {
57
+ return { ok: false, response: guarded.response };
60
58
  }
61
59
  const result = await write();
62
- if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
63
- await runCrudMutationGuardAfterSuccess(container, {
64
- ...guardScope,
65
- metadata: guardResult.metadata ?? null
66
- });
67
- }
60
+ await guarded.runAfterSuccess();
68
61
  return { ok: true, result };
69
62
  }
70
63
  function createBulkNotificationRoute(schema, serviceMethod) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/lib/routeHelpers.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveNotificationService, type NotificationService } from './notificationService'\n\n/**\n * Mutation-guard resource kind for notification rows.\n */\nexport const NOTIFICATION_RESOURCE_KIND = 'notifications.notification'\n\n/**\n * Mutation-guard resource kind for notification delivery settings.\n */\nexport const NOTIFICATION_SETTINGS_RESOURCE_KIND = 'notifications.settings'\n\n/**\n * Notification scope context for service calls\n */\nexport interface NotificationScope {\n tenantId: string\n organizationId: string | null\n userId: string | null\n}\n\n/**\n * Resolved notification context from a request\n */\nexport interface NotificationRequestContext {\n service: NotificationService\n scope: NotificationScope\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n}\n\nfunction formatZodIssues(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\nexport async function notificationValidationErrorResponse(error: z.ZodError): Promise<Response> {\n const { t } = await resolveTranslations()\n const prefix = t('api.errors.invalidPayload', 'Invalid request body')\n const details = formatZodIssues(error)\n return Response.json(\n { error: details ? `${prefix}: ${details}` : prefix },\n { status: 400 },\n )\n}\n\nexport function notificationCrudErrorResponse(error: unknown): Response | null {\n if (!isCrudHttpError(error)) return null\n return Response.json(error.body ?? { error: 'Notification request failed' }, { status: error.status })\n}\n\n/**\n * Resolve notification service and scope from a request.\n * Centralizes the common pattern used across all notification API routes.\n */\nexport async function resolveNotificationContext(req: Request): Promise<NotificationRequestContext> {\n const { ctx } = await resolveRequestContext(req)\n return {\n service: resolveNotificationService(ctx.container),\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? null,\n userId: ctx.auth?.sub ?? null,\n },\n ctx,\n }\n}\n\n/**\n * Mutation-guard options for a notification write.\n */\nexport interface NotificationMutationGuardOptions {\n resourceKind: string\n resourceId?: string | null\n operation: 'create' | 'update' | 'delete' | 'custom'\n payload?: Record<string, unknown> | null\n}\n\nexport type GuardedNotificationWriteResult<T> =\n | { ok: true; result: T }\n | { ok: false; response: Response }\n\n/**\n * Run a notification write through the mutation guard lifecycle.\n * Validates before the mutation, performs the write, then runs after-success\n * hooks only when the write succeeded and the guard requested them. Returns the\n * guard's own block response when validation fails so authorization behavior and\n * conflict shapes are preserved.\n */\nexport async function runGuardedNotificationWrite<T>(\n container: AwilixContainer,\n scope: NotificationScope,\n req: Request,\n options: NotificationMutationGuardOptions,\n write: () => Promise<T>,\n): Promise<GuardedNotificationWriteResult<T>> {\n const guardScope = {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId ?? '',\n resourceKind: options.resourceKind,\n resourceId: options.resourceId ?? '',\n operation: options.operation,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n ...guardScope,\n mutationPayload: options.payload ?? null,\n })\n if (guardResult && !guardResult.ok) {\n return { ok: false, response: Response.json(guardResult.body, { status: guardResult.status }) }\n }\n\n const result = await write()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n ...guardScope,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return { ok: true, result }\n}\n\n/**\n * Create a POST handler for bulk notification creation routes.\n * Used by batch, role, and feature notification endpoints.\n */\nexport function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n serviceMethod: 'createBatch' | 'createForRole' | 'createForFeature'\n) {\n return async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = schema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service[serviceMethod](parsed.data as never, scope),\n )\n if (!guarded.ok) return guarded.response\n const notifications = guarded.result\n\n return Response.json({\n ok: true,\n count: notifications.length,\n ids: notifications.map((n) => n.id),\n }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n }\n}\n\n/**\n * Create OpenAPI spec for bulk notification creation routes.\n */\nexport function createBulkNotificationOpenApi<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n summary: string,\n description?: string\n) {\n return {\n POST: {\n summary,\n description,\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema,\n },\n },\n },\n responses: {\n 201: {\n description: 'Notifications created',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n ids: z.array(z.string().uuid()),\n }),\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * Create a PUT handler for single notification action routes.\n * Used by read and dismiss endpoints.\n */\nexport function createSingleNotificationActionRoute(\n serviceMethod: 'markAsRead' | 'dismiss'\n) {\n return async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'update',\n },\n () => service[serviceMethod](id, scope),\n )\n if (!guarded.ok) return guarded.response\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n\n return Response.json({ ok: true })\n }\n}\n\n/**\n * Create OpenAPI spec for single notification action routes.\n */\nexport function createSingleNotificationActionOpenApi(\n summary: string,\n description: string\n) {\n return {\n PUT: {\n summary,\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n responses: {\n 200: {\n description,\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,6BAA6B;AACtC,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,SAAS,kCAA4D;AAK9D,MAAM,6BAA6B;AAKnC,MAAM,sCAAsC;AAoBnD,SAAS,gBAAgB,OAA2B;AAClD,SAAO,MAAM,OACV,IAAI,CAAC,UAAU;AACd,UAAM,OAAO,MAAM,KAAK,SAAS,GAAG,MAAM,KAAK,KAAK,GAAG,CAAC,OAAO;AAC/D,WAAO,GAAG,IAAI,GAAG,MAAM,OAAO;AAAA,EAChC,CAAC,EACA,KAAK,IAAI;AACd;AAEA,eAAsB,oCAAoC,OAAsC;AAC9F,QAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,QAAM,SAAS,EAAE,6BAA6B,sBAAsB;AACpE,QAAM,UAAU,gBAAgB,KAAK;AACrC,SAAO,SAAS;AAAA,IACd,EAAE,OAAO,UAAU,GAAG,MAAM,KAAK,OAAO,KAAK,OAAO;AAAA,IACpD,EAAE,QAAQ,IAAI;AAAA,EAChB;AACF;AAEO,SAAS,8BAA8B,OAAiC;AAC7E,MAAI,CAAC,gBAAgB,KAAK,EAAG,QAAO;AACpC,SAAO,SAAS,KAAK,MAAM,QAAQ,EAAE,OAAO,8BAA8B,GAAG,EAAE,QAAQ,MAAM,OAAO,CAAC;AACvG;AAMA,eAAsB,2BAA2B,KAAmD;AAClG,QAAM,EAAE,IAAI,IAAI,MAAM,sBAAsB,GAAG;AAC/C,SAAO;AAAA,IACL,SAAS,2BAA2B,IAAI,SAAS;AAAA,IACjD,OAAO;AAAA,MACL,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,0BAA0B;AAAA,MAC9C,QAAQ,IAAI,MAAM,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,EACF;AACF;AAuBA,eAAsB,4BACpB,WACA,OACA,KACA,SACA,OAC4C;AAC5C,QAAM,aAAa;AAAA,IACjB,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,QAAQ,MAAM,UAAU;AAAA,IACxB,cAAc,QAAQ;AAAA,IACtB,YAAY,QAAQ,cAAc;AAAA,IAClC,WAAW,QAAQ;AAAA,IACnB,eAAe,IAAI;AAAA,IACnB,gBAAgB,IAAI;AAAA,EACtB;AAEA,QAAM,cAAc,MAAM,0BAA0B,WAAW;AAAA,IAC7D,GAAG;AAAA,IACH,iBAAiB,QAAQ,WAAW;AAAA,EACtC,CAAC;AACD,MAAI,eAAe,CAAC,YAAY,IAAI;AAClC,WAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,YAAY,MAAM,EAAE,QAAQ,YAAY,OAAO,CAAC,EAAE;AAAA,EAChG;AAEA,QAAM,SAAS,MAAM,MAAM;AAE3B,MAAI,aAAa,MAAM,YAAY,uBAAuB;AACxD,UAAM,iCAAiC,WAAW;AAAA,MAChD,GAAG;AAAA,MACH,UAAU,YAAY,YAAY;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,IAAI,MAAM,OAAO;AAC5B;AAMO,SAAS,4BACd,QACA,eACA;AACA,SAAO,eAAe,KAAK,KAAc;AACvC,UAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,SAAS,OAAO,UAAU,IAAI;AACpC,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,oCAAoC,OAAO,KAAK;AAAA,IACzD;AAEA,QAAI;AACF,YAAM,UAAU,MAAM;AAAA,QACpB,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,UACE,cAAc;AAAA,UACd,WAAW;AAAA,UACX,SAAS,OAAO;AAAA,QAClB;AAAA,QACA,MAAM,QAAQ,aAAa,EAAE,OAAO,MAAe,KAAK;AAAA,MAC1D;AACA,UAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAChC,YAAM,gBAAgB,QAAQ;AAE9B,aAAO,SAAS,KAAK;AAAA,QACnB,IAAI;AAAA,QACJ,OAAO,cAAc;AAAA,QACrB,KAAK,cAAc,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,MACpC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpB,SAAS,OAAO;AACd,YAAM,gBAAgB,8BAA8B,KAAK;AACzD,UAAI,cAAe,QAAO;AAC1B,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAKO,SAAS,8BACd,QACA,SACA,aACA;AACA,SAAO;AAAA,IACL,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,MAAM,CAAC,eAAe;AAAA,MACtB,aAAa;AAAA,QACX,UAAU;AAAA,QACV,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT,KAAK;AAAA,UACH,aAAa;AAAA,UACb,SAAS;AAAA,YACP,oBAAoB;AAAA,cAClB,QAAQ,EAAE,OAAO;AAAA,gBACf,IAAI,EAAE,QAAQ;AAAA,gBACd,OAAO,EAAE,OAAO;AAAA,gBAChB,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;AAAA,cAChC,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,oCACd,eACA;AACA,SAAO,eAAe,IAAI,KAAc,EAAE,OAAO,GAAwC;AACvF,UAAM,EAAE,GAAG,IAAI,MAAM;AACrB,UAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAI;AACF,YAAM,UAAU,MAAM;AAAA,QACpB,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,UACE,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,WAAW;AAAA,QACb;AAAA,QACA,MAAM,QAAQ,aAAa,EAAE,IAAI,KAAK;AAAA,MACxC;AACA,UAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAAA,IAClC,SAAS,OAAO;AACd,YAAM,gBAAgB,8BAA8B,KAAK;AACzD,UAAI,cAAe,QAAO;AAC1B,YAAM;AAAA,IACR;AAEA,WAAO,SAAS,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACnC;AACF;AAKO,SAAS,sCACd,SACA,aACA;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH;AAAA,MACA,MAAM,CAAC,eAAe;AAAA,MACtB,YAAY;AAAA,QACV;AAAA,UACE,MAAM;AAAA,UACN,IAAI;AAAA,UACJ,UAAU;AAAA,UACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,QAC3C;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT,KAAK;AAAA,UACH;AAAA,UACA,SAAS;AAAA,YACP,oBAAoB;AAAA,cAClB,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAAA,YACtC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { AwilixContainer } from 'awilix'\nimport { resolveRequestContext } from '@open-mercato/shared/lib/api/context'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveNotificationService, type NotificationService } from './notificationService'\n\n/**\n * Mutation-guard resource kind for notification rows.\n */\nexport const NOTIFICATION_RESOURCE_KIND = 'notifications.notification'\n\n/**\n * Mutation-guard resource kind for notification delivery settings.\n */\nexport const NOTIFICATION_SETTINGS_RESOURCE_KIND = 'notifications.settings'\n\n/**\n * Notification scope context for service calls\n */\nexport interface NotificationScope {\n tenantId: string\n organizationId: string | null\n userId: string | null\n}\n\n/**\n * Resolved notification context from a request\n */\nexport interface NotificationRequestContext {\n service: NotificationService\n scope: NotificationScope\n ctx: Awaited<ReturnType<typeof resolveRequestContext>>['ctx']\n}\n\nfunction formatZodIssues(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\nexport async function notificationValidationErrorResponse(error: z.ZodError): Promise<Response> {\n const { t } = await resolveTranslations()\n const prefix = t('api.errors.invalidPayload', 'Invalid request body')\n const details = formatZodIssues(error)\n return Response.json(\n { error: details ? `${prefix}: ${details}` : prefix },\n { status: 400 },\n )\n}\n\nexport function notificationCrudErrorResponse(error: unknown): Response | null {\n if (!isCrudHttpError(error)) return null\n return Response.json(error.body ?? { error: 'Notification request failed' }, { status: error.status })\n}\n\n/**\n * Resolve notification service and scope from a request.\n * Centralizes the common pattern used across all notification API routes.\n */\nexport async function resolveNotificationContext(req: Request): Promise<NotificationRequestContext> {\n const { ctx } = await resolveRequestContext(req)\n return {\n service: resolveNotificationService(ctx.container),\n scope: {\n tenantId: ctx.auth?.tenantId ?? '',\n organizationId: ctx.selectedOrganizationId ?? null,\n userId: ctx.auth?.sub ?? null,\n },\n ctx,\n }\n}\n\n/**\n * Mutation-guard options for a notification write.\n */\nexport interface NotificationMutationGuardOptions {\n resourceKind: string\n resourceId?: string | null\n operation: 'create' | 'update' | 'delete' | 'custom'\n payload?: Record<string, unknown> | null\n}\n\nexport type GuardedNotificationWriteResult<T> =\n | { ok: true; result: T }\n | { ok: false; response: Response }\n\n/**\n * Run a notification write through the mutation guard lifecycle.\n * Validates before the mutation, performs the write, then runs after-success\n * hooks only when the write succeeded and the guard requested them. Returns the\n * guard's own block response when validation fails so authorization behavior and\n * conflict shapes are preserved.\n */\nexport async function runGuardedNotificationWrite<T>(\n container: AwilixContainer,\n scope: NotificationScope,\n req: Request,\n options: NotificationMutationGuardOptions,\n write: () => Promise<T>,\n): Promise<GuardedNotificationWriteResult<T>> {\n const guarded = await runRouteMutationGuards({\n container,\n req,\n auth: {\n userId: scope.userId ?? '',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n input: {\n resourceKind: options.resourceKind,\n resourceId: options.resourceId ?? null,\n operation: options.operation,\n mutationPayload: options.payload ?? null,\n },\n })\n if (!guarded.ok) {\n return { ok: false, response: guarded.response }\n }\n\n const result = await write()\n\n await guarded.runAfterSuccess()\n\n return { ok: true, result }\n}\n\n/**\n * Create a POST handler for bulk notification creation routes.\n * Used by batch, role, and feature notification endpoints.\n */\nexport function createBulkNotificationRoute<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n serviceMethod: 'createBatch' | 'createForRole' | 'createForFeature'\n) {\n return async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = schema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service[serviceMethod](parsed.data as never, scope),\n )\n if (!guarded.ok) return guarded.response\n const notifications = guarded.result\n\n return Response.json({\n ok: true,\n count: notifications.length,\n ids: notifications.map((n) => n.id),\n }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n }\n}\n\n/**\n * Create OpenAPI spec for bulk notification creation routes.\n */\nexport function createBulkNotificationOpenApi<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n summary: string,\n description?: string\n) {\n return {\n POST: {\n summary,\n description,\n tags: ['Notifications'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema,\n },\n },\n },\n responses: {\n 201: {\n description: 'Notifications created',\n content: {\n 'application/json': {\n schema: z.object({\n ok: z.boolean(),\n count: z.number(),\n ids: z.array(z.string().uuid()),\n }),\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * Create a PUT handler for single notification action routes.\n * Used by read and dismiss endpoints.\n */\nexport function createSingleNotificationActionRoute(\n serviceMethod: 'markAsRead' | 'dismiss'\n) {\n return async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'update',\n },\n () => service[serviceMethod](id, scope),\n )\n if (!guarded.ok) return guarded.response\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n\n return Response.json({ ok: true })\n }\n}\n\n/**\n * Create OpenAPI spec for single notification action routes.\n */\nexport function createSingleNotificationActionOpenApi(\n summary: string,\n description: string\n) {\n return {\n PUT: {\n summary,\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n responses: {\n 200: {\n description,\n content: {\n 'application/json': {\n schema: z.object({ ok: z.boolean() }),\n },\n },\n },\n },\n },\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -243,7 +243,7 @@ function PaymentTransactionsPage() {
243
243
  }
244
244
  }, [loadDetail, loadRows, runMutation, selectedId, t]);
245
245
  const providerOptions = React.useMemo(() => {
246
- const values = Array.from(new Set(rows.map((row) => row.providerKey).filter(Boolean))).sort();
246
+ const values = Array.from(new Set(rows.map((row) => row.providerKey).filter(Boolean))).sort((a, b) => a.localeCompare(b));
247
247
  return values.map((value) => ({
248
248
  label: formatTypeLabel(value),
249
249
  value