Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c2a7a51
Refactor: 문서 생성 전략 분리(MySQL Tx / Saga) | 조회 서비스 분리 | 테스트 수정
lunarbae628 Feb 12, 2026
bc74e88
Refactor: 문서 생성 시 MySQL 실패시 MongoDB 보상 삭제 로직 추가 및 MongoIdsDto에 팩토리 메서…
lunarbae628 Feb 13, 2026
6366201
Refactor: Mongo 삭제 로직을 deleteAllById 기반 벌크 처리로 변경(MongoDeleteRetrySer…
lunarbae628 Feb 13, 2026
a77420c
Refactor: 문서 생성 시 유저검사&중복제목 검사 후 DocCreateMySqlTxService 타도록 변경
lunarbae628 Feb 13, 2026
2879ca9
Refactor: 문서 생성 실패 MongoDB 보상 삭제 통합테스트 작성
lunarbae628 Feb 13, 2026
71fc76f
Chore: 스케쥴러 관련 TODO
lunarbae628 Feb 13, 2026
c1c2cc7
Refactor: getSimpleList이름 변경(getSimplePage) 및 DocQueryService로 분리 및 테…
lunarbae628 Feb 13, 2026
ef40d88
Refactor: DocService의 Repository 직접 접근 로직을 DocQueryService로 분리
lunarbae628 Feb 14, 2026
a2f0796
Refactor: findTitleOnlyById삭제(DocRepository)
lunarbae628 Feb 14, 2026
b0955d5
Chore: 메소드명 수정
lunarbae628 Feb 14, 2026
ad4898b
Fix: Doc 서비스 분리로인한 테스트코드 수정
lunarbae628 Feb 14, 2026
e052537
Refactor: 프로파릴별 CORS 허용 도메인 분리
lunarbae628 Feb 14, 2026
155d961
Test: 문서 생성 : MySQL 실패 + 보상 삭제 3회 실패 -> RDB 실패 내역 저장 테스트
lunarbae628 Feb 14, 2026
1b6a348
Merge: 문서 생성 보상트랜잭션 도입 / 문서: 명령-조회 서비스 책임분리 / 외 잔잔한 코드수정
lunarbae628 Feb 14, 2026
6702514
Chore: TestUserInitializer 프로파일에 stg도 적용
lunarbae628 Feb 14, 2026
b7a4eb2
Chore: 인증서 갱신 스크립트 및 구조 변경
lunarbae628 Feb 18, 2026
bb8cdfc
Merge: Chore: 인증서 갱신 스크립트 및 구조 변경
lunarbae628 Feb 18, 2026
0d65876
Chore: 오타수정
lunarbae628 Feb 18, 2026
651da3e
Feat: 갱신 스크립트 알림 설정 추가
lunarbae628 Feb 18, 2026
d9918e6
Refactor: Commit 생성 MongoTxService 분리 및 editorId/mongoId 네이밍 정리
lunarbae628 Feb 19, 2026
9ed198a
Chore: 미사용 import 제거
lunarbae628 Feb 19, 2026
3f5524c
Refactor: Commit 생성 MySqlTxService 분리
lunarbae628 Feb 19, 2026
ee90c8c
Refactor: 전역 예외 처리 로깅 개선 및 fallback 핸들러 추가
lunarbae628 Feb 20, 2026
17cb2c1
Refactor: 커밋 생성 Saga 구조 적용 및 Mongo/MySQL 트랜잭션 분리 (기능 테스트 성공)
lunarbae628 Feb 20, 2026
a3fc2ee
Refactor: Commit 생성 baseCommit 조회 안정화 및 merge saveIds null-safe 처리
lunarbae628 Feb 20, 2026
0873a88
Refactor: 문서 생성/전역 예외 처리 보정 (Mongo 예외 매핑, 인증/파라미터 타입 핸들링)
lunarbae628 Feb 20, 2026
181d911
Test: Commit/Doc 생성 Saga 실패 시나리오 및 통합/단위 테스트 보강
lunarbae628 Feb 20, 2026
c52d289
Refactor: Commit CQRS 분리 및 Doc 생성 책임 재배치
lunarbae628 Feb 21, 2026
bf8006b
Refactor: CommitService / CommitQueryService 분리 일부 롤백
lunarbae628 Feb 21, 2026
97a85c7
Refactor: CommitService merge 검증 흐름 단순화 및 Query 책임 분리
lunarbae628 Feb 21, 2026
4024ef7
Refactor: Merge commit Saga 분리 및 보상 데이터 경계 정리
lunarbae628 Feb 21, 2026
ddc13f4
Chore: MongoDeleteFailureScheduler log 변경
lunarbae628 Feb 21, 2026
984e92f
Refactor: CommitMySqlTxService#saveCommit 메소드 삭제
lunarbae628 Feb 21, 2026
811833a
Chore: Commit app 패키지 구조를 create/merge로 분리
lunarbae628 Feb 21, 2026
4b32525
Test: Commit/Doc 서비스 리팩토링 반영 및 merge 검증 케이스 보강
lunarbae628 Feb 21, 2026
8d593f4
Chore: 공백 제거(INVALID_FROM_COMMIT 메시지)
lunarbae628 Feb 22, 2026
3491b6a
Fix: 500 예외 응답의 body status를 HTTP status와 일치하도록 수정
lunarbae628 Feb 22, 2026
c0eb2ff
Chore: 메소드명 변경 - createDefaultSave, saveDefaultSaveContent -> createS…
lunarbae628 Feb 22, 2026
c3a5fe8
Chore: 메소드명 변경 - createDefaultBranch -> createBranch
lunarbae628 Feb 22, 2026
f7188f1
Refactor: Branch createBranchOrSave Saga 분리 및 BranchQueryService 도입
lunarbae628 Feb 23, 2026
88548d4
Test: CommitServiceMockTest BranchQueryService 호출 동기화
lunarbae628 Feb 23, 2026
52d6cbd
Fix: save가 있는 브랜치에서 새 브랜치 생성 실패 수정
lunarbae628 Feb 23, 2026
0036a54
Chore: BranchCreateRequest @NotNull 메시지 오타 수정
lunarbae628 Feb 23, 2026
5626a32
Test: BranchService 이어가기 분기(기존 save 재사용/이름중복/새 브랜치 경로) 검증 추가
lunarbae628 Feb 23, 2026
f20ee49
Fix: 이어서 작업하기 - leafCommit=rootCommit이고 save가 있는 상태에서 기존 브랜치명 요청 시 발생…
lunarbae628 Feb 23, 2026
daa6d72
Feat: fromCommit ManyToOne으로 변경
lunarbae628 Feb 23, 2026
de26642
Test: Branch 이어서 작업하기 분기 통합/단위 테스트 보강
lunarbae628 Feb 23, 2026
c76e79e
Chore: doc/app/create 패키지 생성
lunarbae628 Feb 25, 2026
45bb01f
Chore: 미사용 의존성 제거
lunarbae628 Feb 25, 2026
ef4f8a5
Refactor: SaveService / SaveQueryService로 분리
lunarbae628 Feb 25, 2026
033e361
Test: SaveQueryService 분리에 따른 테스트 코드 수정
lunarbae628 Feb 25, 2026
0561c4c
Chore: DocService에서 BranchRepository 의존성 제거 -> BranchQueryService
lunarbae628 Feb 26, 2026
a45b28e
Chore: CommitGraphResponse 네이밍 변경 -> GraphResponse
lunarbae628 Feb 26, 2026
f1ac23c
Chore: BranchQueryService 조회 메소드 @Transactional(readOnly = true) 설정
lunarbae628 Feb 26, 2026
4aa7eed
Chore: GraphCommitDto -> CommitGraphDto, GraphEdgeDto -> EdgeDto
lunarbae628 Feb 26, 2026
e48ebb2
Chore: Doc 그래프 조회 경로 정리 (CommitRepository -> CommitQueryService)
lunarbae628 Feb 26, 2026
508f061
Test: DocService 그래프 조회 테스트 의존성 보정
lunarbae628 Feb 26, 2026
e940a54
Chore: edge 패키지 구조 변경 -> domain/edge
lunarbae628 Feb 26, 2026
73b9a2d
Refactor: 타 도메인의 EdgeRepository 직접 의존 제거 및 EdgeService 메서드 추가
lunarbae628 Feb 27, 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
4 changes: 2 additions & 2 deletions infra/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ echo "[deploy] compose=$COMPOSE_FILE env=$ENV_FILE service=$SERVICE"
docker compose "${ARGS[@]}" config >/dev/null

# 2) 이미지 풀 + 대상 서비스만 업데이트
docker compose "${ARGS[@]}" pull "$SERVICE" || true
docker compose "${ARGS[@]}" pull "$SERVICE"
docker compose "${ARGS[@]}" up -d "$SERVICE"

# 3) 컨테이너 ID를 compose로 조회(이름 하드코딩 회피)
Expand Down Expand Up @@ -85,4 +85,4 @@ if [[ $ok -eq 0 ]]; then
echo -e "\n$SERVICE failed to become healthy (status=${status:-unknown})"
docker logs --tail=200 "$CID" || true
exit 1
fi
fi
31 changes: 2 additions & 29 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ services:
--agree-tos --non-interactive
--no-eff-email

# 자동 갱신 데몬(하루마다 확인, 갱신되면 nginx 리로드)
# 인증서 갱신용 컨테이너(cron)
certbot_renew:
image: certbot/certbot:latest
container_name: docsa-certbot-renew
Expand All @@ -114,33 +114,6 @@ services:
- ./certbot/www:/var/www/certbot:rw
- ./certbot/etc:/etc/letsencrypt:rw
- ./certbot/logs:/var/log/letsencrypt:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
entrypoint: >
sh -c '
set -eu

# curl 없으면 설치
if ! command -v curl >/dev/null 2>&1; then
if command -v apk >/dev/null 2>&1; then
apk add --no-cache curl >/dev/null 2>&1
else
echo "[ERROR] curl not found and apk not available. Need curl-capable image." >&2
exit 1
fi
fi

while :; do
# renew 실행
certbot renew --webroot -w /var/www/certbot --quiet || true

# nginx 무중단 reload (Docker Engine API)
curl -sS --unix-socket /var/run/docker.sock \
-X POST "http://localhost/v1.41/containers/docsa-nginx/kill?signal=HUP" \
>/dev/null 2>&1 || true

sleep 24h
done
'

# ===== METRICS =====
cadvisor:
Expand Down Expand Up @@ -228,4 +201,4 @@ volumes:
grafana_data:

networks:
docsa_net:
docsa_net:
67 changes: 67 additions & 0 deletions infra/scripts/cert_renew.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# Slack webhook (선택)
ENV_FILE="$ROOT/docsa-alert.env"
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"

notify_slack () {
local msg="$1"
[[ -z "${SLACK_WEBHOOK_URL:-}" ]] && return 0
curl -sS -X POST -H 'Content-type: application/json' \
--data "{\"text\":${msg@Q}}" \
"$SLACK_WEBHOOK_URL" >/dev/null 2>&1 || true
}

# ---- 1) certbot renew 실행 ----
out="$(
docker compose run --rm --no-deps certbot_renew \
certbot renew --webroot -w /var/www/certbot 2>&1 || true
)"

echo "$out"

# ---- 2) 실패 감지 ----
# certbot이 실패할 때 자주 나오는 문구 위주로 (오탐 줄임)
if echo "$out" | grep -Eqi \
"certbot failed to authenticate|failed authorization procedure|the following errors were reported|unauthorized|too many requests|nxdomain|connection refused|timeout"; then
err_summary="$(echo "$out" | tail -n 40)"
notify_slack "❌ *Certbot renew 실패*
\`\`\`
${err_summary}
\`\`\`"
exit 1
fi

# ---- 3) 갱신됨 감지 (갱신 성공 시에만 nginx reload) ----
# 성공 시 출력되는 대표 문구들
renewed=0
if echo "$out" | grep -Eqi "successfully renewed|congratulations, all renewals succeeded|renewal succeeded"; then
renewed=1
fi

if (( renewed == 1 )); then
# 실행 중인 컨테이너만 HUP
for c in docsa-nginx docsa-nginx-stg; do
if [[ -n "$(docker ps -q -f "name=^/${c}$")" ]]; then
docker kill -s HUP "$c" >/dev/null 2>&1 || true
fi
done

# ---- 4) 실제 서비스에서 인증서 만료일 확인(nginx 적용 검증) ----
cert_api="$(echo | openssl s_client -connect api.docsa.o-r.kr:443 -servername api.docsa.o-r.kr 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2 || true)"

cert_stg="$(echo | openssl s_client -connect stg.api.docsa.o-r.kr:8443 -servername stg.api.docsa.o-r.kr 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2 || true)"

notify_slack "✅ *인증서 갱신 성공* → Nginx Reload 완료
• api 만료일: \`${cert_api:-확인실패}\`
• stg 만료일: \`${cert_stg:-확인실패}\`"
else
# 갱신 불필요도 정상. (스팸 방지로 슬랙은 안 보냄)
echo "[info] No renewal needed."
fi
48 changes: 48 additions & 0 deletions infra/scripts/check_cert_expiry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="/srv/docsa/infra/docsa-alert.env"
THRESHOLD_DAYS=14

DOMAINS=(
"api.docsa.o-r.kr:443"
"stg.api.docsa.o-r.kr:8443"
)

[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"

notify_slack () {
local msg="$1"
[[ -z "${SLACK_WEBHOOK_URL:-}" ]] && return 0
curl -sS -X POST -H 'Content-type: application/json' \
--data "{\"text\":${msg@Q}}" \
"$SLACK_WEBHOOK_URL" >/dev/null || true
}

failed=0
now_epoch=$(date -u +%s)

for target in "${DOMAINS[@]}"; do
host="${target%%:*}"
port="${target##*:}"

exp_date=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null \
| openssl x509 -noout -enddate \
| cut -d= -f2 || true)

[[ -z "$exp_date" ]] && {
notify_slack "❌ $host:$port 인증서 만료일 조회 실패"
failed=1
continue
}

exp_epoch=$(date -u -d "$exp_date" +%s)
remain_days=$(( (exp_epoch - now_epoch) / 86400 ))

if (( remain_days <= THRESHOLD_DAYS )); then
notify_slack "⚠️ $host:$port 인증서 만료 임박 D-${remain_days} (만료: $exp_date UTC)"
failed=1
fi
done

exit "$failed"
29 changes: 29 additions & 0 deletions infra/scripts/check_server_alive.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="/srv/docsa/infra/docsa-alert.env"
TIMEOUT=5

TARGETS=(
"api.docsa.o-r.kr:443"
"stg.api.docsa.o-r.kr:8443"
)

[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"

notify_slack () {
local msg="$1"
[[ -z "${SLACK_WEBHOOK_URL:-}" ]] && return 0
curl -sS -X POST -H 'Content-type: application/json' \
--data "{\"text\":${msg@Q}}" \
"$SLACK_WEBHOOK_URL" >/dev/null 2>&1 || true
}

for target in "${TARGETS[@]}"; do
host="${target%%:*}"
port="${target##*:}"

if ! timeout "${TIMEOUT}" bash -c "</dev/tcp/${host}/${port}" 2>/dev/null; then
notify_slack "🚨 아아 공습경보 서버 불량: ${host}:${port}"
fi
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.ejangs.docsa.domain.branch.app;

import io.ejangs.docsa.domain.branch.dao.mysql.BranchRepository;
import io.ejangs.docsa.domain.branch.entity.Branch;
import io.ejangs.docsa.domain.commit.entity.Commit;
import io.ejangs.docsa.domain.edge.dto.graph.BranchGraphDto;
import io.ejangs.docsa.domain.doc.entity.Doc;
import io.ejangs.docsa.global.exception.CustomException;
import io.ejangs.docsa.global.exception.errorcode.BranchErrorCode;
import io.ejangs.docsa.global.util.RenewUpdatedAtHelper;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class BranchQueryService {

private final BranchRepository branchRepository;

@Transactional(readOnly = true)
public Branch getById(Long id) {
return branchRepository.findById(id).orElseThrow(() -> new CustomException(BranchErrorCode.BRANCH_NOT_FOUND));
}

@Transactional(readOnly = true)
public List<BranchGraphDto> getBranchGraphList(Long docId) {
return branchRepository.findBranchGraphDtoList(docId);
}

public Branch save(Branch branch) {
return branchRepository.save(branch);
}

public Branch createBranch(Doc doc, String branchName) {
Branch branch = branchRepository.save(Branch.builder().name(branchName).doc(doc).build());
RenewUpdatedAtHelper.touch(branch);
return branch;
}

public Branch createBranch(Doc doc, String branchName, Commit fromCommit) {
Branch branch = Branch.builder().name(branchName).doc(doc).fromCommit(fromCommit).build();
return branchRepository.save(branch);
}

@Transactional(readOnly = true)
public boolean existsSubBranchByFromCommitIds(List<Long> commitIds) {
return branchRepository.existsByFromCommitIdIn(commitIds);
}

public void delete(Branch branch) {
branchRepository.delete(branch);
}

//TODO: 검증하는 메소드는 별도의 ex.ValidationService로 분리(모든 도메인)
public void checkBranchInDocOwnedByUser(Long documentId, Long branchId, Long userId) {
boolean exists =
branchRepository.existsByIdAndDocIdAndDocUserId(branchId, documentId, userId);
if (!exists) {
throw new CustomException(BranchErrorCode.BRANCH_NOT_FOUND);
}
}

public boolean checkFromOrRootCommitInBranch(Commit commit) {
return branchRepository.existsByRootCommitIdOrFromCommitId(commit.getId());
}

public void checkDuplicatedWithBranchName(Long docId, String branchName) {
boolean isDuplicate = branchRepository.existsByDocIdAndName(docId, branchName);

if (isDuplicate) {
throw new CustomException(BranchErrorCode.BRANCH_NAME_DUPLICATED);
}
}
}
Loading
Loading