diff --git a/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupController.java b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupController.java new file mode 100644 index 0000000..60dad35 --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupController.java @@ -0,0 +1,129 @@ +package com.eactive.testmaster.api.controller; + +import com.eactive.testmaster.api.dto.MockRouteGroupSearch; +import com.eactive.testmaster.api.entity.MockRouteGroup; +import com.eactive.testmaster.api.repository.MockRouteGroupRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; + +import javax.validation.Valid; +import java.util.List; + +@Controller +@RequestMapping("/mgmt/routes/group") +public class MockRouteGroupController { + + private static final String GROUP = "group"; + private static final String GROUP_LIST_VIEW = "page/routes/groupList"; + private static final String GROUP_EDIT = "page/routes/groupEdit"; + private static final String REDIRECT_TO_LIST = "redirect:/mgmt/routes/group/list_view.do"; + + private final MockRouteGroupRepository mockRouteGroupRepository; + private final MockRouteGroupValidator mockRouteGroupValidator; + + public MockRouteGroupController(MockRouteGroupRepository mockRouteGroupRepository, + MockRouteGroupValidator mockRouteGroupValidator) { + this.mockRouteGroupRepository = mockRouteGroupRepository; + this.mockRouteGroupValidator = mockRouteGroupValidator; + } + + @InitBinder("group") + public void initBinder(WebDataBinder binder) { + binder.setValidator(mockRouteGroupValidator); + } + + @GetMapping("/list_view.do") + @Secured("ROLE_MANAGE_ROUTE") + public ModelAndView listView(@ModelAttribute("groupSearch") MockRouteGroupSearch groupSearch, + Pageable pageable) { + ModelAndView mav = new ModelAndView(GROUP_LIST_VIEW); + Page page = mockRouteGroupRepository.findAll( + groupSearch.buildSpecification(), pageable); + mav.addObject("page", page); + mav.addObject("pageable", pageable); + mav.addObject("modelSearch", groupSearch); + return mav; + } + + @GetMapping("/create_view.do") + @Secured("ROLE_MANAGE_ROUTE") + public ModelAndView createView() { + ModelAndView mav = new ModelAndView(GROUP_EDIT); + mav.addObject(GROUP, new MockRouteGroup()); + return mav; + } + + @GetMapping("/update_view.do") + @Secured("ROLE_MANAGE_ROUTE") + public ModelAndView updateView(@RequestParam("id") String id) { + ModelAndView mav = new ModelAndView(GROUP_EDIT); + MockRouteGroup group = mockRouteGroupRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Group not found")); + mav.addObject(GROUP, group); + return mav; + } + + @PostMapping("/register.do") + @Secured("ROLE_MANAGE_ROUTE") + public String register(@Valid @ModelAttribute(GROUP) MockRouteGroup group, + BindingResult bindingResult, + ModelMap model) { + if (bindingResult.hasErrors()) { + model.addAttribute(GROUP, group); + return GROUP_EDIT; + } + + try { + mockRouteGroupRepository.save(group); + return REDIRECT_TO_LIST; + } catch (Exception e) { + bindingResult.rejectValue("id", "error.save.failed", + "Failed to save group: " + e.getMessage()); + model.addAttribute(GROUP, group); + return GROUP_EDIT; + } + } + + @PostMapping("/update.do") + @Secured("ROLE_MANAGE_ROUTE") + public String update(@RequestParam("id") String id, + @Valid @ModelAttribute(GROUP) MockRouteGroup group, + BindingResult bindingResult, + ModelMap model) { + if (bindingResult.hasErrors()) { + model.addAttribute(GROUP, group); + return GROUP_EDIT; + } + + try { + MockRouteGroup existingGroup = mockRouteGroupRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Group not found")); + existingGroup.setName(group.getName()); + mockRouteGroupRepository.save(existingGroup); + return REDIRECT_TO_LIST; + } catch (Exception e) { + bindingResult.rejectValue("name", "error.update.failed", + "Failed to update group: " + e.getMessage()); + model.addAttribute(GROUP, group); + return GROUP_EDIT; + } + } + + @PostMapping("/delete.do") + @Secured("ROLE_MANAGE_ROUTE") + public String delete(@RequestParam("checkedIdForDel") List ids) { + try { + mockRouteGroupRepository.deleteAllById(ids); + } catch (Exception e) { + // Log error but continue + } + return REDIRECT_TO_LIST; + } +} diff --git a/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupRestController.java b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupRestController.java new file mode 100644 index 0000000..34d8053 --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupRestController.java @@ -0,0 +1,119 @@ +package com.eactive.testmaster.api.controller; + +import com.eactive.testmaster.api.dto.MockRouteGroupSearch; +import com.eactive.testmaster.api.entity.MockRouteGroup; +import com.eactive.testmaster.api.repository.MockRouteGroupRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/mock-route-groups") +public class MockRouteGroupRestController { + + private final MockRouteGroupRepository mockRouteGroupRepository; + private final MockRouteGroupValidator mockRouteGroupValidator; + + public MockRouteGroupRestController(MockRouteGroupRepository mockRouteGroupRepository, + MockRouteGroupValidator mockRouteGroupValidator) { + this.mockRouteGroupRepository = mockRouteGroupRepository; + this.mockRouteGroupValidator = mockRouteGroupValidator; + } + + @GetMapping + public ResponseEntity> listGroups( + @RequestParam(required = false) String name, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + MockRouteGroupSearch search = new MockRouteGroupSearch(); + search.setName(name); + + Pageable pageable = PageRequest.of(page, size); + Page groups = mockRouteGroupRepository.findAll( + search.buildSpecification(), + pageable + ); + + return ResponseEntity.ok(groups); + } + + @GetMapping("/{id}") + public ResponseEntity getGroup(@PathVariable String id) { + return mockRouteGroupRepository.findById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public ResponseEntity createGroup(@RequestBody MockRouteGroup group) { + // Validate using the custom validator + Errors errors = new BeanPropertyBindingResult(group, "group"); + mockRouteGroupValidator.validate(group, errors); + + if (errors.hasErrors()) { + Map validationErrors = new HashMap<>(); + errors.getFieldErrors().forEach(error -> + validationErrors.put(error.getField(), error.getDefaultMessage()) + ); + return ResponseEntity.badRequest().body(validationErrors); + } + + try { + MockRouteGroup saved = mockRouteGroupRepository.save(group); + return ResponseEntity.ok(saved); + } catch (Exception e) { + return ResponseEntity.badRequest() + .body("Failed to create group: " + e.getMessage()); + } + } + + @PostMapping("/update/{id}") + public ResponseEntity updateGroup( + @PathVariable String id, @RequestBody MockRouteGroup group) { + + // Validate using the custom validator + Errors errors = new BeanPropertyBindingResult(group, "group"); + mockRouteGroupValidator.validate(group, errors); + + if (errors.hasErrors()) { + Map validationErrors = new HashMap<>(); + errors.getFieldErrors().forEach(error -> + validationErrors.put(error.getField(), error.getDefaultMessage()) + ); + return ResponseEntity.badRequest().body(validationErrors); + } + + try { + return mockRouteGroupRepository.findById(id) + .map(existingGroup -> { + existingGroup.setName(group.getName()); + MockRouteGroup updated = mockRouteGroupRepository.save(existingGroup); + return ResponseEntity.ok(updated); + }) + .orElse(ResponseEntity.notFound().build()); + } catch (Exception e) { + return ResponseEntity.badRequest() + .body("Failed to update group: " + e.getMessage()); + } + } + + @PostMapping("/delete") + public ResponseEntity deleteGroups(@RequestBody List ids) { + try { + mockRouteGroupRepository.deleteAllById(ids); + return ResponseEntity.ok().build(); + } catch (Exception e) { + return ResponseEntity.badRequest().build(); + } + } +} diff --git a/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupValidator.java b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupValidator.java new file mode 100644 index 0000000..38096fd --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/controller/MockRouteGroupValidator.java @@ -0,0 +1,49 @@ +package com.eactive.testmaster.api.controller; + +import com.eactive.testmaster.api.entity.MockRouteGroup; +import com.eactive.testmaster.api.repository.MockRouteGroupRepository; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +public class MockRouteGroupValidator implements Validator { + + private final MockRouteGroupRepository mockRouteGroupRepository; + + public MockRouteGroupValidator(MockRouteGroupRepository mockRouteGroupRepository) { + this.mockRouteGroupRepository = mockRouteGroupRepository; + } + + @Override + public boolean supports(Class clazz) { + return MockRouteGroup.class.equals(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + MockRouteGroup group = (MockRouteGroup) target; + + // Validate ID + if (group.getId() == null || group.getId().trim().isEmpty()) { + errors.rejectValue("id", "error.id.required", "Group ID is required"); + } else if (!group.getId().matches("^[a-zA-Z0-9_-]+$")) { + errors.rejectValue("id", "error.id.invalid", "Group ID must contain only letters, numbers, underscores, and hyphens"); + } else if (group.getId().length() > 50) { + errors.rejectValue("id", "error.id.length", "Group ID must not exceed 50 characters"); + } + + // Validate Name + if (group.getName() == null || group.getName().trim().isEmpty()) { + errors.rejectValue("name", "error.name.required", "Group name is required"); + } else if (group.getName().length() > 100) { + errors.rejectValue("name", "error.name.length", "Group name must not exceed 100 characters"); + } + + // Check for duplicate ID on creation + if (group.getId() != null && !errors.hasFieldErrors("id")) { + mockRouteGroupRepository.findById(group.getId()) + .ifPresent(existingGroup -> errors.rejectValue("id", "error.id.duplicate", "Group ID already exists")); + } + } +} diff --git a/src/main/java/com/eactive/testmaster/api/controller/MockRouteMgmtController.java b/src/main/java/com/eactive/testmaster/api/controller/MockRouteMgmtController.java index f0f6a00..617b2f9 100644 --- a/src/main/java/com/eactive/testmaster/api/controller/MockRouteMgmtController.java +++ b/src/main/java/com/eactive/testmaster/api/controller/MockRouteMgmtController.java @@ -6,6 +6,7 @@ import com.eactive.testmaster.api.dto.MockRouteSearch; import com.eactive.testmaster.api.dto.MockRouteUpdateDTO; import com.eactive.testmaster.api.entity.MockRoute; import com.eactive.testmaster.api.mapper.MockRouteMapper; +import com.eactive.testmaster.api.repository.MockRouteGroupRepository; import com.eactive.testmaster.api.service.MockRouteService; import java.util.List; import javax.annotation.PostConstruct; @@ -43,11 +44,14 @@ public class MockRouteMgmtController { private final MockApiHandlerMapping mockApiHandlerMapping; - public MockRouteMgmtController(MockRouteService mockRouteService, Validator validator, MockRouteMapper mockRouteMapper, MockApiHandlerMapping mockApiHandlerMapping) { + private final MockRouteGroupRepository mockRouteGroupRepository; + + public MockRouteMgmtController(MockRouteService mockRouteService, Validator validator, MockRouteMapper mockRouteMapper, MockApiHandlerMapping mockApiHandlerMapping, MockRouteGroupRepository mockRouteGroupRepository) { this.mockRouteService = mockRouteService; this.validator = validator; this.mockRouteMapper = mockRouteMapper; this.mockApiHandlerMapping = mockApiHandlerMapping; + this.mockRouteGroupRepository = mockRouteGroupRepository; } public static final String MOCK_ROUTE_EDIT = "page/routes/mockRouteEdit"; @@ -61,6 +65,7 @@ public class MockRouteMgmtController { mav.addObject("page", page); mav.addObject("pageable", pageable); mav.addObject("modelSearch", mockRouteSearch); + mav.addObject("groupList", mockRouteGroupRepository.findAll()); return mav; } @@ -74,6 +79,7 @@ public class MockRouteMgmtController { defaultResponse.setDefaultResponse("true"); defaultRoute.getResponses().add(defaultResponse); mav.addObject(MOCK_ROUTE, defaultRoute); + mav.addObject("groupList", mockRouteGroupRepository.findAll()); return mav; } @@ -84,7 +90,7 @@ public class MockRouteMgmtController { ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT); MockRoute found = mockRouteService.findById(id); mav.addObject(MOCK_ROUTE, mockRouteMapper.map(found)); - + mav.addObject("groupList", mockRouteGroupRepository.findAll()); return mav; } @@ -97,6 +103,7 @@ public class MockRouteMgmtController { MockRouteRegisterDTO dto = mockRouteMapper.map(found); dto.setId(null); mav.addObject(MOCK_ROUTE, dto); + mav.addObject("groupList", mockRouteGroupRepository.findAll()); return mav; } diff --git a/src/main/java/com/eactive/testmaster/api/dto/MockRouteGroupSearch.java b/src/main/java/com/eactive/testmaster/api/dto/MockRouteGroupSearch.java new file mode 100644 index 0000000..3380c00 --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/dto/MockRouteGroupSearch.java @@ -0,0 +1,41 @@ +package com.eactive.testmaster.api.dto; + +import com.eactive.testmaster.common.search.BaseSearch; +import com.eactive.testmaster.common.search.ColumnSearchModel; +import com.eactive.testmaster.common.search.SearchCondition; +import com.eactive.testmaster.common.search.SearchModel; +import com.eactive.testmaster.api.entity.MockRouteGroup; + +import java.util.ArrayList; +import java.util.List; + +public class MockRouteGroupSearch implements BaseSearch { + + private String id; + private String name; + + @Override + public List buildSearchCondition() { + List models = new ArrayList<>(); + models.add(new ColumnSearchModel("id", SearchCondition.LIKE, id)); + models.add(new ColumnSearchModel("name", SearchCondition.LIKE, name)); + return models; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + diff --git a/src/main/java/com/eactive/testmaster/api/dto/MockRouteRegisterDTO.java b/src/main/java/com/eactive/testmaster/api/dto/MockRouteRegisterDTO.java index 2339c87..32b6e0b 100644 --- a/src/main/java/com/eactive/testmaster/api/dto/MockRouteRegisterDTO.java +++ b/src/main/java/com/eactive/testmaster/api/dto/MockRouteRegisterDTO.java @@ -20,6 +20,8 @@ public class MockRouteRegisterDTO { @NotEmpty private String name; + private String group; + private String description; private String requestScript; @@ -82,4 +84,12 @@ public class MockRouteRegisterDTO { public void setRequestScript(String requestScript) { this.requestScript = requestScript; } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } } diff --git a/src/main/java/com/eactive/testmaster/api/dto/MockRouteSearch.java b/src/main/java/com/eactive/testmaster/api/dto/MockRouteSearch.java index b852b40..5e1bac0 100644 --- a/src/main/java/com/eactive/testmaster/api/dto/MockRouteSearch.java +++ b/src/main/java/com/eactive/testmaster/api/dto/MockRouteSearch.java @@ -12,10 +12,13 @@ public class MockRouteSearch implements BaseSearch { private String name; + private String group; + @Override public List buildSearchCondition() { List models = new ArrayList<>(); models.add(new ColumnSearchModel("name", SearchCondition.LIKE, name)); + models.add(new ColumnSearchModel("group", SearchCondition.EQUAL, group)); return models; } @@ -27,4 +30,11 @@ public class MockRouteSearch implements BaseSearch { this.name = name; } + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } } diff --git a/src/main/java/com/eactive/testmaster/api/dto/MockRouteUpdateDTO.java b/src/main/java/com/eactive/testmaster/api/dto/MockRouteUpdateDTO.java index ee42882..0465ee5 100644 --- a/src/main/java/com/eactive/testmaster/api/dto/MockRouteUpdateDTO.java +++ b/src/main/java/com/eactive/testmaster/api/dto/MockRouteUpdateDTO.java @@ -22,6 +22,7 @@ public class MockRouteUpdateDTO { private String requestScript; + private String group; @Size(min = 1, message = "Response must have at least one item") private List responses = new ArrayList<>(); @@ -81,4 +82,12 @@ public class MockRouteUpdateDTO { public void setRequestScript(String requestScript) { this.requestScript = requestScript; } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } } diff --git a/src/main/java/com/eactive/testmaster/api/entity/MockRoute.java b/src/main/java/com/eactive/testmaster/api/entity/MockRoute.java index d9dd685..6ca0ed5 100644 --- a/src/main/java/com/eactive/testmaster/api/entity/MockRoute.java +++ b/src/main/java/com/eactive/testmaster/api/entity/MockRoute.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonManagedReference; import java.util.ArrayList; import java.util.List; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -30,6 +31,9 @@ public class MockRoute { private String description; + @Column(name = "MOCK_GROUP") + private String group; + @Lob private String requestScript; @@ -85,7 +89,6 @@ public class MockRoute { this.description = description; } - public String getRequestScript() { return requestScript; } @@ -93,4 +96,12 @@ public class MockRoute { public void setRequestScript(String requestScript) { this.requestScript = requestScript; } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } } diff --git a/src/main/java/com/eactive/testmaster/api/entity/MockRouteGroup.java b/src/main/java/com/eactive/testmaster/api/entity/MockRouteGroup.java new file mode 100644 index 0000000..c8c2f15 --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/entity/MockRouteGroup.java @@ -0,0 +1,32 @@ +package com.eactive.testmaster.api.entity; + + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "MOCK_ROUTE_GROUP") +public class MockRouteGroup { + + @Id + private String id; + + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/eactive/testmaster/api/repository/MockRouteGroupRepository.java b/src/main/java/com/eactive/testmaster/api/repository/MockRouteGroupRepository.java new file mode 100644 index 0000000..d44f4ce --- /dev/null +++ b/src/main/java/com/eactive/testmaster/api/repository/MockRouteGroupRepository.java @@ -0,0 +1,9 @@ +package com.eactive.testmaster.api.repository; + +import com.eactive.testmaster.api.entity.MockRouteGroup; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface MockRouteGroupRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/src/main/java/com/eactive/testmaster/api/service/MockRouteService.java b/src/main/java/com/eactive/testmaster/api/service/MockRouteService.java index 6bf3354..4bfa093 100644 --- a/src/main/java/com/eactive/testmaster/api/service/MockRouteService.java +++ b/src/main/java/com/eactive/testmaster/api/service/MockRouteService.java @@ -75,6 +75,7 @@ public class MockRouteService { route.setName(updated.getName()); route.setDescription(updated.getDescription()); route.setRequestScript(updated.getRequestScript()); + route.setGroup(updated.getGroup()); if (!route.getPathPattern().startsWith("/")) { route.setPathPattern("/" + route.getPathPattern()); } diff --git a/src/main/resources/templates/page/routes/groupEdit.html b/src/main/resources/templates/page/routes/groupEdit.html new file mode 100644 index 0000000..c667b1e --- /dev/null +++ b/src/main/resources/templates/page/routes/groupEdit.html @@ -0,0 +1,77 @@ + + + + +
+
+
+
+
+

API 그룹 등록

+

API 그룹 수정

+
+
+ + + +
+ + + +
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+
+ + + + + + diff --git a/src/main/resources/templates/page/routes/groupList.html b/src/main/resources/templates/page/routes/groupList.html new file mode 100644 index 0000000..11ae264 --- /dev/null +++ b/src/main/resources/templates/page/routes/groupList.html @@ -0,0 +1,145 @@ + + + + +
+
+
+
+
API 그룹 목록
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
번호
+
+ +
+
ID
이름
비고
+
+ + +
+
+ + + + +
+ +
+
+
+
+ +
+
+
+ + + + + + diff --git a/src/main/resources/templates/page/routes/mockRouteEdit.html b/src/main/resources/templates/page/routes/mockRouteEdit.html index e9b4b28..810b831 100644 --- a/src/main/resources/templates/page/routes/mockRouteEdit.html +++ b/src/main/resources/templates/page/routes/mockRouteEdit.html @@ -36,6 +36,18 @@
+
+ + +
+
+ +
+
+ +
@@ -43,6 +58,7 @@ + @@ -58,6 +74,7 @@ + @@ -73,6 +90,14 @@ +
group
method
path
이름
+
+ 그룹 미지정 + +
+
@@ -138,6 +163,7 @@ let fnSearch = document.querySelector(".btn_search"); let fnDelete = document.querySelector(".btn_delete"); let fnAdd = document.querySelector(".btn_add"); + let fnAddGroup = document.querySelector(".btn_add_group"); fnSearch.addEventListener("click", function () { document.searchForm.page.value = 1; @@ -176,6 +202,10 @@ fnAdd.addEventListener("click", function () { location.href = '[[@{/mgmt/routes/create_view.do}]]'; }); + + fnAddGroup.addEventListener("click", function () { + location.href = '[[@{/mgmt/routes/group/list_view.do}]]'; + }); })