From b1a3461c333d252157bc35e7170fa4668cb0dcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=D1=81=20=D0=A1=D1=83=D0=BB=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Tue, 8 Sep 2026 00:27:17 +0300 Subject: [PATCH] =?UTF-8?q?feat(onboarding+modules):=20=D1=87=D0=B5=D0=BA?= =?UTF-8?q?=D0=BB=D0=B8=D1=81=D1=82=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B9=20org?= =?UTF-8?q?,=20MedSchedule=20=D0=B7=D0=B0=20=D1=84=D0=BB=D0=B0=D0=B3=D0=BE?= =?UTF-8?q?=D0=BC,=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20Ca?= =?UTF-8?q?pacitor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шаги 1.9 и 1.10 плана production-готовности: - OnboardingChecklist (форма→коллеги→задача) на главной, фикс EmptyState в Tasks - organizations.medschedule_enabled (миграция 0081), гейт модуля по флагу - Capacitor-стек удалён (5 зависимостей, push-notifications.ts, capacitor.config) - check/lint/build чисто, vitest 96/96 --- IMPLEMENTATION_LOG.md | 21 + capacitor.config.ts | 18 - client/src/App.tsx | 2 - client/src/components/OnboardingChecklist.tsx | 111 +++ client/src/components/Sidebar.tsx | 4 +- client/src/lib/push-notifications.ts | 93 --- client/src/pages/Home.tsx | 4 + client/src/pages/Tasks.tsx | 40 +- client/src/pages/medschedule/index.tsx | 22 + client/src/types/auth.types.ts | 1 + migrations/0081_medschedule_org_flag.sql | 5 + package-lock.json | 786 +----------------- package.json | 5 - server/medschedule/routes.ts | 18 +- server/routes/auth.core.routes.ts | 3 +- shared/schema.ts | 1 + 16 files changed, 213 insertions(+), 921 deletions(-) delete mode 100644 capacitor.config.ts create mode 100644 client/src/components/OnboardingChecklist.tsx delete mode 100644 client/src/lib/push-notifications.ts create mode 100644 migrations/0081_medschedule_org_flag.sql diff --git a/IMPLEMENTATION_LOG.md b/IMPLEMENTATION_LOG.md index 1765480..c4f7032 100644 --- a/IMPLEMENTATION_LOG.md +++ b/IMPLEMENTATION_LOG.md @@ -277,3 +277,24 @@ - Что изменено: `DOCKER.md` — раздел «Бэкапы PostgreSQL (production)»: расписание (3:00 pg_dump + 3:30 CSV, ротация 14/7), RPO 24ч/RTO 15–30 мин, команды восстановления, что НЕ входит в дамп (MinIO/Ollama/crm_data), риск «бэкапы на том же VPS» (выгрузка наружу — в бэклог). - Как проверялось: восстановление свежего дампа (2026-09-07 03:00, 11 МБ) в scratch-БД на сервере — 0 ошибок pg_restore, 125 таблиц, 990 задач/49 пользователей/14 форм/6024 значений полей, последняя миграция 0078 (дамп предшествует 0079/0080 — консистентно). Scratch-БД удалена. - Влияние на поиск/UX: нет. + +--- + +## [1.9] Onboarding + +- Статус: ✅ done +- Зачем: новая организация не попадает в тупик (Фаза 1). +- Что изменено: `client/src/components/OnboardingChecklist.tsx` — чеклист «Начало работы · N из 3» (форма → коллеги → задача), состояние из реальных данных, скрытие per org (localStorage); встроен в Home.tsx. `client/src/pages/Tasks.tsx` — при отсутствии форм EmptyState с кнопкой «Создать форму» вместо задизейбленной «Создать задачу». +- Как проверялось: `npm run check`/lint/build чисто; vitest 96/96. +- Влияние на поиск/UX: нет (новая плашка только для пустых организаций). + +## [1.10] Модули и мёртвый код + +- Статус: ✅ done +- Зачем: MedSchedule не в ядре без флага; мёртвый Capacitor-стек удалён (Фаза 1). +- Что изменено: + - MedSchedule за org-флагом `organizations.medschedule_enabled` (миграция 0081, default FALSE, TRUE для slug='medschedule'; выбран org-флаг как наименее инвазивный и соответствующий прежнему org-ориентированному гейту, admin-override сохранён): `/api/auth/me` отдаёт флаг, серверный гейт requireMedOrganization, сайдбар и страница модуля по флагу. Worker оставлен (работает только с org medschedule). + - Capacitor удалён полностью: capacitor.config.ts, client/src/lib/push-notifications.ts (единственный файл с импортами @capacitor/*), вызов в App.tsx, 5 зависимостей @capacitor/* из package.json. Expo (mobile/) не тронут — рабочее. Web-Push через @/lib/pwa остался. +- Как проверялось: grep — упоминаний Capacitor не осталось (и в lockfile); `npm run check`/lint/build чисто; vitest 96/96. +- Влияние на поиск/UX: нет. +- Подводные камни: включение MedSchedule для другой org — SQL UPDATE (UI-переключателя нет, как у GPS-флагов до матрицы). diff --git a/capacitor.config.ts b/capacitor.config.ts deleted file mode 100644 index c52b300..0000000 --- a/capacitor.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CapacitorConfig } from '@capacitor/cli'; - -const config: CapacitorConfig = { - appId: 'com.iistwin.app', - appName: 'iistwin', - webDir: 'dist/public', - server: { - androidScheme: 'https', - iosScheme: 'https', - }, - plugins: { - PushNotifications: { - presentationOptions: ['badge', 'sound', 'alert'], - }, - }, -}; - -export default config; diff --git a/client/src/App.tsx b/client/src/App.tsx index ed31657..7563df4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -8,7 +8,6 @@ import { useAuthProvider, AuthContext } from "@/hooks/useAuth"; import { ThemeProvider } from "@/themes/ThemeProvider"; import { OfflineSyncProvider } from "@/contexts/OfflineSyncContext"; import { useEffect, lazy, Suspense, useCallback, type ComponentType, type LazyExoticComponent } from "react"; -import { initializePushNotifications } from "@/lib/push-notifications"; import { initPWA, initWebPush } from "@/lib/pwa"; import { useToast } from "@/hooks/use-toast"; import { usePrefetchOfflineTasks } from "@/hooks/usePrefetchOfflineTasks"; @@ -284,7 +283,6 @@ function App() { useEffect(() => { if (auth.isAuthenticated) { - initializePushNotifications().catch(console.error); initWebPush().catch(console.error); } }, [auth.isAuthenticated]); diff --git a/client/src/components/OnboardingChecklist.tsx b/client/src/components/OnboardingChecklist.tsx new file mode 100644 index 0000000..2496147 --- /dev/null +++ b/client/src/components/OnboardingChecklist.tsx @@ -0,0 +1,111 @@ +import { useState } from 'react'; +import { Link } from 'wouter'; +import { useQuery } from '@tanstack/react-query'; +import { CheckCircle2, Circle, X, Sparkles } from 'lucide-react'; +import { useAuth } from '@/hooks/useAuth'; +import { apiRequest } from '@/lib/queryClient'; +import { cn } from '@/lib/utils'; + +interface OnboardingStep { + key: string; + label: string; + href: string; + done: boolean; +} + +/** + * Ненавязчивый чеклист первых шагов для новой организации. + * Состояние шагов вычисляется из реальных данных (формы / пользователи / задачи). + * Скрывается, когда всё выполнено, либо вручную (localStorage per org). + */ +export function OnboardingChecklist() { + const { user } = useAuth(); + const orgId = user?.organization?.id; + const storageKey = orgId ? `onboarding_checklist_hidden_${orgId}` : null; + const [dismissed, setDismissed] = useState( + () => !!storageKey && localStorage.getItem(storageKey) === '1' + ); + + const { data: formsData, isLoading: formsLoading } = useQuery<{ success: boolean; forms: unknown[] }>({ + queryKey: ['/api/forms'], + }); + const { data: usersData, isLoading: usersLoading } = useQuery<{ success: boolean; users: unknown[] }>({ + queryKey: ['/api/users/list'], + }); + const { data: tasksData, isLoading: tasksLoading } = useQuery<{ success: boolean; tasks: unknown[] }>({ + queryKey: ['/api/tasks', 'onboarding-first'], + queryFn: () => apiRequest('GET', '/api/tasks?minimal=true&limit=1').then(r => r.json()), + }); + + if (!storageKey || dismissed) return null; + if (formsLoading || usersLoading || tasksLoading) return null; + + const steps: OnboardingStep[] = [ + { + key: 'form', + label: 'Создайте первую форму', + href: '/forms', + done: (formsData?.forms?.length ?? 0) > 0, + }, + { + key: 'team', + label: 'Пригласите коллег', + href: '/users', + done: (usersData?.users?.length ?? 0) > 1, + }, + { + key: 'task', + label: 'Создайте первую задачу', + href: '/tasks', + done: (tasksData?.tasks?.length ?? 0) > 0, + }, + ]; + + // Всё выполнено — чеклист больше не нужен + if (steps.every(s => s.done)) return null; + + const doneCount = steps.filter(s => s.done).length; + + function handleDismiss() { + try { localStorage.setItem(storageKey!, '1'); } catch {} + setDismissed(true); + } + + return ( +
+
+ + + Начало работы · {doneCount} из {steps.length} + + +
+
+ {steps.map((step, i) => ( + + + {step.done + ? + : } + + {i + 1}. {step.label} + + + + ))} +
+
+ ); +} diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 522e244..a4f69bb 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -1314,7 +1314,9 @@ function MedScheduleNavItem({ collapsed }: { collapsed: boolean }) { const [location] = useLocation(); const isActive = location === '/med' || location.startsWith('/med/'); - if (user?.organization?.slug !== 'medschedule' && !isAdmin(user)) return null; + // Модуль виден, если у организации включён флаг medschedule_enabled + // (см. миграцию 0081) либо пользователь — админ приложения + if (!user?.organization?.medscheduleEnabled && !isAdmin(user)) return null; if (collapsed) { return ( diff --git a/client/src/lib/push-notifications.ts b/client/src/lib/push-notifications.ts deleted file mode 100644 index f195263..0000000 --- a/client/src/lib/push-notifications.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { PushNotifications, Token, PushNotificationSchema, ActionPerformed } from '@capacitor/push-notifications'; -import { Capacitor } from '@capacitor/core'; -import { apiRequest } from './queryClient'; - -async function registerTokenOnServer(token: string, platform: string): Promise { - try { - await apiRequest('POST', '/api/devices/register', { - token, - platform, - deviceInfo: { - model: navigator.userAgent, - timestamp: new Date().toISOString(), - } - }); - - console.log('Device token registered successfully'); - } catch (error) { - console.error('Error registering device token:', error); - } -} - -async function sendHeartbeat(): Promise { - try { - await apiRequest('POST', '/api/devices/heartbeat', {}); - } catch (error) { - console.error('Heartbeat failed:', error); - } -} - -export async function initializePushNotifications(): Promise { - if (!Capacitor.isNativePlatform()) { - console.log('Push notifications only available on native platforms'); - return; - } - - try { - const permStatus = await PushNotifications.checkPermissions(); - - if (permStatus.receive === 'prompt') { - const result = await PushNotifications.requestPermissions(); - if (result.receive !== 'granted') { - console.log('Push notification permission denied'); - return; - } - } else if (permStatus.receive !== 'granted') { - console.log('Push notification permission not granted'); - return; - } - - await PushNotifications.register(); - - PushNotifications.addListener('registration', async (token: Token) => { - console.log('Push registration success, token:', token.value); - const platform = Capacitor.getPlatform(); - await registerTokenOnServer(token.value, platform); - }); - - PushNotifications.addListener('registrationError', (error: any) => { - console.error('Push registration error:', error); - }); - - PushNotifications.addListener('pushNotificationReceived', (notification: PushNotificationSchema) => { - console.log('Push notification received:', notification); - }); - - PushNotifications.addListener('pushNotificationActionPerformed', (notification: ActionPerformed) => { - console.log('Push notification action performed:', notification); - - const taskId = notification.notification.data?.taskId; - if (taskId) { - window.location.href = `/tasks/${taskId}`; - } - }); - - setInterval(sendHeartbeat, 60000); - sendHeartbeat(); - - console.log('Push notifications initialized'); - } catch (error) { - console.error('Error initializing push notifications:', error); - } -} - -export async function unregisterPushNotifications(): Promise { - if (!Capacitor.isNativePlatform()) return; - - try { - await PushNotifications.removeAllListeners(); - console.log('Push notifications unregistered'); - } catch (error) { - console.error('Error unregistering push notifications:', error); - } -} diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 916631e..704a9e2 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -31,6 +31,7 @@ import { useInboxWithCache } from '@/hooks/useInboxWithCache'; import { patchTaskCache } from '@/lib/tasksCache'; import { HomeGlobalSearch } from '@/components/HomeGlobalSearch'; import { QrScannerButton } from '@/components/QrScannerButton'; +import { OnboardingChecklist } from '@/components/OnboardingChecklist'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; @@ -1431,6 +1432,9 @@ export default function HomePage() { + {/* Чеклист первых шагов для новой организации */} + + {/* Desktop tabs */}
{(['inbox', 'recent', 'snoozed', 'hidden'] as Tab[]).map(t => ( diff --git a/client/src/pages/Tasks.tsx b/client/src/pages/Tasks.tsx index fc50a7d..1817506 100644 --- a/client/src/pages/Tasks.tsx +++ b/client/src/pages/Tasks.tsx @@ -173,16 +173,36 @@ function EmptyState({ forms, onCreateClick }: { forms: FormItem[]; onCreateClick
-
-

Задач пока нет

-

- Выберите форму и создайте первую задачу -

-
- + {forms.length === 0 ? ( + <> +
+

Пока нет ни одной формы

+

+ Задачи создаются внутри форм — начните с создания первой формы +

+
+ + + + + ) : ( + <> +
+

Задач пока нет

+

+ Выберите форму и создайте первую задачу +

+
+ + + )}
{forms.length > 0 && ( diff --git a/client/src/pages/medschedule/index.tsx b/client/src/pages/medschedule/index.tsx index 94f1467..effdb48 100644 --- a/client/src/pages/medschedule/index.tsx +++ b/client/src/pages/medschedule/index.tsx @@ -9,6 +9,8 @@ import { Badge } from "@/components/ui/badge"; import { Plus, Users, Pill, ClipboardList, Stethoscope, MessageCircle } from "lucide-react"; import Layout from "@/components/Layout"; import { useToast } from "@/hooks/use-toast"; +import { useAuth } from "@/hooks/useAuth"; +import { isAdmin } from "@/lib/permissions"; import { apiRequest } from "@/lib/queryClient"; const TABS = [ @@ -22,19 +24,39 @@ const TABS = [ export default function MedSchedulePage() { const [location, setLocation] = useLocation(); const { toast } = useToast(); + const { user } = useAuth(); const queryClient = useQueryClient(); const match = location.match(/\/med\/(.*)/); const activeTab = match?.[1] || "schedule"; + // Модуль доступен только организациям с флагом medschedule_enabled (миграция 0081) + // и администраторам приложения — серверный гейт в server/medschedule/routes.ts + const moduleEnabled = !!user?.organization?.medscheduleEnabled || isAdmin(user); + const { data: familyData } = useQuery({ queryKey: ["/api/med/family"], retry: false, + enabled: moduleEnabled, }); const families: any[] = familyData?.groups || []; const family = families[0]; const onTabChange = (value: string) => setLocation(`/med/${value}`); + if (!moduleEnabled) { + return ( + +
+ + +

Модуль MedSchedule не подключён для вашей организации.

+
+
+
+
+ ); + } + return (
diff --git a/client/src/types/auth.types.ts b/client/src/types/auth.types.ts index 7d1d6ff..45af446 100644 --- a/client/src/types/auth.types.ts +++ b/client/src/types/auth.types.ts @@ -18,6 +18,7 @@ export interface User { slug: string; displayName: string; activeSkinId?: string; + medscheduleEnabled?: boolean; }; } diff --git a/migrations/0081_medschedule_org_flag.sql b/migrations/0081_medschedule_org_flag.sql new file mode 100644 index 0000000..521c46d --- /dev/null +++ b/migrations/0081_medschedule_org_flag.sql @@ -0,0 +1,5 @@ +-- Доступ к модулю MedSchedule: флаг на уровне организации. +-- DEFAULT false — модуль скрыт у всех организаций, кроме служебной org medschedule. +-- Гейт: server/medschedule/routes.ts (requireMedOrganization), меню — Sidebar MedScheduleNavItem. +ALTER TABLE organizations ADD COLUMN IF NOT EXISTS medschedule_enabled BOOLEAN NOT NULL DEFAULT FALSE; +UPDATE organizations SET medschedule_enabled = TRUE WHERE slug = 'medschedule'; diff --git a/package-lock.json b/package-lock.json index 139c316..e3f864c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,6 @@ "@aws-sdk/client-s3": "^3.1040.0", "@aws-sdk/s3-request-presigner": "^3.1040.0", "@babel/standalone": "^7.29.2", - "@capacitor/android": "^8.0.0", - "@capacitor/cli": "^7.4.4", - "@capacitor/core": "^8.0.0", - "@capacitor/ios": "^8.0.0", - "@capacitor/push-notifications": "^8.0.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -1033,74 +1028,6 @@ "node": ">=6.9.0" } }, - "node_modules/@capacitor/android": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.4.1.tgz", - "integrity": "sha512-igtDCJ7QQn0P2qHFD9p4KXaa6V1b2PRNt+MxjVwtjTm/BJvqmiazOJq6rPjwFSZnfHm6iFoZk8TfzHd44pyBGw==", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": "^8.4.0" - } - }, - "node_modules/@capacitor/cli": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.6.7.tgz", - "integrity": "sha512-nV9j1+431/rsiabGs1UK0SbETrl2JfAf3SSlsM9RbMMjAL7WmruxnlJcbB9IXPoa6L99GljSO5q/9UCNvYz6Ag==", - "license": "MIT", - "dependencies": { - "@ionic/cli-framework-output": "^2.2.8", - "@ionic/utils-subprocess": "^3.0.1", - "@ionic/utils-terminal": "^2.3.5", - "commander": "^12.1.0", - "debug": "^4.4.0", - "env-paths": "^2.2.0", - "fs-extra": "^11.2.0", - "kleur": "^4.1.5", - "native-run": "^2.0.3", - "open": "^8.4.0", - "plist": "^3.1.0", - "prompts": "^2.4.2", - "rimraf": "^6.0.1", - "semver": "^7.6.3", - "tar": "^7.5.3", - "tslib": "^2.8.1", - "xml2js": "^0.6.2" - }, - "bin": { - "cap": "bin/capacitor", - "capacitor": "bin/capacitor" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@capacitor/core": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.4.1.tgz", - "integrity": "sha512-xqhOGLbTAYeOWK+IDUNSjQJAPapQjRHrIcgk9PYp52or9zFTaaMko31uNi16N6W+CRJ8VrRram6fOYILkBG2Hg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@capacitor/ios": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-8.4.1.tgz", - "integrity": "sha512-EgcAk7NYheHMTyP3CUrA65qKs4D2UEAKgw44HI6Uk3dTw6KjLQkdLOQWvbeRncHaZ2gklfOojUoc5DlSw2lhYg==", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": "^8.4.0" - } - }, - "node_modules/@capacitor/push-notifications": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@capacitor/push-notifications/-/push-notifications-8.1.1.tgz", - "integrity": "sha512-WqzjPKIbYbARMN+GC0XMAJcxJpUUzqgzS/Ny8RODLrro38pQhm3GXYwX2Mwd+LZlLY39rGImkCkrKyQSNfuikA==", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": ">=8.0.0" - } - }, "node_modules/@dnd-kit/accessibility": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", @@ -2401,145 +2328,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@ionic/cli-framework-output": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", - "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==", - "license": "MIT", - "dependencies": { - "@ionic/utils-terminal": "2.3.5", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-array": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz", - "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==", - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-fs": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz", - "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==", - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^8.0.0", - "debug": "^4.0.0", - "fs-extra": "^9.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-fs/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@ionic/utils-object": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz", - "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==", - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-process": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz", - "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==", - "license": "MIT", - "dependencies": { - "@ionic/utils-object": "2.1.6", - "@ionic/utils-terminal": "2.3.5", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "tree-kill": "^1.2.2", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz", - "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==", - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-subprocess": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz", - "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==", - "license": "MIT", - "dependencies": { - "@ionic/utils-array": "2.1.6", - "@ionic/utils-fs": "3.1.7", - "@ionic/utils-process": "2.1.12", - "@ionic/utils-stream": "3.1.7", - "@ionic/utils-terminal": "2.3.5", - "cross-spawn": "^7.0.3", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-terminal": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", - "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==", - "license": "MIT", - "dependencies": { - "@types/slice-ansi": "^4.0.0", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "tslib": "^2.0.1", - "untildify": "^4.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2636,18 +2424,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -6909,15 +6685,6 @@ "@types/express": "*" } }, - "node_modules/@types/fs-extra": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", - "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -7158,12 +6925,6 @@ "@types/node": "*" } }, - "node_modules/@types/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", - "license": "MIT" - }, "node_modules/@types/superagent": { "version": "8.1.10", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.10.tgz", @@ -7962,15 +7723,6 @@ "node": ">=12" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -7983,15 +7735,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/autoprefixer": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", @@ -8210,15 +7953,6 @@ "node": ">= 18" } }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -8312,18 +8046,6 @@ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, - "node_modules/bplist-parser": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", - "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", - "license": "MIT", - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/brace-expansion": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", @@ -8678,15 +8400,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -8810,15 +8523,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/complex.js": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz", @@ -9281,15 +8985,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10200,18 +9895,6 @@ "dev": true, "license": "ISC" }, - "node_modules/elementtree": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", - "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==", - "license": "Apache-2.0", - "dependencies": { - "sax": "1.1.4" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/embla-carousel": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", @@ -10281,15 +9964,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -11059,15 +10733,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -11361,20 +11026,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -11726,6 +11377,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/gtoken": { @@ -12189,21 +11841,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -12283,18 +11920,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -12469,18 +12094,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -12607,15 +12220,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -14221,18 +13825,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", @@ -14336,40 +13928,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/native-run": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz", - "integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==", - "license": "MIT", - "dependencies": { - "@ionic/utils-fs": "^3.1.7", - "@ionic/utils-terminal": "^2.3.4", - "bplist-parser": "^0.3.2", - "debug": "^4.3.4", - "elementtree": "^0.1.7", - "ini": "^4.1.1", - "plist": "^3.1.0", - "split2": "^4.2.0", - "through2": "^4.0.2", - "tslib": "^2.6.2", - "yauzl": "^2.10.0" - }, - "bin": { - "native-run": "bin/native-run" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/native-run/node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -14587,23 +14145,6 @@ "wrappy": "1" } }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/openapi-types": { "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", @@ -14875,12 +14416,6 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, "node_modules/pg": { "version": "8.22.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", @@ -15024,38 +14559,6 @@ "node": ">=16.20.0" } }, - "node_modules/plist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", - "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.9.10", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/plist/node_modules/@xmldom/xmldom": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", - "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", - "license": "MIT", - "engines": { - "node": ">=14.6" - } - }, - "node_modules/plist/node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -15305,28 +14808,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -16147,103 +15628,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", - "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "glob": "^13.0.3", - "package-json-from-dist": "^1.0.1" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rolldown": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", @@ -16411,12 +15795,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/sax": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", - "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", - "license": "ISC" - }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -16616,35 +15994,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17259,22 +16608,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tar-stream": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", @@ -17350,29 +16683,6 @@ "node": ">=0.8" } }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -17465,15 +16775,6 @@ "node": ">=0.6" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -17737,15 +17038,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -17755,15 +17047,6 @@ "node": ">= 0.8" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -18744,23 +18027,6 @@ "react": ">=16.8.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -18842,28 +18108,6 @@ "node": ">=16.0.0" } }, - "node_modules/xml2js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", @@ -18902,15 +18146,6 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -18946,25 +18181,6 @@ "node": ">=6" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yauzl/node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ce89c43..81c353b 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,6 @@ "@aws-sdk/client-s3": "^3.1040.0", "@aws-sdk/s3-request-presigner": "^3.1040.0", "@babel/standalone": "^7.29.2", - "@capacitor/android": "^8.0.0", - "@capacitor/cli": "^7.4.4", - "@capacitor/core": "^8.0.0", - "@capacitor/ios": "^8.0.0", - "@capacitor/push-notifications": "^8.0.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", diff --git a/server/medschedule/routes.ts b/server/medschedule/routes.ts index 22b302c..9d98f29 100644 --- a/server/medschedule/routes.ts +++ b/server/medschedule/routes.ts @@ -2,6 +2,7 @@ import { Router } from "express"; import type { Express, Request, Response, NextFunction } from "express"; import type { Response as ExpressResponse } from "express"; import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware"; +import { storage } from "../storage"; import { medScheduleStorage } from "./storage"; @@ -25,12 +26,17 @@ function requireMedOrganization() { // MedSchedule API работает в контексте org medschedule, а не текущей org пользователя req.organizationId = medOrgId; - // Доступ разрешён либо членам org medschedule, либо администраторам приложения - if (req.user && req.user.organizationId !== medOrgId && req.user.appRole !== "admin") { - return res.status(403).json({ - success: false, - error: "Этот раздел доступен только пользователям MedSchedule", - }); + // Доступ: у организации пользователя включён флаг medschedule_enabled + // (миграция 0081) либо пользователь — администратор приложения + // (admin-override, как в доступе к модулям GPS/Финансы). + if (req.user && req.user.appRole !== "admin") { + const userOrg = await storage.getOrganization(req.user.organizationId); + if (!userOrg?.medscheduleEnabled) { + return res.status(403).json({ + success: false, + error: "Модуль MedSchedule не подключён для вашей организации", + }); + } } next(); diff --git a/server/routes/auth.core.routes.ts b/server/routes/auth.core.routes.ts index e80abe0..fa1292e 100644 --- a/server/routes/auth.core.routes.ts +++ b/server/routes/auth.core.routes.ts @@ -212,7 +212,8 @@ export function registerAuthCoreRoutes(router: Router): void { name: user.organization.name, slug: user.organization.slug, displayName: user.organization.displayName, - activeSkinId: user.organization.activeSkinId + activeSkinId: user.organization.activeSkinId, + medscheduleEnabled: user.organization.medscheduleEnabled ?? false } : null } }); diff --git a/shared/schema.ts b/shared/schema.ts index bf6ffe5..9108d56 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -37,6 +37,7 @@ export const organizations = pgTable("organizations", { updatedAt: timestamp("updated_at").defaultNow(), isActive: boolean("is_active").default(true), billingBlocked: boolean("billing_blocked").default(false), + medscheduleEnabled: boolean("medschedule_enabled").notNull().default(false), }); // Organization skins