@rashidee/co2 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# REST API Patterns — Controllers, DTOs, and Conventions
|
|
2
|
+
|
|
3
|
+
This reference describes the REST API-specific patterns for the Spring Boot application.
|
|
4
|
+
Include this content in Sections 7-9 of the generated specification.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Architecture Overview
|
|
9
|
+
|
|
10
|
+
The REST API uses a layered composition model:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Request → SecurityFilter → CorrelationIdFilter → @RestController
|
|
14
|
+
→ Service (queries DB via Repository)
|
|
15
|
+
→ Response DTO (mapped via MapStruct)
|
|
16
|
+
→ JSON Response (serialized by Jackson)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## REST Controller Pattern
|
|
22
|
+
|
|
23
|
+
Every REST controller follows this structure:
|
|
24
|
+
|
|
25
|
+
```java
|
|
26
|
+
package {{BASE_PACKAGE}}.{{module}}.internal;
|
|
27
|
+
|
|
28
|
+
import {{BASE_PACKAGE}}.{{module}}.{{ModuleName}}Service;
|
|
29
|
+
import {{BASE_PACKAGE}}.{{module}}.{{ModuleName}}DTO;
|
|
30
|
+
import io.swagger.v3.oas.annotations.Operation;
|
|
31
|
+
import io.swagger.v3.oas.annotations.Parameter;
|
|
32
|
+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
33
|
+
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
34
|
+
import jakarta.validation.Valid;
|
|
35
|
+
import lombok.RequiredArgsConstructor;
|
|
36
|
+
import org.springframework.data.domain.Page;
|
|
37
|
+
import org.springframework.data.domain.Pageable;
|
|
38
|
+
import org.springframework.data.web.PageableDefault;
|
|
39
|
+
import org.springframework.http.ResponseEntity;
|
|
40
|
+
import org.springframework.security.access.prepost.PreAuthorize;
|
|
41
|
+
import org.springframework.web.bind.annotation.*;
|
|
42
|
+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
|
43
|
+
import java.net.URI;
|
|
44
|
+
|
|
45
|
+
@RestController
|
|
46
|
+
@RequestMapping("/api/v1/{{resource-plural}}")
|
|
47
|
+
@RequiredArgsConstructor
|
|
48
|
+
@Tag(name = "{{ModuleName}}", description = "{{ModuleName}} management operations")
|
|
49
|
+
class {{ModuleName}}Controller {
|
|
50
|
+
|
|
51
|
+
private final {{ModuleName}}Service service;
|
|
52
|
+
|
|
53
|
+
@GetMapping
|
|
54
|
+
@Operation(summary = "List all {{resource-plural}} with pagination")
|
|
55
|
+
public ResponseEntity<Page<{{ModuleName}}DTO>> list(
|
|
56
|
+
@PageableDefault(size = 20, sort = "createdAt") Pageable pageable,
|
|
57
|
+
@RequestParam(required = false) String search) {
|
|
58
|
+
Page<{{ModuleName}}DTO> results = service.findAll(search, pageable);
|
|
59
|
+
return ResponseEntity.ok(results);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@GetMapping("/{id}")
|
|
63
|
+
@Operation(summary = "Get {{resource}} by ID")
|
|
64
|
+
@ApiResponse(responseCode = "200", description = "{{ModuleName}} found")
|
|
65
|
+
@ApiResponse(responseCode = "404", description = "{{ModuleName}} not found")
|
|
66
|
+
public ResponseEntity<{{ModuleName}}DTO> getById(@PathVariable String id) {
|
|
67
|
+
{{ModuleName}}DTO result = service.findById(id);
|
|
68
|
+
return ResponseEntity.ok(result);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@PostMapping
|
|
72
|
+
@Operation(summary = "Create a new {{resource}}")
|
|
73
|
+
@ApiResponse(responseCode = "201", description = "{{ModuleName}} created")
|
|
74
|
+
@ApiResponse(responseCode = "400", description = "Validation failed")
|
|
75
|
+
public ResponseEntity<{{ModuleName}}DTO> create(
|
|
76
|
+
@Valid @RequestBody Create{{ModuleName}}Request request) {
|
|
77
|
+
{{ModuleName}}DTO created = service.create(request);
|
|
78
|
+
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
|
|
79
|
+
.path("/{id}")
|
|
80
|
+
.buildAndExpand(created.id())
|
|
81
|
+
.toUri();
|
|
82
|
+
return ResponseEntity.created(location).body(created);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@PutMapping("/{id}")
|
|
86
|
+
@Operation(summary = "Update an existing {{resource}}")
|
|
87
|
+
@ApiResponse(responseCode = "200", description = "{{ModuleName}} updated")
|
|
88
|
+
@ApiResponse(responseCode = "404", description = "{{ModuleName}} not found")
|
|
89
|
+
public ResponseEntity<{{ModuleName}}DTO> update(
|
|
90
|
+
@PathVariable String id,
|
|
91
|
+
@Valid @RequestBody Update{{ModuleName}}Request request) {
|
|
92
|
+
{{ModuleName}}DTO updated = service.update(id, request);
|
|
93
|
+
return ResponseEntity.ok(updated);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@DeleteMapping("/{id}")
|
|
97
|
+
@Operation(summary = "Delete a {{resource}}")
|
|
98
|
+
@ApiResponse(responseCode = "204", description = "{{ModuleName}} deleted")
|
|
99
|
+
@ApiResponse(responseCode = "404", description = "{{ModuleName}} not found")
|
|
100
|
+
public ResponseEntity<Void> delete(@PathVariable String id) {
|
|
101
|
+
service.delete(id);
|
|
102
|
+
return ResponseEntity.noContent().build();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Request/Response DTO Pattern
|
|
110
|
+
|
|
111
|
+
### Response DTO (Java Record)
|
|
112
|
+
|
|
113
|
+
Response DTOs are immutable Java records that represent the API contract for responses:
|
|
114
|
+
|
|
115
|
+
```java
|
|
116
|
+
package {{BASE_PACKAGE}}.{{module}};
|
|
117
|
+
|
|
118
|
+
import java.time.Instant;
|
|
119
|
+
|
|
120
|
+
public record {{ModuleName}}DTO(
|
|
121
|
+
String id,
|
|
122
|
+
String name,
|
|
123
|
+
String code,
|
|
124
|
+
String description,
|
|
125
|
+
String status,
|
|
126
|
+
Instant createdAt,
|
|
127
|
+
Instant updatedAt,
|
|
128
|
+
String createdBy,
|
|
129
|
+
String updatedBy
|
|
130
|
+
) {}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Request DTO with Bean Validation
|
|
134
|
+
|
|
135
|
+
Request DTOs use Java records with Bean Validation annotations:
|
|
136
|
+
|
|
137
|
+
```java
|
|
138
|
+
package {{BASE_PACKAGE}}.{{module}};
|
|
139
|
+
|
|
140
|
+
import jakarta.validation.constraints.NotBlank;
|
|
141
|
+
import jakarta.validation.constraints.NotNull;
|
|
142
|
+
import jakarta.validation.constraints.Positive;
|
|
143
|
+
import jakarta.validation.constraints.Size;
|
|
144
|
+
|
|
145
|
+
public record Create{{ModuleName}}Request(
|
|
146
|
+
@NotBlank(message = "Name is required")
|
|
147
|
+
@Size(max = 255, message = "Name must not exceed 255 characters")
|
|
148
|
+
String name,
|
|
149
|
+
|
|
150
|
+
@NotBlank(message = "Code is required")
|
|
151
|
+
@Size(max = 50, message = "Code must not exceed 50 characters")
|
|
152
|
+
String code,
|
|
153
|
+
|
|
154
|
+
@Size(max = 1000, message = "Description must not exceed 1000 characters")
|
|
155
|
+
String description
|
|
156
|
+
) {}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```java
|
|
160
|
+
package {{BASE_PACKAGE}}.{{module}};
|
|
161
|
+
|
|
162
|
+
import jakarta.validation.constraints.Size;
|
|
163
|
+
|
|
164
|
+
public record Update{{ModuleName}}Request(
|
|
165
|
+
@Size(max = 255, message = "Name must not exceed 255 characters")
|
|
166
|
+
String name,
|
|
167
|
+
|
|
168
|
+
@Size(max = 1000, message = "Description must not exceed 1000 characters")
|
|
169
|
+
String description
|
|
170
|
+
) {}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Standardized Error Response
|
|
176
|
+
|
|
177
|
+
### Error Envelope
|
|
178
|
+
|
|
179
|
+
All error responses use a consistent JSON structure:
|
|
180
|
+
|
|
181
|
+
```java
|
|
182
|
+
package {{BASE_PACKAGE}}.shared.exception;
|
|
183
|
+
|
|
184
|
+
import java.time.Instant;
|
|
185
|
+
import java.util.List;
|
|
186
|
+
|
|
187
|
+
public record ApiError(
|
|
188
|
+
Instant timestamp,
|
|
189
|
+
int status,
|
|
190
|
+
String error,
|
|
191
|
+
String message,
|
|
192
|
+
String path,
|
|
193
|
+
List<FieldError> fieldErrors
|
|
194
|
+
) {
|
|
195
|
+
public record FieldError(String field, String message) {}
|
|
196
|
+
|
|
197
|
+
public static ApiError of(int status, String error, String message, String path) {
|
|
198
|
+
return new ApiError(Instant.now(), status, error, message, path, null);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public static ApiError withFieldErrors(int status, String error, String message,
|
|
202
|
+
String path, List<FieldError> fieldErrors) {
|
|
203
|
+
return new ApiError(Instant.now(), status, error, message, path, fieldErrors);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Example Error Responses
|
|
209
|
+
|
|
210
|
+
**404 Not Found:**
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"timestamp": "2026-03-13T10:15:30Z",
|
|
214
|
+
"status": 404,
|
|
215
|
+
"error": "Not Found",
|
|
216
|
+
"message": "Job Demand not found with id: 12345",
|
|
217
|
+
"path": "/api/v1/job-demands/12345",
|
|
218
|
+
"fieldErrors": null
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**400 Validation Error:**
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"timestamp": "2026-03-13T10:15:30Z",
|
|
226
|
+
"status": 400,
|
|
227
|
+
"error": "Validation Failed",
|
|
228
|
+
"message": "Request body contains invalid fields",
|
|
229
|
+
"path": "/api/v1/job-demands",
|
|
230
|
+
"fieldErrors": [
|
|
231
|
+
{ "field": "name", "message": "Name is required" },
|
|
232
|
+
{ "field": "code", "message": "Code must not exceed 50 characters" }
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Global Exception Handler
|
|
240
|
+
|
|
241
|
+
```java
|
|
242
|
+
package {{BASE_PACKAGE}}.shared.exception;
|
|
243
|
+
|
|
244
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
245
|
+
import lombok.extern.slf4j.Slf4j;
|
|
246
|
+
import org.springframework.http.ResponseEntity;
|
|
247
|
+
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
248
|
+
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
249
|
+
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
250
|
+
|
|
251
|
+
import java.util.List;
|
|
252
|
+
|
|
253
|
+
@RestControllerAdvice
|
|
254
|
+
@Slf4j
|
|
255
|
+
public class GlobalExceptionHandler {
|
|
256
|
+
|
|
257
|
+
@ExceptionHandler(ResourceNotFoundException.class)
|
|
258
|
+
public ResponseEntity<ApiError> handleNotFound(
|
|
259
|
+
ResourceNotFoundException ex, HttpServletRequest request) {
|
|
260
|
+
ApiError error = ApiError.of(404, "Not Found",
|
|
261
|
+
ex.getMessage(), request.getRequestURI());
|
|
262
|
+
return ResponseEntity.status(404).body(error);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
266
|
+
public ResponseEntity<ApiError> handleValidation(
|
|
267
|
+
MethodArgumentNotValidException ex, HttpServletRequest request) {
|
|
268
|
+
List<ApiError.FieldError> fieldErrors = ex.getBindingResult()
|
|
269
|
+
.getFieldErrors().stream()
|
|
270
|
+
.map(fe -> new ApiError.FieldError(fe.getField(), fe.getDefaultMessage()))
|
|
271
|
+
.toList();
|
|
272
|
+
|
|
273
|
+
ApiError error = ApiError.withFieldErrors(400, "Validation Failed",
|
|
274
|
+
"Request body contains invalid fields",
|
|
275
|
+
request.getRequestURI(), fieldErrors);
|
|
276
|
+
return ResponseEntity.badRequest().body(error);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@ExceptionHandler(ConflictException.class)
|
|
280
|
+
public ResponseEntity<ApiError> handleConflict(
|
|
281
|
+
ConflictException ex, HttpServletRequest request) {
|
|
282
|
+
ApiError error = ApiError.of(409, "Conflict",
|
|
283
|
+
ex.getMessage(), request.getRequestURI());
|
|
284
|
+
return ResponseEntity.status(409).body(error);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@ExceptionHandler(Exception.class)
|
|
288
|
+
public ResponseEntity<ApiError> handleGeneral(
|
|
289
|
+
Exception ex, HttpServletRequest request) {
|
|
290
|
+
log.error("Unhandled exception at {}", request.getRequestURI(), ex);
|
|
291
|
+
ApiError error = ApiError.of(500, "Internal Server Error",
|
|
292
|
+
"An unexpected error occurred", request.getRequestURI());
|
|
293
|
+
return ResponseEntity.status(500).body(error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Base Exception Classes
|
|
301
|
+
|
|
302
|
+
```java
|
|
303
|
+
package {{BASE_PACKAGE}}.shared.exception;
|
|
304
|
+
|
|
305
|
+
public class ResourceNotFoundException extends RuntimeException {
|
|
306
|
+
public ResourceNotFoundException(String message) {
|
|
307
|
+
super(message);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
|
|
311
|
+
super(String.format("%s not found with %s: %s", resourceName, fieldName, fieldValue));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```java
|
|
317
|
+
package {{BASE_PACKAGE}}.shared.exception;
|
|
318
|
+
|
|
319
|
+
public class ConflictException extends RuntimeException {
|
|
320
|
+
public ConflictException(String message) {
|
|
321
|
+
super(message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Pagination Response Structure
|
|
329
|
+
|
|
330
|
+
Spring Data `Page` is serialized by Jackson automatically. The response structure:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"content": [
|
|
335
|
+
{ "id": "1", "name": "Widget A", "code": "WA01" },
|
|
336
|
+
{ "id": "2", "name": "Widget B", "code": "WB01" }
|
|
337
|
+
],
|
|
338
|
+
"pageable": {
|
|
339
|
+
"pageNumber": 0,
|
|
340
|
+
"pageSize": 20,
|
|
341
|
+
"sort": { "sorted": true, "unsorted": false, "empty": false },
|
|
342
|
+
"offset": 0,
|
|
343
|
+
"paged": true,
|
|
344
|
+
"unpaged": false
|
|
345
|
+
},
|
|
346
|
+
"totalElements": 142,
|
|
347
|
+
"totalPages": 8,
|
|
348
|
+
"last": false,
|
|
349
|
+
"first": true,
|
|
350
|
+
"size": 20,
|
|
351
|
+
"number": 0,
|
|
352
|
+
"sort": { "sorted": true, "unsorted": false, "empty": false },
|
|
353
|
+
"numberOfElements": 20,
|
|
354
|
+
"empty": false
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Controller Pagination Pattern
|
|
359
|
+
|
|
360
|
+
```java
|
|
361
|
+
@GetMapping
|
|
362
|
+
public ResponseEntity<Page<{{ModuleName}}DTO>> list(
|
|
363
|
+
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC)
|
|
364
|
+
Pageable pageable,
|
|
365
|
+
@RequestParam(required = false) String search) {
|
|
366
|
+
Page<{{ModuleName}}DTO> results = service.findAll(search, pageable);
|
|
367
|
+
return ResponseEntity.ok(results);
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Custom Page Serialization (Optional)
|
|
372
|
+
|
|
373
|
+
If you need a simplified page response:
|
|
374
|
+
|
|
375
|
+
```java
|
|
376
|
+
package {{BASE_PACKAGE}}.shared.dto;
|
|
377
|
+
|
|
378
|
+
import org.springframework.data.domain.Page;
|
|
379
|
+
import java.util.List;
|
|
380
|
+
|
|
381
|
+
public record PageResponse<T>(
|
|
382
|
+
List<T> content,
|
|
383
|
+
int pageNumber,
|
|
384
|
+
int pageSize,
|
|
385
|
+
long totalElements,
|
|
386
|
+
int totalPages,
|
|
387
|
+
boolean first,
|
|
388
|
+
boolean last
|
|
389
|
+
) {
|
|
390
|
+
public static <T> PageResponse<T> from(Page<T> page) {
|
|
391
|
+
return new PageResponse<>(
|
|
392
|
+
page.getContent(),
|
|
393
|
+
page.getNumber(),
|
|
394
|
+
page.getSize(),
|
|
395
|
+
page.getTotalElements(),
|
|
396
|
+
page.getTotalPages(),
|
|
397
|
+
page.isFirst(),
|
|
398
|
+
page.isLast()
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Correlation ID Filter
|
|
407
|
+
|
|
408
|
+
```java
|
|
409
|
+
package {{BASE_PACKAGE}}.shared.filter;
|
|
410
|
+
|
|
411
|
+
import jakarta.servlet.FilterChain;
|
|
412
|
+
import jakarta.servlet.ServletException;
|
|
413
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
414
|
+
import jakarta.servlet.http.HttpServletResponse;
|
|
415
|
+
import org.slf4j.MDC;
|
|
416
|
+
import org.springframework.stereotype.Component;
|
|
417
|
+
import org.springframework.web.filter.OncePerRequestFilter;
|
|
418
|
+
import java.io.IOException;
|
|
419
|
+
import java.util.Optional;
|
|
420
|
+
import java.util.UUID;
|
|
421
|
+
|
|
422
|
+
@Component
|
|
423
|
+
public class CorrelationIdFilter extends OncePerRequestFilter {
|
|
424
|
+
|
|
425
|
+
private static final String CORRELATION_HEADER = "X-Correlation-Id";
|
|
426
|
+
private static final String MDC_KEY = "correlationId";
|
|
427
|
+
|
|
428
|
+
@Override
|
|
429
|
+
protected void doFilterInternal(HttpServletRequest request,
|
|
430
|
+
HttpServletResponse response, FilterChain chain)
|
|
431
|
+
throws ServletException, IOException {
|
|
432
|
+
|
|
433
|
+
String correlationId = Optional
|
|
434
|
+
.ofNullable(request.getHeader(CORRELATION_HEADER))
|
|
435
|
+
.orElse(UUID.randomUUID().toString());
|
|
436
|
+
|
|
437
|
+
MDC.put(MDC_KEY, correlationId);
|
|
438
|
+
response.setHeader(CORRELATION_HEADER, correlationId);
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
chain.doFilter(request, response);
|
|
442
|
+
} finally {
|
|
443
|
+
MDC.remove(MDC_KEY);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Jackson Configuration
|
|
452
|
+
|
|
453
|
+
```java
|
|
454
|
+
package {{BASE_PACKAGE}}.config;
|
|
455
|
+
|
|
456
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
457
|
+
import com.fasterxml.jackson.databind.SerializationFeature;
|
|
458
|
+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
|
459
|
+
import org.springframework.context.annotation.Bean;
|
|
460
|
+
import org.springframework.context.annotation.Configuration;
|
|
461
|
+
|
|
462
|
+
@Configuration
|
|
463
|
+
public class JacksonConfig {
|
|
464
|
+
|
|
465
|
+
@Bean
|
|
466
|
+
public ObjectMapper objectMapper() {
|
|
467
|
+
ObjectMapper mapper = new ObjectMapper();
|
|
468
|
+
mapper.registerModule(new JavaTimeModule());
|
|
469
|
+
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
|
470
|
+
mapper.setDefaultPropertyInclusion(
|
|
471
|
+
com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL);
|
|
472
|
+
return mapper;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## CORS Configuration
|
|
480
|
+
|
|
481
|
+
```java
|
|
482
|
+
package {{BASE_PACKAGE}}.config;
|
|
483
|
+
|
|
484
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
485
|
+
import org.springframework.context.annotation.Bean;
|
|
486
|
+
import org.springframework.context.annotation.Configuration;
|
|
487
|
+
import org.springframework.web.cors.CorsConfiguration;
|
|
488
|
+
import org.springframework.web.cors.CorsConfigurationSource;
|
|
489
|
+
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
490
|
+
import java.util.List;
|
|
491
|
+
|
|
492
|
+
@Configuration
|
|
493
|
+
public class CorsConfig {
|
|
494
|
+
|
|
495
|
+
@Value("${app.cors.allowed-origins:http://localhost:3000}")
|
|
496
|
+
private List<String> allowedOrigins;
|
|
497
|
+
|
|
498
|
+
@Bean
|
|
499
|
+
public CorsConfigurationSource corsConfigurationSource() {
|
|
500
|
+
CorsConfiguration config = new CorsConfiguration();
|
|
501
|
+
config.setAllowedOrigins(allowedOrigins);
|
|
502
|
+
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
|
503
|
+
config.setAllowedHeaders(List.of("*"));
|
|
504
|
+
config.setAllowCredentials(true);
|
|
505
|
+
config.setMaxAge(3600L);
|
|
506
|
+
|
|
507
|
+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
508
|
+
source.registerCorsConfiguration("/api/**", config);
|
|
509
|
+
return source;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## OpenAPI / Springdoc Configuration
|
|
517
|
+
|
|
518
|
+
### Maven Dependency
|
|
519
|
+
|
|
520
|
+
```xml
|
|
521
|
+
<dependency>
|
|
522
|
+
<groupId>org.springdoc</groupId>
|
|
523
|
+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
|
524
|
+
<version>2.8.0</version>
|
|
525
|
+
</dependency>
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Application Configuration
|
|
529
|
+
|
|
530
|
+
```yaml
|
|
531
|
+
springdoc:
|
|
532
|
+
api-docs:
|
|
533
|
+
path: /api-docs
|
|
534
|
+
swagger-ui:
|
|
535
|
+
path: /swagger-ui.html
|
|
536
|
+
operations-sorter: method
|
|
537
|
+
tags-sorter: alpha
|
|
538
|
+
default-produces-media-type: application/json
|
|
539
|
+
default-consumes-media-type: application/json
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### OpenAPI Global Config
|
|
543
|
+
|
|
544
|
+
```java
|
|
545
|
+
package {{BASE_PACKAGE}}.config;
|
|
546
|
+
|
|
547
|
+
import io.swagger.v3.oas.models.OpenAPI;
|
|
548
|
+
import io.swagger.v3.oas.models.info.Info;
|
|
549
|
+
import io.swagger.v3.oas.models.info.Contact;
|
|
550
|
+
import io.swagger.v3.oas.models.servers.Server;
|
|
551
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
552
|
+
import org.springframework.context.annotation.Bean;
|
|
553
|
+
import org.springframework.context.annotation.Configuration;
|
|
554
|
+
import java.util.List;
|
|
555
|
+
|
|
556
|
+
@Configuration
|
|
557
|
+
public class OpenApiConfig {
|
|
558
|
+
|
|
559
|
+
@Value("${spring.application.name}")
|
|
560
|
+
private String applicationName;
|
|
561
|
+
|
|
562
|
+
@Bean
|
|
563
|
+
public OpenAPI customOpenAPI() {
|
|
564
|
+
return new OpenAPI()
|
|
565
|
+
.info(new Info()
|
|
566
|
+
.title(applicationName + " API")
|
|
567
|
+
.version("v1")
|
|
568
|
+
.description("REST API for " + applicationName)
|
|
569
|
+
.contact(new Contact()
|
|
570
|
+
.name("Bestinet")
|
|
571
|
+
.email("support@bestinet.com")))
|
|
572
|
+
.servers(List.of(
|
|
573
|
+
new Server().url("http://localhost:8080").description("Local development")
|
|
574
|
+
));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Controller Annotations
|
|
580
|
+
|
|
581
|
+
```java
|
|
582
|
+
@RestController
|
|
583
|
+
@RequestMapping("/api/v1/job-demands")
|
|
584
|
+
@Tag(name = "Job Demands", description = "Job demand management operations")
|
|
585
|
+
class JobDemandController {
|
|
586
|
+
|
|
587
|
+
@Operation(
|
|
588
|
+
summary = "Search job demands",
|
|
589
|
+
description = "Search job demands with pagination and optional filters",
|
|
590
|
+
responses = {
|
|
591
|
+
@ApiResponse(responseCode = "200", description = "Job demands retrieved"),
|
|
592
|
+
@ApiResponse(responseCode = "400", description = "Invalid search parameters")
|
|
593
|
+
}
|
|
594
|
+
)
|
|
595
|
+
@GetMapping
|
|
596
|
+
public ResponseEntity<Page<JobDemandDTO>> search(
|
|
597
|
+
@Parameter(description = "Country ISO3 code") @RequestParam(required = false) String country,
|
|
598
|
+
@Parameter(description = "Status filter") @RequestParam(required = false) String status,
|
|
599
|
+
@PageableDefault(size = 20) Pageable pageable) {
|
|
600
|
+
// ...
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Service Interface Pattern
|
|
608
|
+
|
|
609
|
+
```java
|
|
610
|
+
package {{BASE_PACKAGE}}.{{module}};
|
|
611
|
+
|
|
612
|
+
import org.springframework.data.domain.Page;
|
|
613
|
+
import org.springframework.data.domain.Pageable;
|
|
614
|
+
|
|
615
|
+
public interface {{ModuleName}}Service {
|
|
616
|
+
|
|
617
|
+
Page<{{ModuleName}}DTO> findAll(String search, Pageable pageable);
|
|
618
|
+
|
|
619
|
+
{{ModuleName}}DTO findById(String id);
|
|
620
|
+
|
|
621
|
+
{{ModuleName}}DTO create(Create{{ModuleName}}Request request);
|
|
622
|
+
|
|
623
|
+
{{ModuleName}}DTO update(String id, Update{{ModuleName}}Request request);
|
|
624
|
+
|
|
625
|
+
void delete(String id);
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Service Implementation Pattern
|
|
632
|
+
|
|
633
|
+
```java
|
|
634
|
+
package {{BASE_PACKAGE}}.{{module}}.internal;
|
|
635
|
+
|
|
636
|
+
import {{BASE_PACKAGE}}.{{module}}.*;
|
|
637
|
+
import {{BASE_PACKAGE}}.shared.exception.ResourceNotFoundException;
|
|
638
|
+
import lombok.RequiredArgsConstructor;
|
|
639
|
+
import lombok.extern.slf4j.Slf4j;
|
|
640
|
+
import org.springframework.data.domain.Page;
|
|
641
|
+
import org.springframework.data.domain.Pageable;
|
|
642
|
+
import org.springframework.stereotype.Service;
|
|
643
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
644
|
+
|
|
645
|
+
@Service
|
|
646
|
+
@RequiredArgsConstructor
|
|
647
|
+
@Slf4j
|
|
648
|
+
@Transactional(readOnly = true)
|
|
649
|
+
class {{ModuleName}}ServiceImpl implements {{ModuleName}}Service {
|
|
650
|
+
|
|
651
|
+
private final {{ModuleName}}Repository repository;
|
|
652
|
+
private final {{ModuleName}}Mapper mapper;
|
|
653
|
+
|
|
654
|
+
@Override
|
|
655
|
+
public Page<{{ModuleName}}DTO> findAll(String search, Pageable pageable) {
|
|
656
|
+
if (search != null && !search.isBlank()) {
|
|
657
|
+
return repository.findByNameContainingIgnoreCase(search, pageable)
|
|
658
|
+
.map(mapper::toDTO);
|
|
659
|
+
}
|
|
660
|
+
return repository.findAll(pageable).map(mapper::toDTO);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
@Override
|
|
664
|
+
public {{ModuleName}}DTO findById(String id) {
|
|
665
|
+
return repository.findById(id)
|
|
666
|
+
.map(mapper::toDTO)
|
|
667
|
+
.orElseThrow(() -> new ResourceNotFoundException("{{ModuleName}}", "id", id));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@Override
|
|
671
|
+
@Transactional
|
|
672
|
+
public {{ModuleName}}DTO create(Create{{ModuleName}}Request request) {
|
|
673
|
+
var entity = mapper.toEntity(request);
|
|
674
|
+
var saved = repository.save(entity);
|
|
675
|
+
log.info("Created {{ModuleName}}: {}", saved.getId());
|
|
676
|
+
return mapper.toDTO(saved);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
@Override
|
|
680
|
+
@Transactional
|
|
681
|
+
public {{ModuleName}}DTO update(String id, Update{{ModuleName}}Request request) {
|
|
682
|
+
var entity = repository.findById(id)
|
|
683
|
+
.orElseThrow(() -> new ResourceNotFoundException("{{ModuleName}}", "id", id));
|
|
684
|
+
mapper.updateEntity(request, entity);
|
|
685
|
+
var saved = repository.save(entity);
|
|
686
|
+
log.info("Updated {{ModuleName}}: {}", saved.getId());
|
|
687
|
+
return mapper.toDTO(saved);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
@Override
|
|
691
|
+
@Transactional
|
|
692
|
+
public void delete(String id) {
|
|
693
|
+
if (!repository.existsById(id)) {
|
|
694
|
+
throw new ResourceNotFoundException("{{ModuleName}}", "id", id);
|
|
695
|
+
}
|
|
696
|
+
repository.deleteById(id);
|
|
697
|
+
log.info("Deleted {{ModuleName}}: {}", id);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## MapStruct Mapper Pattern
|
|
705
|
+
|
|
706
|
+
```java
|
|
707
|
+
package {{BASE_PACKAGE}}.{{module}}.internal;
|
|
708
|
+
|
|
709
|
+
import {{BASE_PACKAGE}}.{{module}}.*;
|
|
710
|
+
import org.mapstruct.Mapper;
|
|
711
|
+
import org.mapstruct.MappingTarget;
|
|
712
|
+
|
|
713
|
+
@Mapper(componentModel = "spring")
|
|
714
|
+
interface {{ModuleName}}Mapper {
|
|
715
|
+
|
|
716
|
+
{{ModuleName}}DTO toDTO({{ModuleName}}Entity entity);
|
|
717
|
+
|
|
718
|
+
{{ModuleName}}Entity toEntity(Create{{ModuleName}}Request request);
|
|
719
|
+
|
|
720
|
+
void updateEntity(Update{{ModuleName}}Request request, @MappingTarget {{ModuleName}}Entity entity);
|
|
721
|
+
}
|
|
722
|
+
```
|