diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/controller/FunctionController.java b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/FunctionController.java new file mode 100644 index 0000000..babe767 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/FunctionController.java @@ -0,0 +1,74 @@ +package com.vega.hrm.controller; + +import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.request.function.CreateFunctionRequest; +import com.vega.hrm.request.function.UpdateFunctionRequest; +import com.vega.hrm.response.FunctionDto; +import com.vega.hrm.service.FunctionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/auth/function") +@RequiredArgsConstructor +@Tag(name = "Auth - Function/Menu", description = "Quản lý menu và chức năng hệ thống") +public class FunctionController { + private final FunctionService functionService; + + @PostMapping("/create") + @Operation(summary = "Tạo menu mới", description = "Tạo menu/chức năng mới trong hệ thống.") + public ResponseEntity> createFunction( + @Valid @RequestBody CreateFunctionRequest request, + @Parameter(description = "Người tạo (từ token)") @RequestParam(defaultValue = "admin") String currentUser + ) { + return ResponseEntity.ok(functionService.createFunction(request, currentUser)); + } + + @PutMapping("/update") + @Operation(summary = "Cập nhật menu", description = "Cập nhật thông tin menu/chức năng.") + public ResponseEntity> updateFunction(@Valid @RequestBody UpdateFunctionRequest request) { + return ResponseEntity.ok(functionService.updateFunction(request)); + } + + @DeleteMapping("/delete") + @Operation(summary = "Xóa menu", description = "Xóa menu/chức năng (không thể xóa nếu có menu con).") + public ResponseEntity> deleteFunction( + @Parameter(description = "ID của menu") @RequestParam String functionId + ) { + return ResponseEntity.ok(functionService.deleteFunction(functionId)); + } + + @GetMapping("/list") + @Operation(summary = "Lấy danh sách menu", description = "Lấy tất cả menu dạng flat list.") + public ResponseEntity>> getAllFunctions() { + return ResponseEntity.ok(functionService.getAllFunctions()); + } + + @GetMapping("/tree") + @Operation(summary = "Lấy cây menu", description = "Lấy menu dạng cây phân cấp (parent-children).") + public ResponseEntity>> getMenuTree() { + return ResponseEntity.ok(functionService.getMenuTree()); + } + + @GetMapping("/detail") + @Operation(summary = "Lấy chi tiết menu", description = "Lấy thông tin chi tiết của menu/chức năng.") + public ResponseEntity> getFunctionDetail( + @Parameter(description = "ID của menu") @RequestParam String functionId + ) { + return ResponseEntity.ok(functionService.getFunctionDetail(functionId)); + } +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/controller/RoleController.java b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/RoleController.java new file mode 100644 index 0000000..6223313 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/RoleController.java @@ -0,0 +1,78 @@ +package com.vega.hrm.controller; + +import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.request.role.AssignPermissionsRequest; +import com.vega.hrm.request.role.CreateRoleRequest; +import com.vega.hrm.request.role.UpdateRoleRequest; +import com.vega.hrm.response.RoleDto; +import com.vega.hrm.service.RoleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/auth/role") +@RequiredArgsConstructor +@Tag(name = "Auth - Role", description = "Quản lý vai trò và phân quyền") +public class RoleController { + private final RoleService roleService; + + @PostMapping("/create") + @Operation(summary = "Tạo role mới", description = "Tạo vai trò mới và gán quyền menu.") + public ResponseEntity> createRole( + @Valid @RequestBody CreateRoleRequest request, + @Parameter(description = "Người tạo (từ token)") @RequestParam(defaultValue = "admin") String currentUser + ) { + return ResponseEntity.ok(roleService.createRole(request, currentUser)); + } + + @PutMapping("/update") + @Operation(summary = "Cập nhật role", description = "Cập nhật thông tin vai trò.") + public ResponseEntity> updateRole(@Valid @RequestBody UpdateRoleRequest request) { + return ResponseEntity.ok(roleService.updateRole(request)); + } + + @DeleteMapping("/delete") + @Operation(summary = "Xóa role", description = "Xóa vai trò (soft delete).") + public ResponseEntity> deleteRole( + @Parameter(description = "ID của role") @RequestParam String roleId + ) { + return ResponseEntity.ok(roleService.deleteRole(roleId)); + } + + @GetMapping("/list") + @Operation(summary = "Lấy danh sách role", description = "Lấy tất cả vai trò trong hệ thống.") + public ResponseEntity>> getAllRoles() { + return ResponseEntity.ok(roleService.getAllRoles()); + } + + @GetMapping("/detail") + @Operation(summary = "Lấy chi tiết role", description = "Lấy thông tin chi tiết vai trò và quyền.") + public ResponseEntity> getRoleDetail( + @Parameter(description = "ID của role") @RequestParam String roleId + ) { + return ResponseEntity.ok(roleService.getRoleDetail(roleId)); + } + + @PostMapping("/assign-permissions") + @Operation(summary = "Gán quyền cho role", description = "Gán danh sách menu/function cho vai trò.") + public ResponseEntity> assignPermissions( + @Valid @RequestBody AssignPermissionsRequest request, + @Parameter(description = "Người thực hiện (từ token)") @RequestParam(defaultValue = "admin") String currentUser + ) { + return ResponseEntity.ok(roleService.assignPermissions(request, currentUser)); + } +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/controller/UserController.java b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/UserController.java index e4d6123..085b08a 100644 --- a/vega-hrm-auth/src/main/java/com/vega/hrm/controller/UserController.java +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/controller/UserController.java @@ -1,22 +1,33 @@ package com.vega.hrm.controller; import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.request.user.ChangePasswordRequest; import com.vega.hrm.request.user.CreateUserRequest; +import com.vega.hrm.request.user.ForgotPasswordRequest; import com.vega.hrm.request.user.LoginRequest; +import com.vega.hrm.request.user.RegisterRequest; +import com.vega.hrm.request.user.UpdateProfileRequest; +import com.vega.hrm.response.UserInfoResponse; import com.vega.hrm.service.UserService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("api/auth/user") @RequiredArgsConstructor -@Tag(name = "Auth - User", description = "Các API liên quan đến xác thực và quản lý người dùng nội bộ") +@Tag(name = "Auth - User", description = "Các API liên quan đến xác thực và quản lý người dùng") public class UserController { private final UserService userService; @@ -27,8 +38,52 @@ public class UserController { } @PostMapping("/insert") - @Operation(summary = "Tạo người dùng nội bộ", description = "Tạo tài khoản quản trị viên/nhân sự mới.") - public ResponseEntity> insert(@RequestBody CreateUserRequest request) { + @Operation(summary = "Tạo người dùng nội bộ (Admin)", description = "Tạo tài khoản quản trị viên/nhân sự mới bởi admin và gán role.") + public ResponseEntity> insert(@Valid @RequestBody CreateUserRequest request) { return ResponseEntity.ok(userService.insert(request)); } + + @PostMapping("/register") + @Operation(summary = "Đăng ký tài khoản", description = "Người dùng tự đăng ký tài khoản mới.") + public ResponseEntity> register(@Valid @RequestBody RegisterRequest request) { + return ResponseEntity.ok(userService.register(request)); + } + + @GetMapping("/info") + @Operation(summary = "Lấy thông tin tài khoản", description = "Lấy thông tin chi tiết của người dùng theo User ID.") + public ResponseEntity> getUserInfo( + @Parameter(description = "ID của người dùng") + @RequestParam String userId + ) { + return ResponseEntity.ok(userService.getUserInfo(userId)); + } + + @PutMapping("/profile") + @Operation(summary = "Cập nhật thông tin cá nhân", description = "Cập nhật họ tên, email của người dùng.") + public ResponseEntity> updateProfile(@Valid @RequestBody UpdateProfileRequest request) { + return ResponseEntity.ok(userService.updateProfile(request)); + } + + @PutMapping("/change-password") + @Operation(summary = "Đổi mật khẩu", description = "Người dùng đổi mật khẩu khi biết mật khẩu cũ.") + public ResponseEntity> changePassword(@Valid @RequestBody ChangePasswordRequest request) { + return ResponseEntity.ok(userService.changePassword(request)); + } + + @PostMapping("/forgot-password") + @Operation(summary = "Quên mật khẩu", description = "Tạo mật khẩu mới và gửi qua email khi người dùng quên mật khẩu.") + public ResponseEntity> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) { + return ResponseEntity.ok(userService.forgotPassword(request)); + } + + @PostMapping("/upload-avatar") + @Operation(summary = "Upload ảnh đại diện", description = "Upload ảnh đại diện cho người dùng (jpg, jpeg, png, gif).") + public ResponseEntity> uploadAvatar( + @Parameter(description = "ID của người dùng") + @RequestParam String userId, + @Parameter(description = "File ảnh đại diện") + @RequestParam("file") MultipartFile file + ) { + return ResponseEntity.ok(userService.uploadAvatar(userId, file)); + } } diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/CreateFunctionRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/CreateFunctionRequest.java new file mode 100644 index 0000000..0a8991f --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/CreateFunctionRequest.java @@ -0,0 +1,31 @@ +package com.vega.hrm.request.function; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CreateFunctionRequest { + @NotBlank(message = "Tên menu không được để trống") + @Size(max = 200, message = "Tên menu không được vượt quá 200 ký tự") + private String functionName; + + @NotNull(message = "Cấp độ menu không được để trống") + private Long functionLevel; // 1: menu cha, 2: menu con, 3: sub menu... + + @Size(max = 100, message = "URL không được vượt quá 100 ký tự") + private String functionUrl; // Đường dẫn route + + @NotNull(message = "Thứ tự hiển thị không được để trống") + private Long functionOrder; // Thứ tự sắp xếp + + private String parentId; // ID menu cha (null nếu là menu top level) + + @NotBlank(message = "Trạng thái hiển thị không được để trống") + @Size(max = 20, message = "Trạng thái hiển thị không được vượt quá 20 ký tự") + private String functionDisplay; // "show" / "hide" +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/UpdateFunctionRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/UpdateFunctionRequest.java new file mode 100644 index 0000000..9d4ff18 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/function/UpdateFunctionRequest.java @@ -0,0 +1,32 @@ +package com.vega.hrm.request.function; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateFunctionRequest { + @NotBlank(message = "Function ID không được để trống") + private String functionId; + + @NotBlank(message = "Tên menu không được để trống") + @Size(max = 200, message = "Tên menu không được vượt quá 200 ký tự") + private String functionName; + + @NotNull(message = "Cấp độ menu không được để trống") + private Long functionLevel; + + @Size(max = 100, message = "URL không được vượt quá 100 ký tự") + private String functionUrl; + + @NotNull(message = "Thứ tự hiển thị không được để trống") + private Long functionOrder; + + @NotBlank(message = "Trạng thái hiển thị không được để trống") + @Size(max = 20, message = "Trạng thái hiển thị không được vượt quá 20 ký tự") + private String functionDisplay; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/AssignPermissionsRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/AssignPermissionsRequest.java new file mode 100644 index 0000000..28fcc53 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/AssignPermissionsRequest.java @@ -0,0 +1,19 @@ +package com.vega.hrm.request.role; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AssignPermissionsRequest { + @NotBlank(message = "Role ID không được để trống") + private String roleId; + + @NotEmpty(message = "Danh sách quyền không được để trống") + private List functionIds; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/CreateRoleRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/CreateRoleRequest.java new file mode 100644 index 0000000..903d7d6 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/CreateRoleRequest.java @@ -0,0 +1,22 @@ +package com.vega.hrm.request.role; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CreateRoleRequest { + @NotBlank(message = "Tên role không được để trống") + @Size(max = 200, message = "Tên role không được vượt quá 200 ký tự") + private String roleName; + + @Size(max = 1000, message = "Mô tả không được vượt quá 1000 ký tự") + private String description; + + private List functionIds; // Danh sách function/menu IDs được gán cho role +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/UpdateRoleRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/UpdateRoleRequest.java new file mode 100644 index 0000000..b9f842f --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/role/UpdateRoleRequest.java @@ -0,0 +1,21 @@ +package com.vega.hrm.request.role; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateRoleRequest { + @NotBlank(message = "Role ID không được để trống") + private String roleId; + + @NotBlank(message = "Tên role không được để trống") + @Size(max = 200, message = "Tên role không được vượt quá 200 ký tự") + private String roleName; + + @Size(max = 1000, message = "Mô tả không được vượt quá 1000 ký tự") + private String description; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ChangePasswordRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ChangePasswordRequest.java new file mode 100644 index 0000000..78735d1 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ChangePasswordRequest.java @@ -0,0 +1,21 @@ +package com.vega.hrm.request.user; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChangePasswordRequest { + @NotBlank(message = "User ID không được để trống") + private String userId; + + @NotBlank(message = "Mật khẩu cũ không được để trống") + private String oldPassword; + + @NotBlank(message = "Mật khẩu mới không được để trống") + @Size(min = 6, message = "Mật khẩu mới phải có ít nhất 6 ký tự") + private String newPassword; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/CreateUserRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/CreateUserRequest.java index a80c6a9..26b57e4 100644 --- a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/CreateUserRequest.java +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/CreateUserRequest.java @@ -1,14 +1,30 @@ package com.vega.hrm.request.user; -import java.util.UUID; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CreateUserRequest { + @NotBlank(message = "Tên đăng nhập không được để trống") + @Size(max = 200, message = "Tên đăng nhập không được vượt quá 200 ký tự") private String userName; + + @NotBlank(message = "Mật khẩu không được để trống") + @Size(min = 6, message = "Mật khẩu phải có ít nhất 6 ký tự") private String password; + + @NotBlank(message = "Họ tên không được để trống") + @Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự") private String fullName; - private String roleId; + + @Email(message = "Email không đúng định dạng") + @Size(max = 255, message = "Email không được vượt quá 255 ký tự") + private String email; + + @NotBlank(message = "Role ID không được để trống") + private String roleId; // UUID của role } \ No newline at end of file diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ForgotPasswordRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ForgotPasswordRequest.java new file mode 100644 index 0000000..1e4cdbb --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/ForgotPasswordRequest.java @@ -0,0 +1,15 @@ +package com.vega.hrm.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ForgotPasswordRequest { + @NotBlank(message = "Email không được để trống") + @Email(message = "Email không đúng định dạng") + private String email; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/RegisterRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/RegisterRequest.java new file mode 100644 index 0000000..2123f8a --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/RegisterRequest.java @@ -0,0 +1,28 @@ +package com.vega.hrm.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RegisterRequest { + @NotBlank(message = "Tên đăng nhập không được để trống") + @Size(min = 3, max = 200, message = "Tên đăng nhập phải từ 3-200 ký tự") + private String userName; + + @NotBlank(message = "Mật khẩu không được để trống") + @Size(min = 6, message = "Mật khẩu phải có ít nhất 6 ký tự") + private String password; + + @NotBlank(message = "Họ tên không được để trống") + @Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự") + private String fullName; + + @Email(message = "Email không đúng định dạng") + @Size(max = 255, message = "Email không được vượt quá 255 ký tự") + private String email; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/UpdateProfileRequest.java b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/UpdateProfileRequest.java new file mode 100644 index 0000000..78d7732 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/request/user/UpdateProfileRequest.java @@ -0,0 +1,23 @@ +package com.vega.hrm.request.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UpdateProfileRequest { + @NotBlank(message = "User ID không được để trống") + private String userId; + + @NotBlank(message = "Họ tên không được để trống") + @Size(max = 200, message = "Họ tên không được vượt quá 200 ký tự") + private String fullName; + + @Email(message = "Email không đúng định dạng") + @Size(max = 255, message = "Email không được vượt quá 255 ký tự") + private String email; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/response/FunctionDto.java b/vega-hrm-auth/src/main/java/com/vega/hrm/response/FunctionDto.java new file mode 100644 index 0000000..dec5fb4 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/response/FunctionDto.java @@ -0,0 +1,27 @@ +package com.vega.hrm.response; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FunctionDto { + private UUID functionId; + private String functionName; + private Long functionLevel; + private String functionUrl; + private Long functionOrder; + private UUID parentId; + private String functionDisplay; + private String createdBy; + private Instant createTime; + private List children; // Menu con (nếu có) +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/response/LoginResponse.java b/vega-hrm-auth/src/main/java/com/vega/hrm/response/LoginResponse.java index a6f4f36..8ade0cc 100644 --- a/vega-hrm-auth/src/main/java/com/vega/hrm/response/LoginResponse.java +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/response/LoginResponse.java @@ -1,17 +1,55 @@ package com.vega.hrm.response; -import com.vega.hrm.core.entities.BoUser; import java.util.List; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.Getter; -import lombok.Setter; +import lombok.NoArgsConstructor; @Data @Builder +@NoArgsConstructor @AllArgsConstructor public class LoginResponse { - private BoUser user; + private UserLoginInfo user; private String token; + private RoleInfo role; + private List functions; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserLoginInfo { + private UUID userId; + private String userName; + private String fullName; + private String email; + private String avatarUrl; + private String status; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class RoleInfo { + private UUID roleId; + private String roleName; + private String description; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class FunctionInfo { + private UUID functionId; + private String functionName; + private String functionUrl; + private Long functionLevel; + private Long functionOrder; + private UUID parentId; + } } diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/response/RoleDto.java b/vega-hrm-auth/src/main/java/com/vega/hrm/response/RoleDto.java new file mode 100644 index 0000000..cc81d81 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/response/RoleDto.java @@ -0,0 +1,24 @@ +package com.vega.hrm.response; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RoleDto { + private UUID roleId; + private String roleName; + private String description; + private String status; + private String createdBy; + private Instant createTime; + private List functions; // Danh sách menu/quyền của role +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/response/UserInfoResponse.java b/vega-hrm-auth/src/main/java/com/vega/hrm/response/UserInfoResponse.java new file mode 100644 index 0000000..c24b18b --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/response/UserInfoResponse.java @@ -0,0 +1,25 @@ +package com.vega.hrm.response; + +import java.time.Instant; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserInfoResponse { + private UUID userId; + private String userName; + private String fullName; + private String email; + private String avatarUrl; + private String status; + private UUID roleId; + private Instant lastLoginTime; + private Instant createdDate; +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/service/FunctionService.java b/vega-hrm-auth/src/main/java/com/vega/hrm/service/FunctionService.java new file mode 100644 index 0000000..0ef5445 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/service/FunctionService.java @@ -0,0 +1,199 @@ +package com.vega.hrm.service; + +import com.vega.hrm.core.entities.BoFunction; +import com.vega.hrm.core.helpers.LogHelper; +import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.core.repositories.CoreFunctionRepository; +import com.vega.hrm.request.function.CreateFunctionRequest; +import com.vega.hrm.request.function.UpdateFunctionRequest; +import com.vega.hrm.response.FunctionDto; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class FunctionService { + private final CoreFunctionRepository functionRepository; + + /** + * Tạo menu/function mới + */ + @Transactional + public BaseResponse createFunction(CreateFunctionRequest request, String currentUser) { + var function = new BoFunction(); + function.setId(UUID.randomUUID()); + function.setFunctionName(request.getFunctionName()); + function.setFunctionLevel(request.getFunctionLevel()); + function.setFunctionUrl(request.getFunctionUrl()); + function.setFunctionOrder(request.getFunctionOrder()); + function.setFunctionDisplay(request.getFunctionDisplay()); + function.setCreatedBy(currentUser); + function.setCreateTime(Instant.now()); + + // Xử lý parent + if (request.getParentId() != null && !request.getParentId().isEmpty()) { + try { + function.setParentId(UUID.fromString(request.getParentId())); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Parent ID không hợp lệ"); + } + } else { + // Menu top level - dùng UUID zero + function.setParentId(UUID.fromString("00000000-0000-0000-0000-000000000000")); + } + + functionRepository.save(function); + LogHelper.info("Tạo menu thành công: " + function.getFunctionName()); + + return BaseResponse.success("Tạo menu thành công", mapToFunctionDto(function)); + } + + /** + * Cập nhật menu/function + */ + @Transactional + public BaseResponse updateFunction(UpdateFunctionRequest request) { + try { + UUID functionId = UUID.fromString(request.getFunctionId()); + var function = functionRepository.findById(functionId).orElse(null); + + if (function == null) { + return BaseResponse.notFound("Không tìm thấy menu"); + } + + function.setFunctionName(request.getFunctionName()); + function.setFunctionLevel(request.getFunctionLevel()); + function.setFunctionUrl(request.getFunctionUrl()); + function.setFunctionOrder(request.getFunctionOrder()); + function.setFunctionDisplay(request.getFunctionDisplay()); + + functionRepository.save(function); + LogHelper.info("Cập nhật menu thành công: " + function.getFunctionName()); + + return BaseResponse.success("Cập nhật menu thành công", mapToFunctionDto(function)); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Function ID không hợp lệ"); + } + } + + /** + * Xóa menu/function + */ + @Transactional + public BaseResponse deleteFunction(String functionId) { + try { + UUID id = UUID.fromString(functionId); + var function = functionRepository.findById(id).orElse(null); + + if (function == null) { + return BaseResponse.notFound("Không tìm thấy menu"); + } + + // Kiểm tra có menu con không + List children = functionRepository.findByParentId(id); + if (!children.isEmpty()) { + return BaseResponse.invalid("Không thể xóa menu có menu con. Vui lòng xóa menu con trước."); + } + + functionRepository.delete(function); + LogHelper.info("Xóa menu: " + function.getFunctionName()); + + return BaseResponse.success("Xóa menu thành công", true); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Function ID không hợp lệ"); + } + } + + /** + * Lấy tất cả menu dạng flat list + */ + public BaseResponse> getAllFunctions() { + List functions = functionRepository.findAllOrderByLevelAndOrder(); + List functionDtos = functions.stream() + .map(this::mapToFunctionDto) + .collect(Collectors.toList()); + + return BaseResponse.success("Lấy danh sách menu thành công", functionDtos); + } + + /** + * Lấy menu dạng tree (phân cấp) + */ + public BaseResponse> getMenuTree() { + List allFunctions = functionRepository.findAllOrderByLevelAndOrder(); + + // Group theo parent + Map> functionsByParent = new HashMap<>(); + for (BoFunction function : allFunctions) { + functionsByParent + .computeIfAbsent(function.getParentId(), k -> new ArrayList<>()) + .add(function); + } + + // Build tree từ root (parentId = 00000000-0000-0000-0000-000000000000) + UUID rootId = UUID.fromString("00000000-0000-0000-0000-000000000000"); + List tree = buildTree(rootId, functionsByParent); + + return BaseResponse.success("Lấy cây menu thành công", tree); + } + + /** + * Lấy chi tiết menu + */ + public BaseResponse getFunctionDetail(String functionId) { + try { + UUID id = UUID.fromString(functionId); + var function = functionRepository.findById(id).orElse(null); + + if (function == null) { + return BaseResponse.notFound("Không tìm thấy menu"); + } + + return BaseResponse.success("Lấy thông tin menu thành công", mapToFunctionDto(function)); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Function ID không hợp lệ"); + } + } + + /** + * Helper: Build tree từ flat list + */ + private List buildTree(UUID parentId, Map> functionsByParent) { + List children = functionsByParent.getOrDefault(parentId, new ArrayList<>()); + + return children.stream() + .map(function -> { + FunctionDto dto = mapToFunctionDto(function); + // Đệ quy build children + dto.setChildren(buildTree(function.getId(), functionsByParent)); + return dto; + }) + .collect(Collectors.toList()); + } + + /** + * Map entity sang DTO + */ + private FunctionDto mapToFunctionDto(BoFunction function) { + return FunctionDto.builder() + .functionId(function.getId()) + .functionName(function.getFunctionName()) + .functionLevel(function.getFunctionLevel()) + .functionUrl(function.getFunctionUrl()) + .functionOrder(function.getFunctionOrder()) + .parentId(function.getParentId()) + .functionDisplay(function.getFunctionDisplay()) + .createdBy(function.getCreatedBy()) + .createTime(function.getCreateTime()) + .build(); + } +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/service/RoleService.java b/vega-hrm-auth/src/main/java/com/vega/hrm/service/RoleService.java new file mode 100644 index 0000000..a423679 --- /dev/null +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/service/RoleService.java @@ -0,0 +1,235 @@ +package com.vega.hrm.service; + +import com.vega.hrm.core.entities.BoFunction; +import com.vega.hrm.core.entities.BoRole; +import com.vega.hrm.core.entities.BoRoleFunc; +import com.vega.hrm.core.helpers.LogHelper; +import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.core.repositories.BoRoleFuncRepository; +import com.vega.hrm.core.repositories.CoreFunctionRepository; +import com.vega.hrm.core.repositories.CoreRoleRepository; +import com.vega.hrm.request.role.AssignPermissionsRequest; +import com.vega.hrm.request.role.CreateRoleRequest; +import com.vega.hrm.request.role.UpdateRoleRequest; +import com.vega.hrm.response.FunctionDto; +import com.vega.hrm.response.RoleDto; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class RoleService { + private final CoreRoleRepository roleRepository; + private final CoreFunctionRepository functionRepository; + private final BoRoleFuncRepository roleFuncRepository; + + /** + * Tạo role mới và gán quyền + */ + @Transactional + public BaseResponse createRole(CreateRoleRequest request, String currentUser) { + // Kiểm tra tên role đã tồn tại + var existingRole = roleRepository.findByRoleName(request.getRoleName()); + if (existingRole != null) { + return BaseResponse.invalid("Tên role đã tồn tại"); + } + + // Tạo role mới + var role = new BoRole(); + role.setId(UUID.randomUUID()); + role.setRoleName(request.getRoleName()); + role.setDescription(request.getDescription()); + role.setStatus("1"); // Active + role.setCreatedBy(currentUser); + role.setCreateTime(Instant.now()); + + roleRepository.save(role); + + // Gán quyền nếu có + if (request.getFunctionIds() != null && !request.getFunctionIds().isEmpty()) { + assignFunctionsToRole(role.getId(), request.getFunctionIds(), currentUser); + } + + LogHelper.info("Tạo role thành công: " + role.getRoleName()); + return BaseResponse.success("Tạo role thành công", mapToRoleDto(role)); + } + + /** + * Cập nhật thông tin role + */ + @Transactional + public BaseResponse updateRole(UpdateRoleRequest request) { + try { + UUID roleId = UUID.fromString(request.getRoleId()); + var role = roleRepository.findById(roleId).orElse(null); + + if (role == null) { + return BaseResponse.notFound("Không tìm thấy role"); + } + + // Kiểm tra tên role trùng (nếu thay đổi) + if (!role.getRoleName().equals(request.getRoleName())) { + var existingRole = roleRepository.findByRoleName(request.getRoleName()); + if (existingRole != null) { + return BaseResponse.invalid("Tên role đã tồn tại"); + } + } + + role.setRoleName(request.getRoleName()); + role.setDescription(request.getDescription()); + + roleRepository.save(role); + LogHelper.info("Cập nhật role thành công: " + role.getRoleName()); + + return BaseResponse.success("Cập nhật role thành công", mapToRoleDto(role)); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Role ID không hợp lệ"); + } + } + + /** + * Xóa role (soft delete - chuyển status) + */ + @Transactional + public BaseResponse deleteRole(String roleId) { + try { + UUID id = UUID.fromString(roleId); + var role = roleRepository.findById(id).orElse(null); + + if (role == null) { + return BaseResponse.notFound("Không tìm thấy role"); + } + + role.setStatus("0"); // Inactive + roleRepository.save(role); + + LogHelper.info("Xóa role: " + role.getRoleName()); + return BaseResponse.success("Xóa role thành công", true); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Role ID không hợp lệ"); + } + } + + /** + * Lấy danh sách tất cả role + */ + public BaseResponse> getAllRoles() { + List roles = roleRepository.findAll(); + List roleDtos = roles.stream() + .map(this::mapToRoleDto) + .collect(Collectors.toList()); + + return BaseResponse.success("Lấy danh sách role thành công", roleDtos); + } + + /** + * Lấy chi tiết role và quyền + */ + public BaseResponse getRoleDetail(String roleId) { + try { + UUID id = UUID.fromString(roleId); + var role = roleRepository.findById(id).orElse(null); + + if (role == null) { + return BaseResponse.notFound("Không tìm thấy role"); + } + + // Lấy danh sách function của role + List functionIds = roleFuncRepository.findFunctionIdsByRoleId(id); + List functions = functionRepository.findByIdIn(functionIds); + + RoleDto roleDto = mapToRoleDto(role); + roleDto.setFunctions(functions.stream() + .map(this::mapToFunctionDto) + .collect(Collectors.toList())); + + return BaseResponse.success("Lấy thông tin role thành công", roleDto); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Role ID không hợp lệ"); + } + } + + /** + * Gán quyền cho role + */ + @Transactional + public BaseResponse assignPermissions(AssignPermissionsRequest request, String currentUser) { + try { + UUID roleId = UUID.fromString(request.getRoleId()); + var role = roleRepository.findById(roleId).orElse(null); + + if (role == null) { + return BaseResponse.notFound("Không tìm thấy role"); + } + + // Xóa tất cả quyền cũ + roleFuncRepository.deleteByRoleId(roleId); + + // Gán quyền mới + assignFunctionsToRole(roleId, request.getFunctionIds(), currentUser); + + LogHelper.info("Gán quyền cho role: " + role.getRoleName() + + " (" + request.getFunctionIds().size() + " quyền)"); + + return BaseResponse.success("Gán quyền thành công", true); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Role ID không hợp lệ"); + } + } + + /** + * Helper: Gán functions cho role + */ + private void assignFunctionsToRole(UUID roleId, List functionIds, String currentUser) { + var role = roleRepository.findById(roleId).orElse(null); + if (role == null) return; + + for (UUID functionId : functionIds) { + var function = functionRepository.findById(functionId).orElse(null); + if (function == null) continue; + + var roleFunc = new BoRoleFunc(); + roleFunc.setId(UUID.randomUUID()); + roleFunc.setRole(role); + roleFunc.setFunction(function); + roleFunc.setCreateTime(Instant.now()); + roleFunc.setCreateUser(currentUser); + + roleFuncRepository.save(roleFunc); + } + } + + /** + * Map entity sang DTO + */ + private RoleDto mapToRoleDto(BoRole role) { + return RoleDto.builder() + .roleId(role.getId()) + .roleName(role.getRoleName()) + .description(role.getDescription()) + .status(role.getStatus()) + .createdBy(role.getCreatedBy()) + .createTime(role.getCreateTime()) + .build(); + } + + private FunctionDto mapToFunctionDto(BoFunction function) { + return FunctionDto.builder() + .functionId(function.getId()) + .functionName(function.getFunctionName()) + .functionLevel(function.getFunctionLevel()) + .functionUrl(function.getFunctionUrl()) + .functionOrder(function.getFunctionOrder()) + .parentId(function.getParentId()) + .functionDisplay(function.getFunctionDisplay()) + .createdBy(function.getCreatedBy()) + .createTime(function.getCreateTime()) + .build(); + } +} + diff --git a/vega-hrm-auth/src/main/java/com/vega/hrm/service/UserService.java b/vega-hrm-auth/src/main/java/com/vega/hrm/service/UserService.java index b34d468..1a51bcc 100644 --- a/vega-hrm-auth/src/main/java/com/vega/hrm/service/UserService.java +++ b/vega-hrm-auth/src/main/java/com/vega/hrm/service/UserService.java @@ -2,17 +2,32 @@ package com.vega.hrm.service; import com.vega.hrm.AuthHelper; import com.vega.hrm.core.constants.CommonConst; +import com.vega.hrm.core.entities.BoFunction; +import com.vega.hrm.core.entities.BoRole; import com.vega.hrm.core.entities.BoUser; import com.vega.hrm.core.helpers.JwtHelper; +import com.vega.hrm.core.helpers.LogHelper; import com.vega.hrm.core.models.responses.BaseResponse; +import com.vega.hrm.core.repositories.BoRoleFuncRepository; +import com.vega.hrm.core.repositories.CoreFunctionRepository; +import com.vega.hrm.core.repositories.CoreRoleRepository; import com.vega.hrm.core.repositories.CoreUserRepository; +import com.vega.hrm.core.service.FileStorageService; import com.vega.hrm.core.service.RedisService; +import com.vega.hrm.request.user.ChangePasswordRequest; import com.vega.hrm.request.user.CreateUserRequest; +import com.vega.hrm.request.user.ForgotPasswordRequest; import com.vega.hrm.request.user.LoginRequest; +import com.vega.hrm.request.user.RegisterRequest; +import com.vega.hrm.request.user.UpdateProfileRequest; import com.vega.hrm.response.LoginResponse; +import com.vega.hrm.response.UserInfoResponse; +import java.security.SecureRandom; import java.time.Instant; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.util.Strings; import org.springframework.stereotype.Service; @@ -22,7 +37,11 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class UserService { private final CoreUserRepository userRepository; + private final CoreRoleRepository roleRepository; + private final CoreFunctionRepository functionRepository; + private final BoRoleFuncRepository roleFuncRepository; private final RedisService redisService; + private final FileStorageService fileStorageService; public BaseResponse login(LoginRequest request) { var user = userRepository.findByUserName(request.getUserName()); @@ -35,22 +54,70 @@ public class UserService { user.setNumberOfFailedLogins(0L); user.setLastLoginTime(Instant.now()); userRepository.save(user); - var claims = new HashMap(); - claims.put("UserId", user.getId()); - claims.put("UserName", user.getUserName()); + var token = JwtHelper.generateToken(user.getUserName(), user.getId(), UUID.randomUUID()); if (Strings.isBlank(token)) { return BaseResponse.internalSystemError(); } - redisService.hSet(CommonConst.TOKEN,user.getId().toString(),token); + redisService.hSet(CommonConst.TOKEN, user.getId().toString(), token); - var baseResponseLogin = BaseResponse.builder().code("00"); - baseResponseLogin.data( - LoginResponse.builder() - .user(user) - .token(token) - .build()); - return baseResponseLogin.build(); + // Lấy thông tin role + LoginResponse.RoleInfo roleInfo = null; + if (user.getRoleId() != null) { + BoRole role = roleRepository.findById(user.getRoleId()).orElse(null); + if (role != null) { + roleInfo = LoginResponse.RoleInfo.builder() + .roleId(role.getId()) + .roleName(role.getRoleName()) + .description(role.getDescription()) + .build(); + } + } + + // Lấy danh sách function/menu được phép truy cập + List functions = new ArrayList<>(); + if (user.getRoleId() != null) { + List functionIds = roleFuncRepository.findFunctionIdsByRoleId(user.getRoleId()); + if (!functionIds.isEmpty()) { + List functionList = functionRepository.findByIdIn(functionIds); + functions = functionList.stream() + .map(f -> LoginResponse.FunctionInfo.builder() + .functionId(f.getId()) + .functionName(f.getFunctionName()) + .functionUrl(f.getFunctionUrl()) + .functionLevel(f.getFunctionLevel()) + .functionOrder(f.getFunctionOrder()) + .parentId(f.getParentId()) + .build()) + .collect(Collectors.toList()); + } + } + + // Build user info + LoginResponse.UserLoginInfo userInfo = LoginResponse.UserLoginInfo.builder() + .userId(user.getId()) + .userName(user.getUserName()) + .fullName(user.getFullName()) + .email(user.getEmail()) + .avatarUrl(user.getAvatarUrl()) + .status(user.getStatus()) + .build(); + + var loginResponse = LoginResponse.builder() + .user(userInfo) + .token(token) + .role(roleInfo) + .functions(functions) + .build(); + + LogHelper.info("Đăng nhập thành công: " + user.getUserName() + + " với " + functions.size() + " quyền"); + + return BaseResponse.builder() + .code("00") + .message("Đăng nhập thành công") + .data(loginResponse) + .build(); } if (user.getStatus().equals("1")) { user.setNumberOfFailedLogins(user.getNumberOfFailedLogins() + 1); @@ -58,29 +125,270 @@ public class UserService { } } - return BaseResponse.invalid( - "UserName or Password is valid. Please check."); + return BaseResponse.invalid("Tên đăng nhập hoặc mật khẩu không đúng"); } @Transactional public BaseResponse insert(CreateUserRequest request) { - var user = userRepository.findByUserName(request.getUserName()); - if (user != null) { - return BaseResponse.success("User created unsuccessful"); + // Kiểm tra username đã tồn tại + var existingUser = userRepository.findByUserName(request.getUserName()); + if (existingUser != null) { + return BaseResponse.invalid("Tên đăng nhập đã tồn tại"); } + + // Kiểm tra email đã tồn tại + if (request.getEmail() != null) { + var existingEmail = userRepository.findByEmail(request.getEmail()); + if (existingEmail != null) { + return BaseResponse.invalid("Email đã được sử dụng"); + } + } + + // Validate roleId + UUID roleId; + try { + roleId = UUID.fromString(request.getRoleId()); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("Role ID không hợp lệ"); + } + var newUser = new BoUser(); newUser.setId(UUID.randomUUID()); newUser.setUserName(request.getUserName()); newUser.setFullName(request.getFullName()); - newUser.setRoleId(UUID.randomUUID()); + newUser.setEmail(request.getEmail()); + newUser.setRoleId(roleId); newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword())); newUser.setStatus("1"); newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60)); newUser.setCreatedDate(Instant.now()); - newUser.setCreatedUser("system"); + newUser.setCreatedUser("admin"); // TODO: Lấy từ token newUser.setNumberOfFailedLogins(0L); newUser.setIsPasswordChanged(0L); + userRepository.save(newUser); - return BaseResponse.success("User created successfully"); + LogHelper.info("Tạo user thành công: " + request.getUserName() + " với role: " + roleId); + + return BaseResponse.success("Tạo user thành công", true); + } + + /** + * Đăng ký tài khoản mới (cho người dùng tự đăng ký) + */ + @Transactional + public BaseResponse register(RegisterRequest request) { + // Kiểm tra username đã tồn tại + var existingUser = userRepository.findByUserName(request.getUserName()); + if (existingUser != null) { + return BaseResponse.invalid("Tên đăng nhập đã tồn tại"); + } + + // Kiểm tra email đã tồn tại + if (request.getEmail() != null) { + var existingEmail = userRepository.findByEmail(request.getEmail()); + if (existingEmail != null) { + return BaseResponse.invalid("Email đã được sử dụng"); + } + } + + var newUser = new BoUser(); + newUser.setId(UUID.randomUUID()); + newUser.setUserName(request.getUserName()); + newUser.setFullName(request.getFullName()); + newUser.setEmail(request.getEmail()); + newUser.setPassword(AuthHelper.hashPassword(request.getUserName(), request.getPassword())); + newUser.setStatus("1"); + newUser.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60)); + newUser.setCreatedDate(Instant.now()); + newUser.setCreatedUser("self-register"); + newUser.setNumberOfFailedLogins(0L); + newUser.setIsPasswordChanged(0L); + + userRepository.save(newUser); + LogHelper.info("Đăng ký tài khoản thành công: " + request.getUserName()); + return BaseResponse.success("Đăng ký tài khoản thành công", true); + } + + /** + * Lấy thông tin tài khoản + */ + public BaseResponse getUserInfo(String userId) { + try { + UUID id = UUID.fromString(userId); + var user = userRepository.findById(id).orElse(null); + + if (user == null) { + return BaseResponse.notFound("Không tìm thấy người dùng"); + } + + UserInfoResponse response = UserInfoResponse.builder() + .userId(user.getId()) + .userName(user.getUserName()) + .fullName(user.getFullName()) + .email(user.getEmail()) + .avatarUrl(user.getAvatarUrl()) + .status(user.getStatus()) + .roleId(user.getRoleId()) + .lastLoginTime(user.getLastLoginTime()) + .createdDate(user.getCreatedDate()) + .build(); + + return BaseResponse.success("Lấy thông tin thành công", response); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("User ID không hợp lệ"); + } + } + + /** + * Cập nhật thông tin cá nhân + */ + @Transactional + public BaseResponse updateProfile(UpdateProfileRequest request) { + try { + UUID id = UUID.fromString(request.getUserId()); + var user = userRepository.findById(id).orElse(null); + + if (user == null) { + return BaseResponse.notFound("Không tìm thấy người dùng"); + } + + // Kiểm tra email trùng (nếu thay đổi) + if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { + var existingEmail = userRepository.findByEmail(request.getEmail()); + if (existingEmail != null && !existingEmail.getId().equals(id)) { + return BaseResponse.invalid("Email đã được sử dụng bởi tài khoản khác"); + } + } + + user.setFullName(request.getFullName()); + user.setEmail(request.getEmail()); + user.setUpdatedDate(Instant.now()); + user.setUpdatedUser(user.getUserName()); + + userRepository.save(user); + LogHelper.info("Cập nhật thông tin thành công cho user: " + user.getUserName()); + return BaseResponse.success("Cập nhật thông tin thành công", true); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("User ID không hợp lệ"); + } + } + + /** + * Đổi mật khẩu + */ + @Transactional + public BaseResponse changePassword(ChangePasswordRequest request) { + try { + UUID id = UUID.fromString(request.getUserId()); + var user = userRepository.findById(id).orElse(null); + + if (user == null) { + return BaseResponse.notFound("Không tìm thấy người dùng"); + } + + // Kiểm tra mật khẩu cũ + String hashedOldPassword = AuthHelper.hashPassword(user.getUserName(), request.getOldPassword()); + if (!user.getPassword().equals(hashedOldPassword)) { + return BaseResponse.invalid("Mật khẩu cũ không đúng"); + } + + // Cập nhật mật khẩu mới + String hashedNewPassword = AuthHelper.hashPassword(user.getUserName(), request.getNewPassword()); + user.setPassword(hashedNewPassword); + user.setIsPasswordChanged(1L); + user.setPwdExpireDate(Instant.now().plusSeconds(90L * 24 * 60 * 60)); + user.setUpdatedDate(Instant.now()); + user.setUpdatedUser(user.getUserName()); + + userRepository.save(user); + LogHelper.info("Đổi mật khẩu thành công cho user: " + user.getUserName()); + return BaseResponse.success("Đổi mật khẩu thành công", true); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid("User ID không hợp lệ"); + } + } + + /** + * Quên mật khẩu - Tạo mật khẩu mới và gửi qua email (giả lập) + */ + @Transactional + public BaseResponse forgotPassword(ForgotPasswordRequest request) { + var user = userRepository.findByEmail(request.getEmail()); + + if (user == null) { + // Không tiết lộ email có tồn tại hay không (bảo mật) + return BaseResponse.success("Nếu email tồn tại, mật khẩu mới đã được gửi", null); + } + + // Tạo mật khẩu ngẫu nhiên + String newPassword = generateRandomPassword(8); + + // Hash và lưu mật khẩu mới + String hashedPassword = AuthHelper.hashPassword(user.getUserName(), newPassword); + user.setPassword(hashedPassword); + user.setIsPasswordChanged(0L); // Bắt buộc đổi mật khẩu lần đầu đăng nhập + user.setPwdExpireDate(Instant.now().plusSeconds(7L * 24 * 60 * 60)); // 7 ngày + user.setUpdatedDate(Instant.now()); + user.setUpdatedUser("system-forgot-password"); + + userRepository.save(user); + + // TODO: Tích hợp email service để gửi mật khẩu mới + LogHelper.info("Tạo mật khẩu mới cho user: " + user.getUserName()); + LogHelper.info("Mật khẩu tạm thời (chỉ hiển thị trong môi trường dev): " + newPassword); + + // Trong production, không trả về mật khẩu qua API + return BaseResponse.success("Mật khẩu mới đã được gửi qua email", newPassword); + } + + /** + * Tạo mật khẩu ngẫu nhiên + */ + private String generateRandomPassword(int length) { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%"; + SecureRandom random = new SecureRandom(); + StringBuilder password = new StringBuilder(); + + for (int i = 0; i < length; i++) { + password.append(chars.charAt(random.nextInt(chars.length()))); + } + + return password.toString(); + } + + /** + * Upload avatar cho người dùng + */ + @Transactional + public BaseResponse uploadAvatar(String userId, org.springframework.web.multipart.MultipartFile file) { + try { + UUID id = UUID.fromString(userId); + var user = userRepository.findById(id).orElse(null); + + if (user == null) { + return BaseResponse.notFound("Không tìm thấy người dùng"); + } + + // Xóa avatar cũ nếu có + if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) { + fileStorageService.deleteAvatarFile(user.getAvatarUrl()); + } + + // Lưu avatar mới + String avatarUrl = fileStorageService.storeAvatarFile(file, userId); + user.setAvatarUrl(avatarUrl); + user.setUpdatedDate(Instant.now()); + user.setUpdatedUser(user.getUserName()); + + userRepository.save(user); + LogHelper.info("Upload avatar thành công cho user: " + user.getUserName()); + + return BaseResponse.success("Upload avatar thành công", avatarUrl); + } catch (IllegalArgumentException e) { + return BaseResponse.invalid(e.getMessage()); + } catch (Exception e) { + LogHelper.error("Lỗi khi upload avatar: " + e.getMessage()); + return BaseResponse.internalSystemError(); + } } } diff --git a/vega-hrm-auth/src/main/resources/application.properties b/vega-hrm-auth/src/main/resources/application.properties index fbd0af3..13f6aab 100644 --- a/vega-hrm-auth/src/main/resources/application.properties +++ b/vega-hrm-auth/src/main/resources/application.properties @@ -5,9 +5,11 @@ vega.hrm.postgre.enabled=true vega.jpa.repository.basePackage=com.vega.hrm.core.repositories vega.jpa.entity.basePackage=com.vega.hrm.core.entities spring.config.import=file:config/shared.properties -logging.config=file:config/log4j2.properties -springdoc.api-docs.path=/api-docs/auth -springdoc.swagger-ui.path=/swagger-ui/auth +logging.config=file:./config/log4j2.properties + springdoc.swagger-ui.operations-sorter=method springdoc.swagger-ui.tags-sorter=alpha +springdoc.swagger-ui.path=/auth/swagger-ui/index.html +springdoc.api-docs.path=/auth/api-docs + diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/configs/StaticResourceConfig.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/configs/StaticResourceConfig.java new file mode 100644 index 0000000..2b5646a --- /dev/null +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/configs/StaticResourceConfig.java @@ -0,0 +1,21 @@ +package com.vega.hrm.core.configs; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class StaticResourceConfig implements WebMvcConfigurer { + + @Value("${file.upload.avatar-dir:uploads/avatars}") + private String avatarDir; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Map URL /uploads/avatars/** to file system directory + registry.addResourceHandler("/uploads/avatars/**") + .addResourceLocations("file:" + avatarDir + "/"); + } +} + diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/entities/BoUser.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/entities/BoUser.java index 8f6641d..773aae6 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/entities/BoUser.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/entities/BoUser.java @@ -86,4 +86,8 @@ public class BoUser { @Column(name = "email") private String email; + @Size(max = 500) + @Column(name = "avatar_url", length = 500) + private String avatarUrl; + } \ No newline at end of file diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/AuthorizationFilter.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/AuthorizationFilter.java index 1e0630b..95d2829 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/AuthorizationFilter.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/AuthorizationFilter.java @@ -41,7 +41,26 @@ public class AuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) { try { - var uri = request.getRequestURI(); + String uri = request.getRequestURI(); // /auth/api/v1/test + String[] parts = uri.split("/"); + + if(!uri.contains("swagger-ui")) { + if(!uri.contains("api-docs")) { + if (parts.length > 1) { + String first = parts[1]; + + if ("auth".equals(first) || "report".equals(first)) { + + String newUri = uri.replaceFirst("/" + first, ""); + request.getRequestDispatcher(newUri).forward(request, response); + return; + } + } + } + + } + + if (EXCLUDE_URIS.contains(uri) || uri.contains("actuator") || uri.contains("swagger") || uri.contains("api-docs")) { filterChain.doFilter(request, response); return; diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/CorsFilter.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/CorsFilter.java index fd2a222..5c4aaa3 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/CorsFilter.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/filters/CorsFilter.java @@ -2,11 +2,13 @@ package com.vega.hrm.core.filters; import com.vega.hrm.core.helpers.LogHelper; +import jakarta.annotation.PostConstruct; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.time.Instant; import java.util.Arrays; +import java.util.List; import java.util.UUID; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -20,11 +22,27 @@ import org.springframework.web.filter.OncePerRequestFilter; @Component @Slf4j -@Order(1) +@Order(0) public class CorsFilter extends OncePerRequestFilter { + /** + * Ví dụ cấu hình: + * cors.allowed-origins=* + * cors.allowed-origins=http://localhost:3000,https://vega-frontend.onestop.vn + */ @Value("${cors.allowed-origins:*}") - private String[] allowedOrigins; + private String allowedOriginsConfig; + + private List allowedOrigins; + + @PostConstruct + public void init() { + this.allowedOrigins = Arrays.stream(allowedOriginsConfig.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } + @Override protected void doFilterInternal( @NonNull HttpServletRequest request, @@ -33,20 +51,30 @@ public class CorsFilter extends OncePerRequestFilter { ) { try { var startTime = Instant.now(); + + // Xử lý CORS var origin = request.getHeader("Origin"); - if (Strings.isNotBlank(origin) && Arrays.stream(allowedOrigins).toList() - .contains(origin)) { - response.setHeader("Access-Control-Allow-Origin", origin); + if (Strings.isNotBlank(origin)) { + // Nếu cấu hình chứa "*" thì cho phép mọi origin, + // còn không thì chỉ cho phép những origin nằm trong danh sách. + if (allowedOrigins.contains("*") || allowedOrigins.contains(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Vary", "Origin"); + } } - response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + response.setHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS"); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, b"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setCharacterEncoding("UTF-8"); + + // Preflight request if (request.getMethod().equalsIgnoreCase("OPTIONS")) { response.setStatus(HttpServletResponse.SC_OK); return; } + + // Logging + trace var clientIp = request.getHeader("X-Forwarded-For"); if (Strings.isBlank(clientIp)) { clientIp = request.getRemoteAddr(); @@ -58,7 +86,9 @@ public class CorsFilter extends OncePerRequestFilter { var traceId = UUID.randomUUID().toString(); ThreadContext.put("traceId", traceId); LogHelper.info("Request start"); + filterChain.doFilter(request, response); + var duration = Instant.now().toEpochMilli() - startTime.toEpochMilli(); LogHelper.info("Request end - Duration: " + duration + "ms"); } catch (Exception e) { diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/BoRoleFuncRepository.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/BoRoleFuncRepository.java new file mode 100644 index 0000000..1e8447c --- /dev/null +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/BoRoleFuncRepository.java @@ -0,0 +1,25 @@ +package com.vega.hrm.core.repositories; + +import com.vega.hrm.core.entities.BoRoleFunc; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface BoRoleFuncRepository extends JpaRepository { + + @Query("SELECT rf FROM BoRoleFunc rf WHERE rf.role.id = :roleId") + List findByRoleId(@Param("roleId") UUID roleId); + + @Modifying + @Query("DELETE FROM BoRoleFunc rf WHERE rf.role.id = :roleId") + void deleteByRoleId(@Param("roleId") UUID roleId); + + @Query("SELECT rf.function.id FROM BoRoleFunc rf WHERE rf.role.id = :roleId") + List findFunctionIdsByRoleId(@Param("roleId") UUID roleId); +} + diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreFunctionRepository.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreFunctionRepository.java index c4eeb51..b78646b 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreFunctionRepository.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreFunctionRepository.java @@ -10,6 +10,14 @@ import org.springframework.stereotype.Repository; @Repository public interface CoreFunctionRepository extends JpaRepository { - + + @Query("SELECT f FROM BoFunction f ORDER BY f.functionLevel, f.functionOrder") + List findAllOrderByLevelAndOrder(); + + @Query("SELECT f FROM BoFunction f WHERE f.parentId = :parentId ORDER BY f.functionOrder") + List findByParentId(@Param("parentId") UUID parentId); + + @Query("SELECT f FROM BoFunction f WHERE f.id IN :ids ORDER BY f.functionLevel, f.functionOrder") + List findByIdIn(@Param("ids") List ids); } diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreRoleRepository.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreRoleRepository.java index 08fff68..9d4cbdd 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreRoleRepository.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreRoleRepository.java @@ -1,11 +1,14 @@ package com.vega.hrm.core.repositories; import com.vega.hrm.core.entities.BoRole; +import java.util.List; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface CoreRoleRepository extends JpaRepository { - BoRole findBoRoleById(UUID id); + BoRole findBoRoleById(UUID id); + BoRole findByRoleName(String roleName); + List findByStatus(String status); } diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreUserRepository.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreUserRepository.java index 43b7e72..9a33298 100644 --- a/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreUserRepository.java +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/repositories/CoreUserRepository.java @@ -9,4 +9,5 @@ import org.springframework.stereotype.Repository; @Repository public interface CoreUserRepository extends JpaRepository { BoUser findByUserName(String username); + BoUser findByEmail(String email); } diff --git a/vega-hrm-core/src/main/java/com/vega/hrm/core/service/FileStorageService.java b/vega-hrm-core/src/main/java/com/vega/hrm/core/service/FileStorageService.java new file mode 100644 index 0000000..50c9845 --- /dev/null +++ b/vega-hrm-core/src/main/java/com/vega/hrm/core/service/FileStorageService.java @@ -0,0 +1,113 @@ +package com.vega.hrm.core.service; + +import com.vega.hrm.core.helpers.LogHelper; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class FileStorageService { + + private final Path avatarStorageLocation; + + public FileStorageService(@Value("${file.upload.avatar-dir:uploads/avatars}") String avatarDir) { + this.avatarStorageLocation = Paths.get(avatarDir).toAbsolutePath().normalize(); + + try { + Files.createDirectories(this.avatarStorageLocation); + LogHelper.info("Thư mục lưu avatar đã được tạo: " + this.avatarStorageLocation); + } catch (IOException ex) { + LogHelper.error("Không thể tạo thư mục lưu trữ avatar: " + ex.getMessage()); + throw new RuntimeException("Không thể tạo thư mục lưu trữ avatar", ex); + } + } + + /** + * Lưu file avatar và trả về đường dẫn tương đối + */ + public String storeAvatarFile(MultipartFile file, String userId) { + if (file.isEmpty()) { + throw new IllegalArgumentException("File rỗng, không thể upload"); + } + + // Lấy extension gốc + String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); + String fileExtension = ""; + + if (originalFilename.contains(".")) { + fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + + // Validate extension + if (!isValidImageExtension(fileExtension)) { + throw new IllegalArgumentException("Chỉ chấp nhận file ảnh: jpg, jpeg, png, gif"); + } + + // Tạo tên file unique: userId_timestamp_uuid.ext + String newFilename = userId + "_" + System.currentTimeMillis() + "_" + + UUID.randomUUID().toString().substring(0, 8) + fileExtension; + + try { + // Kiểm tra path traversal attack + if (newFilename.contains("..")) { + throw new IllegalArgumentException("Tên file không hợp lệ: " + newFilename); + } + + Path targetLocation = this.avatarStorageLocation.resolve(newFilename); + Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING); + + LogHelper.info("Đã lưu avatar: " + newFilename + " (size: " + file.getSize() + " bytes)"); + + // Trả về đường dẫn tương đối để lưu vào DB + return "/uploads/avatars/" + newFilename; + + } catch (IOException ex) { + LogHelper.error("Lỗi khi lưu file avatar: " + ex.getMessage()); + throw new RuntimeException("Lỗi khi lưu file avatar", ex); + } + } + + /** + * Xóa file avatar cũ + */ + public void deleteAvatarFile(String avatarUrl) { + if (avatarUrl == null || avatarUrl.isEmpty()) { + return; + } + + try { + // Lấy tên file từ URL + String filename = avatarUrl.substring(avatarUrl.lastIndexOf("/") + 1); + Path filePath = this.avatarStorageLocation.resolve(filename).normalize(); + + Files.deleteIfExists(filePath); + LogHelper.info("Đã xóa avatar cũ: " + filename); + } catch (IOException ex) { + LogHelper.error("Lỗi khi xóa avatar cũ: " + ex.getMessage()); + } + } + + /** + * Kiểm tra extension hợp lệ + */ + private boolean isValidImageExtension(String extension) { + String lowerExt = extension.toLowerCase(); + return lowerExt.equals(".jpg") || lowerExt.equals(".jpeg") + || lowerExt.equals(".png") || lowerExt.equals(".gif"); + } + + /** + * Lấy đường dẫn đầy đủ của file + */ + public Path getAvatarPath(String filename) { + return this.avatarStorageLocation.resolve(filename).normalize(); + } +} + diff --git a/vega-hrm-core/src/main/resources/application.properties b/vega-hrm-core/src/main/resources/application.properties index 9492855..3e3cd02 100644 --- a/vega-hrm-core/src/main/resources/application.properties +++ b/vega-hrm-core/src/main/resources/application.properties @@ -1,4 +1,4 @@ -google.client.clientId = 719251949807-0jqbsmlh0a116cm8vm47oknmc5vpi19q.apps.googleusercontent.com -google.client.clientSecret = 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 -google.client.redirect.uri=http://localhost:8089/api/google/user/callback - +# File Upload Configuration +file.upload.avatar-dir=uploads/avatars +spring.servlet.multipart.max-file-size=5MB +spring.servlet.multipart.max-request-size=5MB diff --git a/vega-hrm-report/src/main/resources/application.properties b/vega-hrm-report/src/main/resources/application.properties index e90db04..bfc2fa2 100644 --- a/vega-hrm-report/src/main/resources/application.properties +++ b/vega-hrm-report/src/main/resources/application.properties @@ -6,8 +6,8 @@ vega.jpa.repository.basePackage=com.vega.hrm.core.repositories vega.jpa.entity.basePackage=com.vega.hrm.core.entities spring.config.import=file:config/shared.properties logging.config=file:config/log4j2.properties -springdoc.api-docs.path=/api-docs/report -springdoc.swagger-ui.path=/swagger-ui/report +springdoc.swagger-ui.path=/report/swagger-ui/index.html +springdoc.api-docs.path=/report/api-docs springdoc.swagger-ui.operations-sorter=method springdoc.swagger-ui.tags-sorter=alpha