diff --git a/src/main/java/cz/trailsthroughshadows/api/debug/DebugController.java b/src/main/java/cz/trailsthroughshadows/api/debug/DebugController.java index 0bd3fb0..82ff5a0 100644 --- a/src/main/java/cz/trailsthroughshadows/api/debug/DebugController.java +++ b/src/main/java/cz/trailsthroughshadows/api/debug/DebugController.java @@ -1,4 +1,9 @@ package cz.trailsthroughshadows.api.debug; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; @@ -19,8 +24,31 @@ public class DebugController { @Autowired private ResourceLoader resourceLoader; + @Operation( + summary = "Get Latest Logs", + description = """ + # Get Latest Logs + Retrieves the latest entries from the system's log file up to a specified number of characters. This endpoint is useful for quickly accessing recent log data without needing to download or search through the entire log file. + + **Parameters**: + - `characters` - Optional. Specifies the maximum number of characters to retrieve from the end of the log file. Defaults to 50000 characters. + + This endpoint is particularly useful for developers and system administrators for monitoring and troubleshooting purposes. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Log entries successfully retrieved", + content = {@Content(mediaType = "text/plain")}), + @ApiResponse(responseCode = "500", description = "Error reading log file", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/logs/latest") - public String getLatestLogs(@RequestParam(required = false, defaultValue = "50000") int characters) throws IOException { + public String getLatestLogs( + @Parameter(description = "The maximum number of characters from the log file to retrieve. If not specified, defaults to 50000 characters.", required = false) + @RequestParam(required = false, defaultValue = "50000") int characters + ) throws IOException { String currDir = System.getProperty("user.dir"); String path = currDir + "/logs/latest.log"; Resource resource = resourceLoader.getResource("file:" + path); diff --git a/src/main/java/cz/trailsthroughshadows/api/images/ImageController.java b/src/main/java/cz/trailsthroughshadows/api/images/ImageController.java index 4f440e5..e9a58ea 100644 --- a/src/main/java/cz/trailsthroughshadows/api/images/ImageController.java +++ b/src/main/java/cz/trailsthroughshadows/api/images/ImageController.java @@ -1,6 +1,12 @@ package cz.trailsthroughshadows.api.images; import cz.trailsthroughshadows.api.configuration.ImageLoaderConfig; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; @@ -28,16 +34,53 @@ public class ImageController { private ResourceLoader resourceLoader; + @Operation( + summary = "Retrieve Customizable Image", + description = """ + # Retrieve Customizable Image + Fetches an image based on specified parameters allowing for dynamic customization. This endpoint caters to various needs by providing options to adjust the size, apply rounding to corners, and select specific image types or tokens. + + **Parameters**: + - `type` - The category or collection to which the image belongs. + - `file` - The specific file name of the image to retrieve. + - `width` - Optional. The desired width to which the image should be resized. + - `height` - Optional. The desired height to which the image should be resized. + - `size` - Optional. A single value to resize the image to a square of specified dimensions. + - `radius` - Optional, defaults to 0. Applies a radius to round the corners of the image. + - `token` - Optional. A boolean that if set to true, fetches images from a token-specific path. + + This endpoint is designed to be flexible, supporting various image retrieval scenarios from adjusting dimensions to accessing specific types of images like tokens. If the requested image is not found, a default placeholder image is returned, potentially resized and rounded according to specified parameters. Image is in png format. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Image successfully retrieved", + content = {@Content(mediaType = "image/png", // Assume PNG, adjust as necessary + schema = @Schema(type = "string", format = "binary"))}), + @ApiResponse(responseCode = "404", description = "Image not found", + content = @Content), + @ApiResponse(responseCode = "500", description = "Error processing image request", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{type}/{file}") @Cacheable(value = "image", key = "T(java.util.Objects).hash(#type, #file, #width, #height, #size, #radius, #token)") public @ResponseBody byte[] getImage( + @Parameter(description = "The category or collection to which the image belongs.", required = true) @PathVariable String type, + @Parameter(description = "The specific file name of the image to retrieve.", required = true) @PathVariable String file, + @Parameter(description = "The desired width to which the image should be resized. If not specified, the image will be returned in its original size.", required = false) @RequestParam(required = false) Integer width, + @Parameter(description = "The desired height to which the image should be resized. If not specified, the image will be returned in its original size.", required = false) @RequestParam(required = false) Integer height, + @Parameter(description = "A single value to resize the image to a square of specified dimensions. Overrides individual width/height settings if provided.", required = false) @RequestParam(required = false) Integer size, + @Parameter(description = "Applies a radius to round the corners of the image, given in pixels. Defaults to 0, meaning no rounding.", required = false) @RequestParam(required = false, defaultValue = "0") Integer radius, - @RequestParam(required = false) boolean token) throws IOException { + @Parameter(description = "A boolean indicating if the image should be retrieved from a token-specific path. Useful for token images in games.", required = false) + @RequestParam(required = false) boolean token + ) throws IOException { String localpath = type + "/" + ((token) ? "tokens/" : "") + file; var physicalPath = config.getPath(); @@ -80,10 +123,34 @@ public class ImageController { } + @Operation( + summary = "Retrieve SVG File", + description = """ + # Retrieve SVG File + Fetches a scalable vector graphics (SVG) file based on a specified type and file name. This endpoint is designed to serve SVG files, which are ideal for high-quality graphics that need to be scalable without loss of resolution. + + **Parameters**: + - `type` - The category or collection to which the SVG file belongs. + - `file` - The specific file name of the SVG file to retrieve. The file name must include the '.svg' extension. + + This method ensures that SVG files are easily accessible and can be retrieved dynamically based on their type and name. This is particularly useful for applications requiring high-quality graphic representations that are scalable. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "SVG file successfully retrieved", + content = {@Content(mediaType = "image/svg+xml", + schema = @Schema(type = "string", format = "binary"))}), + @ApiResponse(responseCode = "404", description = "SVG file not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping(value = "/svg/{type}/{file:.+\\.svg}") @Cacheable(value = "svg", key = "T(java.util.Objects).hash(#type, #file)") - public ResponseEntity getSwg( + public ResponseEntity getSvg( + @Parameter(description = "The category or collection to which the SVG file belongs.", required = true) @PathVariable String type, + @Parameter(description = "The specific file name of the SVG file to retrieve, including the '.svg' extension.", required = true) @PathVariable String file ) throws IOException { diff --git a/src/main/java/cz/trailsthroughshadows/api/table/action/ActionController.java b/src/main/java/cz/trailsthroughshadows/api/table/action/ActionController.java index 8f1c590..ccce9aa 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/action/ActionController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/action/ActionController.java @@ -65,31 +65,40 @@ public class ActionController { summary = "Get all actions", description = """ # Get all actions - By default it lazy loads items and returns them - + This endpoint retrieves all actions with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the actions. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=id:asc,title:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. """ ) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "All actions", + @ApiResponse(responseCode = "200", description = "All actions retrieved successfully", content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = Action.class))}), + schema = @Schema(implementation = RestPaginatedResult.class))}), @ApiResponse(responseCode = "default", description = "Unexpected error", content = @Content) }) @GetMapping("/actions") - @Cacheable(value = "action", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Cacheable(value = "action", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> findAllEntities( - @Parameter(description = "Page number, starts from 0", required = false) + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, - @Parameter(description = "Number of items per page", required = false) + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "Filtering string", required = false) + @Parameter(description = "Filter conditions in the format: &filter=title:eq:fireball,id:bwn:1_20,type:is:false,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, - @Parameter(description = "Sorting string", required = false) + @Parameter(description = "Sorting parameters in the format: &sort=id:asc,title:desc,... Controls the order in which actions are returned.", required = false) @RequestParam(defaultValue = "") String sort, - @Parameter(description = "Case sensitive fields which you want to be loaded", required = false) + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, - @Parameter(description = "- **false** - all fields are loaded \n - **true** - loaded only things that are in include", required = false) + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -116,25 +125,29 @@ public ResponseEntity> findAllEntities( } @Operation( - summary = "Get a action by its id", + summary = "Get an action by its id", description = """ - # Get a action by its id - By default it loads all fields and returns them - - Action contains attack, movement, skill, restoreCards, summonActions and some of them contains effects - - - **attack** - contains effects - - **movement** - contains effects - - **skill** - contains effects - - **restoreCards** - doesnt contains effects - - **summonActions** - contains effects + # Get an action by its id + Retrieves a specific action by its unique identifier. By default, it loads all fields and returns them unless specified otherwise through query parameters. + + **Parameters**: + - `id` - The unique identifier of the action to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + The Action model includes: + - **attack**: Contains effects + - **movement**: Contains effects + - **skill**: Contains effects + - **restoreCards**: Does not contain effects + - **summonActions**: Contains effects """ ) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Found the action", + @ApiResponse(responseCode = "200", description = "Action successfully found", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Action.class))}), - @ApiResponse(responseCode = "400", description = "Invalid id supplied", + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", content = @Content), @ApiResponse(responseCode = "404", description = "Action not found", content = @Content), @@ -142,15 +155,16 @@ public ResponseEntity> findAllEntities( content = @Content) }) @GetMapping("/actions/{id}") - @Cacheable(value = "action", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "action", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( - @Parameter(description = "Id of the action to be obtained.\n\n Cannot be empty.", required = true) + @Parameter(description = "The unique identifier of the action to be retrieved. Cannot be empty.", required = true) @PathVariable int id, - @Parameter(description = "Case sensitive fields which you want to be loaded", required = false) + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, - @Parameter(description = "- **false** - all fields are loaded \n - **true** - loaded only things that are in include", required = false) + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { + ActionDTO entity = actionRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Action with id '{}' not found!", id)); @@ -294,17 +308,19 @@ public ActionDTO createInnner(ActionDTO action) { @Operation( summary = "Update an existing action", description = """ - # Update an existing action - This endpoint is used to update an existing action in the system. It requires two parameters: - 1. `id` (Path Variable): This is the unique identifier of the action to be updated. It cannot be empty and is required for the operation. - 2. `action` (Request Body): This is the action data that will be used to update the existing action. It cannot be null or empty and is required for the operation. - """ + # Update an existing action + Updates an action using its unique identifier with the provided action details. This operation requires: + - `id` - The unique identifier of the action to be updated. It must be provided as a path variable. + - `action` - The updated details of the action, provided within the request body. + """ ) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Action updated", + @ApiResponse(responseCode = "200", description = "Action successfully updated", content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = Action.class))}), - @ApiResponse(responseCode = "400", description = "Invalid input", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", content = @Content), @ApiResponse(responseCode = "404", description = "Action not found", content = @Content), @@ -313,11 +329,14 @@ public ActionDTO createInnner(ActionDTO action) { }) @PutMapping("/actions/{id}") @CacheEvict(value = "action", allEntries = true) + @ResponseStatus(HttpStatus.OK) public ResponseEntity update( - @Parameter(description = "Id of the action to be updated.\n\n Cannot be empty.", required = true) + @Parameter(description = "The unique identifier of the action to be updated. Cannot be empty.", required = true) @PathVariable int id, - @Parameter(description = "Action to add. Cannot null or empty.", required = true) - @RequestBody ActionDTO action) { + @Parameter(description = "The action data to be used for the update. Cannot be null or empty.", required = true) + @RequestBody ActionDTO action + ) { + validation.validate(action); ActionDTO updated = updateInner(id, action); return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Action with id '{}' updated!", id), HttpStatus.OK); @@ -327,14 +346,21 @@ public ResponseEntity update( summary = "Delete an action", description = """ # Delete an action - Check if action exists and delete it + This endpoint allows for the deletion of an action specified by its unique identifier. It checks if the action exists and then proceeds to delete it, permanently removing it from the system. + + **Parameters**: + - `id` - The unique identifier of the action to be deleted. It must be provided in the path to execute the deletion. + + Users must be authorized to perform this operation, ensuring that only eligible users can delete actions. """ ) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Action created", + @ApiResponse(responseCode = "200", description = "Action deleted successfully", content = {@Content(mediaType = "application/json", - schema = @Schema(implementation = Action.class))}), - @ApiResponse(responseCode = "400", description = "Invalid input", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", content = @Content), @ApiResponse(responseCode = "404", description = "Action not found", content = @Content), @@ -343,7 +369,11 @@ public ResponseEntity update( }) @DeleteMapping("/actions/{id}") @CacheEvict(value = "action", allEntries = true) - public ResponseEntity delete(@PathVariable int id) { + public ResponseEntity delete( + @Parameter(description = "The unique identifier of the action to be deleted. Cannot be empty.", required = true) + @PathVariable int id + ) { + ActionDTO entity = actionRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Action with id '{}' not found!", id)); @@ -352,9 +382,36 @@ public ResponseEntity delete(@PathVariable int id) { return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Action with id '{}' deleted!", id), HttpStatus.OK); } + @Operation( + summary = "Create multiple actions", + description = """ + # Create multiple actions + This endpoint allows for the batch creation of multiple actions at once. Clients must provide a list of action data in the request body. + + **Parameters**: + - `actions` - List of action details; each must conform to the ActionDTO specification for successful creation. + + This method is particularly useful for initializing data or bulk imports, offering an efficient way to handle multiple records simultaneously. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All actions created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/actions") @CacheEvict(value = "action", allEntries = true) - public ResponseEntity createList(@RequestBody List actions) { + public ResponseEntity createList( + @Parameter(description = "List of action data to be created. Each entry must conform to the ActionDTO structure and include all necessary details as required by the system.", required = true) + @RequestBody List actions + ) { + List ids = new ArrayList<>(); for (ActionDTO action : actions) { @@ -368,9 +425,36 @@ public ResponseEntity createList(@RequestBody List a HttpStatus.OK); } + @Operation( + summary = "Get Action Card Details", + description = """ + # Get Action Card Details by Action ID + Retrieves details necessary for creating a card representation of a specific action using its unique identifier. This includes data related to the source of the action (like enemy, class, or race), along with specific visual representations such as color and icon based on the source. + + **Parameters**: + - `id` - The unique identifier of the action whose card details are to be retrieved. This is required and cannot be empty. + + This endpoint will determine the source of the action (whether it's associated with an enemy, a class, or a race) and will return specific styling attributes such as color and icon. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Action card details successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = LinkedHashMap.class))}), + @ApiResponse(responseCode = "404", description = "Action not found", + content = @Content), + @ApiResponse(responseCode = "418", description = "Error while converting to JSON", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/actions/{id}/card") - @Cacheable(value = "actionCard", key="T(java.util.Objects).hash(#id)") - public ResponseEntity> getCard(@PathVariable int id) { + @Cacheable(value = "actionCard", key = "T(java.util.Objects).hash(#id)") + public ResponseEntity> getCard( + @Parameter(description = "The unique identifier of the action to be retrieved. Cannot be empty.", required = true) + @PathVariable int id + ) { + ActionDTO entitydto = actionRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Action with id '{}' not found!", id)); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/background/clazz/ClazzController.java b/src/main/java/cz/trailsthroughshadows/api/table/background/clazz/ClazzController.java index b3d6454..0ad12ad 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/background/clazz/ClazzController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/background/clazz/ClazzController.java @@ -16,6 +16,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -38,14 +44,44 @@ public class ClazzController { private EffectRepo effectRepo; private ActionRepo actionRepo; + @Operation( + summary = "Get all classes", + description = """ + # Get all classes + Retrieves all class records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the classes. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,complexity:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All classes retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/classes") - @Cacheable(value = "class", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") - public ResponseEntity> getEnemies( + @Cacheable(value = "class", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + public ResponseEntity> getClasses( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:Warrior,complexity:lte:5,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=name:asc,complexity:desc,... Controls the order in which classes are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -71,11 +107,39 @@ public ResponseEntity> getEnemies( return new ResponseEntity<>(RestPaginatedResult.of(pagination, entriesPage.stream().map(Clazz::fromDTO).toList()), HttpStatus.OK); } + @Operation( + summary = "Get Class by ID", + description = """ + # Get Class by ID + Retrieves detailed information about a specific class using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific requirements. + + **Parameters**: + - `id` - The unique identifier of the class to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve class data while allowing customization of the returned data set. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Class successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Clazz.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Class not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/classes/{id}") - @Cacheable(value = "class", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "class", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the class to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { ClazzDTO entity = clazzRepo @@ -91,10 +155,35 @@ public ResponseEntity findById( return new ResponseEntity<>(Clazz.fromDTO(entity), HttpStatus.OK); } + @Operation( + summary = "Update an existing class", + description = """ + # Update an existing class + Updates a class entity using its unique identifier with the provided class details. This operation requires: + - `id` - The unique identifier of the class to be updated. It must be provided as a path variable. + - `entity` - The updated details of the class, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Class successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Class not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("classes/{id}") @CacheEvict(value = "class", allEntries = true) + @ResponseStatus(HttpStatus.OK) public ResponseEntity updateEntity( + @Parameter(description = "The unique identifier of the class to be updated. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "The class data to be used for the update. Cannot be null or empty.", required = true) @RequestBody ClazzDTO entity ) { log.debug("Updating class with id: " + id); @@ -161,9 +250,33 @@ public ResponseEntity updateEntity( return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Class with id '{}' updated!", id), HttpStatus.OK); } + @Operation( + summary = "Create multiple classes", + description = """ + # Create multiple classes + This endpoint allows for the batch creation of multiple class entities at once. Clients must provide a list of class details in the request body. + + **Parameters**: + - `clazzes` - List of class details; each must conform to the ClazzDTO specification for successful creation. + + This method is particularly useful for initializing class data or conducting bulk imports, offering an efficient way to handle multiple class records simultaneously. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All classes created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/classes") @CacheEvict(value = "class", allEntries = true) public ResponseEntity createEntity( + @Parameter(description = "List of class data to be created. Each entry must conform to the ClazzDTO structure and include all necessary details as required by the system.", required = true) @RequestBody List clazzes ) { // Validate enemies @@ -221,9 +334,35 @@ private EffectDTO processEffects(EffectDTO inputEffect) { return effect; } + @Operation( + summary = "Delete a class", + description = """ + # Delete a class + This endpoint allows for the deletion of a class specified by its unique identifier. The operation checks if the class exists within the system and then deletes it, thereby permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the class to be deleted. This ID must be specified in the path to locate and delete the class. + + It is necessary to verify user authorization before performing this action to ensure that only qualified users can delete classes. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Class deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User does not have permission to delete the class", + content = @Content), + @ApiResponse(responseCode = "404", description = "Class not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @DeleteMapping("/classes/{id}") @CacheEvict(value = "class", allEntries = true) public ResponseEntity deleteEntity( + @Parameter(description = "The unique identifier of the class to be deleted. Cannot be empty.", required = true) @PathVariable int id ) { ClazzDTO entity = clazzRepo diff --git a/src/main/java/cz/trailsthroughshadows/api/table/campaign/CampaignController.java b/src/main/java/cz/trailsthroughshadows/api/table/campaign/CampaignController.java index b682422..a81a9c5 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/campaign/CampaignController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/campaign/CampaignController.java @@ -13,6 +13,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,14 +49,44 @@ public class CampaignController { @Autowired private ValidationService validation; - @GetMapping("") - @Cacheable(value = "campaign", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Operation( + summary = "Get all campaigns", + description = """ + # Get all campaigns + This endpoint retrieves all campaign records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the campaigns. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,start_date:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All campaigns retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) + @GetMapping("/campaigns") + @Cacheable(value = "campaign", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> findAllEntities( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:Quest for the Holy Grail,start_date:gte:2020-01-01,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=name:asc,start_date:desc,... Controls the order in which campaigns are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -76,11 +112,39 @@ public ResponseEntity> findAllEntities( return new ResponseEntity<>(RestPaginatedResult.of(pagination, entries), HttpStatus.OK); } + @Operation( + summary = "Get Campaign by ID", + description = """ + # Get Campaign by ID + Retrieves detailed information about a campaign using its unique identifier. This endpoint supports selective field loading through optional parameters, enabling optimized data retrieval based on specific needs. + + **Parameters**: + - `id` - The unique identifier of the campaign to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This approach allows clients to fine-tune the response to fit specific use cases or to reduce network overhead. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Campaign successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = CampaignDTO.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Campaign not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{id}") - @Cacheable(value = "campaign", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "campaign", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public CampaignDTO findById( + @Parameter(description = "The unique identifier of the campaign to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { CampaignDTO entity = campaignRepo @@ -96,12 +160,40 @@ public CampaignDTO findById( } + @Operation( + summary = "Get Campaign Location by Campaign and Location IDs", + description = """ + # Get Campaign Location by Campaign and Location IDs + Retrieves detailed information about a specific location within a campaign using the campaign's unique identifier and the location's unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval. + + **Parameters**: + - `id` - The unique identifier of the campaign. + - `idLocation` - The unique identifier of the location within the campaign. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is ideal for retrieving specific location details within a broader campaign context, reducing the necessity to load full campaign data. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Campaign location successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = CampaignLocation.class))}), + @ApiResponse(responseCode = "404", description = "Campaign or location not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{id}/location/{idLocation}") - @Cacheable(value = "campaign", key="T(java.util.Objects).hash(#id, #idLocation, #include, #lazy)") + @Cacheable(value = "campaign", key = "T(java.util.Objects).hash(#id, #idLocation, #include, #lazy)") public CampaignLocation findById2( + @Parameter(description = "The unique identifier of the campaign. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "The unique identifier of the location within the campaign. Cannot be empty.", required = true) @PathVariable int idLocation, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { CampaignDTO entity = campaignRepo @@ -121,10 +213,38 @@ public CampaignLocation findById2( return location; } - @Transactional + @Operation( + summary = "Update an existing campaign", + description = """ + # Update an existing campaign + Updates a campaign using its unique identifier with the provided campaign details. This operation requires: + - `id` - The unique identifier of the campaign to be updated. It must be provided as a path variable. + - `campaign` - The updated details of the campaign, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Campaign successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Campaign not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/{id}") @CacheEvict(value = "campaign", allEntries = true) - public ResponseEntity update(@PathVariable int id, @RequestBody CampaignDTO campaign) { + @Transactional + @ResponseStatus(HttpStatus.OK) + public ResponseEntity update( + @Parameter(description = "The unique identifier of the campaign to be updated. Cannot be empty.", required = true) + @PathVariable int id, + @Parameter(description = "The campaign data to be used for the update. Cannot be null or empty.", required = true) + @RequestBody CampaignDTO campaign + ) { log.debug("Updating campaign with id '{}': {}", id, campaign); // Validate campaign @@ -196,10 +316,37 @@ public ResponseEntity update(@PathVariable int id, @RequestBody return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Campaign with id '{}' updated!", id), HttpStatus.OK); } + @Operation( + summary = "Delete a campaign", + description = """ + # Delete a campaign + This endpoint facilitates the deletion of a campaign by its unique identifier. Once the campaign is identified, the system proceeds to delete it, thus permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the campaign to be deleted. This identifier is required to locate the campaign in the system. + + Only authorized users with the right privileges can perform this operation, ensuring the integrity and security of the data. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Campaign deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "User not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Campaign not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "An unexpected error occurred", + content = @Content) + }) @Transactional @DeleteMapping("/{id}") @CacheEvict(value = "campaign", allEntries = true) - public ResponseEntity delete(@PathVariable int id) { + public ResponseEntity delete( + @Parameter(description = "The unique identifier of the campaign to be deleted. Cannot be empty.", required = true) + @PathVariable int id) { CampaignDTO campaign = campaignRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Campaign with id '{}' not found!", id)); @@ -209,10 +356,36 @@ public ResponseEntity delete(@PathVariable int id) { } - @Transactional + @Operation( + summary = "Create multiple campaigns", + description = """ + # Create multiple campaigns + This endpoint allows for the batch creation of multiple campaigns at once. Clients must provide a list of campaign details in the request body. + + **Parameters**: + - `campaigns` - List of campaign details; each entry must conform to the CampaignDTO specification for successful creation. + + This method is particularly useful for initializing campaign data or conducting bulk imports, offering an efficient way to handle multiple campaign records simultaneously. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All campaigns created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("") @CacheEvict(value = "campaign", allEntries = true) - public ResponseEntity create(@RequestBody List campaigns) { + @Transactional + public ResponseEntity create( + @Parameter(description = "List of campaign data to be created. Each entry must conform to the CampaignDTO structure and include all necessary details as required by the system.", required = true) + @RequestBody List campaigns + ) { log.info("Creating campaigns: {}", campaigns); // validate all and set ids to null @@ -270,9 +443,33 @@ public ResponseEntity create(@RequestBody List cam return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Campaigns created: " + ids), HttpStatus.OK); } - @Cacheable(value = "campaignTree", key = "#id") + @Operation( + summary = "Get Campaign Tree Structure", + description = """ + # Get Campaign Tree Structure + Retrieves the hierarchical tree structure of a campaign using its unique identifier. This endpoint is particularly useful for back-office applications where understanding the relational structure of campaign elements is necessary. + + **Parameters**: + - `id` - The unique identifier of the campaign to retrieve the tree structure for. This identifier is required and must correspond to an existing campaign. + + This method provides a visual or structured representation of the campaign elements, facilitating easier navigation and management in back-office systems. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Campaign tree structure successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(type = "string"))}), // Assuming the output is a JSON string. + @ApiResponse(responseCode = "404", description = "Campaign not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping(value = "/{id}/tree", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getTree(@PathVariable int id) { + @Cacheable(value = "campaignTree", key = "#id") + public ResponseEntity getTree( + @Parameter(description = "The unique identifier of the campaign to retrieve its tree structure. Cannot be empty.", required = true) + @PathVariable int id + ) { Campaign campaign = Campaign.fromDTO(campaignRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Campaign with id '{}' not found!", id))); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/effect/EffectController.java b/src/main/java/cz/trailsthroughshadows/api/table/effect/EffectController.java index 8229d5a..14ec1f7 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/effect/EffectController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/effect/EffectController.java @@ -11,6 +11,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; @@ -28,15 +34,46 @@ public class EffectController { private ValidationService validation; private EffectRepo effectRepo; + @Operation( + summary = "Get all effects", + description = """ + # Get all effects + This endpoint retrieves all effects with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the effects. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,duration:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All effects retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/effects") - @Cacheable(value = "effect", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") - public ResponseEntity> getEnemies( + @Cacheable(value = "effect", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + public ResponseEntity> getEffects( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=type:eq:Magical,name:has:fire,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=name:asc,duration:desc,... Controls the order in which effects are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, - @RequestParam(required = false, defaultValue = "true") boolean lazy) { + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) + @RequestParam(required = false, defaultValue = "true") boolean lazy + ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty // Issue: https://github.com/Trails-Through-Shadows/TTS-API/issues/31 @@ -62,6 +99,22 @@ public ResponseEntity> getEnemies( RestPaginatedResult.of(pagination, entriesPage.stream().map(Effect::fromDTO).toList()), HttpStatus.OK); } + @Operation( + summary = "Get All Effect Types", + description = """ + # Get All Effect Types + Retrieves a list of all possible effect types defined in the system's enumeration. This endpoint is useful for understanding the various types of effects that can be applied within the system, such as in configurations or during operations that manipulate or rely on effect-based logic. + + This endpoint returns an array of effect types, helping clients understand and select appropriate effects for different scenarios. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Effect types successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = EffectEnum.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/enum/effectType") public ResponseEntity> getEffectType() { List obj = Arrays.stream(EffectDTO.EffectType.values()) @@ -75,12 +128,41 @@ public ResponseEntity> getEffectTarget() { return new ResponseEntity<>(Arrays.asList(EffectDTO.EffectTarget.values()), HttpStatus.OK); } + @Operation( + summary = "Get Effect by ID", + description = """ + # Get Effect by ID + Retrieves detailed information about a specific effect using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific requirements. + + **Parameters**: + - `id` - The unique identifier of the effect to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve detailed data on individual effects, providing flexibility in data retrieval and reducing overhead. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Effect successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Effect.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Effect not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/effects/{id}") - @Cacheable(value = "effect", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "effect", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the effect to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, - @RequestParam(required = false, defaultValue = "false") boolean lazy) { + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) + @RequestParam(required = false, defaultValue = "false") boolean lazy + ) { EffectDTO entity = effectRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Effect with id '{}' not found!", id)); @@ -94,8 +176,36 @@ public ResponseEntity findById( return new ResponseEntity<>(Effect.fromDTO(entity), HttpStatus.OK); } + @Operation( + summary = "Update an existing effect", + description = """ + # Update an existing effect + Updates an effect entity using its unique identifier with the provided effect details. This operation requires: + - `id` - The unique identifier of the effect to be updated. It must be provided as a path variable. + - `effect` - The updated details of the effect, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Effect successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Effect not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/effects/{id}") - public ResponseEntity updateEffect(@PathVariable int id, @RequestBody EffectDTO effect) { + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updateEffect( + @Parameter(description = "The unique identifier of the effect to be updated. Cannot be empty.", required = true) + @PathVariable int id, + @Parameter(description = "The effect data to be used for the update. Cannot be null or empty.", required = true) + @RequestBody EffectDTO effect + ) { validation.validate(effect); EffectDTO existing = effectRepo.findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Effect with id '{}' not found!", id)); @@ -112,8 +222,34 @@ public ResponseEntity updateEffect(@PathVariable int id, @Reque } + @Operation( + summary = "Create multiple effects", + description = """ + # Create multiple effects + This endpoint allows for the batch creation of multiple effects at once. Clients must provide a list of effect details in the request body. + + **Parameters**: + - `effect` - List of effect details; each entry must conform to the EffectDTO specification for successful creation. + + This method is particularly useful for initializing effect data or conducting bulk imports, offering an efficient way to handle multiple effect records simultaneously. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All effects created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/effects") - public ResponseEntity createEffect(@RequestBody List effect) { + public ResponseEntity createEffect( + @Parameter(description = "List of effect data to be created. Each entry must conform to the EffectDTO structure and include all necessary details as required by the system.", required = true) + @RequestBody List effect + ) { effect.forEach(validation::validate); effect.forEach(e -> e.setId(null)); effectRepo.saveAll(effect); @@ -122,8 +258,35 @@ public ResponseEntity createEffect(@RequestBody List return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Effects with ids '%s'", ids), HttpStatus.OK); } + @Operation( + summary = "Delete an effect", + description = """ + # Delete an effect + This endpoint facilitates the deletion of an effect identified by its unique identifier. Upon identifying the effect, it proceeds to delete it from the system, thereby permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the effect to be deleted. This ID must be specified in the path to ensure the correct effect is targeted for deletion. + + Deleting an effect requires proper authorization checks to ensure that only users with sufficient permissions can perform this action. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Effect deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User lacks necessary permissions to delete the effect", + content = @Content), + @ApiResponse(responseCode = "404", description = "Effect not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @DeleteMapping("/effects/{id}") - public ResponseEntity deleteEffect(@PathVariable int id) { + public ResponseEntity deleteEffect( + @Parameter(description = "The unique identifier of the effect to be deleted. Cannot be empty.", required = true) + @PathVariable int id) { EffectDTO effect = effectRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Effect with id '{}' not found!", id)); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/enemy/EnemyController.java b/src/main/java/cz/trailsthroughshadows/api/table/enemy/EnemyController.java index aa8ad6b..404c474 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/enemy/EnemyController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/enemy/EnemyController.java @@ -17,6 +17,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -39,14 +45,44 @@ public class EnemyController { private EffectRepo effectRepo; private ActionRepo actionRepo; + @Operation( + summary = "Get all enemies", + description = """ + # Get all enemies + This endpoint retrieves all enemy entities with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the enemies. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,strength:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All enemies retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/enemies") - @Cacheable(value = "enemy", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Cacheable(value = "enemy", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> getEnemies( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:Goblin,type:has:undead,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=strength:asc,speed:desc,... Controls the order in which enemies are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -74,11 +110,39 @@ public ResponseEntity> getEnemies( RestPaginatedResult.of(pagination, entriesPage.stream().map(Enemy::fromDTO).toList()), HttpStatus.OK); } + @Operation( + summary = "Get Enemy by ID", + description = """ + # Get Enemy by ID + Retrieves detailed information about a specific enemy using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific needs. + + **Parameters**: + - `id` - The unique identifier of the enemy to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve detailed data on individual enemies, providing flexibility in data retrieval and reducing overhead for systems and applications. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Enemy successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Enemy.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Enemy not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/enemies/{id}") - @Cacheable(value = "enemy", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "enemy", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the enemy to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { EnemyDTO entity = enemyRepo @@ -94,10 +158,35 @@ public ResponseEntity findById( return new ResponseEntity<>(Enemy.fromDTO(entity), HttpStatus.OK); } + @Operation( + summary = "Update an existing enemy", + description = """ + # Update an existing enemy + Updates an enemy entity using its unique identifier with the provided enemy details. This operation requires: + - `id` - The unique identifier of the enemy to be updated. It must be provided as a path variable. + - `enemy` - The updated details of the enemy, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Enemy successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Enemy not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/enemies/{id}") @CacheEvict(value = "enemy", allEntries = true) + @ResponseStatus(HttpStatus.OK) public ResponseEntity updateEnemyById( + @Parameter(description = "The unique identifier of the enemy to be updated. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "The enemy data to be used for the update. Cannot be null or empty.", required = true) @RequestBody EnemyDTO enemy ) { log.debug("Updating enemy with id: " + id); @@ -164,9 +253,33 @@ public ResponseEntity updateEnemyById( return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Enemy with id '{}' updated!", id), HttpStatus.OK); } + @Operation( + summary = "Create multiple enemies", + description = """ + # Create multiple enemies + This endpoint allows for the batch creation of multiple enemies at once. Clients must provide a list of enemy details in the request body. + + **Parameters**: + - `enemies` - List of enemy details; each entry must conform to the EnemyDTO specification for successful creation. + + This method is particularly useful for populating game worlds or scenarios with multiple enemies, offering an efficient way to handle bulk data management. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All enemies created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/enemies") @CacheEvict(value = "enemy", allEntries = true) public ResponseEntity createEnemies( + @Parameter(description = "List of enemy data to be created. Each entry must conform to the EnemyDTO structure and include all necessary details as required by the system.", required = true) @RequestBody List enemies ) { // Validate enemies @@ -224,9 +337,37 @@ private EffectDTO processEffects(EffectDTO inputEffect) { return effect; } + @Operation( + summary = "Delete an enemy", + description = """ + # Delete an enemy + This endpoint allows for the deletion of an enemy specified by its unique identifier. After verifying that the enemy exists within the system, it proceeds to remove the enemy permanently from the database. + + **Parameters**: + - `id` - The unique identifier of the enemy to be deleted. This ID is crucial for locating the enemy in the system to ensure accurate deletion. + + The operation also includes an authorization check to confirm that only users with appropriate permissions can execute deletions of enemy entities. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Enemy deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User lacks permission to delete the enemy", + content = @Content), + @ApiResponse(responseCode = "404", description = "Enemy not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @DeleteMapping("/enemies/{id}") @CacheEvict(value = "enemy", allEntries = true) - public ResponseEntity deleteEnemy(@PathVariable int id) { + public ResponseEntity deleteEnemy( + @Parameter(description = "The unique identifier of the enemy to be deleted. Cannot be empty.", required = true) + @PathVariable int id) { + EnemyDTO enemyDTO = enemyRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Enemy with id %d not found", id)); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/market/item/ItemController.java b/src/main/java/cz/trailsthroughshadows/api/table/market/item/ItemController.java index fb5b04c..33a8e58 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/market/item/ItemController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/market/item/ItemController.java @@ -10,6 +10,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -27,14 +33,44 @@ public class ItemController { private ValidationService validation; private ItemRepo itemRepo; + @Operation( + summary = "Get all items", + description = """ + # Get all items + This endpoint retrieves all item records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the items. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,value:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All items retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/items") - @Cacheable(value = "item", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") - public ResponseEntity> findAllEntities( + @Cacheable(value = "item", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + public ResponseEntity> findAllItems( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=type:eq:Weapon,rarity:has:Rare,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=type:asc,value:desc,... Controls the order in which items are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -60,11 +96,39 @@ public ResponseEntity> findAllEntities( return new ResponseEntity<>(RestPaginatedResult.of(pagination, entriesPage.stream().map(Item::fromDTO).toList()), HttpStatus.OK); } + @Operation( + summary = "Get Item by ID", + description = """ + # Get Item by ID + Retrieves detailed information about a specific item using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific needs. + + **Parameters**: + - `id` - The unique identifier of the item to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve detailed data on individual items, providing flexibility in data retrieval and reducing overhead for systems and applications. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Item successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Item.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Item not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/items/{id}") - @Cacheable(value = "item", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "item", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the item to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { ItemDTO entity = itemRepo @@ -80,9 +144,35 @@ public ResponseEntity findById( return new ResponseEntity<>(Item.fromDTO(entity), HttpStatus.OK); } + @Operation( + summary = "Create multiple items", + description = """ + # Create multiple items + This endpoint allows for the batch creation of multiple items at once. Clients must provide a list of item details in the request body. + + **Parameters**: + - `entities` - List of item details; each entry must conform to the ItemDTO specification for successful creation. + + This method is particularly useful for populating databases with item data or conducting bulk imports, offering an efficient way to manage multiple item records simultaneously. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All items created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/items") @CacheEvict(value = "item", allEntries = true) - public ResponseEntity createEntity(@RequestBody List enTITIES) { + public ResponseEntity createEntity( + @Parameter(description = "List of item data to be created. Each entry must conform to the ItemDTO structure and include all necessary details as required by the system.", required = true) + @RequestBody List enTITIES + ) { enTITIES.forEach(validation::validate); List saved = itemRepo.saveAll(enTITIES); String ids = saved.stream().map(ItemDTO::getId).map(String::valueOf).toList().toString(); @@ -90,9 +180,37 @@ public ResponseEntity createEntity(@RequestBody List e HttpStatus.OK); } + @Operation( + summary = "Update an existing item", + description = """ + # Update an existing item + Updates an item using its unique identifier with the provided item details. This operation requires: + - `id` - The unique identifier of the item to be updated. It must be provided as a path variable. + - `item` - The updated details of the item, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Item successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Item not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/items/{id}") @CacheEvict(value = "item", allEntries = true) - public ResponseEntity update(@PathVariable int id, @RequestBody ItemDTO item) { + @ResponseStatus(HttpStatus.OK) + public ResponseEntity update( + @Parameter(description = "The unique identifier of the item to be updated. Cannot be empty.", required = true) + @PathVariable int id, + @Parameter(description = "The item data to be used for the update. Cannot be null or empty.", required = true) + @RequestBody ItemDTO item + ) { validation.validate(item); ItemDTO entityToUPdate = itemRepo @@ -112,9 +230,36 @@ public ResponseEntity update(@PathVariable int id, @RequestBody return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Item with id '{}' updated!", id), HttpStatus.OK); } + @Operation( + summary = "Delete an item", + description = """ + # Delete an item + This endpoint facilitates the deletion of an item identified by its unique identifier. It checks if the item exists within the system and then deletes it, thereby permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the item to be deleted. This ID must be provided in the path to ensure the correct item is targeted for deletion. + + The deletion operation includes an authorization check to ensure that only users with the appropriate permissions can perform this action. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Item deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User lacks necessary permissions to delete the item", + content = @Content), + @ApiResponse(responseCode = "404", description = "Item not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @DeleteMapping("/items/{id}") @CacheEvict(value = "item", allEntries = true) - public ResponseEntity delete(@PathVariable int id) { + public ResponseEntity delete( + @Parameter(description = "The unique identifier of the item to be deleted. Cannot be empty.", required = true) + @PathVariable int id) { ItemDTO entityToDelete = itemRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Item with id '{}' not found!", id)); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/playerdata/adventure/AdventureController.java b/src/main/java/cz/trailsthroughshadows/api/table/playerdata/adventure/AdventureController.java index ecea336..914b129 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/playerdata/adventure/AdventureController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/playerdata/adventure/AdventureController.java @@ -15,6 +15,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -38,11 +44,39 @@ public class AdventureController { @Autowired private CharacterService characterService; + @Operation( + summary = "Get Adventure by ID", + description = """ + # Get Adventure by ID + Retrieves an adventure by its unique identifier. Optionally, specific fields can be included or excluded in the response based on the 'include' and 'lazy' parameters. + + **Parameters**: + - `id` - The unique identifier of the adventure to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + The method ensures that data retrieval is efficient and customized as needed by the consumer of the API. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Adventure successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Adventure.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Adventure not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{id}") - @Cacheable(value = "adventure", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "adventure", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the adventure to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy, HttpServletRequest request ) { @@ -63,16 +97,46 @@ public ResponseEntity findById( return new ResponseEntity<>(res, HttpStatus.OK); } - @GetMapping("") - @Cacheable(value = "adventure", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Operation( + summary = "Get all adventures", + description = """ + # Get all adventures + This endpoint retrieves all adventure records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the adventures. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=id:asc,title:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All adventures retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) + @GetMapping("/adventures") + @Cacheable(value = "adventure", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> findAllEntities( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=title:eq:fireball,id:bwn:1_20,type:is:false,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=id:asc,title:desc,... Controls the order in which adventures are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy, - HttpServletRequest request + @Parameter(hidden = true) HttpServletRequest request ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty // Issue: https://github.com/Trails-Through-Shadows/TTS-API/issues/31 @@ -100,49 +164,161 @@ public ResponseEntity> findAllEntities( return new ResponseEntity<>(RestPaginatedResult.of(pagination, entriesPage), HttpStatus.OK); } + @Operation( + summary = "Add an adventure", + description = """ + # Add an adventure + This endpoint is for adding a new adventure associated with a specific license. The client must provide the adventure details in the request body. + + **Parameters**: + - `idLicense` - The ID of the license to which the adventure is to be added. + - `adventure` - The details of the adventure, conforming to the AdventureDTO specification. + + This method is useful for dynamically adding adventures to a given license, facilitating easy expansion and management of available adventures. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Adventure added successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body or incorrect license ID", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "License not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/{idLicense}") @CacheEvict(value = "adventure", allEntries = true) public ResponseEntity addAdventure( + @Parameter(description = "The ID of the license to which the adventure is being added.", required = true) @PathVariable int idLicense, + + @Parameter(description = "Adventure details to be added. Must conform to the AdventureDTO specification.", required = true) @RequestBody AdventureDTO adventure, + HttpServletRequest request ) { Session session = sessionHandler.getSessionFromRequest(request); return new ResponseEntity<>(adventureService.add(adventure, idLicense, session), HttpStatus.OK); } + @Operation( + summary = "Update an existing adventure", + description = """ + # Update an existing adventure + Updates an adventure using its unique identifier with the provided adventure details. This operation requires: + - `id` - The unique identifier of the adventure to be updated. It must be provided as a path variable. + - `adventure` - The updated details of the adventure, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Adventure successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Adventure not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/{id}") @CacheEvict(value = "adventure", allEntries = true) + @ResponseStatus(HttpStatus.OK) public ResponseEntity updateAdventure( + @Parameter(description = "The unique identifier of the adventure to be updated. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "The adventure data to be used for the update. Cannot be null or empty.", required = true) @RequestBody AdventureDTO adventure, - HttpServletRequest request + @Parameter(hidden = true) HttpServletRequest request ) { Session session = sessionHandler.getSessionFromRequest(request); return new ResponseEntity<>(adventureService.update(id, adventure, session), HttpStatus.OK); } + @Operation( + summary = "Delete an adventure", + description = """ + # Delete an adventure + This endpoint allows for the deletion of an adventure specified by its unique identifier. It checks if the adventure exists and then proceeds to delete it, permanently removing it from the system. + + **Parameters**: + - `id` - The unique identifier of the adventure to be deleted. It must be provided in the path to execute the deletion. + + The operation requires checking user authorization to ensure that only eligible users can delete adventures. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Adventure deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Adventure not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @DeleteMapping("/{id}") @CacheEvict(value = "adventure", allEntries = true) public ResponseEntity deleteAdventure( + @Parameter(description = "The unique identifier of the adventure to be deleted. Cannot be empty.", required = true) @PathVariable int id, - HttpServletRequest request - ) { + HttpServletRequest request) { Session session = sessionHandler.getSessionFromRequest(request); return new ResponseEntity<>(adventureService.delete(id, session), HttpStatus.OK); } + @Operation( + summary = "Get all characters in an adventure", + description = """ + # Get all characters in an adventure + Retrieves all character records associated with a specific adventure ID, supporting advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `id` - The adventure ID to which the characters are related. + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the characters. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,age:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All characters retrieved successfully for the specified adventure", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{id}/characters") - @Cacheable(value = "adventure", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Cacheable(value = "adventure", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> findAllEntities( + @Parameter(description = "Adventure ID from which characters are retrieved.", required = true) + @PathVariable int id, + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:John,age:gte:30,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=name:asc,level:desc,... Controls the order in which characters are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy, - @PathVariable int id, - HttpServletRequest request + @Parameter(hidden = true) HttpServletRequest request ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty // Issue: https://github.com/Trails-Through-Shadows/TTS-API/issues/31 @@ -172,11 +348,41 @@ public ResponseEntity> findAllEntities( return new ResponseEntity<>(RestPaginatedResult.of(pagination, characters), HttpStatus.OK); } + @Operation( + summary = "Add a character to an adventure", + description = """ + # Add a character to an adventure + This endpoint allows clients to add a new character to an existing adventure using the adventure's unique ID. The details of the character must be provided in the request body. + + **Parameters**: + - `id` - The ID of the adventure to which the character is being added. + - `character` - The details of the character, adhering to the CharacterDTO specification. + + This method facilitates the expansion of an adventure's story by allowing the addition of new characters dynamically. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Character added successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body or incorrect adventure ID", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Adventure not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/{id}/characters") @CacheEvict(value = "adventure", allEntries = true) public ResponseEntity addCharacter( + @Parameter(description = "The ID of the adventure to which the character is being added. This ID should match an existing adventure in the system.", required = true) @PathVariable int id, + + @Parameter(description = "Character details to be added to the adventure. The provided data must conform to the CharacterDTO specification.", required = true) @RequestBody CharacterDTO character, + HttpServletRequest request ) { Session session = sessionHandler.getSessionFromRequest(request); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/playerdata/character/CharacterController.java b/src/main/java/cz/trailsthroughshadows/api/table/playerdata/character/CharacterController.java index 300baa5..9f79875 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/playerdata/character/CharacterController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/playerdata/character/CharacterController.java @@ -14,6 +14,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -37,11 +43,39 @@ public class CharacterController { @Autowired AdventureService adventureService; + @Operation( + summary = "Get Character by ID", + description = """ + # Get Character by ID + Retrieves detailed information about a specific character using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific needs. + + **Parameters**: + - `id` - The unique identifier of the character to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve detailed data on individual characters, providing flexibility in data retrieval and reducing overhead for systems and applications. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Character successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Character.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Character not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/{id}") - @Cacheable(value = "character",key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "character", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( + @Parameter(description = "The unique identifier of the character to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy, HttpServletRequest request ) { @@ -62,16 +96,46 @@ public ResponseEntity findById( return new ResponseEntity<>(Character.fromDTO(entity), HttpStatus.OK); } - @GetMapping("") - @Cacheable(value = "character", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Operation( + summary = "Get all characters", + description = """ + # Get all characters + This endpoint retrieves all character records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of items per page, default is 100. + - `filter` - Defines the conditions for filtering the characters. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,level:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All characters retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) + @GetMapping("/characters") + @Cacheable(value = "character", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> findAllEntities( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of items per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:John,class:has:Warrior,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=level:asc,name:desc,... Controls the order in which characters are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy, - HttpServletRequest request + @Parameter(hidden = true) HttpServletRequest request ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty // Issue: https://github.com/Trails-Through-Shadows/TTS-API/issues/31 @@ -100,23 +164,75 @@ public ResponseEntity> findAllEntities( return new ResponseEntity<>(RestPaginatedResult.of(pagination, characters), HttpStatus.OK); } + @Operation( + summary = "Update an existing character", + description = """ + # Update an existing character + Updates a character using its unique identifier with the provided character details. This operation requires: + - `id` - The unique identifier of the character to be updated. It must be provided as a path variable. + - `character` - The updated details of the character, provided within the request body. + Additionally, the session information is retrieved from the HTTP request to ensure that the operation is performed within the context of a valid session. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Character successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Character not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/{id}") @CacheEvict(value = "character", allEntries = true) + @ResponseStatus(HttpStatus.OK) public ResponseEntity updateCharacter( + @Parameter(description = "The unique identifier of the character to be updated. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "The character data to be used for the update. Cannot be null or empty.", required = true) @RequestBody CharacterDTO character, - HttpServletRequest request + @Parameter(hidden = true) HttpServletRequest request ) { Session session = sessionHandler.getSessionFromRequest(request); return new ResponseEntity<>(characterService.update(id, character, session), HttpStatus.OK); } + @Operation( + summary = "Delete a character", + description = """ + # Delete a character + This endpoint allows for the deletion of a character specified by its unique identifier. It checks the existence of the character in the system and then deletes it, permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the character to be deleted. This ID must be provided in the path to locate and ensure the correct character is targeted for deletion. + + This operation requires user authorization to confirm that only users with appropriate permissions can delete characters, maintaining the security and integrity of the data. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Character deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User lacks necessary permissions to delete the character", + content = @Content), + @ApiResponse(responseCode = "404", description = "Character not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @DeleteMapping("/{id}") @CacheEvict(value = "character", allEntries = true) public ResponseEntity deleteCharacter( + @Parameter(description = "The unique identifier of the character to be deleted. Cannot be empty.", required = true) @PathVariable int id, - HttpServletRequest request - ) { + HttpServletRequest request) { + Session session = sessionHandler.getSessionFromRequest(request); return new ResponseEntity<>(characterService.delete(id, session), HttpStatus.OK); } diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/location/LocationController.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/location/LocationController.java index d70755e..48643cb 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/location/LocationController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/location/LocationController.java @@ -18,6 +18,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -40,14 +46,44 @@ public class LocationController { private LocationRepo locationRepo; private CampaignRepo campaignRepo; + @Operation( + summary = "Get all locations", + description = """ + # Get all locations + This endpoint retrieves all location records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of locations per page, default is 100. + - `filter` - Defines the conditions for filtering the locations. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=name:asc,importance:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All locations retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/locations") - @Cacheable(value = "location", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + @Cacheable(value = "location", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") public ResponseEntity> getLocations( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of locations per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=name:eq:Evergreen Forest,type:has:Natural,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=name:asc,area:desc,... Controls the order in which locations are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @@ -67,26 +103,46 @@ public ResponseEntity> getLocations( if (lazy && !include.isEmpty()) { entriesPage.forEach(e -> Initialization.hibernateInitializeAll(e, include)); -// if (include.contains("stories")) { -// List loc = entriesPage.stream().map(Location::fromDTO).toList(); -// //loc.forEach(l -> l.findStories(campaignRepo)); -// return new ResponseEntity<>(RestPaginatedResult.of(pagination, loc), HttpStatus.OK); -// } } else if (!lazy) { entriesPage.forEach(Initialization::hibernateInitializeAll); -// List loc = entriesPage.stream().map(Location::fromDTO).toList(); -// //loc.forEach(l -> l.findStories(campaignRepo)); -// return new ResponseEntity<>(RestPaginatedResult.of(pagination, loc), HttpStatus.OK); } return new ResponseEntity<>(RestPaginatedResult.of(pagination, entriesPage.stream().map(Location::fromDTO).toList()), HttpStatus.OK); } + @Operation( + summary = "Get Location by ID", + description = """ + # Get Location by ID + Retrieves detailed information about a specific location using its unique identifier. This endpoint supports selective field loading through optional parameters, allowing for optimized data retrieval tailored to specific needs. + + **Parameters**: + - `id` - The unique identifier of the location to be retrieved. This is required and cannot be empty. + - `include` - Optional. Specifies the case-sensitive fields to be loaded. If left empty, all fields are loaded. + - `lazy` - Optional. Controls the loading of fields: if set to **true**, only fields specified in 'include' are loaded; if **false** or omitted, all fields are loaded. + + This method is designed to efficiently retrieve detailed data on individual locations, providing flexibility in data retrieval and reducing overhead for systems and applications. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Location successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Location.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID supplied", + content = @Content), + @ApiResponse(responseCode = "404", description = "Location not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/locations/{id}") - @Cacheable(value = "location", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "location", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity getLocationById( + @Parameter(description = "The unique identifier of the location to be retrieved. Cannot be empty.", required = true) @PathVariable int id, + @Parameter(description = "Specifies the case-sensitive fields to be loaded. Leave empty to load all fields.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **false** - All fields are loaded; **true** - Only specified fields in 'include' are loaded.", required = false) @RequestParam(required = false, defaultValue = "false") boolean lazy ) { LocationDTO entity = locationRepo @@ -103,10 +159,34 @@ public ResponseEntity getLocationById( return new ResponseEntity<>(Location.fromDTO(entity), HttpStatus.OK); } + @Operation( + summary = "Get Part by Location ID", + description = """ + # Get Part by Location ID + Retrieves detailed information about a specific part within a given location using both the location's ID and the part's ID. This endpoint allows precise access to parts of locations, facilitating detailed queries about segments or components of larger geographical or structural areas. + + **Parameters**: + - `idLocation` - The unique identifier of the location to which the part belongs. This identifier is required. + - `idPart` - The unique identifier of the part within the location that is to be retrieved. This identifier is also required. + + This method is particularly useful for accessing detailed data about specific sections or components of a location, which may be crucial for applications dealing with geographic, urban, or architectural data. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Part successfully retrieved", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = Part.class))}), + @ApiResponse(responseCode = "404", description = "Location or part not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/locations/{idLocation}/parts/{idPart}") - @Cacheable(value = "location", key="T(java.util.Objects).hash(#idLocation, #idPart)") + @Cacheable(value = "location", key = "T(java.util.Objects).hash(#idLocation, #idPart)") public ResponseEntity getPartByLocationId( + @Parameter(description = "The unique identifier of the location to which the part belongs. Cannot be empty.", required = true) @PathVariable Integer idLocation, + @Parameter(description = "The unique identifier of the part within the location that is to be retrieved. Cannot be empty.", required = true) @PathVariable Integer idPart ) { @@ -130,10 +210,36 @@ public ResponseEntity getPartByLocationId( return new ResponseEntity<>(retPart, HttpStatus.OK); } - @Transactional + @Operation( + summary = "Create multiple locations", + description = """ + # Create multiple locations + This endpoint allows for the batch creation of multiple locations at once. Clients must provide a list of location details in the request body. + + **Parameters**: + - `locations` - List of location details; each entry must conform to the LocationDTO specification for successful creation. + + This method is particularly useful for setting up geographical or conceptual spaces within an application, facilitating bulk data management and initialization. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All locations created successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid data in request body", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PostMapping("/locations") + @Transactional @CacheEvict(value = "location", allEntries = true) - public ResponseEntity createLocation(@RequestBody List locations) { + public ResponseEntity createLocation( + @Parameter(description = "List of location data to be created. Each entry must conform to the LocationDTO structure and include all necessary details as required by the system.", required = true) + @RequestBody List locations + ) { log.debug("Creating locations: " + locations); // Validate all locations @@ -202,10 +308,38 @@ public ResponseEntity createLocation(@RequestBody List(MessageResponse.of(HttpStatus.OK, "Locations created: " + ids), HttpStatus.OK); } - @Transactional + @Operation( + summary = "Update an existing location", + description = """ + # Update an existing location + Updates a location using its unique identifier with the provided location details. This operation requires: + - `id` - The unique identifier of the location to be updated. It must be provided as a path variable. + - `location` - The updated details of the location, provided within the request body. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Location successfully updated", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid input or bad request", + content = @Content), + @ApiResponse(responseCode = "401", description = "Not authorized to perform this operation", + content = @Content), + @ApiResponse(responseCode = "404", description = "Location not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @PutMapping("/locations/{id}") + @Transactional @CacheEvict(value = "location", allEntries = true) - public ResponseEntity updateLocationById(@PathVariable int id, @RequestBody LocationDTO location) { + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updateLocationById( + @Parameter(description = "The unique identifier of the location to be updated. Cannot be empty.", required = true) + @PathVariable int id, + @Parameter(description = "The location data to be used for the update. Cannot be null or empty.", required = true) + @RequestBody LocationDTO location + ) { LocationDTO locationToUpdate = locationRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Location with id '{}' not found!", id)); @@ -227,10 +361,39 @@ public ResponseEntity updateLocationById(@PathVariable int id, return new ResponseEntity<>(MessageResponse.of(HttpStatus.OK, "Location updated!"), HttpStatus.OK); } + @Operation( + summary = "Delete a location", + description = """ + # Delete a location + This endpoint allows for the deletion of a location identified by its unique identifier. It checks if the location exists within the system and proceeds to delete it, thereby permanently removing it from the database. + + **Parameters**: + - `id` - The unique identifier of the location to be deleted. This ID must be provided in the path to ensure the correct location is targeted for deletion. + + The deletion is performed as a transaction, ensuring that all data changes are consistent and any related data dependencies are handled correctly. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Location deleted successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = MessageResponse.class))}), + @ApiResponse(responseCode = "400", description = "Invalid ID or request parameters", + content = @Content), + @ApiResponse(responseCode = "401", description = "Unauthorized - User lacks necessary permissions to delete the location", + content = @Content), + @ApiResponse(responseCode = "404", description = "Location not found", + content = @Content), + @ApiResponse(responseCode = "default", description = "Unexpected error occurred", + content = @Content) + }) @Transactional @DeleteMapping("/locations/{id}") @CacheEvict(value = "location", allEntries = true) - public ResponseEntity deleteLocationById(@PathVariable int id) { + public ResponseEntity deleteLocationById( + @Parameter(description = "The unique identifier of the location to be deleted. Cannot be empty.", required = true) + @PathVariable int id + ) { + LocationDTO location = locationRepo .findById(id) .orElseThrow(() -> RestException.of(HttpStatus.NOT_FOUND, "Location with id '{}' not found!", id)); diff --git a/src/main/java/cz/trailsthroughshadows/api/table/schematic/obstacle/ObstacleController.java b/src/main/java/cz/trailsthroughshadows/api/table/schematic/obstacle/ObstacleController.java index 85f379b..d6783c2 100644 --- a/src/main/java/cz/trailsthroughshadows/api/table/schematic/obstacle/ObstacleController.java +++ b/src/main/java/cz/trailsthroughshadows/api/table/schematic/obstacle/ObstacleController.java @@ -13,6 +13,12 @@ import cz.trailsthroughshadows.api.util.reflect.Filtering; import cz.trailsthroughshadows.api.util.reflect.Initialization; import cz.trailsthroughshadows.api.util.reflect.Sorting; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -34,14 +40,44 @@ public class ObstacleController { private ObstacleRepo obstacleRepo; private EffectRepo effectRepo; + @Operation( + summary = "Get all obstacles", + description = """ + # Get all obstacles + This endpoint retrieves all obstacle records with support for advanced query capabilities such as pagination, filtering, sorting, and selective field loading. By default, it employs lazy loading of items. + + **Parameters**: + - `page` - Specifies the page number, starting from 0. + - `limit` - Number of obstacles per page, default is 100. + - `filter` - Defines the conditions for filtering the obstacles. Supported operations include eq, of, is, gt, gte, lt, lte, has, and bwn. + - `sort` - Defines the order of the results. Format example: &sort=size:asc,difficulty:desc. + - `include` - Specifies which fields to load; if empty, all fields are considered. + - `lazy` - Determines if only specified fields should be loaded (true) or all fields (false). + + These parameters allow for detailed customization of the returned data, accommodating various user needs for data retrieval and display. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "All obstacles retrieved successfully", + content = {@Content(mediaType = "application/json", + schema = @Schema(implementation = RestPaginatedResult.class))}), + @ApiResponse(responseCode = "default", description = "Unexpected error", + content = @Content) + }) @GetMapping("/obstacles") - @Cacheable(value = "obstacle", key="T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") - public ResponseEntity> findAllEntities( + @Cacheable(value = "obstacle", key = "T(java.util.Objects).hash(#page, #limit, #filter, #sort, #include, #lazy)") + public ResponseEntity> findAllObstacles( + @Parameter(description = "Page number, starts from 0. Helps in paginating the result set.", required = false) @RequestParam(defaultValue = "0") int page, + @Parameter(description = "Number of obstacles per page. Determines the size of each page of results.", required = false) @RequestParam(defaultValue = "100") int limit, + @Parameter(description = "Filter conditions in the format: &filter=type:eq:Natural,height:gte:10,... Supported operations include: eq, of, is, gt, gte, lt, lte, has, bwn (between, numbers are split by _).", required = false) @RequestParam(defaultValue = "") String filter, + @Parameter(description = "Sorting parameters in the format: &sort=difficulty:asc,size:desc,... Controls the order in which obstacles are returned.", required = false) @RequestParam(defaultValue = "") String sort, + @Parameter(description = "Specifies the fields to be loaded, which is case sensitive. If left empty, all fields are loaded.", required = false) @RequestParam(required = false, defaultValue = "") List include, + @Parameter(description = "Controls the loading of fields: **true** loads only specified fields in 'include', **false** loads all fields.", required = false) @RequestParam(required = false, defaultValue = "true") boolean lazy ) { // TODO: Re-Implement filtering, sorting and pagination @rcMarty @@ -71,7 +107,7 @@ public ResponseEntity> findAllEntities( } @GetMapping("/obstacles/{id}") - @Cacheable(value = "obstacle", key="T(java.util.Objects).hash(#id, #include, #lazy)") + @Cacheable(value = "obstacle", key = "T(java.util.Objects).hash(#id, #include, #lazy)") public ResponseEntity findById( @PathVariable int id, @RequestParam(required = false, defaultValue = "") List include, @@ -91,7 +127,7 @@ public ResponseEntity findById( } @DeleteMapping("/obstacles/{id}") - @CacheEvict(value = "obstacle",allEntries = true) + @CacheEvict(value = "obstacle", allEntries = true) public ResponseEntity deleteObstacleById( @PathVariable int id ) {