Merge pull request #55 from KeyZox71/user-management

Adding user management to main
This commit is contained in:
Adam
2025-10-22 20:54:08 +02:00
committed by GitHub
35 changed files with 1707 additions and 174 deletions

View File

@ -55,4 +55,4 @@ set dotenv-load
forge verify-contract --chain-id 43113 --rpc-url=${AVAX_RPC_URL} --watch ${AVAX_CONTRACT_ADDR}
@status:
docker compose -f docker/docker-compose.yml ps
docker compose -f docker/docker-compose.yml ps

120
doc/user/avatar.md Normal file
View File

@ -0,0 +1,120 @@
# Avatar
Available endpoints:
- POST `/users/:userId/avatar`
- GET `/users/:userId/avatar`
- PATCH `/users/:userId/avatar`
- DELETE `/users/:userId/avatar`
Common return:
- 500 with response
```json
{
"error": "Internal server error"
}
```
## POST /users/:userId/avatar
Used to upload an avatar
Input needed :
```json
{
<FormData object containing the file>
}
```
Can return:
- 200 with response
```json
{
"msg": "Avatar uploaded successfully"
}
```
- 400 with response (if the file is too large, or file is missing, or it is missing a file name, or it is missing a mime type)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if the user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## GET /users/:userId/avatar
Used to download an avatar
Input needed :
```json
{
<FormData object containing the file>
}
```
Can return:
- 200 with response
```json
{
"msg": "Avatar uploaded successfully"
}
```
- 404 with response (if the user does not exist, or the user does not have an assigned avatar, or the image does not exist)
```json
{
"error": "<corresponding error>"
}
```
## PATCH /users/:userId/avatar
Used to modify an avatar
Input needed :
```json
{
<FormData object containing the file>
}
```
Can return:
- 200 with response
```json
{
"msg": "Avatar modified successfully"
}
```
- 400 with response (if the file is too large, or file is missing, or it is missing a file name, or it is missing a mime type)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if the user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## DELETE /users/:userId/avatar
Used to delete an avatar
Can return:
- 200 with response
```json
{
"msg": "Avatar deleted successfully"
}
```
- 404 with response (if the user does not exist, or the user does not have an assigned avatar)
```json
{
"error": "<corresponding error>"
}
```

155
doc/user/friend.md Normal file
View File

@ -0,0 +1,155 @@
# Friend
Available endpoints:
- POST `/users/:userId/friends`
- GET `/users/:userId/friends`
- GET `/users/:userId/friends/count`
- DELETE `/users/:userId/friends`
- DELETE `/users/:userId/friends/:friendId`
Common return:
- 500 with response
```json
{
"error": "Internal server error"
}
```
## POST `/users/:userId/friends/:friendId`
Used to add a friend to an user
Can return:
- 200 with response
```json
{
"msg": "Friend added successfully"
}
```
- 400 with response (if no user is specified in header, or friend is the user specified in header, or friend is already added)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist, or friend does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users/:userId/friends?iStart=<starting index (included)>&iEnd=<ending index (excluded)>`
Used to get the friends of an user
Can return:
- 200 with response (list of friend objects (between iStart and iEnd))
```json
{
"friends":
[
{
"friendName": "<the friend's username>",
"friendDisplayName": "<the friend's display name>"
},
...
]
}
```
- 400 with response (if iStart/iEnd is missing, or iEnd < iStart)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist, or no friends exist in the selected range)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users/:userId/friends/count`
Used to get the number of friends of an user
Can return:
- 200 with response
```json
{
"n_friends": <number of friends>
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## DELETE `/users/:userId/friends`
Used to delete the friends of an user
Can return:
- 200 with response
```json
{
"msg": "Friends deleted successfully"
}
```
- 400 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## DELETE `/users/:userId/friends/:friendId`
Used to delete a friend of an user
Can return:
- 200 with response
```json
{
"msg": "Friend deleted successfully"
}
```
- 400 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist, or friend does not exist)
```json
{
"error": "<corresponding error>"
}
```

148
doc/user/matchHistory.md Normal file
View File

@ -0,0 +1,148 @@
# Match History
Available endpoints:
- POST `/users/:userId/matchHistory`
- GET `/users/:userId/matchHistory`
- GET `/users/:userId/matchHistory/count`
- DELETE `/users/:userId/matchHistory`
Common return:
- 500 with response
```json
{
"error": "Internal server error"
}
```
## POST `/users/:userId/matchHistory?game=<pong/tetris>`
Used to add a match result to an user to a specific game
Input needed :
```json
{
"game": "<pong/tetris>"
"opponent": "<the opponent's username>", <= item only present if the match involved 2 players
"myScore": <my score>,
"opponentScore": <the opponent's score>, <= item only present if the match involved 2 players
"date": <seconds since Epoch (Date.now() return)>
}
```
Can return:
- 200 with response
```json
{
"msg": "Match successfully saved to the blockchain"
}
```
- 400 with response (if no user is specified in header, or no opponent/p1Score/p2Score is specified in body, or opponent is the user specified in header, or a score is negative, or the game specified is invalid, or the game should involve more players than was specified)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist, or opponent does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users/:userId/matchHistory?game=<pong/tetris>&iStart=<starting index (included)>&iEnd=<ending index (excluded)>`
Used to get the match history of an user for a specific game
Can return:
- 200 with response (list of matches results (between iStart and iEnd))
```json
{
"matchHistory":
[
{
"score":
{
"p1": "<the name of the p1>",
"p2": "<the name of the p2>", <= item only present if the match involved 2 players
"p1Score": "<the score of the p1>",
"p2Score": "<the score of the p2>", <= item only present if the match involved 2 players
"date": <seconds since Epoch (Date.now() return)>
},
"tx": "<the transcaction hash>"
},
...
]
}
```
- 400 with response (if iStart/iEnd does not exist, or iEnd < iStart, or the game specified is invalid)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist, or no matches exist in the selected range)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users/:userId/matchHistory/count?game=<pong/tetris>`
Used to get the number of matches an user played for a specific game
Can return:
- 200 with response
```json
{
"n_matches": <number of matches played by the user>
}
```
- 400 with response (if game does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## DELETE `/users/:userId/matchHistory?game=<pong/tetris>`
Used to delete the match history of an user for a specific game
Can return:
- 200 with response
```json
{
"msg": "Match history deleted successfully"
}
```
- 400 with response (if user specified in header is neither admin nor user, or the game specified is invalid)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```

41
doc/user/ping.md Normal file
View File

@ -0,0 +1,41 @@
# ping
Available endpoints:
- POST `/ping`
- GET `/ping/:userId`
Common return:
- 500 with response
```json
{
"error": "Internal server error"
}
```
## POST `/ping`
Used to send a ping and update the lastSeenTime (can be used for activity time)
Input needed : just need a valid token
Can return:
- 200
```json
{
"msg": "last seen time updated successfully"
}
```
## GET `/ping/:userId`
Used to retrive the lastSeenTime of a user
Input needed : just need a valid token
Can return:
- 200
```json
{
"isLogged": "<true/false>"
}
```

212
doc/user/user.md Normal file
View File

@ -0,0 +1,212 @@
# User
Available endpoints:
- POST `/users/:userId`
- GET `/users`
- GET `/users/count`
- GET `/users/:userId`
- PATCH `/users/:userId/:member`
- DELETE `/users/:userId`
- DELETE `/users/:userId/:member`
Common return:
- 500 with response
```json
{
"error": "Internal server error"
}
```
## POST `/users/:userId`
Used to create an user
Input needed :
```json
{
"displayName": "<the display name>"
}
```
Can return:
- 200 with response
```json
{
"msg": "User created successfully"
}
```
- 400 with response (if no user is specified in header, or user already exists, or no display name is specified in body)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is not admin)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users?iStart=<starting index (included)>&iEnd=<ending index (excluded)>`
Used to get the list of users
Can return:
- 200 with response (list of user objects (between iStart and iEnd))
```json
{
"users":
[
{
"username": "<the username>",
"displayName": "<the display name>",
"pong": {
"wins": <the number of pong matches won>,
"losses": <the number of pong matches lost>
},
"tetris": {
"wins": <the number of tetris matches won>,
"losses": <the number of tetris matches lost>
}
},
...
]
}
```
- 400 with response (if iStart/iEnd is missing, or iEnd < iStart)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if no users exist in the selected range)
```json
{
"error": "<corresponding error>"
}
```
## GET `/users/count`
Used to get the number of users
Always returns:
- 200 with response
```json
{
"n_users": <number of users>
}
```
## GET `/users/:userId`
Used to get an user
Can return:
- 200 with response (an user object)
```json
{
"username": "<the username>",
"displayName": "<the display name>",
"pong": {
"wins": <the number of pong matches won>,
"losses": <the number of pong matches lost>
},
"tetris": {
"wins": <the number of tetris matches won>,
"losses": <the number of tetris matches lost>
}
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## PATCH `/users/:userId/:member`
Used to modify a member of an user (only displayName can be modified)
Input needed :
```json
{
"<the member to modify (must be identical to :member)>": "<the member's new value>"
}
```
Can return:
- 200 with response
```json
{
"msg": "<:member> modified sucessfully"
}
```
- 400 with response (if no user is specified in header, or new value of member to modify is not provided in the body, or member does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 401 with response (if user specified in header is not admin)
```json
{
"error": "<corresponding error>"
}
```
## DELETE `/users/:userId`
Used to delete an user
Can return:
- 200 with response
```json
{
"msg": "User deleted successfully"
}
```
- 404 with response (user does not exist)
```json
{
"error": "<corresponding error>"
}
```
## DELETE `/users/:userId/:member`
Used to delete a member of an user (only displayName can be deleted)
Can return:
- 200 with response
```json
{
"msg": "<:member> deleted successfully"
}
```
- 401 with response (if user specified in header is neither admin nor user)
```json
{
"error": "<corresponding error>"
}
```
- 400 with response (if no user is specified in header, or member to delete does not exist)
```json
{
"error": "<corresponding error>"
}
```
- 404 with response (if user does not exist)
```json
{
"error": "<corresponding error>"
}
```

View File

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

288
pnpm-lock.yaml generated
View File

@ -20,6 +20,9 @@ importers:
'@fastify/jwt':
specifier: ^9.1.0
version: 9.1.0
'@fastify/multipart':
specifier: ^9.2.1
version: 9.2.1
axios:
specifier: ^1.10.0
version: 1.10.0
@ -47,6 +50,9 @@ importers:
prom-client:
specifier: ^15.1.3
version: 15.1.3
sharp:
specifier: ^0.34.4
version: 0.34.4
solhint:
specifier: ^6.0.0
version: 6.0.0(typescript@5.8.3)
@ -88,6 +94,9 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
'@emnapi/runtime@1.5.0':
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
'@esbuild/aix-ppc64@0.25.6':
resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==}
engines: {node: '>=18'}
@ -252,12 +261,18 @@ packages:
'@fastify/ajv-compiler@4.0.2':
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':
resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==}
'@fastify/deepmerge@2.0.2':
resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==}
'@fastify/deepmerge@3.1.0':
resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==}
'@fastify/env@5.0.2':
resolution: {integrity: sha512-4m/jHS3s/G/DBJVODob9sxGUei/Ij8JFbA2PYqBfoihTm+Qqae2xD9xhez68UFZu1d4SNJPIb6uAOwbNvRYw+A==}
@ -276,6 +291,9 @@ packages:
'@fastify/merge-json-schemas@0.2.1':
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
'@fastify/multipart@9.2.1':
resolution: {integrity: sha512-U4221XDMfzCUtfzsyV1/PkR4MNgKI0158vUUyn/oF2Tl6RxMc+N7XYLr5fZXQiEC+Fmw5zFaTjxsTGTgtDtK+g==}
'@fastify/proxy-addr@5.0.0':
resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==}
@ -283,6 +301,132 @@ packages:
resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==}
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':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
@ -753,6 +897,10 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
dotenv-expand@10.0.0:
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
engines: {node: '>=12'}
@ -1452,6 +1600,10 @@ packages:
set-cookie-parser@2.7.1:
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:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
@ -1667,6 +1819,11 @@ snapshots:
'@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':
optional: true
@ -1753,6 +1910,8 @@ snapshots:
ajv-formats: 3.0.1(ajv@8.17.1)
fast-uri: 3.0.6
'@fastify/busboy@3.2.0': {}
'@fastify/cookie@11.0.2':
dependencies:
cookie: 1.0.2
@ -1760,6 +1919,8 @@ snapshots:
'@fastify/deepmerge@2.0.2': {}
'@fastify/deepmerge@3.1.0': {}
'@fastify/env@5.0.2':
dependencies:
env-schema: 6.0.1
@ -1785,6 +1946,14 @@ snapshots:
dependencies:
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':
dependencies:
'@fastify/forwarded': 3.0.0
@ -1792,6 +1961,94 @@ snapshots:
'@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':
dependencies:
minipass: 7.1.2
@ -2183,6 +2440,8 @@ snapshots:
detect-libc@2.0.4: {}
detect-libc@2.1.2: {}
dotenv-expand@10.0.0: {}
dotenv@16.6.1: {}
@ -2914,6 +3173,35 @@ snapshots:
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-get@4.0.1:

View File

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

View File

@ -9,12 +9,11 @@ import { callAddScore, callLastId } from "../../utils/scoreStore_contract.js";
*/
export async function addTx(request, reply, fastify) {
try {
const id = await callLastId();
const tx = callAddScore(request.body.p1, request.body.p2, request.body.p1Score, request.body.p2Score);
const {tx, id} = await callAddScore(request.body.p1, request.body.p2, request.body.p1Score, request.body.p2Score);
tx.then(tx => {
scoreDB.addTx(id, tx.hash);
});
scoreDB.addTx(id, tx.hash);
// tx.then(tx => {
// });
return reply.code(200).send({
id: Number(id)

View File

@ -20,7 +20,7 @@ export default async function(fastify, options) {
required: ['p1', 'p2', 'p1Score', 'p2Score'],
properties: {
p1: { type: 'string', minLength: 1 },
p2: { type: 'string', minLength: 1 },
p2: { type: 'string', minLength: 0 },
p1Score: { type: 'integer', minimum: 0 },
p2Score: { type: 'integer', minimum: 0 },
}

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

@ -0,0 +1,18 @@
export async function dAvatar(request, reply, fastify, getUserInfo, getAvatarId, deleteAvatarId, deleteImage) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.cose(404).send({ error: "User does not exist" });
}
const imageId = getAvatarId.get(userId);
if (imageId.avatarId === -1) {
return reply.code(404).send({ error: "User does not have an avatar" });
}
deleteImage.run(imageId.avatarId);
deleteAvatarId.run(userId);
return reply.code(200).send({ msg: "Avatar deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

23
src/api/user/dFriend.js Normal file
View File

@ -0,0 +1,23 @@
export async function dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend) {
try {
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
const friendId = request.params.friendId;
if (!getFriend.get(userId, friendId)) {
return reply.code(404).send({ error: "Friend does not exist" });
}
deleteFriend.run(userId, friendId);
return reply.code(200).send({ msg: "Friend deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

19
src/api/user/dFriends.js Normal file
View File

@ -0,0 +1,19 @@
export async function dFriends(request, reply, fastify, getUserInfo, deleteFriends) {
try {
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
deleteFriends.run(userId);
return reply.code(200).send({ msg: "Friends deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,29 @@
export async function dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris) {
try {
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { game } = request.query;
if (game !== 'pong' && game !== 'tetris') {
return reply.code(400).send({ error: "Specified game does not exist" });
}
deleteMatchHistory.run(game, userId);
if (game === 'pong') {
deleteStatsPong.run(userId);
}
else if (game === 'tetris') {
deleteStatsTetris.run(userId);
}
return reply.code(200).send({ msg: "Match history deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

25
src/api/user/dMember.js Normal file
View File

@ -0,0 +1,25 @@
export async function dMember(request, reply, fastify, getUserInfo, changeDisplayName) {
try {
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const user = request.user;
const member = request.params.member;
if (user === 'admin' || user === request.params.userId) {
if (member === 'displayName') {
changeDisplayName.run("", request.params.userId);
return reply.code(200).send({ msg: "Display name deleted successfully" });
}
return reply.code(400).send({ msg: "Member does not exist" })
} else {
return reply.code(401).send({ error: 'You dont have the right to delete this' });
}
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

15
src/api/user/dUser.js Normal file
View File

@ -0,0 +1,15 @@
export async function dUser(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteFriends, deleteUser) {
try {
if (!getUserInfo.get(request.params.userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
deleteMatchHistory.run('pong', request.params.userId);
deleteMatchHistory.run('tetris', request.params.userId);
deleteFriends.run(request.params.userId);
deleteUser.run(request.params.userId);
return reply.code(200).send({ msg: "User deleted successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -1,60 +1,148 @@
import fastifyJWT from '@fastify/jwt';
import fastifyCookie from '@fastify/cookie';
import Database from 'better-sqlite3';
import multipart from '@fastify/multipart';
var env = process.env.NODE_ENV || 'development';
import { gUsers } from './gUsers.js';
import { gUser } from './gUser.js';
import { gNumberUsers } from './gNumberUsers.js';
import { gFriends } from './gFriends.js';
import { gNumberFriends } from './gNumberFriends.js';
import { gMatchHistory } from './gMatchHistory.js';
import { gNumberMatches } from './gNumberMatches.js';
import { pUser } from './pUser.js';
import { pFriend } from './pFriend.js';
import { pMatchHistory } from './pMatchHistory.js';
import { uMember } from './uMember.js';
import { dUser } from './dUser.js';
import { dMember } from './dMember.js';
import { dFriends } from './dFriends.js';
import { dFriend } from './dFriend.js';
import { dMatchHistory } from './dMatchHistory.js';
import { pAvatar } from './pAvatar.js';
import { gAvatar } from './gAvatar.js';
import { uAvatar } from './uAvatar.js';
import { dAvatar } from './dAvatar.js';
import { pPing } from './pPing.js';
import { gPing } from './gPing.js';
const env = process.env.NODE_ENV || 'development';
let database;
if (!env || env === 'development') {
database = new Database(":memory:", { verbose: console.log });
database = new Database(':memory:', { verbose: console.log });
} else {
var dbPath = process.env.DB_PATH || '/db/db.sqlite'
const dbPath = process.env.DB_PATH || '/db/db.sqlite'
database = new Database(dbPath);
}
function prepareDB() {
database.exec(`
CREATE TABLE IF NOT EXISTS userData (
username TEXT PRIMARY KEY,
displayName TEXT
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
displayName TEXT,
avatarId INTEGER,
pongWins INTEGER,
pongLosses INTEGER,
tetrisWins INTEGER,
tetrisLosses INTEGER,
UNIQUE(username),
CHECK(pongWins >= 0),
CHECK(pongLosses >= 0),
CHECK(tetrisWins >= 0),
CHECK(tetrisLosses >= 0)
) STRICT
`);
database.exec(`
CREATE TABLE IF NOT EXISTS friends (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
friendName TEXT,
UNIQUE(username, friendName),
CHECK(username != friendName)
)
) STRICT
`);
database.exec(`
CREATE TABLE IF NOT EXISTS matchHistory (
id INTEGER PRIMARY KEY AUTOINCREMENT,
game TEXT,
date INTEGER,
player1 TEXT,
player2 TEXT,
matchId INTEGER,
CHECK(game = 'pong' OR game = 'tetris'),
CHECK(date >= 0),
CHECK(player1 != player2)
) STRICT
`);
database.exec(`
CREATE TABLE IF NOT EXISTS activityTime (
username TEXT PRIMARY KEY,
time TEXT
) STRICT
`);
database.exec(`
CREATE TABLE IF NOT EXISTS images (
imageId INTEGER PRIMARY KEY AUTOINCREMENT,
fileName TEXT,
mimeType TEXT,
data BLOB
) STRICT
`);
}
prepareDB();
// POST
const createUser = database.prepare('INSERT INTO userData (username, displayName) VALUES (?, ?);');
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 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 incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLosses + 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 setAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
const postImage = database.prepare('INSERT INTO images (fileName, mimeType, data) VALUES (?, ?, ?);');
const setActivityTime = database.prepare(`
INSERT INTO activityTime (username, time)
VALUES (?, ?)
ON CONFLICT(username) DO UPDATE SET time = excluded.time;
`);
// PATCH
const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;');
const changeAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
// GET
const getUserInfo = database.prepare('SELECT * FROM userData WHERE username = ?;');
const getUserData = database.prepare('SELECT * FROM userData;');
const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ?;');
// const isFriend = database.prepare('SELECT 1 FROM friends WHERE username = ? AND friendName = ?;');
const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;');
const getUserInfo = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData WHERE username = ?;');
const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ? LIMIT ? OFFSET ?;');
const getFriend = database.prepare('SELECT friendName FROM friends WHERE username = ? AND friendName = ?;');
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 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 getAvatarId = database.prepare('SELECT avatarId FROM userData WHERE username = ?;');
const getImage = database.prepare('SELECT fileName, mimeType, data FROM images WHERE imageId = ?;');
const getActivityTime = database.prepare('SELECT time FROM activityTime WHERE username = ?;')
// DELETE
const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;');
const deleteFriend = database.prepare('DELETE FROM friends WHERE username = ? AND friendName = ?;');
const deleteFriends = database.prepare('DELETE FROM friends WHERE username = ?;');
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 deleteStatsTetris = database.prepare('UPDATE userData SET tetrisWins = 0, tetrisLosses = 0 WHERE username = ?;');
const deleteAvatarId = database.prepare('UPDATE userData SET avatarId = -1 WHERE username = ?;');
const deleteImage = database.prepare('DELETE FROM images WHERE imageId = ?;');
const querySchema = { type: 'object', required: ['iStart', 'iEnd'], properties: { iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } };
const bodySchemaMember = { type: 'object', properties: { displayName: { type: 'string' } } };
const querySchemaMatchHistory = { type: 'object', required: ['game', 'iStart', 'iEnd'], properties: { game: { type: 'string' }, iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } };
const bodySchemaMatchHistory = { type: 'object', required: ['game', 'date', 'myScore'], properties: { game: { type: 'string' }, date: { type: 'integer', minimum: 0 }, opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } };
const querySchemaMatchHistoryGame = { type: 'object', required: ['game'], properties: { game: { type: 'string' } } };
/**
* @param {import('fastify').FastifyInstance} fastify
* @param {import('fastify').FastifyPluginOptions} options
*/
export default async function(fastify, options) {
fastify.register(fastifyJWT, {
secret: process.env.JWT_SECRET || '123456789101112131415161718192021',
@ -63,8 +151,9 @@ export default async function(fastify, options) {
},
});
fastify.register(fastifyCookie);
fastify.register(multipart, { limits: { fileSize: 2 * 1024 * 1024 + 1 } });
fastify.decorate("authenticate", async function(request, reply) {
fastify.decorate('authenticate', async function(request, reply) {
try {
const jwt = await request.jwtVerify();
request.user = jwt.user;
@ -73,11 +162,11 @@ export default async function(fastify, options) {
}
});
fastify.decorate("authenticateAdmin", async function(request, reply) {
fastify.decorate('authenticateAdmin', async function(request, reply) {
try {
const jwt = await request.jwtVerify();
if (jwt.user !== 'admin') {
throw ("");
throw ('You lack administrator privileges');
}
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' });
@ -85,166 +174,76 @@ export default async function(fastify, options) {
});
// GET
fastify.get('/users', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const users = getUserData.all();
return reply.code(200).send({ users });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
fastify.get('/users', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
return gUsers(request, reply, fastify, getUserData);
});
fastify.get('/users/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gNumberUsers(request, reply, fastify, getNumberUsers);
});
fastify.get('/users/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const info = getUserInfo.get(request.params.userId);
return reply.code(200).send({ info });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
return gUser(request, reply, fastify, getUserInfo);
});
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (userId == request.user || request.user == 'admin') {
const friends = getFriends.all(userId);
if (!friends) {
return reply.code(404).send({ error: "User does not have friends D:" });
}
return reply.code(200).send({ friends });
}
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
return gFriends(request, reply, fastify, getUserInfo, getFriends);
});
fastify.get('/users/:userId/friends/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends);
});
fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { querystring: querySchemaMatchHistory } }, async (request, reply) => {
return gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory);
});
fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches);
});
fastify.get('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gAvatar(request, reply, fastify, getUserInfo, getAvatarId, getImage);
});
fastify.get('/ping/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return gPing(request, reply, fastify, getActivityTime);
});
// POST
fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
try {
const userId = request.params.userId;
if (getUserInfo.get(userId)) {
return reply.code(400).send({ error: "User already exist" });
}
createUser.run(userId, userId);
return reply.code(200).send({ msg: "User created sucessfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
})
fastify.post('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.params.userId;
if (request.user != 'admin' && request.user != userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
if (!request.body || !request.body.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (!getUserInfo.get(request.body.user)) {
return reply.code(404).send({ error: "Friend does not exist" });
}
if (request.body.user === userId) {
return reply.code(400).send({ error: "You can't add yourself :D" });
}
addFriend.run(userId, request.body.user)
return reply.code(200).send({ msg: "Friend added sucessfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
return pUser(request, reply, fastify, getUserInfo, createUser);
});
fastify.post('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return pFriend(request, reply, fastify, getUserInfo, getFriend, addFriend);
});
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);
});
fastify.post('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return pAvatar(request, reply, fastify, getUserInfo, setAvatarId, postImage);
});
fastify.post('/ping', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return pPing(request, reply, fastify, setActivityTime);
})
// PATCH
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.params.userId;
if (request.user != 'admin' && request.user != userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const member = request.params.member;
if (member === 'displayName') {
if (!request.body || !request.body.displayName) {
return reply.code(400).send({ error: "Please specify a displayName" });
}
changeDisplayName.run(request.body.displayName, userId);
return reply.code(200).send({ msg: "displayName modified sucessfully" });
}
return reply.code(400).send({ error: "Member does not exist" })
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
})
fastify.patch('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return uAvatar(request, reply, fastify, getUserInfo, setAvatarId, getAvatarId, deleteAvatarId, postImage, deleteImage);
});
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMember } }, async (request, reply) => {
return uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId);
});
// DELETE
/**
* @description Can be used to delete a user from the db
*/
fastify.delete('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
try {
if (!getUserInfo(request.params.userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
deleteUser.run(request.params.userId);
deleteFriends.run(request.params.userId);
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
return dUser(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteFriends, deleteUser);
});
fastify.delete('/users/:userId/:member', { preHandler: fastify.authenticate }, async (request, reply) => {
try {
const user = request.user;
const member = request.params.member;
if (user == 'admin' || user == request.params.userId) {
if (member == 'displayName') {
changeDisplayName.run("", request.params.userId);
return reply.code(200).send({ msg: "displayName cleared sucessfully" });
}
return reply.code(400).send({ msg: "member does not exist" })
} else {
return reply.code(401).send({ error: 'You dont have the right to delete this' });
}
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
return dMember(request, reply, fastify, getUserInfo, changeDisplayName);
});
fastify.delete('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
return dFriends(request, reply, fastify, getUserInfo, deleteFriends);
});
fastify.delete('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try {
const userId = request.params.userId;
const friendId = request.params.friendId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (request.user != 'admin' && request.user != userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
deleteFriend.run(userId, friendId);
return reply.code(200).send({ msg: "Friend remove sucessfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
return dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend);
});
fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
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, getUserInfo, getAvatarId, deleteAvatarId, deleteImage);
});
}

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

@ -0,0 +1,20 @@
export async function gAvatar(request, reply, fastify, getUserInfo, getAvatarId, getImage) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const imageId = getAvatarId.get(userId);
if (imageId.avatarId === -1) {
return reply.code(404).send({ error: "User does not have an avatar" });
}
const image = getImage.get(imageId.avatarId);
if (!image) {
return reply.code(404).send({ error: "Avatar does not exist" });
}
return reply.code(200).type(image.mimeType).send(image.data);
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

26
src/api/user/gFriends.js Normal file
View File

@ -0,0 +1,26 @@
export async function gFriends(request, reply, fastify, getUserInfo, getFriends) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const { iStart, iEnd } = request.query;
if (Number(iEnd) < Number(iStart)) {
return reply.code(400).send({ error: "Starting index cannot be strictly inferior to ending index" });
}
const friendNames = getFriends.all(userId, Number(iEnd) - Number(iStart), Number(iStart));
if (!friendNames.length) {
return reply.code(404).send({ error: "No friends exist in the selected range" });
}
const promises = friendNames.map(async (friendName) => {
const friend = getUserInfo.get(friendName.friendName);
friendName.friendDisplayName = friend.displayName;
return friendName;
});
const friends = await Promise.all(promises);
return reply.code(200).send({ friends });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,37 @@
export async function gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const { game, iStart, iEnd } = request.query;
if (game !== 'pong' && game !== 'tetris') {
return reply.code(400).send({ error: "Specified game does not exist" });
}
if (Number(iEnd) < Number(iStart)) {
return reply.code(400).send({ error: "Starting index cannot be strictly inferior to ending index" });
}
const matchHistoryId = getMatchHistory.all(game, userId, Number(iEnd) - Number(iStart), Number(iStart));
if (!matchHistoryId.length) {
return reply.code(404).send({ error: "No matches exist in the selected range" });
}
const promises = matchHistoryId.map(async (match) => {
const res = await fetch(`http://localhost:3003/${match.matchId}`, { method: "GET" });
if (!res.ok) {
throw new Error('Failed to fetch item from blockchain API');
}
const resJson = await res.json();
resJson.score.date = match.date;
if (resJson.score.p2 === "" && resJson.score.p2Score === 0) {
delete resJson.score.p2;
delete resJson.score.p2Score;
}
return resJson;
});
const matchHistory = await Promise.all(promises);
return reply.code(200).send({ matchHistory });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,13 @@
export async function gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const row = getNumberFriends.get(userId);
return reply.code(200).send({ n_friends: row.n_friends });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,17 @@
export async function gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const { game } = request.query;
if (game !== 'pong' && game !== 'tetris') {
return reply.code(400).send({ error: "Specified game does not exist" });
}
const row = getNumberMatches.get(game, userId);
return reply.code(200).send({ n_matches: row.n_matches });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,9 @@
export async function gNumberUsers(request, reply, fastify, getNumberUsers) {
try {
const row = getNumberUsers.get();
return reply.code(200).send({ n_users: row.n_users });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

28
src/api/user/gPing.js Normal file
View File

@ -0,0 +1,28 @@
/**
* @param {import('fastify').FastifyRequest} request
* @param {import('fastify').FastifyReply} reply
* @param {import('fastify').FastifyInstance} fastify
*/
export async function gPing(request, reply, fastify, getActivityTime) {
try {
const user = request.params.userId;
const time = getActivityTime.get(user);
if (!time || !time.time) {
return reply.code(404).send({ error: "User not found or no activity time recorded" });
}
const lastSeenTime = new Date(time.time);
const now = new Date();
const oneMinuteAgo = new Date(now.getTime() - 60000); // 60,000 ms = 1 minute
const isActiveInLastMinute = lastSeenTime >= oneMinuteAgo;
return reply.code(200).send({
isLogged: isActiveInLastMinute
});
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

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

@ -0,0 +1,13 @@
export async function gUser(request, reply, fastify, getUserInfo) {
try {
const userId = request.params.userId;
const userInfo = getUserInfo.get(userId);
if (!userInfo) {
return reply.code(404).send({ error: "User does not exist" });
}
return reply.code(200).send({ username: userInfo.username, displayName: userInfo.displayName, pong: { wins: userInfo.pongWins, losses: userInfo.pongLosses }, tetris: { wins: userInfo.tetrisWins, losses: userInfo.tetrisLosses } });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

28
src/api/user/gUsers.js Normal file
View File

@ -0,0 +1,28 @@
export async function gUsers(request, reply, fastify, getUserData) {
try {
const { iStart, iEnd } = request.query;
if (Number(iEnd) < Number(iStart)) {
return reply.code(400).send({ error: "Starting index cannot be strictly inferior to ending index" });
}
const users = getUserData.all(Number(iEnd) - Number(iStart), Number(iStart));
if (!users.length) {
return reply.code(404).send({ error: "No users exist in the selected range" });
}
const usersFormat = users.map(obj => ({
username: obj.username,
displayName: obj.displayName,
pong: {
wins: obj.pongWins,
losses: obj.pongLosses
},
tetris: {
wins: obj.tetrisWins,
losses: obj.tetrisLosses
}
}));
return reply.code(200).send({ usersFormat });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

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

@ -0,0 +1,39 @@
import sharp from 'sharp';
export async function pAvatar(request, reply, fastify, getUserInfo, setAvatarId, postImage) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.cose(404).send({ error: "User does not exist" });
}
const parts = request.parts();
for await (const part of parts) {
if (part.file) {
let size = 0;
const chunks = [];
for await (const chunk of part.file) {
size += chunk.length;
chunks.push(chunk);
}
if (size === 5 * 1024 * 1024 + 1) {
return reply.code(400).send({ error: "File too large" });
}
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 webpBuffer = await sharp(buffer).toFormat('webp').toBuffer();
const imageId = postImage.run(part.filename, part.mimetype, webpBuffer);
setAvatarId.run(imageId.lastInsertRowid, userId);
return reply.code(200).send({ msg: "Avatar uploaded successfully" });
}
}
return reply.code(400).send({ error: "No avatar uploaded" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

29
src/api/user/pFriend.js Normal file
View File

@ -0,0 +1,29 @@
export async function pFriend(request, reply, fastify, getUserInfo, getFriend, addFriend) {
try {
const userId = request.params.userId;
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const friendId = request.params.friendId;
if (!getUserInfo.get(friendId)) {
return reply.code(404).send({ error: "Friend does not exist" });
}
if (friendId === userId) {
return reply.code(400).send({ error: "You can't add yourself :D" });
}
if (getFriend.get(userId, friendId)) {
return reply.code(400).send({ error: "Friend already added" });
}
addFriend.run(userId, friendId)
return reply.code(200).send({ msg: "Friend added successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -0,0 +1,66 @@
async function fetchSave(request, reply, userId, addMatch) {
let opponentName = '';
let opponentScore = 0;
if (request.body.opponent && request.body.opponentScore) {
opponentName = request.body.opponent;
opponentScore = request.body.opponentScore;
}
const res = await fetch('http://localhost:3003/', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ p1: userId, p2: opponentName, p1Score: request.body.myScore, p2Score: opponentScore }) });
if (!res.ok) {
throw new Error('Internal server error');
}
const data = await res.json();
addMatch.run(request.body.game, request.body.date, userId, opponentName, data.id);
}
export async function pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris) {
try {
const userId = request.params.userId;
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
if (request.body.game !== 'pong' && request.body.game !== 'tetris') {
return reply.code(400).send({ error: "Specified game does not exist" });
}
if (request.body.game === 'pong' && (!request.body.opponent || !request.body.opponentScore)) {
return reply.code(400).send({ error: "Game requires two players" });
}
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
if (request.body.opponent) {
if (!getUserInfo.get(request.body.opponent)) {
return reply.code(404).send({ error: "Opponent does not exist" });
}
if (request.body.opponent === userId) {
return reply.code(400).send({ error: "Do you have dementia ? You cannot have played a match against yourself gramps" });
}
}
await fetchSave(request, reply, userId, addMatch);
if (request.body.game === 'pong') {
if (request.body.myScore > request.body.opponentScore) {
incWinsPong.run(userId);
incLossesPong.run(request.body.opponent);
} else if (request.body.myScore < request.body.opponentScore) {
incWinsPong.run(request.body.opponent);
incLossesPong.run(userId);
}
}
else if (request.body.game === 'tetris' && request.body.opponent && request.body.opponentScore) {
if (request.body.myScore > request.body.opponentScore) {
incWinsTetris.run(userId);
incLossesTetris.run(request.body.opponent);
} else if (request.body.myScore < request.body.opponentScore) {
incWinsTetris.run(request.body.opponent);
incLossesTetris.run(userId);
}
}
return reply.code(200).send({ msg: "Match successfully saved to the blockchain" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

21
src/api/user/pPing.js Normal file
View File

@ -0,0 +1,21 @@
/**
* @param {import('fastify').FastifyRequest} request
* @param {import('fastify').FastifyReply} request
* @param {import('fastify').Fastify} fastify
*/
export async function pPing(request, reply, fastify, setActivityTime) {
try {
const user = request.user;
const currentTime = new Date().toISOString();
setActivityTime.run(user, currentTime);
return reply.code(200)
.send({
msg: "last seen time updated successfully"
});
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

22
src/api/user/pUser.js Normal file
View File

@ -0,0 +1,22 @@
export async function pUser(request, reply, fastify, getUserInfo, createUser) {
try {
const userId = request.params.userId;
if (!request.user || !request.user.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
if (request.user.user !== 'admin') {
return reply.code(401).send({ error: "Unauthorized" });
}
if (getUserInfo.get(userId)) {
return reply.code(400).send({ error: "User already exist" });
}
if (!request.body || !request.body.displayName) {
return reply.code(400).send({ error: "Please specify a display name" });
}
createUser.run(userId, request.body.displayName);
return reply.code(200).send({ msg: "User created successfully" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

45
src/api/user/uAvatar.js Normal file
View File

@ -0,0 +1,45 @@
import sharp from 'sharp';
export async function uAvatar(request, reply, fastify, getUserInfo, setAvatarId, getAvatarId, deleteAvatarId, postImage, deleteImage) {
try {
const userId = request.params.userId;
if (!getUserInfo.get(userId)) {
return reply.cose(404).send({ error: "User does not exist" });
}
deleteAvatarId.run(userId);
const parts = request.parts();
for await (const part of parts) {
if (part.file) {
let size = 0;
const chunks = [];
for await (const chunk of part.file) {
size += chunk.length;
chunks.push(chunk);
}
if (size === 5 * 1024 * 1024 + 1) {
return reply.code(400).send({ error: "File too large" });
}
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 webpBuffer = await sharp(buffer).toFormat('webp').toBuffer();
const imageId = postImage.run(part.filename, part.mimetype, webpBuffer);
const oldImageId = getAvatarId.get(userId);
if (oldImageId.avatarId !== -1) {
deleteImage.run(oldImageId.avatarId);
deleteAvatarId.run(userId);
}
setAvatarId.run(imageId.lastInsertRowid, userId);
return reply.code(200).send({ msg: "Avatar modified successfully" });
}
}
return reply.code(400).send({ error: "No avatar modified" });
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

26
src/api/user/uMember.js Normal file
View File

@ -0,0 +1,26 @@
export async function uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId) {
try {
const userId = request.params.userId;
if (!request.user) {
return reply.code(400).send({ error: "Please specify a user" });
}
if (request.user !== 'admin' && request.user !== userId) {
return reply.code(401).send({ error: "Unauthorized" });
}
if (!getUserInfo.get(userId)) {
return reply.code(404).send({ error: "User does not exist" });
}
const member = request.params.member;
if (member === 'displayName') {
if (!request.body || !request.body.displayName) {
return reply.code(400).send({ error: "Please specify a displayName" });
}
changeDisplayName.run(request.body.displayName, userId);
return reply.code(200).send({ msg: "Display name modified successfully" });
}
return reply.code(400).send({ error: "Member does not exist" })
} catch (err) {
fastify.log.error(err);
return reply.code(500).send({ error: "Internal server error" });
}
}

View File

@ -29,7 +29,7 @@ async function loadContract() {
async function callGetScore(id) {
try {
const contract = await loadContract();
const result = await contract.getScore(id);
const result = await contract.getScore(id - 1);
return result;
} catch (error) {
console.error('Error calling view function:', error);
@ -54,8 +54,9 @@ async function callAddScore(p1, p2, p1Score, p2Score) {
const tx = await contract.addScore(p1, p2, p1Score, p2Score);
console.log('Transaction sent:', tx.hash);
await tx.wait(); // Wait for the transaction to be mined
const id = await callLastId();
console.log('Transaction confirmed');
return tx;
return { tx, id };
} catch (error) {
console.error('Error calling addScore function:', error);
throw error;