This commit is contained in:
Rinjae
2025-08-07 15:36:26 +09:00
commit cc925e0475
1172 changed files with 142573 additions and 0 deletions
@@ -0,0 +1,17 @@
package com.eactive.apim.gateway.data;
import com.eactive.eai.data.entity.onl.authserver.ClientEntity;
import com.eactive.eai.data.jpa.BaseRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface ClientEntityRepository extends BaseRepository<ClientEntity, String> {
Page<ClientEntity> findAllByOrgid(Pageable pageable, String orgid);
List<ClientEntity> findAllByOrgid(String orgid);
Optional<ClientEntity> findByClientidAndOrgid(String clientid, String orgid);
}
@@ -0,0 +1,8 @@
package com.eactive.apim.gateway.data.apiservice.repository;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ApiServiceRepository extends JpaRepository<ApiGroup, String>, JpaSpecificationExecutor<ApiGroup> {
}
@@ -0,0 +1,8 @@
package com.eactive.apim.gateway.data.bizcode;
import com.eactive.eai.data.entity.onl.unifbwk.UnifBwkTp;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UnifBwkTpRepository extends JpaRepository<UnifBwkTp, String>, JpaSpecificationExecutor<UnifBwkTp>{
}
@@ -0,0 +1,8 @@
package com.eactive.apim.gateway.data.eaimessage;
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface EAIMessageRepository extends JpaRepository<EAIMessageEntity, String>, JpaSpecificationExecutor<EAIMessageEntity> {
}
@@ -0,0 +1,12 @@
package com.eactive.apim.gateway.data.standardmessageinfo;
import com.eactive.eai.data.entity.onl.stdmessage.StandardMessageInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import java.util.Optional;
public interface StandardMessageInfoRepository extends JpaRepository<StandardMessageInfo, String>,JpaSpecificationExecutor<StandardMessageInfo> {
Optional<StandardMessageInfo> findByEaisvcname(String eaiSvcName);
}
@@ -0,0 +1,35 @@
package com.eactive.apim.portal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {
UserDetailsServiceAutoConfiguration.class,
},
scanBasePackages = "com.eactive.apim.portal")
@EnableScheduling
@EnableConfigurationProperties
@ConfigurationPropertiesScan(basePackages = {"com.eactive.apim.portal"})
public class PortalApplication extends SpringBootServletInitializer {
private static final Logger portal_logger = LoggerFactory.getLogger(PortalApplication.class);
public static void main(String[] args) {
portal_logger.info("##### PortalApplication Start #####");
SpringApplication.run(new Class[]{PortalApplication.class}, args);
portal_logger.info("##### PortalApplication Ready #####");
}
}
@@ -0,0 +1,14 @@
package com.eactive.apim.portal.apps;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthCheckController {
@GetMapping(value = "/health", produces = "text/plain")
public String health() {
return "ok";
}
}
@@ -0,0 +1,64 @@
package com.eactive.apim.portal.apps.agreements.controller;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO;
import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/agreements")
public class AgreementsController {
public static final String TERMS_AGREEMENTS = "fragment/kbank/terms_agreements";
private final AgreementsFacade agreementsFacade;
@Autowired
public AgreementsController(AgreementsFacade agreementsFacade) {
this.agreementsFacade = agreementsFacade;
}
@GetMapping("/terms")
public String showTerms(@RequestParam(required = false) String tab,
@RequestParam(required = false) String publishedOn,
Model model) {
AgreementType type;
if ("privacy".equals(tab)) {
type = AgreementType.PRIVACY_POLICY;
} else {
type = AgreementType.TERMS_OF_USE;
}
List<AgreementsDTO> agreementsList = agreementsFacade.getAgreementsList(String.valueOf(type));
model.addAttribute("agreementsList", agreementsList);
AgreementsDTO selectedAgreement = publishedOn != null && !publishedOn.isEmpty() ?
findAgreementByDate(agreementsList, publishedOn) : agreementsList.get(0);
model.addAttribute("selectedAgreement", selectedAgreement);
model.addAttribute("selectedDate", publishedOn);
model.addAttribute("isTermsOfUse", type == AgreementType.TERMS_OF_USE);
model.addAttribute("isPrivacyPolicy", type == AgreementType.PRIVACY_POLICY);
model.addAttribute("agreementTitle", type.getDescription());
model.addAttribute("agreementType", type.getCode());
model.addAttribute("currentTab", tab != null ? tab : "terms");
return TERMS_AGREEMENTS;
}
private AgreementsDTO findAgreementByDate(List<AgreementsDTO> agreements, String publishedOn) {
return agreements.stream()
.filter(a -> publishedOn.equals(
a.getPublishedOn().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))))
.findFirst()
.orElse(agreements.get(0));
}
}
@@ -0,0 +1,43 @@
package com.eactive.apim.portal.apps.agreements.dto;
import com.eactive.apim.portal.agreements.entity.AgreementsStatus;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
public class AgreementsDTO {
/* pk */
private String agreementsType;
private int revision;
private String name;
private String contents;
private AgreementsStatus status;
/* 제정일 */
private LocalDateTime publishedOn;
/* 시행일 */
private LocalDateTime scheduledOn;
private String createdBy;
private LocalDateTime createdDate;
private String updatedBy;
private LocalDateTime updatedDate;
// 기본 생성자 추가
public AgreementsDTO() {
this.contents = ""; // null 방지를 위한 기본값 설정
this.name = "";
}
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.apps.agreements.mapper;
import com.eactive.apim.portal.agreements.entity.Agreements;
import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface AgreementsMapper {
AgreementsDTO map(Agreements entity);
List<AgreementsDTO> map(List<Agreements> agreementsList);
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.apps.agreements.service;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO;
import java.util.List;
public interface AgreementsFacade {
AgreementsDTO getAgreement(String agreementsType);
List<AgreementsDTO> getAgreementsList(String agreementsType);
void saveUserAgreements(String userId, AgreementType privacyCollectType);
void deleteUserAgreements(String userId);
}
@@ -0,0 +1,163 @@
package com.eactive.apim.portal.apps.agreements.service;
import com.eactive.apim.portal.agreements.entity.AgreementType;
import com.eactive.apim.portal.agreements.entity.Agreements;
import com.eactive.apim.portal.agreements.service.AgreementsService;
import com.eactive.apim.portal.apps.agreements.dto.AgreementsDTO;
import com.eactive.apim.portal.apps.agreements.mapper.AgreementsMapper;
import com.eactive.apim.portal.apps.user.service.PortalUserPrivacyAgreementService;
import com.eactive.apim.portal.common.exception.NotFoundException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AgreementsFacadeImpl implements AgreementsFacade {
private final AgreementsService agreementsService;
private final AgreementsMapper agreementsMapper;
private final PortalUserPrivacyAgreementService userAgreementService;
@Autowired
public AgreementsFacadeImpl(AgreementsService agreementsService,
AgreementsMapper agreementsMapper,
PortalUserPrivacyAgreementService userAgreementService) {
this.agreementsService = agreementsService;
this.agreementsMapper = agreementsMapper;
this.userAgreementService = userAgreementService;
}
@Override
@Transactional
public void saveUserAgreements(String userId, AgreementType privacyCollectType) {
LocalDateTime now = LocalDateTime.now();
// 1. 이용약관 처리
Optional<Agreements> termsOfUse = agreementsService
.findLastesBeforeDate(
AgreementType.TERMS_OF_USE,
now);
if (termsOfUse.isPresent()) {
userAgreementService.saveOrUpdateAgreements(
userId, // UUID 사용
String.valueOf(AgreementType.TERMS_OF_USE),
termsOfUse.get().getRevision()
);
} else {
throw new NotFoundException("현재 시행중인 이용약관이 없습니다.");
}
// 2. 개인정보수집동의서 처리
if (privacyCollectType != null && privacyCollectType.isPrivacyCollect()) {
Optional<Agreements> privacyCollect = agreementsService
.findLastesBeforeDate(
privacyCollectType,
now);
if (privacyCollect.isPresent()) {
userAgreementService.saveOrUpdateAgreements(
userId,
String.valueOf(privacyCollectType),
privacyCollect.get().getRevision()
);
} else {
throw new NotFoundException("현재 시행중인 개인정보수집동의서가 없습니다.");
}
}
}
// HTML 언이스케이프 처리
private AgreementsDTO convertToDTO(Agreements agreement) {
AgreementsDTO dto = agreementsMapper.map(agreement);
if (dto.getContents() != null) {
dto.setContents(StringEscapeUtils.unescapeHtml4(dto.getContents()));
}
return dto;
}
@Override
public AgreementsDTO getAgreement(String agreementsTypeCode) {
LocalDateTime date = LocalDateTime.now();
AgreementType agreementType = AgreementType.fromCode(agreementsTypeCode);
// 1. 현재 날짜 이전의 가장 최근 약관 조회
Optional<Agreements> beforeToday = agreementsService
.findLastesBeforeDate(agreementType, date);
if (beforeToday.isPresent()) {
return convertToDTO(beforeToday.get());
}
// 2. 현재 날짜 이후의 가장 가까운 약관 조회
Optional<Agreements> afterToday = agreementsService
.findEarliesAfterDate(String.valueOf(agreementType), date);
if (afterToday.isPresent()) {
return convertToDTO(afterToday.get());
}
// 3. 약관이 없는 경우 기본 약관
return createDefaultAgreement(String.valueOf(agreementType));
}
private AgreementsDTO createDefaultAgreement(String agreementType) {
AgreementsDTO defaultAgreement = new AgreementsDTO();
defaultAgreement.setAgreementsType(agreementType);
defaultAgreement.setRevision(0);
switch (agreementType) {
case "TERMS_OF_USE":
defaultAgreement.setName("이용약관");
defaultAgreement.setContents("현재 이용약관을 불러올 수 없습니다. 잠시 후 다시 시도해 주세요.");
break;
case "PRIVACY_POLICY":
defaultAgreement.setName("개인정보처리방침");
defaultAgreement.setContents("현재 개인정보처리방침을 불러올 수 없습니다. 잠시 후 다시 시도해 주세요.");
break;
case "PRIVACY_COLLECT_IND":
defaultAgreement.setName("개인용 개인정보수집동의서");
defaultAgreement.setContents("현재 개인용 개인정보수집동의서를 불러올 수 없습니다. 잠시 후 다시 시도해 주세요.");
break;
case "PRIVACY_COLLECT_ORG":
defaultAgreement.setName("법인용 개인정보수집동의서");
defaultAgreement.setContents("현재 법인용 개인정보수집동의서를 불러올 수 없습니다. 잠시 후 다시 시도해 주세요.");
break;
default:
defaultAgreement.setName("약관");
defaultAgreement.setContents("약관 내용을 불러올 수 없습니다. 잠시 후 다시 시도해 주세요.");
break;
}
return defaultAgreement;
}
@Override
public List<AgreementsDTO> getAgreementsList(String agreementsType) {
AgreementType agreementTypeCode = AgreementType.fromCode(agreementsType);
List<Agreements> agreementsList = agreementsService.findAllAgreementsSortPublishedOn(agreementTypeCode);
if (agreementsList == null || agreementsList.isEmpty()) {
return Collections.singletonList(createDefaultAgreement(String.valueOf(agreementTypeCode)));
}
return agreementsList.stream()
.map(agreement -> {
AgreementsDTO dto = agreementsMapper.map(agreement);
// HTML 언이스케이프 처리
if (dto.getContents() != null) {
dto.setContents(StringEscapeUtils.unescapeHtml4(dto.getContents()));
}
return dto;
})
.collect(Collectors.toList());
}
@Override
@Transactional
public void deleteUserAgreements(String userId) {
userAgreementService.deleteUserAgreements(userId);
}
}
@@ -0,0 +1,106 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/apis")
@RequiredArgsConstructor
public class ApiAjaxController {
private final static String STG_SERVER = "STG";
private final ApiService apiService;
private final ApiServiceService apiServiceService;
private final AppServiceFacade appServiceFacade;
public Map<String, ApiServiceDTO> getMainIconsFromServiceDtos(List<ApiServiceDTO> apiServices) {
Map<String, ApiServiceDTO> mainIconsMap = new HashMap<>();
apiServices.forEach(service -> {
service.getApiGroupApiList().forEach(api -> {
String apiId = api.getApiId();
mainIconsMap.put(apiId, service);
});
});
return mainIconsMap;
}
@GetMapping("/for_request")
@Secured("ROLE_API_KEY_REQUEST")
public List<ApiSpecInfoDto> apiRequest(@ModelAttribute ApiGroupSearch search) {
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
List<ApiSpecInfoDto> apiSpecInfoDtos = apiService.findApis();
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
if (serviceDTO != null) {
String mainIcon = serviceDTO.getMainIcon();
String service = serviceDTO.getGroupName();
spec.setMainIcon(mainIcon);
spec.setService(service);
}
}
return apiSpecInfoDtos;
}
@GetMapping("/for_request_prod")
@Secured("ROLE_API_KEY_REQUEST")
public List<ApiSpecInfoDto> apiRequestProd(@ModelAttribute ApiGroupSearch search) {
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
Map<String, ApiServiceDTO> mainIconsMap = getMainIconsFromServiceDtos(apiServices);
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
List<ApiServiceApiDTO> apis = apikeys.stream()
.flatMap(client -> client.getApiList().stream()) // flatten all apiLists into a single stream of ApiServiceApiDTO
.distinct() // remove duplicates
.collect(Collectors.toList()); // collect to List
List<ApiSpecInfoDto> apiSpecInfoDtos = new ArrayList<>();
apis.forEach(api -> {
ApiSpecInfoDto specInfoDto = new ApiSpecInfoDto();
specInfoDto.setApiId(api.getApiId());
specInfoDto.setApiName(api.getApiDesc());
apiSpecInfoDtos.add(specInfoDto);
});
for (ApiSpecInfoDto spec : apiSpecInfoDtos) {
ApiServiceDTO serviceDTO = mainIconsMap.get(spec.getApiId());
if (serviceDTO != null) {
String mainIcon = serviceDTO.getMainIcon();
String service = serviceDTO.getGroupName();
spec.setMainIcon(mainIcon);
spec.setService(service);
}
}
return apiSpecInfoDtos;
}
}
@@ -0,0 +1,144 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import java.util.*;
import com.eactive.apim.portal.common.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/apis")
@RequiredArgsConstructor
public class ApiController {
private static final String NOT_FOUND_MESSAGE = "찾을 수 없습니다.";
private final ApiService apiService;
private final ApiServiceService apiServiceService;
private final ApiSearchFacade apiSearchFacade;
private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec";
private static final String DEFAULT_TOKEN_API_NAME = "인증";
@GetMapping("/detail")
public String apidetail(@RequestParam(value = "id", required = false) String id, ModelMap model) {
if( id ==null) {
return "redirect:/apis/common";
}
ApiSpecInfoDto api = apiService.selectDetail(id);
if(api==null){
throw new NotFoundException(NOT_FOUND_MESSAGE);
}
model.addAttribute("apiSpecInfo", api);
return "apps/apis/mainApiDetail";
}
@GetMapping
public String apiList(@ModelAttribute ApiGroupSearch search, Model model) {
Map<String, Object> searchResult = apiSearchFacade.searchApis(search);
model.addAttribute("search", search);
model.addAttribute("services", searchResult.get("services"));
model.addAttribute("apis", searchResult.get("apis"));
model.addAttribute("totalApiCount", searchResult.get("totalApiCount"));
model.addAttribute("selectedApiCount", searchResult.get("selectedApiCount"));
model.addAttribute("selected", search.getGroupIds().size() >0 ? search.getGroupIds().get(0) : "-1");
return "apps/apis/mainApiList";
}
@GetMapping("/testbed/api")
public String testbedByApi(@RequestParam(value = "id", required = false) String id, Model model) {
String selectedApiServiceName = "API 서비스 선택";
String selectedServiceId = "";
boolean idExists = false;
try {
if (id != null && !id.isEmpty()) {
ApiServiceDTO apiService = apiServiceService.findApiServiceByApiId(id);
if (apiService != null) {
selectedApiServiceName = apiService.getGroupName();
selectedServiceId = apiService.getId();
idExists = true;
setupTestbedModel(model, selectedServiceId, selectedApiServiceName, idExists);
}
}
} catch (Exception e) {
model.addAttribute("errorMessage", "API 정보를 조회하는 중 오류가 발생했습니다.");
}
setupTestbedModel(model, selectedServiceId, selectedApiServiceName, idExists);
return "apps/apis/mainTestbed";
}
@GetMapping("/testbed")
public String testbedByApiService(@RequestParam(value = "id", required = false) String id, Model model) {
String selectedApiServiceName = "API 서비스 선택";
boolean idExists = false;
try {
if (id != null && !id.isEmpty()) {
List<ApiServiceDTO> apiServiceDtoList = apiServiceService.searchApiGroupsForLnb();
Optional<ApiServiceDTO> selectedApi = apiServiceDtoList.stream()
.filter(api -> api.getId().equals(id))
.findFirst();
if (selectedApi.isPresent()) {
selectedApiServiceName = selectedApi.get().getGroupName();
idExists = true;
}
}
} catch (Exception e) {
model.addAttribute("errorMessage", "API 서비스 정보를 조회하는 중 오류가 발생했습니다.");
}
setupTestbedModel(model, id, selectedApiServiceName, idExists);
return "apps/apis/mainTestbed";
}
private void setupTestbedModel(Model model, String id, String selectedApiServiceName, boolean idExists) {
List<ApiServiceDTO> apiServiceDtoList = apiServiceService.searchApiGroupsForLnb();
if (apiServiceDtoList == null) {
apiServiceDtoList = new ArrayList<>();
}
// OAuth API를 첫 번째 항목으로 추가
ApiServiceDTO oauthService = new ApiServiceDTO();
oauthService.setId(DEFAULT_TOKEN_API_ID);
oauthService.setGroupName(DEFAULT_TOKEN_API_NAME);
apiServiceDtoList.add(0, oauthService);
for (ApiServiceDTO apiServiceDTO : apiServiceDtoList) {
apiServiceDTO.setContentIcon(null);
apiServiceDTO.setMainIcon(null);
apiServiceDTO.setApiGroupApiList(null);
}
// id가 없는 경우 OAuth를 기본값으로 설정
if (!idExists) {
id = DEFAULT_TOKEN_API_ID;
selectedApiServiceName = DEFAULT_TOKEN_API_NAME;
idExists = true;
}
model.addAttribute("apiServices", apiServiceDtoList);
model.addAttribute("id", idExists ? id : "");
model.addAttribute("selectedApiName", selectedApiServiceName);
}
}
@@ -0,0 +1,34 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.common.pagerouter.PageHandler;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
@Component("apiHandler")
@RequiredArgsConstructor
public class ApiHandler implements PageHandler {
private final ApiServiceService apiServiceService;
@Override
public void handleRequest(HttpServletRequest httpRequest, Model model) {
ApiGroupSearch search = new ApiGroupSearch();
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
for (ApiServiceDTO apiServiceDTO : apiServices) {
ApiServiceDTO apiService = apiServiceService.getApiGroupById(apiServiceDTO.getId());
apiServiceDTO.getApiGroupApiList().clear();
apiServiceDTO.getApiGroupApiList().addAll(apiService.getApiGroupApiList());
}
model.addAttribute("apiServices", apiServices);
}
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apis.dto.ParsedRequestDTO;
import com.eactive.apim.portal.apps.apis.service.SampleCodeGenerator;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* Created by Sungpil Hyun
*/
@RestController
@RequestMapping("/api/sample_code")
public class SampleCodeGeneratorController {
private final SampleCodeGenerator sampleCodeGenerator;
private static final List<String> allowLang = Arrays.asList("java", "csharp", "python", "ecma5");
public SampleCodeGeneratorController(SampleCodeGenerator sampleCodeGenerator) {
this.sampleCodeGenerator = sampleCodeGenerator;
}
@PostMapping(value = "/{language}", consumes = "application/json", produces = "text/plain")
public ResponseEntity<String> createRoute(@PathVariable("language") String language,
@RequestBody ParsedRequestDTO parsedRequest) {
if (!allowLang.contains(language)) {
return ResponseEntity.ok().body("");
}
String code = sampleCodeGenerator.generateCodeSnippet(language, parsedRequest);
return ResponseEntity.ok().body(code);
}
}
@@ -0,0 +1,51 @@
package com.eactive.apim.portal.apps.apis.controller;
import com.eactive.apim.portal.apps.apiservice.service.ApiSpecService;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/apis")
@Slf4j
public class TestbedSpecController {
@Autowired
ApiSpecService apiSpecService;
private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec";
private static final String DEFAULT_SPEC_PATH = "swagger/default-token-api-spec.json";
@GetMapping(value = "/{id}/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> getSwagger(@PathVariable String id) {
if (DEFAULT_TOKEN_API_ID.equals(id)) {
try {
Resource resource = new ClassPathResource(DEFAULT_SPEC_PATH);
String content = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
return ResponseEntity.ok().body(content);
} catch (IOException e) {
log.error("Failed to read default token api spec file", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
String testbedSpec = apiSpecService.generateApiServiceTestbedSpec(id);
if (testbedSpec.isEmpty()) {
return ResponseEntity.notFound().build();
} else {
return ResponseEntity.ok().body(testbedSpec);
}
}
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.apps.apis.dto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ApiSearchData {
private List<ApiServiceDTO> allServices;
private Map<String, ApiSpecInfoDto> apiSpecsById;
private Map<String, ApiServiceDTO> servicesByApiId;
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.apps.apis.dto;
import lombok.Data;
@Data
public class ApiSpecInfoDto {
private String apiId;
private String apiName;
private String apiSimpleDescription;
private String apiUrl;
private String apiMethod;
private String apiContentType;
private String apiRequestSpec;
private String apiResponseSpec;
private String sampleRequest;
private String sampleResponse;
private String responseType; //sample, mock
private String mockUrl;
private String fileId;
private String description;
private String testbedSpec; //swagger json
private String mainIcon;
private String service;
private String displayYn;
private String displayOrg;
private String displayRoleCode;
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.apps.apis.dto;
import lombok.Data;
@Data
public class Param {
private String key;
private Boolean isFile;
private String value;
public Param() {
}
public Param(String key, Boolean isFile, String value) {
this.key = key;
this.isFile = isFile;
this.value = value;
}
}
@@ -0,0 +1,22 @@
package com.eactive.apim.portal.apps.apis.dto;
import java.util.Map;
import lombok.Data;
@Data
public class ParsedRequestDTO {
private String method;
private String originalUrl;
private String host;
private String port;
private String protocol;
private Map<String, String> headers;
private RequestBody body;
public String getContentType(){
if(headers == null || headers.isEmpty()) return "application/json";
return headers.get("content-type");
}
}
@@ -0,0 +1,32 @@
package com.eactive.apim.portal.apps.apis.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
public class PortalApiDTO {
private String id;
private String name; //gw
private String method; //gw
private String uri; //http://로 시작하는 서버 경로 //gw
private String path; //gateway 에 노출 되는 경로 //gw
private String apiPath; //backend 경로 //gw
private String apiSpec; //api_spec_info
private String description; //api_spec_info
private String responseType; //api_spec_info
private String mockUrl; //api_spec_info
private String sampleResponse; //api_spec_info
}
@@ -0,0 +1,38 @@
package com.eactive.apim.portal.apps.apis.dto;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
@Data
public class PropertyDTO implements Serializable {
private Long id;
private String application;
private String profile;
private String label;
private String propertyKey;
private String propertyValue;
private String description;
private String descriptionEn;
private Integer orderNo;
private boolean useYn;
private Date registeredOn;
private String registeredBy;
private Date modifiedOn;
private String modifiedBy;
}
@@ -0,0 +1,14 @@
package com.eactive.apim.portal.apps.apis.dto;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class RequestBody {
private Boolean isFile;
private String payload;
private List<Param> params = new ArrayList<>();
}
@@ -0,0 +1,145 @@
package com.eactive.apim.portal.apps.apis.filter;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class APISender {
private static final Logger logger = LoggerFactory.getLogger(APISender.class);
public String requestPost(String uri, String requestBody) throws IOException {
HttpURLConnection connection = getHttpURLConnection(uri, requestBody);
String response = getResponse(connection);
connection.disconnect();
if (logger.isDebugEnabled()) {
logger.debug(response);
}
return response;
}
private String appendUriAndParams(String uri, Map<String, String[]> params) throws UnsupportedEncodingException {
StringBuilder uriBuilder = new StringBuilder(uri);
if (params != null && !params.isEmpty()) {
uriBuilder.append("?");
for (Map.Entry<String, String[]> entry : params.entrySet()) {
String key = entry.getKey();
String[] values = entry.getValue();
for (String value : values) {
uriBuilder.append(URLEncoder.encode(key, "UTF-8"));
uriBuilder.append("=");
uriBuilder.append(URLEncoder.encode(value, "UTF-8"));
uriBuilder.append("&");
}
}
// Remove the last '&'
uriBuilder.deleteCharAt(uriBuilder.length() - 1);
}
return uriBuilder.toString();
}
public String requestGet(String uri, Map<String, String> headers, Map<String, String[]> params) throws IOException {
URL endpoint = new URL(appendUriAndParams(uri, params));
HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key != null && value != null) {
connection.setRequestProperty(key, value);
}
}
connection.setDoOutput(true);
String response = getResponse(connection);
connection.disconnect();
if (logger.isDebugEnabled()) {
logger.debug(response);
}
return response;
}
public String requestPost(String uri, Map<String, String> headers, Map<String, String[]> params, String requestBody) throws IOException {
URL endpoint = new URL(appendUriAndParams(uri, params));
HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection();
connection.setRequestMethod("POST");
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key != null && value != null) {
connection.setRequestProperty(key, value);
}
}
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8);
outputStream.write(requestBodyBytes);
outputStream.flush();
}
String response = getResponse(connection);
connection.disconnect();
if (logger.isDebugEnabled()) {
logger.debug(response);
}
return response;
}
private static String getResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();
StringBuilder response = new StringBuilder();
try (InputStream stream = (responseCode < 400) ? connection.getInputStream() : connection.getErrorStream(); InputStreamReader isr = new InputStreamReader(stream);
BufferedReader reader = new BufferedReader(isr)) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
}
return response.toString();
}
private static HttpURLConnection getHttpURLConnection(String uri, String requestBody) throws IOException {
URL endpoint = new URL(uri);
HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8);
outputStream.write(requestBodyBytes);
outputStream.flush();
}
return connection;
}
}
@@ -0,0 +1,158 @@
package com.eactive.apim.portal.apps.apis.filter;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.common.util.ApplicationContextUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WebFilter(urlPatterns = "/api/call-api")
public class ApiTesterFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ApiTesterFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("ApiTesterFilter initialized");
}
public String parseUri(String url) {
if (url == null || url.trim().isEmpty()) {
return "/";
}
try {
URI uri = new URI(url);
String path = uri.getPath();
// If path is null or empty, return root path
return (path == null || path.isEmpty()) ? "/" : path;
} catch (URISyntaxException e) {
logger.error("Failed to parse URL: {}", url, e);
return "/";
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getRequestURI().contains("/api/call-api")) {
ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class);
String url = httpServletRequest.getHeader("original-url");
if (url.contains("/api/v1/oauth/token")){
StringBuilder sb = new StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
// Parse request parameters
Map<String, String> params = new HashMap<>();
String[] pairs = body.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}
}
String scope = params.getOrDefault("scope", "default");
String token = "{\n" +
" \"access_token\": \"kbank_gw_sample_token\",\n" +
" \"token_type\": \"bearer\",\n" +
" \"expires_in\": 86400,\n" +
" \"scope\": \""+scope +"\",\n" +
" \"jti\": \""+ UUID.randomUUID().toString()+"\"\n" +
"}";
response.setContentType("application/json");
response.getWriter().println(token);
} else {
ApiSpecInfoDto apiSpecInfoDto = apiSpecInfoDtoService.selectDetailByURLAndMethod(parseUri(url), httpServletRequest.getMethod());
if (apiSpecInfoDto.getResponseType().equals("sample")) {
response.setContentType("application/json");
response.getWriter().println(apiSpecInfoDto.getSampleResponse());
} else {
String mockUrl = apiSpecInfoDto.getMockUrl();
String method = apiSpecInfoDto.getApiMethod();
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
Map<String, String> headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
headers.put(header, httpServletRequest.getHeader(header));
}
Map<String, String[]> paramMap = new HashMap<>();
String originalUrl = headers.get("original-url");
//sprlit original url with ? get the second part then split with & put into paramMap
String[] originalUrlArr = originalUrl.split("\\?");
if (originalUrlArr.length > 1) {
String[] paramArr = originalUrlArr[1].split("&");
for (String param : paramArr) {
String[] paramKeyValue = param.split("=");
if (paramKeyValue.length > 1) {
paramMap.put(paramKeyValue[0], new String[]{paramKeyValue[1]});
}
}
}
headers.remove("original-url");
headers.remove("original-api-id");
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
String responseStr = "";
if (method.equalsIgnoreCase("post")) {
StringBuilder sb = new StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
responseStr = apiSender.requestPost(mockUrl, headers, paramMap, body);
} else {
responseStr = apiSender.requestGet(mockUrl, headers, paramMap);
}
response.setContentType("application/json");
response.getWriter().println(responseStr);
}
}
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// Do nothing
}
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.apps.apis.mapper;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import java.util.Optional;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class)
public interface ApiSpecMapper {
ApiSpecInfoDto mapToDto(ApiSpecInfo apiSpecInfo);
default ApiSpecInfoDto map(Optional<ApiSpecInfo> optionalApiSpecInfo) {
return optionalApiSpecInfo.map(this::mapToDto).orElse(new ApiSpecInfoDto());
}
}
@@ -0,0 +1,17 @@
package com.eactive.apim.portal.apps.apis.service;
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import java.util.List;
import java.util.Map;
public interface ApiSearchFacade {
ApiSearchData loadAllApiData();
Map<String, Object> searchApis(ApiGroupSearch search);
List<ApiSpecInfoDto> sortApisByService(List<ApiSpecInfoDto> filteredApis, List<ApiServiceDTO> services);
}
@@ -0,0 +1,205 @@
package com.eactive.apim.portal.apps.apis.service;
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.auth.service.ApiPermissionFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ApiSearchFacadeImpl implements ApiSearchFacade {
private final ApiService apiService;
private final ApiServiceService apiServiceService;
public ApiSearchData loadAllApiData() {
List<ApiServiceDTO> allServices = apiServiceService.searchApiGroups(new ApiGroupSearch());
Set<String> allApiIds = allServices.stream()
.filter(service -> service.getApiGroupApiList() != null)
.flatMap(service -> service.getApiGroupApiList().stream())
.map(ApiServiceApiDTO::getApiId)
.collect(Collectors.toSet());
Map<String, ApiSpecInfoDto> apiSpecsById = apiService.findApisForApiIds(new ArrayList<>(allApiIds))
.stream()
.collect(Collectors.toMap(
ApiSpecInfoDto::getApiId,
Function.identity(),
(existing, replacement) -> existing
));
Map<String, ApiServiceDTO> servicesByApiId = new HashMap<>();
allServices.forEach(service -> {
if (service.getApiGroupApiList() != null) {
service.getApiGroupApiList().forEach(api ->
servicesByApiId.put(api.getApiId(), service));
}
});
return new ApiSearchData(allServices, apiSpecsById, servicesByApiId);
}
@Override
public Map<String, Object> searchApis(ApiGroupSearch search) {
ApiSearchData apiData = loadAllApiData();
String keyword = search.getKeyword();
Set<String> matchingApiIds = new HashSet<>();
if (!StringUtils.isEmpty(keyword)) {
apiData.getAllServices().stream()
.filter(service -> isServiceMatchingKeyword(service, keyword))
.filter(service -> service.getApiGroupApiList() != null)
.flatMap(service -> service.getApiGroupApiList().stream())
.map(ApiServiceApiDTO::getApiId)
.forEach(matchingApiIds::add);
apiData.getApiSpecsById().values().stream()
.filter(api -> isApiMatchingKeyword(api, keyword))
.map(ApiSpecInfoDto::getApiId)
.forEach(matchingApiIds::add);
} else {
matchingApiIds.addAll(apiData.getApiSpecsById().keySet());
}
if (search.getGroupIds() != null && !search.getGroupIds().isEmpty()) {
Set<String> selectedGroupIds = new HashSet<>(search.getGroupIds());
Set<String> allowedApiIds = apiData.getAllServices().stream()
.filter(service -> selectedGroupIds.contains(service.getId()))
.filter(service -> service.getApiGroupApiList() != null)
.flatMap(service -> service.getApiGroupApiList().stream())
.map(ApiServiceApiDTO::getApiId)
.collect(Collectors.toSet());
matchingApiIds.retainAll(allowedApiIds);
}
List<ApiSpecInfoDto> matchingApis = matchingApiIds.stream()
.map(apiId -> apiData.getApiSpecsById().get(apiId))
.filter(Objects::nonNull)
.collect(Collectors.toList());
List<ApiSpecInfoDto> filteredApis = ApiPermissionFilter.filterApisByPermissions(matchingApis);
Set<String> finalApiIds = filteredApis.stream()
.map(ApiSpecInfoDto::getApiId)
.collect(Collectors.toSet());
filteredApis.forEach(api -> {
api.setMainIcon(apiData.getServicesByApiId().get(api.getApiId()).getMainIcon());
});
List<ApiServiceDTO> servicesToReturn = apiData.getAllServices().stream()
.map(service -> {
ApiServiceDTO filteredService = new ApiServiceDTO();
BeanUtils.copyProperties(service, filteredService);
if (service.getApiGroupApiList() != null) {
List<ApiServiceApiDTO> filteredApiList = service.getApiGroupApiList().stream()
.filter(api -> finalApiIds.contains(api.getApiId()))
.collect(Collectors.toList());
filteredService.setApiGroupApiList(filteredApiList);
} else {
filteredService.setApiGroupApiList(new ArrayList<>());
}
return filteredService;
})
.collect(Collectors.toList());
List<ApiSpecInfoDto> sortedApis = sortApisByService(filteredApis, servicesToReturn);
Map<String, Object> result = new HashMap<>();
result.put("services", servicesToReturn);
result.put("apis", sortedApis);
result.put("selectedApiCount", sortedApis.size());
return result;
}
private boolean isServiceMatchingKeyword(ApiServiceDTO service, String keyword) {
String lowerKeyword = keyword.toLowerCase();
return service.getGroupName() != null &&
service.getGroupName().toLowerCase().contains(lowerKeyword);
}
private boolean isApiMatchingKeyword(ApiSpecInfoDto api, String keyword) {
String lowerKeyword = keyword.toLowerCase();
return (api.getApiName() != null &&
api.getApiName().toLowerCase().contains(lowerKeyword)) ||
(api.getApiSimpleDescription() != null &&
api.getApiSimpleDescription().toLowerCase().contains(lowerKeyword));
}
public List<ApiSpecInfoDto> sortApisByService(List<ApiSpecInfoDto> filteredApis, List<ApiServiceDTO> services) {
// Create a map of apiId to its service and API details for efficient lookup
Map<String, SortingMetadata> sortingMetadataMap = new HashMap<>();
services.forEach(service -> {
if (service.getApiGroupApiList() != null) {
for (int i = 0; i < service.getApiGroupApiList().size(); i++) {
ApiServiceApiDTO api = service.getApiGroupApiList().get(i);
sortingMetadataMap.put(api.getApiId(), new SortingMetadata(service.getDisplayOrder() != null ? service.getDisplayOrder() : Integer.MAX_VALUE, i));
}
}
});
// Sort the filtered APIs based on both service and API display orders
return filteredApis.stream()
.filter(api -> sortingMetadataMap.containsKey(api.getApiId()))
.sorted((api1, api2) -> {
SortingMetadata metadata1 = sortingMetadataMap.getOrDefault(
api1.getApiId(),
new SortingMetadata(Integer.MAX_VALUE, Integer.MAX_VALUE)
);
SortingMetadata metadata2 = sortingMetadataMap.getOrDefault(
api2.getApiId(),
new SortingMetadata(Integer.MAX_VALUE, Integer.MAX_VALUE)
);
// First compare by service display order
int serviceOrderComparison = metadata1.getServiceDisplayOrder().compareTo(metadata2.getServiceDisplayOrder());
if (serviceOrderComparison != 0) {
return serviceOrderComparison;
}
// If service display orders are equal, compare by API display order
return metadata1.getApiDisplayOrder().compareTo(metadata2.getApiDisplayOrder());
})
.collect(Collectors.toList());
}
}
@@ -0,0 +1,74 @@
package com.eactive.apim.portal.apps.apis.service;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.mapper.ApiSpecMapper;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class ApiService {
private final ApiSpecMapper apiSpecMapper;
private final ApiSpecInfoService apiSpecInfoService;
private ApiSpecInfoDto processDTO(ApiSpecInfo apiSpecInfo) {
return Optional.ofNullable(apiSpecInfo)
.map(apiSpecMapper::mapToDto)
.map(dto -> {
if (dto.getDescription() != null) {
dto.setDescription(StringEscapeUtils.unescapeHtml4(dto.getDescription()));
}
if (dto.getApiRequestSpec() != null) {
dto.setApiRequestSpec(StringEscapeUtils.unescapeHtml4(dto.getApiRequestSpec()));
}
if (dto.getApiResponseSpec() != null) {
dto.setApiResponseSpec(StringEscapeUtils.unescapeHtml4(dto.getApiResponseSpec()));
}
if (dto.getDescription() != null) {
dto.setDescription(StringEscapeUtils.unescapeHtml4(dto.getDescription()));
}
return dto;
})
.orElse(null);
}
public ApiSpecInfoDto selectDetail(String eaiSvcName) {
Optional<ApiSpecInfo> apiSpecInfo = apiSpecInfoService.findById(eaiSvcName);
return apiSpecInfo.map(this::processDTO).orElse(null);
}
public ApiSpecInfoDto selectDetailByURLAndMethod(String url, String method) {
Optional<ApiSpecInfo> apiSpecInfo = apiSpecInfoService.findByUrlAndMethod(url, method);
return apiSpecMapper.map(apiSpecInfo);
}
public List<ApiSpecInfoDto> findApis() {
return apiSpecInfoService.findPublicApis().stream()
.map(apiSpecMapper::mapToDto)
.collect(Collectors.toList());
}
public List<ApiSpecInfoDto> findApisForApiIds(List<String> apiIds) {
return apiSpecInfoService.findApiSpecsByApiIds(apiIds).stream()
.map(apiSpecMapper::mapToDto)
.collect(Collectors.toList());
}
public List<ApiSpecInfoDto> searchApis(String keyword) {
return apiSpecInfoService.searchApis(keyword).stream()
.map(this::processDTO)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,51 @@
package com.eactive.apim.portal.apps.apis.service;
import com.eactive.apim.portal.apps.apis.dto.ParsedRequestDTO;
import java.io.StringWriter;
import java.util.Properties;
import javax.annotation.PostConstruct;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SampleCodeGenerator {
private static final String TEMPLATE_PATH = "templates/sample_code/";
@Value("${sample-code-path}")
private String sampleCodePath;
private VelocityEngine velocityEngine;
public String generateCodeSnippet(String language, ParsedRequestDTO parsedRequest) {
Template t = velocityEngine.getTemplate( TEMPLATE_PATH + language + ".vm");
VelocityContext context = new VelocityContext();
context.put("request", parsedRequest);
StringWriter writer = new StringWriter();
t.merge(context, writer);
return writer.toString();
}
@PostConstruct
public void init() {
velocityEngine = new VelocityEngine();
Properties props = new Properties();
props.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
props.setProperty( "resource.loader.classpath.class", ClasspathResourceLoader.class.getName() );
props.setProperty("resource.loader.classpath.path", sampleCodePath);
props.setProperty("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.NullLogSystem");
velocityEngine.init(props);
}
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.apps.apis.service;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class SortingMetadata {
private Integer serviceDisplayOrder;
private Integer apiDisplayOrder;
}
@@ -0,0 +1,55 @@
package com.eactive.apim.portal.apps.apiservice.controller;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceTabInfo;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/api-services")
@RequiredArgsConstructor
public class ApiServiceController {
private final ApiServiceService apiServiceService;
@GetMapping
public ModelAndView apiServiceList(@ModelAttribute ApiGroupSearch search) {
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
ModelAndView mav = new ModelAndView("apps/apiservice/apiServiceList");
mav.addObject("apiServices", apiServices);
mav.addObject("search", search);
List<ApiServiceTabInfo> tabs = Arrays.asList(
new ApiServiceTabInfo("전체", true, apiServices.size()),
new ApiServiceTabInfo("공통", false, 0),
new ApiServiceTabInfo("이체", false, 0),
new ApiServiceTabInfo("조회", false, 0),
new ApiServiceTabInfo("인증", false, 0),
new ApiServiceTabInfo("결제", false, 0),
new ApiServiceTabInfo("펌뱅킹", false, 0),
new ApiServiceTabInfo("가상계좌", false, 0)
);
mav.addObject("tabs", tabs);
return mav;
}
@GetMapping("/detail/{id}")
public String apiServiceDetail(@PathVariable String id, Model model) {
ApiServiceDTO apiService = apiServiceService.getApiGroupById(id);
model.addAttribute("apiService", apiService);
return "apps/apiservice/apiServiceDetail";
}
}
@@ -0,0 +1,40 @@
package com.eactive.apim.portal.apps.apiservice.dto;
import com.eactive.apim.portal.common.search.BaseSearch;
import com.eactive.apim.portal.common.search.ColumnSearchModel;
import com.eactive.apim.portal.common.search.SearchCondition;
import com.eactive.apim.portal.common.search.SearchModel;
import com.eactive.apim.portal.common.search.SpecificationSearchModel;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ApiGroupSearch implements BaseSearch<ApiGroup> {
private String keyword;
private List<String> groupIds = new ArrayList<>();
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
if (keyword != null && !keyword.trim().isEmpty()) {
// keyword로 groupName과 groupDesc 모두 검색하는 OR 조건
models.add(new SpecificationSearchModel<ApiGroup>(
(root, query, cb) -> cb.or(
cb.like(cb.lower(root.get("groupName")), "%" + keyword.toLowerCase() + "%"),
cb.like(cb.lower(root.get("groupDesc")), "%" + keyword.toLowerCase() + "%")
)
));
}
if (groupIds != null && !groupIds.isEmpty()) {
models.add(new ColumnSearchModel("id", SearchCondition.IN, groupIds));
}
models.add(new ColumnSearchModel("displayYn", SearchCondition.EQUAL, "1"));
return models;
}
}
@@ -0,0 +1,14 @@
package com.eactive.apim.portal.apps.apiservice.dto;
import lombok.Data;
@Data
public class ApiServiceApiDTO {
private String apiGroupId;
private String bizCode;
private String apiId;
private String apiDesc;
private String apiUrl;
private String apiMethod;
private String service;
}
@@ -0,0 +1,30 @@
package com.eactive.apim.portal.apps.apiservice.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
@Data
public class ApiServiceDTO {
private String id;
private String groupName;
private String groupDesc;
private String contentDetail;
private String contentIcon;
private String contentSubject;
private Integer displayOrder;
private Integer menuDisplayOrder;
private String mainText;
private String displayOrg;
private String displayRoleCode;
private String displayYn;
private String mainIcon;
private String createdBy;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdDate;
private String lastModifiedBy;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastModifiedDate;
private List<ApiServiceApiDTO> apiGroupApiList;
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.apps.apiservice.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ApiServiceTabInfo {
private String name;
private boolean active;
private int count;
}
@@ -0,0 +1,24 @@
package com.eactive.apim.portal.apps.apiservice.mapper;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApi;
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = BaseMapperConfig.class, uses = {})
public interface ApiServiceApiMapper extends GenericMapper<ApiServiceApiDTO, ApiGroupApi> {
@Mapping(source = "id.apiGroupId", target = "apiGroupId")
@Mapping(source = "id.apiId", target = "apiId")
ApiServiceApiDTO toVo(ApiGroupApi apiGroupApi);
@InheritInverseConfiguration
ApiGroupApi toEntity(ApiServiceApiDTO apiServiceApiDTO);
@Mapping(source = "eaibzwkdstcd", target = "bizCode")
@Mapping(source = "eaisvcname", target = "apiId")
@Mapping(source = "eaisvcdesc", target = "apiDesc")
ApiServiceApiDTO toVo(EAIMessageEntity eaiMessageEntity);
}
@@ -0,0 +1,27 @@
package com.eactive.apim.portal.apps.apiservice.mapper;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
import com.eactive.eai.data.entity.onl.message.EAIMessageEntity;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.*;
import java.util.List;
@Mapper(config = BaseMapperConfig.class, uses = { ApiServiceApiMapper.class })
public interface ApiServiceMapper extends GenericMapper<ApiServiceDTO, ApiGroup> {
@Mapping(source = "apiGroupApiList", target = "apiGroupApiList")
ApiServiceDTO toVo(ApiGroup apiGroup);
@Mapping(source = "eaiMessageEntityList", target = "apiGroupApiList")
ApiServiceDTO toVo(ApiGroup apiGroup, List<EAIMessageEntity> eaiMessageEntityList);
@InheritInverseConfiguration
ApiGroup toEntity(ApiServiceDTO apiServiceDTO);
@InheritInverseConfiguration
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateToEntity(ApiServiceDTO apiServiceDTO, @MappingTarget ApiGroup apiGroup);
}
@@ -0,0 +1,16 @@
package com.eactive.apim.portal.apps.apiservice.mapper;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = BaseMapperConfig.class, uses = {})
public interface ApiSpecInfoMapper extends GenericMapper<ApiServiceApiDTO, ApiSpecInfo> {
@Mapping(source = "apiName", target = "apiDesc")
ApiServiceApiDTO toVo(ApiSpecInfo apiGroupApi);
}
@@ -0,0 +1,113 @@
package com.eactive.apim.portal.apps.apiservice.service;
import com.eactive.apim.gateway.data.apiservice.repository.ApiServiceRepository;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceMapper;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiSpecInfoMapper;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroup;
import com.eactive.eai.data.entity.onl.apim.apigroup.ApiGroupApi;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("apiServiceService")
@Transactional
@RequiredArgsConstructor
@Slf4j
public class ApiServiceService {
private final ApiServiceRepository apiServiceRepository;
private final ApiServiceMapper apiServiceMapper;
private final ApiSpecInfoService apiSpecInfoService;
private final ApiSpecInfoMapper apiSpecInfoMapper;
public List<ApiServiceDTO> searchApiGroups(ApiGroupSearch search) {
Sort sortByDisplayOrder = Sort.by(Sort.Direction.ASC, "displayOrder");
List<ApiGroup> apiGroups = apiServiceRepository.findAll(search.buildSpecification(), sortByDisplayOrder);
List<ApiServiceDTO> apiServices = apiGroups.stream()
.map(apiServiceMapper::toVo)
.collect(Collectors.toList());
for (ApiServiceDTO apiServiceDTO : apiServices) {
ApiServiceDTO apiService = getApiGroupById(apiServiceDTO.getId());
apiServiceDTO.getApiGroupApiList().clear();
apiServiceDTO.getApiGroupApiList().addAll(apiService.getApiGroupApiList());
}
return apiServices;
}
public List<ApiServiceDTO> searchApiGroupsForLnb() {
ApiGroupSearch search = new ApiGroupSearch();
Sort sortByDisplayOrder = Sort.by(Sort.Direction.ASC, "displayOrder");
List<ApiGroup> apiGroups = apiServiceRepository.findAll(search.buildSpecification(), sortByDisplayOrder);
List<ApiServiceDTO> apiServices = apiGroups.stream()
.map(apiServiceMapper::toVo)
.collect(Collectors.toList());
for (ApiServiceDTO apiServiceDTO : apiServices) {
ApiServiceDTO apiService = getApiGroupById(apiServiceDTO.getId());
apiServiceDTO.getApiGroupApiList().clear();
apiServiceDTO.getApiGroupApiList().addAll(apiService.getApiGroupApiList());
}
return apiServices;
}
public ApiServiceDTO getApiGroupById(String id) {
ApiGroup apiGroup = apiServiceRepository.findById(id)
.orElseThrow(() -> new NotFoundException("API Group not found with id: " + id));
ApiServiceDTO apiServiceDTO = apiServiceMapper.toVo(apiGroup);
if (apiGroup.getApiGroupApiList() != null && !apiGroup.getApiGroupApiList().isEmpty()) {
List<String> apiIds = apiGroup.getApiGroupApiList().stream()
.sorted(Comparator.comparing(
ApiGroupApi::getDisplayOrder,
Comparator.nullsLast(Comparator.naturalOrder())
))
.map(a -> a.getId().getApiId())
.collect(Collectors.toList());
List<ApiSpecInfo> apiSpecInfos = apiSpecInfoService.findApiSpecsByApiIds(apiIds);
// Create a map of apiId to index for efficient lookup
Map<String, Integer> orderMap = IntStream.range(0, apiIds.size())
.boxed()
.collect(Collectors.toMap(apiIds::get, i -> i));
List<ApiServiceApiDTO> apiServiceApiDTOS = apiSpecInfos.stream()
.sorted(Comparator.comparing(api -> orderMap.get(api.getApiId())))
.map(apiSpecInfoMapper::toVo)
.collect(Collectors.toList());
apiServiceDTO.setApiGroupApiList(apiServiceApiDTOS);
}
return apiServiceDTO;
}
public ApiServiceDTO findApiServiceByApiId(String apiId) {
return apiServiceRepository.findAll().stream()
.filter(apiGroup -> apiGroup.getApiGroupApiList().stream()
.anyMatch(api -> api.getId().getApiId().equals(apiId)))
.map(apiServiceMapper::toVo)
.findFirst()
.orElse(null);
}
}
@@ -0,0 +1,531 @@
package com.eactive.apim.portal.apps.apiservice.service;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class ApiSpecService {
private final ObjectMapper objectMapper = new ObjectMapper();
private final ApiServiceService apiServiceService;
private final ApiService apiService;
public String generateApiServiceTestbedSpec(String apiServiceId) {
ApiServiceDTO apiServiceDTO = apiServiceService.getApiGroupById(apiServiceId);
List<ApiServiceApiDTO> apiGroupApiList = apiServiceDTO.getApiGroupApiList();
String groupName = apiServiceDTO.getGroupName();
OpenAPI mergedOpenAPI = new OpenAPI()
.info(new Info()
.title(groupName + " API Documentation")
.version("1.0.0")
.description("API documentation for " + groupName))
.tags(Collections.singletonList(new Tag().name(groupName)));
Paths mergedPaths = new Paths();
Map<String, Object> mergedComponents = new HashMap<>();
// 경로 중복 체크를 위한 맵
Map<String, String> pathOwners = new HashMap<>();
List<String> duplicateWarnings = new ArrayList<>();
for (ApiServiceApiDTO apiServiceApiDTO : apiGroupApiList) {
String apiId = apiServiceApiDTO.getApiId();
ApiSpecInfoDto apiSpecInfoDto = apiService.selectDetail(apiId);
OpenAPI currentAPI = parseSpec(apiSpecInfoDto.getTestbedSpec());
if (currentAPI == null) {
continue;
}
String basePath = getBasePath(currentAPI);
checkAndMergePaths(currentAPI, basePath, groupName, apiId, mergedPaths,
apiSpecInfoDto, pathOwners, duplicateWarnings);
mergeComponents(currentAPI, apiId, apiSpecInfoDto.getApiName(), mergedComponents, objectMapper);
}
// 중복 경로 경고가 있으면 description에 추가
if (!duplicateWarnings.isEmpty()) {
String currentDesc = mergedOpenAPI.getInfo().getDescription();
String warningDesc = "\n\n⚠️ Warning: Duplicate Path Detected\n" +
String.join("\n", duplicateWarnings);
mergedOpenAPI.getInfo().setDescription(currentDesc + warningDesc);
}
if (!mergedComponents.isEmpty()) {
mergedOpenAPI.setComponents(objectMapper.convertValue(mergedComponents, io.swagger.v3.oas.models.Components.class));
}
mergedOpenAPI.setPaths(mergedPaths);
try {
return Json.mapper().writeValueAsString(mergedOpenAPI);
} catch (Exception e) {
log.error("Error generating merged API specification", e);
return "{}";
}
}
private PathItem mergePathItems(PathItem existing, PathItem newItem, String fullPath,
String apiName, Map<String, String> pathOwners, List<String> duplicateWarnings) {
PathItem merged = new PathItem();
// HTTP 메소드와 getter/setter를 매핑
Map<String, Triple<Operation, Operation, Consumer<Operation>>> methodMap = new HashMap<>();
methodMap.put("GET", Triple.of(existing.getGet(), newItem.getGet(), merged::setGet));
methodMap.put("POST", Triple.of(existing.getPost(), newItem.getPost(), merged::setPost));
methodMap.put("PUT", Triple.of(existing.getPut(), newItem.getPut(), merged::setPut));
methodMap.put("DELETE", Triple.of(existing.getDelete(), newItem.getDelete(), merged::setDelete));
methodMap.put("PATCH", Triple.of(existing.getPatch(), newItem.getPatch(), merged::setPatch));
methodMap.put("HEAD", Triple.of(existing.getHead(), newItem.getHead(), merged::setHead));
methodMap.put("OPTIONS", Triple.of(existing.getOptions(), newItem.getOptions(), merged::setOptions));
methodMap.put("TRACE", Triple.of(existing.getTrace(), newItem.getTrace(), merged::setTrace));
// 각 메소드에 대해 처리
methodMap.forEach((method, triple) -> {
Operation existingOp = triple.getLeft();
Operation newOp = triple.getMiddle();
Consumer<Operation> setter = triple.getRight();
if (existingOp != null && newOp != null) {
checkAndAddOperation(existingOp, newOp, method, setter, fullPath, apiName, pathOwners, duplicateWarnings);
} else {
setter.accept(existingOp != null ? existingOp : newOp);
}
});
// 공통 속성 병합
merged.setParameters(mergeParameters(existing.getParameters(), newItem.getParameters()));
merged.setServers(mergeServers(existing.getServers(), newItem.getServers()));
merged.setDescription(mergeDescriptions(existing.getDescription(), newItem.getDescription()));
merged.setSummary(mergeSummaries(existing.getSummary(), newItem.getSummary()));
return merged;
}
private void checkAndMergePaths(OpenAPI currentAPI, String basePath, String groupName,
String apiId, Paths mergedPaths, ApiSpecInfoDto apiSpecInfoDto,
Map<String, String> pathOwners, List<String> duplicateWarnings) {
if (currentAPI.getPaths() == null) {
return;
}
currentAPI.getPaths().forEach((path, pathItem) -> {
updateOperationTags(pathItem, groupName);
updateOperationSummary(pathItem, apiSpecInfoDto.getApiName());
updateReferences(pathItem, apiId);
String tmpFullPath = StringUtils.isNotEmpty(basePath)
? StringUtils.join(basePath, "/", StringUtils.removeStart(path, "/"))
: path;
final String fullPath = StringUtils.replacePattern(tmpFullPath, "//+", "/");
// HTTP 메소드별로 중복 체크
pathItem.readOperationsMap().forEach((httpMethod, operation) -> {
final String pathKey = fullPath + ":" + httpMethod;
final String existingOwner = pathOwners.get(pathKey);
if (existingOwner != null) {
String warning = String.format(
"- Path '%s' [%s] is duplicated between '%s' and '%s'",
fullPath,
httpMethod,
existingOwner,
apiSpecInfoDto.getApiName()
);
duplicateWarnings.add(warning);
log.warn(warning);
}
pathOwners.put(pathKey, apiSpecInfoDto.getApiName());
});
// 기존 PathItem 가져오기
PathItem existingPathItem = mergedPaths.get(fullPath);
if (existingPathItem == null) {
// 새로운 경로면 그대로 추가
mergedPaths.addPathItem(fullPath, pathItem);
} else {
// 기존 경로가 있으면 HTTP 메소드별로 병합
PathItem mergedPathItem = mergePathItems(existingPathItem, pathItem, fullPath,
apiSpecInfoDto.getApiName(), pathOwners, duplicateWarnings);
mergedPaths.put(fullPath, mergedPathItem);
}
});
}
private void checkAndAddOperation(Operation existingOp, Operation newOp, String method,
Consumer<Operation> setter, String fullPath, String apiName,
Map<String, String> pathOwners, List<String> duplicateWarnings) {
final String pathKey = fullPath + ":" + method;
final String existingOwner = pathOwners.get(pathKey);
if (existingOwner != null) {
String warning = String.format(
"- Path '%s' [%s] is duplicated between '%s' and '%s'",
fullPath,
method,
existingOwner,
apiName
);
duplicateWarnings.add(warning);
log.warn(warning);
}
// 새로운 오퍼레이션으로 설정
setter.accept(newOp);
pathOwners.put(pathKey, apiName);
}
private List<Parameter> mergeParameters(List<Parameter> existing, List<Parameter> newParams) {
if (existing == null || existing.isEmpty()) {
return newParams;
}
if (newParams == null || newParams.isEmpty()) {
return existing;
}
List<Parameter> merged = new ArrayList<>(existing);
for (Parameter newParam : newParams) {
if (!parameterExists(merged, newParam)) {
merged.add(newParam);
}
}
return merged;
}
private boolean parameterExists(List<Parameter> parameters, Parameter parameter) {
return parameters.stream()
.anyMatch(p -> Objects.equals(p.getName(), parameter.getName())
&& Objects.equals(p.getIn(), parameter.getIn()));
}
private List<Server> mergeServers(List<Server> existing, List<Server> newServers) {
if (existing == null || existing.isEmpty()) {
return newServers;
}
if (newServers == null || newServers.isEmpty()) {
return existing;
}
Set<String> existingUrls = existing.stream()
.map(Server::getUrl)
.collect(Collectors.toSet());
List<Server> merged = new ArrayList<>(existing);
newServers.stream()
.filter(server -> !existingUrls.contains(server.getUrl()))
.forEach(merged::add);
return merged;
}
private String mergeDescriptions(String existing, String newDesc) {
if (StringUtils.isEmpty(existing)) {
return newDesc;
}
if (StringUtils.isEmpty(newDesc)) {
return existing;
}
return existing + "\n\n" + newDesc;
}
private String mergeSummaries(String existing, String newSummary) {
if (StringUtils.isEmpty(existing)) {
return newSummary;
}
if (StringUtils.isEmpty(newSummary)) {
return existing;
}
return existing + " | " + newSummary;
}
private OpenAPI parseSpec(String spec) {
SwaggerParseResult result = new OpenAPIParser().readContents(spec, null, null);
if (result == null || result.getOpenAPI() == null) {
log.error("Failed to parse API specification");
return null;
}
return result.getOpenAPI();
}
private String getBasePath(OpenAPI api) {
if (CollectionUtils.isEmpty(api.getServers())) {
return StringUtils.EMPTY;
}
Server server = api.getServers().get(0);
return StringUtils.removeEnd(server.getUrl(), "/");
}
private void mergeComponents(OpenAPI currentAPI, String apiId, String apiName, Map<String, Object> mergedComponents, ObjectMapper objectMapper) {
if (currentAPI.getComponents() == null) {
return;
}
JsonNode componentsNode = objectMapper.valueToTree(currentAPI.getComponents());
if (!componentsNode.isObject()) {
return;
}
componentsNode.fields().forEachRemaining(entry -> {
if (!entry.getValue().isObject()) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> section = (Map<String, Object>) mergedComponents
.computeIfAbsent(entry.getKey(), k -> new HashMap<String, Object>());
if ("schemas".equals(entry.getKey())) {
entry.getValue().fields().forEachRemaining(schemaEntry -> {
String originalSchemaName = schemaEntry.getKey();
String newSchemaName = apiId + "-" + originalSchemaName;
JsonNode schemaValue = schemaEntry.getValue();
// 스키마 내용 복사 및 참조 업데이트
JsonNode updatedSchema = updateReferencesInSchema(schemaValue.deepCopy(), apiId);
// title 필드 추가하여 화면에 표시될 이름 설정
if (updatedSchema.isObject()) {
((ObjectNode) updatedSchema).put("title", apiName + "-" + originalSchemaName);
}
section.put(newSchemaName, objectMapper.convertValue(updatedSchema, Object.class));
});
} else {
entry.getValue().fields().forEachRemaining(subEntry ->
section.put(apiId + "-" + subEntry.getKey(),
objectMapper.convertValue(subEntry.getValue(), Object.class)));
}
});
}
private JsonNode updateReferencesInSchema(JsonNode node, String apiId) {
if (node == null) {
return null;
}
if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
Iterator<Entry<String, JsonNode>> fields = objectNode.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
String key = entry.getKey();
JsonNode value = entry.getValue();
if ("$ref".equals(key)) {
String ref = value.asText();
// 내부 참조인 경우에만 apiId 추가
if (ref.startsWith("#/")) {
String updatedRef = updateSchemaReference(ref, apiId);
objectNode.put(key, updatedRef);
}
} else if (value.isObject() || value.isArray()) {
JsonNode updatedValue = updateReferencesInSchema(value, apiId);
objectNode.set(key, updatedValue);
}
}
return objectNode;
} else if (node.isArray()) {
for (int i = 0; i < node.size(); i++) {
JsonNode item = node.get(i);
if (item.isObject() || item.isArray()) {
((com.fasterxml.jackson.databind.node.ArrayNode) node).set(i,
updateReferencesInSchema(item, apiId));
}
}
}
return node;
}
private String updateSchemaReference(String ref, String apiId) {
if (StringUtils.isEmpty(ref) || !ref.startsWith("#/")) {
return ref;
}
String[] parts = ref.split("/");
if (parts.length < 4) {
return ref; // 최소 #/components/schemas/name 형식이어야 함
}
// 마지막 부분만 apiId- 접두어 추가
String schemaName = parts[parts.length - 1];
parts[parts.length - 1] = apiId + "-" + schemaName;
return String.join("/", parts);
}
private void updateOperationReferences(Operation operation, String apiId) {
// RequestBody 참조 업데이트
if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) {
operation.getRequestBody().getContent().forEach((mediaType, content) -> {
if (content.getSchema() != null) {
updateSchemaReferencesRecursively(content.getSchema(), apiId);
}
});
}
// original-api-id 확장 필드 추가
if (operation.getExtensions() == null) {
operation.setExtensions(new HashMap<>());
}
operation.getExtensions().put("x-original-api-id", apiId);
// Responses 참조 업데이트
if (operation.getResponses() != null) {
operation.getResponses().forEach((code, response) -> {
if (response.getContent() != null) {
response.getContent().forEach((mediaType, content) -> {
if (content.getSchema() != null) {
updateSchemaReferencesRecursively(content.getSchema(), apiId);
}
});
}
});
}
// Parameters 참조 업데이트
if (operation.getParameters() != null) {
operation.getParameters().forEach(parameter -> {
if (parameter.getSchema() != null) {
updateSchemaReferencesRecursively(parameter.getSchema(), apiId);
}
});
}
}
private void updateSchemaReferencesRecursively(io.swagger.v3.oas.models.media.Schema<?> schema, String apiId) {
if (schema == null) {
return;
}
if (schema.get$ref() != null) {
schema.set$ref(updateSchemaReference(schema.get$ref(), apiId));
}
// Handle properties
if (schema.getProperties() != null) {
schema.getProperties().forEach((propertyName, propertySchema) ->
updateSchemaReferencesRecursively((io.swagger.v3.oas.models.media.Schema<?>) propertySchema, apiId));
}
// Handle arrays
if (schema.getItems() != null) {
updateSchemaReferencesRecursively(schema.getItems(), apiId);
}
// Handle allOf, anyOf, oneOf
Stream.of(schema.getAllOf(), schema.getAnyOf(), schema.getOneOf())
.filter(Objects::nonNull)
.flatMap(List::stream)
.forEach(s -> updateSchemaReferencesRecursively(s, apiId));
// Handle additional properties
if (schema.getAdditionalProperties() instanceof io.swagger.v3.oas.models.media.Schema) {
updateSchemaReferencesRecursively(
(io.swagger.v3.oas.models.media.Schema<?>) schema.getAdditionalProperties(),
apiId
);
}
}
private void updateOperationTags(PathItem pathItem, String groupName) {
Stream.of(
pathItem.getGet(), pathItem.getPost(),
pathItem.getPut(), pathItem.getDelete(),
pathItem.getPatch(), pathItem.getHead(),
pathItem.getOptions(), pathItem.getTrace()
)
.filter(Objects::nonNull)
.forEach(operation -> operation.setTags(Collections.singletonList(groupName)));
}
private void updateReferences(PathItem pathItem, String apiId) {
Stream.of(
pathItem.getGet(), pathItem.getPost(),
pathItem.getPut(), pathItem.getDelete(),
pathItem.getPatch(), pathItem.getHead(),
pathItem.getOptions(), pathItem.getTrace()
)
.filter(Objects::nonNull)
.forEach(operation -> updateOperationReferences(operation, apiId));
}
private void updateOperationSummary(PathItem pathItem, String apiName) {
Stream.of(
pathItem.getGet(), pathItem.getPost(),
pathItem.getPut(), pathItem.getDelete(),
pathItem.getPatch(), pathItem.getHead(),
pathItem.getOptions(), pathItem.getTrace()
)
.filter(Objects::nonNull)
.forEach(operation -> {
// 기존 summary가 있으면 API 이름을 앞에 추가
String existingSummary = operation.getSummary();
if (StringUtils.isNotEmpty(existingSummary)) {
operation.setSummary(apiName + " - " + existingSummary);
} else {
operation.setSummary(apiName);
}
// operationId가 있다면 API 이름을 앞에 추가
String existingOperationId = operation.getOperationId();
if (StringUtils.isNotEmpty(existingOperationId)) {
operation.setOperationId(apiName + "-" + existingOperationId);
}
});
}
@Data
@AllArgsConstructor(staticName = "of")
private static class Triple<L, M, R> {
private final L left;
private final M middle;
private final R right;
}
}
@@ -0,0 +1,112 @@
package com.eactive.apim.portal.apps.app.controller;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apps.apis.dto.ApiSearchData;
import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.dto.ClientSimpleDTO;
import com.eactive.apim.portal.apps.app.mapper.CredentialMapper;
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
import com.eactive.apim.portal.common.dto.ResponseDTO;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.EncryptionUtil;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.annotation.Secured;
import org.springframework.ui.Model;
import org.springframework.validation.ValidationUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/myapikey")
@RequiredArgsConstructor
@Secured("ROLE_API_KEY_REQUEST")
public class ApiKeyRestController {
private final static String PROD_SERVER = "PROD";
private final static String STG_SERVER = "STG";
private final AppServiceFacade appServiceFacade;
private final CredentialMapper credentialMapper;
private final EncryptionUtil encryptionUtil;
private final ApiSearchFacade apiSearchFacade;
@PostMapping("/api_key_request")
public ResponseDTO apiRequest(@RequestBody AppRequestDTO appRequest) {
appRequest.setOrg(SecurityUtil.getUserOrg());
if (appRequest.getType() == null) {
appRequest.setType(AppRequestType.NEW);
}
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
}
@PostMapping("/api_key_request_prod")
public ResponseDTO requestProdApiKey(@RequestBody AppRequestDTO appRequest) {
appRequest.setOrg(SecurityUtil.getUserOrg());
if (appRequest.getClientId() != null && !appRequest.getClientId().isEmpty()) {
try {
appRequest.setClientId(encryptionUtil.decrypt(appRequest.getClientId()));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
return new ResponseDTO(400, "FAILURE", "API 키 정보가 올바르지 않습니다.");
}
appRequest.setType(AppRequestType.PROD_MODIFY);
} else {
appRequest.setType(AppRequestType.PROD_NEW);
}
AppRequestDTO appRequestDTO = appServiceFacade.createAppRequest(appRequest);
appServiceFacade.beginApproval(appRequestDTO.getApproval().getId());
return new ResponseDTO(200, "SUCCESS", appRequestDTO.getType().getDescription() + " 신청이 완료되었습니다.");
}
@GetMapping("/dev")
public List<ClientSimpleDTO> apiKeyList() {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
List<ClientDTO> apikeys = appServiceFacade.getApiKeyList(user.getPortalOrg(), STG_SERVER);
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
for (ClientDTO apikey : apikeys) {
apikey.getApiList().forEach(api -> {
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
});
}
return apikeys.stream().map(credentialMapper::mapToClientSimpleDTO).collect(Collectors.toList());
}
@PostMapping("/prod")
public ClientSimpleDTO apiKeyProdList(@RequestBody AppRequestDTO appRequest) {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
try {
ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), encryptionUtil.decrypt(appRequest.getClientId()), PROD_SERVER);
ApiSearchData apiSearchData = apiSearchFacade.loadAllApiData();
apiKey.getApiList().forEach(api -> {
api.setService(apiSearchData.getServicesByApiId().get(api.getApiId()).getGroupName());
});
apiKey.setClientid(encryptionUtil.encrypt(apiKey.getClientid()));
return credentialMapper.mapToClientSimpleDTO(apiKey);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
return new ClientSimpleDTO();
}
}
}
@@ -0,0 +1,263 @@
package com.eactive.apim.portal.apps.app.controller;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.service.AppServiceFacade;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.ApiServiceHelper;
import com.eactive.apim.portal.common.util.EncryptionUtil;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/myapikey")
@RequiredArgsConstructor
public class MyAppController {
private final static String PROD_SERVER = "PROD";
private final static String STG_SERVER = "STG";
public static final String API_KEY_LIST = "apps/mypage/apiKeyList";
public static final String API_KEY_DETAIL = "apps/mypage/apiKeyDetail";
public static final String API_KEY_PROD_LIST = "apps/mypage/apiKeyProdList";
public static final String API_KEY_PROD_DETAIL = "apps/mypage/apiKeyProdDetail";
public static final String API_KEY_REQUEST_HISTORY = "apps/mypage/apiRequestHistory";
public static final String API_KEY_REQUEST_DETAIL = "apps/mypage/apiRequestDetail";
public static final String API_KEY_REQUEST_PROD_HISTORY = "apps/mypage/apiRequestProdHistory";
public static final String API_KEY_REQUEST_PROD_DETAIL = "apps/mypage/apiRequestProdDetail";
private final AppServiceFacade appServiceFacade;
private final ApiServiceService apiServiceService;
private final ApiService apiService;
private final EncryptionUtil encryptionUtil;
private final ApiServiceHelper apiServiceHelper;
@GetMapping
@Secured("ROLE_APP")
public ModelAndView apiKeyList(@PageableDefault Pageable pageable, Model model) {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
Page<ClientDTO> apikeys = appServiceFacade.getApiKeyPageForServer(pageable, user.getPortalOrg(), STG_SERVER);
model.addAttribute("apiKeys", apikeys.getContent());
model.addAttribute("page", apikeys);
return new ModelAndView(API_KEY_LIST);
}
@GetMapping("/api_key_prod_page")
@Secured("ROLE_APP")
public ModelAndView apiKeyProdList(@PageableDefault Pageable pageable, Model model) {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
Page<ClientDTO> apikeys = appServiceFacade.getApiKeyPageForServer(pageable, user.getPortalOrg(), PROD_SERVER);
model.addAttribute("apiKeys", apikeys.getContent());
model.addAttribute("page", apikeys);
return new ModelAndView(API_KEY_PROD_LIST);
}
@GetMapping("/api_key_prod_detail")
@Secured("ROLE_APP")
public ModelAndView apiKeyProdDetail(@RequestParam(value = "id", required = false) String id, Model model) {
if(id == null) {
return new ModelAndView("redirect:/myapikey/api_key_prod_page");
}
ClientDTO apiKey = null;
try {
apiKey = appServiceFacade.getApiKey(SecurityUtil.getPortalAuthenticatedUser().getPortalOrg().getId(), encryptionUtil.decrypt(id), PROD_SERVER);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
return new ModelAndView("/");
}
if (apiKey == null) {
return new ModelAndView("/");
} else {
ApiGroupSearch search = new ApiGroupSearch();
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
Map<String, ApiServiceDTO> mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices);
apiKey.getApiList().forEach(api -> {
ApiServiceDTO serviceDTO = mainIconsMap.get(api.getApiId());
api.setService(serviceDTO.getGroupName());
});
model.addAttribute("apiKey", apiKey);
return new ModelAndView(API_KEY_PROD_DETAIL);
}
}
@GetMapping("/api_key_detail")
@Secured("ROLE_APP")
public ModelAndView apiKeyDetail(@RequestParam(value = "id", required = false) String id, Model model) {
if(id == null) {
return new ModelAndView("redirect:/myapikey");
}
ClientDTO apiKey = appServiceFacade.getApiKey(SecurityUtil.getPortalAuthenticatedUser().getPortalOrg().getId(), id, STG_SERVER);
if (apiKey == null) {
return new ModelAndView("/");
} else {
ApiGroupSearch search = new ApiGroupSearch();
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(search);
Map<String, ApiServiceDTO> mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices);
apiKey.getApiList().forEach(api -> {
ApiServiceDTO serviceDTO = mainIconsMap.get(api.getApiId());
api.setService(serviceDTO.getGroupName());
});
List<AppRequestDTO> prodHistory = appServiceFacade.getProdApiRequestHistory(apiKey.getClientid());
model.addAttribute("apiKey", apiKey);
model.addAttribute("prodhistory", prodHistory);
return new ModelAndView(API_KEY_DETAIL);
}
}
@PostMapping("/api_key_request/cancel")
@Secured("ROLE_API_KEY_REQUEST")
public String apiRequestCancel(@RequestParam String id) {
appServiceFacade.cancelApiRequest(id, SecurityUtil.getPortalAuthenticatedUser().getPortalOrg());
return "redirect:/myapikey/api_key_request/history";
}
@GetMapping("/api_key_request/history")
@Secured("ROLE_API_KEY_REQUEST_VIEW")
public ModelAndView apiRequestHistory(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, Model model) {
Page<AppRequestDTO> apiRequests = appServiceFacade.getApiRequestHistory(pageable, SecurityUtil.getUserOrg());
for (AppRequestDTO request : apiRequests.getContent()) {
if (!StringUtils.isEmpty(request.getApiGroupList())) {
String[] apiGroupList = request.getApiGroupList().split(",");
ApiServiceDTO firstService = apiServiceService.getApiGroupById(apiGroupList[0]);
if (apiGroupList.length == 1) {
request.setContents(firstService.getGroupName());
} else {
request.setContents(firstService.getGroupName() + "" + (apiGroupList.length - 1) + "개의 API 그룹");
}
}
if (StringUtils.isEmpty(request.getContents())) {
if (!StringUtils.isEmpty(request.getApiList())) {
String[] apiList = request.getApiList().split(",");
ApiSpecInfoDto firstApi = apiService.selectDetail(apiList[0]);
if (apiList.length == 1) {
request.setContents(firstApi.getApiName());
} else {
request.setContents(firstApi.getApiName() + "" + (apiList.length - 1) + "개의 API");
}
}
}
}
model.addAttribute("requests", apiRequests.getContent());
model.addAttribute("page", apiRequests);
return new ModelAndView(API_KEY_REQUEST_HISTORY);
}
@GetMapping("/api_key_request/prod_history")
@Secured("ROLE_API_KEY_REQUEST_VIEW")
public ModelAndView apiRequestProdHistory(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, Model model) {
Page<AppRequestDTO> apiRequests = appServiceFacade.getApiRequestProdHistory(pageable, SecurityUtil.getUserOrg());
for (AppRequestDTO request : apiRequests.getContent()) {
if (!StringUtils.isEmpty(request.getApiGroupList())) {
String[] apiGroupList = request.getApiGroupList().split(",");
ApiServiceDTO firstService = apiServiceService.getApiGroupById(apiGroupList[0]);
if (apiGroupList.length == 1) {
request.setContents(firstService.getGroupName());
} else {
request.setContents(firstService.getGroupName() + "" + (apiGroupList.length - 1) + "개의 API 그룹");
}
}
if (StringUtils.isEmpty(request.getContents())) {
if (!StringUtils.isEmpty(request.getApiList())) {
String[] apiList = request.getApiList().split(",");
ApiSpecInfoDto firstApi = apiService.selectDetail(apiList[0]);
if (apiList.length == 1) {
request.setContents(firstApi.getApiName());
} else {
request.setContents(firstApi.getApiName() + "" + (apiList.length - 1) + "개의 API");
}
}
}
}
model.addAttribute("requests", apiRequests.getContent());
model.addAttribute("page", apiRequests);
return new ModelAndView(API_KEY_REQUEST_PROD_HISTORY);
}
@GetMapping("/api_key_request/detail")
@Secured("ROLE_API_KEY_REQUEST_VIEW")
public ModelAndView apiRequestDetail(@RequestParam(value = "id", required = false) String id, Model model) {
if (id == null) {
return new ModelAndView("redirect:/myapikey/api_key_request/history");
}
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
AppRequestDTO appRequest = appServiceFacade.getApiRequestByIdAndPortalOrg(id, user.getPortalOrg());
model.addAttribute("appRequest", appRequest);
return new ModelAndView(API_KEY_REQUEST_DETAIL);
}
@GetMapping("/api_key_request/prod_detail")
@Secured("ROLE_API_KEY_REQUEST_VIEW")
public ModelAndView apiRequestProdDetail(@RequestParam(value = "id", required = false) String id, Model model) {
if(id == null) {
return new ModelAndView("redirect:/myapikey/api_key_request/prod_history");
}
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
AppRequestDTO appRequest = appServiceFacade.getApiRequestProdByIdAndPortalOrg(id, user.getPortalOrg());
model.addAttribute("appRequest", appRequest);
return new ModelAndView(API_KEY_REQUEST_PROD_DETAIL);
}
}
@@ -0,0 +1,57 @@
package com.eactive.apim.portal.apps.app.dto;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.approval.entity.ApprovalStatus;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.approval.dto.ApprovalDTO;
import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import lombok.Data;
@Data
public class AppRequestDTO {
private String id;
private PortalOrgDTO org;
private ApprovalStatus status; // 승인 대기, 승인 완료, 승인 거부
private AppRequestType type; //신규, 변경
private ApprovalDTO approval;
@NotEmpty
private String apiList = ""; //comma separated api id list
private String apiGroupList = ""; //comma separated api group id list
private String clientId;
private String clientName;
private String refClient;
private List<ApiServiceDTO> apiServiceDTOList = new ArrayList<>();
private List<ApiSpecInfoDto> apiSpecList = new ArrayList<>();
private String contents;
private String reason;
private LocalDateTime createdDate;
private String prevApiList;
private List<ApiSpecInfoDto> removedApiList = new ArrayList<>();
public String getPrevApiList() {
return prevApiList == null ? "" : prevApiList;
}
}
@@ -0,0 +1,50 @@
package com.eactive.apim.portal.apps.app.dto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ClientDTO {
private String clientid;
private String accesstokenvalidityseconds;
private String allowedips;
private String authorities;
private String autoapprove;
private String clientname;
private String clientsecret;
private String granttypes;
private String modifiedby;
private LocalDateTime modifiedon;
private String redirecturi;
private String refreshtokenvalidityseconds;
private String resourceids;
private String scope;
private String securitykey;
private String orgid;
private String orgname;
private String appstatus;
private List<ApiServiceApiDTO> apiList = new ArrayList<>();
}
@@ -0,0 +1,18 @@
package com.eactive.apim.portal.apps.app.dto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ClientSimpleDTO {
private String clientid;
private String clientname;
private List<ApiServiceApiDTO> apiList = new ArrayList<>();
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.apps.app.dto;
import java.io.Serializable;
import lombok.Data;
@Data
public class PortalOrgDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String orgName;
}
@@ -0,0 +1,16 @@
package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceApiMapper;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.approval.mapper.ApprovalMapper;
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {PortalOrgMapper.class, ApiServiceApiMapper.class, ApprovalMapper.class})
public interface AppRequestMapper extends GenericMapper<AppRequestDTO, AppRequest> {
}
@@ -0,0 +1,15 @@
package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiServiceApiMapper;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.eai.data.entity.onl.authserver.ClientEntity;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {ApiServiceApiMapper.class})
public interface ClientEntityMapper extends GenericMapper<ClientDTO, ClientEntity> {
}
@@ -0,0 +1,16 @@
package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.app.entity.Credential;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiSpecInfoMapper;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.dto.ClientSimpleDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {ApiSpecInfoMapper.class})
public interface CredentialMapper extends GenericMapper<ClientDTO, Credential> {
ClientSimpleDTO mapToClientSimpleDTO(ClientDTO client);
}
@@ -0,0 +1,15 @@
package com.eactive.apim.portal.apps.app.mapper;
import com.eactive.apim.portal.app.entity.ProdClient;
import com.eactive.apim.portal.apps.apiservice.mapper.ApiSpecInfoMapper;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {ApiSpecInfoMapper.class})
public interface ProdClientMapper extends GenericMapper<ClientDTO, ProdClient> {
}
@@ -0,0 +1,195 @@
package com.eactive.apim.portal.apps.app.service;
import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.app.entity.Credential;
import com.eactive.apim.portal.app.repository.CredentialRepository;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apis.service.ApiService;
import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceApiDTO;
import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService;
import com.eactive.apim.portal.apps.app.dto.AppRequestDTO;
import com.eactive.apim.portal.apps.app.dto.ClientDTO;
import com.eactive.apim.portal.apps.app.mapper.AppRequestMapper;
import com.eactive.apim.portal.apps.app.mapper.CredentialMapper;
import com.eactive.apim.portal.apps.approval.service.ApprovalService;
import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO;
import com.eactive.apim.portal.apps.user.mapper.PortalOrgMapper;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.apim.portal.common.util.ApiServiceHelper;
import com.eactive.apim.portal.portalorg.entity.PortalOrg;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
@Transactional
@RequiredArgsConstructor
public class AppServiceFacade {
private final CredentialRepository credentialRepository;
private final AppRequestRepository appRequestRepository;
private final ApprovalService approvalService;
private final ApiServiceService apiServiceService;
private final ApiService apiService;
private final CredentialMapper credentialMapper;
private final AppRequestMapper appRequestMapper;
private final PortalOrgMapper portalOrgMapper;
private final ApiServiceHelper apiServiceHelper;
public Page<ClientDTO> getApiKeyPageForServer(Pageable pageable, PortalOrg portalOrg, String server) {
Page<Credential> clients = credentialRepository.findAllByOrgidAndServer(pageable, portalOrg.getId(), server);
return clients.map(credentialMapper::toVo);
}
public List<ClientDTO> getApiKeyList(PortalOrg portalOrg, String server) {
List<Credential> clients = credentialRepository.findAllByOrgidAndServer(portalOrg.getId(), server);
return clients.stream().map(credentialMapper::toVo).collect(Collectors.toList());
}
public ClientDTO getApiKey(String orgid, String clientId, String server) {
return credentialRepository.findByClientidAndOrgidAndServer(clientId, orgid, server).map(credentialMapper::toVo).orElse(null);
}
public Page<AppRequestDTO> getApiRequestHistory(Pageable pageable, PortalOrgDTO portalOrg) {
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.NEW,AppRequestType.MODIFY, AppRequestType.DELETE)).map(appRequestMapper::toVo);
}
public Page<AppRequestDTO> getApiRequestProdHistory(Pageable pageable, PortalOrgDTO portalOrg) {
return appRequestRepository.findAllByOrgAndTypeIsIn(pageable, portalOrgMapper.toEntity(portalOrg), Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(appRequestMapper::toVo);
}
public AppRequestDTO createAppRequest(AppRequestDTO appRequest) {
setApiGroupsFromApiList(appRequest);
AppRequest request = appRequestRepository.save(appRequestMapper.toEntity(appRequest));
if (request.getType() != null && (request.getType().equals(AppRequestType.MODIFY) || request.getType().equals(AppRequestType.DELETE) ||
request.getType().equals(AppRequestType.PROD_MODIFY) || request.getType().equals(AppRequestType.PROD_DELETE))) {
Optional<Credential> client = credentialRepository.findById(request.getClientId());
if (client.isPresent()) {
request.setClientName(client.get().getClientname());
if(client.get().getApiList() != null) {
// 기존 API 목록을 저장
String existingApiList = client.get().getApiList().stream()
.map(ApiSpecInfo::getApiId)
.collect(Collectors.joining(","));
request.setPrevApiList(existingApiList);
}
if(AppRequestType.DELETE.equals(request.getType()) || AppRequestType.PROD_DELETE.equals(request.getType())) {
request.setApiList(request.getPrevApiList());
}
} else {
throw new NotFoundException("Client not found");
}
}
request.setCreatedDate(LocalDateTime.now());
Approval approval = approvalService.createAppApproval(request);
request.setApproval(approval);
request = appRequestRepository.save(request);
return appRequestMapper.toVo(request);
}
private void setApiGroupsFromApiList(AppRequestDTO appRequest) {
if (StringUtils.isEmpty(appRequest.getApiList())) {
return;
}
Set<String> uniqueGroupIds = Arrays.stream(appRequest.getApiList().split(","))
.map(apiId -> apiServiceService.findApiServiceByApiId(apiId))
.filter(Objects::nonNull)
.flatMap(apiService -> apiService.getApiGroupApiList().stream())
.map(ApiServiceApiDTO::getApiGroupId)
.collect(Collectors.toSet());
appRequest.setApiGroupList(String.join(",", uniqueGroupIds));
}
public void beginApproval(String approvalId) {
approvalService.beginApproval(approvalId);
}
public void cancelApiRequest(String id, PortalOrg portalOrg) {
appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(approvalService::cancelAppApproval);
}
public List<AppRequestDTO> getProdApiRequestHistory(String clientid) {
return appRequestRepository.findAllByClientIdsContainsAndTypeIsIn(clientid,Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).stream().map(appRequestMapper::toVo).collect(Collectors.toList());
}
public AppRequestDTO getApiRequestProdByIdAndPortalOrg(String id, PortalOrg portalOrg) {
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrgAndTypeIsIn(id, portalOrg,
Arrays.asList(AppRequestType.PROD_NEW,AppRequestType.PROD_MODIFY, AppRequestType.PROD_DELETE)).map(this::convertToAppRequestDTO).orElse(null);
if(appRequest != null) {
processApiGroups(appRequest);
processApiList(appRequest);
}
return appRequest;
}
public AppRequestDTO getApiRequestByIdAndPortalOrg(String id, PortalOrg org) {
AppRequestDTO appRequest = appRequestRepository.findByIdAndOrg(id, org).map(this::convertToAppRequestDTO).orElse(null);
if(appRequest != null) {
processApiGroups(appRequest);
processApiList(appRequest);
}
return appRequest;
}
private AppRequestDTO convertToAppRequestDTO(AppRequest entity) {
AppRequestDTO dto = appRequestMapper.toVo(entity);
// prevApiList null 처리
if (dto.getPrevApiList() == null) {
dto.setPrevApiList("");
}
return dto;
}
private void processApiGroups(AppRequestDTO appRequest) {
if (!StringUtils.isEmpty(appRequest.getApiGroupList())) {
String[] apiGroupList = appRequest.getApiGroupList().split(",");
for (String apiGroupId : apiGroupList) {
appRequest.getApiServiceDTOList().add(apiServiceService.getApiGroupById(apiGroupId));
}
}
}
private void processApiList(AppRequestDTO appRequest) {
if (!StringUtils.isEmpty(appRequest.getApiList())) {
String[] apiList = appRequest.getApiList().split(",");
List<ApiServiceDTO> apiServices = apiServiceService.searchApiGroups(new ApiGroupSearch());
Map<String, ApiServiceDTO> mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices);
for (String apiId : apiList) {
ApiServiceDTO serviceDTO = mainIconsMap.get(apiId);
ApiSpecInfoDto spec = apiService.selectDetail(apiId);
spec.setService(serviceDTO.getGroupName());
appRequest.getApiSpecList().add(spec);
}
}
}
}
@@ -0,0 +1,34 @@
package com.eactive.apim.portal.apps.approval.dto;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ApprovalDTO {
private String id;
private ApprovalState approvalStatus;
private ApprovalType approvalType;
private String targetId;
private PortalUser requester;
private List<Approver> approvers = new ArrayList<>();
private String approvalSubject; //제목
private String approvalDetail; //승인 요청 내용
private LocalDateTime createdDate;
private LocalDateTime approvalDate;
}
@@ -0,0 +1,33 @@
package com.eactive.apim.portal.apps.approval.dto;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.apim.portal.approval.statemachine.ApprovalState;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class ApproverDTO {
private String id;
private ApprovalState approvalStatus;
private ApprovalType approvalType;
private String targetId;
private PortalUser requester;
private List<ApproverDTO> approvers = new ArrayList<>();
private String approvalSubject; //제목
private String approvalDetail; //승인 요청 내용
private LocalDateTime createdDate;
private LocalDateTime approvalDate;
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.apps.approval.mapper;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.apps.approval.dto.ApprovalDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
@Mapper(config = BaseMapperConfig.class, uses = {ApproverMapper.class})
public interface ApprovalMapper extends GenericMapper<ApprovalDTO, Approval> {
}
@@ -0,0 +1,44 @@
package com.eactive.apim.portal.apps.approval.mapper;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.entity.ApproverId;
import com.eactive.apim.portal.apps.approval.dto.ApproverDTO;
import com.eactive.eai.data.mapper.BaseMapperConfig;
import com.eactive.eai.data.mapper.GenericMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = BaseMapperConfig.class, uses = {})
public interface ApproverMapper extends GenericMapper<ApproverDTO, Approver> {
@Mapping(target = "id", expression = "java(approverIdToString(approver.getId()))")
@Override
ApproverDTO toVo(Approver approver);
@Mapping(target = "id", expression = "java(stringToApproverId(approverDTO.getId()))")
@Override
Approver toEntity(ApproverDTO approverDTO);
default String approverIdToString(ApproverId approverId) {
if (approverId == null) {
return null;
}
return approverId.getOrdinal().toString();
}
default ApproverId stringToApproverId(String id) {
if (id == null) {
return null;
}
String[] parts = id.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid ApproverId string format");
}
ApproverId approverId = new ApproverId();
approverId.setApproval(parts[0]);
approverId.setOrdinal(Integer.parseInt(parts[1]));
return approverId;
}
}
@@ -0,0 +1,101 @@
package com.eactive.apim.portal.apps.approval.service;
import com.eactive.apim.portal.apprequest.entity.AppRequest;
import com.eactive.apim.portal.approval.entity.Approval;
import com.eactive.apim.portal.approval.entity.ApprovalType;
import com.eactive.apim.portal.approval.entity.Approver;
import com.eactive.apim.portal.approval.entity.ApproverId;
import com.eactive.apim.portal.approval.entity.ApproverStatus;
import com.eactive.apim.portal.approval.repository.ApprovalRepository;
import com.eactive.apim.portal.approval.statemachine.ApprovalEvent;
import com.eactive.apim.portal.approval.statemachine.ApprovalStateMachine;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLine;
import com.eactive.apim.portal.approvalline.entity.PortalApprovalLineUser;
import com.eactive.apim.portal.approvalline.repository.PortalApprovalLineRepository;
import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.apim.portal.user.entity.UserInfo;
import java.util.HashMap;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class ApprovalService {
private final ApprovalRepository approvalRepository;
private final PortalApprovalLineRepository portalApprovalLineRepository;
private final ApprovalStateMachine approvalStateMachine;
public Approval createAppApproval(AppRequest request) {
Optional<PortalApprovalLine> optLine = portalApprovalLineRepository.findPortalApprovalLineByApprovalType(ApprovalType.APP.name());
if (optLine.isPresent()) {
Approval approval = new Approval();
approval.setApprovalType(ApprovalType.APP);
approval.setTargetId(request.getId());
approval.setRequester(SecurityUtil.getPortalAuthenticatedUser());
approval.setApprovalSubject("[" + request.getOrg().getOrgName() + "] " + request.getType().getDescription() + " 승인");
for (PortalApprovalLineUser user : optLine.get().getPortalApprovalLineUsers()) {
this.addApprover(approval, user.getUser(), user.getApprovalOrder());
}
return approvalRepository.save(approval);
}
return null;
}
public Approval createUserApproval(PortalUser newPortalUser) {
Optional<PortalApprovalLine> optLine = portalApprovalLineRepository.findPortalApprovalLineByApprovalType(ApprovalType.USER.name());
if (optLine.isPresent()) {
Approval approval = new Approval();
approval.setApprovalType(ApprovalType.USER);
approval.setTargetId(newPortalUser.getId());
approval.setRequester(newPortalUser);
approval.setApprovalSubject( newPortalUser.getPortalOrg().getOrgName() + " 사용 신청 승인");
for (PortalApprovalLineUser user : optLine.get().getPortalApprovalLineUsers()) {
this.addApprover(approval, user.getUser(), user.getApprovalOrder());
}
approvalStateMachine.transition(approval, ApprovalEvent.BEGIN, new HashMap<>());
return approvalRepository.save(approval);
}
return null;
}
public void addApprover(Approval apprval, UserInfo user, int ordinal) {
Approver newApprover = new Approver();
ApproverId approverId = new ApproverId(apprval.getId(), ordinal);
newApprover.setId(approverId);
newApprover.setUser(user);
newApprover.setApproverStatus(ApproverStatus.PENDING);
newApprover.setApproval(apprval);
apprval.getApprovers().add(newApprover);
}
public void beginApproval(String approvalId) {
Optional<Approval> approval = approvalRepository.findById(approvalId);
if (approval.isPresent()) {
approvalStateMachine.transition(approval.get(), ApprovalEvent.BEGIN, new HashMap<>());
approvalRepository.save(approval.get());
}
}
public void cancelAppApproval(AppRequest appRequest) {
Optional<Approval> optApproval = approvalRepository.findById(appRequest.getApproval().getId());
if (optApproval.isPresent()) {
approvalStateMachine.transition(optApproval.get(), ApprovalEvent.CANCEL, new HashMap<>());
approvalRepository.save(optApproval.get());
}
}
}
@@ -0,0 +1,11 @@
package com.eactive.apim.portal.apps.auth.exception;
public class AuthNumberNotFoundException extends RuntimeException {
public AuthNumberNotFoundException(String message) {
super(message);
}
public AuthNumberNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,95 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto;
import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO;
import com.eactive.apim.portal.common.util.SecurityUtil;
import lombok.var;
import java.util.List;
import java.util.stream.Collectors;
public class ApiPermissionFilter {
public static List<ApiSpecInfoDto> filterApisByPermissions(List<ApiSpecInfoDto> apis) {
boolean isAuthenticated = SecurityUtil.isAuthenticated();
String roleCode = null;
String org = null;
if (isAuthenticated) {
// null 체크 추가
var user = SecurityUtil.getPortalAuthenticatedUser();
if (user != null && user.getRoleCode() != null) {
roleCode = user.getRoleCode().toString();
}
var userOrg = SecurityUtil.getUserOrg();
if (userOrg != null) {
org = userOrg.getId();
}
}
String finalRoleCode = roleCode;
String finalOrg = org;
return apis.stream()
.filter(spec -> checkApiPermissions(spec, isAuthenticated, finalOrg, finalRoleCode))
.collect(Collectors.toList());
}
public static List<ApiServiceDTO> filterServicesByPermissions(List<ApiServiceDTO> services) {
boolean isAuthenticated = SecurityUtil.isAuthenticated();
String roleCode = null;
String org = null;
if (isAuthenticated) {
// null 체크 추가
var user = SecurityUtil.getPortalAuthenticatedUser();
if (user != null && user.getRoleCode() != null) {
roleCode = user.getRoleCode().toString();
}
var userOrg = SecurityUtil.getUserOrg();
if (userOrg != null) {
org = userOrg.getId();
}
}
String finalRoleCode = roleCode;
String finalOrg = org;
return services.stream()
.filter(service -> checkServicePermissions(service, isAuthenticated, finalOrg, finalRoleCode))
.collect(Collectors.toList());
}
private static boolean checkServicePermissions(ApiServiceDTO spec, boolean isAuthenticated,
String org, String roleCode) {
if (!isAuthenticated) {
return (spec.getDisplayOrg() == null || spec.getDisplayOrg().isEmpty()) && (spec.getDisplayRoleCode() == null || spec.getDisplayRoleCode().isEmpty());
}
boolean orgMatch = org != null && (spec.getDisplayOrg() == null ||
spec.getDisplayOrg().isEmpty() ||
spec.getDisplayOrg().contains(org));
boolean roleMatch = roleCode != null && (spec.getDisplayRoleCode() == null ||
spec.getDisplayRoleCode().isEmpty() ||
spec.getDisplayRoleCode().contains(roleCode));
return orgMatch || roleMatch;
}
private static boolean checkApiPermissions(ApiSpecInfoDto spec, boolean isAuthenticated,
String org, String roleCode) {
if (!isAuthenticated) {
return (spec.getDisplayOrg() == null || spec.getDisplayOrg().isEmpty()) && (spec.getDisplayRoleCode() == null || spec.getDisplayRoleCode().isEmpty());
}
boolean orgMatch = org != null && (spec.getDisplayOrg() == null ||
spec.getDisplayOrg().isEmpty() ||
spec.getDisplayOrg().contains(org));
boolean roleMatch = roleCode != null && (spec.getDisplayRoleCode() == null ||
spec.getDisplayRoleCode().isEmpty() ||
spec.getDisplayRoleCode().contains(roleCode));
return orgMatch || roleMatch;
}
}
@@ -0,0 +1,30 @@
package com.eactive.apim.portal.apps.auth.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.security.SecureRandom;
import java.util.Arrays;
@Component
public class AuthNumberGenerator {
private final Environment env;
@Autowired
public AuthNumberGenerator(Environment env) {
this.env = env;
}
public String generateAuthNumber() {
if (isDevProfile()) {
return "123456";
}
SecureRandom rand = new SecureRandom();
return String.format("%06d", 100000 + rand.nextInt(900000));
}
private boolean isDevProfile() {
return Arrays.asList(env.getActiveProfiles()).contains("dev");
}
}
@@ -0,0 +1,8 @@
package com.eactive.apim.portal.apps.auth.service;
public interface AuthNumberService {
void sendRequestAuthNumber(String recipientKey, String msgType);
boolean verifyAuthNumber(String recipientKey, String authNumber);
}
@@ -0,0 +1,98 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth;
import com.eactive.apim.portal.portaluser.service.AuthNumberException;
import com.eactive.apim.portal.template.service.MessageRecipient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
@Transactional
public class AuthNumberServiceImpl implements AuthNumberService {
private static final Logger logger = LoggerFactory.getLogger(AuthNumberServiceImpl.class);
private final AuthNumberStorage storage;
private final AuthNumberGenerator generator;
private final MessageSender messageSender;
@Value("${auth-ttl:300}")
private int authNumberExpirationTime;
@Value("${auth.resend.limit.seconds:30}")
private int resendLimitSeconds;
@Autowired
public AuthNumberServiceImpl(AuthNumberStorage storage,
AuthNumberGenerator generator,
MessageSender messageSender) {
this.storage = storage;
this.generator = generator;
this.messageSender = messageSender;
}
@Override
@Transactional(noRollbackFor = AuthNumberException.class)
public void sendRequestAuthNumber(String recipientKey, String msgType) {
logger.info("Sending auth number to: {} via {}", recipientKey, msgType);
validateResendTime(recipientKey);
String authNumber = generator.generateAuthNumber();
MessageRecipient recipient = createMessageRecipient(recipientKey, msgType);
messageSender.sendAuthMessage(recipient, authNumber, msgType);
storage.saveAuthNumber(recipientKey, authNumber,
LocalDateTime.now().plusSeconds(authNumberExpirationTime));
}
@Override
@Transactional(noRollbackFor = AuthNumberException.class)
public boolean verifyAuthNumber(String recipientKey, String authNumber) {
logger.info("Verifying auth number for: {}", recipientKey);
TwoFactorAuth storedAuth = storage.getAuthNumber(recipientKey)
.orElseThrow(() -> new AuthNumberException("인증번호가 존재하지 않습니다. 인증번호를 다시 발송해주세요."));
if (storedAuth.getExpiresAt().isBefore(LocalDateTime.now())) {
storage.deleteAuthNumber(recipientKey);
throw new AuthNumberException("입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.");
}
if (authNumber.equals(storedAuth.getAuthNumber())) {
return true;
} else {
throw new AuthNumberException("입력된 인증번호가 올바르지 않습니다.");
}
}
private void validateResendTime(String recipientKey) {
storage.getAuthNumber(recipientKey).ifPresent(existingAuth -> {
LocalDateTime now = LocalDateTime.now();
if (existingAuth.getExpiresAt().minusSeconds(authNumberExpirationTime)
.plusSeconds(resendLimitSeconds).isAfter(now)) {
throw new AuthNumberException("잠시 후에 다시 시도해 주세요.");
}
});
}
private MessageRecipient createMessageRecipient(String recipientKey, String msgType) {
MessageRecipient recipient = new MessageRecipient();
recipient.setUserId(recipientKey);
if ("SMS".equalsIgnoreCase(msgType)) {
recipient.setPhone(recipientKey);
} else if ("EMAIL".equalsIgnoreCase(msgType)) {
recipient.setUserId(recipientKey);
} else {
throw new IllegalArgumentException("Unsupported message type: " + msgType);
}
return recipient;
}
}
@@ -0,0 +1,42 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth;
import com.eactive.apim.portal.portaluser.repository.TwoFactorAuthRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Optional;
@Component
public class AuthNumberStorage {
private final TwoFactorAuthRepository twoFactorAuthRepository;
@Autowired
public AuthNumberStorage(TwoFactorAuthRepository twoFactorAuthRepository) {
this.twoFactorAuthRepository = twoFactorAuthRepository;
}
public void saveAuthNumber(String recipientKey, String authNumber, LocalDateTime expiresAt) {
Optional<TwoFactorAuth> exist = twoFactorAuthRepository.findById(recipientKey);
if (exist.isPresent()) {
TwoFactorAuth old = exist.get();
old.setAuthNumber(authNumber);
old.setExpiresAt(expiresAt);
twoFactorAuthRepository.save(old);
} else {
TwoFactorAuth auth = new TwoFactorAuth(recipientKey, authNumber, expiresAt);
twoFactorAuthRepository.save(auth);
}
}
public Optional<TwoFactorAuth> getAuthNumber(String recipientKey) {
return twoFactorAuthRepository.findById(recipientKey);
}
public void deleteAuthNumber(String recipientKey) {
twoFactorAuthRepository.deleteById(recipientKey);
}
}
@@ -0,0 +1,34 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.event.RequestAuthNumberEvent;
import com.eactive.apim.portal.template.service.MessageHandlerService;
import com.eactive.apim.portal.template.service.MessageRecipient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
@Component
public class MessageSender {
private final MessageHandlerService messageHandlerService;
@Autowired
public MessageSender(MessageHandlerService messageHandlerService) {
this.messageHandlerService = messageHandlerService;
}
public void sendAuthMessage(MessageRecipient recipient, String authNumber, String msgType) {
if ("sms".equalsIgnoreCase(msgType)) {
recipient.setPhone(recipient.getUserId());
} else if ("email".equalsIgnoreCase(msgType)) {
recipient.setUserId(recipient.getUserId());
} else {
throw new IllegalArgumentException("Unsupported message type: " + msgType);
}
HashMap<String, Object> params = new HashMap<>();
params.put("authNumber", authNumber);
messageHandlerService.publishEvent(RequestAuthNumberEvent.KEY, recipient, params);
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.entity.PasswordResetToken;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, String> {
}
@@ -0,0 +1,31 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.entity.PasswordResetToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
@Transactional
public class PasswordResetTokenService {
@Autowired
private PasswordTokenRepository passwordTokenRepository;
public PasswordResetToken createToken(String email) {
String token = UUID.randomUUID().toString();
LocalDateTime expiresAt = LocalDateTime.now().plusHours(24);
PasswordResetToken tokenObj = new PasswordResetToken(token, email, expiresAt);
return passwordTokenRepository.save(tokenObj);
}
public PasswordResetToken findByToken(String token) {
return passwordTokenRepository.findById(token).orElse(null);
}
public void deleteToken(PasswordResetToken token) {
passwordTokenRepository.delete(token);
}
}
@@ -0,0 +1,7 @@
package com.eactive.apim.portal.apps.auth.service;
import com.eactive.apim.portal.portaluser.entity.PasswordResetToken;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PasswordTokenRepository extends JpaRepository<PasswordResetToken, String> {
}
@@ -0,0 +1,50 @@
package com.eactive.apim.portal.apps.community.faq.controller;
import com.eactive.apim.portal.apps.community.faq.dto.FaqDTO;
import com.eactive.apim.portal.apps.community.faq.dto.FaqSearch;
import com.eactive.apim.portal.apps.community.faq.service.FaqFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class FaqController {
public static final String FAQ_QUESTION = "faqQuestion";
private final FaqFacade faqFacade;
@Autowired
public FaqController(FaqFacade faqFacade){
this.faqFacade = faqFacade;
}
@PostMapping("/faq_list")
public String postSearchView(@ModelAttribute("search") FaqSearch search, Model model,
@PageableDefault(sort = FAQ_QUESTION, direction = Sort.Direction.ASC) Pageable pageable) {
return searchView(search, pageable, model);
}
@GetMapping("/faq_list")
public String getSearchView(@PageableDefault(sort = FAQ_QUESTION,
direction = Sort.Direction.ASC) Pageable pageable, Model model) {
return searchView(new FaqSearch(), pageable, model);
}
private String searchView(FaqSearch search, Pageable pageable, Model model) {
Page<FaqDTO> page = faqFacade.getFaqs(search, pageable);
model.addAttribute("search", search);
model.addAttribute("page", page);
return "apps/community/mainFaqList";
}
}
@@ -0,0 +1,47 @@
package com.eactive.apim.portal.apps.community.faq.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode
public class FaqDTO {
private String id;
/**
* 답변내용
*/
@NotEmpty(message = "{field.required}")
private String faqAnswer;
/**
* 질문내용
*/
@NotEmpty(message = "{field.required}")
private String faqQuestion;
/**
* 사용여부
*/
private String useYn;
/**
* 첨부파일ID
*/
private String fileId;
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
}
@@ -0,0 +1,36 @@
package com.eactive.apim.portal.apps.community.faq.dto;
import com.eactive.apim.portal.common.search.*;
import com.eactive.apim.portal.portalfaq.entity.PortalFaq;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class FaqSearch implements BaseSearch<PortalFaq> {
private String searchWrd;
public String getSearchWrd() {
return searchWrd;
}
public void setSearchWrd(String searchWrd) {
this.searchWrd = searchWrd;
}
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
if(StringUtils.isNotEmpty(searchWrd)) {
models.add(new SpecificationSearchModel((root, query, criteriaBuilder) ->
criteriaBuilder.or(
criteriaBuilder.like(root.get("faqQuestion"), "%" + searchWrd + "%"),
criteriaBuilder.like(root.get("faqAnswer"), "%" + searchWrd + "%")
)
));
}
return models;
}
}
@@ -0,0 +1,20 @@
package com.eactive.apim.portal.apps.community.faq.mapper;
import com.eactive.apim.portal.apps.community.faq.dto.FaqDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import com.eactive.apim.portal.portalfaq.entity.PortalFaq;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface FaqMapper {
PortalFaq map(FaqDTO dto);
FaqDTO map(PortalFaq entity);
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.apps.community.faq.repository;
import com.eactive.apim.portal.portalfaq.entity.PortalFaq;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
@EMSDataSource
public interface FaqRepository extends JpaRepository<PortalFaq, String>, JpaSpecificationExecutor<PortalFaq> {
}
@@ -0,0 +1,10 @@
package com.eactive.apim.portal.apps.community.faq.service;
import com.eactive.apim.portal.apps.community.faq.dto.FaqDTO;
import com.eactive.apim.portal.apps.community.faq.dto.FaqSearch;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface FaqFacade {
Page<FaqDTO> getFaqs(FaqSearch search, Pageable pageable);
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.community.faq.service;
import com.eactive.apim.portal.apps.community.faq.dto.FaqDTO;
import com.eactive.apim.portal.apps.community.faq.dto.FaqSearch;
import com.eactive.apim.portal.apps.community.faq.mapper.FaqMapper;
import com.eactive.apim.portal.portalfaq.entity.PortalFaq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
@Service
public class FaqFacadeImpl implements FaqFacade {
private final FaqService faqService;
private final FaqMapper faqMapper;
@Autowired
public FaqFacadeImpl(FaqService faqService, FaqMapper faqMapper) {
this.faqService = faqService;
this.faqMapper = faqMapper;
}
@Override
public Page<FaqDTO> getFaqs(FaqSearch search, Pageable pageable) {
Specification<PortalFaq> useYnSpec = (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("useYn"), "Y");
Specification<PortalFaq> searchSpec = search.buildSpecification();
Specification<PortalFaq> finalSpec = useYnSpec.and(searchSpec);
Page<PortalFaq> faqPage = faqService.findAll(finalSpec, pageable);
return faqPage.map(faqMapper::map);
}
}
@@ -0,0 +1,36 @@
package com.eactive.apim.portal.apps.community.faq.service;
import com.eactive.apim.portal.apps.community.faq.repository.FaqRepository;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.apim.portal.portalfaq.entity.PortalFaq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class FaqService {
private final FaqRepository faqRepository;
@Autowired
public FaqService(FaqRepository faqRepository) {
this.faqRepository = faqRepository;
}
@Transactional(readOnly = true)
public Page<PortalFaq> findAll(Specification<PortalFaq> spec, Pageable pageable) {
return faqRepository.findAll(spec, pageable);
}
@Transactional(readOnly = true)
public PortalFaq findById(String id) {
return faqRepository.findById(id)
.orElseThrow(() -> new NotFoundException("FAQ not found with id: " + id));
}
}
@@ -0,0 +1,55 @@
package com.eactive.apim.portal.apps.community.notice.controller;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeSearch;
import com.eactive.apim.portal.apps.community.notice.service.PortalNoticeFacade;
import com.eactive.apim.portal.file.service.FileService;
import javax.annotation.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 공지사항 컨트롤러
*/
@Controller
@RequestMapping("/portalnotice")
public class PortalNoticeController {
private final PortalNoticeFacade portalNoticeFacade;
public PortalNoticeController(PortalNoticeFacade portalNoticeFacade) {
this.portalNoticeFacade = portalNoticeFacade;
}
@Resource(name = "fileService")
private FileService fileService;
@GetMapping
public String listNotices(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable,
@ModelAttribute("search") PortalNoticeSearch portalNoticeSearch, Model model) {
Page<PortalNoticeDTO> portalNotices = portalNoticeFacade.getNotices(portalNoticeSearch, pageable);
model.addAttribute("portalNotices", portalNotices.getContent());
model.addAttribute("page", portalNotices);
return "apps/community/mainNoticeList";
}
@GetMapping("/detail")
public String viewNotice(@RequestParam(value = "id", required = false) String id, Model model) {
if(id == null) {
return "redirect:/portalnotice";
}
PortalNoticeDTO portalNotice = portalNoticeFacade.getNotice(id);
model.addAttribute("portalNotice", portalNotice);
return "apps/community/mainNoticeDetail";
}
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.community.notice.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PortalNoticeDTO {
private String id;
@NotEmpty(message = "제목을 입력해주세요.")
@Size(max = 255, message = "제목은 255자를 초과할 수 없습니다.")
private String noticeSubject;
@NotEmpty(message = "내용을 입력해주세요.")
@Length(max = 4000, message = "내용은 4000자를 초과할 수 없습니다.")
private String noticeDetail;
private String fileId;
private Long readCount;
private boolean useYn;
private String inquirerName;
private LocalDateTime createdDate;
private LocalDateTime lastModifiedDate;
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.community.notice.dto;
import com.eactive.apim.portal.common.search.BaseSearch;
import com.eactive.apim.portal.common.search.ColumnSearchModel;
import com.eactive.apim.portal.common.search.SearchCondition;
import com.eactive.apim.portal.common.search.SearchModel;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
@Data
public class PortalNoticeSearch implements BaseSearch<PortalNotice> {
private String searchSubjectOrDetail;
private String searchUseYn;
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
if (StringUtils.isNotEmpty(searchSubjectOrDetail)) {
models.add(new ColumnSearchModel("noticeSubject", SearchCondition.LIKE, searchSubjectOrDetail));
models.add(new ColumnSearchModel("noticeDetail", SearchCondition.LIKE, searchSubjectOrDetail));
}
if (StringUtils.isNotEmpty(searchUseYn)) {
models.add(new ColumnSearchModel("useYn", SearchCondition.LIKE, searchUseYn));
}
return models;
}
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.apps.community.notice.mapper;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface PortalNoticeMapper {
PortalNotice toEntity(PortalNoticeDTO noticeDTO);
PortalNoticeDTO map(PortalNotice portalNotice);
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.apps.community.notice.repository;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@EMSDataSource
public interface PortalNoticeRepository extends JpaRepository<PortalNotice, String>, JpaSpecificationExecutor<PortalNotice> {
}
@@ -0,0 +1,13 @@
package com.eactive.apim.portal.apps.community.notice.service;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeSearch;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface PortalNoticeFacade {
List<PortalNoticeDTO> getLatestNotices();
Page<PortalNoticeDTO> getNotices(PortalNoticeSearch search, Pageable pageable);
PortalNoticeDTO getNotice(String id);
}
@@ -0,0 +1,48 @@
package com.eactive.apim.portal.apps.community.notice.service;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeSearch;
import com.eactive.apim.portal.apps.community.notice.mapper.PortalNoticeMapper;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("portalNoticeFacade")
@RequiredArgsConstructor
public class PortalNoticeFacadeImpl implements PortalNoticeFacade {
private final PortalNoticeService portalNoticeService;
private final PortalNoticeMapper portalNoticeMapper;
@Override
public List<PortalNoticeDTO> getLatestNotices() {
PortalNoticeSearch search = new PortalNoticeSearch();
Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "createdDate"));
// Pageable pageable = Pageable.ofSize(3);
Page<PortalNoticeDTO> notices = getNotices(search, pageable);
return notices.getContent();
}
@Override
public Page<PortalNoticeDTO> getNotices(PortalNoticeSearch search, Pageable pageable) {
/* 사용여부 Y일경우만 조회 */
Specification<PortalNotice> spec = Specification.where(search.buildSpecification())
.and((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("useYn"),"Y"));
Page<PortalNotice> noticesPage = portalNoticeService.getNotices(spec, pageable);
return noticesPage.map(portalNoticeMapper::map);
}
@Override
public PortalNoticeDTO getNotice(String id) {
PortalNotice portalNotice = portalNoticeService.getNotice(id);
return portalNoticeMapper.map(portalNotice);
}
}
@@ -0,0 +1,35 @@
package com.eactive.apim.portal.apps.community.notice.service;
import com.eactive.apim.portal.apps.community.notice.repository.PortalNoticeRepository;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class PortalNoticeService {
private final PortalNoticeRepository portalNoticeRepository;
@Autowired
public PortalNoticeService(PortalNoticeRepository portalNoticeRepository) {
this.portalNoticeRepository = portalNoticeRepository;
}
@Transactional(readOnly = true)
public Page<PortalNotice> getNotices(Specification<PortalNotice> spec, Pageable pageable) {
return portalNoticeRepository.findAll(spec, pageable);
}
@Transactional(readOnly = true)
public PortalNotice getNotice(String id) {
return portalNoticeRepository.findById(id)
.orElseThrow(() -> new NotFoundException("PortalNotice not found with id: " + id));
}
}
@@ -0,0 +1,61 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.controller;
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.apps.community.partnershipapplication.service.PartnershipApplicationFacade;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@Controller
@RequestMapping("/partnership")
public class PartnershipApplicationController {
private final PartnershipApplicationFacade partnershipApplicationFacade;
@Autowired
public PartnershipApplicationController(PartnershipApplicationFacade partnershipApplicationFacade){
this.partnershipApplicationFacade = partnershipApplicationFacade;
}
@GetMapping
public String newPartnershipApplicationForm(Model model, HttpServletRequest request) {
if (!SecurityUtil.isAuthenticated()) {
return "redirect:/login?redirect=/partnership";
}
String referer = request.getHeader("Referer");
if(referer != null && !referer.contains("/partnership")) {
request.getSession().setAttribute("previousPage", referer);
}
model.addAttribute("partnership", new PartnershipApplicationDTO());
return "apps/community/mainPartnershipForm";
}
@PostMapping
public String createPartnershipApplication(@Valid @ModelAttribute PartnershipApplicationDTO partnershipApplicationDTO,
BindingResult bindingResult, HttpServletRequest httpServletRequest, RedirectAttributes redirectAttributes) throws IOException {
if (bindingResult.hasErrors()) {
return "apps/community/mainPartnershipForm";
}
partnershipApplicationFacade.createPartnershipApplication(partnershipApplicationDTO);
// 성공 메시지 추가
redirectAttributes.addFlashAttribute("success", "사업제휴신청이 완료되었습니다.");
httpServletRequest.getSession().removeAttribute("previousPage");
return "redirect:/partnership";
}
}
@@ -0,0 +1,29 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PartnershipApplicationDTO {
@Size(max = 255, message = "제목은 255자를 초과할 수 없습니다.")
private String bizSubject;
private String bizDetail;
/**
* 첨부파일ID
*/
private String fileId;
private List<MultipartFile> files;
}
@@ -0,0 +1,19 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.mapper;
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface PartnershipApplicationMapper {
PartnershipApplication toEntity(PartnershipApplicationDTO dto);
}
@@ -0,0 +1,12 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.repository;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import com.eactive.eai.rms.data.EMSDataSource;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
@EMSDataSource
public interface PartnershipApplicationRepository extends JpaRepository<PartnershipApplication, String>, JpaSpecificationExecutor<PartnershipApplication> {
}
@@ -0,0 +1,9 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
import java.io.IOException;
public interface PartnershipApplicationFacade {
void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException;
}
@@ -0,0 +1,37 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
import com.eactive.apim.portal.apps.community.partnershipapplication.dto.PartnershipApplicationDTO;
import com.eactive.apim.portal.apps.community.partnershipapplication.mapper.PartnershipApplicationMapper;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.service.FileService;
import com.eactive.apim.portal.file.service.FileTypeContext;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
@RequiredArgsConstructor
public class PartnershipApplicationFacadeImpl implements PartnershipApplicationFacade {
private final PartnershipApplicationService partnershipApplicationService;
private final PartnershipApplicationMapper partnershipApplicationMapper;
private final FileService fileService;
@Override
public void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException {
FileInfo file = null;
if (!partnershipApplicationDTO.getFiles().isEmpty()) {
FileTypeContext.setFileType("business");
file = fileService.createFile(partnershipApplicationDTO.getFiles());
}
PartnershipApplication partnershipApplication = partnershipApplicationMapper.toEntity(partnershipApplicationDTO);
if (file != null) {
partnershipApplication.setFileId(file.getFileId());
}
partnershipApplicationService.createPartnershipApplication(partnershipApplication);
}
}
@@ -0,0 +1,24 @@
package com.eactive.apim.portal.apps.community.partnershipapplication.service;
import com.eactive.apim.portal.apps.community.partnershipapplication.repository.PartnershipApplicationRepository;
import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class PartnershipApplicationService {
private final PartnershipApplicationRepository partnershipApplicationRepository;
@Autowired
public PartnershipApplicationService(PartnershipApplicationRepository partnershipApplicationRepository) {
this.partnershipApplicationRepository = partnershipApplicationRepository;
}
public void createPartnershipApplication(PartnershipApplication partnershipApplication) {
partnershipApplicationRepository.save(partnershipApplication);
}
}
@@ -0,0 +1,133 @@
package com.eactive.apim.portal.apps.community.qna.controller;
import com.eactive.apim.portal.apps.community.qna.dto.InquiryDTO;
import com.eactive.apim.portal.apps.community.qna.dto.InquirySearch;
import com.eactive.apim.portal.apps.community.qna.service.InquiryFacade;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.SecurityUtil;
import java.io.IOException;
import com.eactive.apim.portal.file.exception.InvalidFileException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller
@RequestMapping("/inquiry")
@Secured("ROLE_INQUIRY")
public class InquiryController {
public static final String APPS_COMMUNITY_MAIN_INQUIRY_FORM = "apps/community/mainInquiryForm";
public static final String REDIRECT_INQUIRY = "redirect:/inquiry";
private final InquiryFacade inquiryFacade;
public InquiryController(InquiryFacade inquiryFacade) {
this.inquiryFacade = inquiryFacade;
}
/**
* 1. inquiry 목록 조회 페이지
*/
@GetMapping
public String listInquiries(@PageableDefault(sort = "createdDate", direction = Sort.Direction.DESC) Pageable pageable, @ModelAttribute("search") InquirySearch search,
Model model) {
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
Page<InquiryDTO> inquiries = inquiryFacade.getInquiries(search, pageable, user);
model.addAttribute("inquiries", inquiries.getContent());
model.addAttribute("page", inquiries);
return "apps/community/mainInquiryList";
}
/**
* 2. inquiry 상세 조회 페이지
*/
@GetMapping("/detail")
public String viewInquiry(@RequestParam(value = "id", required = false) String id, Model model) {
if(id == null) {
return REDIRECT_INQUIRY;
}
InquiryDTO inquiry = inquiryFacade.getMyInquiry(SecurityUtil.getPortalAuthenticatedUser(), id);
model.addAttribute("inquiry", inquiry);
return "apps/community/mainInquiryDetail";
}
/**
* 3. inquiry 등록 페이지
*/
@GetMapping("/new")
public String newInquiryForm(Model model) {
model.addAttribute("inquiry", new InquiryDTO());
return APPS_COMMUNITY_MAIN_INQUIRY_FORM;
}
/**
* 4. inquiry 수정 페이지
*/
@GetMapping("/edit")
public String editInquiryForm(@RequestParam("id") String id, Model model) {
InquiryDTO inquiry = inquiryFacade.getMyInquiry(SecurityUtil.getPortalAuthenticatedUser(), id);
model.addAttribute("inquiry", inquiry);
return APPS_COMMUNITY_MAIN_INQUIRY_FORM;
}
/**
* 5. inquiry 등록 요청
*/
@PostMapping
public String createInquiry(@Valid @ModelAttribute InquiryDTO inquiryDTO, BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model) throws IOException {
if (bindingResult.hasErrors()) {
model.addAttribute("inquiry", inquiryDTO);
return APPS_COMMUNITY_MAIN_INQUIRY_FORM;
}
inquiryFacade.createInquiry(inquiryDTO);
redirectAttributes.addFlashAttribute("success", "Q&A 작성이 완료되었습니다.");
return REDIRECT_INQUIRY;
}
/**
* 6. inquiry 수정 요청
*/
@PostMapping("/edit")
public String updateInquiry(@Valid @ModelAttribute InquiryDTO inquiryDTO, BindingResult bindingResult,
RedirectAttributes redirectAttributes) throws IOException {
if (bindingResult.hasErrors()) {
return APPS_COMMUNITY_MAIN_INQUIRY_FORM;
}
inquiryFacade.updateUserInquiry(SecurityUtil.getPortalAuthenticatedUser(), inquiryDTO.getId(), inquiryDTO);
redirectAttributes.addFlashAttribute("success", "Q&A 수정이 완료되었습니다.");
return "redirect:/inquiry/detail?id=" + inquiryDTO.getId();
}
/**
* 7. inquiry 삭제 요청
*/
@PostMapping("/{id}/delete")
public String deleteInquiry(@PathVariable String id, RedirectAttributes redirectAttributes) {
inquiryFacade.deleteUserInquiry(SecurityUtil.getPortalAuthenticatedUser(), id);
// 성공 메시지 추가
redirectAttributes.addFlashAttribute("success", "삭제 되었습니다.");
return REDIRECT_INQUIRY;
}
@ExceptionHandler(InvalidFileException.class)
public String handleInvalidFileException(InvalidFileException e, Model model) {
model.addAttribute("inquiry", new InquiryDTO());
model.addAttribute("error", e.getMessage());
return "redirect:/inquiry/new";
}
}
@@ -0,0 +1,59 @@
package com.eactive.apim.portal.apps.community.qna.dto;
import com.eactive.apim.portal.apps.user.dto.PortalUserDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InquiryDTO {
private String id;
@NotEmpty(message = "제목을 입력해주세요.")
@Size(max = 50, message = "제목은 50자를 초과할 수 없습니다.")
private String inquirySubject;
@NotEmpty(message = "내용을 입력해주세요.")
@Size(max = 4000, message = "제목은 50자를 초과할 수 없습니다.")
private String inquiryDetail;
@Pattern(regexp = "^(PENDING|IN_PROGRESS|CLOSED)$", message = "유효하지 않은 문의 상태입니다.")
private String inquiryStatus;
private String inquirerName;
private PortalUserDTO inquirer;
@Size(max = 4000, message = "응답 내용은 4000자를 초과할 수 없습니다.")
private String responseDetail;
private LocalDateTime responseDate;
private String responderName;
private String createdBy;
private LocalDateTime createdDate;
private String lastModifiedBy;
private LocalDateTime lastModifiedDate;
private List<MultipartFile> files;
private String attachFile;
private String maskedInquirerName;
}
@@ -0,0 +1,62 @@
package com.eactive.apim.portal.apps.community.qna.dto;
import com.eactive.apim.portal.common.search.BaseSearch;
import com.eactive.apim.portal.common.search.ColumnSearchModel;
import com.eactive.apim.portal.common.search.SearchCondition;
import com.eactive.apim.portal.common.search.SearchModel;
import com.eactive.apim.portal.qna.entity.Inquiry;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 게시물 검색 조건
*/
public class InquirySearch implements BaseSearch<Inquiry> {
private String searchCnd;
private String searchWrd;
@Override
public List<SearchModel> buildSearchCondition() {
List<SearchModel> models = new ArrayList<>();
if (StringUtils.isNotEmpty(searchCnd) && StringUtils.isNotEmpty(searchWrd)) {
switch (searchCnd) {
case "0":
models.add(new ColumnSearchModel("inquirySubject", SearchCondition.LIKE, searchWrd));
break;
case "1":
models.add(new ColumnSearchModel("inquiryDetail", SearchCondition.LIKE, searchWrd));
break;
case "2":
models.add(new ColumnSearchModel("inquirer.userName", SearchCondition.LIKE, searchWrd));
break;
default:
break;
}
}
return models;
}
public String getSearchCnd() {
return searchCnd;
}
public void setSearchCnd(String searchCnd) {
this.searchCnd = searchCnd;
}
public String getSearchWrd() {
return searchWrd;
}
public void setSearchWrd(String searchWrd) {
this.searchWrd = searchWrd;
}
}
@@ -0,0 +1,22 @@
package com.eactive.apim.portal.apps.community.qna.mapper;
import com.eactive.apim.portal.apps.community.qna.dto.InquiryDTO;
import com.eactive.apim.portal.common.mapper.CommonMapper;
import com.eactive.apim.portal.qna.entity.Inquiry;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring",
uses = {CommonMapper.class},
unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface InquiryMapper {
Inquiry toEntity(InquiryDTO inquiry);
@Mapping(target = "inquirerName", source = "inquirer.userName")
InquiryDTO map(Inquiry inquiry);
}
@@ -0,0 +1,16 @@
package com.eactive.apim.portal.apps.community.qna.repository;
import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.apim.portal.qna.entity.Inquiry;
import com.eactive.eai.rms.data.EMSDataSource;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
@EMSDataSource
public interface InquiryRepository extends JpaRepository<Inquiry, String>, JpaSpecificationExecutor<Inquiry> {
Optional<Inquiry> findByInquirerAndId(PortalUser inquirer, String id);
}
@@ -0,0 +1,23 @@
package com.eactive.apim.portal.apps.community.qna.service;
import com.eactive.apim.portal.apps.community.qna.dto.InquiryDTO;
import com.eactive.apim.portal.apps.community.qna.dto.InquirySearch;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import java.io.IOException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface InquiryFacade {
Page<InquiryDTO> getInquiries(InquirySearch search, Pageable pageable, PortalAuthenticatedUser user);
InquiryDTO getUserInquiry(PortalAuthenticatedUser user, String id);
InquiryDTO getMyInquiry(PortalAuthenticatedUser user, String id);
void createInquiry(InquiryDTO inquiryDTO) throws IOException;
void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO) throws IOException;
void deleteUserInquiry(PortalAuthenticatedUser user, String id);
}
@@ -0,0 +1,128 @@
package com.eactive.apim.portal.apps.community.qna.service;
import com.eactive.apim.portal.apps.community.qna.dto.InquiryDTO;
import com.eactive.apim.portal.apps.community.qna.dto.InquirySearch;
import com.eactive.apim.portal.apps.community.qna.mapper.InquiryMapper;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.common.util.SecurityUtil;
import com.eactive.apim.portal.common.util.StringMaskingUtil;
import com.eactive.apim.portal.file.entity.FileInfo;
import com.eactive.apim.portal.file.service.FileService;
import com.eactive.apim.portal.file.service.FileTypeContext;
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
import com.eactive.apim.portal.qna.entity.Inquiry;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
@Service
@RequiredArgsConstructor
public class InquiryFacadeImpl implements InquiryFacade {
private final FileService fileService;
private final InquiryService inquiryService;
private final InquiryMapper inquiryMapper;
@Override
public Page<InquiryDTO> getInquiries(InquirySearch search, Pageable pageable, PortalAuthenticatedUser user) {
Specification<Inquiry> spec = Specification.where(search.buildSpecification());
spec = user.getRoleCode().equals(PortalUserEnums.RoleCode.ROLE_USER)
? spec.and((root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("inquirer").get("id"), user.getId()))
: spec.and((root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("inquirer").get("portalOrg").get("id"), user.getPortalOrg().getId()));
Page<Inquiry> inquiriesPage = inquiryService.getInquiries(spec, pageable);
return inquiriesPage.map(inquiry -> {
InquiryDTO dto = inquiryMapper.map(inquiry);
if (dto != null && dto.getInquirer() != null) {
dto.setMaskedInquirerName(StringMaskingUtil.maskName(
inquiry.getInquirer().getUserName(),
inquiry.getInquirer().getId(),
user.getId()
));
dto.getInquirer().setUserName(null); // 원본 데이터 제거
}
return dto;
});
}
@Override
public InquiryDTO getUserInquiry(PortalAuthenticatedUser user, String id) {
Inquiry inquiry = inquiryService.getInquiry(id);
InquiryDTO dto = inquiryMapper.map(inquiry);
if (dto != null && dto.getInquirer() != null) {
dto.setMaskedInquirerName(StringMaskingUtil.maskName(
inquiry.getInquirer().getUserName(),
inquiry.getInquirer().getId(),
user.getId()
));
dto.getInquirer().setUserName(null); // 원본 데이터 제거
}
return dto;
}
@Override
public InquiryDTO getMyInquiry(PortalAuthenticatedUser user, String id) {
Inquiry inquiry = inquiryService.getMyInquiry(user, id);
return inquiryMapper.map(inquiry);
}
@Override
public void createInquiry(InquiryDTO inquiryDTO) throws IOException {
FileInfo file = null;
if (!inquiryDTO.getFiles().isEmpty()) {
FileTypeContext.setFileType("qna");
file = fileService.createFile(inquiryDTO.getFiles());
}
Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO);
if (file != null) {
inquiry.setAttachFile(file.getFileId());
}
inquiry.setInquirer(SecurityUtil.getPortalAuthenticatedUser());
inquiryService.createInquiry(inquiry);
HashMap<String, Object> params = new HashMap<>();
params.put("inquiryId", inquiry.getId());
params.put("subject", inquiry.getInquirySubject());
}
@Override
public void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO) throws IOException {
Inquiry existingInquiry = inquiryService.getMyInquiry(user, id);
// 파일이 새로 업로드된 경우
if (!inquiryDTO.getFiles().isEmpty() && !inquiryDTO.getFiles().get(0).isEmpty()) {
FileTypeContext.setFileType("qna");
FileInfo file = fileService.createFile(inquiryDTO.getFiles());
if(file != null) {
inquiryDTO.setAttachFile(file.getFileId());
}
} else if (existingInquiry.getAttachFile() != null && inquiryDTO.getAttachFile() == null) {
fileService.deleteFile(inquiryDTO.getAttachFile());
inquiryDTO.setAttachFile(null);
}
Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO);
inquiry.setInquirer(existingInquiry.getInquirer());
inquiryService.updateInquiry(id, inquiry);
}
@Override
public void deleteUserInquiry(PortalAuthenticatedUser user, String id) {
inquiryService.deleteMyInquiry(user, id);
}
}
@@ -0,0 +1,94 @@
package com.eactive.apim.portal.apps.community.qna.service;
import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository;
import com.eactive.apim.portal.common.exception.NotFoundException;
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
import com.eactive.apim.portal.qna.entity.Inquiry;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class InquiryService {
public static final String PENDING = "PENDING";
private final InquiryRepository inquiryRepository;
public InquiryService(InquiryRepository inquiryRepository) {
this.inquiryRepository = inquiryRepository;
}
/**
* 1. Inquiry 조회
*
* @param id Inquiry의 ID
* @return 조회된 Inquiry 객체
* @throws NotFoundException Inquiry가 존재하지 않을 경우
*/
@Transactional(readOnly = true)
public Inquiry getInquiry(String id) {
return inquiryRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Inquiry not found with id: " + id));
}
/**
* Inquiry 목록 조회 (페이징 필터링 지원)
*
* @param spec 필터링 조건
* @param pageable 페이징 정보
* @return 페이징된 Inquiry 목록
*/
@Transactional(readOnly = true)
public Page<Inquiry> getInquiries(Specification<Inquiry> spec, Pageable pageable) {
return inquiryRepository.findAll(spec, pageable);
}
/**
* 2. 새로운 Inquiry 등록
*
* @param inquiry 등록할 Inquiry 객체
* @return 등록된 Inquiry 객체
*/
public Inquiry createInquiry(Inquiry inquiry) {
inquiry.setId(null);
inquiry.setInquiryStatus(PENDING);
return inquiryRepository.save(inquiry);
}
/**
* 3. Inquiry 수정
*
* @param id 수정할 Inquiry의 ID
* @param updatedInquiry 수정할 내용이 담긴 Inquiry 객체
* @return 수정된 Inquiry 객체
* @throws RuntimeException Inquiry가 존재하지 않을 경우
*/
public Inquiry updateInquiry(String id, Inquiry updatedInquiry) {
Inquiry inquiry = getInquiry(id);
inquiry.setInquirySubject(updatedInquiry.getInquirySubject());
inquiry.setInquiryDetail(updatedInquiry.getInquiryDetail());
inquiry.setAttachFile(updatedInquiry.getAttachFile());
return inquiryRepository.save(inquiry);
}
/**
* 4. Inquiry 삭제
*
* @param id 삭제할 Inquiry의 ID
* @throws NotFoundException Inquiry가 존재하지 않을 경우
*/
public void deleteMyInquiry(PortalAuthenticatedUser user, String id) {
Inquiry myInquiry = inquiryRepository.findByInquirerAndId(user, id).orElseThrow(() -> new NotFoundException("내 질문을 찾을 수 없습니다."));
inquiryRepository.delete(myInquiry);
}
public Inquiry getMyInquiry(PortalAuthenticatedUser user, String id) {
return inquiryRepository.findByInquirerAndId(user, id).orElseThrow(() -> new NotFoundException("내 질문을 찾을 수 없습니다."));
}
}
@@ -0,0 +1,21 @@
package com.eactive.apim.portal.apps.dashboard;
public class ApiDailyStatistics {
private final String apiId;
private final String apiName;
private final String date;
private final Long requestCount;
public ApiDailyStatistics(String apiId, String apiName, String date, Long requestCount) {
this.apiId = apiId;
this.apiName = apiName;
this.date = date;
this.requestCount = requestCount;
}
public String getApiId() { return apiId; }
public String getApiName() { return apiName; }
public String getDate() { return date; }
public Long getRequestCount() { return requestCount; }
}

Some files were not shown because too many files have changed in this diff Show More