unfinished image API

This commit is contained in:
Tzvetan Trave
2025-10-17 20:06:54 +02:00
parent a265583d4d
commit 0fdbc96dc4
15 changed files with 518 additions and 31 deletions

View File

@ -11,6 +11,10 @@ set dotenv-load
@user $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true": @user $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true":
fastify start src/api/user/default.js fastify start src/api/user/default.js
# For launching the images api
@images $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true":
fastify start src/api/images/default.js
@scoreStore $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true": @scoreStore $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true":
fastify start src/api/scoreStore/default.js fastify start src/api/scoreStore/default.js

View File

@ -4,6 +4,7 @@
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@fastify/env": "^5.0.2", "@fastify/env": "^5.0.2",
"@fastify/jwt": "^9.1.0", "@fastify/jwt": "^9.1.0",
"@fastify/multipart": "^9.2.1",
"axios": "^1.10.0", "axios": "^1.10.0",
"base32.js": "^0.1.0", "base32.js": "^0.1.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
@ -13,6 +14,7 @@
"fastify-cli": "^7.4.0", "fastify-cli": "^7.4.0",
"pino": "^9.7.0", "pino": "^9.7.0",
"prom-client": "^15.1.3", "prom-client": "^15.1.3",
"sharp": "^0.34.4",
"solhint": "^6.0.0" "solhint": "^6.0.0"
}, },
"type": "module", "type": "module",

288
pnpm-lock.yaml generated
View File

@ -20,6 +20,9 @@ importers:
'@fastify/jwt': '@fastify/jwt':
specifier: ^9.1.0 specifier: ^9.1.0
version: 9.1.0 version: 9.1.0
'@fastify/multipart':
specifier: ^9.2.1
version: 9.2.1
axios: axios:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
@ -47,6 +50,9 @@ importers:
prom-client: prom-client:
specifier: ^15.1.3 specifier: ^15.1.3
version: 15.1.3 version: 15.1.3
sharp:
specifier: ^0.34.4
version: 0.34.4
solhint: solhint:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0(typescript@5.8.3) version: 6.0.0(typescript@5.8.3)
@ -88,6 +94,9 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@emnapi/runtime@1.5.0':
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
'@esbuild/aix-ppc64@0.25.6': '@esbuild/aix-ppc64@0.25.6':
resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -252,12 +261,18 @@ packages:
'@fastify/ajv-compiler@4.0.2': '@fastify/ajv-compiler@4.0.2':
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
'@fastify/busboy@3.2.0':
resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==}
'@fastify/cookie@11.0.2': '@fastify/cookie@11.0.2':
resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==}
'@fastify/deepmerge@2.0.2': '@fastify/deepmerge@2.0.2':
resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==} resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==}
'@fastify/deepmerge@3.1.0':
resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==}
'@fastify/env@5.0.2': '@fastify/env@5.0.2':
resolution: {integrity: sha512-4m/jHS3s/G/DBJVODob9sxGUei/Ij8JFbA2PYqBfoihTm+Qqae2xD9xhez68UFZu1d4SNJPIb6uAOwbNvRYw+A==} resolution: {integrity: sha512-4m/jHS3s/G/DBJVODob9sxGUei/Ij8JFbA2PYqBfoihTm+Qqae2xD9xhez68UFZu1d4SNJPIb6uAOwbNvRYw+A==}
@ -276,6 +291,9 @@ packages:
'@fastify/merge-json-schemas@0.2.1': '@fastify/merge-json-schemas@0.2.1':
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
'@fastify/multipart@9.2.1':
resolution: {integrity: sha512-U4221XDMfzCUtfzsyV1/PkR4MNgKI0158vUUyn/oF2Tl6RxMc+N7XYLr5fZXQiEC+Fmw5zFaTjxsTGTgtDtK+g==}
'@fastify/proxy-addr@5.0.0': '@fastify/proxy-addr@5.0.0':
resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==} resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==}
@ -283,6 +301,132 @@ packages:
resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.4':
resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.4':
resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.3':
resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.3':
resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.3':
resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.2.3':
resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-ppc64@1.2.3':
resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
cpu: [ppc64]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.2.3':
resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.2.3':
resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.34.4':
resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.34.4':
resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
'@img/sharp-linux-ppc64@0.34.4':
resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
'@img/sharp-linux-s390x@0.34.4':
resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.34.4':
resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.34.4':
resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.34.4':
resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.34.4':
resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.4':
resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.4':
resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.4':
resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@isaacs/fs-minipass@4.0.1': '@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@ -753,6 +897,10 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'} engines: {node: '>=8'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dotenv-expand@10.0.0: dotenv-expand@10.0.0:
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1452,6 +1600,10 @@ packages:
set-cookie-parser@2.7.1: set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
sharp@0.34.4:
resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
simple-concat@1.0.1: simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
@ -1667,6 +1819,11 @@ snapshots:
'@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {}
'@emnapi/runtime@1.5.0':
dependencies:
tslib: 2.7.0
optional: true
'@esbuild/aix-ppc64@0.25.6': '@esbuild/aix-ppc64@0.25.6':
optional: true optional: true
@ -1753,6 +1910,8 @@ snapshots:
ajv-formats: 3.0.1(ajv@8.17.1) ajv-formats: 3.0.1(ajv@8.17.1)
fast-uri: 3.0.6 fast-uri: 3.0.6
'@fastify/busboy@3.2.0': {}
'@fastify/cookie@11.0.2': '@fastify/cookie@11.0.2':
dependencies: dependencies:
cookie: 1.0.2 cookie: 1.0.2
@ -1760,6 +1919,8 @@ snapshots:
'@fastify/deepmerge@2.0.2': {} '@fastify/deepmerge@2.0.2': {}
'@fastify/deepmerge@3.1.0': {}
'@fastify/env@5.0.2': '@fastify/env@5.0.2':
dependencies: dependencies:
env-schema: 6.0.1 env-schema: 6.0.1
@ -1785,6 +1946,14 @@ snapshots:
dependencies: dependencies:
dequal: 2.0.3 dequal: 2.0.3
'@fastify/multipart@9.2.1':
dependencies:
'@fastify/busboy': 3.2.0
'@fastify/deepmerge': 3.1.0
'@fastify/error': 4.2.0
fastify-plugin: 5.0.1
secure-json-parse: 4.0.0
'@fastify/proxy-addr@5.0.0': '@fastify/proxy-addr@5.0.0':
dependencies: dependencies:
'@fastify/forwarded': 3.0.0 '@fastify/forwarded': 3.0.0
@ -1792,6 +1961,94 @@ snapshots:
'@humanwhocodes/momoa@2.0.4': {} '@humanwhocodes/momoa@2.0.4': {}
'@img/colour@1.0.0': {}
'@img/sharp-darwin-arm64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.3
optional: true
'@img/sharp-darwin-x64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.3
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.3':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.3':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.3':
optional: true
'@img/sharp-libvips-linux-arm@1.2.3':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.3':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.3':
optional: true
'@img/sharp-libvips-linux-x64@1.2.3':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
optional: true
'@img/sharp-linux-arm64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.3
optional: true
'@img/sharp-linux-arm@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.3
optional: true
'@img/sharp-linux-ppc64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.3
optional: true
'@img/sharp-linux-s390x@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.3
optional: true
'@img/sharp-linux-x64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.3
optional: true
'@img/sharp-linuxmusl-arm64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
optional: true
'@img/sharp-linuxmusl-x64@0.34.4':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
optional: true
'@img/sharp-wasm32@0.34.4':
dependencies:
'@emnapi/runtime': 1.5.0
optional: true
'@img/sharp-win32-arm64@0.34.4':
optional: true
'@img/sharp-win32-ia32@0.34.4':
optional: true
'@img/sharp-win32-x64@0.34.4':
optional: true
'@isaacs/fs-minipass@4.0.1': '@isaacs/fs-minipass@4.0.1':
dependencies: dependencies:
minipass: 7.1.2 minipass: 7.1.2
@ -2183,6 +2440,8 @@ snapshots:
detect-libc@2.0.4: {} detect-libc@2.0.4: {}
detect-libc@2.1.2: {}
dotenv-expand@10.0.0: {} dotenv-expand@10.0.0: {}
dotenv@16.6.1: {} dotenv@16.6.1: {}
@ -2914,6 +3173,35 @@ snapshots:
set-cookie-parser@2.7.1: {} set-cookie-parser@2.7.1: {}
sharp@0.34.4:
dependencies:
'@img/colour': 1.0.0
detect-libc: 2.1.2
semver: 7.7.2
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.4
'@img/sharp-darwin-x64': 0.34.4
'@img/sharp-libvips-darwin-arm64': 1.2.3
'@img/sharp-libvips-darwin-x64': 1.2.3
'@img/sharp-libvips-linux-arm': 1.2.3
'@img/sharp-libvips-linux-arm64': 1.2.3
'@img/sharp-libvips-linux-ppc64': 1.2.3
'@img/sharp-libvips-linux-s390x': 1.2.3
'@img/sharp-libvips-linux-x64': 1.2.3
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
'@img/sharp-linux-arm': 0.34.4
'@img/sharp-linux-arm64': 0.34.4
'@img/sharp-linux-ppc64': 0.34.4
'@img/sharp-linux-s390x': 0.34.4
'@img/sharp-linux-x64': 0.34.4
'@img/sharp-linuxmusl-arm64': 0.34.4
'@img/sharp-linuxmusl-x64': 0.34.4
'@img/sharp-wasm32': 0.34.4
'@img/sharp-win32-arm64': 0.34.4
'@img/sharp-win32-ia32': 0.34.4
'@img/sharp-win32-x64': 0.34.4
simple-concat@1.0.1: {} simple-concat@1.0.1: {}
simple-get@4.0.1: simple-get@4.0.1:

View File

@ -1,5 +1,5 @@
import authDB from '../../utils/authDB'; import authDB from '../../utils/authDB.js';
import { authUserRemove } from '../../utils/authUserRemove'; import { authUserRemove } from '../../utils/authUserRemove.js';
/** /**
* @param {import('fastify').FastifyRequest} request * @param {import('fastify').FastifyRequest} request

10
src/api/images/dImage.js Normal file
View File

@ -0,0 +1,10 @@
export async function dImage(request, reply, fastify, deleteImage) {
try {
const imageId = Number(request.params.imageId);
deleteImage.run(imageId);
return reply.code(200).send({ msg: "Image deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

86
src/api/images/default.js Normal file
View File

@ -0,0 +1,86 @@
import fastifyJWT from '@fastify/jwt';
import fastifyCookie from '@fastify/cookie';
import Database from 'better-sqlite3';
import multipart from '@fastify/multipart';
import { gImage } from './gImage.js';
import { pImage } from './pImage.js';
import { dImage } from './dImage.js';
const env = process.env.NODE_ENV || 'development';
let database;
if (!env || env === 'development') {
database = new Database(':memory:', { verbose: console.log });
} else {
const dbPath = process.env.DB_PATH || '/db/db.sqlite'
database = new Database(dbPath);
}
function prepareDB() {
database.exec(`
CREATE TABLE IF NOT EXISTS images (
imageId INTEGER PRIMARY KEY AUTOINCREMENT,
fileName TEXT,
mimeType TEXT,
data BLOB
) STRICT
`);
}
prepareDB();
// POST
const postImage = database.prepare('INSERT INTO images (fileName, mimeType, data) VALUES (?, ?, ?);');
// GET
const getImage = database.prepare('SELECT fileName, mimeType, data FROM images WHERE imageId = ?;');
// DELETE
const deleteImage = database.prepare('DELETE FROM images WHERE imageId = ?;');
export default async function(fastify, options) {
fastify.register(fastifyJWT, {
secret: process.env.JWT_SECRET || '123456789101112131415161718192021',
cookie: {
cookieName: 'token',
},
});
fastify.register(fastifyCookie);
fastify.register(multipart, { limits: { fileSize: 2 * 1024 * 1024 } });
fastify.decorate('authenticate', async function(request, reply) {
try {
const jwt = await request.jwtVerify();
request.user = jwt.user;
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
fastify.decorate('authenticateAdmin', async function(request, reply) {
try {
const jwt = await request.jwtVerify();
if (jwt.user !== 'admin') {
throw ('You lack administrator privileges');
}
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
// GET
fastify.get('/images/:imageId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gImage(request, reply, fastify, getImage);
});
// POST
fastify.post('/images', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return pImage(request, reply, fastify, postImage);
});
// DELETE
fastify.delete('/images/:imageId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return dImage(request, reply, fastify, deleteImage);
});
}

13
src/api/images/gImage.js Normal file
View File

@ -0,0 +1,13 @@
export async function gImage(request, reply, fastify, getImage) {
try {
const imageId = Number(request.params.imageId);
const image = getImage.get(imageId);
if (!image) {
return reply.code(404).send({ error: "Image does not exist" });
}
return reply.code(200).type(image.mimeType).header('Content-Disposition', `inline; filename="${image.fileName}"`).send(image.data);
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

33
src/api/images/pImage.js Normal file
View File

@ -0,0 +1,33 @@
export async function pImage(request, reply, fastify, postImage) {
try {
const parts = request.parts();
for await (const part of parts) {
if (part.file) {
const chunks = [];
for await (const chunk of part.file) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
if (!part.filename || part.filename.trim() === '') {
return reply.code(400).send({ error: "Missing filename" });
}
if (!part.mimetype || part.mimetype.trim() === '') {
return reply.code(400).send({ error: "Missing mimetype" });
}
const ext = part.filename.toLowerCase().substring(part.filename.lastIndexOf('.'));
if (ext !== 'webp') {
return reply.code(400).send({ error: "Wrong file extension" });
}
// check size max here ?
// convert image to webp using sharp
//sharp(buffer, ).toFile();
const id = postImage.run(part.filename, part.mimetype, buffer);
return reply.code(200).send({ msg: "Image uploaded successfully", imageId: id.lastInsertRowid });
}
}
return reply.code(400).send({ error: "No file uploaded" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -1,8 +0,0 @@
Todo :
- create users with an avatar (by default) -> POST/GET/PATCH/DELETE avatar
- create a whole image upload API that ensures files are not executables, converts to a single type, stores the image and returns a UID to address them
- add a privacy setting so not anybody can GET friends, match history, etc. (what are the RGPD requirements ?) ?
Always update API doc

9
src/api/user/dAvatar.js Normal file
View File

@ -0,0 +1,9 @@
export async function dAvatar(request, reply, fastify, deleteAvatarId) {
try {
;
return reply.code(200).send({ msg: "Avatar deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -2,22 +2,25 @@ import fastifyJWT from '@fastify/jwt';
import fastifyCookie from '@fastify/cookie'; import fastifyCookie from '@fastify/cookie';
import Database from 'better-sqlite3'; import Database from 'better-sqlite3';
import { gUsers } from './gUsers.js' import { gUsers } from './gUsers.js';
import { gUser } from './gUser.js' import { gUser } from './gUser.js';
import { gNumberUsers } from './gNumberUsers.js' import { gNumberUsers } from './gNumberUsers.js';
import { gFriends } from './gFriends.js' import { gFriends } from './gFriends.js';
import { gNumberFriends } from './gNumberFriends.js' import { gNumberFriends } from './gNumberFriends.js';
import { gMatchHistory } from './gMatchHistory.js' import { gMatchHistory } from './gMatchHistory.js';
import { gNumberMatches } from './gNumberMatches.js' import { gNumberMatches } from './gNumberMatches.js';
import { pUser } from './pUser.js' import { pUser } from './pUser.js';
import { pFriend } from './pFriend.js' import { pFriend } from './pFriend.js';
import { pMatchHistory } from './pMatchHistory.js' import { pMatchHistory } from './pMatchHistory.js';
import { uMember } from './uMember.js' import { uMember } from './uMember.js';
import { dUser } from './dUser.js' import { dUser } from './dUser.js';
import { dMember } from './dMember.js' import { dMember } from './dMember.js';
import { dFriends } from './dFriends.js' import { dFriends } from './dFriends.js';
import { dFriend } from './dFriend.js' import { dFriend } from './dFriend.js';
import { dMatchHistory } from './dMatchHistory.js' import { dMatchHistory } from './dMatchHistory.js';
import { pAvatar } from './pAvatar.js';
import { gAvatar } from './gAvatar.js';
import { dAvatar } from './dAvatar.js';
const env = process.env.NODE_ENV || 'development'; const env = process.env.NODE_ENV || 'development';
@ -35,6 +38,7 @@ function prepareDB() {
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT, username TEXT,
displayName TEXT, displayName TEXT,
avatarId INTEGER,
pongWins INTEGER, pongWins INTEGER,
pongLosses INTEGER, pongLosses INTEGER,
tetrisWins INTEGER, tetrisWins INTEGER,
@ -73,16 +77,18 @@ function prepareDB() {
prepareDB(); prepareDB();
// POST // POST
const createUser = database.prepare('INSERT INTO userData (username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses) VALUES (?, ?, 0, 0, 0, 0);'); const createUser = database.prepare('INSERT INTO userData (username, displayName, avatarId, pongWins, pongLosses, tetrisWins, tetrisLosses) VALUES (?, ?, -1, 0, 0, 0, 0);');
const addFriend = database.prepare('INSERT INTO friends (username, friendName) VALUES (?, ?);'); const addFriend = database.prepare('INSERT INTO friends (username, friendName) VALUES (?, ?);');
const addMatch = database.prepare('INSERT INTO matchHistory (game, date, player1, player2, matchId) VALUES (?, ?, ?, ?, ?);'); const addMatch = database.prepare('INSERT INTO matchHistory (game, date, player1, player2, matchId) VALUES (?, ?, ?, ?, ?);');
const incWinsPong = database.prepare('UPDATE userData SET pongWins = pongWins + 1 WHERE username = ?;'); const incWinsPong = database.prepare('UPDATE userData SET pongWins = pongWins + 1 WHERE username = ?;');
const incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLosses + 1 WHERE username = ?'); const incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLosses + 1 WHERE username = ?');
const incWinsTetris = database.prepare('UPDATE userData SET tetrisWins = tetrisWins + 1 WHERE username = ?;'); const incWinsTetris = database.prepare('UPDATE userData SET tetrisWins = tetrisWins + 1 WHERE username = ?;');
const incLossesTetris = database.prepare('UPDATE userData SET tetrisLosses = tetrisLosses + 1 WHERE username = ?'); const incLossesTetris = database.prepare('UPDATE userData SET tetrisLosses = tetrisLosses + 1 WHERE username = ?');
const setAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
// PATCH // PATCH
const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;'); const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;');
const changeAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
// GET // GET
const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;'); const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;');
@ -92,7 +98,8 @@ const getFriend = database.prepare('SELECT friendName FROM friends WHERE usernam
const getMatchHistory = database.prepare('SELECT matchId, date FROM matchHistory WHERE game = ? AND ? IN (player1, player2) LIMIT ? OFFSET ?;'); const getMatchHistory = database.prepare('SELECT matchId, date FROM matchHistory WHERE game = ? AND ? IN (player1, player2) LIMIT ? OFFSET ?;');
const getNumberUsers = database.prepare('SELECT COUNT (DISTINCT username) AS n_users FROM userData;'); const getNumberUsers = database.prepare('SELECT COUNT (DISTINCT username) AS n_users FROM userData;');
const getNumberFriends = database.prepare('SELECT COUNT (DISTINCT friendName) AS n_friends FROM friends WHERE username = ?;'); const getNumberFriends = database.prepare('SELECT COUNT (DISTINCT friendName) AS n_friends FROM friends WHERE username = ?;');
const getNumberMatches = database.prepare('SELECT COUNT (DISTINCT id) AS n_matches FROM matchHistory WHERE game = ? AND ? IN (player1, player2);') const getNumberMatches = database.prepare('SELECT COUNT (DISTINCT id) AS n_matches FROM matchHistory WHERE game = ? AND ? IN (player1, player2);');
const getAvatarId = database.prepare('SELECT avatarId FROM userData WHERE username = ?;');
// DELETE // DELETE
const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;'); const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;');
@ -101,6 +108,7 @@ const deleteFriends = database.prepare('DELETE FROM friends WHERE username = ?;'
const deleteMatchHistory = database.prepare('DELETE FROM matchHistory WHERE game = ? AND ? IN (player1, player2);'); const deleteMatchHistory = database.prepare('DELETE FROM matchHistory WHERE game = ? AND ? IN (player1, player2);');
const deleteStatsPong = database.prepare('UPDATE userData SET pongWins = 0, pongLosses = 0 WHERE username = ?;'); const deleteStatsPong = database.prepare('UPDATE userData SET pongWins = 0, pongLosses = 0 WHERE username = ?;');
const deleteStatsTetris = database.prepare('UPDATE userData SET tetrisWins = 0, tetrisLosses = 0 WHERE username = ?;'); const deleteStatsTetris = database.prepare('UPDATE userData SET tetrisWins = 0, tetrisLosses = 0 WHERE username = ?;');
const deleteAvatarId = database.prepare('UPDATE userData SET avatarId = -1 WHERE username = ?;');
const querySchema = { type: 'object', required: ['iStart', 'iEnd'], properties: { iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } } const querySchema = { type: 'object', required: ['iStart', 'iEnd'], properties: { iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } }
const bodySchema = { type: 'object', required: ['opponent', 'myScore', 'opponentScore'], properties: { opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } } const bodySchema = { type: 'object', required: ['opponent', 'myScore', 'opponentScore'], properties: { opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } }
@ -159,6 +167,9 @@ export default async function(fastify, options) {
fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => { fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches); return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches);
}); });
fastify.get('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gAvatar(request, reply, fastify, getAvatarId);
});
// POST // POST
fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => { fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
@ -170,10 +181,13 @@ export default async function(fastify, options) {
fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMatchHistory } }, async (request, reply) => { fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMatchHistory } }, async (request, reply) => {
return pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris); return pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris);
}); });
fastify.post('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return pAvatar(request, reply, fastify, setAvatarId);
});
// PATCH // PATCH
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => { fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return uMember(request, reply, fastify, getUserInfo, changeDisplayName); return uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId);
}); });
// DELETE // DELETE
@ -192,4 +206,7 @@ export default async function(fastify, options) {
fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => { fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris); return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris);
}); });
fastify.delete('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return dAvatar(request, reply, fastify, deleteAvatarId);
});
} }

9
src/api/user/gAvatar.js Normal file
View File

@ -0,0 +1,9 @@
export async function gAvatar(request, reply, fastify, getAvatarId) {
try {
;
return reply.code(200).send({ });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

13
src/api/user/pAvatar.js Normal file
View File

@ -0,0 +1,13 @@
export async function pAvatar(request, reply, fastify, setAvatarId) {
try {
/* const res = await fetch('http://localhost:3004/images', { method: "POST", headers: { } });
if (!res.ok) {
return reply.code(500).send({ error: "Internal server error" });
}
const data = await res.json();*/
return reply.code(200).send({ msg: "Avatar uploaded successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -1,4 +1,4 @@
export async function uMember(request, reply, fastify, getUserInfo, changeDisplayName) { export async function uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId) {
try { try {
const userId = request.params.userId; const userId = request.params.userId;
if (!request.user) { if (!request.user) {

View File

@ -1,6 +1,7 @@
import Fastify from 'fastify'; import Fastify from 'fastify';
import authApi from './api/auth/default.js'; import authApi from './api/auth/default.js';
import userApi from './api/user/default.js'; import userApi from './api/user/default.js';
import imagesApi from './api/images/default.js';
import scoreApi from './api/scoreStore/default.js'; import scoreApi from './api/scoreStore/default.js';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@ -68,6 +69,16 @@ async function start() {
servers.push(score); servers.push(score);
} }
if (target === 'images' || target === 'all') {
const images = Fastify({ logger: loggerOption('images') });
images.register(imagesApi);
const port = target === 'all' ? 3004 : 3000;
const host = target === 'all' ? '127.0.0.1' : '0.0.0.0';
await images.listen({ port, host });
console.log(`Images API listening on http://${host}:${port}`);
servers.push(images);
}
// Graceful shutdown on SIGINT // Graceful shutdown on SIGINT
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('SIGINT received, closing servers...'); console.log('SIGINT received, closing servers...');