Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ FROM eclipse-temurin:25-jre

WORKDIR /app

# Install curl for healthcheck
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# 빌드된 JAR 복사
COPY --from=builder /app/build/libs/*.jar app.jar

Expand All @@ -40,10 +36,5 @@ EXPOSE 8080
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
ENV SPRING_PROFILES_ACTIVE=prod

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://gateway.jagalchi.local:8080/actuator/health || exit 1

# 애플리케이션 실행
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
implementation 'com.fasterxml.jackson.core:jackson-databind'
// Support Java 8+ date/time (LocalDate) serialization for Jackson
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package gajeman.jagalchi.jagalchiserver.presentation.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class OpenApiConfig {

@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("Jagalchi Node API")
.version("v1.0.0")
.description("노드 이벤트 API"))
.servers(List.of(new Server().url("/").description("Gateway Server")));
}

@Bean
public OpenApiCustomizer nodeGatewayPathCustomizer() {
return openApi -> {
if (openApi.getPaths() == null) {
return;
}

var paths = openApi.getPaths();

if (paths.containsKey("/health")) {
var healthPath = paths.remove("/health");
paths.addPathItem("/node/health", healthPath);
}

if (paths.containsKey("/stomp/info")) {
var stompPath = paths.remove("/stomp/info");
paths.addPathItem("/node/stomp/info", stompPath);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gajeman.jagalchi.jagalchiserver.presentation.rest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class HealthController {

@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "health");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package gajeman.jagalchi.jagalchiserver.presentation.rest;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class StompDocsController {

@Operation(
summary = "STOMP/WebSocket 연결 정보 조회",
description = "Swagger에서 확인할 수 있는 STOMP 연결 안내용 엔드포인트입니다. " +
"실제 STOMP 연결 endpoint, broker prefix, publish/subscribe destination, 필요한 헤더를 반환합니다.",
responses = @ApiResponse(
responseCode = "200",
description = "STOMP 연결 정보 반환",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = StompDocsResponse.class),
examples = @ExampleObject(
value = """
{
\"endpoint\": \"/ws/roadmap\",
\"sockJsEnabled\": true,
\"applicationDestinationPrefix\": \"/app\",
\"brokerPrefixes\": [\"/topic\", \"/queue\", \"/user\"],
\"connectHeaders\": [\"X-User-ID\", \"X-User-Role\", \"X-Roadmap-ID\", \"X-Permissions\"],
\"publishDestinations\": [
\"/app/roadmap/{roadmapId}/action\",
\"/app/roadmap/{roadmapId}/cursor\",
\"/app/roadmap/{roadmapId}/cursor/hide\"
],
\"subscribeDestinations\": [
\"/user/queue/ack\",
\"/user/queue/nack\",
\"/topic/roadmap/{roadmapId}/state\",
\"/topic/roadmap/{roadmapId}/cursors\",
\"/topic/roadmap/{roadmapId}/cursors/hide\"
]
}
"""
)
)
)
)
@GetMapping("/stomp/info")
public StompDocsResponse stompInfo() {
return new StompDocsResponse(
"/ws/roadmap",
true,
"/app",
List.of("/topic", "/queue", "/user"),
List.of("X-User-ID", "X-User-Role", "X-Roadmap-ID", "X-Permissions"),
List.of(
"/app/roadmap/{roadmapId}/action",
"/app/roadmap/{roadmapId}/cursor",
"/app/roadmap/{roadmapId}/cursor/hide"
),
List.of(
"/user/queue/ack",
"/user/queue/nack",
"/topic/roadmap/{roadmapId}/state",
"/topic/roadmap/{roadmapId}/cursors",
"/topic/roadmap/{roadmapId}/cursors/hide"
)
);
}

public record StompDocsResponse(
String endpoint,
boolean sockJsEnabled,
String applicationDestinationPrefix,
List<String> brokerPrefixes,
List<String> connectHeaders,
List<String> publishDestinations,
List<String> subscribeDestinations
) {
}
}
Loading