feat: generation data script

This commit is contained in:
Julien Froidefond
2025-08-21 14:12:30 +02:00
parent 578f0858e8
commit c7ac6fc920
3 changed files with 577 additions and 1 deletions

View File

@@ -6,7 +6,8 @@
"build": "next build", "build": "next build",
"dev": "next dev", "dev": "next dev",
"lint": "next lint", "lint": "next lint",
"start": "next start" "start": "next start",
"generate-test-data": "tsx scripts/generate-test-data.ts"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.0.0", "@fortawesome/fontawesome-svg-core": "^7.0.0",
@@ -74,6 +75,7 @@
"postcss": "^8.5", "postcss": "^8.5",
"tailwindcss": "^4.1.9", "tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3", "tw-animate-css": "1.3.3",
"tsx": "^4.19.2",
"typescript": "^5" "typescript": "^5"
} }
} }

303
pnpm-lock.yaml generated
View File

@@ -195,6 +195,9 @@ importers:
tailwindcss: tailwindcss:
specifier: ^4.1.9 specifier: ^4.1.9
version: 4.1.12 version: 4.1.12
tsx:
specifier: ^4.19.2
version: 4.20.4
tw-animate-css: tw-animate-css:
specifier: 1.3.3 specifier: 1.3.3
version: 1.3.3 version: 1.3.3
@@ -218,6 +221,162 @@ packages:
'@emnapi/runtime@1.4.5': '@emnapi/runtime@1.4.5':
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
'@esbuild/aix-ppc64@0.25.9':
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.25.9':
resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.25.9':
resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.25.9':
resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.25.9':
resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.25.9':
resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.25.9':
resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.9':
resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.25.9':
resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.25.9':
resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.25.9':
resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.25.9':
resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.25.9':
resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.25.9':
resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.25.9':
resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.25.9':
resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.25.9':
resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.9':
resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.9':
resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.9':
resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.9':
resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.9':
resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.25.9':
resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.25.9':
resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.25.9':
resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.25.9':
resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@floating-ui/core@1.7.3': '@floating-ui/core@1.7.3':
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
@@ -1370,6 +1529,11 @@ packages:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
esbuild@0.25.9:
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
engines: {node: '>=18'}
hasBin: true
escalade@3.2.0: escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1384,6 +1548,11 @@ packages:
fraction.js@4.3.7: fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
geist@1.4.2: geist@1.4.2:
resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==} resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==}
peerDependencies: peerDependencies:
@@ -1393,6 +1562,9 @@ packages:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -1703,6 +1875,9 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
scheduler@0.26.0: scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -1774,6 +1949,11 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.20.4:
resolution: {integrity: sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==}
engines: {node: '>=18.0.0'}
hasBin: true
tw-animate-css@1.3.3: tw-animate-css@1.3.3:
resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==} resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==}
@@ -1849,6 +2029,84 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@esbuild/aix-ppc64@0.25.9':
optional: true
'@esbuild/android-arm64@0.25.9':
optional: true
'@esbuild/android-arm@0.25.9':
optional: true
'@esbuild/android-x64@0.25.9':
optional: true
'@esbuild/darwin-arm64@0.25.9':
optional: true
'@esbuild/darwin-x64@0.25.9':
optional: true
'@esbuild/freebsd-arm64@0.25.9':
optional: true
'@esbuild/freebsd-x64@0.25.9':
optional: true
'@esbuild/linux-arm64@0.25.9':
optional: true
'@esbuild/linux-arm@0.25.9':
optional: true
'@esbuild/linux-ia32@0.25.9':
optional: true
'@esbuild/linux-loong64@0.25.9':
optional: true
'@esbuild/linux-mips64el@0.25.9':
optional: true
'@esbuild/linux-ppc64@0.25.9':
optional: true
'@esbuild/linux-riscv64@0.25.9':
optional: true
'@esbuild/linux-s390x@0.25.9':
optional: true
'@esbuild/linux-x64@0.25.9':
optional: true
'@esbuild/netbsd-arm64@0.25.9':
optional: true
'@esbuild/netbsd-x64@0.25.9':
optional: true
'@esbuild/openbsd-arm64@0.25.9':
optional: true
'@esbuild/openbsd-x64@0.25.9':
optional: true
'@esbuild/openharmony-arm64@0.25.9':
optional: true
'@esbuild/sunos-x64@0.25.9':
optional: true
'@esbuild/win32-arm64@0.25.9':
optional: true
'@esbuild/win32-ia32@0.25.9':
optional: true
'@esbuild/win32-x64@0.25.9':
optional: true
'@floating-ui/core@1.7.3': '@floating-ui/core@1.7.3':
dependencies: dependencies:
'@floating-ui/utils': 0.2.10 '@floating-ui/utils': 0.2.10
@@ -2969,6 +3227,35 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.2 tapable: 2.2.2
esbuild@0.25.9:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.9
'@esbuild/android-arm': 0.25.9
'@esbuild/android-arm64': 0.25.9
'@esbuild/android-x64': 0.25.9
'@esbuild/darwin-arm64': 0.25.9
'@esbuild/darwin-x64': 0.25.9
'@esbuild/freebsd-arm64': 0.25.9
'@esbuild/freebsd-x64': 0.25.9
'@esbuild/linux-arm': 0.25.9
'@esbuild/linux-arm64': 0.25.9
'@esbuild/linux-ia32': 0.25.9
'@esbuild/linux-loong64': 0.25.9
'@esbuild/linux-mips64el': 0.25.9
'@esbuild/linux-ppc64': 0.25.9
'@esbuild/linux-riscv64': 0.25.9
'@esbuild/linux-s390x': 0.25.9
'@esbuild/linux-x64': 0.25.9
'@esbuild/netbsd-arm64': 0.25.9
'@esbuild/netbsd-x64': 0.25.9
'@esbuild/openbsd-arm64': 0.25.9
'@esbuild/openbsd-x64': 0.25.9
'@esbuild/openharmony-arm64': 0.25.9
'@esbuild/sunos-x64': 0.25.9
'@esbuild/win32-arm64': 0.25.9
'@esbuild/win32-ia32': 0.25.9
'@esbuild/win32-x64': 0.25.9
escalade@3.2.0: {} escalade@3.2.0: {}
eventemitter3@4.0.7: {} eventemitter3@4.0.7: {}
@@ -2977,12 +3264,19 @@ snapshots:
fraction.js@4.3.7: {} fraction.js@4.3.7: {}
fsevents@2.3.3:
optional: true
geist@1.4.2(next@15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)): geist@1.4.2(next@15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)):
dependencies: dependencies:
next: 15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next: 15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
get-nonce@1.0.1: {} get-nonce@1.0.1: {}
get-tsconfig@4.10.1:
dependencies:
resolve-pkg-maps: 1.0.0
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
input-otp@1.4.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): input-otp@1.4.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
@@ -3259,6 +3553,8 @@ snapshots:
tiny-invariant: 1.3.3 tiny-invariant: 1.3.3
victory-vendor: 36.9.2 victory-vendor: 36.9.2
resolve-pkg-maps@1.0.0: {}
scheduler@0.26.0: {} scheduler@0.26.0: {}
semver@7.7.2: semver@7.7.2:
@@ -3335,6 +3631,13 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tsx@4.20.4:
dependencies:
esbuild: 0.25.9
get-tsconfig: 4.10.1
optionalDependencies:
fsevents: 2.3.3
tw-animate-css@1.3.3: {} tw-animate-css@1.3.3: {}
typescript@5.9.2: {} typescript@5.9.2: {}

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env tsx
import { getPool, closePool } from "../services/database";
import { SkillLevel } from "../lib/types";
import * as fs from "fs";
import * as path from "path";
// Données de test
const TEST_DIRECTION = "TestData";
const TEST_TEAMS = [
{ id: "test-frontend", name: "Test Frontend Team" },
{ id: "test-backend", name: "Test Backend Team" },
{ id: "test-fullstack", name: "Test Full Stack Team" },
{ id: "test-mobile", name: "Test Mobile Team" },
{ id: "test-devops", name: "Test DevOps Team" },
];
const FIRST_NAMES = [
"Alice",
"Bob",
"Claire",
"David",
"Emma",
"François",
"Julie",
"Marc",
"Sophie",
"Thomas",
"Amélie",
"Nicolas",
"Camille",
"Pierre",
"Léa",
"Julien",
"Marie",
"Antoine",
"Sarah",
"Maxime",
];
const LAST_NAMES = [
"Martin",
"Bernard",
"Dubois",
"Thomas",
"Robert",
"Petit",
"Durand",
"Leroy",
"Moreau",
"Simon",
"Laurent",
"Lefebvre",
"Michel",
"Garcia",
"David",
"Bertrand",
"Roux",
"Vincent",
"Fournier",
"Morel",
];
const SKILL_LEVELS: SkillLevel[] = [
"never",
"not-autonomous",
"autonomous",
"expert",
];
interface SkillData {
id: string;
name: string;
description: string;
icon?: string;
category: string;
}
async function loadAllSkills(): Promise<SkillData[]> {
const skillsDir = path.join(__dirname, "../data/skills");
const files = fs
.readdirSync(skillsDir)
.filter((file) => file.endsWith(".json"));
const allSkills: SkillData[] = [];
for (const file of files) {
const filePath = path.join(skillsDir, file);
const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
content.skills.forEach((skill: any) => {
allSkills.push({
...skill,
category: content.category,
});
});
}
return allSkills;
}
function getRandomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
function getRandomElements<T>(array: T[], count: number): T[] {
const shuffled = [...array].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
function generateRandomLevel(): SkillLevel {
const weights = [0.3, 0.4, 0.25, 0.05]; // never, not-autonomous, autonomous, expert
const random = Math.random();
let cumulative = 0;
for (let i = 0; i < weights.length; i++) {
cumulative += weights[i];
if (random <= cumulative) {
return SKILL_LEVELS[i];
}
}
return "never";
}
async function insertTestTeams() {
const pool = getPool();
console.log("🏢 Création des teams de test...");
for (const team of TEST_TEAMS) {
await pool.query(
"INSERT INTO teams (id, name, direction) VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING",
[team.id, team.name, TEST_DIRECTION]
);
console.log(` ✅ Team créée: ${team.name}`);
}
}
async function insertTestUsers() {
const pool = getPool();
console.log("👥 Création des utilisateurs de test...");
const users = [];
// Créer 3-5 utilisateurs par team
for (const team of TEST_TEAMS) {
const userCount = 3 + Math.floor(Math.random() * 3); // 3 à 5 utilisateurs
for (let i = 0; i < userCount; i++) {
const firstName = getRandomElement(FIRST_NAMES);
const lastName = getRandomElement(LAST_NAMES);
try {
const result = await pool.query(
`INSERT INTO users (first_name, last_name, team_id)
VALUES ($1, $2, $3)
ON CONFLICT (first_name, last_name, team_id) DO NOTHING
RETURNING uuid_id`,
[firstName, lastName, team.id]
);
if (result.rows.length > 0) {
users.push({
uuid: result.rows[0].uuid_id,
firstName,
lastName,
teamId: team.id,
});
console.log(
` ✅ Utilisateur créé: ${firstName} ${lastName} (${team.name})`
);
}
} catch (error) {
// Ignore les conflits de nom
console.log(
` ⚠️ Utilisateur ${firstName} ${lastName} existe déjà dans ${team.name}`
);
}
}
}
return users;
}
async function insertTestEvaluations(users: any[], skills: SkillData[]) {
const pool = getPool();
console.log("📊 Création des évaluations de test...");
for (const user of users) {
console.log(` 📝 Évaluation pour ${user.firstName} ${user.lastName}...`);
// Créer une évaluation pour cet utilisateur
const evaluationResult = await pool.query(
`INSERT INTO user_evaluations (user_uuid)
VALUES ($1)
ON CONFLICT (user_uuid) DO UPDATE SET last_updated = CURRENT_TIMESTAMP
RETURNING id`,
[user.uuid]
);
const evaluationId = evaluationResult.rows[0].id;
// Sélectionner aléatoirement 15-30 skills à évaluer
const skillsToEvaluate = getRandomElements(
skills,
15 + Math.floor(Math.random() * 16)
);
for (const skill of skillsToEvaluate) {
const level = generateRandomLevel();
const canMentor = level === "expert" ? Math.random() > 0.5 : false;
const wantsToLearn =
level === "never" || level === "not-autonomous"
? Math.random() > 0.3
: false;
await pool.query(
`INSERT INTO skill_evaluations (user_evaluation_id, skill_id, level, can_mentor, wants_to_learn, is_selected)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (user_evaluation_id, skill_id) DO UPDATE SET
level = $3, can_mentor = $4, wants_to_learn = $5, updated_at = CURRENT_TIMESTAMP`,
[evaluationId, skill.id, level, canMentor, wantsToLearn, true]
);
}
console.log(`${skillsToEvaluate.length} skills évaluées`);
}
}
async function main() {
try {
console.log("🚀 Génération des données de test...\n");
// Charger tous les skills
console.log("📚 Chargement des skills...");
const skills = await loadAllSkills();
console.log(`${skills.length} skills chargées\n`);
// Créer les teams
await insertTestTeams();
console.log("");
// Créer les utilisateurs
const users = await insertTestUsers();
console.log(` 📊 Total: ${users.length} utilisateurs créés\n`);
// Créer les évaluations
await insertTestEvaluations(users, skills);
console.log("");
console.log("✨ Données de test générées avec succès !");
console.log(`📈 Résumé :`);
console.log(` - Direction: ${TEST_DIRECTION}`);
console.log(` - Teams: ${TEST_TEAMS.length}`);
console.log(` - Utilisateurs: ${users.length}`);
console.log(` - Skills disponibles: ${skills.length}`);
} catch (error) {
console.error("❌ Erreur lors de la génération des données:", error);
process.exit(1);
} finally {
await closePool();
}
}
// Exécuter le script
if (require.main === module) {
main();
}