- {/* Domain groups */}
- {serviceOpen && (
-
- {service.domains.map((dg) => {
- const domainKey = `${service.serviceId}:${dg.domain}`;
- const domainOpen = openDomains[domainKey] ?? false;
+ {/* API items */}
+ {domainOpen && (
+
+ {dg.apis.map((api) => {
+ const apiPath = `/api-hub/services/${api.serviceId}/apis/${api.apiId}`;
+ const isActive = location.pathname === apiPath;
return (
-
- {/* Domain header */}
-
-
- {/* API items */}
- {domainOpen && (
-
- {dg.apis.map((api) => {
- const apiPath = `/api-hub/services/${service.serviceId}/apis/${api.apiId}`;
- const isActive = location.pathname === apiPath;
-
- return (
-
-
- {api.apiMethod.toUpperCase()}
-
- {api.apiName}
-
- );
- })}
-
- )}
-
+
+ {api.apiName}
+
);
})}
diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx
index ff16a4b..8d0422f 100644
--- a/frontend/src/layouts/MainLayout.tsx
+++ b/frontend/src/layouts/MainLayout.tsx
@@ -40,6 +40,9 @@ const navGroups: NavGroup[] = [
adminOnly: true,
items: [
{ label: 'Services', path: '/admin/services' },
+ { label: 'Domains', path: '/admin/domains' },
+ { label: 'API 관리', path: '/admin/apis' },
+ { label: '공통 샘플 코드', path: '/admin/sample-code' },
{ label: 'Users', path: '/admin/users' },
{ label: 'Tenants', path: '/admin/tenants' },
],
diff --git a/frontend/src/pages/admin/DomainsPage.tsx b/frontend/src/pages/admin/DomainsPage.tsx
new file mode 100644
index 0000000..8c073d5
--- /dev/null
+++ b/frontend/src/pages/admin/DomainsPage.tsx
@@ -0,0 +1,302 @@
+import { useState, useEffect } from 'react';
+import type { ApiDomainInfo } from '../../types/apihub';
+import type { CreateDomainRequest, UpdateDomainRequest } from '../../services/serviceService';
+import { getDomains, createDomain, updateDomain, deleteDomain } from '../../services/serviceService';
+
+const DEFAULT_ICON_PATHS = ['M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776'];
+
+const parseIconPaths = (iconPath: string | null): string[] => {
+ if (!iconPath) return DEFAULT_ICON_PATHS;
+ const pathRegex = /d="([^"]+)"/g;
+ const matches: string[] = [];
+ let m;
+ while ((m = pathRegex.exec(iconPath)) !== null) {
+ matches.push(m[1]);
+ }
+ return matches.length > 0 ? matches : [iconPath];
+};
+
+const DomainsPage = () => {
+ const [domains, setDomains] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [editingDomain, setEditingDomain] = useState(null);
+ const [domainName, setDomainName] = useState('');
+ const [iconPath, setIconPath] = useState('');
+ const [sortOrder, setSortOrder] = useState(0);
+
+ const fetchDomains = async () => {
+ try {
+ setLoading(true);
+ const res = await getDomains();
+ if (res.success && res.data) {
+ setDomains(res.data);
+ } else {
+ setError(res.message || '도메인 목록을 불러오는데 실패했습니다.');
+ }
+ } catch {
+ setError('도메인 목록을 불러오는데 실패했습니다.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchDomains();
+ }, []);
+
+ const handleOpenCreate = () => {
+ setEditingDomain(null);
+ setDomainName('');
+ setIconPath('');
+ setSortOrder(domains.length > 0 ? Math.max(...domains.map((d) => d.sortOrder)) + 1 : 0);
+ setIsModalOpen(true);
+ };
+
+ const handleOpenEdit = (domain: ApiDomainInfo) => {
+ setEditingDomain(domain);
+ setDomainName(domain.domainName);
+ setIconPath(domain.iconPath ?? '');
+ setSortOrder(domain.sortOrder);
+ setIsModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setIsModalOpen(false);
+ setEditingDomain(null);
+ setError(null);
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+
+ try {
+ if (editingDomain) {
+ const req: UpdateDomainRequest = {
+ domainName,
+ iconPath: iconPath || null,
+ sortOrder,
+ };
+ const res = await updateDomain(editingDomain.domainId, req);
+ if (!res.success) {
+ setError(res.message || '도메인 수정에 실패했습니다.');
+ return;
+ }
+ } else {
+ const req: CreateDomainRequest = {
+ domainName,
+ iconPath: iconPath || null,
+ sortOrder,
+ };
+ const res = await createDomain(req);
+ if (!res.success) {
+ setError(res.message || '도메인 생성에 실패했습니다.');
+ return;
+ }
+ }
+ handleCloseModal();
+ await fetchDomains();
+ } catch {
+ setError(editingDomain ? '도메인 수정에 실패했습니다.' : '도메인 생성에 실패했습니다.');
+ }
+ };
+
+ const handleDelete = async (domain: ApiDomainInfo) => {
+ if (!window.confirm(`'${domain.domainName}' 도메인을 삭제하시겠습니까?`)) return;
+ try {
+ const res = await deleteDomain(domain.domainId);
+ if (!res.success) {
+ setError(res.message || '도메인 삭제에 실패했습니다.');
+ return;
+ }
+ await fetchDomains();
+ } catch {
+ setError('도메인 삭제에 실패했습니다.');
+ }
+ };
+
+ const previewPath = iconPath.trim() || null;
+
+ if (loading) {
+ return 로딩 중...
;
+ }
+
+ return (
+
+
+
Domains
+
+
+
+ {error && !isModalOpen && (
+
{error}
+ )}
+
+
+
+
+
+ | # |
+ 아이콘 |
+ 도메인명 |
+ 정렬순서 |
+ Actions |
+
+
+
+ {domains.map((domain, index) => (
+
+ | {index + 1} |
+
+
+ |
+ {domain.domainName} |
+ {domain.sortOrder} |
+
+
+
+
+
+ |
+
+ ))}
+ {domains.length === 0 && (
+
+ |
+ 등록된 도메인이 없습니다.
+ |
+
+ )}
+
+
+
+
+ {isModalOpen && (
+
+
+
+
+ {editingDomain ? '도메인 수정' : '도메인 추가'}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default DomainsPage;
diff --git a/frontend/src/types/apihub.ts b/frontend/src/types/apihub.ts
index 27c17d3..a4985d3 100644
--- a/frontend/src/types/apihub.ts
+++ b/frontend/src/types/apihub.ts
@@ -1,8 +1,19 @@
export interface DomainGroup {
domain: string;
+ iconPath: string | null;
+ sortOrder: number;
apis: ServiceApiItem[];
}
+export interface ApiDomainInfo {
+ domainId: number;
+ domainName: string;
+ iconPath: string | null;
+ sortOrder: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
export interface ServiceApiItem {
apiId: number;
serviceId: number;
@@ -22,6 +33,7 @@ export interface ServiceCatalog {
serviceCode: string;
serviceName: string;
description: string | null;
+ serviceUrl: string | null;
healthStatus: 'UP' | 'DOWN' | 'UNKNOWN';
apiCount: number;
domains: DomainGroup[];
diff --git a/scripts/init-domains.sh b/scripts/init-domains.sh
new file mode 100644
index 0000000..077b24d
--- /dev/null
+++ b/scripts/init-domains.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+# 도메인 초기 데이터 생성 스크립트
+# 사용법: bash scripts/init-domains.sh
+
+BASE_URL="http://localhost:8042/snp-connection/api/domains"
+
+echo "=== 도메인 초기 데이터 생성 ==="
+
+create_domain() {
+ local name="$1"
+ local icon="$2"
+ local order="$3"
+ echo -n " $name (sortOrder=$order) ... "
+ curl -s -X POST "$BASE_URL" \
+ -H "Content-Type: application/json" \
+ -d "{\"domainName\":\"$name\",\"iconPath\":\"$icon\",\"sortOrder\":$order}" \
+ | python3 -c "import sys,json; d=json.load(sys.stdin); print('OK' if d.get('success') else d.get('message','FAIL'))" 2>/dev/null || echo "ERROR"
+}
+
+create_domain "SCREENING" \
+ "M10 1l2.39 4.843 5.346.777-3.868 3.77.913 5.323L10 13.347l-4.781 2.366.913-5.323L2.264 6.62l5.346-.777L10 1z" \
+ 1
+
+create_domain "AIS" \
+ "M10 2a6 6 0 00-6 6c0 4.5 6 10 6 10s6-5.5 6-10a6 6 0 00-6-6zm0 8a2 2 0 110-4 2 2 0 010 4z" \
+ 2
+
+create_domain "SHIP" \
+ "M3 15l1.5-6h11L17 15M5 15l-2 3h14l-2-3M7 9V5a1 1 0 011-1h4a1 1 0 011 1v4" \
+ 3
+
+create_domain "PORT" \
+ "M3 17h14M5 17V7l5-4 5 4v10M8 17v-3h4v3M8 10h.01M12 10h.01" \
+ 4
+
+create_domain "TRADE" \
+ "M4 6h12M4 6v10a1 1 0 001 1h10a1 1 0 001-1V6M4 6l1-3h10l1 3M8 10h4M8 13h4" \
+ 5
+
+create_domain "WEATHER" \
+ "M3 13.5c0-1.38 1.12-2.5 2.5-2.5.39 0 .76.09 1.09.25A4.002 4.002 0 0110.5 8c1.82 0 3.36 1.22 3.84 2.88A2.5 2.5 0 0117 13.5 2.5 2.5 0 0114.5 16h-9A2.5 2.5 0 013 13.5z" \
+ 6
+
+create_domain "COMPLIANCE" \
+ "M9 12l2 2 4-4m-3-5.96A8 8 0 1017.96 14H10V6.04z" \
+ 7
+
+create_domain "MONITORING" \
+ "M3 13h2l2-4 3 8 2-6 2 2h3M3 17h14" \
+ 8
+
+echo ""
+echo "=== 완료 ==="
+echo "기존 API의 api_domain 값과 도메인명을 일치시켜야 사이드바에 아이콘이 표시됩니다."
+echo "Admin > Domains 에서 추가/수정/삭제할 수 있습니다."
diff --git a/src/main/java/com/gcsc/connection/apihub/dto/DomainGroup.java b/src/main/java/com/gcsc/connection/apihub/dto/DomainGroup.java
index fd346e2..2095db3 100644
--- a/src/main/java/com/gcsc/connection/apihub/dto/DomainGroup.java
+++ b/src/main/java/com/gcsc/connection/apihub/dto/DomainGroup.java
@@ -6,6 +6,8 @@ import java.util.List;
public record DomainGroup(
String domain,
+ String iconPath,
+ int sortOrder,
List apis
) {
}
diff --git a/src/main/java/com/gcsc/connection/apihub/dto/ServiceCatalogResponse.java b/src/main/java/com/gcsc/connection/apihub/dto/ServiceCatalogResponse.java
index 036c93e..594f8fa 100644
--- a/src/main/java/com/gcsc/connection/apihub/dto/ServiceCatalogResponse.java
+++ b/src/main/java/com/gcsc/connection/apihub/dto/ServiceCatalogResponse.java
@@ -1,6 +1,7 @@
package com.gcsc.connection.apihub.dto;
import com.gcsc.connection.service.dto.ServiceApiResponse;
+import com.gcsc.connection.service.entity.SnpApiDomain;
import com.gcsc.connection.service.entity.SnpService;
import com.gcsc.connection.service.entity.SnpServiceApi;
@@ -12,13 +13,27 @@ public record ServiceCatalogResponse(
Long serviceId,
String serviceCode,
String serviceName,
+ String serviceUrl,
String description,
String healthStatus,
int apiCount,
List domains
) {
+ /**
+ * 도메인 메타 정보(아이콘, 정렬) 없이 도메인명 기준 알파벳 정렬로 카탈로그 생성
+ */
public static ServiceCatalogResponse from(SnpService service, List apis) {
+ return from(service, apis, Map.of());
+ }
+
+ /**
+ * 도메인 메타 정보(아이콘, 정렬)를 포함하여 카탈로그 생성
+ *
+ * @param domainMap domainName → SnpApiDomain 매핑
+ */
+ public static ServiceCatalogResponse from(SnpService service, List apis,
+ Map domainMap) {
Map> byDomain = apis.stream()
.collect(Collectors.groupingBy(
api -> api.getApiDomain() != null ? api.getApiDomain() : "",
@@ -26,14 +41,29 @@ public record ServiceCatalogResponse(
));
List domainGroups = byDomain.entrySet().stream()
- .sorted(Map.Entry.comparingByKey())
- .map(entry -> new DomainGroup(entry.getKey(), entry.getValue()))
+ .sorted((a, b) -> {
+ SnpApiDomain da = domainMap.get(a.getKey());
+ SnpApiDomain db = domainMap.get(b.getKey());
+ int orderA = da != null ? da.getSortOrder() : Integer.MAX_VALUE;
+ int orderB = db != null ? db.getSortOrder() : Integer.MAX_VALUE;
+ if (orderA != orderB) {
+ return Integer.compare(orderA, orderB);
+ }
+ return a.getKey().compareTo(b.getKey());
+ })
+ .map(entry -> {
+ SnpApiDomain domainMeta = domainMap.get(entry.getKey());
+ String iconPath = domainMeta != null ? domainMeta.getIconPath() : null;
+ int sortOrder = domainMeta != null ? domainMeta.getSortOrder() : Integer.MAX_VALUE;
+ return new DomainGroup(entry.getKey(), iconPath, sortOrder, entry.getValue());
+ })
.toList();
return new ServiceCatalogResponse(
service.getServiceId(),
service.getServiceCode(),
service.getServiceName(),
+ service.getServiceUrl(),
service.getDescription(),
service.getHealthStatus().name(),
apis.size(),
diff --git a/src/main/java/com/gcsc/connection/apihub/service/ApiHubService.java b/src/main/java/com/gcsc/connection/apihub/service/ApiHubService.java
index 6f79fbb..75e1e0d 100644
--- a/src/main/java/com/gcsc/connection/apihub/service/ApiHubService.java
+++ b/src/main/java/com/gcsc/connection/apihub/service/ApiHubService.java
@@ -2,8 +2,12 @@ package com.gcsc.connection.apihub.service;
import com.gcsc.connection.apihub.dto.RecentApiResponse;
import com.gcsc.connection.apihub.dto.ServiceCatalogResponse;
+import com.gcsc.connection.common.exception.BusinessException;
+import com.gcsc.connection.common.exception.ErrorCode;
+import com.gcsc.connection.service.entity.SnpApiDomain;
import com.gcsc.connection.service.entity.SnpService;
import com.gcsc.connection.service.entity.SnpServiceApi;
+import com.gcsc.connection.service.repository.SnpApiDomainRepository;
import com.gcsc.connection.service.repository.SnpServiceApiRepository;
import com.gcsc.connection.service.repository.SnpServiceRepository;
import lombok.RequiredArgsConstructor;
@@ -12,6 +16,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
@Service
@Slf4j
@@ -20,23 +27,41 @@ public class ApiHubService {
private final SnpServiceRepository snpServiceRepository;
private final SnpServiceApiRepository snpServiceApiRepository;
+ private final SnpApiDomainRepository snpApiDomainRepository;
/**
* 활성 서비스와 각 서비스의 활성 API를 도메인별로 그룹화하여 카탈로그 반환
*/
@Transactional(readOnly = true)
public List getCatalog() {
+ Map domainMap = buildDomainMap();
List activeServices = snpServiceRepository.findByIsActiveTrue();
return activeServices.stream()
.map(service -> {
List activeApis = snpServiceApiRepository
.findByServiceServiceIdAndIsActiveTrue(service.getServiceId());
- return ServiceCatalogResponse.from(service, activeApis);
+ return ServiceCatalogResponse.from(service, activeApis, domainMap);
})
.toList();
}
+ /**
+ * 서비스 단건 카탈로그 조회
+ */
+ @Transactional(readOnly = true)
+ public ServiceCatalogResponse getServiceCatalog(Long serviceId) {
+ SnpService service = snpServiceRepository.findById(serviceId)
+ .filter(SnpService::getIsActive)
+ .orElseThrow(() -> new BusinessException(ErrorCode.SERVICE_NOT_FOUND));
+
+ List activeApis = snpServiceApiRepository
+ .findByServiceServiceIdAndIsActiveTrue(serviceId);
+
+ Map domainMap = buildDomainMap();
+ return ServiceCatalogResponse.from(service, activeApis, domainMap);
+ }
+
/**
* 최근 등록된 활성 API 상위 10건 반환
*/
@@ -46,4 +71,9 @@ public class ApiHubService {
.map(RecentApiResponse::from)
.toList();
}
+
+ private Map buildDomainMap() {
+ return snpApiDomainRepository.findAllByOrderBySortOrderAscDomainNameAsc().stream()
+ .collect(Collectors.toMap(SnpApiDomain::getDomainName, Function.identity()));
+ }
}
diff --git a/src/main/java/com/gcsc/connection/service/controller/ApiDomainController.java b/src/main/java/com/gcsc/connection/service/controller/ApiDomainController.java
new file mode 100644
index 0000000..35e346f
--- /dev/null
+++ b/src/main/java/com/gcsc/connection/service/controller/ApiDomainController.java
@@ -0,0 +1,80 @@
+package com.gcsc.connection.service.controller;
+
+import com.gcsc.connection.common.dto.ApiResponse;
+import com.gcsc.connection.service.dto.ApiDomainResponse;
+import com.gcsc.connection.service.dto.SaveApiDomainRequest;
+import com.gcsc.connection.service.entity.SnpApiDomain;
+import com.gcsc.connection.service.repository.SnpApiDomainRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * API 도메인 관리 API
+ */
+@RestController
+@RequestMapping("/api/domains")
+@RequiredArgsConstructor
+public class ApiDomainController {
+
+ private final SnpApiDomainRepository snpApiDomainRepository;
+
+ /**
+ * 전체 도메인 목록 조회 (sortOrder 오름차순)
+ */
+ @GetMapping
+ public ResponseEntity>> getDomains() {
+ List domains = snpApiDomainRepository.findAllByOrderBySortOrderAscDomainNameAsc()
+ .stream()
+ .map(ApiDomainResponse::from)
+ .toList();
+ return ResponseEntity.ok(ApiResponse.ok(domains));
+ }
+
+ /**
+ * 도메인 생성
+ */
+ @PostMapping
+ public ResponseEntity> createDomain(
+ @RequestBody SaveApiDomainRequest request) {
+ SnpApiDomain domain = SnpApiDomain.builder()
+ .domainName(request.domainName())
+ .iconPath(request.iconPath())
+ .sortOrder(request.sortOrder())
+ .build();
+ SnpApiDomain saved = snpApiDomainRepository.save(domain);
+ return ResponseEntity.ok(ApiResponse.ok(ApiDomainResponse.from(saved)));
+ }
+
+ /**
+ * 도메인 수정
+ */
+ @PutMapping("/{domainId}")
+ public ResponseEntity> updateDomain(
+ @PathVariable Long domainId,
+ @RequestBody SaveApiDomainRequest request) {
+ SnpApiDomain domain = snpApiDomainRepository.findById(domainId)
+ .orElseThrow(() -> new IllegalArgumentException("도메인을 찾을 수 없습니다: " + domainId));
+ domain.update(request.domainName(), request.iconPath(), request.sortOrder());
+ SnpApiDomain saved = snpApiDomainRepository.save(domain);
+ return ResponseEntity.ok(ApiResponse.ok(ApiDomainResponse.from(saved)));
+ }
+
+ /**
+ * 도메인 삭제
+ */
+ @DeleteMapping("/{domainId}")
+ public ResponseEntity> deleteDomain(@PathVariable Long domainId) {
+ snpApiDomainRepository.deleteById(domainId);
+ return ResponseEntity.ok(ApiResponse.ok(null));
+ }
+}
diff --git a/src/main/java/com/gcsc/connection/service/dto/ApiDomainResponse.java b/src/main/java/com/gcsc/connection/service/dto/ApiDomainResponse.java
new file mode 100644
index 0000000..5a19b7a
--- /dev/null
+++ b/src/main/java/com/gcsc/connection/service/dto/ApiDomainResponse.java
@@ -0,0 +1,26 @@
+package com.gcsc.connection.service.dto;
+
+import com.gcsc.connection.service.entity.SnpApiDomain;
+
+import java.time.LocalDateTime;
+
+public record ApiDomainResponse(
+ Long domainId,
+ String domainName,
+ String iconPath,
+ Integer sortOrder,
+ LocalDateTime createdAt,
+ LocalDateTime updatedAt
+) {
+
+ public static ApiDomainResponse from(SnpApiDomain domain) {
+ return new ApiDomainResponse(
+ domain.getDomainId(),
+ domain.getDomainName(),
+ domain.getIconPath(),
+ domain.getSortOrder(),
+ domain.getCreatedAt(),
+ domain.getUpdatedAt()
+ );
+ }
+}
diff --git a/src/main/java/com/gcsc/connection/service/dto/SaveApiDomainRequest.java b/src/main/java/com/gcsc/connection/service/dto/SaveApiDomainRequest.java
new file mode 100644
index 0000000..86c1005
--- /dev/null
+++ b/src/main/java/com/gcsc/connection/service/dto/SaveApiDomainRequest.java
@@ -0,0 +1,8 @@
+package com.gcsc.connection.service.dto;
+
+public record SaveApiDomainRequest(
+ String domainName,
+ String iconPath,
+ Integer sortOrder
+) {
+}
diff --git a/src/main/java/com/gcsc/connection/service/entity/SnpApiDomain.java b/src/main/java/com/gcsc/connection/service/entity/SnpApiDomain.java
new file mode 100644
index 0000000..cb9cee2
--- /dev/null
+++ b/src/main/java/com/gcsc/connection/service/entity/SnpApiDomain.java
@@ -0,0 +1,46 @@
+package com.gcsc.connection.service.entity;
+
+import com.gcsc.connection.common.entity.BaseEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
+@Entity
+@Table(name = "snp_api_domain", schema = "common")
+public class SnpApiDomain extends BaseEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "domain_id")
+ private Long domainId;
+
+ @Column(name = "domain_name", length = 100, unique = true, nullable = false)
+ private String domainName;
+
+ @Column(name = "icon_path", columnDefinition = "TEXT")
+ private String iconPath;
+
+ @Column(name = "sort_order", nullable = false)
+ private Integer sortOrder = 0;
+
+ @Builder
+ public SnpApiDomain(String domainName, String iconPath, Integer sortOrder) {
+ this.domainName = domainName;
+ this.iconPath = iconPath;
+ this.sortOrder = sortOrder != null ? sortOrder : 0;
+ }
+
+ public void update(String domainName, String iconPath, Integer sortOrder) {
+ if (domainName != null) this.domainName = domainName;
+ if (iconPath != null) this.iconPath = iconPath;
+ if (sortOrder != null) this.sortOrder = sortOrder;
+ }
+}
diff --git a/src/main/java/com/gcsc/connection/service/repository/SnpApiDomainRepository.java b/src/main/java/com/gcsc/connection/service/repository/SnpApiDomainRepository.java
new file mode 100644
index 0000000..fca4a88
--- /dev/null
+++ b/src/main/java/com/gcsc/connection/service/repository/SnpApiDomainRepository.java
@@ -0,0 +1,14 @@
+package com.gcsc.connection.service.repository;
+
+import com.gcsc.connection.service.entity.SnpApiDomain;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface SnpApiDomainRepository extends JpaRepository {
+
+ Optional findByDomainName(String domainName);
+
+ List findAllByOrderBySortOrderAscDomainNameAsc();
+}