Repository navigation
feat: help content api/#713 #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
6990574
feat: 도움말 내용 조회 및 수정 API 추가
chaeyn c3eb31c
chore: 도움말 내용 테이블 마이그레이션 추가
chaeyn eda8714
test: 도움말 API ETag 동작 검증 추가
chaeyn 73077b1
feat: 도움말 내용 생성 API 추가
chaeyn b699960
fix: 도움말 ETag 검증 및 동시 수정 처리 개선
chaeyn e8045a5
fix: 도움말 저장 결과 동기화
chaeyn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
45 changes: 45 additions & 0 deletions
45
src/main/java/com/process/clash/adapter/persistence/helpcontent/HelpContentJpaEntity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.process.clash.adapter.persistence.helpcontent; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EntityListeners; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.persistence.Version; | ||
| import lombok.AccessLevel; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.springframework.data.annotation.CreatedDate; | ||
| import org.springframework.data.annotation.LastModifiedDate; | ||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| @Entity | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @Table(name = "help_contents") | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| public class HelpContentJpaEntity { | ||
|
|
||
| @Id | ||
| @Column(nullable = false, updatable = false) | ||
| private String key; | ||
|
|
||
| @Column(nullable = false, columnDefinition = "TEXT") | ||
| private String content; | ||
|
|
||
| @Version | ||
| @Column(nullable = false) | ||
| private Long version; | ||
|
|
||
| @CreatedDate | ||
| @Column(nullable = false, updatable = false) | ||
| private Instant createdAt; | ||
|
|
||
| @LastModifiedDate | ||
| @Column(nullable = false) | ||
| private Instant updatedAt; | ||
| } |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/process/clash/adapter/persistence/helpcontent/HelpContentJpaMapper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.process.clash.adapter.persistence.helpcontent; | ||
|
|
||
| import com.process.clash.domain.helpcontent.entity.HelpContent; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class HelpContentJpaMapper { | ||
|
|
||
| public HelpContent toDomain(HelpContentJpaEntity entity) { | ||
| return new HelpContent( | ||
| entity.getKey(), | ||
| entity.getContent(), | ||
| entity.getVersion(), | ||
| entity.getCreatedAt(), | ||
| entity.getUpdatedAt() | ||
| ); | ||
| } | ||
|
|
||
| public HelpContentJpaEntity toJpaEntity(HelpContent domain) { | ||
| return new HelpContentJpaEntity( | ||
| domain.key(), | ||
| domain.content(), | ||
| domain.version(), | ||
| domain.createdAt(), | ||
| domain.updatedAt() | ||
| ); | ||
| } | ||
| } |
6 changes: 6 additions & 0 deletions
6
...main/java/com/process/clash/adapter/persistence/helpcontent/HelpContentJpaRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.process.clash.adapter.persistence.helpcontent; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface HelpContentJpaRepository extends JpaRepository<HelpContentJpaEntity, String> { | ||
| } |
33 changes: 33 additions & 0 deletions
33
...java/com/process/clash/adapter/persistence/helpcontent/HelpContentPersistenceAdapter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.process.clash.adapter.persistence.helpcontent; | ||
|
|
||
| import com.process.clash.application.helpcontent.port.out.HelpContentRepositoryPort; | ||
| import com.process.clash.domain.helpcontent.entity.HelpContent; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| @RequiredArgsConstructor | ||
| public class HelpContentPersistenceAdapter implements HelpContentRepositoryPort { | ||
|
|
||
| private final HelpContentJpaRepository helpContentJpaRepository; | ||
| private final HelpContentJpaMapper helpContentJpaMapper; | ||
|
|
||
| @Override | ||
| public Optional<HelpContent> findByKey(String key) { | ||
| return helpContentJpaRepository.findById(key).map(helpContentJpaMapper::toDomain); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean existsByKey(String key) { | ||
| return helpContentJpaRepository.existsById(key); | ||
| } | ||
|
|
||
| @Override | ||
| public HelpContent save(HelpContent helpContent) { | ||
| HelpContentJpaEntity entity = helpContentJpaMapper.toJpaEntity(helpContent); | ||
| HelpContentJpaEntity saved = helpContentJpaRepository.saveAndFlush(entity); | ||
| return helpContentJpaMapper.toDomain(saved); | ||
| } | ||
| } | ||
56 changes: 56 additions & 0 deletions
56
...om/process/clash/adapter/web/helpcontent/admin/controller/AdminHelpContentController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.process.clash.adapter.web.helpcontent.admin.controller; | ||
|
|
||
| import com.process.clash.adapter.web.common.ApiResponse; | ||
| import com.process.clash.adapter.web.helpcontent.docs.controller.AdminHelpContentControllerDocument; | ||
| import com.process.clash.adapter.web.helpcontent.dto.CreateHelpContentDto; | ||
| import com.process.clash.adapter.web.helpcontent.dto.UpdateHelpContentDto; | ||
| import com.process.clash.adapter.web.security.AuthenticatedActor; | ||
| import com.process.clash.application.common.actor.Actor; | ||
| import com.process.clash.application.helpcontent.data.CreateHelpContentData; | ||
| import com.process.clash.application.helpcontent.data.UpdateHelpContentData; | ||
| import com.process.clash.application.helpcontent.port.in.CreateHelpContentUseCase; | ||
| import com.process.clash.application.helpcontent.port.in.UpdateHelpContentUseCase; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| 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; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/admin/help-contents") | ||
| @RequiredArgsConstructor | ||
| public class AdminHelpContentController implements AdminHelpContentControllerDocument { | ||
|
|
||
| private final CreateHelpContentUseCase createHelpContentUseCase; | ||
| private final UpdateHelpContentUseCase updateHelpContentUseCase; | ||
|
|
||
| @Override | ||
| @PostMapping | ||
| public ApiResponse<CreateHelpContentDto.Response> createHelpContent( | ||
| @AuthenticatedActor Actor actor, | ||
| @Valid @RequestBody CreateHelpContentDto.Request request | ||
| ) { | ||
| CreateHelpContentData.Result result = createHelpContentUseCase.execute(request.toCommand(actor)); | ||
| return ApiResponse.created( | ||
| CreateHelpContentDto.Response.from(result), | ||
| "도움말 내용이 성공적으로 생성되었습니다." | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| @PutMapping("/{key}") | ||
| public ApiResponse<UpdateHelpContentDto.Response> updateHelpContent( | ||
| @AuthenticatedActor Actor actor, | ||
| @PathVariable String key, | ||
| @Valid @RequestBody UpdateHelpContentDto.Request request | ||
| ) { | ||
| UpdateHelpContentData.Result result = updateHelpContentUseCase.execute(request.toCommand(actor, key)); | ||
| return ApiResponse.success( | ||
| UpdateHelpContentDto.Response.from(result), | ||
| "도움말 내용이 성공적으로 수정되었습니다." | ||
| ); | ||
| } | ||
| } |
44 changes: 44 additions & 0 deletions
44
...main/java/com/process/clash/adapter/web/helpcontent/controller/HelpContentController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package com.process.clash.adapter.web.helpcontent.controller; | ||
|
|
||
| import com.process.clash.adapter.web.helpcontent.docs.controller.HelpContentControllerDocument; | ||
| import com.process.clash.application.helpcontent.data.GetHelpContentData; | ||
| import com.process.clash.application.helpcontent.port.in.GetHelpContentUseCase; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.CacheControl; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.http.ResponseEntity; | ||
| 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; | ||
| import org.springframework.web.context.request.WebRequest; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/help-contents") | ||
| @RequiredArgsConstructor | ||
| public class HelpContentController implements HelpContentControllerDocument { | ||
|
|
||
| private final GetHelpContentUseCase getHelpContentUseCase; | ||
|
|
||
| @Override | ||
| @GetMapping("/{key}") | ||
| public ResponseEntity<String> getHelpContent( | ||
|
chaeyn marked this conversation as resolved.
Resolved
|
||
| @PathVariable String key, | ||
| WebRequest request | ||
| ) { | ||
| GetHelpContentData.Result result = getHelpContentUseCase.execute(key); | ||
| String etag = "\"" + result.version() + "\""; | ||
|
|
||
| if (request.checkNotModified(etag)) { | ||
| return null; | ||
| } | ||
|
|
||
| return ResponseEntity.ok() | ||
| .contentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8)) | ||
| .cacheControl(CacheControl.noCache()) | ||
| .eTag(etag) | ||
| .body(result.content()); | ||
| } | ||
| } | ||
69 changes: 69 additions & 0 deletions
69
...ess/clash/adapter/web/helpcontent/docs/controller/AdminHelpContentControllerDocument.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.process.clash.adapter.web.helpcontent.docs.controller; | ||
|
|
||
| import com.process.clash.adapter.web.common.ApiResponse; | ||
| import com.process.clash.adapter.web.helpcontent.dto.CreateHelpContentDto; | ||
| import com.process.clash.adapter.web.helpcontent.dto.UpdateHelpContentDto; | ||
| import com.process.clash.application.common.actor.Actor; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| 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.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
|
|
||
| @Tag(name = "어드민 도움말 API", description = "클라이언트 도움말 수정") | ||
| public interface AdminHelpContentControllerDocument { | ||
|
|
||
| @Operation(summary = "도움말 생성", description = "새 도움말 키와 전체 텍스트를 생성합니다.") | ||
| @ApiResponses({ | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "생성 성공", | ||
| content = @Content( | ||
| schema = @Schema(implementation = CreateHelpContentDto.Response.class), | ||
| examples = @ExampleObject(value = """ | ||
| { | ||
| "success": true, | ||
| "message": "도움말 내용이 성공적으로 생성되었습니다.", | ||
| "data": { | ||
| "key": "new-tooltip", | ||
| "content": "새 안내 문구입니다.", | ||
| "version": 0, | ||
| "createdAt": "2026-07-14T14:00:00Z" | ||
| } | ||
| } | ||
| """) | ||
| )), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "이미 존재하는 도움말 키") | ||
| }) | ||
| ApiResponse<CreateHelpContentDto.Response> createHelpContent( | ||
| @Parameter(hidden = true) Actor actor, | ||
| CreateHelpContentDto.Request request | ||
| ); | ||
|
|
||
| @Operation(summary = "도움말 수정", description = "도움말 전체 텍스트를 수정하고 ETag 버전을 증가시킵니다.") | ||
| @ApiResponses({ | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "수정 성공", | ||
| content = @Content( | ||
| schema = @Schema(implementation = UpdateHelpContentDto.Response.class), | ||
| examples = @ExampleObject(value = """ | ||
| { | ||
| "success": true, | ||
| "message": "도움말 내용이 성공적으로 수정되었습니다.", | ||
| "data": { | ||
| "key": "cookie-tooltip", | ||
| "content": "쿠키는 상점에서 사용 할 수 있어요!", | ||
| "version": 2, | ||
| "updatedAt": "2026-07-14T14:00:00Z" | ||
| } | ||
| } | ||
| """) | ||
| )), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "도움말을 찾을 수 없음") | ||
| }) | ||
| ApiResponse<UpdateHelpContentDto.Response> updateHelpContent( | ||
| @Parameter(hidden = true) Actor actor, | ||
| @Parameter(description = "수정할 도움말 키", example = "cookie-tooltip", required = true) @PathVariable String key, | ||
| UpdateHelpContentDto.Request request | ||
| ); | ||
| } |
28 changes: 28 additions & 0 deletions
28
.../process/clash/adapter/web/helpcontent/docs/controller/HelpContentControllerDocument.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.process.clash.adapter.web.helpcontent.docs.controller; | ||
|
|
||
| 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.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.context.request.WebRequest; | ||
|
|
||
| @Tag(name = "도움말 API", description = "클라이언트 도움말 조회") | ||
| public interface HelpContentControllerDocument { | ||
|
|
||
| @Operation(summary = "도움말 조회", description = "If-None-Match 헤더가 현재 ETag와 같으면 304를, 다르면 도움말 전체 텍스트와 ETag를 반환합니다.") | ||
| @ApiResponses({ | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", | ||
| content = @Content( | ||
| mediaType = "text/plain", | ||
| schema = @Schema(type = "string"), | ||
| examples = @ExampleObject(value = "쿠키는 상점에서 사용 할 수 있어요!\\n\\n로드맵 챕터 클리어: 100 쿠키") | ||
| )), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "304", description = "변경 없음"), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "도움말을 찾을 수 없음") | ||
| }) | ||
| ResponseEntity<String> getHelpContent(@PathVariable String key, WebRequest request); | ||
| } |
33 changes: 33 additions & 0 deletions
33
src/main/java/com/process/clash/adapter/web/helpcontent/dto/CreateHelpContentDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.process.clash.adapter.web.helpcontent.dto; | ||
|
|
||
| import com.process.clash.application.common.actor.Actor; | ||
| import com.process.clash.application.helpcontent.data.CreateHelpContentData; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Pattern; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public class CreateHelpContentDto { | ||
|
|
||
| @Schema(name = "CreateHelpContentDtoRequest") | ||
| public record Request( | ||
| @NotBlank(message = "도움말 키는 비워둘 수 없습니다.") | ||
| @Pattern(regexp = "[a-z0-9-]+", message = "도움말 키는 영문 소문자, 숫자, 하이픈만 사용할 수 있습니다.") | ||
| String key, | ||
|
|
||
| @NotBlank(message = "도움말 내용은 비워둘 수 없습니다.") | ||
| String content | ||
| ) { | ||
| public CreateHelpContentData.Command toCommand(Actor actor) { | ||
| return new CreateHelpContentData.Command(actor, key, content); | ||
| } | ||
| } | ||
|
|
||
| @Schema(name = "CreateHelpContentDtoResponse") | ||
| public record Response(String key, String content, long version, Instant createdAt) { | ||
| public static Response from(CreateHelpContentData.Result result) { | ||
| return new Response(result.key(), result.content(), result.version(), result.createdAt()); | ||
| } | ||
| } | ||
| } |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/process/clash/adapter/web/helpcontent/dto/UpdateHelpContentDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.process.clash.adapter.web.helpcontent.dto; | ||
|
|
||
| import com.process.clash.application.common.actor.Actor; | ||
| import com.process.clash.application.helpcontent.data.UpdateHelpContentData; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public class UpdateHelpContentDto { | ||
|
|
||
| @Schema(name = "UpdateHelpContentDtoRequest") | ||
| public record Request( | ||
| @NotBlank(message = "도움말 내용은 비워둘 수 없습니다.") | ||
| String content | ||
| ) { | ||
| public UpdateHelpContentData.Command toCommand(Actor actor, String key) { | ||
| return new UpdateHelpContentData.Command(actor, key, content); | ||
| } | ||
| } | ||
|
|
||
| @Schema(name = "UpdateHelpContentDtoResponse") | ||
| public record Response(String key, String content, long version, Instant updatedAt) { | ||
| public static Response from(UpdateHelpContentData.Result result) { | ||
| return new Response(result.key(), result.content(), result.version(), result.updatedAt()); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.