Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
6de2608
feat: 도커파일 추가
2Jin1031 Nov 9, 2025
8a35243
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
586b092
Merge pull request #101 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
d767c3e
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
804497f
Merge pull request #102 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
243dccc
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
f847f66
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
fe5bdc0
Merge pull request #103 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
1a09bfc
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
4c26dd0
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
3dd40bf
Merge pull request #104 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
bf2e482
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
5534cbf
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
9f7018b
Merge pull request #106 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
22d24c4
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
30beb53
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
2d9ca12
Merge pull request #107 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
94d39e6
chore: .gitignore 업데이트
2Jin1031 Nov 9, 2025
b819115
chore: .gitignore 업데이트
2Jin1031 Nov 9, 2025
8a0cce0
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
1db186e
Merge pull request #108 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
8a3b35b
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
cb69ae2
tmp: dev cd 트리거
2Jin1031 Nov 9, 2025
4da8e40
Merge pull request #109 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
8b5cbc6
merge: main과 merge
2Jin1031 Nov 9, 2025
5160c0f
Merge pull request #110 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
bbcbf07
`Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
f362c70
Merge pull request #111 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
020a049
Merge branch 'main' into feat/100
2Jin1031 Nov 9, 2025
97c2646
Merge pull request #112 from Crew-Wiki/feat/100
2Jin1031 Nov 9, 2025
a9791ea
Merge branch 'main' into feat/100
2Jin1031 Nov 11, 2025
e019ef0
Merge pull request #113 from Crew-Wiki/feat/100
2Jin1031 Nov 11, 2025
5b10c31
Merge branch 'main' into develop
2Jin1031 Nov 11, 2025
2aa974e
feat: 문서 삭제 요청 데이터 수정 (id -> uuid)
2Jin1031 Nov 17, 2025
21e21dc
Merge pull request #116 from Crew-Wiki/feat/115
2Jin1031 Nov 19, 2025
c9882a7
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
9ece709
feat: local yml 추가
2Jin1031 Dec 14, 2025
c34984a
Merge pull request #118 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
827347c
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
5e1e391
tmp: cd 트리거
2Jin1031 Dec 14, 2025
4aaf39c
Merge pull request #120 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
84652bb
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
5d457e0
Merge pull request #121 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
8cb2a53
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
671c800
Merge pull request #122 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
bf2ab25
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
fa3e183
Merge pull request #123 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
1fec7aa
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
b0e5a39
Merge pull request #124 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
f3dba2b
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
fa81e46
Merge pull request #125 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
387c8ce
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
84f56dc
Merge pull request #126 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
179fa59
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
68964b7
Merge pull request #128 from Crew-Wiki/feat/117
2Jin1031 Dec 14, 2025
4ba1f46
Merge branch 'main' into develop
2Jin1031 Dec 14, 2025
084513a
feat: Dockerfile 환경 변수 일반화
2Jin1031 Dec 14, 2025
3be3d90
Merge pull request #130 from Crew-Wiki/feat/129
2Jin1031 Dec 14, 2025
47ee6ee
refactor: 자바 마이그레이션 (#132)
CheChe903 Dec 28, 2025
ab6b65b
test: 문서 조회 테스트 추가
Starlight258 Jan 11, 2026
b76d2d2
test: 사용하지 않은 변수에 대한 선언 제거
Starlight258 Jan 11, 2026
027304c
feat: 조직문서 수정시 히스토리 저장 (#134)
CheChe903 Jan 12, 2026
fabdf02
Merge pull request #136 from Starlight258/test/135
Starlight258 Jan 13, 2026
f1910ca
feat: 특정 문서에 대한 조직 문서 삭제 API 구현
2Jin1031 Feb 1, 2026
1105cc3
Merge pull request #138 from Crew-Wiki/feat/137
2Jin1031 Feb 1, 2026
e1d1253
feat: 조직 문서 생성 시 첫 로그 저장 (#140)
CheChe903 Feb 7, 2026
7731cbf
feat: 문서 조회시 문서 타입을 응답에 추가 (#142)
CheChe903 Feb 7, 2026
1ae401e
feat: 크루문서 삭제 시, 조직 문서 연결 해제 기능 구현
2Jin1031 Feb 23, 2026
937a189
feat: DocumentService에서 CrewDocumentService 분리
2Jin1031 Feb 23, 2026
6021df7
test: 변경된 메서드명으로 테스트파일 수정
2Jin1031 Feb 23, 2026
55a8667
[REFACTOR]: 코드 컨벤션 적용 (#144)
CheChe903 Feb 26, 2026
d9ebaa8
fix: develop 브랜치 충돌 해결
2Jin1031 Mar 2, 2026
e69b3e8
Merge pull request #148 from Crew-Wiki/feat/147
2Jin1031 Mar 2, 2026
3155886
test: DocumentService 분리로 Crew/Organization 테스트 분리
2Jin1031 Mar 3, 2026
f1a8798
Merge pull request #149 from Crew-Wiki/feat/147
2Jin1031 Mar 3, 2026
cc11cfa
feat: 기존 조직 문서를 크루 문서에 연결 기능 구현
2Jin1031 Mar 4, 2026
a497dc4
Merge pull request #151 from Crew-Wiki/feat/150
2Jin1031 Mar 4, 2026
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: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM eclipse-temurin:17

WORKDIR /app

COPY build/libs/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE}", "-jar", "app.jar"]
160 changes: 160 additions & 0 deletions docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# CrewWiki Backend 컨벤션

## 1. 패키지 구조
```
├── post
│ ├── controller
│ ├── dto
│ ├── repository
│ └── service
└── user
│ ├── controller
│ ├── dto
│ ├── repository
│ └── service
├── common
│ ├── config
│ ├── entity
│ ├── exception
│ ├── infrastructure
│ ├── log
│ └── utils
```

## 2. 테스트 코드
### 2.1 네이밍
- `@Nested`는 프로덕션 **메서드명**을 기준으로 작성한다.
- `@Nested` 클래스명은 **UpperCamel**로 표기한다.
- 테스트 메서드명 형식:
- `프로덕션메서드_success_조건`
- `프로덕션메서드_fail_조건`

예시
```java
@Nested
@DisplayName("로그인 기능")
class Login {
@Test
@DisplayName("유효한 정보로 로그인 성공")
void login_success_byValidCredentials() { }

@Test
@DisplayName("유효하지 않은 정보로 로그인 실패")
void login_fail_byInvalidCredentials() { }
}
```

### 2.2 BDD 패턴
```java
// given (불필요하면 생략)
// when
// then
```

### 2.3 Fixture
- 정적 팩토리 메서드 사용

예시
```java
CrewDocument doc = DocumentFixture.createDefaultCrewDocument();
```

### 2.4 테스트 환경 및 전략
- 인증/인가 필요 시 컨트롤러 테스트에서 `RestAssured` 사용
- 서비스 테스트는 통합 테스트로 작성 (`@SpringBootTest(WebEnvironment.MOCK/NONE)`)하여 톰캣을 띄우지 않음
- 필요 시 `assertSoftly`로 감싸기
- 결과 검증은 **상태 검증 우선**
- 서비스 테스트는 필수, 컨트롤러 테스트는 필요 시

## 3. 네이밍 규칙
- 메서드명: 동사로 시작, 엔티티명 미사용
- 예: `memberService.findById(Long id)`
- Path Variable 명확화
- 예: `/api/{documentId}`
- DTO: `Request`/`Response` 접미사 사용
- 등록/수정은 `Register`/`Update` 가능
- 예: `DocumentSearchResponse`, `DocumentUpdateRequest`

## 4. 객체지향 생활체조 (최대한 지킴)
1. 한 메서드 한 indent
2. `else` 금지
3. 원시값·문자열 포장
4. 한 줄에 점 하나
5. 축약 금지
6. 작은 엔티티
7. 인스턴스 변수 ≤ 2
8. 일급 컬렉션
9. Getter/Setter는 필요할 때만 사용

## 5. 예외 처리
- 에러 코드를 포함해 반환

## 6. 어노테이션 순서 (권장)
1. 로깅
2. 롬복
3. 스프링 메타
4. 스프링 컴포넌트 (가장 아래)

예시
```java
@Slf4j
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
@Entity
@Table(name = "...")
public class SampleEntity {
...
}
```

## 7. 도메인 외부 노출 범위
- 서비스 외부에는 DTO만 노출

예시
```java
public ApiResponse<SuccessBody<DocumentResponse>> getByUuid(...) { }
```

## 8. API ↔ DTO
- 1:1 매핑
- 어드민/일반 API가 다르면 DTO도 분리

예시
- 어드민 문서 검색 → `AdminDocumentSearchResponse`
- 일반 문서 검색 → `DocumentSearchResponse`

## 9. 코드 스타일
- 클래스 선언 전 공백 한 줄
- 메서드 파라미터 2개 이상이면 개행
- 메서드 라인 수 제한 없음 (개행 규칙 우선)

## 10. 검증 위치
- 행동 검증: 서비스
- 상태 검증: 도메인
- 애매한 경우 개발 시 협의

## 11. import
- 와일드카드 금지
- IntelliJ 컨벤션 적용
- static import 허용

## 12. 기타
- `final`은 필드/상수에 사용 가능
- 정적 팩토리 메서드 사용 가능
- VO 미사용 (필요 시 도입)
- 상수는 `UPPER_SNAKE_CASE`
- 접근자/메서드 정렬: `public` → `protected` → `private`
- 예외: getter는 맨 아래

## 13. DTO 정책 (업데이트)
- DTO는 `record`를 최대한 유지
- 불가피한 경우에만 `class` 사용 (예: `@ModelAttribute` 바인딩 등)

현재 적용된 공용 네이밍
- `PagedResponse`
- `PagingRequest`
- `TokenInfoResponse`
- `AuthTokensResponse`
1 change: 0 additions & 1 deletion src/main/java/com/wooteco/wiki/WikiApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public class WikiApplication {
public static void main(String[] args) {
SpringApplication.run(WikiApplication.class, args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.wooteco.wiki.global.common.ApiResponse;
import com.wooteco.wiki.global.common.ApiResponseGenerator;
import io.swagger.v3.oas.annotations.Operation;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -20,10 +21,10 @@ public AdminController(AdminService adminService) {
this.adminService = adminService;
}

@Operation(summary = "문서 삭제", description = "문서 ID로 문서를 삭제합니다.")
@DeleteMapping("/documents/{documentId}")
public ApiResponse<ApiResponse.SuccessBody<Void>> deleteDocumentByDocumentId(@PathVariable Long documentId) {
adminService.deleteDocumentByDocumentId(documentId);
@Operation(summary = "문서 삭제", description = "문서 Uuid로 문서를 삭제합니다.")
@DeleteMapping("/documents/{documentUuid}")
public ApiResponse<ApiResponse.SuccessBody<Void>> deleteDocumentByDocumentId(@PathVariable UUID documentUuid) {
adminService.deleteDocumentByDocumentUuid(documentUuid);
return ApiResponseGenerator.success(HttpStatus.NO_CONTENT);
}
}
16 changes: 6 additions & 10 deletions src/main/java/com/wooteco/wiki/admin/service/AdminService.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package com.wooteco.wiki.admin.service;

import com.wooteco.wiki.document.service.DocumentService;
import com.wooteco.wiki.global.exception.ErrorCode;
import com.wooteco.wiki.global.exception.WikiException;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class AdminService {

private final DocumentService documentService;
private final CrewDocumentService crewDocumentService;

public AdminService(DocumentService documentService) {
this.documentService = documentService;
}

public void deleteDocumentByDocumentId(Long documentId) {
documentService.deleteById(documentId);
public void deleteDocumentByDocumentUuid(UUID documentUuid) {
crewDocumentService.deleteByUuid(documentUuid);
}
}
106 changes: 106 additions & 0 deletions src/main/java/com/wooteco/wiki/admin/service/CrewDocumentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.wooteco.wiki.admin.service;

import com.wooteco.wiki.document.domain.CrewDocument;
import com.wooteco.wiki.document.domain.Document;
import com.wooteco.wiki.document.domain.dto.CrewDocumentCreateRequest;
import com.wooteco.wiki.document.domain.dto.DocumentResponse;
import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest;
import com.wooteco.wiki.document.repository.CrewDocumentRepository;
import com.wooteco.wiki.document.repository.DocumentRepository;
import com.wooteco.wiki.global.exception.ErrorCode;
import com.wooteco.wiki.global.exception.WikiException;
import com.wooteco.wiki.history.service.HistoryService;
import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse;
import com.wooteco.wiki.organizationdocument.service.DocumentOrganizationLinkService;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class CrewDocumentService {

private final DocumentOrganizationLinkService documentOrganizationLinkService;
private final CrewDocumentRepository crewDocumentRepository;
private final DocumentRepository documentRepository;
private final HistoryService historyService;
private final Random random;

@Transactional
public void deleteByUuid(UUID documentUuid) {
CrewDocument crewDocument = crewDocumentRepository.findByUuid(documentUuid)
.orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND));

documentOrganizationLinkService.unlinkAll(crewDocument);

documentRepository.deleteByUuid(documentUuid);
}

@Transactional
public DocumentResponse create(CrewDocumentCreateRequest request) {
String title = request.title();
if (documentRepository.existsByTitle(title)) {
throw new WikiException(ErrorCode.DOCUMENT_DUPLICATE);
}

CrewDocument crewDocument = request.toCrewDocument();
CrewDocument savedDocument = crewDocumentRepository.save(crewDocument);
historyService.save(savedDocument);
return mapToResponse(savedDocument);
}

public DocumentResponse getByUuid(UUID uuid) {
CrewDocument crewDocument = crewDocumentRepository.findByUuid(uuid)
.orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND));
return mapToResponse(crewDocument);
}

public DocumentResponse getByTitle(String title) {
Document document = documentRepository.findByTitle(title)
.orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND));

if (!(document instanceof CrewDocument crewDocument)) {
throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND);
}

return mapToResponse(crewDocument);
}

public DocumentResponse getRandom() {
List<CrewDocument> documents = crewDocumentRepository.findAll();
if (documents.isEmpty()) {
throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND);
}
CrewDocument document = documents.get(random.nextInt(documents.size()));
return mapToResponse(document);
}

@Transactional
public DocumentResponse update(UUID uuid, DocumentUpdateRequest request) {
CrewDocument crewDocument = crewDocumentRepository.findByUuid(uuid)
.orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND));

Document updateData = crewDocument.update(
request.title(),
request.contents(),
request.writer(),
request.documentBytes(),
LocalDateTime.now()
);
historyService.save(updateData);
return mapToResponse(crewDocument);
}

private DocumentResponse mapToResponse(CrewDocument crewDocument) {
long latestVersion = historyService.findLatestVersionByDocument(crewDocument);
List<OrganizationDocumentResponse> organizationDocumentResponses =
documentOrganizationLinkService.findOrganizationDocumentResponsesByDocument(crewDocument);

return DocumentResponse.toDocumentResponse(crewDocument, latestVersion, organizationDocumentResponses);
}
}
Loading
Loading