mirror of
https://github.com/KeyZox71/knl_meowscendence.git
synced 2025-12-31 21:56:41 +01:00
Merge branch 'main' into front
This commit is contained in:
39
README.md
39
README.md
@ -11,6 +11,7 @@ Press F to pay respect
|
|||||||
│ └── volumes.yml # Docker volume definitions
|
│ └── volumes.yml # Docker volume definitions
|
||||||
├── src/ # Application source code
|
├── src/ # Application source code
|
||||||
│ ├── api/ # Backend logic (auth, user management)
|
│ ├── api/ # Backend logic (auth, user management)
|
||||||
|
│ ├── contract/ # Smart contract files
|
||||||
│ ├── front/ # Frontend files
|
│ ├── front/ # Frontend files
|
||||||
│ └── utils/ # Utility modules (auth, TOTP, etc.)
|
│ └── utils/ # Utility modules (auth, TOTP, etc.)
|
||||||
├── flake.nix & flake.lock # Nix flake configuration
|
├── flake.nix & flake.lock # Nix flake configuration
|
||||||
@ -18,44 +19,44 @@ Press F to pay respect
|
|||||||
```
|
```
|
||||||
## Modules done
|
## Modules done
|
||||||
|
|
||||||
6 major + 2 minor = 7 full modules
|
8 major + 4 minor = 10 full modules
|
||||||
|
|
||||||
- **Web**
|
- **Web**
|
||||||
- [x] Use a framework to build the backend.(node with Fastify) - Major
|
- [x] Use a framework to build the backend.(node with Fastify) - Major
|
||||||
- [ ] Use a framework or toolkit to build the front-end.(Tailwind CSS) - Minor
|
- [x] Use a framework or toolkit to build the front-end.(Tailwind CSS) - Minor
|
||||||
- [x] Use a database for the backend -and more.(SQLite) - Minor
|
- [x] Use a database for the backend -and more.(SQLite) - Minor
|
||||||
- [x] Store the score of a tournament in the Blockchain.(Soldity on Avalanche) - Major
|
- [x] Store the score of a tournament in the Blockchain.(Soldity on Avalanche) - Major
|
||||||
- **User Management**
|
- **User Management**
|
||||||
- [ ] Standard user management, authentication and users across tournaments. - Major
|
- [x] Standard user management, authentication and users across tournaments. - Major
|
||||||
- [x] Implement remote authentication. - Major
|
- [x] Implement remote authentication. - Major
|
||||||
- **Gameplay and user experience**
|
- **Gameplay and user experience**
|
||||||
- [ ] Remote players - Major
|
- [ ] ~~Remote players - Major~~
|
||||||
- [ ] Multiplayer - Major
|
- [ ] ~~Multiplayer - Major~~
|
||||||
- [ ] Add another game - Major
|
- [x] Add another game - Major
|
||||||
- [ ] Game customization options - Minor
|
- [ ] ~~Game customization options - Minor~~
|
||||||
- [ ] Live chat - Major
|
- [ ] ~~Live chat - Major~~
|
||||||
- **AI-Algo**
|
- **AI-Algo**
|
||||||
- [ ] AI opponent - Major
|
- [ ] ~~AI opponent - Major~~
|
||||||
- [ ] User and game stats dashboards - Minor
|
- [ ] User and game stats dashboards - Minor
|
||||||
- **Cybersecurity**
|
- **Cybersecurity**
|
||||||
- [ ] WAF/ModSecurity and Hashicorp Vault - Major
|
- [ ] ~~WAF/ModSecurity and Hashicorp Vault - Major~~
|
||||||
- [ ] RGPD compliance - Minor
|
- [ ] ~~RGPD compliance - Minor~~
|
||||||
- [x] 2FA and JWT - Major
|
- [x] 2FA and JWT - Major
|
||||||
- **DevOps**
|
- **DevOps**
|
||||||
- [x] Infrasctructure setup for log management - Major
|
- [x] Infrasctructure setup for log management - Major
|
||||||
- [x] Monitoring system - Minor
|
- [x] Monitoring system - Minor
|
||||||
- [x] Designing the backend in micro-architecture - Major
|
- [x] Designing the backend in micro-architecture - Major
|
||||||
- **Graphics**
|
- **Graphics**
|
||||||
- [ ] Use of advanced 3D techniques - Major
|
- [ ] ~~Use of advanced 3D techniques - Major~~
|
||||||
- **Accessibility**
|
- **Accessibility**
|
||||||
- [ ] Support on all devices - Minor
|
- [ ] ~~Support on all devices - Minor~~
|
||||||
- [ ] Expanding Browser compatibility - Minor
|
- [x] Expanding Browser compatibility - Minor
|
||||||
- [ ] Multiple language support - Minor
|
- [ ] ~~Multiple language support - Minor~~
|
||||||
- [ ] Add accessibility for visually impaired users - Minor
|
- [ ] ~~Add accessibility for visually impaired users - Minor~~
|
||||||
- [ ] Server-Side Rendering (SSR) integration - Minor
|
- [ ] ~~Server-Side Rendering (SSR) integration - Minor~~9
|
||||||
- **Server-Side Pong**
|
- **Server-Side Pong**
|
||||||
- [ ] Replace basic pong with server-side pong and implementing an API - Major
|
- [ ] ~~Replace basic pong with server-side pong and implementing an API - Major~~
|
||||||
- [ ] Enabling pong gameplay via CLI against web users with API integration - Major
|
- [ ] ~~Enabling pong gameplay via CLI against web users with API integration - Major~~
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@ -21,7 +21,8 @@ Input needed :
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"user": "<string>",
|
"user": "<string>",
|
||||||
"password": "<string>"
|
"password": "<string>",
|
||||||
|
(optional)"token": "<2fa token>"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -32,7 +33,13 @@ Can return:
|
|||||||
"msg": "Login successfully"
|
"msg": "Login successfully"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- 400 with response
|
- 402 with response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": "Please specify a 2fa token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 400 || 401 with response
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"error": "<corresponding error>"
|
"error": "<corresponding error>"
|
||||||
|
|||||||
32
doc/auth/remove.md
Normal file
32
doc/auth/remove.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# remove user
|
||||||
|
|
||||||
|
Available endpoints:
|
||||||
|
- DELETE `/`
|
||||||
|
|
||||||
|
Common return:
|
||||||
|
- 500 with response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Internal server error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## DELETE `/`
|
||||||
|
|
||||||
|
User to remove a user from the backend
|
||||||
|
|
||||||
|
Inputs: just need a valid JWT cookie
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- 200
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"msg": "User successfully deleted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 401 || 400
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "<corresponding msg>
|
||||||
|
}
|
||||||
|
```
|
||||||
120
doc/user/avatar.md
Normal file
120
doc/user/avatar.md
Normal 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
155
doc/user/friend.md
Normal 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
148
doc/user/matchHistory.md
Normal 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
41
doc/user/ping.md
Normal 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
212
doc/user/user.md
Normal 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>"
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -14,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",
|
||||||
|
|||||||
264
pnpm-lock.yaml
generated
264
pnpm-lock.yaml
generated
@ -50,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)
|
||||||
@ -91,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'}
|
||||||
@ -289,6 +295,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'}
|
||||||
@ -759,6 +891,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'}
|
||||||
@ -1458,6 +1594,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==}
|
||||||
|
|
||||||
@ -1673,6 +1813,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
|
||||||
|
|
||||||
@ -1803,6 +1948,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
|
||||||
@ -2194,6 +2427,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: {}
|
||||||
@ -2925,6 +3160,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:
|
||||||
|
|||||||
@ -5,3 +5,4 @@ ignoredBuiltDependencies:
|
|||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- better-sqlite3
|
- better-sqlite3
|
||||||
|
- sharp
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import fastifyJWT from '@fastify/jwt';
|
import fastifyJWT from '@fastify/jwt';
|
||||||
import fastifyCookie from '@fastify/cookie';
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import cors from '@fastify/cors'
|
import cors from '@fastify/cors';
|
||||||
|
|
||||||
|
import { totpCheck } from './totpCheck.js';
|
||||||
import { register } from './register.js';
|
import { register } from './register.js';
|
||||||
import { login } from './login.js';
|
import { login } from './login.js';
|
||||||
import { gRedir } from './gRedir.js';
|
import { gRedir } from './gRedir.js';
|
||||||
@ -12,6 +13,7 @@ import { totpSetup } from './totpSetup.js';
|
|||||||
import { totpDelete } from './totpDelete.js';
|
import { totpDelete } from './totpDelete.js';
|
||||||
import { totpVerify } from './totpVerify.js';
|
import { totpVerify } from './totpVerify.js';
|
||||||
import { logout } from './logout.js';
|
import { logout } from './logout.js';
|
||||||
|
import { remove } from './remove.js';
|
||||||
|
|
||||||
const saltRounds = 10;
|
const saltRounds = 10;
|
||||||
export const appName = process.env.APP_NAME || 'knl_meowscendence';
|
export const appName = process.env.APP_NAME || 'knl_meowscendence';
|
||||||
@ -25,9 +27,9 @@ authDB.prepareDB();
|
|||||||
export default async function(fastify, options) {
|
export default async function(fastify, options) {
|
||||||
|
|
||||||
fastify.register(cors, {
|
fastify.register(cors, {
|
||||||
origin: process.ENV.CORS_ORIGIN || 'http://localhost:5173',
|
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: [ "GET", "POST", "DELETE", "OPTIONS" ]
|
methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"]
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.register(fastifyJWT, {
|
fastify.register(fastifyJWT, {
|
||||||
@ -53,6 +55,9 @@ export default async function(fastify, options) {
|
|||||||
fastify.get('/me', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.get('/me', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
return { user: request.user };
|
return { user: request.user };
|
||||||
});
|
});
|
||||||
|
fastify.get('/2fa', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
|
return totpCheck(request, reply);
|
||||||
|
});
|
||||||
|
|
||||||
// GOOGLE sign in
|
// GOOGLE sign in
|
||||||
fastify.get('/login/google', async (request, reply) => {
|
fastify.get('/login/google', async (request, reply) => {
|
||||||
@ -117,4 +122,6 @@ export default async function(fastify, options) {
|
|||||||
}, async (request, reply) => { return register(request, reply, saltRounds, fastify); });
|
}, async (request, reply) => { return register(request, reply, saltRounds, fastify); });
|
||||||
|
|
||||||
fastify.get('/logout', {}, async (request, reply) => { return logout(reply, fastify); })
|
fastify.get('/logout', {}, async (request, reply) => { return logout(reply, fastify); })
|
||||||
|
|
||||||
|
fastify.delete('/', { preHandler: fastify.authenticate }, async (request, reply) => { return remove(request, reply, fastify) })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,8 +37,8 @@ export async function login(request, reply, fastify) {
|
|||||||
|
|
||||||
const userTOTP = authDB.getUser(user);
|
const userTOTP = authDB.getUser(user);
|
||||||
if (userTOTP.totpEnabled == 1) {
|
if (userTOTP.totpEnabled == 1) {
|
||||||
if (!request.body.token){
|
if (!request.body.token) {
|
||||||
return reply.code(401).send({ error: 'Invalid 2FA token' });
|
return reply.code(402).send({ error: 'Please specify a 2fa token' });
|
||||||
}
|
}
|
||||||
const isValid = verifyTOTP(userTOTP.totpHash, request.body.token);
|
const isValid = verifyTOTP(userTOTP.totpHash, request.body.token);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
|||||||
35
src/api/auth/remove.js
Normal file
35
src/api/auth/remove.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import authDB from '../../utils/authDB.js';
|
||||||
|
import { authUserRemove } from '../../utils/authUserRemove.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('fastify').FastifyRequest} request
|
||||||
|
* @param {import('fastify').FastifyReply} reply
|
||||||
|
* @param {import('fastify').FastifyInstance} fastify
|
||||||
|
*/
|
||||||
|
export async function remove(request, reply, fastify) {
|
||||||
|
try {
|
||||||
|
const user = request.user;
|
||||||
|
|
||||||
|
if (authDB.RESERVED_USERNAMES.includes(user)) {
|
||||||
|
return reply.code(400).send({ error: 'Reserved username' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authDB.checkUser(user) === false) {
|
||||||
|
return reply.code(400).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
authDB.rmUser(user)
|
||||||
|
|
||||||
|
authUserRemove(user, fastify)
|
||||||
|
|
||||||
|
return reply
|
||||||
|
.code(200)
|
||||||
|
.clearCookie("token")
|
||||||
|
.send({
|
||||||
|
msg: "User successfully deleted"
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
return reply.code(500).send({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/api/auth/totpCheck.js
Normal file
24
src/api/auth/totpCheck.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import authDB from '../../utils/authDB.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('fastify').FastifyRequest} request
|
||||||
|
* @param {import('fastify').FastifyReply} reply
|
||||||
|
*/
|
||||||
|
export async function totpCheck(request, reply) {
|
||||||
|
try {
|
||||||
|
const user = request.user;
|
||||||
|
|
||||||
|
if (authDB.checkUser(user) === false) {
|
||||||
|
return reply.code(400).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply
|
||||||
|
.code(200)
|
||||||
|
.send({
|
||||||
|
totp: authDB.isTOTPEnabled(user)
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
return reply.code(500).send({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,12 +9,11 @@ import { callAddScore, callLastId } from "../../utils/scoreStore_contract.js";
|
|||||||
*/
|
*/
|
||||||
export async function addTx(request, reply, fastify) {
|
export async function addTx(request, reply, fastify) {
|
||||||
try {
|
try {
|
||||||
const id = await callLastId();
|
const {tx, id} = await callAddScore(request.body.p1, request.body.p2, request.body.p1Score, request.body.p2Score);
|
||||||
const tx = 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({
|
return reply.code(200).send({
|
||||||
id: Number(id)
|
id: Number(id)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export default async function(fastify, options) {
|
|||||||
required: ['p1', 'p2', 'p1Score', 'p2Score'],
|
required: ['p1', 'p2', 'p1Score', 'p2Score'],
|
||||||
properties: {
|
properties: {
|
||||||
p1: { type: 'string', minLength: 1 },
|
p1: { type: 'string', minLength: 1 },
|
||||||
p2: { type: 'string', minLength: 1 },
|
p2: { type: 'string', minLength: 0 },
|
||||||
p1Score: { type: 'integer', minimum: 0 },
|
p1Score: { type: 'integer', minimum: 0 },
|
||||||
p2Score: { type: 'integer', minimum: 0 },
|
p2Score: { type: 'integer', minimum: 0 },
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/api/user/dAvatar.js
Normal file
24
src/api/user/dAvatar.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export async function dAvatar(request, reply, fastify, getUserInfo, getAvatarId, deleteAvatarId, deleteImage) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
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
23
src/api/user/dFriend.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export async function dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/api/user/dFriends.js
Normal file
16
src/api/user/dFriends.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export async function dFriends(request, reply, fastify, getUserInfo, deleteFriends) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/api/user/dMatchHistory.js
Normal file
26
src/api/user/dMatchHistory.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export async function dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/api/user/dMember.js
Normal file
22
src/api/user/dMember.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export async function dMember(request, reply, fastify, getUserInfo, changeDisplayName) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
const user = request.user;
|
||||||
|
const member = request.params.member;
|
||||||
|
if (member === 'displayName') {
|
||||||
|
changeDisplayName.run("", request.params.userId);
|
||||||
|
return reply.code(200).send({ msg: "Display name deleted successfully" });
|
||||||
|
} else {
|
||||||
|
return reply.code(400).send({ msg: "Member does not exist" })
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
return reply.code(500).send({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/api/user/dUser.js
Normal file
19
src/api/user/dUser.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export async function dUser(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteFriends, deleteUser) {
|
||||||
|
try {
|
||||||
|
if (request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
deleteMatchHistory.run('pong', userId);
|
||||||
|
deleteMatchHistory.run('tetris', userId);
|
||||||
|
deleteFriends.run(userId);
|
||||||
|
deleteUser.run(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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,59 +2,151 @@ import fastifyJWT from '@fastify/jwt';
|
|||||||
import fastifyCookie from '@fastify/cookie';
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import cors from '@fastify/cors'
|
import cors from '@fastify/cors'
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
|
import cors from '@fastify/cors';
|
||||||
|
|
||||||
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;
|
let database;
|
||||||
|
|
||||||
if (!env || env === 'development') {
|
if (!env || env === 'development') {
|
||||||
database = new Database(":memory:", { verbose: console.log });
|
database = new Database(':memory:', { verbose: console.log });
|
||||||
} else {
|
} else {
|
||||||
var dbPath = process.env.DB_PATH || '/db/db.sqlite'
|
const dbPath = process.env.DB_PATH || '/db/db.sqlite'
|
||||||
database = new Database(dbPath);
|
database = new Database(dbPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareDB() {
|
function prepareDB() {
|
||||||
database.exec(`
|
database.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS userData (
|
CREATE TABLE IF NOT EXISTS userData (
|
||||||
username TEXT PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
displayName TEXT
|
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
|
) STRICT
|
||||||
`);
|
`);
|
||||||
database.exec(`
|
database.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS friends (
|
CREATE TABLE IF NOT EXISTS friends (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT,
|
username TEXT,
|
||||||
friendName TEXT,
|
friendName TEXT,
|
||||||
UNIQUE(username, friendName),
|
UNIQUE(username, friendName),
|
||||||
CHECK(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();
|
prepareDB();
|
||||||
|
|
||||||
// POST
|
// 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 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
|
// 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 getUserInfo = database.prepare('SELECT * FROM userData WHERE username = ?;');
|
const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;');
|
||||||
const getUserData = database.prepare('SELECT * FROM userData;');
|
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 = ?;');
|
const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ? LIMIT ? OFFSET ?;');
|
||||||
// const isFriend = database.prepare('SELECT 1 FROM friends WHERE username = ? AND friendName = ?;');
|
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
|
// DELETE
|
||||||
const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;');
|
const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;');
|
||||||
const deleteFriend = database.prepare('DELETE FROM friends WHERE username = ? AND friendName = ?;');
|
const deleteFriend = database.prepare('DELETE FROM friends WHERE username = ? AND friendName = ?;');
|
||||||
const deleteFriends = database.prepare('DELETE FROM friends WHERE username = ?;');
|
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').FastifyInstance} fastify
|
||||||
* @param {import('fastify').FastifyPluginOptions} options
|
* @param {import('fastify').FastifyPluginOptions} options
|
||||||
*/
|
*/
|
||||||
export default async function(fastify, options) {
|
export default async function(fastify, options) {
|
||||||
|
|
||||||
@ -71,8 +163,19 @@ export default async function(fastify, options) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
fastify.register(fastifyCookie);
|
fastify.register(fastifyCookie);
|
||||||
|
fastify.register(cors, {
|
||||||
|
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
||||||
|
credentials: true,
|
||||||
|
methods: [ "GET", "POST", "PATCH", "DELETE", "OPTIONS" ]
|
||||||
|
});
|
||||||
|
|
||||||
fastify.decorate("authenticate", async function(request, reply) {
|
fastify.addContentTypeParser(
|
||||||
|
['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||||
|
{ parseAs: 'buffer' },
|
||||||
|
async (request, payload) => payload
|
||||||
|
);
|
||||||
|
|
||||||
|
fastify.decorate('authenticate', async function(request, reply) {
|
||||||
try {
|
try {
|
||||||
const jwt = await request.jwtVerify();
|
const jwt = await request.jwtVerify();
|
||||||
request.user = jwt.user;
|
request.user = jwt.user;
|
||||||
@ -81,178 +184,89 @@ export default async function(fastify, options) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.decorate("authenticateAdmin", async function(request, reply) {
|
fastify.decorate('authenticateAdmin', async function(request, reply) {
|
||||||
try {
|
try {
|
||||||
const jwt = await request.jwtVerify();
|
const jwt = await request.jwtVerify();
|
||||||
if (jwt.user !== 'admin') {
|
if (jwt.user !== 'admin') {
|
||||||
throw ("");
|
throw ('You lack administrator privileges');
|
||||||
}
|
}
|
||||||
|
request.user = jwt.user;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reply.code(401).send({ error: 'Unauthorized' });
|
reply.code(401).send({ error: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
fastify.get('/users', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.get('/users', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
|
||||||
try {
|
return gUsers(request, reply, fastify, getUserData);
|
||||||
const users = getUserData.all();
|
});
|
||||||
|
fastify.get('/users/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
return reply.code(200).send({ users });
|
return gNumberUsers(request, reply, fastify, getNumberUsers);
|
||||||
} catch (err) {
|
|
||||||
fastify.log.error(err);
|
|
||||||
return reply.code(500).send({ error: "Internal server error" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
fastify.get('/users/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.get('/users/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
try {
|
return gUser(request, reply, fastify, getUserInfo);
|
||||||
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" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
|
||||||
try {
|
return gFriends(request, reply, fastify, getUserInfo, getFriends);
|
||||||
const userId = request.params.userId;
|
});
|
||||||
|
fastify.get('/users/:userId/friends/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
if (!getUserInfo.get(userId)) {
|
return gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends);
|
||||||
return reply.code(404).send({ error: "User does not exist" });
|
});
|
||||||
}
|
fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { querystring: querySchemaMatchHistory } }, async (request, reply) => {
|
||||||
|
return gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory);
|
||||||
if (userId == request.user || request.user == 'admin') {
|
});
|
||||||
const friends = getFriends.all(userId);
|
fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
|
||||||
|
return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches);
|
||||||
if (!friends) {
|
});
|
||||||
return reply.code(404).send({ error: "User does not have friends D:" });
|
fastify.get('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
}
|
return gAvatar(request, reply, fastify, getUserInfo, getAvatarId, getImage);
|
||||||
return reply.code(200).send({ friends });
|
});
|
||||||
}
|
fastify.get('/ping/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
} catch (err) {
|
return gPing(request, reply, fastify, getActivityTime);
|
||||||
fastify.log.error(err);
|
|
||||||
return reply.code(500).send({ error: "Internal server error" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST
|
// POST
|
||||||
fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
||||||
try {
|
return pUser(request, reply, fastify, getUserInfo, createUser);
|
||||||
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" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
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', { bodyLimit: 5242880, 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
|
// PATCH
|
||||||
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.patch('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
try {
|
return uAvatar(request, reply, fastify, getUserInfo, setAvatarId, getAvatarId, deleteAvatarId, postImage, deleteImage);
|
||||||
const userId = request.params.userId;
|
});
|
||||||
if (request.user != 'admin' && request.user != userId) {
|
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMember } }, async (request, reply) => {
|
||||||
return reply.code(401).send({ error: "Unauthorized" });
|
return uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId);
|
||||||
}
|
});
|
||||||
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" });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
/**
|
|
||||||
* @description Can be used to delete a user from the db
|
|
||||||
*/
|
|
||||||
fastify.delete('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
fastify.delete('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
||||||
try {
|
return dUser(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteFriends, deleteUser);
|
||||||
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" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
fastify.delete('/users/:userId/:member', { preHandler: fastify.authenticate }, async (request, reply) => {
|
fastify.delete('/users/:userId/:member', { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||||
try {
|
return dMember(request, reply, fastify, getUserInfo, changeDisplayName);
|
||||||
const user = request.user;
|
});
|
||||||
const member = request.params.member;
|
fastify.delete('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
if (user == 'admin' || user == request.params.userId) {
|
return dFriends(request, reply, fastify, getUserInfo, deleteFriends);
|
||||||
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" });
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
fastify.delete('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
fastify.delete('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
try {
|
return dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend);
|
||||||
const userId = request.params.userId;
|
});
|
||||||
const friendId = request.params.friendId;
|
fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
|
||||||
if (!getUserInfo.get(userId)) {
|
return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris);
|
||||||
return reply.code(404).send({ error: "User does not exist" });
|
});
|
||||||
}
|
fastify.delete('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||||
if (request.user != 'admin' && request.user != userId) {
|
return dAvatar(request, reply, fastify, getUserInfo, getAvatarId, deleteAvatarId, deleteImage);
|
||||||
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" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/api/user/gAvatar.js
Normal file
23
src/api/user/gAvatar.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export async function gAvatar(request, reply, fastify, getUserInfo, getAvatarId, getImage) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/api/user/gFriends.js
Normal file
29
src/api/user/gFriends.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export async function gFriends(request, reply, fastify, getUserInfo, getFriends) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/api/user/gMatchHistory.js
Normal file
40
src/api/user/gMatchHistory.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export async function gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/api/user/gNumberFriends.js
Normal file
17
src/api/user/gNumberFriends.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export async function gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/api/user/gNumberMatches.js
Normal file
20
src/api/user/gNumberMatches.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export async function gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/api/user/gNumberUsers.js
Normal file
9
src/api/user/gNumberUsers.js
Normal 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
28
src/api/user/gPing.js
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/api/user/gUser.js
Normal file
24
src/api/user/gUser.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export async function gUser(request, reply, fastify, getUserInfo) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
const userInfo = getUserInfo.get(userId);
|
||||||
|
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
28
src/api/user/gUsers.js
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/api/user/pAvatar.js
Normal file
45
src/api/user/pAvatar.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('fastify').FastifyRequest} request
|
||||||
|
* @param {import('fastify').FastifyReply} reply
|
||||||
|
* @param {import('fastify').FastifyInstance} fastify
|
||||||
|
*/
|
||||||
|
export async function pAvatar(request, reply, fastify, getUserInfo, setAvatarId, postImage) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the raw body as a Buffer
|
||||||
|
const buffer = request.body;
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
return reply.code(400).send({ error: "No file uploaded" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size (5MB limit)
|
||||||
|
if (buffer.length > 5 * 1024 * 1024) {
|
||||||
|
return reply.code(400).send({ error: "File too large" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to WebP
|
||||||
|
const webpBuffer = await sharp(buffer).toFormat('webp').toBuffer();
|
||||||
|
|
||||||
|
// Save the image and update the user's avatar
|
||||||
|
const mimeType = request.headers['content-type'];
|
||||||
|
const fileName = `avatar_${userId}.webp`;
|
||||||
|
const imageId = postImage.run(fileName, mimeType, webpBuffer);
|
||||||
|
|
||||||
|
setAvatarId.run(imageId.lastInsertRowid, userId);
|
||||||
|
|
||||||
|
return reply.code(200).send({ msg: "Avatar uploaded successfully" });
|
||||||
|
} catch (err) {
|
||||||
|
fastify.log.error(err);
|
||||||
|
return reply.code(500).send({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/api/user/pFriend.js
Normal file
26
src/api/user/pFriend.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export async function pFriend(request, reply, fastify, getUserInfo, getFriend, addFriend) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/api/user/pMatchHistory.js
Normal file
66
src/api/user/pMatchHistory.js
Normal 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 !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(404).send({ error: "User does not exist" });
|
||||||
|
}
|
||||||
|
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
21
src/api/user/pPing.js
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/api/user/pUser.js
Normal file
19
src/api/user/pUser.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export async function pUser(request, reply, fastify, getUserInfo, createUser) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/api/user/uAvatar.js
Normal file
48
src/api/user/uAvatar.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
|
export async function uAvatar(request, reply, fastify, getUserInfo, setAvatarId, getAvatarId, deleteAvatarId, postImage, deleteImage) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
if (!getUserInfo.get(userId)) {
|
||||||
|
return reply.code(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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/api/user/uMember.js
Normal file
23
src/api/user/uMember.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export async function uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId) {
|
||||||
|
try {
|
||||||
|
const userId = request.params.userId;
|
||||||
|
if (request.user !== userId && request.user !== 'admin') {
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -82,9 +82,15 @@ function getUser(user) {
|
|||||||
return stmt.get(user);
|
return stmt.get(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rmUser(user) {
|
||||||
|
const stmt = database.prepare('DELETE FROM credentials WHERE username = ?');
|
||||||
|
stmt.run(user);
|
||||||
|
}
|
||||||
|
|
||||||
const authDB = {
|
const authDB = {
|
||||||
prepareDB,
|
prepareDB,
|
||||||
checkUser,
|
checkUser,
|
||||||
|
rmUser,
|
||||||
addUser,
|
addUser,
|
||||||
passwordQuery,
|
passwordQuery,
|
||||||
setTOTPSecret,
|
setTOTPSecret,
|
||||||
|
|||||||
19
src/utils/authUserRemove.js
Normal file
19
src/utils/authUserRemove.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} username
|
||||||
|
* @param {import('fastify').FastifyInstance} fastify
|
||||||
|
*/
|
||||||
|
export async function authUserRemove(username, fastify) {
|
||||||
|
const url = (process.env.USER_URL || "http://localhost:3002") + "/users/" + username;
|
||||||
|
const cookie = fastify.jwt.sign({ user: "admin" });
|
||||||
|
|
||||||
|
await axios.delete(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Cookie': 'token=' + cookie,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -29,7 +29,7 @@ async function loadContract() {
|
|||||||
async function callGetScore(id) {
|
async function callGetScore(id) {
|
||||||
try {
|
try {
|
||||||
const contract = await loadContract();
|
const contract = await loadContract();
|
||||||
const result = await contract.getScore(id);
|
const result = await contract.getScore(id - 1);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error calling view function:', 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);
|
const tx = await contract.addScore(p1, p2, p1Score, p2Score);
|
||||||
console.log('Transaction sent:', tx.hash);
|
console.log('Transaction sent:', tx.hash);
|
||||||
await tx.wait(); // Wait for the transaction to be mined
|
await tx.wait(); // Wait for the transaction to be mined
|
||||||
|
const id = await callLastId();
|
||||||
console.log('Transaction confirmed');
|
console.log('Transaction confirmed');
|
||||||
return tx;
|
return { tx, id };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error calling addScore function:', error);
|
console.error('Error calling addScore function:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user