Zwiscshenstand - laufende Version

Dieser Commit ist enthalten in:
Claude Project Manager
2025-09-27 13:11:39 +02:00
Ursprung 81e57ecff6
Commit 2689cd2d32
30 geänderte Dateien mit 1021 neuen und 459 gelöschten Zeilen

Datei anzeigen

@ -4,10 +4,10 @@
## Project Overview
- **Path**: `A:/GiTea/SkillMate`
- **Files**: 277 files
- **Size**: 9.7 MB
- **Last Modified**: 2025-09-23 00:39
- **Path**: `A:\GiTea\SkillMate`
- **Files**: 191 files
- **Size**: 3.3 MB
- **Last Modified**: 2025-09-26 00:23
## Technology Stack
@ -42,12 +42,6 @@ admin-panel/
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── vite.config.ts
│ ├── dist/
│ │ ├── index.html
│ │ └── assets/
│ │ ├── index-BRjBeEgH.css
│ │ └── index-gij1mIll.js
│ ├── public
│ └── src/
│ ├── App.tsx
│ ├── index.css
@ -63,9 +57,6 @@ admin-panel/
│ │ ├── SunIcon.tsx
│ │ ├── SyncStatus.tsx
│ │ └── UsersIcon.tsx
│ ├── data/
│ │ ├── skillCategories.ts
│ │ └── skills.ts
│ ├── services/
│ │ ├── api.ts
│ │ └── networkApi.ts
@ -91,65 +82,14 @@ backend/
│ ├── mock-server.js
│ ├── package-lock.json
│ ├── package.json
│ ├── skillmate.dev.db
│ ├── skillmate.dev.encrypted.db
│ ├── dist/
│ ├── index.js
│ │ ├── index.js.map
│ │ ├── config/
│ │ │ ├── appConfig.js
│ │ │ ├── appConfig.js.map
│ │ │ ├── database.js
│ │ │ ├── database.js.map
│ │ │ ├── secureDatabase.js
│ │ │ └── secureDatabase.js.map
│ │ ├── middleware/
│ │ │ ├── auth.js
│ │ │ ├── auth.js.map
│ │ │ ├── errorHandler.js
│ │ │ ├── errorHandler.js.map
│ │ │ ├── roleAuth.js
│ │ │ └── roleAuth.js.map
│ │ ├── repositories/
│ │ │ ├── employeeRepository.js
│ │ │ └── employeeRepository.js.map
│ │ ├── routes/
│ │ │ ├── analytics.js
│ │ │ ├── analytics.js.map
│ │ │ ├── auth.js
│ │ │ ├── auth.js.map
│ │ │ ├── bookings.js
│ │ │ ├── bookings.js.map
│ │ │ ├── employees.js
│ │ │ ├── employees.js.map
│ │ │ ├── employeesSecure.js
│ │ │ └── employeesSecure.js.map
│ │ ├── services/
│ │ │ ├── auditService.js
│ │ │ ├── auditService.js.map
│ │ │ ├── emailService.js
│ │ │ ├── emailService.js.map
│ │ │ ├── encryption.js
│ │ │ ├── encryption.js.map
│ │ │ ├── reminderService.js
│ │ │ ├── reminderService.js.map
│ │ │ ├── syncScheduler.js
│ │ │ └── syncScheduler.js.map
│ │ ├── usecases/
│ │ │ ├── employees.js
│ │ │ ├── employees.js.map
│ │ │ ├── users.js
│ │ │ └── users.js.map
│ │ ├── utils/
│ │ │ ├── logger.js
│ │ │ └── logger.js.map
│ │ └── validation/
│ │ ├── employeeValidators.js
│ │ └── employeeValidators.js.map
│ ├── skillmate.dev.encrypted.db-shm
│ ├── skillmate.dev.encrypted.db-wal
│ ├── logs/
│ │ ├── combined.log
│ │ └── error.log
│ ├── scripts/
│ │ ├── extract-pdf.js
│ │ ├── migrate-users.js
│ │ ├── purge-users.js
│ │ ├── reset-admin.js
@ -175,13 +115,13 @@ backend/
│ │ │ ├── analytics.ts
│ │ │ ├── auth.ts
│ │ │ ├── bookings.ts.disabled
│ │ │ ├── employeeOrganization.ts
│ │ │ ├── employees.ts
│ │ │ ├── employeesSecure.ts
│ │ │ ├── network.ts
│ │ │ ├── organization.ts
│ │ │ ├── organizationImport.ts
│ │ │ ── profiles.ts
│ │ │ └── settings.ts
│ │ │ ── profiles.ts
│ │ ├── services/
│ │ │ ├── auditService.ts
│ │ │ ├── emailService.ts
@ -197,10 +137,7 @@ backend/
│ │ └── validation/
│ │ └── employeeValidators.ts
│ └── uploads/
│ ├── photos/
│ │ ├── 0def5f6f-c1ef-4f88-9105-600c75278f10.jpg
│ │ ├── 72c09fa1-f0a8-444c-918f-95258ca56f61.gif
│ │ └── 80c44681-d6b4-474e-8ff1-c6d02da0cd7d.gif
│ ├── photos
│ └── temp
docs/
│ ├── ARCHITECTURE.md
@ -216,13 +153,7 @@ frontend/
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── dist/
│ │ ├── debug.html
│ │ ├── icon.svg
│ │ ├── index.html
│ │ └── assets/
│ │ ├── index-BUJNM8Sh.css
│ │ └── index-n0FiY1wQ.js
│ ├── vite.config.electron.ts
│ ├── electron/
│ │ ├── main.js
│ │ ├── preload.js
@ -244,8 +175,8 @@ frontend/
│ │ ├── OfficeMap3D.tsx
│ │ ├── OfficeMapModal.tsx
│ │ ├── OrganizationChart.tsx
│ │ ├── PhotoPreview.tsx
│ │ └── PhotoUpload.tsx
│ │ ├── OrganizationSelector.tsx
│ │ └── PhotoPreview.tsx
│ ├── data/
│ │ └── skillCategories.ts
│ ├── hooks/
@ -312,3 +243,5 @@ This project is managed with Claude Project Manager. To work with this project:
- README updated on 2025-09-21 16:48:11
- README updated on 2025-09-21 16:48:44
- README updated on 2025-09-23 19:19:20
- README updated on 2025-09-25 22:01:37
- README updated on 2025-09-27 12:01:06

Datei anzeigen

@ -34,13 +34,13 @@ SkillMate ist eine spezialisierte Anwendung zur Verwaltung von Mitarbeiterfähig
### Alle Services starten
**Windows:** `start-dev.bat`
**macOS/Linux:** `./start-dev.sh`
**Windows:** `run-dev.cmd`
**macOS/Linux:** `python3 main.py --mode dev`
Dies startet:
- Backend auf http://localhost:3001
- Backend auf http://localhost:3004
- Frontend auf http://localhost:5173
- Admin Panel auf http://localhost:3002
- Admin Panel auf http://localhost:5174
### Einzelne Services
@ -57,10 +57,9 @@ cd admin-panel && npm run dev
## Production Build
### Kompletten Build erstellen
### Production Start (Windows)
**Windows:** `build-production.bat`
**macOS/Linux:** `./build-production.sh`
`run-prod.cmd`
### Ausgabe
@ -75,12 +74,11 @@ SkillMate/
├── frontend/ # Electron Desktop App
├── backend/ # Express.js API Server
├── admin-panel/ # React Admin Interface
├── shared/ # Gemeinsame TypeScript-Typen
├── setup.bat # Windows Setup-Script
├── setup.sh # Unix Setup-Script
├── start-dev.bat # Windows Dev-Start
── start-dev.sh # Unix Dev-Start
└── README.md # Diese Datei
├── shared/ # Gemeinsame TypeScript-Typen
├── main.py # Windows-optimierter Starter (auch für Unix nutzbar)
├── run-dev.cmd # Windows Dev-Start
├── run-prod.cmd # Windows Prod-Start
── README.md # Diese Datei
```
## Standard-Login

Datei anzeigen

@ -16,6 +16,7 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-router-dom": "^6.20.0",
"reactflow": "^11.10.0",
"zustand": "^4.4.7"
},
"devDependencies": {
@ -849,6 +850,108 @@
"node": ">=14"
}
},
"node_modules/@reactflow/background": {
"version": "11.3.14",
"resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz",
"integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/controls": {
"version": "11.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
"integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/core": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
"integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==",
"license": "MIT",
"dependencies": {
"@types/d3": "^7.4.0",
"@types/d3-drag": "^3.0.1",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/minimap": {
"version": "11.7.14",
"resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
"integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-resizer": {
"version": "2.2.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
"integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-toolbar": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
"integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==",
"license": "MIT",
"dependencies": {
"@reactflow/core": "11.11.4",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
@ -1194,6 +1297,259 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
"node_modules/@types/d3-axis": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-contour": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"license": "MIT",
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
"license": "MIT"
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
"integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
"license": "MIT"
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-fetch": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"license": "MIT",
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
"license": "MIT"
},
"node_modules/@types/d3-format": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
"license": "MIT"
},
"node_modules/@types/d3-geo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-polygon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
"license": "MIT"
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
"license": "MIT"
},
"node_modules/@types/d3-random": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
"license": "MIT"
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
"license": "MIT"
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-time-format": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/d3-transition": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"license": "MIT",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"license": "MIT",
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@ -1201,6 +1557,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
@ -1517,6 +1879,12 @@
"node": ">= 6"
}
},
"node_modules/classcat": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT"
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1601,6 +1969,111 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
@ -2764,6 +3237,24 @@
"react-dom": ">=16.8"
}
},
"node_modules/reactflow": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
"integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==",
"license": "MIT",
"dependencies": {
"@reactflow/background": "11.3.14",
"@reactflow/controls": "11.2.14",
"@reactflow/core": "11.11.4",
"@reactflow/minimap": "11.7.14",
"@reactflow/node-resizer": "2.2.14",
"@reactflow/node-toolbar": "1.3.14"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

Datei anzeigen

@ -26,6 +26,7 @@
"multer": "^2.0.1",
"node-cron": "^4.2.1",
"nodemailer": "^7.0.6",
"pdf-parse": "^1.1.1",
"sqlite3": "^5.1.6",
"uuid": "^9.0.1",
"winston": "^3.11.0"
@ -3862,6 +3863,12 @@
"node": ">=6.0.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
"integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==",
"license": "MIT"
},
"node_modules/node-gyp": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
@ -4096,6 +4103,34 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/pdf-parse": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
"integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==",
"license": "MIT",
"dependencies": {
"debug": "^3.1.0",
"node-ensure": "^0.0.0"
},
"engines": {
"node": ">=6.8.1"
}
},
"node_modules/pdf-parse/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/pdf-parse/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",

Datei anzeigen

@ -9,7 +9,8 @@
"start": "node dist/index.js",
"reset-admin": "node scripts/reset-admin.js",
"migrate-users": "node scripts/migrate-users.js",
"seed-skills": "node scripts/seed-skills-from-frontend.js",
"seed-skills": "node scripts/seed-skills-from-shared.js",
"seed-skills-shared": "node scripts/seed-skills-from-shared.js",
"purge-users": "node scripts/purge-users.js"
},
"dependencies": {

Datei anzeigen

@ -0,0 +1,134 @@
// Seed controlled vocabulary (categories/subcategories) and skills
// from shared/skills.js (SKILL_HIERARCHY) into the local SQLite DB.
//
// Usage:
// cd backend
// node scripts/seed-skills-from-shared.js
//
// Optionally set DATABASE_PATH to seed a custom DB file.
const path = require('path')
const fs = require('fs')
const crypto = require('crypto')
const Database = require('better-sqlite3')
function getDbPath() {
if (process.env.DATABASE_PATH && process.env.DATABASE_PATH.length > 0) {
return process.env.DATABASE_PATH
}
// default dev DB in backend folder
return path.join(__dirname, '..', 'skillmate.dev.encrypted.db')
}
function ensureTables(db) {
// controlled_vocabulary (canonical definition from secureDatabase)
db.exec(`
CREATE TABLE IF NOT EXISTS controlled_vocabulary (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
value TEXT NOT NULL,
description TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
UNIQUE(category, value)
);
CREATE INDEX IF NOT EXISTS idx_vocab_category ON controlled_vocabulary(category);
CREATE INDEX IF NOT EXISTS idx_vocab_value ON controlled_vocabulary(value);
`)
// skills (canonical definition from secureDatabase)
db.exec(`
CREATE TABLE IF NOT EXISTS skills (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT,
requires_certification INTEGER DEFAULT 0,
expires_after INTEGER
)
`)
}
function loadHierarchy() {
// Load from shared/skills.js
const skillsPath = path.join(__dirname, '..', '..', 'shared', 'skills.js')
if (!fs.existsSync(skillsPath)) {
throw new Error('shared/skills.js not found')
}
const mod = require(skillsPath)
if (!mod || !Array.isArray(mod.SKILL_HIERARCHY)) {
throw new Error('SKILL_HIERARCHY missing or invalid in shared/skills.js')
}
return mod.SKILL_HIERARCHY
}
function seed(db, hierarchy) {
const now = new Date().toISOString()
const insertVocab = db.prepare(`
INSERT OR IGNORE INTO controlled_vocabulary (id, category, value, description, is_active, created_at)
VALUES (?, ?, ?, ?, ?, ?)
`)
const insertSkill = db.prepare(`
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
VALUES (?, ?, ?, ?, ?, ?)
`)
let catCount = 0
let subCount = 0
let skillCount = 0
for (const cat of hierarchy) {
const catId = String(cat.id)
const catName = String(cat.name || cat.id)
// category
const catPk = crypto.randomUUID()
const catRes = insertVocab.run(catPk, 'skill_category', catId, catName, 1, now)
if (catRes.changes > 0) catCount++
for (const sub of (cat.subcategories || [])) {
const subId = String(sub.id)
const subName = String(sub.name || sub.id)
const key = `${catId}.${subId}`
// subcategory
const subPk = crypto.randomUUID()
const subRes = insertVocab.run(subPk, 'skill_subcategory', key, subName, 1, now)
if (subRes.changes > 0) subCount++
for (const sk of (sub.skills || [])) {
const sId = `${key}.${sk.id}`
const sName = String(sk.name || sk.id)
const requires = (catId === 'certifications' || subId === 'weapons') ? 1 : 0
const expires = (catId === 'certifications') ? 36 : null
const sRes = insertSkill.run(sId, sName, key, null, requires, expires)
if (sRes.changes > 0) skillCount++
}
}
}
return { catCount, subCount, skillCount }
}
function main() {
const dbPath = getDbPath()
console.log('➡️ Seeding skills into DB:', dbPath)
const db = new Database(dbPath)
try {
ensureTables(db)
const hierarchy = loadHierarchy()
const { catCount, subCount, skillCount } = seed(db, hierarchy)
console.log(`✅ Seeding completed. Inserted: ${catCount} categories, ${subCount} subcategories, ${skillCount} skills.`)
console.log(' Re-open the Admin Panel to see Skills in hierarchy view.')
} catch (err) {
console.error('❌ Seeding failed:', err.message)
process.exitCode = 1
} finally {
try { db.close() } catch {}
}
}
main()

Datei anzeigen

@ -9,22 +9,30 @@ export { initializeSecureDatabase } from './secureDatabase'
// Export the secure database instance
export const db = secureDb
// IMPORTANT: secureDatabase.ts owns all securitysensitive base tables
// (users, employees, skills and their junctions, language_skills, specializations,
// controlled_vocabulary, system_settings, security_audit_log) including
// encrypted fields and indexes. This module defines only extended, nonsensitive
// schemas (profiles, workspaces/bookings, organizational structure, reminders, analytics).
export function initializeDatabase() {
// Users table
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('admin', 'superuser', 'user')),
employee_id TEXT,
last_login TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
`)
// Lightweight schema guard (no mutations): warn if legacy/plain schemas are present
try {
const usersCols: any[] = db.prepare(`PRAGMA table_info(users)`).all() as any
const hasEmailHash = usersCols.some(c => c.name === 'email_hash')
if (!hasEmailHash) {
console.warn('[DB-Guard] users.email_hash missing. Ensure initializeSecureDatabase() runs first and legacy data is migrated.')
}
} catch {}
try {
const empCols: any[] = db.prepare(`PRAGMA table_info(employees)`).all() as any
const hasEmpEmailHash = empCols.some(c => c.name === 'email_hash')
const hasEmpPhoneHash = empCols.some(c => c.name === 'phone_hash')
if (!hasEmpEmailHash || !hasEmpPhoneHash) {
console.warn('[DB-Guard] employees hash columns missing. Ensure secure schema initialized and data migrated.')
}
} catch {}
// Profiles table (erweitert für Yellow Pages)
db.exec(`
@ -68,57 +76,7 @@ export function initializeDatabase() {
ON profiles(review_due_at);
`)
// Employees table (für Kompatibilität beibehalten)
db.exec(`
CREATE TABLE IF NOT EXISTS employees (
id TEXT PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
employee_number TEXT UNIQUE NOT NULL,
photo TEXT,
position TEXT NOT NULL,
department TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT NOT NULL,
mobile TEXT,
office TEXT,
availability TEXT NOT NULL,
clearance_level TEXT,
clearance_valid_until TEXT,
clearance_issued_date TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
created_by TEXT NOT NULL,
updated_by TEXT
)
`)
// Skills table
db.exec(`
CREATE TABLE IF NOT EXISTS skills (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT,
requires_certification INTEGER DEFAULT 0,
expires_after INTEGER
)
`)
// Employee skills junction table
db.exec(`
CREATE TABLE IF NOT EXISTS employee_skills (
employee_id TEXT NOT NULL,
skill_id TEXT NOT NULL,
level TEXT,
verified INTEGER DEFAULT 0,
verified_by TEXT,
verified_date TEXT,
PRIMARY KEY (employee_id, skill_id),
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE,
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE
)
`)
// Employees/Skills tables are defined in secureDatabase (encrypted & indexed)
// Profile Kompetenzen (Arrays als separate Tabellen)
db.exec(`
@ -210,62 +168,9 @@ export function initializeDatabase() {
);
`)
// Language skills table (für Kompatibilität)
db.exec(`
CREATE TABLE IF NOT EXISTS language_skills (
id TEXT PRIMARY KEY,
employee_id TEXT NOT NULL,
language TEXT NOT NULL,
proficiency TEXT NOT NULL,
certified INTEGER DEFAULT 0,
certificate_type TEXT,
is_native INTEGER DEFAULT 0,
can_interpret INTEGER DEFAULT 0,
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
)
`)
// Language skills / Specializations are defined in secureDatabase
// Specializations table
db.exec(`
CREATE TABLE IF NOT EXISTS specializations (
id TEXT PRIMARY KEY,
employee_id TEXT NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
)
`)
// Sync log table
db.exec(`
CREATE TABLE IF NOT EXISTS sync_log (
id TEXT PRIMARY KEY,
sync_time TEXT NOT NULL,
success INTEGER NOT NULL,
items_synced INTEGER,
error_message TEXT,
duration INTEGER
)
`)
// Create default admin user if not exists
const adminExists = db.prepare('SELECT id FROM users WHERE username = ?').get('admin')
if (!adminExists) {
const hashedPassword = bcrypt.hashSync('admin123', 10)
const now = new Date().toISOString()
db.prepare(`
INSERT INTO users (id, username, email, password, role, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).run(
uuidv4(),
'admin',
'admin@skillmate.local',
hashedPassword,
'admin',
1,
now,
now
)
}
// Sync tables are owned by SyncService initializer (avoid duplicate legacy schema)
// Workspace Management Tables
@ -435,6 +340,9 @@ export function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_emp_units_employee ON employee_unit_assignments(employee_id);
CREATE INDEX IF NOT EXISTS idx_emp_units_unit ON employee_unit_assignments(unit_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_emp_units_primary_unique
ON employee_unit_assignments(employee_id)
WHERE is_primary = 1;
`)
// Special Positions (Personalrat, Beauftragte, etc.)
@ -538,29 +446,8 @@ export function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_reminders_sent ON reminders(sent_at);
`)
// Kontrollierte Vokabulare/Tags
// Create indexes for better performance (only for tables defined here)
db.exec(`
CREATE TABLE IF NOT EXISTS controlled_vocabulary (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
value TEXT NOT NULL,
description TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
UNIQUE(category, value)
);
CREATE INDEX IF NOT EXISTS idx_vocab_category ON controlled_vocabulary(category);
`)
// Create indexes for better performance
db.exec(`
CREATE INDEX IF NOT EXISTS idx_employees_availability ON employees(availability);
CREATE INDEX IF NOT EXISTS idx_employees_department ON employees(department);
CREATE INDEX IF NOT EXISTS idx_employee_skills_employee ON employee_skills(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_skills_skill ON employee_skills(skill_id);
CREATE INDEX IF NOT EXISTS idx_language_skills_employee ON language_skills(employee_id);
CREATE INDEX IF NOT EXISTS idx_specializations_employee ON specializations(employee_id);
CREATE INDEX IF NOT EXISTS idx_bookings_workspace ON bookings(workspace_id);
CREATE INDEX IF NOT EXISTS idx_bookings_user ON bookings(user_id);
CREATE INDEX IF NOT EXISTS idx_bookings_start_time ON bookings(start_time);

Datei anzeigen

@ -86,11 +86,13 @@ export const encryptedDb = {
id, first_name, last_name, employee_number, photo, position,
department, email, email_hash, phone, phone_hash, mobile, office, availability,
clearance_level, clearance_valid_until, clearance_issued_date,
primary_unit_id,
created_at, updated_at, created_by
) VALUES (
@id, @first_name, @last_name, @employee_number, @photo, @position,
@department, @email, @email_hash, @phone, @phone_hash, @mobile, @office, @availability,
@clearance_level, @clearance_valid_until, @clearance_issued_date,
@primary_unit_id,
@created_at, @updated_at, @created_by
)
`).run(encrypted)
@ -173,6 +175,7 @@ export function initializeSecureDatabase() {
clearance_level TEXT,
clearance_valid_until TEXT,
clearance_issued_date TEXT,
primary_unit_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
created_by TEXT NOT NULL,
@ -180,6 +183,17 @@ export function initializeSecureDatabase() {
)
`)
// Add primary_unit_id column if missing (legacy DBs)
try {
const cols: any[] = db.prepare(`PRAGMA table_info(employees)`).all() as any
const hasPrimaryUnitId = cols.some(c => c.name === 'primary_unit_id')
if (!hasPrimaryUnitId) {
db.exec(`ALTER TABLE employees ADD COLUMN primary_unit_id TEXT`)
}
} catch (e) {
// ignore
}
// Add indexes for hash fields
db.exec(`
CREATE INDEX IF NOT EXISTS idx_employees_email_hash ON employees(email_hash);
@ -309,6 +323,7 @@ export function initializeSecureDatabase() {
db.exec(`
CREATE INDEX IF NOT EXISTS idx_employees_availability ON employees(availability);
CREATE INDEX IF NOT EXISTS idx_employees_department ON employees(department);
CREATE INDEX IF NOT EXISTS idx_employees_primary_unit ON employees(primary_unit_id);
CREATE INDEX IF NOT EXISTS idx_employee_skills_employee ON employee_skills(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_skills_skill ON employee_skills(skill_id);
CREATE INDEX IF NOT EXISTS idx_language_skills_employee ON language_skills(employee_id);

Datei anzeigen

@ -24,6 +24,7 @@ import employeeOrganizationRoutes from './routes/employeeOrganization'
import { errorHandler } from './middleware/errorHandler'
import { logger } from './utils/logger'
import { syncScheduler } from './services/syncScheduler'
import { ensureSkillsSeeded } from './services/skillSeeder'
dotenv.config()
@ -33,6 +34,8 @@ const PORT = process.env.PORT || 3004
// Initialize secure database (core tables) and extended schema (organization, deputies, etc.)
initializeSecureDatabase()
initializeDatabase()
// Ensure skills/categories exist for Admin Skill Management (idempotent)
ensureSkillsSeeded()
// Initialize sync scheduler
syncScheduler

Datei anzeigen

@ -66,7 +66,32 @@ function logSecurityAudit(
// Get all employees
router.get('/', authenticate, requirePermission('employees:read'), async (req: AuthRequest, res, next) => {
try {
const employees = encryptedDb.getAllEmployees()
const { unitId, includeDescendants } = (req.query || {}) as any
// Optional filter by unit (including descendants)
let allowedIds: Set<string> | null = null
if (unitId) {
const collectDescendants = (rootId: string): string[] => {
const ids: string[] = [rootId]
const stack: string[] = [rootId]
while (stack.length) {
const curr = stack.pop()!
const children = db.prepare('SELECT id FROM organizational_units WHERE parent_id = ?').all(curr) as any[]
for (const ch of children) {
ids.push(ch.id)
stack.push(ch.id)
}
}
return ids
}
const unitIds = includeDescendants === '1' || includeDescendants === 'true'
? collectDescendants(String(unitId))
: [String(unitId)]
const rows = db.prepare(`SELECT id FROM employees WHERE primary_unit_id IN (${unitIds.map(() => '?').join(',')})`).all(...unitIds) as any[]
allowedIds = new Set(rows.map(r => r.id))
}
const employees = encryptedDb.getAllEmployees().filter((e: any) => !allowedIds || allowedIds.has(e.id))
const employeesWithDetails = employees.map((emp: any) => {
// Get skills
@ -331,7 +356,8 @@ router.post('/',
const {
firstName, lastName, employeeNumber, photo, position = 'Mitarbeiter',
department, email, phone = 'Nicht angegeben', mobile, office, availability = 'available',
clearance, skills = [], languages = [], specializations = [], userRole, createUser
clearance, skills = [], languages = [], specializations = [], userRole, createUser,
primaryUnitId, assignmentRole
} = req.body
// Generate employee number if not provided
@ -363,11 +389,28 @@ router.post('/',
clearance_level: clearance?.level || null,
clearance_valid_until: clearance?.validUntil || null,
clearance_issued_date: clearance?.issuedDate || null,
primary_unit_id: primaryUnitId || null,
created_at: now,
updated_at: now,
created_by: req.user!.id
})
// Create primary assignment if provided
if (primaryUnitId) {
const unit = db.prepare('SELECT id, type FROM organizational_units WHERE id = ?').get(primaryUnitId)
if (!unit) {
return res.status(400).json({ success: false, error: { message: 'Invalid primary unit' } })
}
db.prepare(`
INSERT INTO employee_unit_assignments (
id, employee_id, unit_id, role, start_date, end_date, is_primary, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
uuidv4(), employeeId, primaryUnitId, assignmentRole || 'mitarbeiter',
now, null, 1, now, now
)
}
// Insert skills (only if they exist in skills table)
if (skills && skills.length > 0) {
const insertSkill = db.prepare(`
@ -477,6 +520,7 @@ router.post('/',
office: office || null,
availability,
clearance,
primaryUnitId: primaryUnitId || null,
skills,
languages,
specializations,

Datei anzeigen

@ -0,0 +1,112 @@
import path from 'path'
import { db } from '../config/secureDatabase'
import { logger } from '../utils/logger'
type SkillHierarchy = Array<{
id: string
name: string
subcategories?: Array<{
id: string
name: string
skills?: Array<{ id: string; name: string }>
}>
}>
function loadSharedHierarchy(): SkillHierarchy | null {
try {
const sharedPath = path.join(__dirname, '../../../shared/skills.js')
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = require(sharedPath)
if (mod && Array.isArray(mod.SKILL_HIERARCHY)) {
return mod.SKILL_HIERARCHY as SkillHierarchy
}
} catch (err) {
logger.warn('SkillSeeder: Could not load shared/skills.js. Skipping seeding.')
}
return null
}
export function ensureSkillsSeeded() {
try {
// Always ensure vocabulary tables exist (idempotent)
db.exec(`
CREATE TABLE IF NOT EXISTS controlled_vocabulary (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
value TEXT NOT NULL,
description TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
UNIQUE(category, value)
);
CREATE INDEX IF NOT EXISTS idx_vocab_category ON controlled_vocabulary(category);
CREATE INDEX IF NOT EXISTS idx_vocab_value ON controlled_vocabulary(value);
`)
db.exec(`
CREATE TABLE IF NOT EXISTS skills (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT,
requires_certification INTEGER DEFAULT 0,
expires_after INTEGER
)
`)
const hierarchy = loadSharedHierarchy()
if (!hierarchy) return
const now = new Date().toISOString()
const insertVocab = db.prepare(`
INSERT OR IGNORE INTO controlled_vocabulary (id, category, value, description, is_active, created_at)
VALUES (?, ?, ?, ?, ?, ?)
`)
const insertSkill = db.prepare(`
INSERT OR IGNORE INTO skills (id, name, category, description, requires_certification, expires_after)
VALUES (?, ?, ?, ?, ?, ?)
`)
let cats = 0, subs = 0, skills = 0
for (const cat of hierarchy) {
const catId = String(cat.id)
const catName = String(cat.name || cat.id)
// category
insertVocab.run(cryptoRandomUUID(), 'skill_category', catId, catName, 1, now)
cats++
for (const sub of (cat.subcategories || [])) {
const subId = String(sub.id)
const subName = String(sub.name || sub.id)
const key = `${catId}.${subId}`
insertVocab.run(cryptoRandomUUID(), 'skill_subcategory', key, subName, 1, now)
subs++
for (const sk of (sub.skills || [])) {
const sId = `${key}.${sk.id}`
const sName = String(sk.name || sk.id)
const requires = (catId === 'certifications' || subId === 'weapons') ? 1 : 0
const expires = (catId === 'certifications') ? 36 : null
insertSkill.run(sId, sName, key, null, requires, expires)
skills++
}
}
}
logger.info(`SkillSeeder: ensured ${cats} categories, ${subs} subcategories, ${skills} skills (idempotent).`)
} catch (err) {
logger.warn('SkillSeeder failed (non-fatal): ' + (err as Error).message)
}
}
function cryptoRandomUUID() {
// Node 18 has crypto.randomUUID, but keep a tiny fallback
try { return (require('crypto').randomUUID() as string) } catch {}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}

Datei anzeigen

@ -1,60 +0,0 @@
@echo off
title SkillMate Debug Console - Alle Services
color 0E
echo.
echo ================================================
echo SkillMate Debug Console - Alle Services
echo Backend mit vollstaendigem Debug-Log
echo + Frontend + Admin Panel
echo ================================================
echo.
echo Starting all SkillMate services...
echo Zeit: %DATE% %TIME%
echo.
REM Start Frontend
echo Starting Frontend on port 5173...
start "Frontend" cmd /k "cd frontend && npm run dev"
REM Wait a bit
timeout /t 2 /nobreak > nul
REM Start Admin Panel
echo Starting Admin Panel on port 3006...
start "Admin Panel" cmd /k "cd admin-panel && npm run dev"
REM Wait a bit more
timeout /t 3 /nobreak > nul
REM Open browsers after services start
echo Opening browsers...
timeout /t 5 /nobreak > nul
start http://localhost:3006
timeout /t 2 /nobreak > nul
start http://localhost:5173
echo.
echo ================================================
echo Backend Debug Log startet jetzt...
echo Alle API-Requests werden hier angezeigt
echo ================================================
echo.
cd backend
set PORT=3005
set FIELD_ENCRYPTION_KEY=dev_field_key_change_in_production_32chars_min!
set NODE_ENV=development
set DEBUG=*
echo Backend laeuft auf http://localhost:3005
echo Frontend laeuft auf http://localhost:5173
echo Admin Panel laeuft auf http://localhost:3006
echo.
echo === DEBUG LOG ===
echo.
node full-backend-3005.js
echo.
echo Backend wurde beendet. Druecken Sie eine Taste zum Schliessen...
pause

Datei anzeigen

@ -7,6 +7,11 @@ Layers
- Adapters/HTTP: `backend/src/routes/*` map HTTP <-> use-cases and validate inputs.
- Infra/Services: encryption, email, sync components, logger.
Database Ownership
- Canonical core schema lives in `backend/src/config/secureDatabase.ts` (users, employees, skills + junctions, language_skills, specializations, controlled_vocabulary, system_settings, security_audit_log). These include field encryption and hash indexes.
- Extended, non-sensitive schema lives in `backend/src/config/database.ts` (profiles + facets, workspaces/bookings, analytics, organizational structure, deputies, reminders, audit_log).
- Initialization order: `initializeSecureDatabase()` runs before `initializeDatabase()`. Routes import `db` from either module, but both resolve to the same secure DB instance.
Security
- JWT required; in production `JWT_SECRET` must be set.
- Field-level encryption (AES) for sensitive data + deterministic hashes for lookups.

Datei anzeigen

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import api from '../services/api'
import { DeputyAssignment, Employee } from '@skillmate/shared'
import type { DeputyAssignment, Employee } from '@skillmate/shared'
import { useAuthStore } from '../stores/authStore'
export default function DeputyManagement() {

Datei anzeigen

@ -1,5 +1,5 @@
import { useEffect, useState, useCallback } from 'react'
import { OrganizationalUnit, EmployeeUnitAssignment } from '@skillmate/shared'
import type { OrganizationalUnit, EmployeeUnitAssignment } from '@skillmate/shared'
import api from '../services/api'
import { useAuthStore } from '../stores/authStore'

Datei anzeigen

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { OrganizationalUnit } from '@skillmate/shared'
import type { OrganizationalUnit } from '@skillmate/shared'
import api from '../services/api'
import { ChevronRight, Building2, Search, X } from 'lucide-react'

Datei anzeigen

@ -1,4 +1,4 @@
import { UserRole } from '@skillmate/shared'
import type { UserRole } from '@skillmate/shared'
import { useAuthStore } from '../stores/authStore'
// Define role permissions directly to avoid import issues

Datei anzeigen

@ -1,5 +1,5 @@
import axios from 'axios'
import { Employee } from '@skillmate/shared'
import type { Employee } from '@skillmate/shared'
const API_BASE_URL = (import.meta as any).env?.VITE_API_URL || 'http://localhost:3004/api'
@ -31,7 +31,8 @@ export const authApi = {
export const employeeApi = {
getAll: async () => {
const response = await api.get('/employees/public')
// Hole vollständige Liste (berechtigt durch employees:read)
const response = await api.get('/employees')
return response.data.data
},
getById: async (id: string) => {

Datei anzeigen

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { api } from '../services/api'
import { Workspace, Booking, WorkspaceFilter } from '@skillmate/shared'
import type { Workspace, Booking, WorkspaceFilter } from '@skillmate/shared'
export default function DeskBooking() {
const navigate = useNavigate()

Datei anzeigen

@ -4,6 +4,7 @@ import type { Employee } from '@skillmate/shared'
import { employeeApi } from '../services/api'
import { SKILL_HIERARCHY, LANGUAGE_LEVELS } from '../data/skillCategories'
import PhotoPreview from '../components/PhotoPreview'
import OrganizationSelector from '../components/OrganizationSelector'
export default function EmployeeForm() {
const navigate = useNavigate()
@ -28,6 +29,8 @@ export default function EmployeeForm() {
languages: [] as string[],
specializations: [] as string[]
})
const [primaryUnitId, setPrimaryUnitId] = useState<string | null>(null)
const [primaryUnitName, setPrimaryUnitName] = useState<string>('')
const [employeePhoto, setEmployeePhoto] = useState<string | null>(null)
const [photoFile, setPhotoFile] = useState<File | null>(null)
@ -178,6 +181,7 @@ export default function EmployeeForm() {
if (!formData.email.trim()) errors.email = 'E-Mail ist erforderlich'
else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.email = 'Ungültige E-Mail-Adresse'
if (!formData.phone.trim()) errors.phone = 'Telefonnummer ist erforderlich'
if (!primaryUnitId) errors.primaryUnitId = 'Organisatorische Einheit ist erforderlich'
setValidationErrors(errors)
return Object.keys(errors).length === 0
@ -220,7 +224,7 @@ export default function EmployeeForm() {
createdBy: 'admin'
}
const result = await employeeApi.create(newEmployee)
const result = await employeeApi.create({ ...newEmployee, primaryUnitId })
const newEmployeeId = result.data.id
// Upload photo if we have one
@ -407,6 +411,23 @@ export default function EmployeeForm() {
)}
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-secondary mb-2">
Organisatorische Einheit (Primär) *
</label>
<OrganizationSelector
value={primaryUnitId || undefined}
onChange={(unitId, unitName) => {
setPrimaryUnitId(unitId)
setPrimaryUnitName(unitName)
}}
/>
<p className="text-tertiary text-sm mt-2">{primaryUnitName || 'Bitte auswählen'}</p>
{validationErrors.primaryUnitId && (
<p className="mt-1 text-sm text-red-600">{validationErrors.primaryUnitId}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-secondary mb-2">
Telefon *

Datei anzeigen

@ -1,10 +1,10 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import type { Employee } from '@skillmate/shared'
import { SearchIcon } from '../components/icons'
import EmployeeCard from '../components/EmployeeCard'
import { employeeApi } from '../services/api'
import { useAuthStore } from '../stores/authStore'
import type { Employee } from '@skillmate/shared'
import { usePermissions } from '../hooks/usePermissions'
export default function EmployeeList() {

Datei anzeigen

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import { api } from '../services/api'
import { Workspace, Booking } from '@skillmate/shared'
import type { Workspace, Booking } from '@skillmate/shared'
interface WorkspaceWithStatus extends Workspace {
isBooked?: boolean

Datei anzeigen

@ -11,14 +11,31 @@ export default defineConfig({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
'process.platform': JSON.stringify('win32')
},
server: {
port: 5173,
strictPort: true,
fs: {
// erlaubt Zugriff auf Monorepo-Nachbarordner (z. B. ../shared)
allow: ['..']
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
// erhöht Kompatibilität mit verlinkten/local deps
preserveSymlinks: true,
},
optimizeDeps: {
include: ['@skillmate/shared'],
},
build: {
outDir: 'dist',
emptyOutDir: true,
commonjsOptions: {
include: [/node_modules/, /@skillmate\/shared/],
transformMixedEsModules: true,
},
rollupOptions: {
external: ['electron'],
}

Datei anzeigen

@ -1,32 +0,0 @@
@echo off
echo Installing all dependencies...
echo.
echo [1/4] Installing root dependencies...
call npm install
echo.
echo [2/4] Installing backend dependencies...
cd backend
call npm install
cd ..
echo.
echo [3/4] Installing frontend dependencies...
cd frontend
call npm install
cd ..
echo.
echo [4/4] Installing admin-panel dependencies...
if exist admin-panel (
cd admin-panel
call npm install
cd ..
)
echo.
echo ✅ All dependencies installed!
echo.
echo You can now run: start-dev.cmd
pause

65
main.py
Datei anzeigen

@ -30,6 +30,61 @@ class SkillMateStarter:
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
def _has_local_binary(self, project_dir: Path, binary: str) -> bool:
"""Prüft, ob ein lokales NPM-Binary vorhanden ist (Windows .cmd / Unix ohne Endung)."""
bin_dir = project_dir / 'node_modules' / '.bin'
if sys.platform == 'win32':
return (bin_dir / f'{binary}.cmd').exists() or (bin_dir / f'{binary}.CMD').exists()
else:
return (bin_dir / binary).exists()
def _ensure_dependencies(self) -> bool:
"""Installiert fehlende Node-Abhängigkeiten in backend, frontend, admin-panel.
Rückgabe: True bei Erfolg, False wenn Installation irreparabel fehlschlägt.
"""
projects = [
(self.base_dir / 'backend', ['nodemon', 'ts-node']),
(self.base_dir / 'frontend', ['vite']),
(self.base_dir / 'admin-panel', ['vite'])
]
any_install = False
for proj_dir, required_bins in projects:
if not proj_dir.exists():
continue
needs_install = not (proj_dir / 'node_modules').exists()
if not needs_install:
# Prüfe konkrete Binaries
for b in required_bins:
if not self._has_local_binary(proj_dir, b):
needs_install = True
break
if needs_install:
any_install = True
print(f"🔧 Installiere Abhängigkeiten in {proj_dir.name}...")
# Bevorzugt npm ci, fallback auf npm install
try:
cmd_ci = ["cmd", "/c", "npm ci"] if sys.platform == 'win32' else ["npm", "ci"]
result = subprocess.run(cmd_ci, cwd=str(proj_dir), check=False)
if result.returncode != 0:
cmd_install = ["cmd", "/c", "npm install"] if sys.platform == 'win32' else ["npm", "install"]
result = subprocess.run(cmd_install, cwd=str(proj_dir), check=False)
if result.returncode != 0:
print(f"❌ Konnte Abhängigkeiten in {proj_dir.name} nicht installieren. Prüfen Sie Internet/Proxy.")
return False
except Exception as e:
print(f"❌ Fehler bei der Installation in {proj_dir.name}: {e}")
return False
if any_install:
print("✅ Abhängigkeiten installiert. Fahre mit dem Start fort...\n")
else:
print("✅ Abhängigkeiten geprüft – alles vorhanden.\n")
return True
def signal_handler(self, signum, frame):
"""Handle Strg+C und andere Signale"""
print("\n\n🛑 SkillMate wird beendet...")
@ -121,6 +176,12 @@ class SkillMateStarter:
for p in [self.backend_port, self.frontend_port, self.admin_port]:
self._free_port_windows(p)
# Abhängigkeiten sicherstellen, bevor Services gestartet werden
print(" Prüfe/Installiere Abhängigkeiten...")
if not self._ensure_dependencies():
print("❌ Abbruch: Abhängigkeiten konnten nicht installiert werden.")
return
# Backend
backend_dir = self.base_dir / "backend"
if self.mode == 'dev':
@ -136,7 +197,7 @@ class SkillMateStarter:
# Frontend
frontend_dir = self.base_dir / "frontend"
if self.mode == 'dev':
self._popen_new_console(frontend_dir, f"npm run dev -- --port {self.frontend_port}")
self._popen_new_console(frontend_dir, f"npm run dev -- --strictPort --port {self.frontend_port}")
else:
self._popen_new_console(frontend_dir, f"cmd /c npm run build && npm run preview -- --port {self.frontend_port}")
@ -144,7 +205,7 @@ class SkillMateStarter:
admin_dir = self.base_dir / "admin-panel"
if admin_dir.exists():
if self.mode == 'dev':
self._popen_new_console(admin_dir, f"npm run dev -- --port {self.admin_port}")
self._popen_new_console(admin_dir, f"npm run dev -- --strictPort --port {self.admin_port}")
else:
self._popen_new_console(admin_dir, f"cmd /c npm run build && npm run preview -- --port {self.admin_port}")

Datei anzeigen

@ -1,16 +0,0 @@
@echo off
echo Restarting all services...
echo.
echo Killing existing Node processes...
taskkill /F /IM node.exe 2>nul
echo.
echo Rebuilding shared module...
cd shared
call npm run build
cd ..
echo.
echo Starting services...
call start-dev.cmd

Datei anzeigen

@ -1,28 +0,0 @@
@echo off
echo Starting SkillMate in development mode...
REM Start Backend Debug Console (kontinuierliche Log-Ausgabe)
start debug-console.cmd
REM Wait a bit
timeout /t 3 /nobreak > nul
REM Start Frontend
start "Frontend" cmd /k "cd frontend && npm run dev"
REM Start Admin Panel
start "Admin Panel" cmd /k "cd admin-panel && npm run dev"
echo.
echo SkillMate is starting...
echo.
echo Backend: http://localhost:3005
echo Frontend: http://localhost:5173
echo Admin: http://localhost:3006
echo.
echo Opening Admin Panel and Frontend in browser...
timeout /t 5 /nobreak > nul
start http://localhost:3006
timeout /t 2 /nobreak > nul
start http://localhost:5173

Datei anzeigen

@ -1,32 +0,0 @@
@echo off
REM SkillMate Starter für Windows
REM Prüfe ob Python installiert ist
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Python ist nicht installiert!
echo Bitte installieren Sie Python 3.8 oder höher von https://python.org
pause
exit /b 1
)
REM Gehe zum Projektverzeichnis
cd /d "%~dp0"
REM Installiere Python-Abhängigkeiten falls nötig
if not exist "venv" (
echo 🔧 Erstelle virtuelle Umgebung...
python -m venv venv
)
REM Aktiviere virtuelle Umgebung
call venv\Scripts\activate.bat
REM Installiere Abhängigkeiten
pip install -r requirements.txt >nul 2>&1
REM Starte SkillMate
echo 🚀 Starte SkillMate...
python main.py
pause

Datei anzeigen

@ -1,28 +0,0 @@
#!/bin/bash
# SkillMate Starter für Linux/Mac
# Prüfe ob Python 3 installiert ist
if ! command -v python3 &> /dev/null; then
echo "❌ Python 3 ist nicht installiert!"
echo " Bitte installieren Sie Python 3.8 oder höher"
exit 1
fi
# Gehe zum Projektverzeichnis
cd "$(dirname "$0")"
# Installiere Python-Abhängigkeiten falls nötig
if [ ! -d "venv" ]; then
echo "🔧 Erstelle virtuelle Umgebung..."
python3 -m venv venv
fi
# Aktiviere virtuelle Umgebung
source venv/bin/activate
# Installiere Abhängigkeiten
pip install -r requirements.txt 2>/dev/null
# Starte SkillMate
echo "🚀 Starte SkillMate..."
python3 main.py