diff --git a/.gitignore b/.gitignore index cc90d458..a55620d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ #own folders strapi_test letsencrypt +owncloud # Logs logs diff --git a/README.md b/README.md index fe85ae9e..b7a5f6c5 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ This is a REST API for the Altzone game. For API description see the wiki pages ## Releases +- [Release notes 03.10.2024](https://github.com/Alt-Org/Altzone-Server/blob/dev/doc/release-notes/release-03-10-2024.md) + - [Release notes 03.09.2024](https://github.com/Alt-Org/Altzone-Server/blob/main/doc/release-notes/release-03-09-24.md) @@ -15,7 +17,11 @@ Server is available on https://altzone.fi/api ## Swagger description -[Swagger](https://swagger.altzone.fi/) +1. [Latest swagger file version](https://github.com/Alt-Org/Altzone-Server/blob/dev/swagger/swagger.json) + You can find the latest API description and changes here. + +2. [Swagger on web](https://swagger.altzone.fi/) + ## Getting started @@ -29,7 +35,7 @@ Server is available on https://altzone.fi/api ### Start DB -Run ```docker compose up``` to start DB, mondo-express (UI for Mongo) and Nginx (it will take couple minutes for the first time) +Run ```docker compose up``` to start DB, mongo-express (UI for Mongo) and Nginx (it will take couple minutes for the first time) ### Start the API diff --git a/doc/img/ERD.png b/doc/img/ERD.png index 6ffe04a7..04e753c4 100644 Binary files a/doc/img/ERD.png and b/doc/img/ERD.png differ diff --git a/doc/img/daily_task/daily_task_add_update.drawio b/doc/img/daily_task/daily_task_add_update.drawio new file mode 100644 index 00000000..facaac44 --- /dev/null +++ b/doc/img/daily_task/daily_task_add_update.drawio @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/daily_task/daily_task_add_update.svg b/doc/img/daily_task/daily_task_add_update.svg new file mode 100644 index 00000000..3a2164b3 --- /dev/null +++ b/doc/img/daily_task/daily_task_add_update.svg @@ -0,0 +1,4 @@ + + + +
Tasks file updating
Need to add / update daily task
Find the biggest exsting id, add one to this number and it will be the id of a new task. Notice that it must be unique id across all tasks 
Add the new task object to the appropriate array
An example json file with tasks: 
{
  monday: [  //tasks for monday, this value can be for example tuesday, wednesday, week, month
    {
       id: 1, //unique id, as in SQL auto increment
       title: { fi: "Voita 5 peliä" }, //title of the task, the fi means that it is in Finnish
       content: { fi: "Sun pitää voittaa tänään 5 peliä" },  //The body of the task
       amount: 5,  // how much of atomic tasks to do, here 5 games = 5 tasks
       type: "win_battle",  // an enum type value of the atomic task
       coins: 10,  //how much coins the player gets after completing the whole task, here it is for 5 won games
       points: 20  //how much points player gets after completing the whole task
    }
  ]
}
The task type values:
1. play_battle
2. start_voting
3. collect_diamonds_in_battle
4. win_battle
5. write_chat_message
6. start_battle_with_new_character
7. vote
No
Yes
Are all new tasks added?
Tasks are updated
Restrart server with the updated file, so that the tasks are loaded to RAM
Tasks registration logic
Player have completed an atomic task
Sends data 
about it
Yes
Is the task already completed?

Register the atomic task completion 
in DB

No
Is the 
whole task completed?
Mark the task completion time 
in DB
No
Yes
Is the whole task started?
Write the starting 
date to DB
No
Nothing else to do
Yes
Player gets the notification about task
Send notification 
via MQTT 
that atomic task is completed
Send notification 
via MQTT 
that the whole 
task is completed
The task completion is dictated by the completedAt field. For example if now is 30.09 and the date in the completedAt field also equals 30.09, that means that the task is already completed. The same principle with weekly tasks (week number) and month.
There are no separate endpoint for sending data about the completed task. It is job of API to determine that the task is completed.
For example if the atomic task is to win a battle, API should register the completion of the task when player sends data of the ended game results 
The whole task is completed when when there are no atomic task left to do (amountLeft field equals 0)
Notification is sent to the topic:
/player/{player_id}/daily_task/{task_type}/update
Notification is sent to the topic:
/player/{player_id}/daily_task/{task_type}/end
\ No newline at end of file diff --git a/doc/img/flea_market/flea_market_items.drawio b/doc/img/flea_market/flea_market_items.drawio new file mode 100644 index 00000000..d505c460 --- /dev/null +++ b/doc/img/flea_market/flea_market_items.drawio @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/flea_market/flea_market_items.svg b/doc/img/flea_market/flea_market_items.svg new file mode 100644 index 00000000..b6dfb47e --- /dev/null +++ b/doc/img/flea_market/flea_market_items.svg @@ -0,0 +1,4 @@ + + + +
Item selling process
Clan member wants to put an Item for sale
Sends a request 
to put chosen 
Item for sale
No
Yes
Is player 
logged in and 
a member of Clan 
to which the Item 
belongs to?
Sends a reject
Sends success
Player gets response
Game
Internet
API

Start a voting between Clan members, whenever they want to sell the Item
Save started voting to DB
Item buying process
Clan member wants to buy an Item from flea market
Sends a request 
to buy an Item
No
Is player logged in and 
a member of a Clan?
Sends a reject
Sends success
Player gets response
Game
Internet
API
No
Yes
Does the Clan has 
enough coins to 
buy the Item?
Yes

Start a voting between Clan members, whenever they want to buy the Item

Book the Item from the flea market so other Players can not buy it during the voting 
Book the Item's price from Clan's coins 
Wait until the voting ends
No
Yes
Are Clan 
members desided to 
by the Item?
Release the Item so other Players can now buy it
Release booked Clan's coins
Move the Item to Clan's Stock
Decrease Clan's booked coins amount 
Save voting data 
to DB
\ No newline at end of file diff --git a/doc/img/leader_board/leader_board.drawio b/doc/img/leader_board/leader_board.drawio new file mode 100644 index 00000000..831368d6 --- /dev/null +++ b/doc/img/leader_board/leader_board.drawio @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/leader_board/leader_board.svg b/doc/img/leader_board/leader_board.svg new file mode 100644 index 00000000..84cea036 --- /dev/null +++ b/doc/img/leader_board/leader_board.svg @@ -0,0 +1,4 @@ + + + +
Top Clans leader board request
Player wants to see top Clans
Sends request
Yes
No
Where the 
board updated within 
last 12 hours? 
Regenerate the board again
Send list of 
top Clans
Player gets top Clans
\ No newline at end of file diff --git a/doc/img/notifications/mqtt_notifications_mechanism.drawio b/doc/img/notifications/mqtt_notifications_mechanism.drawio new file mode 100644 index 00000000..d342bd4d --- /dev/null +++ b/doc/img/notifications/mqtt_notifications_mechanism.drawio @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/notifications/mqtt_notifications_mechanism.svg b/doc/img/notifications/mqtt_notifications_mechanism.svg new file mode 100644 index 00000000..49130510 --- /dev/null +++ b/doc/img/notifications/mqtt_notifications_mechanism.svg @@ -0,0 +1,4 @@ + + + +
MQTT topics
Group
For example Clan to which Player belongs to or his/her personal notifications
Group _id
For example Clan's _id or Player's _id.
Notification resource
What kind of the notification it is, for example voting or daily task. 
Notification resource _id
For example voting _id.
Notice that there is "undefined" value, which works as a placeholder in case the resource can not be defined (if process status = new) 
Process status
Usually a notification is coming about some continuing process step. For example voting has 3 steps: start, players are voting, end.
The process status can be described with 4 statuses:
1. New - Starting point, voting started
2. Update - Process updates, someone has voted
3. Error - Error occured during the process and it can not be continued. Voting time is over and there are not enought voters participated
4. End - Process end, voting is done.
MQTT topics examples
Get notification when particular voting is done: 
/ clan / {clan_id} / voting / {voting_id} / end
clan 667eedc9b3b5bf0f7a840ef1 voting 667bfec6afb8211b4bd8dbff / end
Get all notifications coming for particular clan members: 
/ clan / {clan_id}
clan 667eedc9b3b5bf0f7a840ef1
Get notifications of new voting started: 
/ clan / {clan_id} / voting / {voting_id = undefined} / new
clan 667eedc9b3b5bf0f7a840ef1 voting / undefined / new
MQTT topics structure
Vertical Container
Game
Game
MQTT Brocker
API
Sends notification
Notification sending process
API wants to send a notification to game
Determines the topic where notification should be sent
Encrypts a notification message
Sends notification
to MQTT brocker with credentials
Yes
No
Is 
notification from allowed 
domain?
No
Are credentials valid?
Sends reject
Prints the error to log
Sends notification 
to game
API
Internet
MQTT brocker
Internet
Game
Decrypts the notification
Game has the notification
Notification waiting process
Game wants to receive notifications
Subscribes to 
a topic
No
Yes
Are credentials valid?
Sends reject
Sends success
Game is waiting for notifications
Game
Internet
MQTT brocker
\ No newline at end of file diff --git a/doc/img/voting/voting.drawio b/doc/img/voting/voting.drawio new file mode 100644 index 00000000..5bcac481 --- /dev/null +++ b/doc/img/voting/voting.drawio @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/img/voting/voting.svg b/doc/img/voting/voting.svg new file mode 100644 index 00000000..dd24df46 --- /dev/null +++ b/doc/img/voting/voting.svg @@ -0,0 +1,4 @@ + + + +
Voting for item to sell / buy process
Player has chosen an Item to sell / buy
Voting data is saved
Sends data of chosen Item
Sends notification 
via MQTT broker 
about started voting 
with voting _id
Clan members see that the voting is started and deside whenever they want to sell / buy or not
Sends vote 
with voting
 _id
No
Is logged-in 
player, member of 
the Clan where voting is 
going?
Yes
No
Is voting ended = does
 the voting has min amount of approvals or rejections
Sends reject
Sends success
Player receives the response
Clan members receives the notification that voting is ended
Sends notification 
that voting is done 
via MQTT broker
Sends notification 
about new vote 
made
Clan members get the notification about new vote made
Game
Internet
API
Yes
The notification is sent to "/clan/{clan_id}/voting/undefined/new" topic.
With json payload:
{
  topic: "/clan/{clan_id}/voting/{voting_id}",
  status: "new",
  voting_id: {voting_id},
  type: "selling_item", // "selling_item" if selling, "buying_item" if buying
  item: {item_object}, // item data for selling or buying,
  organizer: {player_object} // who has chosen the item = organizer  
}
The notification is sent to "/clan/{clan_id}/voting/{voting_id}/end" topic.
With json payload:
{
  topic: "/clan/{clan_id}/voting/{voting_id}",
  status: "end",
  voting_id: {voting_id},
  type: "selling_item", // "selling_item" if selling, "buying_item" if buying
  item: {item_object}, // item data for selling or buying
}
The notification is sent to "/clan/{clan_id}/voting/{voting_id}/update" topic.
With json payload:
{
  topic: "/clan/{clan_id}/voting/{voting_id}",
  status: "update",
  voting_id: {voting_id},
  type: "selling_item", // "selling_item" if selling, "buying_item" if buying
  item: {item_object}, // item data for selling or buying,
  voter: {player_object} // player who has voted  
}
\ No newline at end of file diff --git a/doc/release-notes/release-03-10-2024.md b/doc/release-notes/release-03-10-2024.md new file mode 100644 index 00000000..14c2e92b --- /dev/null +++ b/doc/release-notes/release-03-10-2024.md @@ -0,0 +1,64 @@ +## Changes list +New features: + +- Saving battle results +- Moving items from Stock to SoulHome and overwise +- Stealing items +- Saving log files + +Changes to existing features: + +- Clan has `labels` field, which replace the `tag` field +- Player got `parentalControl` and `above13` fields + + +## Links + +[Swagger file](https://github.com/Alt-Org/Altzone-Server/blob/dev/swagger/releases/03-10-2024-release.json) + +[Release](https://devapi.altzone.fi/release/03-10-2024) address: _https://devapi.altzone.fi/release/03-10-2024_ + + +## Changes detailed descriptions + + +### Saving battle results +New endpoint /gameData/battle POST added. + +It can be used for saving battles results. + +That data is planned to be used for giving points to Players, as well as determining in-game tasks completion. + + +### Moving items from Stock to SoulHome and overwise +New endpoint /item/move POST added. + +It can be used whenever there are a need to move Items inside Clan: from Stock to SoulHome or from SoulHome to Stock. + +It is a replacement for old item updating endpoint, since the only thing that can be changed in Item data is the `stock_id` and `room_id` fields. The endpoint abstracts updating logic for easier usability by client. + + +### Stealing items +New /item/steal POST and GET endpoints added. + +The GET endpoint can be used for checking from what place (SoulHome) Items can be stolen based on the _steal_token_ retrieved during battle results informing. + +The POST endpoint can be used for stealing itself, which means moving items from loosed Clan's SoulHome to won Clan's SoulHome. + + +### Saving log files +New endpoint /gameAnalytics/logFile POST added. + +It can be used to save log files produced during the game, which then can be retrieved and analyzed later. + + +### Clan has `labels` field, which replace the `tag` field +The `tag` field which there previously a `string` is now deprecated and should be replaced with `labels` field, which is a `string[]` and can hold up to 5 Clan labels. + +In the future release the `tag` field will be removed completely. + + +### Player got `parentalControl` and `above13` fields +Player data now holds two additional fields that was required to satisfy by Google Play's platform rules. + +The value `null` is a default value for both fields, which means that player did not answer the age or parental approval questions. diff --git a/package-lock.json b/package-lock.json index 55af4f6e..8b79500f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,8 @@ "mongodb": "^5.1.0", "mongoose": "^7.3.0", "save": "^2.9.0", - "swagger-ui-express": "^4.6.3" + "swagger-ui-express": "^4.6.3", + "webdav": "4.11.4" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -37,6 +38,7 @@ "@nestjs/testing": "^9.4.3", "@types/express": "^4.17.17", "@types/jest": "^29.5.5", + "@types/multer": "^1.4.12", "@types/node": "^18.14.2", "@types/supertest": "^2.0.14", "@types/swagger-ui-express": "^4.1.3", @@ -1926,6 +1928,15 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "18.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", @@ -2384,8 +2395,16 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } }, "node_modules/babel-jest": { "version": "29.7.0", @@ -2506,8 +2525,12 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2714,6 +2737,11 @@ "node": ">=10.16.0" } }, + "node_modules/byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2821,6 +2849,14 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -3000,7 +3036,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3166,6 +3201,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3213,7 +3256,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -3637,6 +3679,27 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3712,6 +3775,25 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", @@ -3759,7 +3841,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3989,6 +4070,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -3998,6 +4087,11 @@ "node": ">=8" } }, + "node_modules/hot-patcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-1.0.0.tgz", + "integrity": "sha512-3H8VH0PreeNsKMZw16nTHbUp4YoHCnPlawpsPXGJUR4qENDynl79b6Xk9CIFvLcH1qungBsCuzKcWyzoPPalTw==" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4190,6 +4284,11 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-core-module": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", @@ -5314,6 +5413,11 @@ "node": ">=6" } }, + "node_modules/layerr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz", + "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5472,6 +5576,16 @@ "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==" }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5769,6 +5883,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/nested-property": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -6134,6 +6253,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-posix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" + }, "node_modules/path-scurry": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", @@ -6348,6 +6472,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -6452,6 +6581,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -7043,6 +7177,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -7664,6 +7803,20 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7754,6 +7907,48 @@ "defaults": "^1.0.3" } }, + "node_modules/webdav": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-4.11.4.tgz", + "integrity": "sha512-abrHtgE9N/7U7sO7veJO3HeE5cHZ35YOOIU1//SKbVlVlfwq5XvW4omNC1++gRnMwf89qjZI8hWy7jCz2D5haQ==", + "dependencies": { + "axios": "^0.27.2", + "base-64": "^1.0.0", + "byte-length": "^1.0.2", + "fast-xml-parser": "^4.2.4", + "he": "^1.2.0", + "hot-patcher": "^1.0.0", + "layerr": "^0.1.2", + "md5": "^2.3.0", + "minimatch": "^5.1.0", + "nested-property": "^4.0.0", + "path-posix": "^1.0.0", + "url-join": "^4.0.1", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webdav/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/webdav/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 2bb89c92..ae2989d6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "mongodb": "^5.1.0", "mongoose": "^7.3.0", "save": "^2.9.0", - "swagger-ui-express": "^4.6.3" + "swagger-ui-express": "^4.6.3", + "webdav": "4.11.4" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -49,6 +50,7 @@ "@nestjs/testing": "^9.4.3", "@types/express": "^4.17.17", "@types/jest": "^29.5.5", + "@types/multer": "^1.4.12", "@types/node": "^18.14.2", "@types/supertest": "^2.0.14", "@types/swagger-ui-express": "^4.1.3", diff --git a/prod_docker-compose.yml b/prod_docker-compose.yml index a9dce160..b5e4d9f3 100644 --- a/prod_docker-compose.yml +++ b/prod_docker-compose.yml @@ -10,6 +10,7 @@ services: networks: - backend - db + - owncloud environment: - HOST=altzone_api - PORT=8080 @@ -18,6 +19,11 @@ services: - MONGO_HOST=altzone_db - MONGO_PORT=27017 - MONGO_DB_NAME=altzone + - OWNCLOUD_HOST=owncloud + - OWNCLOUD_PORT=8080 + - OWNCLOUD_USER=user + - OWNCLOUD_PASSWORD=password + - OWNCLOUD_LOG_FILES_ROOT=/log-files site: image: leolab1337/altzone_web_pages:v2 @@ -70,6 +76,45 @@ services: - MONGO_INITDB_ROOT_PASSWORD=superSecretPassword - MONGO_INITDB_DATABASE=altzone + owncloud: + image: owncloud/server:10.15 + container_name: altzone_owncloud + restart: always + networks: + - owncloud + - owncloud_db + ports: + - 8082:8080 + depends_on: + - owncloud_db + volumes: + - ./owncloud:/mnt/data + environment: + - OWNCLOUD_DOMAIN=owncloud + - OWNCLOUD_TRUSTED_DOMAINS=owncloud,api,0.0.0.0,localhost + - OWNCLOUD_DB_TYPE=mysql + - OWNCLOUD_DB_NAME=owncloud + - OWNCLOUD_DB_USERNAME=root + - OWNCLOUD_DB_PASSWORD=owncloud + - OWNCLOUD_DB_HOST=owncloud_db + - OWNCLOUD_ADMIN_USERNAME=user + - OWNCLOUD_ADMIN_PASSWORD=password + - OWNCLOUD_MYSQL_UTF8MB4=true + - OWNCLOUD_REDIS_ENABLED=false + + owncloud_db: + image: mariadb:11.1 + container_name: altzone_owncloud_db + restart: always + networks: + - owncloud_db + ports: + - 8083:3306 + environment: + MYSQL_ROOT_PASSWORD: owncloud + volumes: + - owncloud_db:/var/lib/mysql + proxy: build: dockerfile: ./prod_nginx.Dockerfile @@ -84,6 +129,7 @@ services: networks: - backend - altzone_swagger + - owncloud volumes: - ./prod_nginx.conf:/etc/nginx/nginx.conf - ./public:/usr/local/nginx/public:ro @@ -97,6 +143,11 @@ networks: name: altzone_db swagger: name: altzone_swagger + owncloud: + name: altzone_owncloud + owncloud_db: + name: altzone_owncloud_db volumes: - altzone_db: \ No newline at end of file + altzone_db: + owncloud_db: \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index db514a4f..66a911fe 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,6 +24,8 @@ import { SoulHomeModule } from './soulhome/soulhome.module'; import { RoomModule } from './room/room.module'; import { ClanVoteModule } from './shop/clanVote/clanVote.module'; import { ItemShopModule } from './shop/itemShop/itemShop.module'; +import { GameDataModule } from './gameData/gameData.module'; +import { GameAnalyticsModule } from './gameAnalytics/gameAnalytics.module'; // Set up database connection const mongoUser = process.env.MONGO_USERNAME || 'rootUser'; @@ -59,6 +61,8 @@ const mongoString = `mongodb://${mongoUser}:${mongoPassword}@${mongoHost}:${mong AuthorizationModule, PermissionModule, ApiStateModule, + GameDataModule, + GameAnalyticsModule ], controllers: [AppController], providers: [ diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index ef47be2c..cd67db8b 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -10,6 +10,7 @@ import {RequestHelperModule} from "../requestHelper/requestHelper.module"; ApiStateModule ], providers: [AuthService], - controllers: [AuthController] + controllers: [AuthController], + exports: [AuthService] }) export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 2b6a0b82..fecb832d 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@nestjs/common'; +import {Injectable, UnauthorizedException} from '@nestjs/common'; import {MongooseError} from "mongoose"; import {JwtService} from "@nestjs/jwt"; import {ModelName} from "../common/enum/modelName.enum"; @@ -6,6 +6,8 @@ import {RequestHelperService} from "../requestHelper/requestHelper.service"; import {ProfileDto} from "../profile/dto/profile.dto"; import {PlayerDto} from "../player/dto/player.dto"; import {ClanDto} from "../clan/dto/clan.dto"; +import { APIError } from '../common/controller/APIError'; +import { APIErrorReason } from '../common/controller/APIErrorReason'; @Injectable() export class AuthService { @@ -62,4 +64,24 @@ export class AuthService { // return expiresIn ? new Date(expiresIn * 1000) : null; return diffInSeconds ? diffInSeconds : null; } + + /** + * Verifies the provided JWT token. + * + * @param token - The JWT token to verify. + * @returns - A promise that resolves with the decoded token if verification is successful. + * @throws - Throws an UnauthorizedException if the token is invalid or expired + */ + public async verifyToken(token: string): Promise { + try { + return await this.jwtService.verifyAsync(token); + } catch (_) { + const expTime = this.getTokenExpirationTime(token); + const errorMsg = expTime <= 0 ? "Token has expired" : "Invalid token"; + throw new UnauthorizedException({ + statusCode: 403, + errors: [new APIError({ reason: APIErrorReason.NOT_AUTHORIZED, message: errorMsg })] + }); + } + } } diff --git a/src/authorization/authorization.interceptor.ts b/src/authorization/authorization.interceptor.ts index b8c908ae..ef0524e8 100644 --- a/src/authorization/authorization.interceptor.ts +++ b/src/authorization/authorization.interceptor.ts @@ -142,14 +142,15 @@ export class AuthorizationInterceptor implements NestInterceptor{ request.body = pick(dataClass, allowedFields); } - return next.handle().pipe(map((data: any) => { + return next.handle().pipe(map(async (data: any) => { //if nothing came, or it is an array === read many(serialization is done on request) if(!data) return data; - const dataParsed = data as IResponseShape; + const dataParsed = (await data) as IResponseShape; + //it is an array === read many(serialization is done on request) - if(!dataParsed || dataParsed.metaData.dataType === 'Array') + if(!dataParsed || !dataParsed.metaData || !data.metaData?.dataType || dataParsed.metaData.dataType === 'Array') return data; const {dataKey} = dataParsed.metaData; diff --git a/src/authorization/caslAbility.factory.ts b/src/authorization/caslAbility.factory.ts index cfdc2785..40b2c7b3 100644 --- a/src/authorization/caslAbility.factory.ts +++ b/src/authorization/caslAbility.factory.ts @@ -45,6 +45,10 @@ import { ShopItemDTO } from "../shop/itemShop/dto/shopItem.dto"; import { RemovePlayerDTO } from "../clan/join/dto/removePlayer.dto"; import { isType, ObjectType } from "../common/base/decorator/AddType.decorator"; import { PlayerLeaveClanDto } from "../clan/join/dto/playerLeave.dto"; +import { ChatDto } from "../chat/dto/chat.dto"; +import { UpdateChatDto } from "../chat/dto/updateChat.dto"; +import { MessageDto } from "../chat/dto/message.dto"; +import { chatRules } from "./rule/chatRules"; export type AllowedAction = Action.create_request | Action.read_request | Action.read_response | Action.update_request | Action.delete_request; @@ -60,7 +64,8 @@ export type AllowedSubject = typeof SoulHomeDto | typeof UpdateSoulHomeDto | typeof RoomDto | typeof UpdateRoomDto | typeof ClanVoteDto | typeof UpdateClanVoteDto | - typeof ItemShopDto | typeof ShopItemDTO; + typeof ItemShopDto | typeof ShopItemDTO | + typeof ChatDto | typeof UpdateChatDto | typeof MessageDto; type Subjects = InferSubjects; @@ -125,6 +130,8 @@ export class CASLAbilityFactory { if (isType(obj, 'ItemShopDto') || isType(obj, 'ShopItemDTO')) return shopRules(user, subject, action, subjectObj, this.requestHelperService) + if (isType(obj, 'ChatDto') || isType(obj, 'UpdateChatDto') || isType(obj, 'MessageDto')) + return chatRules(user, subject, action, subjectObj, this.requestHelperService); } } diff --git a/src/authorization/rule/chatRules.ts b/src/authorization/rule/chatRules.ts new file mode 100644 index 00000000..d17e1a01 --- /dev/null +++ b/src/authorization/rule/chatRules.ts @@ -0,0 +1,31 @@ +import {AllowedAction} from "../caslAbility.factory"; +import {AbilityBuilder, createMongoAbility, ExtractSubjectType} from "@casl/ability"; +import {Action} from "../enum/action.enum"; +import {InferSubjects, MongoAbility} from "@casl/ability/dist/types"; +import {RulesSetterAsync} from "../type/RulesSetter.type"; +import {ModelName} from "../../common/enum/modelName.enum"; +import {PlayerDto} from "../../player/dto/player.dto"; + +type Subjects = InferSubjects; +type Ability = MongoAbility<[AllowedAction | Action.manage, Subjects | 'all']>; +export const chatRules: RulesSetterAsync = async (user, subject: any, action, subjectObj: any, requestHelperService) => { + const { can, build } = new AbilityBuilder(createMongoAbility); + + const loggedPlayer = await requestHelperService.getModelInstanceById(ModelName.PLAYER, user.player_id, PlayerDto); + + if(loggedPlayer.parentalAuth || loggedPlayer.above13){ + can(Action.create_request, subject); + + can(Action.read_request, subject); + can(Action.read_response, subject); + + can(Action.update_request, subject); + + can(Action.delete_request, subject); + } + + return build({ + detectSubjectType: (item) => + item.constructor as ExtractSubjectType, + }); +} \ No newline at end of file diff --git a/src/authorization/rule/playerRules.ts b/src/authorization/rule/playerRules.ts index 6cd4ba83..4fe15302 100644 --- a/src/authorization/rule/playerRules.ts +++ b/src/authorization/rule/playerRules.ts @@ -19,7 +19,7 @@ export const playerRules: RulesSetterAsync = async (user, sub if(action === Action.create || action === Action.read){ can(Action.create_request, subject); - const publicFields = ['_id', 'name', 'uniqueIdentifier', 'profile_id', 'clan_id', 'Clan', 'CustomCharacter']; + const publicFields = ['_id', 'name', 'uniqueIdentifier', 'profile_id', 'clan_id', 'Clan', 'CustomCharacter', 'above13', 'parentalAuth']; can(Action.read_request, subject); can(Action.read_response, subject, publicFields); can(Action.read_response, subject, {_id: user.player_id}); diff --git a/src/characterClass/files/niklas.txt b/src/characterClass/files/niklas.txt new file mode 100644 index 00000000..02904778 --- /dev/null +++ b/src/characterClass/files/niklas.txt @@ -0,0 +1 @@ +"Hello world" \ No newline at end of file diff --git a/src/characterClass/files/test.txt b/src/characterClass/files/test.txt new file mode 100644 index 00000000..0da04c78 --- /dev/null +++ b/src/characterClass/files/test.txt @@ -0,0 +1 @@ +fg diff --git a/src/chat/chat.controller.ts b/src/chat/chat.controller.ts index f1ba8712..2a31876c 100644 --- a/src/chat/chat.controller.ts +++ b/src/chat/chat.controller.ts @@ -4,7 +4,6 @@ import { Controller, Delete, Get, HttpCode, - NotImplementedException, Param, Post, Put, @@ -26,12 +25,13 @@ import {GetAllQuery} from "../common/decorator/param/GetAllQuery"; import {IGetAllQuery} from "../common/interface/IGetAllQuery"; import { OffsetPaginate } from "../common/interceptor/request/offsetPagination.interceptor"; import { AddSortQuery } from "../common/interceptor/request/addSortQuery.interceptor"; -import {NoAuth} from "../auth/decorator/NoAuth.decorator"; import {CreateMessageDto} from "./dto/createMessage.dto"; import {MessageDto} from "./dto/message.dto"; import {chat_idParam, messageParam} from "./dto/messageParam"; import {APIObjectName} from "../common/enum/apiObjectName.enum"; import { Serialize } from "../common/interceptor/response/Serialize"; +import { Authorize } from "../authorization/decorator/Authorize"; +import { Action } from "../authorization/enum/action.enum"; @Controller('chat') @@ -42,95 +42,70 @@ export class ChatController { ) { } - @NoAuth() @Post() - // @BasicPOST(ChatDto) - public create( - // @Body() body: CreateChatDto - ) { - throw new NotImplementedException(); - //return this.service.createOne(body); + @Authorize({action: Action.create, subject: ChatDto}) + @BasicPOST(ChatDto) + public create(@Body() body: CreateChatDto) { + return this.service.createOne(body); } - @NoAuth() - // @Serialize(ChatDto) + @Serialize(ChatDto) @Get('/:_id') - // @BasicGET(ModelName.CHAT, ChatDto) - // @AddGetQueries() - public get( - //@Param() param: _idDto, - //@Req() request: Request - ) { - throw new NotImplementedException(); - //return this.service.readOneById(param._id, request['mongoPopulate']); + @Authorize({action: Action.read, subject: ChatDto}) + @BasicGET(ModelName.CHAT, ChatDto) + @AddGetQueries() + public get(@Param() param: _idDto, @Req() request: Request) { + return this.service.readOneById(param._id, request['mongoPopulate']); } - @NoAuth() @Get() - // @Serialize(ChatDto) - // @OffsetPaginate(ModelName.CHAT) - // @AddSearchQuery(ChatDto) - // @AddSortQuery(ChatDto) - // @BasicGET(ModelName.CHAT, ChatDto) - public getAll( - //@GetAllQuery() query: IGetAllQuery - ) { - throw new NotImplementedException(); - //return this.service.readAll(query); + @Authorize({action: Action.read, subject: ChatDto}) + @Serialize(ChatDto) + @OffsetPaginate(ModelName.CHAT) + @AddSearchQuery(ChatDto) + @AddSortQuery(ChatDto) + @BasicGET(ModelName.CHAT, ChatDto) + public getAll(@GetAllQuery() query: IGetAllQuery) { + return this.service.readAll(query); } - @NoAuth() @Put() - // @BasicPUT(ModelName.CHAT) - public update( - //@Body() body: UpdateChatDto - ) { - throw new NotImplementedException(); - //return this.service.updateOneById(body); + @Authorize({action: Action.update, subject: UpdateChatDto}) + @BasicPUT(ModelName.CHAT) + public update(@Body() body: UpdateChatDto) { + return this.service.updateOneById(body); } - @NoAuth() @Delete('/:_id') - // @BasicDELETE(ModelName.CHAT) + @Authorize({action: Action.delete, subject: UpdateChatDto}) + @BasicDELETE(ModelName.CHAT) public delete(@Param() param: _idDto) { - throw new NotImplementedException(); - //return this.service.deleteOneById(param._id); + return this.service.deleteOneById(param._id); } - @NoAuth() @Post('/:chat_id/messages') - // @HttpCode(204) - // @BasicPOST(ChatDto) - public createMessage( - // @Param() param: chat_idParam, - // @Body() body: CreateMessageDto - ) { - throw new NotImplementedException(); - //return this.service.createMessage(param.chat_id, body); + @Authorize({action: Action.create, subject: ChatDto}) + @HttpCode(204) + @BasicPOST(ChatDto) + public createMessage(@Param() param: chat_idParam, @Body() body: CreateMessageDto) { + return this.service.createMessage(param.chat_id, body); } - @NoAuth() @Get('/:chat_id/messages/:_id') - // @BasicGET(APIObjectName.MESSAGE, MessageDto) - public getMessage( - // @Param() param: messageParam - ) { - throw new NotImplementedException(); - //return this.service.readOneMessageById(param.chat_id, param._id); + @Authorize({action: Action.read, subject: MessageDto}) + @BasicGET(APIObjectName.MESSAGE, MessageDto) + public getMessage(@Param() param: messageParam) { + return this.service.readOneMessageById(param.chat_id, param._id); } - @NoAuth() @Get('/:chat_id/messages') - // @OffsetPaginate(ModelName.CHAT) - // @AddSearchQuery(MessageDto) - // @AddSortQuery(MessageDto) - // @BasicGET(APIObjectName.MESSAGE, MessageDto) - public getAllMessages( - //@Param() param: chat_idParam, - //@GetAllQuery() query: IGetAllQuery - ) { - throw new NotImplementedException(); - //return this.service.readAllMessages(param.chat_id, query); + @Authorize({action: Action.read, subject: MessageDto}) + @OffsetPaginate(ModelName.CHAT) + @AddSearchQuery(MessageDto) + @AddSortQuery(MessageDto) + @BasicGET(APIObjectName.MESSAGE, MessageDto) + public getAllMessages(@Param() param: chat_idParam, @GetAllQuery() query: IGetAllQuery) { + return this.service.readAllMessages(param.chat_id, query); } // @Put('/:chat_id/messages') diff --git a/src/clan/clan.schema.ts b/src/clan/clan.schema.ts index 916dc60f..08689e46 100644 --- a/src/clan/clan.schema.ts +++ b/src/clan/clan.schema.ts @@ -2,6 +2,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import {HydratedDocument} from "mongoose"; import {ModelName} from "../common/enum/modelName.enum"; import {ExtractField} from "../common/decorator/response/ExtractField"; +import { ClanLabel } from './enum/clanLabel.enum'; export type ClanDocument = HydratedDocument; @@ -13,6 +14,9 @@ export class Clan { @Prop({ type: String }) tag: string; + @Prop({ type: [String], enum: ClanLabel, required: true }) + labels: string[]; + @Prop({ type: Number, default: 0 }) gameCoins: number; diff --git a/src/clan/dto/clan.dto.ts b/src/clan/dto/clan.dto.ts index 59e601ef..947b278c 100644 --- a/src/clan/dto/clan.dto.ts +++ b/src/clan/dto/clan.dto.ts @@ -17,6 +17,9 @@ export class ClanDto { @Expose() tag: string; + @Expose() + labels: string[]; + @Expose() gameCoins: number; diff --git a/src/clan/dto/createClan.dto.ts b/src/clan/dto/createClan.dto.ts index 810d5a66..e75b272b 100644 --- a/src/clan/dto/createClan.dto.ts +++ b/src/clan/dto/createClan.dto.ts @@ -1,5 +1,6 @@ -import {IsBoolean, IsInt, IsOptional, IsString} from "class-validator"; +import { IsArray, ArrayMaxSize, IsEnum, IsInt, IsBoolean, IsOptional, IsString } from "class-validator"; import AddType from "../../common/base/decorator/AddType.decorator"; +import { ClanLabel } from '../enum/clanLabel.enum'; @AddType('CreateClanDto') export class CreateClanDto { @@ -9,9 +10,14 @@ export class CreateClanDto { @IsString() tag: string; + @IsArray() + @ArrayMaxSize(5) + @IsEnum(ClanLabel, { each: true }) + labels: ClanLabel[]; + @IsInt() gameCoins: number; - + @IsBoolean() @IsOptional() isOpen:boolean diff --git a/src/clan/dto/updateClan.dto.ts b/src/clan/dto/updateClan.dto.ts index 0be96639..c274ff6b 100644 --- a/src/clan/dto/updateClan.dto.ts +++ b/src/clan/dto/updateClan.dto.ts @@ -1,7 +1,7 @@ -import {ArrayNotEmpty, IsArray, IsBoolean, IsInt, IsMongoId, IsOptional, IsString, Validate} from "class-validator"; -import {IsClanExists} from "../decorator/validation/IsClanExists.decorator"; +import { ArrayNotEmpty, IsArray, ArrayMaxSize, IsEnum, IsBoolean, IsInt, IsMongoId, IsOptional, IsString, Validate } from 'class-validator';import {IsClanExists} from "../decorator/validation/IsClanExists.decorator"; import { IsPlayerExists } from "../../player/decorator/validation/IsPlayerExists.decorator"; import AddType from "../../common/base/decorator/AddType.decorator"; +import { ClanLabel } from '../enum/clanLabel.enum'; @AddType('UpdateClanDto') export class UpdateClanDto { @@ -17,6 +17,12 @@ export class UpdateClanDto { @IsOptional() tag: string; + @IsArray() + @ArrayMaxSize(5) + @IsEnum(ClanLabel, { each: true }) + @IsOptional() + labels: ClanLabel[]; + @IsInt() @IsOptional() gameCoins: number; diff --git a/src/clan/enum/clanLabel.enum.ts b/src/clan/enum/clanLabel.enum.ts new file mode 100644 index 00000000..e895fbef --- /dev/null +++ b/src/clan/enum/clanLabel.enum.ts @@ -0,0 +1,55 @@ +/** + * Enum used to determine the valid labels that can be assigned to a Clan. + * + * This enum is used for validating the labels field in the Clan entity, + * ensuring that only predefined values are allowed. + * + * Notice that whenever there is a need to add a new label for a Clan, + * this enum must be updated as well with the new label value. + * + * @example ```ts + * // Do not write it as plain text + * const clanLabel = 'eläinrakkaat'; + * // Use the enum instead + * const clanLabel = ClanLabel.ELÄINRAKKAAT; + * ``` + */ +export enum ClanLabel { + ELÄINRAKKAAT = 'eläinrakkaat', + MAAHANMUUTTOMYÖNTEISET = 'maahanmuuttomyönteiset', + LGBTQ = 'lgbtq+', + RAITTIIT = 'raittiit', + KOHTELIAAT = 'kohteliaat', + KIUSAAMISENVASTAISET = 'kiusaamisenvastaiset', + URHEILEVAT = 'urheilevat', + SYVÄLLISET = 'syvälliset', + OIKEUDENMUKAISET = 'oikeudenmukaiset', + KAIKKIEN_KAVERIT = 'kaikkien kaverit', + ITSENÄISET = 'itsenäiset', + RETKEILIJÄT = 'retkeilijät', + SUOMENRUOTSALAISET = 'suomenruotsalaiset', + HUUMORINTAJUISET = 'huumorintajuiset', + RIKKAAT = 'rikkaat', + IKITEINIT = 'ikiteinit', + JUORUILEVAT = 'juoruilevat', + RAKASTAVAT = 'rakastavat', + OLEILIJAT = 'oleilijat', + NÖRTIT = 'nörtit', + MUSADIGGARIT = 'musadiggarit', + TUNTEELLISET = 'tunteelliset', + GAMERIT = 'gamerit', + ANIMEFANIT = 'animefanit', + SINKUT = 'sinkut', + MONIKULTTUURISET = 'monikulttuuriset', + KAUNIIT = 'kauniit', + JÄRJESTELMÄLLISET = 'järjestelmälliset', + EPÄJÄRJESTELMÄLLISET = 'epäjärjestelmälliset', + TASA_ARVOISET = 'tasa-arvoiset', + SOMEPERSOONAT = 'somepersoonat', + KÄDENTAITAJAT = 'kädentaitajat', + MUUSIKOT = 'muusikot', + TAITEILIJAT = 'taiteilijat', + SPÄMMÄÄJÄT = 'spämmääjät', + KASVISSYÖJÄT = 'kasvissyöjät', + TASAPAINOISET = 'tasapainoiset' +} diff --git a/src/common/decorator/response/UniformResponse.ts b/src/common/decorator/response/UniformResponse.ts index 63cbd94a..a935d7f5 100644 --- a/src/common/decorator/response/UniformResponse.ts +++ b/src/common/decorator/response/UniformResponse.ts @@ -3,6 +3,7 @@ import { ValidationExceptionFilter } from '../../exceptionFilter/ValidationExcep import { FormatAPIResponse } from './FormatAPIResponse'; import { ModelName } from '../../enum/modelName.enum'; import { Send204OnEmptyRes } from '../../interceptor/response/Send204OnEmptyRes'; +import { APIErrorFilter } from '../../exceptionFilter/APIErrorFilter'; /** * Uniform response sent to the client side as follows @@ -18,7 +19,7 @@ import { Send204OnEmptyRes } from '../../interceptor/response/Send204OnEmptyRes' export function UniformResponse(modelName?: ModelName) { const decorators = [ Send204OnEmptyRes(), - UseFilters(new ValidationExceptionFilter()), + UseFilters(new ValidationExceptionFilter(), new APIErrorFilter()), FormatAPIResponse(modelName) ]; diff --git a/src/common/enum/modelName.enum.ts b/src/common/enum/modelName.enum.ts index a6b29551..e18cc6b4 100644 --- a/src/common/enum/modelName.enum.ts +++ b/src/common/enum/modelName.enum.ts @@ -30,5 +30,6 @@ export enum ModelName { ROOM = 'Room', SOULHOME = 'SoulHome', CLANVOTE = 'ClanVote', - ITEMSHOP = 'ItemShop' + ITEMSHOP = 'ItemShop', + GAME = 'Game', } \ No newline at end of file diff --git a/src/common/exceptionFilter/APIErrorFilter.ts b/src/common/exceptionFilter/APIErrorFilter.ts new file mode 100644 index 00000000..4a377790 --- /dev/null +++ b/src/common/exceptionFilter/APIErrorFilter.ts @@ -0,0 +1,21 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost +} from '@nestjs/common'; +import { Response } from 'express'; +import { APIError } from '../controller/APIError'; + +/** + * Error filter to handle thrown APIError. + * + * Filter will return APIError as it is to client + */ +@Catch(APIError) +export class APIErrorFilter implements ExceptionFilter { + catch(exception: APIError, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + response.status(exception.statusCode).json(exception); + } +} \ No newline at end of file diff --git a/src/gameAnalytics/FileValidation.filter.ts b/src/gameAnalytics/FileValidation.filter.ts new file mode 100644 index 00000000..5a2ab5d9 --- /dev/null +++ b/src/gameAnalytics/FileValidation.filter.ts @@ -0,0 +1,28 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from "@nestjs/common"; +import { APIError } from "../common/controller/APIError"; +import { APIErrorReason } from "../common/controller/APIErrorReason"; +import { Response } from 'express'; + +/** + * Error filter to handle ValidationErrors thrown by FileInterceptor. + * + * Filter will convert generic BadRequestException to APIError with reason NOT_ALLOWED + * as well as send an array with occurred APIError to the client side + */ +@Catch(BadRequestException) +export class FileValidationFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + response.status(400).json({ + statusCode: 400, + errors: [ + new APIError({ + reason: APIErrorReason.NOT_ALLOWED, + message: 'The field name containing the file must be "logFile"' + }) + ] + }); + } +} \ No newline at end of file diff --git a/src/gameAnalytics/SecretHeader.decorator.ts b/src/gameAnalytics/SecretHeader.decorator.ts new file mode 100644 index 00000000..c25a0cd1 --- /dev/null +++ b/src/gameAnalytics/SecretHeader.decorator.ts @@ -0,0 +1,11 @@ +import { createParamDecorator, ExecutionContext } from "@nestjs/common"; + +/** + * Get the `Secret` field from the request header. + */ +export const SecretHeader = createParamDecorator((data: unknown, context: ExecutionContext): string => { + const request = context.switchToHttp().getRequest(); + const secret = request.headers['secret'] as string; + + return secret; +}); \ No newline at end of file diff --git a/src/gameAnalytics/gameAnalytics.controller.ts b/src/gameAnalytics/gameAnalytics.controller.ts new file mode 100644 index 00000000..c311b1b5 --- /dev/null +++ b/src/gameAnalytics/gameAnalytics.controller.ts @@ -0,0 +1,48 @@ +import {Controller, Post, UploadedFile, UseFilters, UseInterceptors} from "@nestjs/common"; +import {_idDto} from "../common/dto/_id.dto"; +import { FileInterceptor } from "@nestjs/platform-express"; +import { FileValidationFilter } from "./FileValidation.filter"; +import { UniformResponse } from "../common/decorator/response/UniformResponse"; +import { SecretHeader } from "./SecretHeader.decorator"; +import { LoggedUser } from "../common/decorator/param/LoggedUser.decorator"; +import { User } from "../auth/user"; +import { APIError } from "../common/controller/APIError"; +import { APIErrorReason } from "../common/controller/APIErrorReason"; +import { LogFileService } from "./logFile.service"; + +@Controller('gameAnalytics') +export class GameAnalyticsController { + public constructor( + private readonly logFileService: LogFileService + ) { + } + + @Post('/logFile') + @UseFilters(new FileValidationFilter()) + @UseInterceptors(FileInterceptor('logFile')) + @UniformResponse() + async uploadFile( + @UploadedFile() file: Express.Multer.File, + @SecretHeader() secret: string, + @LoggedUser() user: User + ) { + if(!secret) + return [null, [ + new APIError({reason: APIErrorReason.REQUIRED, message: 'The "Secret" header is required'}) + ]]; + + if(secret !== 'my_secret') + return [null, [ + new APIError({reason: APIErrorReason.NOT_AUTHORIZED, message: 'The "Secret" header is not valid'}) + ]]; + + if(!file) + return [null, [ + new APIError({reason: APIErrorReason.REQUIRED, message: 'Could not define a file sent. The file is required'}) + ]]; + + const [resp, errors] = await this.logFileService.saveFile(file, user.player_id); + if(errors) + return [null, errors]; + } +} \ No newline at end of file diff --git a/src/gameAnalytics/gameAnalytics.module.ts b/src/gameAnalytics/gameAnalytics.module.ts new file mode 100644 index 00000000..693061dc --- /dev/null +++ b/src/gameAnalytics/gameAnalytics.module.ts @@ -0,0 +1,16 @@ +import {Module} from '@nestjs/common'; +import {RequestHelperModule} from "../requestHelper/requestHelper.module"; +import {GameAnalyticsController} from "./gameAnalytics.controller"; +import {LogFileService} from "./logFile.service"; +import { ItemModule } from '../item/item.module'; + +@Module({ + imports: [ + RequestHelperModule, + ItemModule + ], + controllers: [GameAnalyticsController], + providers: [ LogFileService ], + exports: [ ] +}) +export class GameAnalyticsModule {} \ No newline at end of file diff --git a/src/gameAnalytics/logFile.service.ts b/src/gameAnalytics/logFile.service.ts new file mode 100644 index 00000000..5b8c8c22 --- /dev/null +++ b/src/gameAnalytics/logFile.service.ts @@ -0,0 +1,153 @@ +import {Injectable} from "@nestjs/common"; +import ServiceError from "../common/service/basicService/ServiceError"; +import { SEReason } from "../common/service/basicService/SEReason"; +import { createClient, WebDAVClient } from "webdav"; + +@Injectable() +export class LogFileService { + constructor() { + this.initializeWebDavClient(); + } + private client: WebDAVClient; + + private readonly logFilesRootFolder = process.env.OWNCLOUD_LOG_FILES_ROOT; + //private readonly logFilesRootFolder = '/log-files'; + + /** + * Saves the provided file to the own cloud via WebDAV in the designated folder. + * + * @param fileToSave - The file to save. + * @param player_id - The player's unique identifier to be included in the file name. + * @returns A tuple with first element set to _true_ if file was saved + * + * or _array of ServiceErrors_ as a second element + */ + async saveFile(fileToSave: Express.Multer.File, player_id: string){ + const folderPath = this.getFolderPath(); + const filePath = this.getFilePath(player_id); + + try { + const isFileFolderExist = await this.client.exists(folderPath); + if(!isFileFolderExist) + //Notice that the "logFilesRootFolder" folder must be already created manually in own cloud + await this.client.createDirectory(folderPath, {recursive: false}); + } catch (error) { + return [null, [ + new ServiceError({ + reason: SEReason.UNEXPECTED, + message: 'Unexpected error happen during folder creation', + additional: this.getWebDavErrorData(error) + }) + ]]; + } + + try { + const isSuccess = await this.client.putFileContents(filePath, fileToSave.buffer, { + overwrite: true + }); + + if(!isSuccess) + return [ null, [ + new ServiceError({ + reason: SEReason.UNEXPECTED, + message: 'Could not save the log file' + }) + ]]; + + return [true, null]; + } catch (error: any) { + return [null, [ + new ServiceError({ + reason: SEReason.UNEXPECTED, + message: 'Unexpected error happen during file saving', + additional: this.getWebDavErrorData(error) + }) + ]]; + } + } + + /** + * Initializes the WebDAV client using credentials from the environment variables. + */ + private initializeWebDavClient() { + this.client = createClient( + `http://${process.env.OWNCLOUD_HOST}:${process.env.OWNCLOUD_PORT}/remote.php/webdav/`, + { + username: process.env.OWNCLOUD_USER, + password: process.env.OWNCLOUD_PASSWORD + } + ); + } + + /** + * Extracts the error data from the WebDAV error response. + * + * @param error - The error object to extract information from. + * @returns The response data from the WebDAV error, or null if not available. + */ + private getWebDavErrorData(error: any){ + return error?.response?.data ?? null; + } + + /** + * Constructs the full path to the folder where log files will be stored. + * + * @returns The full folder path as a string. + */ + private getFolderPath(){ + const folderName = this.getFolderName(); + return `${this.logFilesRootFolder}/${folderName}`; + } + /** + * Constructs the full path to the file, including the folder and the file name. + * + * @param player_id - The player's unique identifier to be included in the file name. + * @returns The full file path. + */ + private getFilePath(player_id: string){ + const folderPath = this.getFolderPath(); + const fileName = this.getFileName(player_id); + return `${folderPath}/${fileName}`; + } + + /** + * Generates a folder name based on the current date. + * + * @returns A string representing the folder name, formatted as DD-MM-YYYY. + */ + private getFolderName(){ + return this.getDateString(); + } + /** + * Generates a file name based on the current date, time, player _id, and a random string. + * + * @param player_id - The player's unique identifier to be included in the file name. + * @returns The file name as a string, formatted as DD-MM-YYYY_HH-MM-SS_playerID_random.log. + */ + private getFileName(player_id: string){ + const dateString = this.getDateString(); + const timeString = this.getTimeString(); + const randomString = Math.floor(Math.random() * 1000000); + + return `${dateString}_${timeString}_${player_id}_${randomString}.log`; + } + + /** + * Gets the current date as a string formatted as DD-MM-YYYY. + * + * @returns A string representing the current date. + */ + private getDateString(){ + const now = new Date(); + return `${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()}`; + } + /** + * Gets the current time as a string formatted as HH-MM-SS. + * + * @returns A string representing the current time. + */ + private getTimeString(){ + const now = new Date(); + return `${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; + } +} \ No newline at end of file diff --git a/src/gameData/dto/battleResponse.dto.ts b/src/gameData/dto/battleResponse.dto.ts new file mode 100644 index 00000000..ecfed451 --- /dev/null +++ b/src/gameData/dto/battleResponse.dto.ts @@ -0,0 +1,5 @@ +export type BattleResponseDto = { + stealToken: string; + soulHome_id: string; + roomIds: string[]; +} \ No newline at end of file diff --git a/src/gameData/dto/battleResult.dto.ts b/src/gameData/dto/battleResult.dto.ts new file mode 100644 index 00000000..fc7cc426 --- /dev/null +++ b/src/gameData/dto/battleResult.dto.ts @@ -0,0 +1,24 @@ +import { IsArray, IsEnum, IsInt, IsMongoId, IsPositive, IsString, Matches, Max, Min } from "class-validator"; +import { RequestType } from "../enum/requestType.enum"; + +export class BattleResultDto { + @IsEnum(RequestType) + type: RequestType.RESULT; + + @IsArray() + @IsMongoId({ each: true }) + team1: string[]; + + @IsArray() + @IsMongoId({ each: true }) + team2: string[]; + + @IsInt() + @IsPositive() + duration: number; + + @IsInt() + @Min(1) + @Max(2) + winnerTeam: number; +} \ No newline at end of file diff --git a/src/gameData/dto/createGame.dto.ts b/src/gameData/dto/createGame.dto.ts new file mode 100644 index 00000000..dcbcbd96 --- /dev/null +++ b/src/gameData/dto/createGame.dto.ts @@ -0,0 +1,37 @@ +import { IsArray, IsMongoId, IsNumber, IsEnum, IsDate, IsNotEmpty } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class CreateGameDto { + @IsArray() + @IsMongoId({ each: true }) + @IsNotEmpty() + team1: string[]; + + @IsArray() + @IsMongoId({ each: true }) + @IsNotEmpty() + team2: string[]; + + @IsMongoId() + @IsNotEmpty() + team1Clan: string; + + @IsMongoId() + @IsNotEmpty() + team2Clan: string; + + @IsNumber() + @IsEnum([1, 2]) + @IsNotEmpty() + winner: number; + + @IsDate() + @Type(() => Date) + @IsNotEmpty() + startedAt: Date; + + @IsDate() + @Type(() => Date) + @IsNotEmpty() + endedAt: Date; +} diff --git a/src/gameData/dto/game.dto.ts b/src/gameData/dto/game.dto.ts new file mode 100644 index 00000000..d00dec51 --- /dev/null +++ b/src/gameData/dto/game.dto.ts @@ -0,0 +1,40 @@ +import { IsArray, IsDate, IsEnum, IsMongoId, IsNotEmpty } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class GameDto { + @IsMongoId() + @IsNotEmpty() + _id: string; + + @IsArray() + @IsMongoId({ each: true }) + @IsNotEmpty() + team1: string[]; + + @IsArray() + @IsMongoId({ each: true }) + @IsNotEmpty() + team2: string[]; + + @IsMongoId() + @IsNotEmpty() + team1Clan: string; + + @IsMongoId() + @IsNotEmpty() + team2Clan: string; + + @IsEnum([1, 2]) + @IsNotEmpty() + winner: number; + + @IsDate() + @Type(() => Date) + @IsNotEmpty() + startedAt: Date; + + @IsDate() + @Type(() => Date) + @IsNotEmpty() + endedAt: Date; + } \ No newline at end of file diff --git a/src/gameData/dto/resultType.dto.ts b/src/gameData/dto/resultType.dto.ts new file mode 100644 index 00000000..2746efc9 --- /dev/null +++ b/src/gameData/dto/resultType.dto.ts @@ -0,0 +1,7 @@ +import { IsEnum } from "class-validator"; +import { RequestType } from "../enum/requestType.enum"; + +export class RequestTypeDto { + @IsEnum(RequestType) + type: RequestType; +} \ No newline at end of file diff --git a/src/gameData/enum/requestType.enum.ts b/src/gameData/enum/requestType.enum.ts new file mode 100644 index 00000000..0db72ee8 --- /dev/null +++ b/src/gameData/enum/requestType.enum.ts @@ -0,0 +1,3 @@ +export enum RequestType { + RESULT = "result", +} \ No newline at end of file diff --git a/src/gameData/game.schema.ts b/src/gameData/game.schema.ts new file mode 100644 index 00000000..39a0431b --- /dev/null +++ b/src/gameData/game.schema.ts @@ -0,0 +1,57 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; +import { ModelName } from '../common/enum/modelName.enum'; +import { Player } from '../player/player.schema'; +import { ExtractField } from '../common/decorator/response/ExtractField'; + +export type GameDocument = HydratedDocument; + +@Schema({ toJSON: { virtuals: true }, toObject: { virtuals: true } }) +export class Game { + @Prop({ type: [MongooseSchema.Types.ObjectId], required: true, ref: ModelName.PLAYER }) + team1: string[]; + + @Prop({ type: [MongooseSchema.Types.ObjectId], required: true, ref: ModelName.PLAYER }) + team2: string[]; + + @Prop({ type: MongooseSchema.Types.ObjectId, required: true, ref: ModelName.CLAN }) + team1Clan: string; + + @Prop({ type: MongooseSchema.Types.ObjectId, required: true, ref: ModelName.CLAN }) + team2Clan: string; + + @Prop({ type: Number, enum: [1, 2], required: true }) + winner: number; + + @Prop ({ type: Date, required: true }) + startedAt: Date; + + @Prop ({ type: Date, required: true }) + endedAt: Date; + + @ExtractField() + _id: string; +} + +export const GameSchema = SchemaFactory.createForClass(Game); +GameSchema.set('collection', ModelName.GAME); +GameSchema.virtual(ModelName.PLAYER + '1', { + ref: ModelName.PLAYER, + localField: 'team1', + foreignField: '_id', +}); +GameSchema.virtual(ModelName.PLAYER + '2', { + ref: ModelName.PLAYER, + localField: 'team2', + foreignField: '_id', +}); +GameSchema.virtual(ModelName.CLAN + '1', { + ref: ModelName.CLAN, + localField: 'team1Clan', + foreignField: '_id', +}); +GameSchema.virtual(ModelName.CLAN + '2', { + ref: ModelName.CLAN, + localField: 'team2Clan', + foreignField: '_id', +}); \ No newline at end of file diff --git a/src/gameData/gameData.controller.ts b/src/gameData/gameData.controller.ts new file mode 100644 index 00000000..acfc832b --- /dev/null +++ b/src/gameData/gameData.controller.ts @@ -0,0 +1,34 @@ +import { Body, Controller, ForbiddenException, HttpException, Post } from '@nestjs/common'; +import { GameDataService } from './gameData.service'; +import { LoggedUser } from '../common/decorator/param/LoggedUser.decorator'; +import { User } from '../auth/user'; +import { UniformResponse } from '../common/decorator/response/UniformResponse'; +import { APIError } from '../common/controller/APIError'; +import { APIErrorReason } from '../common/controller/APIErrorReason'; +import { validate } from 'class-validator'; +import { RequestType } from './enum/requestType.enum'; +import { RequestTypeDto } from './dto/resultType.dto'; + +@Controller('gameData') +export class GameDataController { + constructor( + private readonly service: GameDataService, + ){} + + @Post('battle') + @UniformResponse() + async handleBattleResult(@Body() body: any, @LoggedUser() user: User) { + const typeDto = new RequestTypeDto + typeDto.type = body.type + const errors = await validate(typeDto); + if (errors.length > 0) + return new APIError({ reason: APIErrorReason.WRONG_ENUM, message: "Invalid type" }); + + switch (typeDto.type) { + case RequestType.RESULT: + return await this.service.handleResultType(body, user) + default: + return new APIError({ reason: APIErrorReason.BAD_REQUEST }) + } + } +} diff --git a/src/gameData/gameData.module.ts b/src/gameData/gameData.module.ts new file mode 100644 index 00000000..55a8d0b2 --- /dev/null +++ b/src/gameData/gameData.module.ts @@ -0,0 +1,21 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { GameDataService } from './gameData.service'; +import { GameDataController } from './gameData.controller'; +import { MongooseModule } from '@nestjs/mongoose'; +import { Game, GameSchema } from './game.schema'; +import { PlayerModule } from '../player/player.module'; +import { ClanModule } from '../clan/clan.module'; +import { RoomModule } from '../room/room.module'; +import { JwtModule } from '@nestjs/jwt'; + +@Module({ + imports: [ + MongooseModule.forFeature([ { name: Game.name, schema: GameSchema } ]), + forwardRef(() => PlayerModule), + forwardRef(() => ClanModule), + forwardRef(() => RoomModule), + ], + providers: [GameDataService], + controllers: [GameDataController] +}) +export class GameDataModule {} diff --git a/src/gameData/gameData.service.ts b/src/gameData/gameData.service.ts new file mode 100644 index 00000000..b9202b01 --- /dev/null +++ b/src/gameData/gameData.service.ts @@ -0,0 +1,223 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Game } from './game.schema'; +import { Model } from 'mongoose'; +import BasicService from '../common/service/basicService/BasicService'; +import { CreateGameDto } from './dto/createGame.dto'; +import { PlayerService } from '../player/player.service'; +import ServiceError from '../common/service/basicService/ServiceError'; +import { JwtService } from '@nestjs/jwt'; +import { ClanService } from '../clan/clan.service'; +import { RoomService } from '../room/room.service'; +import { ModelName } from '../common/enum/modelName.enum'; +import { BattleResultDto } from './dto/battleResult.dto'; +import { User } from '../auth/user'; +import { GameDto } from './dto/game.dto'; +import { BattleResponseDto } from './dto/battleResponse.dto'; +import { APIError } from '../common/controller/APIError'; +import { APIErrorReason } from '../common/controller/APIErrorReason'; + +@Injectable() +export class GameDataService { + constructor( + @InjectModel(Game.name) public readonly model: Model, + @Inject(forwardRef(() => PlayerService)) public readonly playerService: PlayerService, + @Inject(forwardRef(() => ClanService)) public readonly clanService: ClanService, + @Inject(forwardRef(() => RoomService)) public readonly roomService: RoomService, + private readonly jwtService: JwtService, + ){ + this.basicService = new BasicService(model); + this.refsInModel = [ModelName.STOCK]; + this.modelName = ModelName.ITEM; + } + + public readonly basicService: BasicService; + public readonly refsInModel: ModelName[]; + public readonly modelName: ModelName; + + /** + * Creates a new game in DB. + * + * @param game - Game data to create + * @returns created Item or an array of service errors if any occurred. + */ + private async createOne(game: CreateGameDto) { + return await this.basicService.createOne(game) + } + + /** + * Creates a new game object based on the provided battle result and team information. + * + * @param battleResult - The battle result data transfer object containing details of the battle. + * @param team1Id - The identifier for team 1's clan. + * @param team2Id - The identifier for team 2's clan. + * @param currentTime - The current date and time when the game is being created. + * @returns A new game data transfer object ready to be saved in the database. + */ + private createNewGameObject(battleResult: BattleResultDto, team1Id: string, team2Id: string, currentTime: Date) { + const newGame: CreateGameDto = { + team1: battleResult.team1, + team2: battleResult.team2, + team1Clan: team1Id, + team2Clan: team2Id, + winner: battleResult.winnerTeam, + startedAt: new Date(currentTime.getTime() - battleResult.duration * 1000), + endedAt: currentTime + } + return newGame + } + + /** + * Generates a response for the battle result, including a steal token, SoulHome ID, and room IDs. + * + * @param battleResult - The battle result data transfer object. + * @param team1ClanId - The identifier for team 1's clan. + * @param team2ClanId - The identifier for team 2's clan. + * @param user - The user who is submitting the battle result. + * @returns - A promise that resolves to an array containing the response object and any service errors. + */ + private async generateResponse(battleResult: BattleResultDto, team1ClanId: string, team2ClanId: string, user: User): Promise<[BattleResponseDto, ServiceError[]]> { + const [clan, errors] = await this.clanService.readOneById(battleResult.winnerTeam === 1 ? team2ClanId : team1ClanId, { includeRefs: [ModelName.SOULHOME] }); + if (errors) { + return [null, errors] + } + const [roomIds, roomErrors] = await this.getRoomIds(clan.SoulHome._id); + if (roomErrors) { + return [null, errors] + } + + const stealToken = await this.generateStealToken(user.player_id, clan.SoulHome._id); + const response: BattleResponseDto = { + stealToken, + soulHome_id: clan.SoulHome._id, + roomIds + } + return [response, null] + } + + /** + * Checks if a game with given information exists in DB. + * + * This function uses data from the client request which doesn't + * include any uniquely identifiable data. This is why the function + * tries to find the latest game between these teams and if the + * game found is older then 30 seconds it returns false. + * This number can be changed based on how long the games and + * requests from the client to server take. + * + * @param team1 - Player ids of team1 players + * @param team2 - Player ids of team2 players + * @param currentTime - Current time + * @returns - Returns a promise that resolves to true in a game exists, otherwise false + */ + private async gameAlreadyExists(team1: string[], team2: string[], currentTime: Date) { + const game = await this.model.findOne({ + team1: { $all: team1 }, + team2: { $all: team2 }, + }).sort({ endedAt: -1 }).exec(); + + if (!game) + return false; + + if (game.endedAt.getTime() < currentTime.getTime() - 30 * 1000) + return false; + + return true + } + + /** + * Retrieves the clan IDs for the given player IDs. + * + * @param playerIds - An array containing the player IDs of the first player from each team. + * @returns - A promise that resolves to an object containing the clan IDs for both teams and any service errors. + */ + private async getClanIdForTeams(playerIds: string[]): Promise<[{team1Id: string, team2Id: string}, ServiceError[]]> { + const [team1Player, team1Errors] = await this.playerService.getPlayerById(playerIds[0]); + if (team1Errors) { + return [null, team1Errors]; + } + + const [team2Player, team2Errors] = await this.playerService.getPlayerById(playerIds[1]); + if (team2Errors) { + return [null, team2Errors]; + } + + const clanIds = { + team1Id: team1Player.clan_id.toString(), + team2Id: team2Player.clan_id.toString() + }; + return [clanIds, null]; + } + + + /** + * Generates a steal token for a winning player. + * + * @param playerId - The ID of the winning player. + * @param soulHomeId - The ID of the losing team SoulHome. + * @returns - A promise that resolves to the generated steal token. + */ + private async generateStealToken(playerId: string, soulHomeId: string): Promise { + return await this.jwtService.signAsync( + { playerId, soulHomeId }, + { expiresIn: '15m' } + ); + } + + /** + * Retrieves the IDs of all rooms associated with a given SoulHome. + * + * @param soulHomeId - The ID of the SoulHome. + * @returns - A promise that resolves to an array containing the room IDs and any service errors. + */ + private async getRoomIds(soulHomeId: string): Promise<[string[], ServiceError[]]> { + const [rooms, roomErrors] = await this.roomService.readAllSoulHomeRooms(soulHomeId); + if (roomErrors) { + console.log("getRoomIds:", roomErrors); + return [null, roomErrors]; + } + + const roomIds = rooms.map((room) => room._id); + return [roomIds, null]; + } + + /** + * Checks if a game already exists and creates a new game if not. + * + * @param battleResult - The battle result data transfer object. + * @param teamIds - The clan IDs for both teams. + * @param currentTime - The current time. + */ + private async createGameIfNotExists(battleResult: BattleResultDto, teamIds: { team1Id: string, team2Id: string }, currentTime: Date) { + const existingGame = await this.gameAlreadyExists(battleResult.team1, battleResult.team2, currentTime); + if (!existingGame) { + const newGame = this.createNewGameObject(battleResult, teamIds.team1Id, teamIds.team2Id, currentTime); + const [_, createGameErrors] = await this.createOne(newGame); + if (createGameErrors) + console.error("Error creating new game:", createGameErrors) + } + } + + /** + * Handles the result type request. + * + * @param battleResult - The battleResult of the request containing battle result data. + * @param user - The user making the request. + * @returns - Returns a promise that resolves to the response or an API error. + */ + async handleResultType(battleResult: BattleResultDto, user: User): Promise { + const currentTime = new Date(); + const winningTeam = battleResult.winnerTeam === 1 ? battleResult.team1 : battleResult.team2; + const playerInWinningTeam = winningTeam.includes(user.player_id); + if (!playerInWinningTeam) + return new APIError({ reason: APIErrorReason.NOT_AUTHORIZED, message: "Invalid type field" }); + + const [teamIds, teamIdsErrors] = await this.getClanIdForTeams([battleResult.team1[0], battleResult.team2[0]]); + if (teamIdsErrors) + return teamIdsErrors + + this.createGameIfNotExists(battleResult, teamIds, currentTime); + + return await this.generateResponse(battleResult, teamIds.team1Id, teamIds.team2Id, user) + } +} diff --git a/src/item/decorator/param/StealToken.decorator.ts b/src/item/decorator/param/StealToken.decorator.ts new file mode 100644 index 00000000..c9cc784e --- /dev/null +++ b/src/item/decorator/param/StealToken.decorator.ts @@ -0,0 +1,28 @@ +import { createParamDecorator, ExecutionContext } from "@nestjs/common"; +import { APIError } from "../../../common/controller/APIError"; +import { APIErrorReason } from "../../../common/controller/APIErrorReason"; + +/** + * Custom decorator to extract the steal token from the request. + * + * @param context - The execution context of the request. + * @returns - The steal token from the request. + * @throws - If the steal token is not provided. + */ +export const StealToken = createParamDecorator( + (_, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest(); + const stealToken = request["steal_token"]; + + if (!stealToken) { + throw new APIError({ + reason: APIErrorReason.MISCONFIGURED, + field: "steal_token", + value: stealToken, + message: "The steal token not provided", + }); + } + + return stealToken; + }, +); diff --git a/src/item/dto/moveItem.dto.ts b/src/item/dto/moveItem.dto.ts new file mode 100644 index 00000000..192559f4 --- /dev/null +++ b/src/item/dto/moveItem.dto.ts @@ -0,0 +1,17 @@ +import { IsMongoId, IsString, IsEnum, ValidateIf } from "class-validator"; +import AddType from "../../common/base/decorator/AddType.decorator"; +import { MoveTo } from "../enum/moveTo.enum"; + +@AddType('MoveItemDto') +export class MoveItemDto { + @IsMongoId() + @IsString() + item_id: string; + + @IsEnum(MoveTo) + moveTo: MoveTo; + + @ValidateIf(o => o.move_to === MoveTo.ROOM) + @IsMongoId() + destination_id: string; +} \ No newline at end of file diff --git a/src/item/dto/stealItems.dto.ts b/src/item/dto/stealItems.dto.ts new file mode 100644 index 00000000..f42b542d --- /dev/null +++ b/src/item/dto/stealItems.dto.ts @@ -0,0 +1,14 @@ +import { ArrayNotEmpty, IsArray, IsMongoId, IsString } from "class-validator"; + +export class StealItemsDto { + @IsString() + steal_token: string; + + @IsArray() + @ArrayNotEmpty() + @IsMongoId({ each: true }) + item_ids: string[]; + + @IsMongoId() + room_id: string; +} \ No newline at end of file diff --git a/src/item/enum/moveTo.enum.ts b/src/item/enum/moveTo.enum.ts new file mode 100644 index 00000000..4c0b647a --- /dev/null +++ b/src/item/enum/moveTo.enum.ts @@ -0,0 +1,4 @@ +export enum MoveTo { + STOCK = "Stock", + ROOM = "Room" +} \ No newline at end of file diff --git a/src/item/errors/item.errors.ts b/src/item/errors/item.errors.ts new file mode 100644 index 00000000..cb51357a --- /dev/null +++ b/src/item/errors/item.errors.ts @@ -0,0 +1,7 @@ +import { SEReason } from "../../common/service/basicService/SEReason"; +import ServiceError from "../../common/service/basicService/ServiceError"; + +export const NotFoundError = new ServiceError({ + reason: SEReason.NOT_FOUND, + message: "Item or destination doesn't belong you your clan", +}) \ No newline at end of file diff --git a/src/item/errors/playerId.errors.ts b/src/item/errors/playerId.errors.ts new file mode 100644 index 00000000..fc1aecfb --- /dev/null +++ b/src/item/errors/playerId.errors.ts @@ -0,0 +1,8 @@ +import { APIError } from "../../common/controller/APIError"; +import { APIErrorReason } from "../../common/controller/APIErrorReason"; + +export const IdMismatchError = new APIError({ + reason: APIErrorReason.NOT_AUTHORIZED, + message: "Steal token doesn't belong to the logged in user", + field: "player_id" +}) \ No newline at end of file diff --git a/src/item/guards/StealToken.guard.ts b/src/item/guards/StealToken.guard.ts new file mode 100644 index 00000000..be9adefe --- /dev/null +++ b/src/item/guards/StealToken.guard.ts @@ -0,0 +1,39 @@ +import { + CanActivate, + ExecutionContext, + Injectable, +} from "@nestjs/common"; +import { AuthService } from "../../auth/auth.service"; +import { APIError } from "../../common/controller/APIError"; +import { APIErrorReason } from "../../common/controller/APIErrorReason"; + +@Injectable() +export class StealTokenGuard implements CanActivate { + constructor(private readonly authService: AuthService) { } + + /** + * Verifies that the request has a valid steal token attached. + * + * @param context - The execution context of the request. + * @returns - A promise that resolves to a boolean indicating whether the request is authorized. + * @throws - If the steal_token is missing or invalid. + */ + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + + const stealToken = request.query.steal_token ?? request.body.steal_token; + + if (!stealToken) { + throw new APIError({ + reason: APIErrorReason.NOT_AUTHORIZED, + message: "steal_token is not provided", + field: "steal_token", + value: stealToken + }); + } + + const decoded = await this.authService.verifyToken(stealToken); + request.steal_token = decoded; // Attach the decoded token to the request object + return true; + } +} diff --git a/src/item/item.controller.ts b/src/item/item.controller.ts index bf854d2d..ece2856d 100644 --- a/src/item/item.controller.ts +++ b/src/item/item.controller.ts @@ -1,21 +1,64 @@ -import {Controller, Get, Param} from "@nestjs/common"; -import {_idDto} from "../common/dto/_id.dto"; -import {ModelName} from "../common/enum/modelName.enum"; -import {ItemService} from "./item.service"; -import {ItemDto} from "./dto/item.dto"; +import { Body, Controller, Get, Param, Post, UseGuards } from "@nestjs/common"; +import { _idDto } from "../common/dto/_id.dto"; +import { ModelName } from "../common/enum/modelName.enum"; +import { ItemService } from "./item.service"; +import { ItemDto } from "./dto/item.dto"; import { Authorize } from "../authorization/decorator/Authorize"; import { Action } from "../authorization/enum/action.enum"; import { UniformResponse } from "../common/decorator/response/UniformResponse"; +import { MoveItemDto } from "./dto/moveItem.dto"; +import { LoggedUser } from "../common/decorator/param/LoggedUser.decorator"; +import { User } from "../auth/user"; +import { StealTokenGuard } from "./guards/StealToken.guard"; +import { StealToken } from "./decorator/param/StealToken.decorator"; +import { StealToken as stealToken } from "./type/stealToken.type"; +import { SoulHomeService } from "../soulhome/soulhome.service"; +import { IdMismatchError } from "./errors/playerId.errors"; +import { SoulHomeDto } from "../soulhome/dto/soulhome.dto"; +import { StealItemsDto } from "./dto/stealItems.dto"; +import { ItemMoverService } from "./itemMover.service"; -@Controller('item') +@Controller("item") export class ItemController { - public constructor(private readonly service: ItemService) { - } + public constructor( + private readonly itemService: ItemService, + private readonly itemMoverService: ItemMoverService, + private readonly soulHomeService: SoulHomeService, + ) {} - @Get('/:_id') - @Authorize({action: Action.read, subject: ItemDto}) - @UniformResponse(ModelName.ITEM) - public get(@Param() param: _idDto) { - return this.service.readOneById(param._id); - } -} \ No newline at end of file + @Get("steal") + @Authorize({ action: Action.read, subject: SoulHomeDto }) + @UseGuards(StealTokenGuard) + @UniformResponse(ModelName.SOULHOME) + public async getSoulHome(@StealToken() stealToken: stealToken, @LoggedUser() user: User) { + if (stealToken.playerId !== user.player_id) + throw IdMismatchError; + + return await this.soulHomeService.readOneById(stealToken.soulHomeId, { includeRefs: [ModelName.ROOM] }); + } + + @Get("/:_id") + @Authorize({ action: Action.read, subject: ItemDto }) + @UniformResponse(ModelName.ITEM) + public get(@Param() param: _idDto) { + return this.itemService.readOneById(param._id); + } + + @Post("/move") + @UniformResponse() + public async moveItems(@Body() body: MoveItemDto, @LoggedUser() user: User) { + const [_, errors] = await this.itemMoverService.moveItem(body.item_id, body.destination_id, body.moveTo, user.player_id); + if (errors) + return errors; + } + + @Post("steal") + @UseGuards(StealTokenGuard) + @UniformResponse(ModelName.ITEM) + public async stealItems(@Body() body: StealItemsDto, @StealToken() stealToken: stealToken, @LoggedUser() user: User) { + if (user.player_id !== stealToken.playerId) + throw IdMismatchError + + return await this.itemMoverService.stealItems(body.item_ids, stealToken, body.room_id); + } +} diff --git a/src/item/item.module.ts b/src/item/item.module.ts index f3416d96..dae31170 100644 --- a/src/item/item.module.ts +++ b/src/item/item.module.ts @@ -1,4 +1,4 @@ -import {Module} from '@nestjs/common'; +import {forwardRef, Module} from '@nestjs/common'; import {MongooseModule} from '@nestjs/mongoose'; import {ItemSchema} from "./item.schema"; import {RequestHelperModule} from "../requestHelper/requestHelper.module"; @@ -6,14 +6,28 @@ import {ModelName} from "../common/enum/modelName.enum"; import {ItemController} from "./item.controller"; import {ItemService} from "./item.service"; import {isItemExists} from "./decorator/validation/IsItemExists.decorator"; +import { StockModule } from '../stock/stock.module'; +import { ClanModule } from '../clan/clan.module'; +import { RoomModule } from '../room/room.module'; +import { PlayerModule } from '../player/player.module'; +import { AuthModule } from '../auth/auth.module'; +import { SoulHomeModule } from '../soulhome/soulhome.module'; +import { ItemHelperService } from './itemHelper.service'; +import { ItemMoverService } from './itemMover.service'; @Module({ imports: [ MongooseModule.forFeature([ {name: ModelName.ITEM, schema: ItemSchema} ]), - RequestHelperModule + RequestHelperModule, + forwardRef(() => StockModule), + forwardRef(() => ClanModule), + forwardRef(() => RoomModule), + forwardRef(() => PlayerModule), + forwardRef(() => SoulHomeModule), + AuthModule, ], controllers: [ItemController], - providers: [ ItemService, isItemExists ], + providers: [ItemService, isItemExists, ItemHelperService, ItemMoverService], exports: [ItemService] }) export class ItemModule {} \ No newline at end of file diff --git a/src/item/item.service.ts b/src/item/item.service.ts index 9023cebf..3cb0d9c2 100644 --- a/src/item/item.service.ts +++ b/src/item/item.service.ts @@ -5,108 +5,130 @@ import {ModelName} from "../common/enum/modelName.enum"; import {Item} from "./item.schema"; import {CreateItemDto} from "./dto/createItem.dto"; import {UpdateItemDto} from "./dto/updateItem.dto"; -import {ItemDto} from "./dto/Item.dto"; +import {ItemDto} from "./dto/item.dto"; import BasicService from "../common/service/basicService/BasicService"; -import { TReadByIdOptions } from "../common/service/basicService/IService"; +import { TIServiceCreateManyOptions, TIServiceUpdateManyOptions, TReadByIdOptions } from "../common/service/basicService/IService"; @Injectable() export class ItemService { - public constructor( - @InjectModel(Item.name) public readonly model: Model - ){ - this.refsInModel = [ModelName.STOCK]; - this.modelName = ModelName.ITEM; - this.basicService = new BasicService(model); - } + public constructor( + @InjectModel(Item.name) public readonly model: Model, + ){ + this.refsInModel = [ModelName.STOCK]; + this.modelName = ModelName.ITEM; + this.basicService = new BasicService(model); + } - public readonly refsInModel: ModelName[]; - public readonly modelName: ModelName; - private readonly basicService: BasicService; + public readonly refsInModel: ModelName[]; + public readonly modelName: ModelName; + private readonly basicService: BasicService; - /** - * Creates an new Item in DB. - * - * @param item - The Item data to create. - * @returns created Item or an array of service errors if any occurred. - */ - async createOne(item: CreateItemDto) { - return this.basicService.createOne(item); - } + /** + * Creates an new Item in DB. + * + * @param item - The Item data to create. + * @returns created Item or an array of service errors if any occurred. + */ + async createOne(item: CreateItemDto) { + return this.basicService.createOne(item); + } - /** - * Creates many new Items in DB. - * - * @param items - The Items data to create. - * @returns created Item or an array of service errors if any occurred. - */ - async createMany(items: CreateItemDto[]) { - return this.basicService.createMany(items); - } + /** + * Creates many new Items in DB. + * + * @param items - The Items data to create. + * @returns created Item or an array of service errors if any occurred. + */ + async createMany(items: CreateItemDto[]) { + return this.basicService.createMany(items); + } - /** - * Creates many new Items in DB. - * - * @deprecated The createMany() method should be used instead - * @param items - The Items data to create. - * @returns created Item or an array of service errors if any occurred. - */ - async createManyWithResponse(items: CreateItemDto[]) { - return this.basicService.createMany(items); - } + /** + * Creates many new Items in DB. + * + * @deprecated The createMany() method should be used instead + * @param items - The Items data to create. + * @returns created Item or an array of service errors if any occurred. + */ + async createManyWithResponse(items: CreateItemDto[]) { + return this.basicService.createMany(items); + } - /** - * Reads an Item by its _id in DB. - * - * @param _id - The Mongo _id of the Item to read. - * @param options - Options for reading the Item. - * @returns Item with the given _id on succeed or an array of ServiceErrors if any occurred. - */ - async readOneById(_id: string, options?: TReadByIdOptions) { - let optionsToApply = options; - if(options?.includeRefs) - optionsToApply.includeRefs = options.includeRefs.filter((ref) => this.refsInModel.includes(ref)); - return this.basicService.readOneById(_id, optionsToApply); - } + /** + * Reads an Item by its _id in DB. + * + * @param _id - The Mongo _id of the Item to read. + * @param options - Options for reading the Item. + * @returns Item with the given _id on succeed or an array of ServiceErrors if any occurred. + */ + async readOneById(_id: string, options?: TReadByIdOptions) { + let optionsToApply = options; + if(options?.includeRefs) + optionsToApply.includeRefs = options.includeRefs.filter((ref) => this.refsInModel.includes(ref)); + return this.basicService.readOneById(_id, optionsToApply); + } - /** - * Updates an Item by its _id in DB. The _id field is read-only and must be found from the parameter - * - * @param Item - The data needs to be updated for the Item. - * @returns _true_ if Item was updated successfully, _false_ if nothing was updated for the Item, - * or a ServiceError array if Item was not found or something else went wrong. - */ - async updateOneById(Item: UpdateItemDto) { - const {_id, ...fieldsToUpdate} = Item - return this.basicService.updateOneById(_id, fieldsToUpdate); - } + /** + * Reads multiple items from the database based on the provided options. + * + * @param options - Optional settings for the read operation. + * @returns A promise that resolves to a tuple where the first element is an array of ItemDto objects, and the second element is either null or an array of ServiceError objects if something went wrong. + */ + async readMany(options?: TIServiceCreateManyOptions) { + return this.basicService.readMany(options); + } - /** - * Deletes an Item by its _id from DB. - * - * @param _id - The Mongo _id of the Item to delete. - * @returns _true_ if Item was removed successfully, or a ServiceError array if the Item was not found or something else went wrong - */ - async deleteOneById(_id: string) { - return this.basicService.deleteOneById(_id); - } + /** + * Updates an Item by its _id in DB. The _id field is read-only and must be found from the parameter + * + * @param Item - The data needs to be updated for the Item. + * @returns _true_ if Item was updated successfully, _false_ if nothing was updated for the Item, + * or a ServiceError array if Item was not found or something else went wrong. + */ + async updateOneById(Item: UpdateItemDto) { + const {_id, ...fieldsToUpdate} = Item + return this.basicService.updateOneById(_id, fieldsToUpdate); + } - /** - * Deletes all Items of the specified by _id Stock from DB. - * - * @param stock_id - The Mongo _id of the Stock from which all items should be deleted - * @returns _true_ if Items was removed successfully, or a ServiceError array if any Items of the Stock was not found or something else went wrong - */ - async deleteAllStockItems(stock_id: string) { - return this.basicService.deleteMany({filter: { stock_id }}); - } + /** + * Updates multiple items in the database. + * + * @template T - The type of items to update. + * @param items - The items to update. + * @param options - Optional settings for the update operation. + * @returns A promise that resolves to a tuple where the first element is a boolean indicating if the update was successful, and the second element is either null or an array of ServiceError objects if something went wrong. + */ + async updateMany(items: T[], options?: TIServiceUpdateManyOptions) { + return this.basicService.updateMany(items, options); + } - /** - * Deletes all Items of the specified by _id Room from DB. - * - * @param room_id - The Mongo _id of the Stock from which all items should be deleted - * @returns _true_ if Items was removed successfully, or a ServiceError array if any Items of the Room was not found or something else went wrong - */ - async deleteAllRoomItems(room_id: string) { - return this.basicService.deleteMany({filter: { room_id }}); - } -} \ No newline at end of file + /** + * Deletes an Item by its _id from DB. + * + * @param _id - The Mongo _id of the Item to delete. + * @returns _true_ if Item was removed successfully, or a ServiceError array if the Item was not found or something else went wrong + */ + async deleteOneById(_id: string) { + return this.basicService.deleteOneById(_id); + } + + /** + * Deletes all Items of the specified by _id Stock from DB. + * + * @param stock_id - The Mongo _id of the Stock from which all items should be deleted + * @returns _true_ if Items was removed successfully, or a ServiceError array if any Items of the Stock was not found or something else went wrong + */ + async deleteAllStockItems(stock_id: string) { + return this.basicService.deleteMany({filter: { stock_id }}); + } + + /** + * Deletes all Items of the specified by _id Room from DB. + * + * @param room_id - The Mongo _id of the Stock from which all items should be deleted + * @returns _true_ if Items was removed successfully, or a ServiceError array if any Items of the Room was not found or something else went wrong + */ + async deleteAllRoomItems(room_id: string) { + return this.basicService.deleteMany({filter: { room_id }}); + } +} diff --git a/src/item/itemHelper.service.ts b/src/item/itemHelper.service.ts new file mode 100644 index 00000000..66ea3aa0 --- /dev/null +++ b/src/item/itemHelper.service.ts @@ -0,0 +1,74 @@ +import { forwardRef, Inject, Injectable } from "@nestjs/common"; +import ServiceError from "../common/service/basicService/ServiceError"; +import { ModelName } from "../common/enum/modelName.enum"; +import { ItemService } from "./item.service"; +import { NotFoundError } from "./errors/item.errors"; +import { RoomService } from "../room/room.service"; +import { ClanService } from "../clan/clan.service"; + +@Injectable() +export class ItemHelperService { + public constructor( + @Inject(forwardRef(() => ItemService)) private readonly itemService: ItemService, + @Inject(forwardRef(() => RoomService)) private readonly roomService: RoomService, + @Inject(forwardRef(() => ClanService)) private readonly clanService: ClanService, + ){} + + /** + * Retrieves the clan ID associated with an item. + * + * @param _id - The ID of the item. + * @returns A promise that resolves to a tuple where the first element is the clan ID as a string or null if not found, and the second element is either null or an array of ServiceErrors if something went wrong. + */ + async getItemClanId(_id: string): Promise<[string | null, ServiceError[] | null]> { + const [item, itemErrors] = await this.itemService.readOneById(_id, { includeRefs: [ModelName.STOCK] }); + if (itemErrors) + return [null, itemErrors]; + + if (item.Stock && item.stock_id && item.Stock.clan_id) + return [item.Stock.clan_id.toString(), null]; + + if (!item.room_id) + return [null, [NotFoundError]]; + + const [room, roomErrors] = await this.roomService.readOneById(item.room_id, { includeRefs: [ModelName.SOULHOME] }); + if (roomErrors) + return [null, roomErrors]; + + if (!room["SoulHome"] || !room["SoulHome"].clan_id) + return [null, [NotFoundError]]; + + return [room["SoulHome"].clan_id, null]; + } + + /** + * Retrieves the SoulHome ID associated with an item. + * + * @param _id - The ID of the item. + * @returns - A promise that resolves to the SoulHome ID as a string. + * @throws - Throws an error if the item or SoulHome is not found. + */ + async getItemSoulHomeId(_id: string) { + const [item, itemErrors] = await this.itemService.readOneById(_id, { includeRefs: [ModelName.ROOM, ModelName.STOCK] }); + if (itemErrors) + throw itemErrors; + + if (item.stock_id) { + const [clan, error] = await this.clanService.readOneById(item.Stock.clan_id.toString(), { includeRefs: [ModelName.SOULHOME] }); + if (error) + throw NotFoundError; + + return clan.SoulHome._id.toString(); + } + + const [room, roomErrors] = await this.roomService.readOneById(item.room_id, { includeRefs: [ModelName.SOULHOME] }); + if (roomErrors) + throw roomErrors; + + if (room["SoulHome"]) + return room.soulHome_id.toString(); + + throw NotFoundError; + } + +} \ No newline at end of file diff --git a/src/item/itemMover.service.ts b/src/item/itemMover.service.ts new file mode 100644 index 00000000..9a6b045f --- /dev/null +++ b/src/item/itemMover.service.ts @@ -0,0 +1,125 @@ +import { forwardRef, Inject, Injectable } from "@nestjs/common"; +import { ItemService } from "./item.service"; +import { ItemHelperService } from "./itemHelper.service"; +import ServiceError from "../common/service/basicService/ServiceError"; +import { ItemDto } from "./dto/item.dto"; +import { MoveTo } from "./enum/moveTo.enum"; +import { NotFoundError } from "./errors/item.errors"; +import { ModelName } from "../common/enum/modelName.enum"; +import { StealToken } from "./type/stealToken.type"; +import { APIErrorReason } from "../common/controller/APIErrorReason"; +import { APIError } from "../common/controller/APIError"; +import { PlayerService } from "../player/player.service"; +import { ClanService } from "../clan/clan.service"; +import { RoomService } from "../room/room.service"; + +@Injectable() +export class ItemMoverService { + public constructor( + @Inject(forwardRef(() => ItemService)) private readonly itemService: ItemService, + @Inject(forwardRef(() => ItemHelperService)) private readonly itemHelperService: ItemHelperService, + @Inject(forwardRef(() => PlayerService)) private readonly playerService: PlayerService, + @Inject(forwardRef(() => ClanService)) private readonly clanService: ClanService, + @Inject(forwardRef(() => RoomService)) private readonly roomService: RoomService, + ){} + + + /** + * Moves multiple items to a specified stock or room. + * + * @param itemIds - The IDs of the items to be moved. + * @param storageId - The ID of the stock or room to which the items should be moved. + * @param storageType - The type of target, either 'stock' or 'room'. + * @returns A promise that resolves to a tuple where the first element is a array of successfully moved items, and the second element is an array of ServiceError objects if something went wrong. + */ + async moveItems(itemIds: string[], storageId: string, storageType: MoveTo): Promise<[ItemDto[], ServiceError[]]> { + const [items, itemErrors] = await this.itemService.readMany({ filter: { _id: { $in: itemIds } } }); + if (itemErrors) + return [null, itemErrors]; + + const filter = { _id: { $in: items.map((item) => item._id) } }; + const update = + storageType === MoveTo.STOCK + ? { $set: { stock_id: storageId, room_id: null } } + : { $set: { room_id: storageId, stock_id: null } }; + + const [_, err] = await this.itemService.updateMany([update], { filter }); + if (err) + return [null, err] + + return [items, null] + } + + /** + * Moves a single item to a specified destination if player, destination and item are in same clan. + * + * @param itemId - The ID of the item to be moved. + * @param destinationId - The ID of the destination (Room or Stock). + * @param moveTo - The type of target, either 'Stock' or 'Room'. + * @param playerId - The ID of the player performing the move. + * @returns A promise that resolves to a tuple where the first element is the moved item, and the second element an array of ServiceError objects if something went wrong. + */ + async moveItem(itemId: string, destinationId: string, moveTo: MoveTo, playerId: string): Promise<[ItemDto[], ServiceError[]]> { + let destinationClanId: string; + + const [player, playerErrors] = await this.playerService.getPlayerById(playerId); + if (playerErrors || !player.clan_id) + return [null, [NotFoundError]]; + + const playerClanId = player.clan_id.toString(); + + const [id, errors] = await this.itemHelperService.getItemClanId(itemId); + if (errors) + return [null, errors]; + + const itemClanId = id; + + if (moveTo === MoveTo.ROOM) { + const [room, roomErrors] = await this.roomService.readOneById(destinationId, { includeRefs: [ModelName.SOULHOME] }); + if (roomErrors) + return [null, roomErrors]; + + destinationClanId = room['SoulHome'].clan_id; + } else { + destinationClanId = player.clan_id.toString(); + const [clan, clanErrors] = await this.clanService.readOneById(destinationClanId, { includeRefs: [ModelName.STOCK]}); + if (clanErrors) + return [null, clanErrors]; + + destinationId = clan.Stock._id.toString(); + } + + if (playerClanId !== itemClanId || playerClanId !== destinationClanId) + return [null, [NotFoundError]]; + + return this.moveItems([itemId], destinationId, moveTo); + } + + /** + * Steal items from another clan. + * Finds the SoulHome ids of provided items and validates the ids with stealToken + * and then moves all the valid items to the provided room. + * + * @param itemIds - IDs of the items to be stolen. + * @param stealToken - Decoded JWT for authorizing steal actions. + * @param roomId - ID of the room to move the stolen items to. + * @returns - A promise that resolves into array of stolen items. + * @throws - Throws an error if no movable items are found. + */ + async stealItems(itemIds: string[], stealToken: StealToken, roomId: string) { + const itemSoulHomeIds: { itemId: string; soulHomeId: string }[] = + await Promise.all( + itemIds.map(async (itemId) => { + const soulHomeId = await this.itemHelperService.getItemSoulHomeId(itemId); + return { itemId, soulHomeId }; + }) + ); + + const filteredItems = itemSoulHomeIds.filter((item) => item.soulHomeId === stealToken.soulHomeId); + if (filteredItems.length === 0) + throw new APIError({ reason: APIErrorReason.NOT_FOUND, message: "No movable items found" }); + + const filteredItemIds = filteredItems.map((item) => item.itemId); + return this.moveItems(filteredItemIds, roomId, MoveTo.ROOM); + } +} \ No newline at end of file diff --git a/src/item/type/stealToken.type.ts b/src/item/type/stealToken.type.ts new file mode 100644 index 00000000..169cadb4 --- /dev/null +++ b/src/item/type/stealToken.type.ts @@ -0,0 +1,6 @@ +export type StealToken = { + playerId: string; + soulHomeId: string; + iat: number; + exp: number; +}; diff --git a/src/player/dto/createPlayer.dto.ts b/src/player/dto/createPlayer.dto.ts index 9be5fbe1..2521461f 100644 --- a/src/player/dto/createPlayer.dto.ts +++ b/src/player/dto/createPlayer.dto.ts @@ -1,4 +1,4 @@ -import {IsInt, IsMongoId, IsString} from "class-validator"; +import {IsBoolean, IsInt, IsMongoId, IsOptional, IsString} from "class-validator"; import {IsProfileExists} from "../../profile/decorator/validation/IsProfileExists.decorator"; import AddType from "../../common/base/decorator/AddType.decorator"; @@ -13,7 +13,15 @@ export class CreatePlayerDto { @IsString() uniqueIdentifier: string; + @IsOptional() + @IsBoolean() + above13?: boolean; + + @IsOptional() + @IsBoolean() + parentalAuth?: boolean; + @IsProfileExists() @IsMongoId() - profile_id: string; + profile_id?: string; } \ No newline at end of file diff --git a/src/player/dto/player.dto.ts b/src/player/dto/player.dto.ts index 5d5e04c5..cc058e3b 100644 --- a/src/player/dto/player.dto.ts +++ b/src/player/dto/player.dto.ts @@ -19,6 +19,12 @@ export class PlayerDto { @Expose() uniqueIdentifier: string; + @Expose() + above13?: boolean | null; + + @Expose() + parentalAuth: boolean | null; + @ExtractField() @Expose() profile_id: string; diff --git a/src/player/dto/updatePlayer.dto.ts b/src/player/dto/updatePlayer.dto.ts index d8a88aec..38a258c8 100644 --- a/src/player/dto/updatePlayer.dto.ts +++ b/src/player/dto/updatePlayer.dto.ts @@ -1,4 +1,4 @@ -import {IsInt, IsMongoId, IsOptional, IsString} from "class-validator"; +import {IsBoolean, IsInt, IsMongoId, IsOptional, IsString} from "class-validator"; import {IsClanExists} from "../../clan/decorator/validation/IsClanExists.decorator"; import {IsPlayerExists} from "../decorator/validation/IsPlayerExists.decorator"; import {IsCustomCharacterExists} from "../../customCharacter/decorator/validation/IsCustomCharacterExists.decorator"; @@ -22,6 +22,14 @@ export class UpdatePlayerDto { @IsOptional() uniqueIdentifier: string; + @IsOptional() + @IsBoolean() + above13?: boolean; + + @IsOptional() + @IsBoolean() + parentalAuth?: boolean; + @IsClanExists() @IsMongoId() @IsOptional() diff --git a/src/player/player.schema.ts b/src/player/player.schema.ts index abbf5d04..f1d4d6e4 100644 --- a/src/player/player.schema.ts +++ b/src/player/player.schema.ts @@ -19,6 +19,12 @@ export class Player { @Prop({ type: String, required: true, unique: true }) uniqueIdentifier: string; + @Prop({ type: Boolean, default: null }) + above13: boolean; + + @Prop({ type: Boolean, default: null }) + parentalAuth: boolean; + @ExtractField() @Prop({ type: MongooseSchema.Types.ObjectId, ref: ModelName.PROFILE }) profile_id: Profile; diff --git a/src/player/player.service.ts b/src/player/player.service.ts index a28cdaec..760c2a71 100644 --- a/src/player/player.service.ts +++ b/src/player/player.service.ts @@ -12,6 +12,9 @@ import {AddBasicService} from "../common/base/decorator/AddBasicService.decorato import {ClanDto} from "../clan/dto/clan.dto"; import {IHookImplementer, PostHookFunction} from "../common/interface/IHookImplementer"; import {UpdatePlayerDto} from "./dto/updatePlayer.dto"; +import { PlayerDto } from "./dto/player.dto"; +import BasicService from "../common/service/basicService/BasicService"; +import { TReadByIdOptions } from "../common/service/basicService/IService"; @Injectable() @AddBasicService() @@ -24,12 +27,22 @@ export class PlayerService private readonly requestHelperService: RequestHelperService ){ super(); + this.basicService = new BasicService(model); this.refsInModel = [ModelName.CLAN, ModelName.CUSTOM_CHARACTER, ModelName.ROOM]; this.modelName = ModelName.PLAYER; } public readonly refsInModel: ModelName[]; public readonly modelName: ModelName; + private readonly basicService: BasicService; + + async getPlayerById(_id: string, options?: TReadByIdOptions) { + let optionsToApply = options; + if (options?.includeRefs) { + optionsToApply.includeRefs = options.includeRefs.filter((ref) => this.refsInModel.includes(ref)); + } + return this.basicService.readOneById(_id, optionsToApply); + } public clearCollectionReferences = async (_id: Types.ObjectId, ignoreReferences?: IgnoreReferencesType): Promise => { const isClanRefCleanSuccess = await this.clearClanReferences(_id.toString()); diff --git a/src/Room/dto/ActivateRoom.dto.ts b/src/room/dto/ActivateRoom.dto.ts similarity index 100% rename from src/Room/dto/ActivateRoom.dto.ts rename to src/room/dto/ActivateRoom.dto.ts diff --git a/src/Room/dto/createRoom.dto.ts b/src/room/dto/createRoom.dto.ts similarity index 100% rename from src/Room/dto/createRoom.dto.ts rename to src/room/dto/createRoom.dto.ts diff --git a/src/Room/dto/room.dto.ts b/src/room/dto/room.dto.ts similarity index 100% rename from src/Room/dto/room.dto.ts rename to src/room/dto/room.dto.ts diff --git a/src/Room/dto/updateRoom.dto.ts b/src/room/dto/updateRoom.dto.ts similarity index 100% rename from src/Room/dto/updateRoom.dto.ts rename to src/room/dto/updateRoom.dto.ts diff --git a/src/Room/room.controller.ts b/src/room/room.controller.ts similarity index 100% rename from src/Room/room.controller.ts rename to src/room/room.controller.ts diff --git a/src/Room/room.module.ts b/src/room/room.module.ts similarity index 89% rename from src/Room/room.module.ts rename to src/room/room.module.ts index aac7ea8c..116b91e7 100644 --- a/src/Room/room.module.ts +++ b/src/room/room.module.ts @@ -1,4 +1,4 @@ -import { Module } from "@nestjs/common"; +import { forwardRef, Module } from "@nestjs/common"; import { MongooseModule } from "@nestjs/mongoose"; import { ModelName } from "../common/enum/modelName.enum"; import { RoomController } from "./room.controller"; @@ -16,9 +16,9 @@ import { ItemModule } from "../item/item.module"; MongooseModule.forFeature([ {name: ModelName.SOULHOME, schema: SoulhomeSchema } ]), MongooseModule.forFeature([ {name: ModelName.ROOM, schema: RoomSchema } ]), MongooseModule.forFeature([ {name: ModelName.CLAN, schema: ClanSchema } ]), - PlayerModule, + forwardRef(() => PlayerModule), + forwardRef(() => ItemModule), RequestHelperModule, - ItemModule ], controllers: [ RoomController ], providers: [ RoomService, RoomHelperService ], diff --git a/src/Room/room.schema.ts b/src/room/room.schema.ts similarity index 100% rename from src/Room/room.schema.ts rename to src/room/room.schema.ts diff --git a/src/Room/room.service.ts b/src/room/room.service.ts similarity index 92% rename from src/Room/room.service.ts rename to src/room/room.service.ts index e9ff40fd..e04b9de9 100644 --- a/src/Room/room.service.ts +++ b/src/room/room.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@nestjs/common"; +import { forwardRef, Inject, Injectable } from "@nestjs/common"; import { InjectModel } from "@nestjs/mongoose"; import { Model } from "mongoose"; import { Room } from "./room.schema"; @@ -17,7 +17,7 @@ export class RoomService { public constructor( @InjectModel(Room.name) public readonly model: Model, private readonly roomHelper: RoomHelperService, - private readonly itemService: ItemService + @Inject(forwardRef(() => ItemService)) private readonly itemService: ItemService ){ this.refsInModel = [ModelName.ITEM, ModelName.SOULHOME]; this.basicService = new BasicService(model); @@ -166,4 +166,15 @@ export class RoomService { for(let i=0, l=room_ids.length; i({ filter: { soulHome_id } }); + } } diff --git a/src/Room/utils/room.helper.service.ts b/src/room/utils/room.helper.service.ts similarity index 100% rename from src/Room/utils/room.helper.service.ts rename to src/room/utils/room.helper.service.ts diff --git a/src/stock/stock.module.ts b/src/stock/stock.module.ts index 46404578..2f667ad8 100644 --- a/src/stock/stock.module.ts +++ b/src/stock/stock.module.ts @@ -1,4 +1,4 @@ -import {Module} from '@nestjs/common'; +import {forwardRef, Module} from '@nestjs/common'; import {MongooseModule} from '@nestjs/mongoose'; import {StockSchema} from "./stock.schema"; import {RequestHelperModule} from "../requestHelper/requestHelper.module"; @@ -12,7 +12,7 @@ import { ItemModule } from '../item/item.module'; imports: [ MongooseModule.forFeature([ {name: ModelName.STOCK, schema: StockSchema} ]), RequestHelperModule, - ItemModule + forwardRef(() => ItemModule) ], controllers: [StockController], providers: [ StockService, isStockExists ], diff --git a/src/stock/stock.service.ts b/src/stock/stock.service.ts index 2284a21b..b8324d52 100644 --- a/src/stock/stock.service.ts +++ b/src/stock/stock.service.ts @@ -1,4 +1,4 @@ -import {Injectable} from "@nestjs/common"; +import {forwardRef, Inject, Injectable} from "@nestjs/common"; import {Model} from "mongoose"; import {InjectModel} from "@nestjs/mongoose"; import {ModelName} from "../common/enum/modelName.enum"; @@ -15,7 +15,7 @@ import { TIServiceReadManyOptions, TReadByIdOptions } from "../common/service/ba export class StockService { public constructor( @InjectModel(Stock.name) public readonly model: Model, - private readonly itemService: ItemService + @Inject(forwardRef(() => ItemService)) private readonly itemService: ItemService ){ this.refsInModel = [ModelName.CLAN, ModelName.ITEM]; this.modelName = ModelName.STOCK; diff --git a/swagger/releases/03-09-24-release.json b/swagger/releases/03-09-24-release.json new file mode 100644 index 00000000..b4564592 --- /dev/null +++ b/swagger/releases/03-09-24-release.json @@ -0,0 +1,4732 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Altzone API", + "version": "1.0.0", + "description": "This is a description of API for the Altzone game.\nThis swagger document is describing endpoint in more details and includes the request objects structure, examples, authorization rules and any other additional information.\nNotice, that it does not include common rules and functions, since they are covered on the wikipages in GitHub", + "x-logo": { + "url": "" + } + }, + "servers": [ + { + "url": "https://altzone.fi/api", + "description": "Production" + }, + { + "url": "http://localhost:8080", + "description": "Local" + } + ], + "paths": { + "/clan": { + "get": { + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": [ + { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User2 clan", + "tag": "tag2", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "id": "667eedc9b3b5bf0f7a840ef1" + }, + { + "_id": "667bfec6afb8211b4bd8dbff", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 4, + "admin_ids": [ + "665df7026bf5b8f670569ea4" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "id": "667bfec6afb8211b4bd8dbff" + } + ] + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 2, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Read all clans", + "description": "Read all created Clans. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Clan object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanUpdate" + }, + "examples": { + "Clan": { + "$ref": "#/components/examples/ClanWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "409": { + "$ref": "#/components/responses/409_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a clan", + "description": "Update the Clan, which _id is specified in the body. \n\nOnly Clan admins can change the Clan's data, as well as add or remove admins. Notice that while removing Clan admins, its is not allowed to remove all of them, because Clan must have at least one admin.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins | \n\n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanCreate" + } + } + }, + "required": true + }, + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": { + "objectType": "ClanDto", + "_id": "66d74887c988e09f4c45870a", + "name": "clan2", + "tag": "clan2", + "gameCoins": 0, + "admin_ids": [ + "66d7067645b40dd7f491e753" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "Stock": { + "objectType": "StockDto", + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a" + }, + "SoulHome": { + "objectType": "SoulHomeDto", + "_id": "66d74887c988e09f4c458715", + "name": "clan2", + "clan_id": "66d74887c988e09f4c45870a" + } + } + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "409": { + "$ref": "#/components/responses/409_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a clan", + "description": "Create a new Clan. The creator of the Clan becomes its admin. Notice that if Player is creating a new Clan, he/she becomes a member of it, that means that if Player is member of some Clan it can not create a new one, before leaving the old one.\n\nAlso endpoint creates Clan's Stock containing default Items, as well as Clan's SoulHome and Rooms.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | Player can be in one Clan only at one time => if Player already is in another Clan, he/she can not create a new one | \n\n" + } + }, + "/profile": { + "summary": "user profile", + "description": "", + "get": { + "tags": [ + "Profile" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Profile": { + "_id": "667ee778b3b5bf0f7a840ec9", + "username": "user1" + } + }, + "metaData": { + "dataKey": "Profile", + "modelName": "Profile", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read logged-in user Profile data", + "description": "Read logged-in user Profile data\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only own |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Profile" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update user Profile", + "description": "Update logged-in user Profile data. Notice that only fields needed to be updated should be specified." + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileCreate" + } + } + }, + "required": true + }, + "tags": [ + "Profile" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Profile": { + "objectType": "ProfileDto", + "_id": "667ee778b3b5bf0f7a840ec9", + "username": "user6", + "Player": { + "objectType": "PlayerDto", + "_id": "667ee778b3b5bf0f7a840ecb", + "name": "User 6", + "backpackCapacity": 34, + "uniqueIdentifier": "6", + "profile_id": "667ee778b3b5bf0f7a840ec9" + } + } + }, + "metaData": { + "dataKey": "Profile", + "modelName": "Profile", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "operationId": "profile_create", + "summary": "Create profile", + "description": "Create a user profile with Player object associated with it. Notice, that it is also possible in some edge cases to create a Profile without Player object assosiated with it, however it is not recommended and API expects that for every Profile there is a Player object created. " + }, + "delete": { + "tags": [ + "Profile" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Profile", + "description": "Delete logged-in user's Profile. Notice, that Profile deletion will lead removing all user data, such as Player and CustomCharacters. Since the Player object is assosiated with the Clan, user will be also removed from the Clan. Notice, that if there was nobody in the Clan the Clan with all assosiated objects will be removed. However, in case if the user was admin in this Clan and there are no other admins the user must first set at least one admin for this Clan, overwise the Profile will not be removed and 403 will be returned." + } + }, + "/player": { + "get": { + "tags": [ + "Player" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": [ + { + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9" + } + ] + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 1, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read all players", + "description": "Read all created Players. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Player object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerUpdate" + }, + "examples": { + "Player": { + "$ref": "#/components/examples/PlayerWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "Player" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a player", + "description": "Update the Player, which _id is specified in the body. Only Player, which belong to the logged-in Profile can be changed.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Player owner | \n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerCreate" + } + } + }, + "required": true + }, + "tags": [ + "Player" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": { + "objectType": "PlayerDto", + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9" + } + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a player for logged-in user", + "description": "Create a new Player. This is not recommended way of creating a new Player and it should be used only in edge cases. The recommended way is to create it via /profile POST endpoint.\n\nPlayer is representing an object, which holds data related to game player. This object can be used inside the game for example while joining a Clan. Notice, that the Profile object should not be used inside the game (except for logging-in). \n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | Player can be associated with only one Profile | \n" + } + }, + "/player/{_id}": { + "get": { + "tags": [ + "Player" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": { + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9" + } + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Player by _id", + "description": "Read Player data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Player" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Player", + "description": "Delete Player by its _id field. Notice that only Player, which belongs to loggen-in user Profile can be deleted. In case when the Player is the only admin in some Clan and the Clan has some other Players, the Player can not be removed. User should be asked to first determine at least one admin for the Clan.\n\nAlso, it is not recommended to delete the Player since it can itroduce unexpected behaviour for the user with Profile, but without Player. The better way to remove the Player is do it via /profile DELETE.\n\nPlayer removal basically means removing all data, which is related to the Player: CustomCharacters, Clan, except for the Profile data. In the case when the Profile does not has a Player, user can only login to the system, but can not play the game.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Player owner |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "6686cb3b71adebdb10f33ffb" + } + }, + "name": "_id", + "description": "player _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/clan/{_id}": { + "get": { + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true + } + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Clan by _id", + "description": "Read Clan data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Clan", + "description": "Delete Clan its _id field. \n\nNotice that only Clan admins can delete the Clan.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins |" + }, + "parameters": [ + { + "examples": { + "_id": { + "value": "667462842425aea94d0f66cb" + } + }, + "name": "_id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/clan/join": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanJoinCreate" + } + } + }, + "required": true + }, + "tags": [ + "Clan" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Player requests join to clan", + "description": "Request to join a Clan. \n\nNotice that if the Clan is open the Player will be joined automatically without admin approval. \n\nNotice that if the Player was in another Clan then he/she will be removed from the old one and if in this Clan was no other Players, it will be removed.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no, but for closed Clans there should be an admin approval first | \n\n" + } + }, + "/clan/leave": { + "post": { + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401", + "x-last-modified": 1718901883486 + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500", + "x-last-modified": 1718902019444 + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Player requests leave the Clan", + "description": "Request to leave a Clan. \n\nNotice that Player can leave any Clan without admin approval.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no | \n\n" + } + }, + "/stock": { + "get": { + "tags": [ + "Stock", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Stock": [ + { + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a", + "__v": 0, + "id": "66d74887c988e09f4c45870d" + }, + { + "_id": "66d70d08941f1d7be5033a3f", + "cellCount": 20, + "clan_id": "66d70d08941f1d7be5033a3c", + "__v": 0, + "id": "66d70d08941f1d7be5033a3f" + } + ] + }, + "metaData": { + "dataKey": "Stock", + "modelName": "Stock", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Read all stocks", + "description": "Read all created Stocks of all Clans. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## Endpoint description\n\n### General description\n\nStock is a public place of a Clan, where Items can be stored. These Items, however, can not be stolen from the Clan by other Clans' members during raid. \n\n### Relations\n\nStock can belong to one Clan and Clan may have only one Stock. Stock may have many Items. Stock may belong to one Stock.\n\n### CRUD operations\n\nSince a Clan can have only one Stock, the Stock is created automatically whenever a Clan is created.\n\nStock data can be read by anybody without authentication, as well all existing Stocks list can be fetched.\n\nStock does not have any data, which can be updated by Clan members. The only field, which can be updated is a cellCount, which hold information about Stock capacity. The starting value of it is 30, but more cells can be bought from store later. The cellCount does not have a limit.\n\nStock is deleted automatically (and Items related to it) when the Clan to which it belongs to is also deleted.", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + } + }, + "/stock/{_id}": { + "get": { + "tags": [ + "Stock", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Stock": { + "objectType": "StockDto", + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a" + } + }, + "metaData": { + "dataKey": "Stock", + "modelName": "Stock", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Stock by _id", + "description": "Read Stock data by its _id field.\n\nNotice that everybody is able to read any Stock data.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n\n## Endpoint description\n\n### General description\n\nStock is a public place of a Clan, where Items can be stored. These Items, however, can not be stolen from the Clan by other Clans' members during raid. \n\n### Relations\n\nStock can belong to one Clan and Clan may have only one Stock. Stock may have many Items. Stock may belong to one Stock.\n\n### CRUD operations\n\nSince a Clan can have only one Stock, the Stock is created automatically whenever a Clan is created.\n\nStock data can be read by anybody without authentication, as well all existing Stocks list can be fetched.\n\nStock does not have any data, which can be updated by Clan members. The only field, which can be updated is a cellCount, which hold information about Stock capacity. The starting value of it is 30, but more cells can be bought from store later. The cellCount does not have a limit.\n\nStock is deleted automatically (and Items related to it) when the Clan to which it belongs to is also deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66894fbd4a7b6c993aed1a18" + } + }, + "name": "_id", + "description": "Stock _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/item/{_id}": { + "get": { + "tags": [ + "Item", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Item": { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "filename": "file1", + "location": [ + 2, + 4 + ], + "isFurniture": true, + "stock_id": "668953cd4a7b6c993aed1a36", + "price": 23 + } + }, + "metaData": { + "dataKey": "Item", + "modelName": "Item", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Item by _id", + "description": "Read Item data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, + "name": "_id", + "description": "item _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/soulhome": { + "description": "", + "get": { + "tags": [ + "SoulHome", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "SoulHome": { + "objectType": "SoulHomeDto", + "_id": "66d74887c988e09f4c458715", + "name": "clan2", + "clan_id": "66d74887c988e09f4c45870a" + } + }, + "metaData": { + "dataKey": "SoulHome", + "modelName": "SoulHome", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get SoulHome", + "description": "## Method description\n\nGet SoulHome data for the logged-in user. \n\nIf the logged-in user is a Clan member, the SoulHome for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nSoulhome is a safe place for storing Clan Items (inside SoulHome's Rooms). However, it is possible to steal Items from the SoulHome rooms, but the access to CRUD operations mostly lies in hands of Clan members.\n\nSoulHome does have only one field \"name\", which is same as a Clan name.\n\n### Relations\n\nSoulHome can belong to one Clan and Clan may have only one SoulHome. SoulHome may have many Rooms. Room may belong to one SoulHome.\n\n### CRUD operations\n\nSoulHome must be created automatically when Clan is created. When SoulHome is created 30 Rooms for that SoulHome with default values must created." + } + }, + "/room": { + "get": { + "tags": [ + "Room", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Room": [ + { + "_id": "66d74887c988e09f4c458721", + "floorType": "default", + "wallType": "default", + "cellCount": 10, + "hasLift": false, + "soulHome_id": "66d74887c988e09f4c458715", + "isActive": false, + "id": "66d74887c988e09f4c458721" + } + ] + }, + "metaData": { + "dataKey": "Room", + "modelName": "Room", + "dataType": "Array", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get all Clan's Rooms", + "description": "## Method description\n\nGet all Rooms for the logged-in user. \n\nIf the logged-in user is a Clan member, the Rooms for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan the 404 error will be returned.\n\nIf the pagination is required, it can be used, but by default it will return all 30 rooms at once.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Pagination | yes |\n| Sort | yes |\n| Search | yes |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Room", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update Room by _id", + "description": "## Method description\n\nUpdate Room by its _id specified in the body.\n\nAny Clan member can update any Room, which (SoulHome) belongs to the Clan.\n\nIf the logged-in user is a Clan member and the Clan does have the requested Room, the Room for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan, or Room in that Clan with provided _id is not found the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. Items from the Room can not be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + } + }, + "/itemShop": { + "get": { + "tags": [ + "ItemShop" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "ItemShop": { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "lastRestock": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "item_id": { + "type": "string" + }, + "isInVoting": { + "type": "boolean" + }, + "isSold": { + "type": "boolean" + } + } + } + }, + "__v": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + } + } + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "dataType": { + "type": "string" + }, + "dataCount": { + "type": "integer" + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "itemCount": { + "type": "integer" + }, + "pageCount": { + "type": "integer" + } + } + } + } + }, + "examples": { + "1": { + "value": { + "data": { + "ItemShop": [ + { + "_id": "65feaee74ec1b78cc4afc9da", + "name": "Huonekalukauppa", + "lastRestock": 1720612774100, + "items": [ + { + "item_id": "668e77a657e6f7ebbcf3a7da", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7db", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7dc", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7dd", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7de", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7df", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e0", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e1", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e2", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e3", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e4", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e5", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e6", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e7", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e8", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e9", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ea", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7eb", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ec", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ed", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ee", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ef", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f0", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f1", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f2", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f3", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f4", + "isInVoting": false, + "isSold": false + } + ], + "__v": 2, + "id": "65feaee74ec1b78cc4afc9da" + } + ] + }, + "metaData": { + "dataKey": "ItemShop", + "modelName": "ItemShop", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "Success, rseponse with body" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Get item shops", + "description": "The ItemShop is an object containing ShopItems. There can be only one ItemShop excisting. The ShopItem is an inner object of the ItemShop, which contains item_id (reference to an Item), isInVoting, isSold and vote_id(reference to a ClanVote).\n\nNotice that ItemShop is created automatically by an API with some default ShopItems in it. Each day ItemShop is reset, so that all default values, which was sold are restored back.\n\nNotice that it is possible to access ItemShop via this endpoint and also via /itemShop/{_id}, the response will be the same (except that this endpoint will have the ItemShop data in an array).\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | " + } + }, + "/itemShop/{_id}": { + "get": { + "tags": [ + "ItemShop" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "ItemShop": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "lastRestock": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "item_id": { + "type": "string" + }, + "isInVoting": { + "type": "boolean" + }, + "isSold": { + "type": "boolean" + } + } + } + }, + "__v": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + } + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "dataType": { + "type": "string" + }, + "dataCount": { + "type": "integer" + } + } + } + } + }, + "examples": { + "1": { + "value": { + "data": { + "ItemShop": { + "_id": "65feaee74ec1b78cc4afc9da", + "name": "Huonekalukauppa", + "lastRestock": 1720612774100, + "items": [ + { + "item_id": "668e77a657e6f7ebbcf3a7da", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7db", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7dc", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7dd", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7de", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7df", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e0", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e1", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e2", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e3", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e4", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e5", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e6", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e7", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e8", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7e9", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ea", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7eb", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ec", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ed", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ee", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7ef", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f0", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f1", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f2", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f3", + "isInVoting": false, + "isSold": false + }, + { + "item_id": "668e77a657e6f7ebbcf3a7f4", + "isInVoting": false, + "isSold": false + } + ], + "__v": 2, + "id": "65feaee74ec1b78cc4afc9da" + } + }, + "metaData": { + "dataKey": "ItemShop", + "modelName": "ItemShop", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Success, rseponse with body" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Get item shop", + "description": "The ItemShop is an object containing ShopItems. There can be only one ItemShop excisting. The ShopItem is an inner object of the ItemShop, which contains item_id (reference to an Item), isInVoting, isSold and vote_id(reference to a ClanVote).\n\nNotice that ItemShop is created automatically by an API with some default ShopItems in it. Each day ItemShop is reset, so that all default values, which was sold are restored back.\n\nNotice that it is possible to access ItemShop via this endpoint and also via /itemShop, the response will be the same (except that this endpoint will have the ItemShop data as an object).\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |" + }, + "parameters": [ + { + "name": "_id", + "in": "path", + "required": true + } + ] + }, + "/chat": { + "get": { + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": [ + { + "_id": "66912712d191c865ab53da8a", + "name": "Chat 1", + "__v": 0, + "id": "66912712d191c865ab53da8a" + } + ] + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 1, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Read all existing chats", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all created Chats. Remember about the pagination.\n\nNotice, that use of messages array is not advised and can be removed at some point in the future. For accessing messages of the Chat please use the /chat/:_id/message endpoint.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Chat object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nUpdate the Chat, which _id is specified in the body.\n\nNotice that currently anybody is able to change any Chat.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no | \n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCreate" + } + } + }, + "required": true + }, + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": { + "name": "Chat 1", + "_id": "66912712d191c865ab53da8a", + "messages": [], + "__v": 0, + "id": "66912712d191c865ab53da8a" + } + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Chat. The Chat is an object containing messages.\n\nNotice, that currently there is no restrictions on who can create a Chat.\n\nNotice that the Message objects are inner objects of Chat and can not be used enewhere else than in the Chat. There is also no separate collection for the Message in the DB.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no | \n" + } + }, + "/chat/{_id}": { + "get": { + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": { + "_id": "66912712d191c865ab53da8a", + "name": "Chat 1", + "__v": 0, + "id": "66912712d191c865ab53da8a" + } + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Get Chat by _id", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead Chat data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nDelete Chat by its _id field. \n\nNotice that currently anybody can delete any Chat.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "654fad58fd96261edd2e096f" + } + }, + "name": "_id", + "description": "Chat _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/chat/{_id}/messages": { + "get": { + "tags": [ + "release-03-09-24", + "Chat" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": [ + { + "id": 15, + "senderUsername": "user1", + "content": "message 15", + "feeling": 1 + } + ] + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Read all messages from the Chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all messages of specified Chat. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreate" + } + } + }, + "required": true + }, + "tags": [ + "Chat", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a message", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Message. Message represent the object of message sent by a Player.\n\nNotice that currently there are no authorization required.\n\nNotice, that the messages does not have an usual _id field generated by data base. Instead the Photon id should be used.\n\nNotice that the messages is contained in the array of a Chat collection.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no |\n" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66912712d191c865ab53da8a" + } + }, + "name": "_id", + "description": "Chat _id, with which the message is associated", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/customCharacter": { + "get": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Read all custom characters", + "description": "Read all custom characters. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "CustomCharacter object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomCharacterUpdate" + }, + "examples": { + "CustomCharacter": { + "$ref": "#/components/examples/CustomCharacterWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "description": "Success, no body" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a custom character", + "description": "Update the CustomCharacter, which _id is specified in the body.\n\nOnly the Player, that owns the CustomCharacter can change it.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for character owner |\n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomCharacterCreate" + } + } + }, + "required": true + }, + "tags": [ + "CustomCharacter" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a custom character", + "description": "Create a new CustomCharacter. CustomCharacter represents a character of the Player. Player can have many CustomCharacters, CustomCharacter can belong to only one Player.\n\nNotice, that Player can create CustomCharacters only for himself/herself not for other Players.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only own |\n" + } + }, + "/customCharacter/{_id}": { + "get": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Get CustomCharacter by id", + "description": "Read CustomCharacter data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no |" + }, + "delete": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "description": "Success, no body" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete CustomCharacter", + "description": "Delete CustomCharacter by its _id field. Notice that only the CustomCharacter owner can delete the CustomCharacter.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for CustomCharacter |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66939de4db98c5ba6bb192f1" + } + }, + "name": "_id", + "description": "CustomCharacter _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/room/{_id}": { + "get": { + "tags": [ + "Room", + "release-03-09-24" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Room": { + "_id": "668d6003a9292e300e94c833", + "floorType": "floor1", + "wallType": "wall1", + "isActive": false, + "deactivationTimestamp": 34524523654, + "cellCount": 20, + "soulHome_id": "667ef139b3b5bf0f7a840f2d", + "hasLift": true + } + }, + "metaData": { + "dataKey": "Room", + "modelName": "Room", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get Room by _id", + "description": "## Method description\n\nGet Room by its _id. \n\nIf the logged-in user is a Clan member and the Clan does have the requested Room, the Room for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan, or Room in that Clan with provided _id is not found the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen.\n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "667ee778b3b5bf0f7a840ec9" + } + }, + "name": "_id", + "description": "Room _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/room/activate": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "room_ids" + ], + "type": "object", + "properties": { + "room_ids": { + "description": "List of Room _ids to activate", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + }, + "durationS": { + "description": "Optional duration of room to be active in seconds", + "type": "number", + "example": 60 + } + } + } + } + }, + "required": true + }, + "tags": [ + "Room", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Activate the Room by _id", + "description": "## Method description\n\nActivate the specified Rooms.\n\nIf Room _id specified in the room_ids field does not belong to logged-in user's Clan (SoulHome), it will be ignored. However it will return 404 if non of the Room _ids does not belong to the Clan. \n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + } + }, + "/item/steal": { + "get": { + "tags": [ + "Item", + "in-development" + ], + "parameters": [ + { + "name": "steal_token", + "description": "", + "schema": { + "type": "string" + }, + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "SoulHome": { + "_id": "668c0e9f9470819cf38e371c", + "name": "clan1", + "clan_id": "667bfec6afb8211b4bd8dbff", + "Room": [ + { + "_id": "668d6003a9292e300e94c833", + "floorType": "floor1", + "wallType": "wall1", + "isActive": false, + "deactivationTimestamp": 34524523654, + "cellCount": 20, + "soulHome_id": "668c0e9f9470819cf38e371c", + "hasLift": true + } + ] + } + }, + "metaData": { + "dataKey": "SoulHome", + "modelName": "SoulHome", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "SoulHome and its Rooms" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get SoulHome data from which items can be stolen", + "description": "Based on the provided steal token, which contains the SoulHome _id from which Items can be stolen, the SoulHome data and its Rooms will be returned.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" + }, + "post": { + "requestBody": { + "description": "Items to be stolen", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "steal_token": "some_token", + "item_ids": [ + "668a765091020196cb10d5a3", + "668a765091020196cb10d5a3" + ], + "room_is": "668a765091020196cb10d5a3" + } + } + } + } + }, + "required": true + }, + "tags": [ + "in-development", + "Item" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "Item": [ + { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "filename": "file1", + "location": [ + 2, + 4 + ], + "isFurniture": true, + "stock_id": "668953cd4a7b6c993aed1a36", + "price": 23 + } + ] + }, + "metaData": { + "dataKey": "Item", + "modelName": "Item", + "dataType": "Array", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Array of Items, which were stolen" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Steal Items from another Clan", + "description": "Steal Items from the loser Clan's SoulHome. The stolen Items will be automatically added to a specified Room of the winners Clan's SoulHome.\n\nNotice that at first a steal token should be obtained by the winner player(s) from the /gameData/battle POST endpoint with body type \"result\", while informing about game result.\n\nRequests without the steal token or with an expired token will get 403 as a response.\n\nNotice that only found SoulHome Items will be stolen. It is possible that some other player already has stoled some of the specified Items, in this case these Items will be ignored since they can not be stolen twice. \n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + } + }, + "/gameData/battle": { + "post": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "result": { + "value": { + "type": "result", + "team1": [ + "667bfec6afb8211b4bd8dbff", + "597bfec6afb8211b4bd8db64" + ], + "team2": [ + "397bfec6adb8211b4bd8dbe0", + "207bfec6adb8211b4bd8dba1" + ], + "duration": 120, + "winnerTeam": 1 + } + } + } + } + }, + "required": true + }, + "tags": [ + "GameData", + "in-development" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "result": { + "value": { + "steal_token": "temporary_token_to_use_for_/room/steal", + "stock_id": "667ee778b3b5bf0f7a840ec9", + "clan_id": "668d6003a9292e300e94c833" + } + } + } + } + }, + "description": "" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Inform API about battle", + "description": "Enpoint for notifying the API about battle events or any other data.\n\nNotice, that the field type is required and determines the type of the data.\n\nNotice that the type also determines shape of the body. Examples, for each type can be found in request examples section.\n\n## Type field\n\n### result\n\nResult of the battle, all players of the battle should send this data.\n\nAs a response for winners an access token will be returned, which can be used when stealing Items from losed Clan's SoulHome. Notice that the steal token will expire after some period of time. Loosers will get 403 error\n\nThe steal token can be used only by the winner's Clan's members for the loser's Clan Stock.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" + } + }, + "/item/move": { + "post": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "397bfec6adb8211b4bd8dbe0", + "moveTo": "Stock", + "destination_id": "667bfec6afb8211b4bd8dbff" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Item", + "in-development" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Move Item", + "description": "Move Item from Stock to Room or from Room to Stock, based on the _moveTo_ field, which can have only two values: \"Stock\" or \"Room\"\n\nNotice that Clan members can move only own Clan Items and a member is trying to move other Clan's Item the 404 will be returned.\n\nNotice that if an Item need to be moved to a Stock then there is no need to specify the destination_id field, since Clan can have only one Stock.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + } + }, + "/itemShop/buy": { + "post": { + "tags": [ + "ItemShop", + "in-development" + ], + "responses": { + "501": { + "description": "Not implemented" + } + }, + "summary": "Buy an Item from Shop" + } + }, + "/auth/signIn": { + "post": { + "requestBody": { + "description": "Profile credentials", + "content": { + "application/json": { + "schema": { + "required": [ + "username", + "password" + ], + "type": "object", + "properties": { + "username": { + "description": "The username of the profile", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password of the profile", + "type": "string", + "example": "myPassword" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Auth" + ], + "responses": { + "201": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "objectType": "ProfileDto", + "_id": "651a716b5129c6e54cb7d7c9", + "username": "user1", + "__v": 0, + "Player": { + "objectType": "PlayerDto", + "_id": "651a716b5129c6e54cb7d7cb", + "name": "User1", + "backpackCapacity": 456, + "uniqueIdentifier": "3", + "profile_id": "651a716b5129c6e54cb7d7c9", + "__v": 0, + "clan_id": "651a72c35129c6e54cb7d7de" + }, + "Clan": { + "objectType": "ClanDto", + "itemCount": 0, + "isOpen": true, + "_id": "651a72c35129c6e54cb7d7de", + "name": "User1 clan", + "tag": "sometage", + "gameCoins": 645, + "admin_ids": [ + "651a716b5129c6e54cb7d7cb" + ], + "__v": 0, + "playerCount": 2, + "stockCount": 1 + }, + "accessToken": "your_token", + "tokenExpires": 1725799544 + } + } + } + } + }, + "description": "Successful login with the profile data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Log in to the system", + "description": "After the profile with player was created, the user can log in to the system and get a JWT token to access resources.\n\nIf the user provides the correct credentials, the access token will be returned, which should be used as a Bearer token in the Authorization header.\n\nIf the user provides wrong credentials, a 401 status will be returned." + } + }, + "/clan/exclude": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanExclude" + } + } + }, + "required": true + }, + "tags": [ + "Clan", + "release-03-09-24" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Exclude Player from Clan", + "description": "Request exclude the player from clan. \n\nNotice that only Clan admin can remove the Player\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only clan admin | \n\n" + } + } + }, + "components": { + "schemas": { + "200": { + "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name, usually same as dataKey", + "type": "string" + }, + "dataType": { + "description": "Object or Array", + "type": "string" + }, + "dataCount": { + "description": "1 for Object dataType and length if Array", + "type": "integer" + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "description": "The current page number", + "type": "integer" + }, + "limit": { + "description": "The limit of items per page", + "type": "integer" + }, + "offset": { + "description": "The offset of items", + "type": "integer" + }, + "itemCount": { + "description": "The total number of items", + "type": "integer" + }, + "pageCount": { + "description": "The total number of pages", + "type": "integer" + } + } + } + } + }, + "201": { + "description": "Created object. The response objects has two fields: data, metaData.\n\nThe data has the created object data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in the data object.", + "required": [ + "data", + "metaData" + ], + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name", + "type": "string" + }, + "dataType": { + "description": "always has Object value", + "type": "string" + }, + "dataCount": { + "description": "always has 1 value", + "type": "integer" + } + } + } + } + }, + "APIError": { + "description": "API error", + "type": "object", + "properties": { + "statusCode": { + "description": "The HTTP status code", + "type": "integer", + "example": 404 + }, + "message": { + "description": "The error message", + "type": "string", + "example": "Cannot GET /api/profile" + }, + "error": { + "description": "The error name", + "type": "string", + "example": "Not Found" + } + }, + "x-last-modified": 1718798371474 + }, + "APIConflictError": { + "description": "API error", + "required": [], + "type": "object", + "properties": { + "statusCode": { + "description": "The HTTP status code", + "type": "integer", + "example": 400 + }, + "message": { + "description": "An array of error messages", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "password must be a string" + ] + }, + "error": { + "description": "The error name", + "type": "string", + "example": "Bad Request" + } + } + }, + "ProfileCreate": { + "type": "object", + "properties": { + "username": { + "description": "The username for the Profile, unique", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password for the Profile", + "type": "string", + "example": "my_password" + }, + "Player": { + "type": "object", + "properties": { + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 453 + }, + "uniqueIdentifier": { + "description": "The identifier for the player, unique", + "type": "string", + "example": "1" + } + } + } + } + }, + "ClanUpdate": { + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "The _id of the clan to be updated", + "type": "string", + "example": "667462842425aea94d0f66cb" + }, + "gameCoins": { + "description": "The number of clan coins", + "type": "integer", + "example": 45 + }, + "tag": { + "description": "The tag associated with the clan", + "type": "string", + "example": "my_tag" + }, + "isOpen": { + "description": "whenever the clan is open or closed", + "type": "boolean", + "example": true + }, + "admin_idsToDelete": { + "description": "List of admin _ids to delete", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + }, + "admin_idsToAdd": { + "description": "List of admin _ids to add", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + } + } + }, + "JoinClanReq": { + "description": "Clan joining request by Player", + "required": [ + "clan_id", + "player_id" + ], + "type": "object", + "properties": { + "clan_id": { + "description": "which Clan to join", + "type": "string" + }, + "player_id": { + "description": "who want to join the Clan", + "type": "string" + }, + "join_message": { + "description": "optional for open Clans", + "type": "string" + } + }, + "example": { + "clan_id": "667462842425aea94d0f66cb", + "player_id": "666720806cc90102f60bd325", + "join_message": "User 2 wants to join" + } + }, + "ProfileUpdate": { + "type": "object", + "properties": { + "username": { + "description": "The username for the Profile, unique", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password for the Profile", + "type": "string", + "example": "my_password" + } + } + }, + "200_one": { + "description": "Found object. The response objects has two fields: data and metaData.\nThe data has the requested data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name", + "type": "string" + }, + "dataType": { + "description": "always has Object value", + "type": "string" + }, + "dataCount": { + "description": "always has 1 value", + "type": "integer" + } + } + } + } + }, + "PlayerCreate": { + "description": "", + "type": "object", + "properties": { + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "uniqueIdentifier": { + "description": "The identifier for the Player, unique", + "type": "string", + "example": "1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 34 + }, + "profile_id": { + "description": "The id of the associated Profile", + "type": "string", + "example": "6686cac271adebdb10f33fee" + } + } + }, + "PlayerUpdate": { + "description": "", + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Player, readonly", + "type": "string", + "example": "6686cb3b71adebdb10f33ffb" + }, + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "uniqueIdentifier": { + "description": "The identifier for the Player, unique", + "type": "string", + "example": "1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 34 + }, + "profile_id": { + "description": "The id of the associated Profile", + "type": "string", + "example": "6686cac271adebdb10f33fee" + } + } + }, + "ClanCreate": { + "required": [ + "name", + "tag", + "gameCoins" + ], + "type": "object", + "properties": { + "name": { + "description": "name of the clan, unique", + "type": "string", + "example": "my_clan" + }, + "gameCoins": { + "description": "amount of clan coins", + "default": 0, + "type": "integer", + "example": 23 + }, + "tag": { + "description": "amount of clan coins", + "type": "string", + "example": "my_tag" + }, + "isOpen": { + "description": "whenever the clan is open or closed", + "default": true, + "type": "boolean", + "example": true + } + } + }, + "ClanJoinCreate": { + "required": [ + "clan_id", + "player_id" + ], + "type": "object", + "properties": { + "clan_id": { + "description": "the Clan _id which player want to join", + "type": "string", + "example": "667bfec6afb8211b4bd8dbff" + }, + "player_id": { + "description": "the Player _id who want to join", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "join_message": { + "description": "optional message for joining request (for closed Clans only)", + "type": "string", + "example": "User 2 wants to join" + } + } + }, + "ClanJoinUpdate": { + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "the request _id, to be updated", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "clan_id": { + "description": "the Clan _id which player want to join", + "type": "string", + "example": "667bfec6afb8211b4bd8dbff" + }, + "player_id": { + "description": "the Player _id who want to join", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "join_message": { + "description": "optional message for joining request (for closed Clans only)", + "type": "string", + "example": "User 2 wants to join" + } + } + }, + "RoomUpdate": { + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Room, readonly", + "type": "string", + "example": "668d6003a9292e300e94c833" + }, + "floorType": { + "description": "The type of the floor in the room", + "type": "string", + "example": "floor1" + }, + "wallType": { + "description": "The type of the wall in the room", + "type": "string", + "example": "wall1" + }, + "isActive": { + "description": "Indicates whether the room is active or deactivated", + "type": "boolean", + "example": false + }, + "deactivationTimestamp": { + "description": "Indicates when the Room will be deactivated", + "type": "number", + "example": 1721927762 + }, + "hasLift": { + "description": "Indicates whether the room has lift or staicases", + "type": "boolean", + "example": false + } + } + }, + "ChatCreate": { + "description": "Update an existing Chat", + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Chat, readonly", + "type": "string", + "example": "66912712d191c865ab53da8a" + }, + "name": { + "description": "The name of the chat", + "type": "string", + "example": "Chat 1" + }, + "messages": { + "description": "List of messages in the chat", + "type": "array", + "items": { + "type": "object" + }, + "example": [] + } + } + }, + "ChatUpdate": { + "description": "Create a new Chat", + "type": "object", + "properties": { + "name": { + "description": "The name of the chat", + "type": "string", + "example": "Chat 1" + } + } + }, + "MessageCreate": { + "description": "", + "type": "object", + "properties": { + "id": { + "description": "The photon ID of the message", + "type": "integer", + "example": 15 + }, + "senderUsername": { + "description": "The username of the message sender", + "type": "string", + "example": "user1" + }, + "content": { + "description": "The content of the message", + "type": "string", + "example": "message 15" + }, + "feeling": { + "description": "The feeling associated with the message (enum from 1-3)", + "type": "integer", + "example": 1 + } + } + }, + "CustomCharacterCreate": { + "description": "", + "required": [ + "unityKey", + "name", + "speed", + "resistance", + "attack", + "defence", + "characterClass_id", + "player_id" + ], + "type": "object", + "properties": { + "unityKey": { + "description": "The Unity key of the custom character", + "type": "string", + "example": "somekey12" + }, + "name": { + "description": "unique, The name of the custom character", + "type": "string", + "example": "My custom5" + }, + "speed": { + "description": "The speed attribute of the custom character", + "type": "integer", + "example": 37 + }, + "resistance": { + "description": "The resistance attribute of the custom character", + "type": "integer", + "example": 40 + }, + "attack": { + "description": "The attack attribute of the custom character", + "type": "integer", + "example": 55 + }, + "defence": { + "description": "The defence attribute of the custom character", + "type": "integer", + "example": 14 + }, + "characterClass_id": { + "description": "reference, The ID of the character class", + "type": "string", + "example": "66939d9f9fdbf76f01d5dbd5" + }, + "player_id": { + "description": "reference, The ID of the player", + "type": "string", + "example": "665df7026bf5b8f670569ea4" + } + } + }, + "CustomCharacterUpdate": { + "description": "", + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "The CustomCharacter _id to update", + "type": "string", + "example": "somekey12" + }, + "unityKey": { + "description": "The Unity key of the custom character", + "type": "string", + "example": "somekey12" + }, + "name": { + "description": "unique, The name of the custom character", + "type": "string", + "example": "My custom5" + }, + "speed": { + "description": "The speed attribute of the custom character", + "type": "integer", + "example": 37 + }, + "resistance": { + "description": "The resistance attribute of the custom character", + "type": "integer", + "example": 40 + }, + "attack": { + "description": "The attack attribute of the custom character", + "type": "integer", + "example": 55 + }, + "defence": { + "description": "The defence attribute of the custom character", + "type": "integer", + "example": 14 + }, + "characterClass_id": { + "description": "reference, The ID of the character class", + "type": "string", + "example": "66939d9f9fdbf76f01d5dbd5" + }, + "player_id": { + "description": "reference, The ID of the player", + "type": "string", + "example": "665df7026bf5b8f670569ea4" + } + } + }, + "ErrorAuthentication": { + "title": "Error on Authentication", + "description": "Error happen on authentication", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "AUTHENTICATION_FAILED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 401 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Could not authenticate the user" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "AUTHENTICATION_FAILED", + "NOT_AUTHENTICATED", + "INVALID_AUTH_TOKEN" + ], + "type": "string", + "example": "AUTHENTICATION_FAILED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 401 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "APIErrorReason": { + "description": "Enum with all possible APIError reasons", + "enum": [ + "NOT_FOUND", + "BAD_REQUEST", + "NOT_UNIQUE", + "REQUIRED", + "NOT_ALLOWED", + "VALIDATION", + "NOT_STRING", + "NOT_NUMBER", + "NOT_BOOLEAN", + "NOT_ARRAY", + "NOT_OBJECT", + "NOT_DATE", + "WRONG_ENUM", + "LESS_THAN_MIN", + "MORE_THAN_MAX", + "NOT_AUTHENTICATED", + "INVALID_AUTH_TOKEN", + "AUTHENTICATION_FAILED", + "NOT_AUTHORIZED", + "TOO_MANY_REQUESTS", + "MISCONFIGURED", + "NOT_AVAILABLE", + "UNEXPECTED" + ], + "type": "string" + }, + "ErrorValidation": { + "title": "Error on Validation", + "description": "Error happen, because some of the fields are not valid", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_STRING" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 400 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "name must be a string" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "BAD_REQUEST", + "REQUIRED", + "NOT_ALLOWED", + "VALIDATION", + "NOT_STRING", + "NOT_NUMBER", + "NOT_BOOLEAN", + "NOT_ARRAY", + "NOT_OBJECT", + "NOT_DATE", + "WRONG_ENUM", + "LESS_THAN_MIN", + "MORE_THAN_MAX" + ], + "type": "string", + "example": "NOT_STRING" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "name" + }, + "value": { + "nullable": true, + "type": "string", + "example": 1 + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string", + "example": "isString" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 400 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorNotFound": { + "title": "Error on Not Found", + "description": "Error happen if the requested object(s) can not be found", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_FOUND" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 404 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Could not find any objects with specified id" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_FOUND" + ], + "type": "string", + "example": "NOT_FOUND" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "_id" + }, + "value": { + "nullable": true, + "type": "string", + "example": "64df3aad42cbaf850a3f891f" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 404 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorNotUnique": { + "title": "Error on Not Unique", + "description": "Error happen if an unique value already exists in DB", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_UNIQUE" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 409 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Field \"name\" with value \"John\" already exists" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_UNIQUE" + ], + "type": "string", + "example": "NOT_UNIQUE" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "name" + }, + "value": { + "nullable": true, + "type": "string", + "example": "John" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 409 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorAuthorization": { + "title": "Error on Authorization", + "description": "Logged-in user does not have a permission to execute the action", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_AUTHORIZED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 403 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "The logged-in user has no permission to execute create_request action" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_AUTHORIZED" + ], + "type": "string", + "example": "NOT_AUTHORIZED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string", + "example": "create_request" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 403 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorServer": { + "title": "Error on Server", + "description": "Error happen on server side", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "UNEXPECTED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "enum": [ + 500, + 503 + ], + "type": "integer", + "example": 500 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Unexpected error happen" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "MISCONFIGURED", + "NOT_AVAILABLE", + "UNEXPECTED" + ], + "type": "string", + "example": "UNEXPECTED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "enum": [ + 500, + 503 + ], + "type": "integer", + "example": 500 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ClanExclude": { + "required": [ + "player_id" + ], + "type": "object", + "properties": { + "player_id": { + "description": "the Player _id which need to be excluded from the clan", + "type": "string", + "example": "666720806cc90102f60bd325" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "The key in the data object", + "type": "string", + "example": "Clan" + }, + "modelName": { + "description": "The name of the model class in data object", + "type": "string", + "example": "Clan" + }, + "dataType": { + "description": "The type of the data: Array or Object", + "type": "string", + "example": "Array" + }, + "dataCount": { + "description": "The count of data items(dataType is Array)", + "type": "integer", + "example": 1 + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "description": "The current page number", + "type": "integer", + "example": 1 + }, + "limit": { + "description": "The limit of items per page", + "type": "integer", + "example": 20 + }, + "offset": { + "description": "The offset of items", + "type": "integer", + "example": 0 + }, + "itemCount": { + "description": "The total number of items", + "type": "integer", + "example": 1 + }, + "pageCount": { + "description": "The total number of pages", + "type": "integer", + "example": 1 + } + } + } + } + } + } + }, + "description": "Success request, response with body. [Here is more about GET requests](https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests))" + }, + "201": { + "content": { + "application/json": { + "schema": { + "description": "Created object. The response objects has two fields: data, metaData.\n\nThe data has the created object data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "required": [], + "type": "object" + } + } + }, + "description": "Created, response for POST requests with body" + }, + "204": { + "description": "Successful request, response with no body" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIConflictError" + }, + "example": { + "statusCode": 400, + "message": [ + "password must be a string" + ], + "error": "Bad Request" + } + } + }, + "description": "Validation error. Some of the fields are not specified, have wrong data types or any over validation problem", + "x-last-modified": 1718798714047 + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 401, + "message": "The access token is not provided. Please add `authorization` field with access token(in bearer token form): `Bearer access-token-here` to request header. The access token you can get from /auth endpoint", + "error": "Unauthorized" + } + } + }, + "description": "Not authenticated. The Authorization header is not specified or the token is expired. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 403, + "message": "The logged-in user has no permission to execute update_request action", + "error": "Forbidden" + } + } + }, + "description": "No permission. The logged-in user has no permission to execute the action. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 404, + "message": "Can not find any Chat instances", + "error": "Not Found" + } + } + }, + "description": "Not found. No object(s) found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIConflictError" + }, + "example": { + "statusCode": 409, + "message": [ + "Field 'username' with value 'user1' already exists" + ], + "error": "Conflict" + } + } + }, + "description": "Some of the fields are not unique " + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 500, + "message": "Internal server error" + } + } + }, + "description": "Unexpected error happened. [Please create a new issue here](https://github.com/Alt-Org/Altzone-Server/issues) and specify the endpoint, HTTP method and description if u have any", + "x-last-modified": 1718799573835 + }, + "200_one": { + "content": { + "application/json": { + "schema": { + "description": "Found object. The response objects has two fields: data and metaData.\nThe data has the requested data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "The key in the data object", + "type": "string", + "example": "Clan" + }, + "modelName": { + "description": "The name of the model class in data object", + "type": "string", + "example": "Clan" + }, + "dataType": { + "description": "The type of the data: Array or Object", + "type": "string", + "example": "Object" + }, + "dataCount": { + "description": "The count of data items(dataType is Array)", + "type": "integer", + "example": 1 + } + } + } + } + } + } + }, + "description": "Success request, response with body. [Here is more about GET requests](https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests))" + }, + "400_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 400 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorValidation" + } + } + } + } + } + }, + "description": "Validation error. Some of the fields are not specified, have wrong data types or any over validation problem" + }, + "401_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 401 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorAuthentication" + } + } + } + } + } + }, + "description": "Not authenticated. The Authorization header is not specified or the token is expired. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "403_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 403 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorAuthorization" + } + } + } + } + } + }, + "description": "No permission. The logged-in user has no permission to execute the action. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "404_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 404 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorNotFound" + } + } + } + } + } + }, + "description": "Not found. No object(s) found" + }, + "409_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 409 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorNotUnique" + } + } + } + } + } + }, + "description": "Some of the fields are not unique " + }, + "500_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 500 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorServer" + } + } + } + } + } + }, + "description": "Unexpected error happened. [Please create a new issue here](https://github.com/Alt-Org/Altzone-Server/issues) and specify the endpoint, HTTP method and description if u have any" + } + }, + "parameters": { + "_id": { + "deprecated": false, + "name": "_id", + "description": "_id of the object", + "in": "path", + "required": true, + "x-last-modified": 1718961163935 + } + }, + "examples": { + "BattleCharacterNoId": { + "value": { + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "customCharacter_id": "6480a27887b5e6ba2a0f630d" + } + }, + "BattleCharacterWithId": { + "value": { + "_id": "648acf3a409da618287a1ca1", + "unityKey": "somekey", + "name": "My custom1", + "resistance": 56, + "speed": 10, + "attack": 28, + "defence": 10, + "characterClassName": "my char", + "gestaltCycle": 1, + "characterClass_id": "648acec6409da618287a1c8e", + "customCharacter_id": "648acf23409da618287a1c98" + } + }, + "CharacterClassNoId": { + "value": { + "name": "my char", + "gestaltCycle": 1, + "speed": 23, + "resistance": 45, + "attack": 10, + "defence": 12 + } + }, + "CharacterClassWithId": { + "value": { + "_id": "648095322a99de2e3bdee480", + "name": "my char", + "gestaltCycle": 1, + "speed": 23, + "resistance": 45, + "attack": 10, + "defence": 12 + } + }, + "ClanNoId": { + "value": { + "name": "clan", + "gameCoins": 260, + "tag": "some tag" + } + }, + "ClanWithId": { + "value": { + "_id": "648091def1bdcdb2a19af6da", + "name": "clan", + "gameCoins": 260, + "tag": "some tag" + } + }, + "CustomCharacterNoId": { + "value": { + "unityKey": "somekey", + "name": "My custom1", + "speed": 10, + "resistance": 56, + "attack": 28, + "defence": 10, + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "player_id": "64809d1336b0779ede245fc1" + } + }, + "CustomCharacterWithId": { + "value": { + "_id": "647f520fa8a94ef3c91cb0e3", + "unityKey": "somekey", + "name": "My custom1", + "speed": 10, + "resistance": 56, + "attack": 28, + "defence": 10, + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "player_id": "64809d1336b0779ede245fc1" + } + }, + "FurnitureNoId": { + "value": { + "name": "furn1", + "shape": "shape1", + "weight": 12, + "material": "mat1", + "recycling": "rec1", + "unityKey": "unity1", + "filename": "file1.txt", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "FurnitureWithId": { + "value": { + "_id": "648ad033409da618287a1ca5", + "name": "furn1", + "shape": "shape1", + "weight": 12, + "material": "mat1", + "recycling": "rec1", + "unityKey": "unity1", + "filename": "file1", + "clan_id": "648acf02409da618287a1c91" + } + }, + "PlayerNoId": { + "value": { + "name": "Anna", + "backpackCapacity": 20, + "uniqueIdentifier": "1", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "PlayerWithId": { + "value": { + "_id": "648acf12409da618287a1c95", + "name": "Anna", + "backpackCapacity": 23, + "uniqueIdentifier": "1", + "clan_id": "648acf02409da618287a1c91" + } + }, + "RaidRoomNoId": { + "value": { + "type": 0, + "rowCount": 4, + "colCount": 6, + "player_id": "64809d1336b0779ede245fc1", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "RaidRoomWithId": { + "value": { + "_id": "647f53b5a8a94ef3c91cb104", + "type": 0, + "rowCount": 4, + "colCount": 6, + "player_id": "64809d1336b0779ede245fc1", + "clan_id": "64809d0a36b0779ede245fbf" + } + } + }, + "securitySchemes": { + "JWTAuth": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http", + "description": "Example of header: \nAuthorization: Bearer access_token" + } + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "tags": [ + { + "name": "release-03-09-24", + "description": "Endpoints affected in 03.09.2024 release" + }, + { + "name": "in-development", + "description": "endpoint is changing or not yet implemented" + }, + { + "name": "Profile", + "description": "profile" + }, + { + "name": "Clan", + "description": "clan" + }, + { + "name": "Player", + "description": "player" + }, + { + "name": "SoulHome", + "description": "soulhome" + }, + { + "name": "Room", + "description": "room" + }, + { + "name": "Stock", + "description": "stock" + }, + { + "name": "Item", + "description": "item" + }, + { + "name": "CustomCharacter", + "description": "CustomCharacter" + }, + { + "name": "Auth", + "description": "Authentication related features" + }, + { + "name": "Chat", + "description": "chats and their messages " + } + ], + "externalDocs": { + "description": "GitHub wikipages", + "url": "https://github.com/Alt-Org/Altzone-Server/wiki" + } +} \ No newline at end of file diff --git a/swagger/releases/03-10-2024-release.json b/swagger/releases/03-10-2024-release.json new file mode 100644 index 00000000..70088ccd --- /dev/null +++ b/swagger/releases/03-10-2024-release.json @@ -0,0 +1,4999 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Altzone API", + "version": "1.0.0", + "description": "This is a description of API for the Altzone game.\nThis swagger document is describing endpoint in more details and includes the request objects structure, examples, authorization rules and any other additional information.\nNotice, that it does not include common rules and functions, since they are covered on the wikipages in GitHub", + "x-logo": { + "url": "" + } + }, + "servers": [ + { + "url": "https://altzone.fi/api", + "description": "Production. Stable version of API, which might not have all of the new changes" + }, + { + "url": "http://localhost:8080", + "description": "Local" + }, + { + "url": "https://devapi.altzone.fi", + "description": "Development. Current version of API with latest changes" + } + ], + "paths": { + "/clan": { + "get": { + "tags": [ + "Clan", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": [ + { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User2 clan", + "tag": "tag2", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, + "id": "667eedc9b3b5bf0f7a840ef1" + }, + { + "_id": "667bfec6afb8211b4bd8dbff", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 4, + "admin_ids": [ + "665df7026bf5b8f670569ea4" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, + "id": "667bfec6afb8211b4bd8dbff" + } + ] + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 2, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Read all clans", + "description": "Read all created Clans. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Clan object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanUpdate" + }, + "examples": { + "Clan": { + "$ref": "#/components/examples/ClanWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "Clan", + "release-03-10-2024" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "409": { + "$ref": "#/components/responses/409_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a clan", + "description": "Update the Clan, which _id is specified in the body. \n\nOnly Clan admins can change the Clan's data, as well as add or remove admins. Notice that while removing Clan admins, its is not allowed to remove all of them, because Clan must have at least one admin.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins | \n\n\nThe labels enum values:\n\n - 'eläinrakkaat',\n - 'maahanmuuttomyönteiset',\n - 'lgbtq+',\n - 'raittiit',\n - 'kohteliaat',\n - 'kiusaamisenvastaiset',\n - 'urheilevat',\n - 'syvälliset',\n - 'oikeudenmukaiset',\n - 'kaikkien kaverit',\n - 'itsenäiset',\n - 'retkeilijät',\n - 'suomenruotsalaiset',\n - 'huumorintajuiset',\n - 'rikkaat',\n - 'ikiteinit',\n - 'juoruilevat',\n - 'rakastavat',\n - 'oleilijat',\n - 'nörtit',\n - 'musadiggarit',\n - 'tunteelliset',\n - 'gamerit',\n - 'animefanit',\n - 'sinkut',\n - 'monikulttuuriset',\n - 'kauniit',\n - 'järjestelmälliset',\n - 'epäjärjestelmälliset',\n - 'tasa-arvoiset',\n - 'somepersoonat',\n - 'kädentaitajat',\n - 'muusikot',\n - 'taiteilijat',\n - 'spämmääjät',\n - 'kasvissyöjät',\n - 'tasapainoiset'" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanCreate" + } + } + }, + "required": true + }, + "tags": [ + "Clan", + "release-03-10-2024" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": { + "objectType": "ClanDto", + "_id": "66d74887c988e09f4c45870a", + "name": "clan2", + "tag": "clan2", + "gameCoins": 0, + "admin_ids": [ + "66d7067645b40dd7f491e753" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "points": 0, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "Stock": { + "objectType": "StockDto", + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a" + }, + "SoulHome": { + "objectType": "SoulHomeDto", + "_id": "66d74887c988e09f4c458715", + "name": "clan2", + "clan_id": "66d74887c988e09f4c45870a" + } + } + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "409": { + "$ref": "#/components/responses/409_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a clan", + "description": "Create a new Clan. The creator of the Clan becomes its admin. Notice that if Player is creating a new Clan, he/she becomes a member of it, that means that if Player is member of some Clan it can not create a new one, before leaving the old one.\n\nAlso endpoint creates Clan's Stock containing default Items, as well as Clan's SoulHome and Rooms.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | Player can be in one Clan only at one time => if Player already is in another Clan, he/she can not create a new one | \n\n" + } + }, + "/profile": { + "summary": "user profile", + "description": "", + "get": { + "tags": [ + "Profile" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Profile": { + "_id": "667ee778b3b5bf0f7a840ec9", + "username": "user1" + } + }, + "metaData": { + "dataKey": "Profile", + "modelName": "Profile", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read logged-in user Profile data", + "description": "Read logged-in user Profile data\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only own |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Profile" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update user Profile", + "description": "Update logged-in user Profile data. Notice that only fields needed to be updated should be specified." + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileCreate" + } + } + }, + "required": true + }, + "tags": [ + "Profile" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Profile": { + "objectType": "ProfileDto", + "_id": "667ee778b3b5bf0f7a840ec9", + "username": "user6", + "Player": { + "objectType": "PlayerDto", + "_id": "667ee778b3b5bf0f7a840ecb", + "name": "User 6", + "backpackCapacity": 34, + "uniqueIdentifier": "6", + "profile_id": "667ee778b3b5bf0f7a840ec9" + } + } + }, + "metaData": { + "dataKey": "Profile", + "modelName": "Profile", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "operationId": "profile_create", + "summary": "Create profile", + "description": "Create a user profile with Player object associated with it. Notice, that it is also possible in some edge cases to create a Profile without Player object assosiated with it, however it is not recommended and API expects that for every Profile there is a Player object created. " + }, + "delete": { + "tags": [ + "Profile" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Profile", + "description": "Delete logged-in user's Profile. Notice, that Profile deletion will lead removing all user data, such as Player and CustomCharacters. Since the Player object is assosiated with the Clan, user will be also removed from the Clan. Notice, that if there was nobody in the Clan the Clan with all assosiated objects will be removed. However, in case if the user was admin in this Clan and there are no other admins the user must first set at least one admin for this Clan, overwise the Profile will not be removed and 403 will be returned." + } + }, + "/player": { + "get": { + "tags": [ + "Player", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": [ + { + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 20 + } + ] + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 1, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read all players", + "description": "Read all created Players. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Player object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerUpdate" + }, + "examples": { + "Player": { + "$ref": "#/components/examples/PlayerWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "Player", + "release-03-10-2024" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a player", + "description": "Update the Player, which _id is specified in the body. Only Player, which belong to the logged-in Profile can be changed.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Player owner | \n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlayerCreate" + } + } + }, + "required": true + }, + "tags": [ + "Player", + "release-03-10-2024" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": { + "objectType": "PlayerDto", + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 0 + } + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a player for logged-in user", + "description": "Create a new Player. This is not recommended way of creating a new Player and it should be used only in edge cases. The recommended way is to create it via /profile POST endpoint.\n\nPlayer is representing an object, which holds data related to game player. This object can be used inside the game for example while joining a Clan. Notice, that the Profile object should not be used inside the game (except for logging-in). \n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | Player can be associated with only one Profile | \n" + } + }, + "/player/{_id}": { + "get": { + "tags": [ + "Player", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Player": { + "_id": "6686cb3b71adebdb10f33ffb", + "name": "User 2", + "backpackCapacity": 654, + "uniqueIdentifier": 2, + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 20 + } + }, + "metaData": { + "dataKey": "Player", + "modelName": "Player", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Player by _id", + "description": "Read Player data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Player" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Player", + "description": "Delete Player by its _id field. Notice that only Player, which belongs to loggen-in user Profile can be deleted. In case when the Player is the only admin in some Clan and the Clan has some other Players, the Player can not be removed. User should be asked to first determine at least one admin for the Clan.\n\nAlso, it is not recommended to delete the Player since it can itroduce unexpected behaviour for the user with Profile, but without Player. The better way to remove the Player is do it via /profile DELETE.\n\nPlayer removal basically means removing all data, which is related to the Player: CustomCharacters, Clan, except for the Profile data. In the case when the Profile does not has a Player, user can only login to the system, but can not play the game.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Player owner |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "6686cb3b71adebdb10f33ffb" + } + }, + "name": "_id", + "description": "player _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/clan/{_id}": { + "get": { + "tags": [ + "Clan", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Clan": { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true + } + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Clan by _id", + "description": "Read Clan data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Clan" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Clan", + "description": "Delete Clan its _id field. \n\nNotice that only Clan admins can delete the Clan.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins |" + }, + "parameters": [ + { + "examples": { + "_id": { + "value": "667462842425aea94d0f66cb" + } + }, + "name": "_id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/clan/join": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanJoinCreate" + } + } + }, + "required": true + }, + "tags": [ + "Clan" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Player requests join to clan", + "description": "Request to join a Clan. \n\nNotice that if the Clan is open the Player will be joined automatically without admin approval. \n\nNotice that if the Player was in another Clan then he/she will be removed from the old one and if in this Clan was no other Players, it will be removed.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no, but for closed Clans there should be an admin approval first | \n\n" + } + }, + "/clan/leave": { + "post": { + "tags": [ + "Clan" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401", + "x-last-modified": 1718901883486 + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500", + "x-last-modified": 1718902019444 + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Player requests leave the Clan", + "description": "Request to leave a Clan. \n\nNotice that Player can leave any Clan without admin approval.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no | \n\n" + } + }, + "/stock": { + "get": { + "tags": [ + "Stock" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Stock": [ + { + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a", + "__v": 0, + "id": "66d74887c988e09f4c45870d" + }, + { + "_id": "66d70d08941f1d7be5033a3f", + "cellCount": 20, + "clan_id": "66d70d08941f1d7be5033a3c", + "__v": 0, + "id": "66d70d08941f1d7be5033a3f" + } + ] + }, + "metaData": { + "dataKey": "Stock", + "modelName": "Stock", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Read all stocks", + "description": "Read all created Stocks of all Clans. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## Endpoint description\n\n### General description\n\nStock is a public place of a Clan, where Items can be stored. These Items, however, can not be stolen from the Clan by other Clans' members during raid. \n\n### Relations\n\nStock can belong to one Clan and Clan may have only one Stock. Stock may have many Items. Stock may belong to one Stock.\n\n### CRUD operations\n\nSince a Clan can have only one Stock, the Stock is created automatically whenever a Clan is created.\n\nStock data can be read by anybody without authentication, as well all existing Stocks list can be fetched.\n\nStock does not have any data, which can be updated by Clan members. The only field, which can be updated is a cellCount, which hold information about Stock capacity. The starting value of it is 30, but more cells can be bought from store later. The cellCount does not have a limit.\n\nStock is deleted automatically (and Items related to it) when the Clan to which it belongs to is also deleted.", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + } + }, + "/stock/{_id}": { + "get": { + "tags": [ + "Stock" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Stock": { + "objectType": "StockDto", + "_id": "66d74887c988e09f4c45870d", + "cellCount": 20, + "clan_id": "66d74887c988e09f4c45870a" + } + }, + "metaData": { + "dataKey": "Stock", + "modelName": "Stock", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Stock by _id", + "description": "Read Stock data by its _id field.\n\nNotice that everybody is able to read any Stock data.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n\n## Endpoint description\n\n### General description\n\nStock is a public place of a Clan, where Items can be stored. These Items, however, can not be stolen from the Clan by other Clans' members during raid. \n\n### Relations\n\nStock can belong to one Clan and Clan may have only one Stock. Stock may have many Items. Stock may belong to one Stock.\n\n### CRUD operations\n\nSince a Clan can have only one Stock, the Stock is created automatically whenever a Clan is created.\n\nStock data can be read by anybody without authentication, as well all existing Stocks list can be fetched.\n\nStock does not have any data, which can be updated by Clan members. The only field, which can be updated is a cellCount, which hold information about Stock capacity. The starting value of it is 30, but more cells can be bought from store later. The cellCount does not have a limit.\n\nStock is deleted automatically (and Items related to it) when the Clan to which it belongs to is also deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66894fbd4a7b6c993aed1a18" + } + }, + "name": "_id", + "description": "Stock _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/item/{_id}": { + "get": { + "tags": [ + "Item" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Item": { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "location": [ + 2, + 4 + ], + "isFurniture": true, + "stock_id": "668953cd4a7b6c993aed1a36", + "price": 23 + } + }, + "metaData": { + "dataKey": "Item", + "modelName": "Item", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get Item by _id", + "description": "Read Item data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, + "name": "_id", + "description": "item _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/soulhome": { + "description": "", + "get": { + "tags": [ + "SoulHome" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "SoulHome": { + "objectType": "SoulHomeDto", + "_id": "66d74887c988e09f4c458715", + "name": "clan2", + "clan_id": "66d74887c988e09f4c45870a" + } + }, + "metaData": { + "dataKey": "SoulHome", + "modelName": "SoulHome", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get SoulHome", + "description": "## Method description\n\nGet SoulHome data for the logged-in user. \n\nIf the logged-in user is a Clan member, the SoulHome for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nSoulhome is a safe place for storing Clan Items (inside SoulHome's Rooms). However, it is possible to steal Items from the SoulHome rooms, but the access to CRUD operations mostly lies in hands of Clan members.\n\nSoulHome does have only one field \"name\", which is same as a Clan name.\n\n### Relations\n\nSoulHome can belong to one Clan and Clan may have only one SoulHome. SoulHome may have many Rooms. Room may belong to one SoulHome.\n\n### CRUD operations\n\nSoulHome must be created automatically when Clan is created. When SoulHome is created 30 Rooms for that SoulHome with default values must created." + } + }, + "/room": { + "get": { + "tags": [ + "Room" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Room": [ + { + "_id": "66d74887c988e09f4c458721", + "floorType": "default", + "wallType": "default", + "cellCount": 10, + "hasLift": false, + "soulHome_id": "66d74887c988e09f4c458715", + "isActive": false, + "id": "66d74887c988e09f4c458721" + } + ] + }, + "metaData": { + "dataKey": "Room", + "modelName": "Room", + "dataType": "Array", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get all Clan's Rooms", + "description": "## Method description\n\nGet all Rooms for the logged-in user. \n\nIf the logged-in user is a Clan member, the Rooms for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan the 404 error will be returned.\n\nIf the pagination is required, it can be used, but by default it will return all 30 rooms at once.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Pagination | yes |\n| Sort | yes |\n| Search | yes |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + }, + "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoomUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Room" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update Room by _id", + "description": "## Method description\n\nUpdate Room by its _id specified in the body.\n\nAny Clan member can update any Room, which (SoulHome) belongs to the Clan.\n\nIf the logged-in user is a Clan member and the Clan does have the requested Room, the Room for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan, or Room in that Clan with provided _id is not found the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. Items from the Room can not be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + } + }, + "/fleaMarket": { + "get": { + "tags": [ + "in-development", + "FleaMarket" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "ItemShop": { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "lastRestock": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "item_id": { + "type": "string" + }, + "isInVoting": { + "type": "boolean" + }, + "isSold": { + "type": "boolean" + } + } + } + }, + "__v": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + } + } + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "dataType": { + "type": "string" + }, + "dataCount": { + "type": "integer" + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "itemCount": { + "type": "integer" + }, + "pageCount": { + "type": "integer" + } + } + } + } + }, + "examples": { + "1": { + "value": { + "data": { + "FleaMarketItem": [ + { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "isFurniture": true, + "price": 23, + "status": "Available", + "clan_id": "668953cd4a7b6c993aed1a36" + } + ] + }, + "metaData": { + "dataKey": "FleaMarketItem", + "modelName": "FleaMarketItem", + "dataType": "Array", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Success, response with body" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get all flea market items", + "description": "Get all FleaMarketItems of the flea market\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" + } + }, + "/fleaMarket/{_id}": { + "get": { + "tags": [ + "in-development", + "FleaMarket" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "ItemShop": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "lastRestock": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "item_id": { + "type": "string" + }, + "isInVoting": { + "type": "boolean" + }, + "isSold": { + "type": "boolean" + } + } + } + }, + "__v": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + } + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "dataType": { + "type": "string" + }, + "dataCount": { + "type": "integer" + } + } + } + } + }, + "examples": { + "1": { + "value": { + "data": { + "FleaMarketItem": { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "isFurniture": true, + "price": 23, + "status": "Available", + "clan_id": "668953cd4a7b6c993aed1a36" + } + }, + "metaData": { + "dataKey": "FleaMarketItem", + "modelName": "FleaMarketItem", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Success, response with body" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get flea market item data by _id", + "description": "Get an idividual FleaMarketItem by its mongo _id field.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, + "name": "_id", + "description": "FleaMarketItem _id to get", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/chat": { + "get": { + "tags": [ + "Chat" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": [ + { + "objectType": "ChatDto", + "_id": "66d8611a184d5f0a7c0675b7", + "name": "chat1" + }, + { + "objectType": "ChatDto", + "_id": "66d85e3f4bed433708d5cb7f", + "name": "Chat 1" + } + ] + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read all existing chats", + "description": "Read all created Chats. Remember about the pagination.\n\nNotice, that use of messages array is not advised and can be removed at some point in the future. For accessing messages of the Chat please use the /chat/:_id/message endpoint.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "Chat object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatUpdate" + } + } + }, + "required": true + }, + "tags": [ + "Chat" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a chat", + "description": "Update the Chat, which _id is specified in the body.\n\nNotice that currently anybody is able to change any Chat.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Players above 13 or with parents approval | \n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatCreate" + } + } + }, + "required": true + }, + "tags": [ + "Chat" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": { + "objectType": "ChatDto", + "_id": "66d86147184d5f0a7c0675ca", + "name": "chat2" + } + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a chat", + "description": "Create a new Chat. The Chat is an object containing messages.\n\nNotice, that currently there is no restrictions on who can create a Chat.\n\nNotice that the Message objects are inner objects of Chat and can not be used enewhere else than in the Chat. There is also no separate collection for the Message in the DB.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Players above 13 or with parents approval | \n" + } + }, + "/chat/{_id}": { + "get": { + "tags": [ + "Chat" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": { + "objectType": "ChatDto", + "_id": "66d8611a184d5f0a7c0675b7", + "name": "chat1" + } + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get Chat by _id", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead Chat data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + }, + "delete": { + "tags": [ + "Chat" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete Chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nDelete Chat by its _id field. \n\nNotice that currently anybody can delete any Chat.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "654fad58fd96261edd2e096f" + } + }, + "name": "_id", + "description": "Chat _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/chat/{_id}/messages": { + "get": { + "tags": [ + "Chat" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "Chat": [ + { + "id": 15, + "senderUsername": "user1", + "content": "message 15", + "feeling": 1 + } + ] + }, + "metaData": { + "dataKey": "Chat", + "modelName": "Chat", + "dataType": "Array", + "dataCount": 1 + }, + "paginationData": { + "currentPage": 1, + "limit": 20, + "offset": 0, + "itemCount": 1, + "pageCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Read all messages from the Chat", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all messages of specified Chat. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageCreate" + } + } + }, + "required": true + }, + "tags": [ + "Chat" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a message", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Message. Message represent the object of message sent by a Player.\n\nNotice that currently there are no authorization required.\n\nNotice, that the messages does not have an usual _id field generated by data base. Instead the Photon id should be used.\n\nNotice that the messages is contained in the array of a Chat collection.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66912712d191c865ab53da8a" + } + }, + "name": "_id", + "description": "Chat _id, with which the message is associated", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/customCharacter": { + "get": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Read all custom characters", + "description": "Read all custom characters. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "externalDocs": { + "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" + } + }, + "put": { + "requestBody": { + "description": "CustomCharacter object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomCharacterUpdate" + }, + "examples": { + "CustomCharacter": { + "$ref": "#/components/examples/CustomCharacterWithId" + } + } + } + }, + "required": true + }, + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "description": "Success, no body" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Update a custom character", + "description": "Update the CustomCharacter, which _id is specified in the body.\n\nOnly the Player, that owns the CustomCharacter can change it.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for character owner |\n" + }, + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomCharacterCreate" + } + } + }, + "required": true + }, + "tags": [ + "CustomCharacter" + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/201" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Created" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "409": { + "$ref": "#/components/responses/409" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Create a custom character", + "description": "Create a new CustomCharacter. CustomCharacter represents a character of the Player. Player can have many CustomCharacters, CustomCharacter can belong to only one Player.\n\nNotice, that Player can create CustomCharacters only for himself/herself not for other Players.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only own |\n" + } + }, + "/customCharacter/{_id}": { + "get": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "CustomCharacter": { + "objectType": "CustomCharacterDto", + "_id": "66939de4db98c5ba6bb192f1", + "unityKey": "somekey12", + "name": "My custom5", + "speed": 37, + "resistance": 40, + "attack": 55, + "defence": 14, + "characterClass_id": "66939d9f9fdbf76f01d5dbd5", + "player_id": "665df7026bf5b8f670569ea4" + } + }, + "metaData": { + "dataKey": "CustomCharacter", + "modelName": "CustomCharacter", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Get CustomCharacter by id", + "description": "Read CustomCharacter data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no |" + }, + "delete": { + "tags": [ + "CustomCharacter" + ], + "responses": { + "200": { + "description": "Success, no body" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Delete CustomCharacter", + "description": "Delete CustomCharacter by its _id field. Notice that only the CustomCharacter owner can delete the CustomCharacter.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for CustomCharacter |" + }, + "parameters": [ + { + "examples": { + "1": { + "value": "66939de4db98c5ba6bb192f1" + } + }, + "name": "_id", + "description": "CustomCharacter _id field", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/room/{_id}": { + "get": { + "tags": [ + "Room" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_one" + }, + "examples": { + "1": { + "value": { + "data": { + "Room": { + "_id": "668d6003a9292e300e94c833", + "floorType": "floor1", + "wallType": "wall1", + "isActive": false, + "deactivationTimestamp": 34524523654, + "cellCount": 20, + "soulHome_id": "667ef139b3b5bf0f7a840f2d", + "hasLift": true + } + }, + "metaData": { + "dataKey": "Room", + "modelName": "Room", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get Room by _id", + "description": "## Method description\n\nGet Room by its _id. \n\nIf the logged-in user is a Clan member and the Clan does have the requested Room, the Room for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan, or Room in that Clan with provided _id is not found the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen.\n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "667ee778b3b5bf0f7a840ec9" + } + }, + "name": "_id", + "description": "Room _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/room/activate": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "room_ids" + ], + "type": "object", + "properties": { + "room_ids": { + "description": "List of Room _ids to activate", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + }, + "durationS": { + "description": "Optional duration of room to be active in seconds", + "type": "number", + "example": 60 + } + } + } + } + }, + "required": true + }, + "tags": [ + "Room" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Activate the Room by _id", + "description": "## Method description\n\nActivate the specified Rooms.\n\nIf Room _id specified in the room_ids field does not belong to logged-in user's Clan (SoulHome), it will be ignored. However it will return 404 if non of the Room _ids does not belong to the Clan. \n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. However, Items from the Room can be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." + } + }, + "/item/steal": { + "get": { + "tags": [ + "Item", + "release-03-10-2024" + ], + "parameters": [ + { + "name": "steal_token", + "description": "", + "schema": { + "type": "string" + }, + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "SoulHome": { + "_id": "668c0e9f9470819cf38e371c", + "name": "clan1", + "clan_id": "667bfec6afb8211b4bd8dbff", + "Room": [ + { + "_id": "668d6003a9292e300e94c833", + "floorType": "floor1", + "wallType": "wall1", + "isActive": false, + "deactivationTimestamp": 34524523654, + "cellCount": 20, + "soulHome_id": "668c0e9f9470819cf38e371c", + "hasLift": true + } + ] + } + }, + "metaData": { + "dataKey": "SoulHome", + "modelName": "SoulHome", + "dataType": "Object", + "dataCount": 1 + } + } + } + } + } + }, + "description": "SoulHome and its Rooms" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get SoulHome data from which items can be stolen", + "description": "Based on the provided steal token, which contains the SoulHome _id from which Items can be stolen, the SoulHome data and its Rooms will be returned.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" + }, + "post": { + "requestBody": { + "description": "Items to be stolen", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "steal_token": "some_token", + "item_ids": [ + "668a765091020196cb10d5a3", + "668a765091020196cb10d5a3" + ], + "room_id": "668a765091020196cb10d5a3" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Item", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "Item": [ + { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "filename": "file1", + "location": [ + 2, + 4 + ], + "isFurniture": true, + "stock_id": "668953cd4a7b6c993aed1a36", + "price": 23 + } + ] + }, + "metaData": { + "dataKey": "Item", + "modelName": "Item", + "dataType": "Array", + "dataCount": 1 + } + } + } + } + } + }, + "description": "Array of Items, which were stolen" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Steal Items from another Clan", + "description": "Steal Items from the loser Clan's SoulHome. The stolen Items will be automatically added to a specified Room of the winners Clan's SoulHome.\n\nNotice that at first a steal token should be obtained by the winner player(s) from the /gameData/battle POST endpoint with body type \"result\", while informing about game result.\n\nRequests without the steal token or with an expired token will get 403 as a response.\n\nNotice that only found SoulHome Items will be stolen. It is possible that some other player already has stoled some of the specified Items, in this case these Items will be ignored since they can not be stolen twice. \n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + } + }, + "/gameData/battle": { + "post": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "result": { + "value": { + "type": "result", + "team1": [ + "667bfec6afb8211b4bd8dbff", + "597bfec6afb8211b4bd8db64" + ], + "team2": [ + "397bfec6adb8211b4bd8dbe0", + "207bfec6adb8211b4bd8dba1" + ], + "duration": 120, + "winnerTeam": 1 + } + } + } + } + }, + "required": true + }, + "tags": [ + "GameData", + "release-03-10-2024" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "result": { + "value": { + "steal_token": "temporary_token_to_use_for_/room/steal", + "soulHome_id": "667ee778b3b5bf0f7a840ec9", + "room_ids": [ + "668d6003a9292e300e94c833" + ] + } + } + } + } + }, + "description": "" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Inform API about battle", + "description": "Enpoint for notifying the API about battle events or any other data.\n\nNotice, that the field type is required and determines the type of the data.\n\nNotice that the type also determines shape of the body. Examples, for each type can be found in request examples section.\n\n## Type field\n\n### result\n\nResult of the battle, all players of the battle should send this data.\n\nAs a response for winners an access token will be returned, which can be used when stealing Items from losed Clan's SoulHome. Notice that the steal token will expire after some period of time. Losers will get 403 error = they can not get the steal token.\n\nThe steal token can be used only by the winner's Clan's members for the loser's Clan Stock.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" + } + }, + "/item/move": { + "post": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "397bfec6adb8211b4bd8dbe0", + "moveTo": "Stock", + "destination_id": "667bfec6afb8211b4bd8dbff" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Item", + "release-03-10-2024" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Move Item", + "description": "Move Item from Stock to Room or from Room to Stock, based on the _moveTo_ field, which can have only two values: \"Stock\" or \"Room\"\n\nNotice that Clan members can move only own Clan Items and a member is trying to move other Clan's Item the 404 will be returned.\n\nNotice that if an Item need to be moved to a Stock then there is no need to specify the destination_id field, since Clan can have only one Stock.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." + } + }, + "/fleaMarket/buy": { + "post": { + "requestBody": { + "description": "Item to buy", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "66d8611a184d5f0a7c0675b7" + } + } + } + } + }, + "required": true + }, + "tags": [ + "in-development", + "FleaMarket" + ], + "responses": { + "201": { + "$ref": "#/components/responses/201" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Buy an Item from the flea market", + "description": "Buy an Item from the flea market. This will start a voting in the Clan for which Item is being purchased. Voting duration is 10 min at max and the min approval percentage is 51. During the voting an Item is in \"Booked\" status and can not be bought by other players.\n\nNotice that if a FleaMarketItem has already \"Booked\" status 403 will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only buying Clan members |\n\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" + } + }, + "/auth/signIn": { + "post": { + "requestBody": { + "description": "Profile credentials", + "content": { + "application/json": { + "schema": { + "required": [ + "username", + "password" + ], + "type": "object", + "properties": { + "username": { + "description": "The username of the profile", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password of the profile", + "type": "string", + "example": "myPassword" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Auth" + ], + "responses": { + "201": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "objectType": "ProfileDto", + "_id": "651a716b5129c6e54cb7d7c9", + "username": "user1", + "__v": 0, + "Player": { + "objectType": "PlayerDto", + "_id": "651a716b5129c6e54cb7d7cb", + "name": "User1", + "backpackCapacity": 456, + "uniqueIdentifier": "3", + "profile_id": "651a716b5129c6e54cb7d7c9", + "points": 10, + "__v": 0, + "clan_id": "651a72c35129c6e54cb7d7de" + }, + "Clan": { + "objectType": "ClanDto", + "itemCount": 0, + "isOpen": true, + "_id": "651a72c35129c6e54cb7d7de", + "name": "User1 clan", + "tag": "sometage", + "gameCoins": 645, + "admin_ids": [ + "651a716b5129c6e54cb7d7cb" + ], + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, + "__v": 0, + "playerCount": 2, + "stockCount": 1 + }, + "accessToken": "your_token", + "tokenExpires": 1725799544 + } + } + } + } + }, + "description": "Successful login with the profile data" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + {} + ], + "summary": "Log in to the system", + "description": "After the profile with player was created, the user can log in to the system and get a JWT token to access resources.\n\nIf the user provides the correct credentials, the access token will be returned, which should be used as a Bearer token in the Authorization header.\n\nIf the user provides wrong credentials, a 401 status will be returned." + } + }, + "/clan/exclude": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClanExclude" + } + } + }, + "required": true + }, + "tags": [ + "Clan" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Exclude Player from Clan", + "description": "Request exclude the player from clan. \n\nNotice that only Clan admin can remove the Player\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only clan admin | \n\n" + } + }, + "/gameAnalytics/logFile": { + "post": { + "requestBody": { + "description": "File that needs to be uploaded", + "content": { + "multipart/form-data": {} + }, + "required": true + }, + "tags": [ + "GameAnalytics", + "release-03-10-2024" + ], + "parameters": [ + { + "name": "Secret", + "description": "secret_string", + "schema": { + "type": "string" + }, + "in": "header", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Upload a game analytics log file", + "description": "Enpoint uploads a log file and save on the server.\n\nThe file will be saved to the folder with name corresponding to the date when it was uploaded, i.e. 1-9-2024.\n\nFile name must be unique and therefore its name will contain date, time, Player _id and a random string, i.e. 1-9-2024_14-45-12_667eedc9b3b5bf0f7a840ef1_123456.log\n\nNotice that the request must have a Secret header which holds a password for such requests.\n\nNotice that the request must be in multipart/form-data format, where field name containing the file is \"logFile\"" + } + }, + "/fleaMarket/sell": { + "post": { + "requestBody": { + "description": "Item to sell", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "66d8611a184d5f0a7c0675b7" + } + } + } + } + }, + "required": true + }, + "tags": [ + "in-development", + "FleaMarket" + ], + "responses": { + "201": { + "$ref": "#/components/responses/201" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Sell an Item on the flea market", + "description": "Sell an Item on the flea market. This will start a voting in the Clan from which Item is being moved to the flee marked. Voting min approval percentage is 51. During the voting an Item is in \"Shipping\" status and can not be bought by other players.\n\nNotice that if a FleaMarketItem has already \"Shipping\" status 403 will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only selling Clan members |\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there." + } + }, + "/voting": { + "put": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "voting_id": "667eedc9b3b5bf0f7a840ef1", + "choice": "accept" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Voting", + "in-development" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Send a vote", + "description": "Send a vote.\n\nThe minimum acceptance percentage refering to the min percentage of a group members that need to accept some option for the voting to be concluded. For example Clan members are voting whenever they want to sell an Item or not and the min percentage might be 51 => if 51% of all this Clan members accept to sell the Item then the Item can be sold and otherwise if the 51% of Clan members reject to sell the Item it will not be sold. Same logic can be applied if there are more than 2 options available.\n\nVoting endpoint is served for different kinds of votings. The \"type\" field is an enum and determines what value the \"choice\" field may have.\n\n| type | options |\n| ------------- | ------------------ |\n| selling_item: | \"accept\", \"reject\" |\n| buying_item: | \"accept\", \"reject\" |\n\n\n## selling_item\n\nPlayer votes whenever he/she wants that an item is going for sale to the flea market. The voter must be a member of the Clan from which item is going to sale, otherwise 403 will be returned. The min acceptance percentage is 51%. Notice that during the voting process there will be sent different notifications via MQTT:\n- new voting started\n- somebody has voted\n- voting ended\n\n## buying_item\n\nPlayer votes whenever he/she wants that an item should be bought from the flea market or not. The voter must be a member of the Clan for which item is going to be bought, otherwise 403 will be returned. The min acceptance percentage is 51%. The time limit for the voting is 10 mins. Notice that during the voting process there will be sent different notifications via MQTT:\n- new voting started\n- somebody has voted\n- voting ended\n- error if time limit expired" + } + }, + "/voting/{_id}": { + "get": { + "tags": [ + "Voting", + "in-development" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "_id": "668953cd4a7b6c993aed1a36", + "startedAt": 1727178420, + "organizer_id": "668d6003a9292e300e94c833", + "type": "selling_item", + "player_ids": [ + "66d86147184d5f0a7c0675ca", + "668a765091020196cb10d5a3" + ], + "votes": [ + { + "player_id": "66d86147184d5f0a7c0675ca", + "choice": "accept" + }, + { + "player_id": "668a765091020196cb10d5a3", + "choice": "reject" + } + ], + "minPercentage": 51 + } + } + } + } + }, + "description": "Success, response with body " + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get voting data by _id", + "description": "Get data of the current voting state.\n\nNotice that it can return 403 in case the logged-in player does not have permission to access the voting, for example if the player tries to access a voting that is an internal for other Clan." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, + "name": "_id", + "description": "voting _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/leaderboard": { + "get": { + "tags": [ + "Leaderboard", + "in-development" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "Clan": [ + { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User2 clan", + "tag": "tag2", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "points": 20, + "id": "667eedc9b3b5bf0f7a840ef1" + }, + { + "_id": "667bfec6afb8211b4bd8dbff", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 4, + "admin_ids": [ + "665df7026bf5b8f670569ea4" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "points": 10, + "id": "667bfec6afb8211b4bd8dbff" + } + ] + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 2, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "Success, with body" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get leaderboard data", + "description": "Various leaderboards define the best ones in different categories, for example top Players or top Clans.\n\nNotice that the leaderboards data is updated once every 12 hours.\n\n## Leaderboard parameters\n\n### Player\n\nTop Players are defined by the amount of points that he/she has.\n\n### Clan\n\nTop Clans are defined by the amount of points that each Clan has." + }, + "parameters": [ + { + "examples": { + "top clans": { + "value": "Clan" + } + }, + "name": "category", + "description": "Category of the leaderboard", + "schema": { + "enum": [ + "Clan", + "Player" + ], + "type": "string" + }, + "in": "query", + "required": true + } + ] + }, + "/playerTasks": { + "get": { + "tags": [ + "in-development", + "PlayerTasks" + ], + "responses": { + "200": { + "description": "Success, response with body" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get Player's tasks", + "description": "Returns a list of logged-in Player's tasks. \n\nThe period query works as follows:\n\ntoday - all tasks for today (completed and uncomleted)\n\nweek - all tasks for the current week (completed and uncomleted) including daily tasks.\n\nmonth - all tasks for the current month (completed and uncompleted), including weekly and daily tasks\n\n| Feature | Has |\n| --------------- | ----------------------- |\n| Authentication | yes |\n| Authorization | only Player's own tasks |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## General description\n\nThe daily task is a predefined task for an individual player to perform.\n\nThere are tasks for each day, each two weeks and each month.\n\nPlayer is getting points for each completed task and each task brings different amount of points and coins.\n\nThese points and coins are going to the player's clan. The points are used to determine top clans in the game and the coins are used to purchase items from the flee market of the game." + }, + "parameters": [ + { + "name": "period", + "description": "Period of time for the tasks", + "schema": { + "enum": [ + "today", + "week", + "month" + ], + "type": "string" + }, + "in": "query", + "required": false + } + ] + } + }, + "components": { + "schemas": { + "200": { + "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name, usually same as dataKey", + "type": "string" + }, + "dataType": { + "description": "Object or Array", + "type": "string" + }, + "dataCount": { + "description": "1 for Object dataType and length if Array", + "type": "integer" + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "description": "The current page number", + "type": "integer" + }, + "limit": { + "description": "The limit of items per page", + "type": "integer" + }, + "offset": { + "description": "The offset of items", + "type": "integer" + }, + "itemCount": { + "description": "The total number of items", + "type": "integer" + }, + "pageCount": { + "description": "The total number of pages", + "type": "integer" + } + } + } + } + }, + "201": { + "description": "Created object. The response objects has two fields: data, metaData.\n\nThe data has the created object data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in the data object.", + "required": [ + "data", + "metaData" + ], + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name", + "type": "string" + }, + "dataType": { + "description": "always has Object value", + "type": "string" + }, + "dataCount": { + "description": "always has 1 value", + "type": "integer" + } + } + } + } + }, + "APIError": { + "description": "API error", + "type": "object", + "properties": { + "statusCode": { + "description": "The HTTP status code", + "type": "integer", + "example": 404 + }, + "message": { + "description": "The error message", + "type": "string", + "example": "Cannot GET /api/profile" + }, + "error": { + "description": "The error name", + "type": "string", + "example": "Not Found" + } + }, + "x-last-modified": 1718798371474 + }, + "APIConflictError": { + "description": "API error", + "required": [], + "type": "object", + "properties": { + "statusCode": { + "description": "The HTTP status code", + "type": "integer", + "example": 400 + }, + "message": { + "description": "An array of error messages", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "password must be a string" + ] + }, + "error": { + "description": "The error name", + "type": "string", + "example": "Bad Request" + } + } + }, + "ProfileCreate": { + "type": "object", + "properties": { + "username": { + "description": "The username for the Profile, unique", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password for the Profile", + "type": "string", + "example": "my_password" + }, + "Player": { + "type": "object", + "properties": { + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 453 + }, + "uniqueIdentifier": { + "description": "The identifier for the player, unique", + "type": "string", + "example": "1" + }, + "above13": { + "description": "Is Player age 13 or above", + "type": "boolean", + "example": true + } + } + } + } + }, + "ClanUpdate": { + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "The _id of the clan to be updated", + "type": "string", + "example": "667462842425aea94d0f66cb" + }, + "gameCoins": { + "description": "The number of clan coins", + "type": "integer", + "example": 45 + }, + "tag": { + "description": "The tag associated with the clan", + "type": "string", + "example": "my_tag" + }, + "isOpen": { + "description": "whenever the clan is open or closed", + "type": "boolean", + "example": true + }, + "admin_idsToDelete": { + "description": "List of admin _ids to delete", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + }, + "admin_idsToAdd": { + "description": "List of admin _ids to add", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "64df3aad42cbaf850a3f891f" + ] + }, + "labels": { + "description": "labels that describes the clan, notice that it is an enum", + "default": [], + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "eläinrakkaat", + "itsenäiset" + ] + } + } + }, + "JoinClanReq": { + "description": "Clan joining request by Player", + "required": [ + "clan_id", + "player_id" + ], + "type": "object", + "properties": { + "clan_id": { + "description": "which Clan to join", + "type": "string" + }, + "player_id": { + "description": "who want to join the Clan", + "type": "string" + }, + "join_message": { + "description": "optional for open Clans", + "type": "string" + } + }, + "example": { + "clan_id": "667462842425aea94d0f66cb", + "player_id": "666720806cc90102f60bd325", + "join_message": "User 2 wants to join" + } + }, + "ProfileUpdate": { + "type": "object", + "properties": { + "username": { + "description": "The username for the Profile, unique", + "type": "string", + "example": "user1" + }, + "password": { + "description": "The password for the Profile", + "type": "string", + "example": "my_password" + } + } + }, + "200_one": { + "description": "Found object. The response objects has two fields: data and metaData.\nThe data has the requested data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name", + "type": "string" + }, + "dataType": { + "description": "always has Object value", + "type": "string" + }, + "dataCount": { + "description": "always has 1 value", + "type": "integer" + } + } + } + } + }, + "PlayerCreate": { + "description": "", + "type": "object", + "properties": { + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "uniqueIdentifier": { + "description": "The identifier for the Player, unique", + "type": "string", + "example": "1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 34 + }, + "profile_id": { + "description": "The id of the associated Profile", + "type": "string", + "example": "6686cac271adebdb10f33fee" + }, + "above13": { + "description": "Is Player age 13 or above, default null", + "type": "boolean", + "example": true + }, + "parentalAuth": { + "description": "Is Player got parents approval, default null", + "type": "boolean", + "example": true + } + } + }, + "PlayerUpdate": { + "description": "", + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Player, readonly", + "type": "string", + "example": "6686cb3b71adebdb10f33ffb" + }, + "name": { + "description": "The name of the Player, unique", + "type": "string", + "example": "User 1" + }, + "uniqueIdentifier": { + "description": "The identifier for the Player, unique", + "type": "string", + "example": "1" + }, + "backpackCapacity": { + "description": "The capacity of the Player's backpack", + "type": "integer", + "example": 34 + }, + "profile_id": { + "description": "The id of the associated Profile", + "type": "string", + "example": "6686cac271adebdb10f33fee" + }, + "above13": { + "description": "Is Player age 13 or above", + "type": "boolean", + "example": true + }, + "parentalAuth": { + "description": "Is Player got parents approval, default null", + "type": "boolean", + "example": true + } + } + }, + "ClanCreate": { + "required": [ + "name", + "tag", + "gameCoins" + ], + "type": "object", + "properties": { + "name": { + "description": "name of the clan, unique", + "type": "string", + "example": "my_clan" + }, + "gameCoins": { + "description": "amount of clan coins", + "default": 0, + "type": "integer", + "example": 23 + }, + "tag": { + "description": "deprecated use labels instead, clan's tag", + "type": "string", + "example": "my_tag" + }, + "isOpen": { + "description": "whenever the clan is open or closed", + "default": true, + "type": "boolean", + "example": true + }, + "labels": { + "description": "labels that describes the clan, notice that it is an enum", + "default": [], + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "eläinrakkaat", + "itsenäiset" + ] + } + } + }, + "ClanJoinCreate": { + "required": [ + "clan_id", + "player_id" + ], + "type": "object", + "properties": { + "clan_id": { + "description": "the Clan _id which player want to join", + "type": "string", + "example": "667bfec6afb8211b4bd8dbff" + }, + "player_id": { + "description": "the Player _id who want to join", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "join_message": { + "description": "optional message for joining request (for closed Clans only)", + "type": "string", + "example": "User 2 wants to join" + } + } + }, + "ClanJoinUpdate": { + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "the request _id, to be updated", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "clan_id": { + "description": "the Clan _id which player want to join", + "type": "string", + "example": "667bfec6afb8211b4bd8dbff" + }, + "player_id": { + "description": "the Player _id who want to join", + "type": "string", + "example": "666720806cc90102f60bd325" + }, + "join_message": { + "description": "optional message for joining request (for closed Clans only)", + "type": "string", + "example": "User 2 wants to join" + } + } + }, + "RoomUpdate": { + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Room, readonly", + "type": "string", + "example": "668d6003a9292e300e94c833" + }, + "floorType": { + "description": "The type of the floor in the room", + "type": "string", + "example": "floor1" + }, + "wallType": { + "description": "The type of the wall in the room", + "type": "string", + "example": "wall1" + }, + "isActive": { + "description": "Indicates whether the room is active or deactivated", + "type": "boolean", + "example": false + }, + "deactivationTimestamp": { + "description": "Indicates when the Room will be deactivated", + "type": "number", + "example": 1721927762 + }, + "hasLift": { + "description": "Indicates whether the room has lift or staicases", + "type": "boolean", + "example": false + } + } + }, + "ChatCreate": { + "description": "Create a new Chat", + "type": "object", + "properties": { + "name": { + "description": "The name of the chat", + "type": "string", + "example": "Chat 1" + } + } + }, + "ChatUpdate": { + "description": "Update an existing Chat", + "type": "object", + "properties": { + "_id": { + "description": "The _id of the Chat, readonly", + "type": "string", + "example": "66912712d191c865ab53da8a" + }, + "name": { + "description": "The name of the chat", + "type": "string", + "example": "Chat 1" + }, + "messages": { + "description": "List of messages in the chat", + "type": "array", + "items": { + "type": "object" + }, + "example": [] + } + } + }, + "MessageCreate": { + "description": "", + "type": "object", + "properties": { + "id": { + "description": "The photon ID of the message", + "type": "integer", + "example": 15 + }, + "senderUsername": { + "description": "The username of the message sender", + "type": "string", + "example": "user1" + }, + "content": { + "description": "The content of the message", + "type": "string", + "example": "message 15" + }, + "feeling": { + "description": "The feeling associated with the message (enum from 1-3)", + "type": "integer", + "example": 1 + } + } + }, + "CustomCharacterCreate": { + "description": "", + "required": [ + "unityKey", + "name", + "speed", + "resistance", + "attack", + "defence", + "characterClass_id", + "player_id" + ], + "type": "object", + "properties": { + "unityKey": { + "description": "The Unity key of the custom character", + "type": "string", + "example": "somekey12" + }, + "name": { + "description": "unique, The name of the custom character", + "type": "string", + "example": "My custom5" + }, + "speed": { + "description": "The speed attribute of the custom character", + "type": "integer", + "example": 37 + }, + "resistance": { + "description": "The resistance attribute of the custom character", + "type": "integer", + "example": 40 + }, + "attack": { + "description": "The attack attribute of the custom character", + "type": "integer", + "example": 55 + }, + "defence": { + "description": "The defence attribute of the custom character", + "type": "integer", + "example": 14 + }, + "characterClass_id": { + "description": "reference, The ID of the character class", + "type": "string", + "example": "66939d9f9fdbf76f01d5dbd5" + }, + "player_id": { + "description": "reference, The ID of the player", + "type": "string", + "example": "665df7026bf5b8f670569ea4" + } + } + }, + "CustomCharacterUpdate": { + "description": "", + "required": [ + "_id" + ], + "type": "object", + "properties": { + "_id": { + "description": "The CustomCharacter _id to update", + "type": "string", + "example": "somekey12" + }, + "unityKey": { + "description": "The Unity key of the custom character", + "type": "string", + "example": "somekey12" + }, + "name": { + "description": "unique, The name of the custom character", + "type": "string", + "example": "My custom5" + }, + "speed": { + "description": "The speed attribute of the custom character", + "type": "integer", + "example": 37 + }, + "resistance": { + "description": "The resistance attribute of the custom character", + "type": "integer", + "example": 40 + }, + "attack": { + "description": "The attack attribute of the custom character", + "type": "integer", + "example": 55 + }, + "defence": { + "description": "The defence attribute of the custom character", + "type": "integer", + "example": 14 + }, + "characterClass_id": { + "description": "reference, The ID of the character class", + "type": "string", + "example": "66939d9f9fdbf76f01d5dbd5" + }, + "player_id": { + "description": "reference, The ID of the player", + "type": "string", + "example": "665df7026bf5b8f670569ea4" + } + } + }, + "ErrorAuthentication": { + "title": "Error on Authentication", + "description": "Error happen on authentication", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "AUTHENTICATION_FAILED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 401 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Could not authenticate the user" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "AUTHENTICATION_FAILED", + "NOT_AUTHENTICATED", + "INVALID_AUTH_TOKEN" + ], + "type": "string", + "example": "AUTHENTICATION_FAILED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 401 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "APIErrorReason": { + "description": "Enum with all possible APIError reasons", + "enum": [ + "NOT_FOUND", + "BAD_REQUEST", + "NOT_UNIQUE", + "REQUIRED", + "NOT_ALLOWED", + "VALIDATION", + "NOT_STRING", + "NOT_NUMBER", + "NOT_BOOLEAN", + "NOT_ARRAY", + "NOT_OBJECT", + "NOT_DATE", + "WRONG_ENUM", + "LESS_THAN_MIN", + "MORE_THAN_MAX", + "NOT_AUTHENTICATED", + "INVALID_AUTH_TOKEN", + "AUTHENTICATION_FAILED", + "NOT_AUTHORIZED", + "TOO_MANY_REQUESTS", + "MISCONFIGURED", + "NOT_AVAILABLE", + "UNEXPECTED" + ], + "type": "string" + }, + "ErrorValidation": { + "title": "Error on Validation", + "description": "Error happen, because some of the fields are not valid", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_STRING" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 400 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "name must be a string" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "BAD_REQUEST", + "REQUIRED", + "NOT_ALLOWED", + "VALIDATION", + "NOT_STRING", + "NOT_NUMBER", + "NOT_BOOLEAN", + "NOT_ARRAY", + "NOT_OBJECT", + "NOT_DATE", + "WRONG_ENUM", + "LESS_THAN_MIN", + "MORE_THAN_MAX" + ], + "type": "string", + "example": "NOT_STRING" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "name" + }, + "value": { + "nullable": true, + "type": "string", + "example": 1 + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string", + "example": "isString" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 400 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorNotFound": { + "title": "Error on Not Found", + "description": "Error happen if the requested object(s) can not be found", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_FOUND" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 404 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Could not find any objects with specified id" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_FOUND" + ], + "type": "string", + "example": "NOT_FOUND" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "_id" + }, + "value": { + "nullable": true, + "type": "string", + "example": "64df3aad42cbaf850a3f891f" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 404 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorNotUnique": { + "title": "Error on Not Unique", + "description": "Error happen if an unique value already exists in DB", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_UNIQUE" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 409 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Field \"name\" with value \"John\" already exists" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_UNIQUE" + ], + "type": "string", + "example": "NOT_UNIQUE" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string", + "example": "name" + }, + "value": { + "nullable": true, + "type": "string", + "example": "John" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 409 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorAuthorization": { + "title": "Error on Authorization", + "description": "Logged-in user does not have a permission to execute the action", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "NOT_AUTHORIZED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 403 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "The logged-in user has no permission to execute create_request action" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "NOT_AUTHORIZED" + ], + "type": "string", + "example": "NOT_AUTHORIZED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string", + "example": "create_request" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "type": "integer", + "example": 403 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ErrorServer": { + "title": "Error on Server", + "description": "Error happen on server side", + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "UNEXPECTED" + }, + "status": { + "format": "int32", + "description": "HTTP status code of the error", + "enum": [ + 500, + 503 + ], + "type": "integer", + "example": 500 + }, + "message": { + "nullable": true, + "description": "Error message explaining the error reason", + "type": "string", + "example": "Unexpected error happen" + }, + "name": { + "description": "Name of the error", + "type": "string", + "example": "" + }, + "reason": { + "description": "Why the error happen", + "enum": [ + "MISCONFIGURED", + "NOT_AVAILABLE", + "UNEXPECTED" + ], + "type": "string", + "example": "UNEXPECTED" + }, + "field": { + "nullable": true, + "description": "On which field the error happen", + "type": "string" + }, + "value": { + "nullable": true, + "type": "string" + }, + "additional": { + "nullable": true, + "description": "Additional data, which can be useful. For unexpected errors this field will include the error happen", + "type": "string" + }, + "statusCode": { + "format": "int32", + "description": "HTTP status code of the error", + "enum": [ + 500, + 503 + ], + "type": "integer", + "example": 500 + }, + "objectType": { + "description": "Type of the object. All errors are of type APIError", + "type": "string", + "example": "APIError" + } + } + }, + "ClanExclude": { + "required": [ + "player_id" + ], + "type": "object", + "properties": { + "player_id": { + "description": "the Player _id which need to be excluded from the clan", + "type": "string", + "example": "666720806cc90102f60bd325" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "The key in the data object", + "type": "string", + "example": "Clan" + }, + "modelName": { + "description": "The name of the model class in data object", + "type": "string", + "example": "Clan" + }, + "dataType": { + "description": "The type of the data: Array or Object", + "type": "string", + "example": "Array" + }, + "dataCount": { + "description": "The count of data items(dataType is Array)", + "type": "integer", + "example": 1 + } + } + }, + "paginationData": { + "type": "object", + "properties": { + "currentPage": { + "description": "The current page number", + "type": "integer", + "example": 1 + }, + "limit": { + "description": "The limit of items per page", + "type": "integer", + "example": 20 + }, + "offset": { + "description": "The offset of items", + "type": "integer", + "example": 0 + }, + "itemCount": { + "description": "The total number of items", + "type": "integer", + "example": 1 + }, + "pageCount": { + "description": "The total number of pages", + "type": "integer", + "example": 1 + } + } + } + } + } + } + }, + "description": "Success request, response with body. [Here is more about GET requests](https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests))" + }, + "201": { + "content": { + "application/json": { + "schema": { + "description": "Created object. The response objects has two fields: data, metaData.\n\nThe data has the created object data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "required": [], + "type": "object" + } + } + }, + "description": "Created, response for POST requests with body" + }, + "204": { + "description": "Successful request, response with no body" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIConflictError" + }, + "example": { + "statusCode": 400, + "message": [ + "password must be a string" + ], + "error": "Bad Request" + } + } + }, + "description": "Validation error. Some of the fields are not specified, have wrong data types or any over validation problem", + "x-last-modified": 1718798714047 + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 401, + "message": "The access token is not provided. Please add `authorization` field with access token(in bearer token form): `Bearer access-token-here` to request header. The access token you can get from /auth endpoint", + "error": "Unauthorized" + } + } + }, + "description": "Not authenticated. The Authorization header is not specified or the token is expired. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 403, + "message": "The logged-in user has no permission to execute update_request action", + "error": "Forbidden" + } + } + }, + "description": "No permission. The logged-in user has no permission to execute the action. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 404, + "message": "Can not find any Chat instances", + "error": "Not Found" + } + } + }, + "description": "Not found. No object(s) found" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIConflictError" + }, + "example": { + "statusCode": 409, + "message": [ + "Field 'username' with value 'user1' already exists" + ], + "error": "Conflict" + } + } + }, + "description": "Some of the fields are not unique " + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/APIError" + }, + "example": { + "statusCode": 500, + "message": "Internal server error" + } + } + }, + "description": "Unexpected error happened. [Please create a new issue here](https://github.com/Alt-Org/Altzone-Server/issues) and specify the endpoint, HTTP method and description if u have any", + "x-last-modified": 1718799573835 + }, + "200_one": { + "content": { + "application/json": { + "schema": { + "description": "Found object. The response objects has two fields: data and metaData.\nThe data has the requested data. It has an object inside, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.", + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "The key in the data object", + "type": "string", + "example": "Clan" + }, + "modelName": { + "description": "The name of the model class in data object", + "type": "string", + "example": "Clan" + }, + "dataType": { + "description": "The type of the data: Array or Object", + "type": "string", + "example": "Object" + }, + "dataCount": { + "description": "The count of data items(dataType is Array)", + "type": "integer", + "example": 1 + } + } + } + } + } + } + }, + "description": "Success request, response with body. [Here is more about GET requests](https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests))" + }, + "400_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 400 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorValidation" + } + } + } + } + } + }, + "description": "Validation error. Some of the fields are not specified, have wrong data types or any over validation problem" + }, + "401_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 401 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorAuthentication" + } + } + } + } + } + }, + "description": "Not authenticated. The Authorization header is not specified or the token is expired. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "403_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 403 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorAuthorization" + } + } + } + } + } + }, + "description": "No permission. The logged-in user has no permission to execute the action. [Here is more about auth](https://github.com/Alt-Org/Altzone-Server/wiki/2.-Authentication-and-authorization)" + }, + "404_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 404 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorNotFound" + } + } + } + } + } + }, + "description": "Not found. No object(s) found" + }, + "409_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 409 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorNotUnique" + } + } + } + } + } + }, + "description": "Some of the fields are not unique " + }, + "500_New": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "statusCode": { + "format": "int32", + "description": "HTTP status code of the response", + "type": "integer", + "example": 500 + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorServer" + } + } + } + } + } + }, + "description": "Unexpected error happened. [Please create a new issue here](https://github.com/Alt-Org/Altzone-Server/issues) and specify the endpoint, HTTP method and description if u have any" + } + }, + "parameters": { + "_id": { + "deprecated": false, + "name": "_id", + "description": "_id of the object", + "in": "path", + "required": true, + "x-last-modified": 1718961163935 + } + }, + "examples": { + "BattleCharacterNoId": { + "value": { + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "customCharacter_id": "6480a27887b5e6ba2a0f630d" + } + }, + "BattleCharacterWithId": { + "value": { + "_id": "648acf3a409da618287a1ca1", + "unityKey": "somekey", + "name": "My custom1", + "resistance": 56, + "speed": 10, + "attack": 28, + "defence": 10, + "characterClassName": "my char", + "gestaltCycle": 1, + "characterClass_id": "648acec6409da618287a1c8e", + "customCharacter_id": "648acf23409da618287a1c98" + } + }, + "CharacterClassNoId": { + "value": { + "name": "my char", + "gestaltCycle": 1, + "speed": 23, + "resistance": 45, + "attack": 10, + "defence": 12 + } + }, + "CharacterClassWithId": { + "value": { + "_id": "648095322a99de2e3bdee480", + "name": "my char", + "gestaltCycle": 1, + "speed": 23, + "resistance": 45, + "attack": 10, + "defence": 12 + } + }, + "ClanNoId": { + "value": { + "name": "clan", + "gameCoins": 260, + "tag": "some tag" + } + }, + "ClanWithId": { + "value": { + "_id": "648091def1bdcdb2a19af6da", + "name": "clan", + "gameCoins": 260, + "tag": "some tag" + } + }, + "CustomCharacterNoId": { + "value": { + "unityKey": "somekey", + "name": "My custom1", + "speed": 10, + "resistance": 56, + "attack": 28, + "defence": 10, + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "player_id": "64809d1336b0779ede245fc1" + } + }, + "CustomCharacterWithId": { + "value": { + "_id": "647f520fa8a94ef3c91cb0e3", + "unityKey": "somekey", + "name": "My custom1", + "speed": 10, + "resistance": 56, + "attack": 28, + "defence": 10, + "characterClass_id": "6480a26287b5e6ba2a0f630a", + "player_id": "64809d1336b0779ede245fc1" + } + }, + "FurnitureNoId": { + "value": { + "name": "furn1", + "shape": "shape1", + "weight": 12, + "material": "mat1", + "recycling": "rec1", + "unityKey": "unity1", + "filename": "file1.txt", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "FurnitureWithId": { + "value": { + "_id": "648ad033409da618287a1ca5", + "name": "furn1", + "shape": "shape1", + "weight": 12, + "material": "mat1", + "recycling": "rec1", + "unityKey": "unity1", + "filename": "file1", + "clan_id": "648acf02409da618287a1c91" + } + }, + "PlayerNoId": { + "value": { + "name": "Anna", + "backpackCapacity": 20, + "uniqueIdentifier": "1", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "PlayerWithId": { + "value": { + "_id": "648acf12409da618287a1c95", + "name": "Anna", + "backpackCapacity": 23, + "uniqueIdentifier": "1", + "clan_id": "648acf02409da618287a1c91" + } + }, + "RaidRoomNoId": { + "value": { + "type": 0, + "rowCount": 4, + "colCount": 6, + "player_id": "64809d1336b0779ede245fc1", + "clan_id": "64809d0a36b0779ede245fbf" + } + }, + "RaidRoomWithId": { + "value": { + "_id": "647f53b5a8a94ef3c91cb104", + "type": 0, + "rowCount": 4, + "colCount": 6, + "player_id": "64809d1336b0779ede245fc1", + "clan_id": "64809d0a36b0779ede245fbf" + } + } + }, + "securitySchemes": { + "JWTAuth": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http", + "description": "Example of header: \nAuthorization: Bearer access_token" + } + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "tags": [ + { + "name": "release-03-10-2024", + "description": "Release on 03.10.2024" + }, + { + "name": "in-development", + "description": "endpoint is changing or not yet implemented" + }, + { + "name": "Profile", + "description": "profile" + }, + { + "name": "Clan", + "description": "clan" + }, + { + "name": "Player", + "description": "player" + }, + { + "name": "SoulHome", + "description": "soulhome" + }, + { + "name": "Room", + "description": "room" + }, + { + "name": "Stock", + "description": "stock" + }, + { + "name": "Item", + "description": "item" + }, + { + "name": "CustomCharacter", + "description": "CustomCharacter" + }, + { + "name": "Auth", + "description": "Authentication related features" + }, + { + "name": "Chat", + "description": "chats and their messages " + }, + { + "name": "GameAnalytics", + "description": "Various functionality for game analytics" + }, + { + "name": "FleaMarket", + "description": "Flea market, place where Clans can sell own items or buy other's." + }, + { + "name": "Voting", + "description": "In-game votings" + }, + { + "name": "Leaderboard", + "description": "Leaderboards of different categories" + }, + { + "name": "PlayerTasks", + "description": "In-game tasks for Player to do daily, weekly, monthly" + } + ], + "externalDocs": { + "description": "GitHub wikipages", + "url": "https://github.com/Alt-Org/Altzone-Server/wiki" + } +} \ No newline at end of file diff --git a/swagger/swagger.json b/swagger/swagger.json index b4564592..a76c99aa 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -11,19 +11,22 @@ "servers": [ { "url": "https://altzone.fi/api", - "description": "Production" + "description": "Production. Stable version of API, which might not have all of the new changes" }, { "url": "http://localhost:8080", "description": "Local" + }, + { + "url": "https://devapi.altzone.fi", + "description": "Development. Current version of API with latest changes" } ], "paths": { "/clan": { "get": { "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "200": { @@ -49,6 +52,11 @@ "itemCount": 0, "stockCount": 0, "isOpen": true, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, "id": "667eedc9b3b5bf0f7a840ef1" }, { @@ -63,6 +71,11 @@ "itemCount": 0, "stockCount": 0, "isOpen": true, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, "id": "667bfec6afb8211b4bd8dbff" } ] @@ -121,8 +134,7 @@ "required": true }, "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "204": { @@ -153,7 +165,7 @@ } ], "summary": "Update a clan", - "description": "Update the Clan, which _id is specified in the body. \n\nOnly Clan admins can change the Clan's data, as well as add or remove admins. Notice that while removing Clan admins, its is not allowed to remove all of them, because Clan must have at least one admin.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins | \n\n" + "description": "Update the Clan, which _id is specified in the body. \n\nOnly Clan admins can change the Clan's data, as well as add or remove admins. Notice that while removing Clan admins, its is not allowed to remove all of them, because Clan must have at least one admin.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Clan admins | \n\n\nThe labels enum values:\n\n - 'eläinrakkaat',\n - 'maahanmuuttomyönteiset',\n - 'lgbtq+',\n - 'raittiit',\n - 'kohteliaat',\n - 'kiusaamisenvastaiset',\n - 'urheilevat',\n - 'syvälliset',\n - 'oikeudenmukaiset',\n - 'kaikkien kaverit',\n - 'itsenäiset',\n - 'retkeilijät',\n - 'suomenruotsalaiset',\n - 'huumorintajuiset',\n - 'rikkaat',\n - 'ikiteinit',\n - 'juoruilevat',\n - 'rakastavat',\n - 'oleilijat',\n - 'nörtit',\n - 'musadiggarit',\n - 'tunteelliset',\n - 'gamerit',\n - 'animefanit',\n - 'sinkut',\n - 'monikulttuuriset',\n - 'kauniit',\n - 'järjestelmälliset',\n - 'epäjärjestelmälliset',\n - 'tasa-arvoiset',\n - 'somepersoonat',\n - 'kädentaitajat',\n - 'muusikot',\n - 'taiteilijat',\n - 'spämmääjät',\n - 'kasvissyöjät',\n - 'tasapainoiset'" }, "post": { "requestBody": { @@ -167,8 +179,7 @@ "required": true }, "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "201": { @@ -194,6 +205,11 @@ "itemCount": 0, "stockCount": 0, "isOpen": true, + "points": 0, + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], "Stock": { "objectType": "StockDto", "_id": "66d74887c988e09f4c45870d", @@ -451,7 +467,10 @@ "name": "User 2", "backpackCapacity": 654, "uniqueIdentifier": 2, - "profile_id": "6686cb3b71adebdb10f33ff9" + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 20 } ] }, @@ -578,7 +597,10 @@ "name": "User 2", "backpackCapacity": 654, "uniqueIdentifier": 2, - "profile_id": "6686cb3b71adebdb10f33ff9" + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 0 } }, "metaData": { @@ -640,7 +662,10 @@ "name": "User 2", "backpackCapacity": 654, "uniqueIdentifier": 2, - "profile_id": "6686cb3b71adebdb10f33ff9" + "profile_id": "6686cb3b71adebdb10f33ff9", + "above13": true, + "parentalAuth": true, + "points": 20 } }, "metaData": { @@ -721,8 +746,7 @@ "/clan/{_id}": { "get": { "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "200": { @@ -743,6 +767,10 @@ "admin_ids": [ "666720806cc90102f60bd325" ], + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], "playerCount": 1, "itemCount": 0, "stockCount": 0, @@ -780,8 +808,7 @@ }, "delete": { "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "204": { @@ -871,8 +898,7 @@ "/clan/leave": { "post": { "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "204": { @@ -902,8 +928,7 @@ "/stock": { "get": { "tags": [ - "Stock", - "release-03-09-24" + "Stock" ], "responses": { "200": { @@ -973,8 +998,7 @@ "/stock/{_id}": { "get": { "tags": [ - "Stock", - "release-03-09-24" + "Stock" ], "responses": { "200": { @@ -1043,8 +1067,7 @@ "/item/{_id}": { "get": { "tags": [ - "Item", - "release-03-09-24" + "Item" ], "responses": { "200": { @@ -1063,7 +1086,6 @@ "weight": 67, "recycling": "yleinen", "unityKey": "unity1", - "filename": "file1", "location": [ 2, 4 @@ -1123,8 +1145,7 @@ "description": "", "get": { "tags": [ - "SoulHome", - "release-03-09-24" + "SoulHome" ], "responses": { "200": { @@ -1179,8 +1200,7 @@ "/room": { "get": { "tags": [ - "Room", - "release-03-09-24" + "Room" ], "responses": { "200": { @@ -1249,8 +1269,7 @@ "required": true }, "tags": [ - "Room", - "release-03-09-24" + "Room" ], "responses": { "204": { @@ -1278,10 +1297,11 @@ "description": "## Method description\n\nUpdate Room by its _id specified in the body.\n\nAny Clan member can update any Room, which (SoulHome) belongs to the Clan.\n\nIf the logged-in user is a Clan member and the Clan does have the requested Room, the Room for this Clan will be returned. \n\nIf the logged-in user is not belonging to any Clan, or Room in that Clan with provided _id is not found the 404 error will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n\n## Endpoint description\n\n### General description\n\nRoom is a place, which can safely store Items of a Clan. Items from the Room can not be stolen. \n\nRoom is closely related to its SoulHome. SoulHome consists of Rooms. \n\nRoom has an automatic behavior. Room has two statuses active and deactivated. When the Room is becoming deactivated its isActive field is taking on the value false. This should be done automatically when the deactivationTimestamp field is expired.\n\n### Relations\n\nRoom can belong to one SoulHome. SoulHome may have many Rooms.\n\nRoom can have many Items. Item may belong to one Room.\n\n### CRUD operations\n\nRooms for a SoulHome must be created automatically, when a SoulHome is created. Therefore there is no enpoint for Room creation.\n\nRoom and all Rooms can be read by Clan members, to which the Room (its SoulHome) is belonging.\n\nRoom can be updated by Clan members, to which it (its SoulHome) belongs.\n\nRoom can be deleted only automatically, when the SoulHome, to which the Room belongs is deleted." } }, - "/itemShop": { + "/fleaMarket": { "get": { "tags": [ - "ItemShop" + "in-development", + "FleaMarket" ], "responses": { "200": { @@ -1378,188 +1398,57 @@ "1": { "value": { "data": { - "ItemShop": [ + "FleaMarketItem": [ { - "_id": "65feaee74ec1b78cc4afc9da", - "name": "Huonekalukauppa", - "lastRestock": 1720612774100, - "items": [ - { - "item_id": "668e77a657e6f7ebbcf3a7da", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7db", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7dc", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7dd", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7de", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7df", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e0", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e1", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e2", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e3", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e4", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e5", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e6", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e7", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e8", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e9", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ea", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7eb", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ec", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ed", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ee", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ef", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f0", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f1", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f2", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f3", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f4", - "isInVoting": false, - "isSold": false - } - ], - "__v": 2, - "id": "65feaee74ec1b78cc4afc9da" + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "isFurniture": true, + "price": 23, + "status": "Available", + "clan_id": "668953cd4a7b6c993aed1a36" } ] }, "metaData": { - "dataKey": "ItemShop", - "modelName": "ItemShop", + "dataKey": "FleaMarketItem", + "modelName": "FleaMarketItem", "dataType": "Array", "dataCount": 1 - }, - "paginationData": { - "currentPage": 1, - "limit": 20, - "offset": 0, - "itemCount": 1, - "pageCount": 1 } } } } } }, - "description": "Success, rseponse with body" + "description": "Success, response with body" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "404": { + "$ref": "#/components/responses/404_New" }, "500": { - "$ref": "#/components/responses/500" + "$ref": "#/components/responses/500_New" } }, "security": [ - {} + { + "JWTAuth": [] + } ], - "summary": "Get item shops", - "description": "The ItemShop is an object containing ShopItems. There can be only one ItemShop excisting. The ShopItem is an inner object of the ItemShop, which contains item_id (reference to an Item), isInVoting, isSold and vote_id(reference to a ClanVote).\n\nNotice that ItemShop is created automatically by an API with some default ShopItems in it. Each day ItemShop is reset, so that all default values, which was sold are restored back.\n\nNotice that it is possible to access ItemShop via this endpoint and also via /itemShop/{_id}, the response will be the same (except that this endpoint will have the ItemShop data in an array).\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | " + "summary": "Get all flea market items", + "description": "Get all FleaMarketItems of the flea market\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" } }, - "/itemShop/{_id}": { + "/fleaMarket/{_id}": { "get": { "tags": [ - "ItemShop" + "in-development", + "FleaMarket" ], "responses": { "200": { @@ -1633,154 +1522,21 @@ "1": { "value": { "data": { - "ItemShop": { - "_id": "65feaee74ec1b78cc4afc9da", - "name": "Huonekalukauppa", - "lastRestock": 1720612774100, - "items": [ - { - "item_id": "668e77a657e6f7ebbcf3a7da", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7db", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7dc", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7dd", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7de", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7df", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e0", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e1", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e2", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e3", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e4", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e5", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e6", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e7", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e8", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7e9", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ea", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7eb", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ec", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ed", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ee", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7ef", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f0", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f1", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f2", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f3", - "isInVoting": false, - "isSold": false - }, - { - "item_id": "668e77a657e6f7ebbcf3a7f4", - "isInVoting": false, - "isSold": false - } - ], - "__v": 2, - "id": "65feaee74ec1b78cc4afc9da" + "FleaMarketItem": { + "_id": "668a70ce91020196cb10d595", + "name": "item1", + "weight": 67, + "recycling": "yleinen", + "unityKey": "unity1", + "isFurniture": true, + "price": 23, + "status": "Available", + "clan_id": "668953cd4a7b6c993aed1a36" } }, "metaData": { - "dataKey": "ItemShop", - "modelName": "ItemShop", + "dataKey": "FleaMarketItem", + "modelName": "FleaMarketItem", "dataType": "Object", "dataCount": 1 } @@ -1789,7 +1545,10 @@ } } }, - "description": "Success, rseponse with body" + "description": "Success, response with body" + }, + "400": { + "$ref": "#/components/responses/400_New" }, "404": { "$ref": "#/components/responses/404" @@ -1799,14 +1558,25 @@ } }, "security": [ - {} + { + "JWTAuth": [] + } ], - "summary": "Get item shop", - "description": "The ItemShop is an object containing ShopItems. There can be only one ItemShop excisting. The ShopItem is an inner object of the ItemShop, which contains item_id (reference to an Item), isInVoting, isSold and vote_id(reference to a ClanVote).\n\nNotice that ItemShop is created automatically by an API with some default ShopItems in it. Each day ItemShop is reset, so that all default values, which was sold are restored back.\n\nNotice that it is possible to access ItemShop via this endpoint and also via /itemShop, the response will be the same (except that this endpoint will have the ItemShop data as an object).\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |" + "summary": "Get flea market item data by _id", + "description": "Get an idividual FleaMarketItem by its mongo _id field.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" }, "parameters": [ { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, "name": "_id", + "description": "FleaMarketItem _id to get", + "schema": { + "type": "string" + }, "in": "path", "required": true } @@ -1815,8 +1585,7 @@ "/chat": { "get": { "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "200": { @@ -1831,10 +1600,14 @@ "data": { "Chat": [ { - "_id": "66912712d191c865ab53da8a", - "name": "Chat 1", - "__v": 0, - "id": "66912712d191c865ab53da8a" + "objectType": "ChatDto", + "_id": "66d8611a184d5f0a7c0675b7", + "name": "chat1" + }, + { + "objectType": "ChatDto", + "_id": "66d85e3f4bed433708d5cb7f", + "name": "Chat 1" } ] }, @@ -1842,13 +1615,13 @@ "dataKey": "Chat", "modelName": "Chat", "dataType": "Array", - "dataCount": 1 + "dataCount": 2 }, "paginationData": { "currentPage": 1, - "limit": 1, + "limit": 20, "offset": 0, - "itemCount": 1, + "itemCount": 2, "pageCount": 1 } } @@ -1866,10 +1639,12 @@ } }, "security": [ - {} + { + "JWTAuth": [] + } ], "summary": "Read all existing chats", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all created Chats. Remember about the pagination.\n\nNotice, that use of messages array is not advised and can be removed at some point in the future. For accessing messages of the Chat please use the /chat/:_id/message endpoint.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "description": "Read all created Chats. Remember about the pagination.\n\nNotice, that use of messages array is not advised and can be removed at some point in the future. For accessing messages of the Chat please use the /chat/:_id/message endpoint.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", "externalDocs": { "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" } @@ -1887,8 +1662,7 @@ "required": true }, "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "204": { @@ -1916,7 +1690,7 @@ } ], "summary": "Update a chat", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nUpdate the Chat, which _id is specified in the body.\n\nNotice that currently anybody is able to change any Chat.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no | \n" + "description": "Update the Chat, which _id is specified in the body.\n\nNotice that currently anybody is able to change any Chat.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Players above 13 or with parents approval | \n" }, "post": { "requestBody": { @@ -1930,8 +1704,7 @@ "required": true }, "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "201": { @@ -1945,11 +1718,9 @@ "value": { "data": { "Chat": { - "name": "Chat 1", - "_id": "66912712d191c865ab53da8a", - "messages": [], - "__v": 0, - "id": "66912712d191c865ab53da8a" + "objectType": "ChatDto", + "_id": "66d86147184d5f0a7c0675ca", + "name": "chat2" } }, "metaData": { @@ -1984,14 +1755,13 @@ } ], "summary": "Create a chat", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Chat. The Chat is an object containing messages.\n\nNotice, that currently there is no restrictions on who can create a Chat.\n\nNotice that the Message objects are inner objects of Chat and can not be used enewhere else than in the Chat. There is also no separate collection for the Message in the DB.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no | \n" + "description": "Create a new Chat. The Chat is an object containing messages.\n\nNotice, that currently there is no restrictions on who can create a Chat.\n\nNotice that the Message objects are inner objects of Chat and can not be used enewhere else than in the Chat. There is also no separate collection for the Message in the DB.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes | \n| Authorization | only for Players above 13 or with parents approval | \n" } }, "/chat/{_id}": { "get": { "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "200": { @@ -2005,10 +1775,9 @@ "value": { "data": { "Chat": { - "_id": "66912712d191c865ab53da8a", - "name": "Chat 1", - "__v": 0, - "id": "66912712d191c865ab53da8a" + "objectType": "ChatDto", + "_id": "66d8611a184d5f0a7c0675b7", + "name": "chat1" } }, "metaData": { @@ -2032,15 +1801,16 @@ } }, "security": [ - {} + { + "JWTAuth": [] + } ], "summary": "Get Chat by _id", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead Chat data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | no | \n| Sort | no |\n| Pagination | no | " + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead Chat data by its _id field\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | no | \n| Sort | no |\n| Pagination | no | " }, "delete": { "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "204": { @@ -2062,7 +1832,7 @@ } ], "summary": "Delete Chat", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nDelete Chat by its _id field. \n\nNotice that currently anybody can delete any Chat.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |" + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nDelete Chat by its _id field. \n\nNotice that currently anybody can delete any Chat.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |" }, "parameters": [ { @@ -2084,7 +1854,6 @@ "/chat/{_id}/messages": { "get": { "tags": [ - "release-03-09-24", "Chat" ], "responses": { @@ -2135,10 +1904,12 @@ } }, "security": [ - {} + { + "JWTAuth": [] + } ], "summary": "Read all messages from the Chat", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all messages of specified Chat. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | no |\n| Authorization | no |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nRead all messages of specified Chat. Remember about the pagination\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | ", "externalDocs": { "url": "https://github.com/Alt-Org/Altzone-Server/wiki/3.-Data-fetching-(GET-requests)" } @@ -2155,8 +1926,7 @@ "required": true }, "tags": [ - "Chat", - "release-03-09-24" + "Chat" ], "responses": { "204": { @@ -2184,7 +1954,7 @@ } ], "summary": "Create a message", - "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Message. Message represent the object of message sent by a Player.\n\nNotice that currently there are no authorization required.\n\nNotice, that the messages does not have an usual _id field generated by data base. Instead the Photon id should be used.\n\nNotice that the messages is contained in the array of a Chat collection.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | no | \n| Authorization | no |\n" + "description": "TEMPORARY DISABLED AND RETURNS 501\n\nCreate a new Message. Message represent the object of message sent by a Player.\n\nNotice that currently there are no authorization required.\n\nNotice, that the messages does not have an usual _id field generated by data base. Instead the Photon id should be used.\n\nNotice that the messages is contained in the array of a Chat collection.\n\n| Feature / Requirement | Is / Has | \n| -------- | -------- | \n| Authentication | yes |\n| Authorization | only for Players above 13 or with parents approval |\n" }, "parameters": [ { @@ -2480,8 +2250,7 @@ "/room/{_id}": { "get": { "tags": [ - "Room", - "release-03-09-24" + "Room" ], "responses": { "200": { @@ -2589,8 +2358,7 @@ "required": true }, "tags": [ - "Room", - "release-03-09-24" + "Room" ], "responses": { "204": { @@ -2621,8 +2389,7 @@ "/item/steal": { "get": { "tags": [ - "Item", - "in-development" + "Item" ], "parameters": [ { @@ -2711,7 +2478,7 @@ "668a765091020196cb10d5a3", "668a765091020196cb10d5a3" ], - "room_is": "668a765091020196cb10d5a3" + "room_id": "668a765091020196cb10d5a3" } } } @@ -2720,7 +2487,6 @@ "required": true }, "tags": [ - "in-development", "Item" ], "responses": { @@ -2811,8 +2577,7 @@ "required": true }, "tags": [ - "GameData", - "in-development" + "GameData" ], "responses": { "200": { @@ -2822,8 +2587,10 @@ "result": { "value": { "steal_token": "temporary_token_to_use_for_/room/steal", - "stock_id": "667ee778b3b5bf0f7a840ec9", - "clan_id": "668d6003a9292e300e94c833" + "soulHome_id": "667ee778b3b5bf0f7a840ec9", + "room_ids": [ + "668d6003a9292e300e94c833" + ] } } } @@ -2837,6 +2604,9 @@ "401": { "$ref": "#/components/responses/401_New" }, + "403": { + "$ref": "#/components/responses/403_New" + }, "404": { "$ref": "#/components/responses/404_New" }, @@ -2850,7 +2620,7 @@ } ], "summary": "Inform API about battle", - "description": "Enpoint for notifying the API about battle events or any other data.\n\nNotice, that the field type is required and determines the type of the data.\n\nNotice that the type also determines shape of the body. Examples, for each type can be found in request examples section.\n\n## Type field\n\n### result\n\nResult of the battle, all players of the battle should send this data.\n\nAs a response for winners an access token will be returned, which can be used when stealing Items from losed Clan's SoulHome. Notice that the steal token will expire after some period of time. Loosers will get 403 error\n\nThe steal token can be used only by the winner's Clan's members for the loser's Clan Stock.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" + "description": "Enpoint for notifying the API about battle events or any other data.\n\nNotice, that the field type is required and determines the type of the data.\n\nNotice that the type also determines shape of the body. Examples, for each type can be found in request examples section.\n\n## Type field\n\n### result\n\nResult of the battle, all players of the battle should send this data.\n\nAs a response for winners an access token will be returned, which can be used when stealing Items from losed Clan's SoulHome. Notice that the steal token will expire after some period of time. Losers will get 403 error = they can not get the steal token.\n\nThe steal token can be used only by the winner's Clan's members for the loser's Clan Stock.\n\nYou can see the process flow from [this diagram](https://github.com/Alt-Org/Altzone-Server/tree/dev/doc/img/game_results)" } }, "/item/move": { @@ -2872,8 +2642,7 @@ "required": true }, "tags": [ - "Item", - "in-development" + "Item" ], "responses": { "204": { @@ -2901,18 +2670,54 @@ "description": "Move Item from Stock to Room or from Room to Stock, based on the _moveTo_ field, which can have only two values: \"Stock\" or \"Room\"\n\nNotice that Clan members can move only own Clan Items and a member is trying to move other Clan's Item the 404 will be returned.\n\nNotice that if an Item need to be moved to a Stock then there is no need to specify the destination_id field, since Clan can have only one Stock.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | no |\n\n## Endpoint description\n\n### General description\n\nItems are objects, which can be placed in SoulHomes' Rooms, Stocks and Shop. Item is an object, which has its value in the game and can be bought from the Shop, stolen from the Stock or placed in safe to SoulHome's Room.\n\nItems can be obtained only from Shop and then stored in Room or a Stock.\n\nItem can be a furniture or some other kind of object. The difference between furniture and not furniture is that a furniture can be placed only on a floor. Not furniture can be placed anywhere including on top of a furniture, walls, celling and floor. This data is used in game logic only and does not affect the API in any way.\n\n### Relations\n\nItem can belong to one Room. Room can have multiple Items.\n\nItem can belong to one Stock. Stock can have multiple Items.\n\nItem can belong to Shop. Shop can have multiple Items.\n\n### CRUD operations\n\nItem can not be created by Player. It can only be bought from Shop. API creates new Items automatically.\n\nItem can be read by anyone. One Item can be accessed via its _id field. Multiple Items can be accessed by requesting them within another endpoints via \"with\" or \"all\" queries (/stock/:stock_id?with=Item), since they are mostly required together with Stock, Room or Shop data.\n\nItem can not be directly updated by Players. However, Players may move Items from one place to another. They first can buy an Item from Shop and so update Item's place by moving it to the Clan's Stock. Then they can move it from SoulHome to Stock or vice versa. Item can be bought from Shop via /stock/buy enpoint (not yet implemented). Item can be moved from Room to Stock or from Stock to Room via /item/move POST endpoint.\n\nItem can not be deleted." } }, - "/itemShop/buy": { + "/fleaMarket/buy": { "post": { + "requestBody": { + "description": "Item to buy", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "66d8611a184d5f0a7c0675b7" + } + } + } + } + }, + "required": true + }, "tags": [ - "ItemShop", - "in-development" + "in-development", + "FleaMarket" ], "responses": { - "501": { - "description": "Not implemented" + "201": { + "$ref": "#/components/responses/201" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" } }, - "summary": "Buy an Item from Shop" + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Buy an Item from the flea market", + "description": "Buy an Item from the flea market. This will start a voting in the Clan for which Item is being purchased. Voting duration is 10 min at max and the min approval percentage is 51. During the voting an Item is in \"Booked\" status and can not be bought by other players.\n\nNotice that if a FleaMarketItem has already \"Booked\" status 403 will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only buying Clan members |\n\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there.\n" } }, "/auth/signIn": { @@ -2965,6 +2770,7 @@ "backpackCapacity": 456, "uniqueIdentifier": "3", "profile_id": "651a716b5129c6e54cb7d7c9", + "points": 10, "__v": 0, "clan_id": "651a72c35129c6e54cb7d7de" }, @@ -2979,6 +2785,11 @@ "admin_ids": [ "651a716b5129c6e54cb7d7cb" ], + "labels": [ + "eläinrakkaat", + "itsenäiset" + ], + "points": 20, "__v": 0, "playerCount": 2, "stockCount": 1 @@ -3022,8 +2833,7 @@ "required": true }, "tags": [ - "Clan", - "release-03-09-24" + "Clan" ], "responses": { "204": { @@ -3050,40 +2860,415 @@ "summary": "Exclude Player from Clan", "description": "Request exclude the player from clan. \n\nNotice that only Clan admin can remove the Player\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only clan admin | \n\n" } - } - }, - "components": { - "schemas": { - "200": { - "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object" - } + }, + "/gameAnalytics/logFile": { + "post": { + "requestBody": { + "description": "File that needs to be uploaded", + "content": { + "multipart/form-data": {} }, - "metaData": { - "type": "object", - "properties": { - "dataKey": { - "description": "collection name", - "type": "string" - }, - "modelName": { - "description": "model name, usually same as dataKey", - "type": "string" - }, - "dataType": { - "description": "Object or Array", - "type": "string" - }, - "dataCount": { - "description": "1 for Object dataType and length if Array", - "type": "integer" - } - } + "required": true + }, + "tags": [ + "GameAnalytics" + ], + "parameters": [ + { + "name": "Secret", + "description": "secret_string", + "schema": { + "type": "string" + }, + "in": "header", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Upload a game analytics log file", + "description": "Enpoint uploads a log file and save on the server.\n\nThe file will be saved to the folder with name corresponding to the date when it was uploaded, i.e. 1-9-2024.\n\nFile name must be unique and therefore its name will contain date, time, Player _id and a random string, i.e. 1-9-2024_14-45-12_667eedc9b3b5bf0f7a840ef1_123456.log\n\nNotice that the request must have a Secret header which holds a password for such requests.\n\nNotice that the request must be in multipart/form-data format, where field name containing the file is \"logFile\"" + } + }, + "/fleaMarket/sell": { + "post": { + "requestBody": { + "description": "Item to sell", + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "item_id": "66d8611a184d5f0a7c0675b7" + } + } + } + } + }, + "required": true + }, + "tags": [ + "in-development", + "FleaMarket" + ], + "responses": { + "201": { + "$ref": "#/components/responses/201" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Sell an Item on the flea market", + "description": "Sell an Item on the flea market. This will start a voting in the Clan from which Item is being moved to the flee marked. Voting min approval percentage is 51. During the voting an Item is in \"Shipping\" status and can not be bought by other players.\n\nNotice that if a FleaMarketItem has already \"Shipping\" status 403 will be returned.\n\n| Feature / Requirement | Is / Has |\n| -------- | -------- |\n| Authentication | yes |\n| Authorization | yes, only selling Clan members |\n\n## Endpoint description\n\n### General description\n\nThe FleaMarket is the place where the Clans can sell their own Items and other Clans can buy these Items. Items can be purchased by any Clan member using this Clan coins.\n\n\n### Logic\n\nThe FleeMarket contains FleeMarketItems. FleeMarketItem object has a status field which determines what can be done with it:\n\n- Available - Item can be purchased\n- Shipping - Item is being shipped = selling Clan members is voting if the Item should be sold or not\n- Booking - Item is booked = buying Clan is voting if the Item should be bought or not. Item can be booked for max. 10 min\n\n\n### Relations\n\nThe FleaMarketItem belongs to a selling Clan. The selling Clan can have many FleaMarketItems. FleaMarketItem can belong to one Clan only.\n\n\n### CRUD operations\n\nFleaMarketItem is added by Clan member by moving the Item from Stock to FleaMarket, which means the Item from Stock is copied to the FleaMarketItem collection and then removed from the Stock.\n\nFleaMarketItems can be read by any logged-in user.\n\nFleaMarketItem data can not be changed at the time and it is read-only.\n\nFleaMarketItem can not be removed from the FleaMarket at the time once it is put there." + } + }, + "/voting": { + "put": { + "requestBody": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "voting_id": "667eedc9b3b5bf0f7a840ef1", + "choice": "accept" + } + } + } + } + }, + "required": true + }, + "tags": [ + "Voting", + "in-development" + ], + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Send a vote", + "description": "Send a vote.\n\nThe minimum acceptance percentage refering to the min percentage of a group members that need to accept some option for the voting to be concluded. For example Clan members are voting whenever they want to sell an Item or not and the min percentage might be 51 => if 51% of all this Clan members accept to sell the Item then the Item can be sold and otherwise if the 51% of Clan members reject to sell the Item it will not be sold. Same logic can be applied if there are more than 2 options available.\n\nVoting endpoint is served for different kinds of votings. The \"type\" field is an enum and determines what value the \"choice\" field may have.\n\n| type | options |\n| ------------- | ------------------ |\n| selling_item: | \"accept\", \"reject\" |\n| buying_item: | \"accept\", \"reject\" |\n\n\n## selling_item\n\nPlayer votes whenever he/she wants that an item is going for sale to the flea market. The voter must be a member of the Clan from which item is going to sale, otherwise 403 will be returned. The min acceptance percentage is 51%. Notice that during the voting process there will be sent different notifications via MQTT:\n- new voting started\n- somebody has voted\n- voting ended\n\n## buying_item\n\nPlayer votes whenever he/she wants that an item should be bought from the flea market or not. The voter must be a member of the Clan for which item is going to be bought, otherwise 403 will be returned. The min acceptance percentage is 51%. The time limit for the voting is 10 mins. Notice that during the voting process there will be sent different notifications via MQTT:\n- new voting started\n- somebody has voted\n- voting ended\n- error if time limit expired" + } + }, + "/voting/{_id}": { + "get": { + "tags": [ + "Voting", + "in-development" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "_id": "668953cd4a7b6c993aed1a36", + "startedAt": 1727178420, + "organizer_id": "668d6003a9292e300e94c833", + "type": "selling_item", + "player_ids": [ + "66d86147184d5f0a7c0675ca", + "668a765091020196cb10d5a3" + ], + "votes": [ + { + "player_id": "66d86147184d5f0a7c0675ca", + "choice": "accept" + }, + { + "player_id": "668a765091020196cb10d5a3", + "choice": "reject" + } + ], + "minPercentage": 51 + } + } + } + } + }, + "description": "Success, response with body " + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "403": { + "$ref": "#/components/responses/403_New" + }, + "404": { + "$ref": "#/components/responses/404_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get voting data by _id", + "description": "Get data of the current voting state.\n\nNotice that it can return 403 in case the logged-in player does not have permission to access the voting, for example if the player tries to access a voting that is an internal for other Clan." + }, + "parameters": [ + { + "examples": { + "1": { + "value": "668a70ce91020196cb10d595" + } + }, + "name": "_id", + "description": "voting _id", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/leaderboard": { + "get": { + "tags": [ + "Leaderboard", + "in-development" + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "1": { + "value": { + "data": { + "Clan": [ + { + "_id": "667eedc9b3b5bf0f7a840ef1", + "name": "User2 clan", + "tag": "tag2", + "gameCoins": 90, + "admin_ids": [ + "666720806cc90102f60bd325" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "points": 20, + "id": "667eedc9b3b5bf0f7a840ef1" + }, + { + "_id": "667bfec6afb8211b4bd8dbff", + "name": "User1 clan", + "tag": "tag1", + "gameCoins": 4, + "admin_ids": [ + "665df7026bf5b8f670569ea4" + ], + "playerCount": 1, + "itemCount": 0, + "stockCount": 0, + "isOpen": true, + "points": 10, + "id": "667bfec6afb8211b4bd8dbff" + } + ] + }, + "metaData": { + "dataKey": "Clan", + "modelName": "Clan", + "dataType": "Array", + "dataCount": 2 + }, + "paginationData": { + "currentPage": 1, + "limit": 2, + "offset": 0, + "itemCount": 2, + "pageCount": 1 + } + } + } + } + } + }, + "description": "Success, with body" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + {} + ], + "summary": "Get leaderboard data", + "description": "Various leaderboards define the best ones in different categories, for example top Players or top Clans.\n\nNotice that the leaderboards data is updated once every 12 hours.\n\n## Leaderboard parameters\n\n### Player\n\nTop Players are defined by the amount of points that he/she has.\n\n### Clan\n\nTop Clans are defined by the amount of points that each Clan has." + }, + "parameters": [ + { + "examples": { + "top clans": { + "value": "Clan" + } + }, + "name": "category", + "description": "Category of the leaderboard", + "schema": { + "enum": [ + "Clan", + "Player" + ], + "type": "string" + }, + "in": "query", + "required": true + } + ] + }, + "/playerTasks": { + "get": { + "tags": [ + "in-development", + "PlayerTasks" + ], + "responses": { + "200": { + "description": "Success, response with body" + }, + "400": { + "$ref": "#/components/responses/400_New" + }, + "401": { + "$ref": "#/components/responses/401_New" + }, + "500": { + "$ref": "#/components/responses/500_New" + } + }, + "security": [ + { + "JWTAuth": [] + } + ], + "summary": "Get Player's tasks", + "description": "Returns a list of logged-in Player's tasks. \n\nThe period query works as follows:\n\ntoday - all tasks for today (completed and uncomleted)\n\nweek - all tasks for the current week (completed and uncomleted) including daily tasks.\n\nmonth - all tasks for the current month (completed and uncompleted), including weekly and daily tasks\n\n| Feature | Has |\n| --------------- | ----------------------- |\n| Authentication | yes |\n| Authorization | only Player's own tasks |\n| Search | yes | \n| Sort | yes |\n| Pagination | yes | \n\n## General description\n\nThe daily task is a predefined task for an individual player to perform.\n\nThere are tasks for each day, each two weeks and each month.\n\nPlayer is getting points for each completed task and each task brings different amount of points and coins.\n\nThese points and coins are going to the player's clan. The points are used to determine top clans in the game and the coins are used to purchase items from the flee market of the game." + }, + "parameters": [ + { + "name": "period", + "description": "Period of time for the tasks", + "schema": { + "enum": [ + "today", + "week", + "month" + ], + "type": "string" + }, + "in": "query", + "required": false + } + ] + } + }, + "components": { + "schemas": { + "200": { + "description": "Found object(s). The response objects has three fields: data, metaData and paginationData.\nThe data has the requested data. It has an object or array, which key is a collection name.\n\nThe metaData contains additional data, which can be used for parsing the data object: \ndataKey - collection name, in data object,\ndataType - type of object in data object.\n\nThe paginationData is the data for pagination. Only on GET requests, when reading many objects\n", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + }, + "metaData": { + "type": "object", + "properties": { + "dataKey": { + "description": "collection name", + "type": "string" + }, + "modelName": { + "description": "model name, usually same as dataKey", + "type": "string" + }, + "dataType": { + "description": "Object or Array", + "type": "string" + }, + "dataCount": { + "description": "1 for Object dataType and length if Array", + "type": "integer" + } + } }, "paginationData": { "type": "object", @@ -3225,6 +3410,11 @@ "description": "The identifier for the player, unique", "type": "string", "example": "1" + }, + "above13": { + "description": "Is Player age 13 or above", + "type": "boolean", + "example": true } } } @@ -3275,6 +3465,18 @@ "example": [ "64df3aad42cbaf850a3f891f" ] + }, + "labels": { + "description": "labels that describes the clan, notice that it is an enum", + "default": [], + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "eläinrakkaat", + "itsenäiset" + ] } } }, @@ -3373,6 +3575,16 @@ "description": "The id of the associated Profile", "type": "string", "example": "6686cac271adebdb10f33fee" + }, + "above13": { + "description": "Is Player age 13 or above, default null", + "type": "boolean", + "example": true + }, + "parentalAuth": { + "description": "Is Player got parents approval, default null", + "type": "boolean", + "example": true } } }, @@ -3404,6 +3616,16 @@ "description": "The id of the associated Profile", "type": "string", "example": "6686cac271adebdb10f33fee" + }, + "above13": { + "description": "Is Player age 13 or above", + "type": "boolean", + "example": true + }, + "parentalAuth": { + "description": "Is Player got parents approval, default null", + "type": "boolean", + "example": true } } }, @@ -3427,7 +3649,7 @@ "example": 23 }, "tag": { - "description": "amount of clan coins", + "description": "deprecated use labels instead, clan's tag", "type": "string", "example": "my_tag" }, @@ -3436,6 +3658,18 @@ "default": true, "type": "boolean", "example": true + }, + "labels": { + "description": "labels that describes the clan, notice that it is an enum", + "default": [], + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "eläinrakkaat", + "itsenäiset" + ] } } }, @@ -3527,6 +3761,17 @@ } }, "ChatCreate": { + "description": "Create a new Chat", + "type": "object", + "properties": { + "name": { + "description": "The name of the chat", + "type": "string", + "example": "Chat 1" + } + } + }, + "ChatUpdate": { "description": "Update an existing Chat", "type": "object", "properties": { @@ -3550,17 +3795,6 @@ } } }, - "ChatUpdate": { - "description": "Create a new Chat", - "type": "object", - "properties": { - "name": { - "description": "The name of the chat", - "type": "string", - "example": "Chat 1" - } - } - }, "MessageCreate": { "description": "", "type": "object", @@ -4676,10 +4910,6 @@ } ], "tags": [ - { - "name": "release-03-09-24", - "description": "Endpoints affected in 03.09.2024 release" - }, { "name": "in-development", "description": "endpoint is changing or not yet implemented" @@ -4723,6 +4953,26 @@ { "name": "Chat", "description": "chats and their messages " + }, + { + "name": "GameAnalytics", + "description": "Various functionality for game analytics" + }, + { + "name": "FleaMarket", + "description": "Flea market, place where Clans can sell own items or buy other's." + }, + { + "name": "Voting", + "description": "In-game votings" + }, + { + "name": "Leaderboard", + "description": "Leaderboards of different categories" + }, + { + "name": "PlayerTasks", + "description": "In-game tasks for Player to do daily, weekly, monthly" } ], "externalDocs": {