Zwiscshenstand - laufende Version
Dieser Commit ist enthalten in:
@ -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
|
||||
|
||||
24
README.md
24
README.md
@ -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
|
||||
|
||||
491
admin-panel/package-lock.json
generiert
491
admin-panel/package-lock.json
generiert
@ -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",
|
||||
|
||||
35
backend/package-lock.json
generiert
35
backend/package-lock.json
generiert
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
134
backend/scripts/seed-skills-from-shared.js
Normale Datei
134
backend/scripts/seed-skills-from-shared.js
Normale Datei
@ -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()
|
||||
|
||||
@ -9,22 +9,30 @@ export { initializeSecureDatabase } from './secureDatabase'
|
||||
// Export the secure database instance
|
||||
export const db = secureDb
|
||||
|
||||
// IMPORTANT: secureDatabase.ts owns all security‑sensitive 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, non‑sensitive
|
||||
// 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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
112
backend/src/services/skillSeeder.ts
Normale Datei
112
backend/src/services/skillSeeder.ts
Normale Datei
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 *
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'],
|
||||
}
|
||||
|
||||
@ -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
65
main.py
@ -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}")
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren