evnict-kit 0.2.1
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/README.md +19 -0
- package/bin/cli.js +38 -0
- package/package.json +48 -0
- package/src/commands/add.js +129 -0
- package/src/commands/init-check.js +19 -0
- package/src/commands/init-context.js +37 -0
- package/src/commands/init-rules.js +42 -0
- package/src/commands/init-workflow.js +36 -0
- package/src/commands/init.js +722 -0
- package/src/utils/config.js +167 -0
- package/src/utils/file.js +53 -0
- package/templates/GETTING-STARTED.md +196 -0
- package/templates/content/context/AGENTS.md.template +462 -0
- package/templates/content/rules/01-evnict-kit-general-rules.md +303 -0
- package/templates/content/rules/02-evnict-kit-security-rules.md +423 -0
- package/templates/content/rules/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/content/rules/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/content/rules/05-evnict-kit-project-conventions.md +228 -0
- package/templates/content/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/content/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/content/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/content/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/content/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/content/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/content/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/content/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/content/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/content/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/content/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/content/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/content/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/content/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/content/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/content/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/content/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/content/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/content/workflows/evnict-kit-archive-wiki.md +100 -0
- package/templates/content/workflows/evnict-kit-attt.md +100 -0
- package/templates/content/workflows/evnict-kit-bug-fix.md +107 -0
- package/templates/content/workflows/evnict-kit-feature-large.md +393 -0
- package/templates/content/workflows/evnict-kit-feature-small.md +86 -0
- package/templates/content/workflows/evnict-kit-handoff.md +243 -0
- package/templates/content/workflows/evnict-kit-implement.md +247 -0
- package/templates/content/workflows/evnict-kit-init-check.md +76 -0
- package/templates/content/workflows/evnict-kit-init-context.md +58 -0
- package/templates/content/workflows/evnict-kit-init-rules.md +114 -0
- package/templates/content/workflows/evnict-kit-init-wiki.md +80 -0
- package/templates/content/workflows/evnict-kit-plan.md +308 -0
- package/templates/content/workflows/evnict-kit-review.md +53 -0
- package/templates/content/workflows/evnict-kit-spec-archive.md +53 -0
- package/templates/content/workflows/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/content/workflows/evnict-kit-wiki-query.md +91 -0
- package/templates/content/workflows/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/context/AGENT.md.template +9 -0
- package/templates/context/AGENTS.md.template +462 -0
- package/templates/context/CLAUDE.md.template +301 -0
- package/templates/context/copilot-instructions.md.template +60 -0
- package/templates/context/cursorrules.template +114 -0
- package/templates/instruct/Instruct-Agent-AI.be.md +96 -0
- package/templates/instruct/Instruct-Agent-AI.fe.md +79 -0
- package/templates/rules/antigravity/01-evnict-kit-general-rules.md +303 -0
- package/templates/rules/antigravity/02-evnict-kit-security-rules.md +423 -0
- package/templates/rules/antigravity/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/rules/antigravity/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/rules/antigravity/05-evnict-kit-project-conventions.md +228 -0
- package/templates/rules/claude/README.md +8 -0
- package/templates/rules/cursor/01-evnict-kit-general-rules.mdc +46 -0
- package/templates/rules/cursor/02-evnict-kit-security-rules.mdc +46 -0
- package/templates/rules/cursor/03-evnict-kit-backend-conventions.mdc +50 -0
- package/templates/rules/cursor/04-evnict-kit-frontend-conventions.mdc +43 -0
- package/templates/rules/cursor/05-evnict-kit-project-conventions.mdc +63 -0
- package/templates/rules/cursor/README.md +7 -0
- package/templates/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/wiki/README.md +35 -0
- package/templates/wiki/config.example.yaml +17 -0
- package/templates/wiki/package.json +17 -0
- package/templates/wiki/raw/notes/.gitkeep +1 -0
- package/templates/wiki/scripts/ingest.js +66 -0
- package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-attt.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-bug-fix.md +107 -0
- package/templates/workflows/antigravity/evnict-kit-feature-large.md +393 -0
- package/templates/workflows/antigravity/evnict-kit-feature-small.md +86 -0
- package/templates/workflows/antigravity/evnict-kit-handoff.md +243 -0
- package/templates/workflows/antigravity/evnict-kit-implement.md +247 -0
- package/templates/workflows/antigravity/evnict-kit-init-check.md +76 -0
- package/templates/workflows/antigravity/evnict-kit-init-context.md +58 -0
- package/templates/workflows/antigravity/evnict-kit-init-rules.md +114 -0
- package/templates/workflows/antigravity/evnict-kit-init-wiki.md +80 -0
- package/templates/workflows/antigravity/evnict-kit-plan.md +308 -0
- package/templates/workflows/antigravity/evnict-kit-review.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-spec-archive.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-query.md +91 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/workflows/claude/README.md +6 -0
- package/templates/workflows/claude/evnict-kit-archive-wiki.md +98 -0
- package/templates/workflows/claude/evnict-kit-attt.md +98 -0
- package/templates/workflows/claude/evnict-kit-bug-fix.md +105 -0
- package/templates/workflows/claude/evnict-kit-feature-large.md +391 -0
- package/templates/workflows/claude/evnict-kit-feature-small.md +84 -0
- package/templates/workflows/claude/evnict-kit-handoff.md +240 -0
- package/templates/workflows/claude/evnict-kit-implement.md +245 -0
- package/templates/workflows/claude/evnict-kit-init-check.md +74 -0
- package/templates/workflows/claude/evnict-kit-init-context.md +56 -0
- package/templates/workflows/claude/evnict-kit-init-rules.md +112 -0
- package/templates/workflows/claude/evnict-kit-init-wiki.md +78 -0
- package/templates/workflows/claude/evnict-kit-plan.md +305 -0
- package/templates/workflows/claude/evnict-kit-review.md +51 -0
- package/templates/workflows/claude/evnict-kit-spec-archive.md +51 -0
- package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +162 -0
- package/templates/workflows/claude/evnict-kit-wiki-query.md +89 -0
- package/templates/workflows/claude/evnict-kit-wiki-scan-project.md +270 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
---
|
|
4
|
+
# Backend Conventions — EVNICT Standard
|
|
5
|
+
**Activation Mode: Always On**
|
|
6
|
+
**Source: QĐ-TTPM Điều 8, Mục 8.6 — Ràng buộc kỹ thuật**
|
|
7
|
+
**Tech: Java Spring Boot + JOOQ + Oracle**
|
|
8
|
+
|
|
9
|
+
> Áp dụng cho TẤT CẢ code backend. Mọi code mới hoặc sửa đổi PHẢI tuân thủ.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. INPUT VALIDATION (RB01)
|
|
14
|
+
|
|
15
|
+
### Quy tắc
|
|
16
|
+
- MỌI input từ client PHẢI được validate ở Controller layer
|
|
17
|
+
- Dùng Bean Validation annotations (@NotNull, @NotBlank, @Size, @Pattern)
|
|
18
|
+
- LUÔN dùng @Valid hoặc @Validated trên request body
|
|
19
|
+
- Custom validator cho business rules phức tạp
|
|
20
|
+
|
|
21
|
+
### Code examples
|
|
22
|
+
```java
|
|
23
|
+
// ❌ SAI — Không validate input
|
|
24
|
+
@PostMapping("/customers")
|
|
25
|
+
public ResponseEntity<?> create(@RequestBody CustomerDTO dto) {
|
|
26
|
+
return ResponseEntity.ok(service.create(dto));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ✅ ĐÚNG — Validation annotations + @Valid
|
|
30
|
+
public class CustomerDTO {
|
|
31
|
+
@NotBlank(message = "Tên không được trống")
|
|
32
|
+
@Size(max = 200, message = "Tên không quá 200 ký tự")
|
|
33
|
+
private String name;
|
|
34
|
+
|
|
35
|
+
@Pattern(regexp = "^0[0-9]{9}$", message = "Số điện thoại không hợp lệ")
|
|
36
|
+
private String phone;
|
|
37
|
+
|
|
38
|
+
@NotNull(message = "Mã đơn vị bắt buộc")
|
|
39
|
+
private Long donViId;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@PostMapping("/customers")
|
|
43
|
+
public ResponseEntity<ResponseData> create(@Valid @RequestBody CustomerDTO dto) {
|
|
44
|
+
return ResponseEntity.ok(ResponseData.ok(service.create(dto)));
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Validation cho search/list parameters
|
|
49
|
+
```java
|
|
50
|
+
// ✅ ĐÚNG — Validate sort column bằng whitelist
|
|
51
|
+
private static final Set<String> ALLOWED_SORT = Set.of("name", "createdDate", "status");
|
|
52
|
+
|
|
53
|
+
@GetMapping("/customers")
|
|
54
|
+
public ResponseEntity<ResponseData> search(
|
|
55
|
+
@RequestParam(defaultValue = "") String keyword,
|
|
56
|
+
@RequestParam(defaultValue = "0") @Min(0) int page,
|
|
57
|
+
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size,
|
|
58
|
+
@RequestParam(defaultValue = "name") String sortBy) {
|
|
59
|
+
if (!ALLOWED_SORT.contains(sortBy)) sortBy = "name";
|
|
60
|
+
return ResponseEntity.ok(ResponseData.ok(service.search(keyword, page, size, sortBy)));
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 2. SERVICE LAYER PATTERN (RB02)
|
|
67
|
+
|
|
68
|
+
### Cấu trúc bắt buộc
|
|
69
|
+
```
|
|
70
|
+
Controller → Service → Repository → Database
|
|
71
|
+
↓ ↓ ↓
|
|
72
|
+
Validate Business Data Access
|
|
73
|
+
+ Auth Logic (JOOQ/JPA)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Quy tắc
|
|
77
|
+
- Controller: validate input, call service, return ResponseData
|
|
78
|
+
- Service: business logic, transaction management, orchestration
|
|
79
|
+
- Repository: data access ONLY — KHÔNG chứa business logic
|
|
80
|
+
- DTO: data transfer giữa layers — KHÔNG dùng Entity trực tiếp
|
|
81
|
+
|
|
82
|
+
### Code examples
|
|
83
|
+
```java
|
|
84
|
+
// ❌ SAI — Business logic trong Controller
|
|
85
|
+
@PostMapping("/orders")
|
|
86
|
+
public ResponseEntity<?> createOrder(@RequestBody OrderDTO dto) {
|
|
87
|
+
// Business logic KHÔNG nên ở đây
|
|
88
|
+
if (dto.getTotal() > 1000000) {
|
|
89
|
+
dto.setNeedApproval(true);
|
|
90
|
+
}
|
|
91
|
+
Order order = orderRepository.save(mapToEntity(dto));
|
|
92
|
+
return ResponseEntity.ok(order);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ✅ ĐÚNG — Controller gọi Service
|
|
96
|
+
@PostMapping("/orders")
|
|
97
|
+
public ResponseEntity<ResponseData> createOrder(@Valid @RequestBody OrderDTO dto) {
|
|
98
|
+
OrderDTO result = orderService.create(dto);
|
|
99
|
+
return ResponseEntity.ok(ResponseData.ok(result));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ✅ Service xử lý business logic
|
|
103
|
+
@Service
|
|
104
|
+
@Transactional
|
|
105
|
+
public class OrderService {
|
|
106
|
+
public OrderDTO create(OrderDTO dto) {
|
|
107
|
+
// Business rule: đơn hàng > 1tr cần duyệt
|
|
108
|
+
if (dto.getTotal() > 1000000) {
|
|
109
|
+
dto.setNeedApproval(true);
|
|
110
|
+
}
|
|
111
|
+
// Repository chỉ lưu data
|
|
112
|
+
return orderRepository.create(dto);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### ResponseData pattern (EVNICT standard)
|
|
118
|
+
```java
|
|
119
|
+
// ✅ Mọi API response PHẢI dùng ResponseData wrapper
|
|
120
|
+
public class ResponseData {
|
|
121
|
+
private int status; // 0 = success, 1 = error
|
|
122
|
+
private String message;
|
|
123
|
+
private Object data;
|
|
124
|
+
|
|
125
|
+
public static ResponseData ok(Object data) {
|
|
126
|
+
return new ResponseData(0, "Thành công", data);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public static ResponseData error(String message) {
|
|
130
|
+
return new ResponseData(1, message, null);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 3. ERROR HANDLING (RB03)
|
|
138
|
+
|
|
139
|
+
### Cấu trúc GlobalExceptionHandler
|
|
140
|
+
```java
|
|
141
|
+
@RestControllerAdvice
|
|
142
|
+
public class GlobalExceptionHandler {
|
|
143
|
+
|
|
144
|
+
// Business exceptions — return message cho user
|
|
145
|
+
@ExceptionHandler(BusinessException.class)
|
|
146
|
+
public ResponseEntity<ResponseData> handleBusiness(BusinessException ex) {
|
|
147
|
+
log.warn("Business error: {}", ex.getMessage());
|
|
148
|
+
return ResponseEntity.ok(ResponseData.error(ex.getMessage()));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validation errors — return danh sách lỗi
|
|
152
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
153
|
+
public ResponseEntity<ResponseData> handleValidation(MethodArgumentNotValidException ex) {
|
|
154
|
+
List<String> errors = ex.getBindingResult().getFieldErrors().stream()
|
|
155
|
+
.map(e -> e.getField() + ": " + e.getDefaultMessage())
|
|
156
|
+
.toList();
|
|
157
|
+
log.warn("Validation errors: {}", errors);
|
|
158
|
+
return ResponseEntity.badRequest()
|
|
159
|
+
.body(ResponseData.error(String.join(", ", errors)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Unexpected errors — log full, return generic message
|
|
163
|
+
@ExceptionHandler(Exception.class)
|
|
164
|
+
public ResponseEntity<ResponseData> handleGeneral(Exception ex) {
|
|
165
|
+
log.error("Unexpected error", ex);
|
|
166
|
+
return ResponseEntity.status(500)
|
|
167
|
+
.body(ResponseData.error("Có lỗi xảy ra trong hệ thống"));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### ZERO TOLERANCE — KHÔNG expose stack trace
|
|
173
|
+
```java
|
|
174
|
+
// ❌ SAI
|
|
175
|
+
return ResponseEntity.status(500).body(ex.toString());
|
|
176
|
+
return ResponseEntity.status(500).body(ex.getStackTrace());
|
|
177
|
+
return ResponseEntity.status(500).body(ex.getMessage()); // Risky cho unexpected
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 4. DATABASE MIGRATION (RB04)
|
|
183
|
+
|
|
184
|
+
### Quy tắc
|
|
185
|
+
- Schema changes PHẢI qua migration scripts — KHÔNG sửa DB trực tiếp
|
|
186
|
+
- Naming: `V{YYYYMMDD}_{seq}__{description}.sql`
|
|
187
|
+
- Mỗi migration PHẢI có rollback script
|
|
188
|
+
- Test cả UP và DOWN trước khi commit
|
|
189
|
+
|
|
190
|
+
### Oracle-specific patterns
|
|
191
|
+
```sql
|
|
192
|
+
-- UP: V20260401_001__add_customer_status.sql
|
|
193
|
+
ALTER TABLE CUSTOMER ADD (STATUS VARCHAR2(20) DEFAULT 'ACTIVE' NOT NULL);
|
|
194
|
+
CREATE INDEX IX_CUSTOMER_STATUS ON CUSTOMER(STATUS);
|
|
195
|
+
COMMENT ON COLUMN CUSTOMER.STATUS IS 'Trạng thái: ACTIVE, INACTIVE, SUSPENDED';
|
|
196
|
+
|
|
197
|
+
-- DOWN: V20260401_001__add_customer_status_ROLLBACK.sql
|
|
198
|
+
DROP INDEX IX_CUSTOMER_STATUS;
|
|
199
|
+
ALTER TABLE CUSTOMER DROP COLUMN STATUS;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### SQL Server patterns
|
|
203
|
+
```sql
|
|
204
|
+
-- UP
|
|
205
|
+
ALTER TABLE Customer ADD Status NVARCHAR(20) NOT NULL DEFAULT 'ACTIVE';
|
|
206
|
+
CREATE INDEX IX_Customer_Status ON Customer(Status);
|
|
207
|
+
|
|
208
|
+
-- DOWN
|
|
209
|
+
DROP INDEX IX_Customer_Status ON Customer;
|
|
210
|
+
ALTER TABLE Customer DROP COLUMN Status;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Scan commands
|
|
214
|
+
```bash
|
|
215
|
+
# Tìm raw DDL trong Java code (KHÔNG nên có)
|
|
216
|
+
grep -rn "CREATE TABLE\|ALTER TABLE\|DROP TABLE" --include="*.java" src/
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 5. AUTHENTICATION ON EVERY ROUTE (RB05)
|
|
222
|
+
|
|
223
|
+
### Quy tắc
|
|
224
|
+
- MỌI endpoint mới PHẢI được bảo vệ (default deny)
|
|
225
|
+
- Public endpoints PHẢI được whitelist rõ ràng
|
|
226
|
+
- Data-level authorization khi cần (chỉ xem data đơn vị mình)
|
|
227
|
+
|
|
228
|
+
### Spring Security configuration
|
|
229
|
+
```java
|
|
230
|
+
@Configuration
|
|
231
|
+
public class SecurityConfig {
|
|
232
|
+
@Bean
|
|
233
|
+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
234
|
+
http.authorizeHttpRequests(auth -> auth
|
|
235
|
+
// Public endpoints — whitelist RÕ RÀNG
|
|
236
|
+
.requestMatchers("/api/auth/login", "/api/auth/refresh").permitAll()
|
|
237
|
+
.requestMatchers("/api/public/**").permitAll()
|
|
238
|
+
.requestMatchers("/actuator/health").permitAll()
|
|
239
|
+
// Mọi route khác — PHẢI authenticated
|
|
240
|
+
.anyRequest().authenticated()
|
|
241
|
+
);
|
|
242
|
+
return http.build();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Method-level security
|
|
248
|
+
```java
|
|
249
|
+
// ✅ ĐÚNG — Role-based access
|
|
250
|
+
@PreAuthorize("hasRole('ADMIN')")
|
|
251
|
+
@DeleteMapping("/users/{id}")
|
|
252
|
+
public ResponseEntity<ResponseData> deleteUser(@PathVariable Long id) { ... }
|
|
253
|
+
|
|
254
|
+
// ✅ ĐÚNG — Data-level authorization
|
|
255
|
+
@GetMapping("/don-vi/{id}/customers")
|
|
256
|
+
public ResponseEntity<ResponseData> getCustomers(@PathVariable Long donViId) {
|
|
257
|
+
// Kiểm tra user có quyền xem data đơn vị này
|
|
258
|
+
Long userDonViId = SecurityUtils.getCurrentUserDonViId();
|
|
259
|
+
if (!donViId.equals(userDonViId) && !SecurityUtils.isAdmin()) {
|
|
260
|
+
throw new BusinessException("Không có quyền truy cập dữ liệu đơn vị này");
|
|
261
|
+
}
|
|
262
|
+
return ResponseEntity.ok(ResponseData.ok(service.getByDonVi(donViId)));
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 6. RATE LIMITING (RB06)
|
|
269
|
+
|
|
270
|
+
### Quy tắc
|
|
271
|
+
- API public (login, register) → rate limit BẮT BUỘC
|
|
272
|
+
- API nội bộ → rate limit khuyến nghị
|
|
273
|
+
- File upload → rate limit + size limit
|
|
274
|
+
|
|
275
|
+
### Spring Boot implementation
|
|
276
|
+
```java
|
|
277
|
+
// ✅ Sử dụng Bucket4j hoặc Resilience4j
|
|
278
|
+
@RateLimiter(name = "loginApi", fallbackMethod = "rateLimitFallback")
|
|
279
|
+
@PostMapping("/api/auth/login")
|
|
280
|
+
public ResponseEntity<ResponseData> login(@Valid @RequestBody LoginDTO dto) {
|
|
281
|
+
return ResponseEntity.ok(authService.login(dto));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public ResponseEntity<ResponseData> rateLimitFallback(LoginDTO dto, Exception ex) {
|
|
285
|
+
return ResponseEntity.status(429)
|
|
286
|
+
.body(ResponseData.error("Quá nhiều request, vui lòng thử lại sau"));
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 7. JOOQ CONVENTIONS (EVNICT-specific)
|
|
293
|
+
|
|
294
|
+
### Record mapping
|
|
295
|
+
```java
|
|
296
|
+
// ❌ SAI — Manual mapping tất cả fields
|
|
297
|
+
CustomerDTO dto = new CustomerDTO();
|
|
298
|
+
dto.setId(record.get(CUSTOMER.ID));
|
|
299
|
+
dto.setName(record.get(CUSTOMER.NAME));
|
|
300
|
+
// ... 20 dòng mapping
|
|
301
|
+
|
|
302
|
+
// ✅ ĐÚNG — Dùng into() hoặc fetchInto()
|
|
303
|
+
List<CustomerDTO> results = dsl.selectFrom(CUSTOMER)
|
|
304
|
+
.where(CUSTOMER.STATUS.eq("ACTIVE"))
|
|
305
|
+
.fetchInto(CustomerDTO.class);
|
|
306
|
+
|
|
307
|
+
// ✅ ĐÚNG — Custom mapping khi cần join
|
|
308
|
+
List<CustomerDetailDTO> results = dsl
|
|
309
|
+
.select(CUSTOMER.fields())
|
|
310
|
+
.select(DON_VI.TEN_DON_VI)
|
|
311
|
+
.from(CUSTOMER)
|
|
312
|
+
.join(DON_VI).on(CUSTOMER.DON_VI_ID.eq(DON_VI.ID))
|
|
313
|
+
.fetchInto(CustomerDetailDTO.class);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Pagination pattern
|
|
317
|
+
```java
|
|
318
|
+
// ✅ Oracle + JOOQ pagination
|
|
319
|
+
public Page<CustomerDTO> search(String keyword, int page, int size) {
|
|
320
|
+
Condition condition = DSL.trueCondition();
|
|
321
|
+
if (StringUtils.isNotBlank(keyword)) {
|
|
322
|
+
condition = condition.and(CUSTOMER.NAME.containsIgnoreCase(keyword));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
int total = dsl.fetchCount(CUSTOMER, condition);
|
|
326
|
+
List<CustomerDTO> data = dsl.selectFrom(CUSTOMER)
|
|
327
|
+
.where(condition)
|
|
328
|
+
.orderBy(CUSTOMER.CREATED_DATE.desc())
|
|
329
|
+
.offset(page * size)
|
|
330
|
+
.limit(size)
|
|
331
|
+
.fetchInto(CustomerDTO.class);
|
|
332
|
+
|
|
333
|
+
return new Page<>(data, total, page, size);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Transaction management
|
|
338
|
+
```java
|
|
339
|
+
// ✅ ĐÚNG — @Transactional ở Service layer
|
|
340
|
+
@Service
|
|
341
|
+
public class OrderService {
|
|
342
|
+
@Transactional // Tại method level
|
|
343
|
+
public OrderDTO create(OrderDTO dto) {
|
|
344
|
+
// Nhiều repository calls trong 1 transaction
|
|
345
|
+
Order order = orderRepo.create(dto);
|
|
346
|
+
orderDetailRepo.createBatch(dto.getDetails(), order.getId());
|
|
347
|
+
inventoryRepo.deduct(dto.getDetails());
|
|
348
|
+
return mapToDTO(order);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 8. NAMING CONVENTIONS
|
|
356
|
+
|
|
357
|
+
### Java
|
|
358
|
+
| Loại | Convention | Ví dụ |
|
|
359
|
+
|------|-----------|-------|
|
|
360
|
+
| Class | PascalCase | `CustomerService`, `OrderDTO` |
|
|
361
|
+
| Method | camelCase | `findByStatus()`, `createOrder()` |
|
|
362
|
+
| Variable | camelCase | `customerId`, `orderTotal` |
|
|
363
|
+
| Constant | UPPER_SNAKE | `MAX_FILE_SIZE`, `DEFAULT_PAGE_SIZE` |
|
|
364
|
+
| Package | lowercase | `com.evn.cmis.customer` |
|
|
365
|
+
| Table | UPPER_SNAKE | `CUSTOMER`, `DON_VI` |
|
|
366
|
+
| Column | UPPER_SNAKE | `CREATED_DATE`, `DON_VI_ID` |
|
|
367
|
+
|
|
368
|
+
### File naming
|
|
369
|
+
| Loại | Pattern | Ví dụ |
|
|
370
|
+
|------|---------|-------|
|
|
371
|
+
| Controller | `{Module}Controller.java` | `CustomerController.java` |
|
|
372
|
+
| Service | `{Module}Service.java` | `CustomerService.java` |
|
|
373
|
+
| Repository | `{Module}Repository.java` | `CustomerRepository.java` |
|
|
374
|
+
| DTO | `{Module}DTO.java` | `CustomerDTO.java` |
|
|
375
|
+
| Migration | `V{date}_{seq}__{desc}.sql` | `V20260401_001__add_status.sql` |
|
|
376
|
+
|
|
377
|
+
### Scan commands
|
|
378
|
+
```bash
|
|
379
|
+
# Tìm class names không đúng convention
|
|
380
|
+
grep -rn "class [a-z]" --include="*.java" src/
|
|
381
|
+
# Tìm constants không UPPER_SNAKE
|
|
382
|
+
grep -rn "static final .* [a-z].*=" --include="*.java" src/
|
|
383
|
+
```
|