mock route 등록/수정 로직 변경, 복제 기능 추가

This commit is contained in:
현성필
2023-08-23 16:13:16 +09:00
parent 613e64de6d
commit d2f6d1c846
10 changed files with 215 additions and 24 deletions
@@ -1,8 +1,9 @@
package com.eactive.httpmockserver.api.controller;
import com.eactive.httpmockserver.api.dto.MockResponseDTO;
import com.eactive.httpmockserver.api.dto.MockRouteDTO;
import com.eactive.httpmockserver.api.dto.MockRouteRegisterDTO;
import com.eactive.httpmockserver.api.dto.MockRouteSearch;
import com.eactive.httpmockserver.api.dto.MockRouteUpdateDTO;
import com.eactive.httpmockserver.api.entity.MockRoute;
import com.eactive.httpmockserver.api.mapper.MockRouteMapper;
import com.eactive.httpmockserver.api.service.MockRouteService;
@@ -14,6 +15,7 @@ import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.PostConstruct;
import java.util.List;
@@ -48,37 +50,51 @@ public class MockRouteMgmtController {
public static final String MOCK_ROUTE_LIST_VIEW = "redirect:/mgmt/routes/list_view.do";
@GetMapping("/list_view.do")
public String listView(@ModelAttribute("mockRouteSearch") MockRouteSearch mockRouteSearch, ModelMap model, Pageable pageable) {
public ModelAndView listView(@ModelAttribute("mockRouteSearch") MockRouteSearch mockRouteSearch, Pageable pageable) {
ModelAndView mav = new ModelAndView("page/routes/mockRouteList");
Page<MockRoute> page = mockRouteService.findAll(mockRouteSearch.buildSpecification(), pageable);
model.addAttribute("page", page);
model.addAttribute("pageable", pageable);
model.addAttribute("modelSearch", mockRouteSearch);
mav.addObject("page", page);
mav.addObject("pageable", pageable);
mav.addObject("modelSearch", mockRouteSearch);
return "page/routes/mockRouteList";
return mav;
}
@GetMapping("/create_view.do")
public String createView(ModelMap model) {
MockRouteDTO defaultRoute = new MockRouteDTO();
public ModelAndView createView() {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRouteRegisterDTO defaultRoute = new MockRouteRegisterDTO();
MockResponseDTO defaultResponse = new MockResponseDTO();
defaultResponse.setDefaultResponse("true");
defaultRoute.getResponses().add(defaultResponse);
model.addAttribute(MOCK_ROUTE, defaultRoute);
return MOCK_ROUTE_EDIT;
mav.addObject(MOCK_ROUTE, defaultRoute);
return mav;
}
@GetMapping("/update_view.do")
public String updateView(ModelMap model, @RequestParam("id") Long id) {
public ModelAndView updateView(@RequestParam("id") Long id) {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRoute found = mockRouteService.findById(id);
mav.addObject(MOCK_ROUTE, mockRouteMapper.map(found));
model.addAttribute(MOCK_ROUTE, mockRouteMapper.map(found));
return MOCK_ROUTE_EDIT;
return mav;
}
@PostMapping("/clone_view.do")
public ModelAndView cloneView(@RequestParam("id") Long id) {
ModelAndView mav = new ModelAndView(MOCK_ROUTE_EDIT);
MockRoute found = mockRouteService.findById(id);
MockRouteRegisterDTO dto = mockRouteMapper.map(found);
dto.setId(null);
mav.addObject(MOCK_ROUTE, dto);
return mav;
}
@PostMapping("/register.do")
public String register(@ModelAttribute("mockRoute") MockRouteDTO dto, BindingResult bindingResult, ModelMap model) {
public String register(@ModelAttribute("mockRoute") MockRouteRegisterDTO dto, BindingResult bindingResult, ModelMap model) {
if (dto == null) {
return "redirect:/mgmt/routes/create_view.do";
@@ -101,7 +117,7 @@ public class MockRouteMgmtController {
@PostMapping("/update.do")
public String update(@RequestParam("id") String id,
@ModelAttribute("mockRoute") MockRouteDTO dto,
@ModelAttribute("mockRoute") MockRouteUpdateDTO dto,
BindingResult bindingResult, ModelMap model) {
if (StringUtils.isEmpty(id)) {
@@ -0,0 +1,76 @@
package com.eactive.httpmockserver.api.dto;
import com.eactive.httpmockserver.common.validator.UniqueRoute;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
@UniqueRoute(path = "pathPattern", method = "method")
public class MockRouteRegisterDTO {
private Long id;
@NotEmpty
private String method;
@NotEmpty
private String pathPattern;
@NotEmpty
private String name;
private String description;
@Size(min = 1, message = "Response must have at least one item")
private List<MockResponseDTO> responses = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPathPattern() {
return pathPattern;
}
public void setPathPattern(String pathPattern) {
this.pathPattern = pathPattern;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<MockResponseDTO> getResponses() {
return responses;
}
public void setResponses(List<MockResponseDTO> responses) {
this.responses = responses;
}
}
@@ -1,11 +1,13 @@
package com.eactive.httpmockserver.api.dto;
import com.eactive.httpmockserver.common.validator.UniqueRoute;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
public class MockRouteDTO {
public class MockRouteUpdateDTO {
private Long id;
@@ -2,7 +2,8 @@ package com.eactive.httpmockserver.api.mapper;
import com.eactive.httpmockserver.api.dto.MockHeaderDTO;
import com.eactive.httpmockserver.api.dto.MockResponseDTO;
import com.eactive.httpmockserver.api.dto.MockRouteDTO;
import com.eactive.httpmockserver.api.dto.MockRouteRegisterDTO;
import com.eactive.httpmockserver.api.dto.MockRouteUpdateDTO;
import com.eactive.httpmockserver.api.entity.MockResponse;
import com.eactive.httpmockserver.api.entity.MockRoute;
import com.eactive.httpmockserver.common.mapper.CommonMapper;
@@ -34,10 +35,11 @@ public abstract class MockRouteMapper {
XML_ESCAPE_MAP.put("&apos;", "'");
}
public abstract MockRoute map(MockRouteDTO dto);
public abstract MockRoute map(MockRouteRegisterDTO dto);
public abstract MockRoute map(MockRouteUpdateDTO dto);
public abstract MockRouteDTO map(MockRoute entity);
public abstract MockRouteRegisterDTO map(MockRoute entity);
@Mapping(target = "headers", source = "headers", qualifiedByName = "mapHeadersToMap")
@Mapping(target = "defaultResponse", source = "defaultResponse", qualifiedByName = "mapEntityDefaultResponse")
@@ -5,4 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface MockRouteRepository extends JpaRepository<MockRoute, Long>, JpaSpecificationExecutor<MockRoute> {
long countByMethodAndPathPattern(String method, String pathPattern);
}
@@ -74,8 +74,8 @@ public class MockRouteService {
route.setName(updated.getName());
route.setDescription(updated.getDescription());
route.setMethod(updated.getMethod());
route.setPathPattern(updated.getPathPattern());
// route.setMethod(updated.getMethod());
// route.setPathPattern(updated.getPathPattern());
if (!route.getPathPattern().startsWith("/")) {
route.setPathPattern("/" + route.getPathPattern());
}
@@ -0,0 +1,22 @@
package com.eactive.httpmockserver.common.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Constraint(validatedBy = UniqueRouteValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UniqueRoute {
String message() default "등록된 API가 존재합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String path();
String method();
}
@@ -0,0 +1,53 @@
package com.eactive.httpmockserver.common.validator;
import com.eactive.httpmockserver.api.repository.MockRouteRepository;
import com.eactive.httpmockserver.common.exception.UserNotFoundException;
import com.eactive.httpmockserver.common.util.ApplicationContextUtil;
import com.eactive.httpmockserver.user.service.UserService;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UniqueRouteValidator implements ConstraintValidator<UniqueRoute, Object> {
private String method;
private String path;
@Override
public void initialize(UniqueRoute constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
this.method = constraintAnnotation.method();
this.path = constraintAnnotation.path();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
String methodValue;
String pathValue;
try {
methodValue = (String) PropertyUtils.getProperty(value, this.method);
pathValue = (String) PropertyUtils.getProperty(value, this.path);
} catch (Exception e) {
return false;
}
MockRouteRepository mockRouteRepository = ApplicationContextUtil.getContext().getBean(MockRouteRepository.class);
long found = mockRouteRepository.countByMethodAndPathPattern(methodValue, pathValue);
String message = "등록된 API가 존재합니다.";
if (found > 0L){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(this.path)
.addConstraintViolation();
return false;
}
return true;
}
}
@@ -29,7 +29,7 @@
</div>
<div class="d-flex align-items-center mt-2">
<div class="form-floating col-md-1 me-2">
<select name="method" class="form-select"
<select name="method" class="form-select" th:readonly="${mockRoute.id != null}"
th:classappend="${#fields.hasErrors('method')}? 'is-invalid'" id="method">
<option th:selected="${mockRoute.method == 'POST'}" value="POST">POST</option>
<option th:selected="${mockRoute.method == 'GET'}" value="GET">GET</option>
@@ -45,11 +45,18 @@
</div>
<div class="form-floating col-md">
<input type="text" name="pathPattern" class="form-control" id="pathPattern"
th:field="*{pathPattern}"
th:field="*{pathPattern}" th:readonly="${mockRoute.id != null}"
placeholder="서버에 노출 되는 경로 (/부터 입력)" required>
<label for="pathPattern" class="must">Path</label>
</div>
</div>
<div class="form-floating col-md">
<input type="hidden" th:classappend="${#fields.hasGlobalErrors() || #fields.hasErrors('pathPattern')}? 'is-invalid'"
placeholder="서버에 노출 되는 경로 (/부터 입력)" required th:readonly="${mockRoute.id != null}">
<th:block th:each="err : ${#fields.errors('pathPattern')}">
<div th:text="${err}" class="invalid-feedback"></div>
</th:block>
</div>
<div id="responses" class="mt-1">
<div class="row" th:each="response, status: *{responses}">
<input type="number" th:field="*{responses[__${status.index}__].statusCode}" required/>
@@ -90,6 +90,9 @@
<button type="button" class="btn btn-primary btn-sm me-2" th:onclick="fnSelectItem([[${row.id}]])" th:value="#{button.update}"><i class="fa-sharp fa-solid fa-edit"></i>
[[#{button.update}]]
</button>
<button type="button" class="btn btn-primary btn-sm me-2" th:onclick="fnCloneItem([[${row.id}]])"><i class="fa-sharp fa-solid fa-copy"></i>
복제
</button>
</div>
</td>
</tr>
@@ -100,6 +103,9 @@
</tbody>
</table>
</form>
<form name="cloneForm" th:action="@{/mgmt/routes/clone_view.do}" method="post" style="display: none">
<input type="hidden" name="id">
</form>
</div>
<div class="card-footer">
<div th:replace="~{fragment/pagination :: pagination(jsFunction='fn_select_page')}"></div>
@@ -123,6 +129,11 @@
location.href = "[[@{/mgmt/routes/update_view.do}]]" + "?id=" + id;
}
function fnCloneItem(id){
document.cloneForm.id.value = id;
document.cloneForm.submit();
}
$(function () {
let fnSearch = document.querySelector(".btn_search");
let fnDelete = document.querySelector(".btn_delete");