Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
cac1b31
feat(report): 월간 리포트 V2 도입 및 통합 조회 확장 (#165)
1Seob May 17, 2026
a9d6258
feat(report): 월간 리포트 V2 단건 조회 응답을 라우팅 정보(report) 구조로 변경 (#166)
1Seob May 17, 2026
f860c0d
feat(social): 댓글/대댓글, 좋아요 기능 및 신고, 소셜 정지 기능 구현 (#167)
coldsunn May 18, 2026
4628bb9
feat(social): 좋아요 목록 응답에 friendshipId와 relationshipStatus 추가 (#168)
coldsunn May 20, 2026
49607e4
fix(social): 댓글 삭제 미반영 및 알림 이모지 깨짐 버그 수정 (#169)
coldsunn May 24, 2026
635a7e6
fix(notify): 대댓글 알림 비밀 처리 및 차단 관계 수신자 오류 수정 (#170)
coldsunn May 24, 2026
ec18d23
feat(report): 답변 상세 조회 응답에 dailyReportId 추가 (#171)
coldsunn May 28, 2026
e408c01
refactor(report): 월간 리포트 v1/v2 완전 분리 및 교차 생성 차단 로직 추가 (#172)
1Seob May 29, 2026
bb7a726
fix(social): 댓글·좋아요 접근 제어, 신고 API 접근 제어에 오늘 공유 조건 추가 (#173)
coldsunn Jun 1, 2026
fcfa5f9
feat(admin): 어드민 버전 관리 기능 전반 신규 구축 (도메인/API/UI/인증/배포 연동) (#174)
1Seob Jun 1, 2026
a7fc885
fix(social): 대댓글 카운트·좋아요 아이콘 노출 정합성 수정 (#175)
coldsunn Jun 2, 2026
87265f9
feat: 탈퇴 V2 구현(사유 저장) 및 탈퇴 통계 페이지 추가 (#176)
1Seob Jun 8, 2026
d41fa64
feat: 개발용 테스트 계정 생성 API 추가 (#177)
1Seob Jun 12, 2026
76f663a
fix: 월간 리포트 테스트 삭제 시 V1 fallback 처리 추가 (#178)
1Seob Jun 12, 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
14 changes: 10 additions & 4 deletions .github/workflows/deploy-to-dev-ec2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,13 @@ jobs:
WEEKLY_PROMPT="$(printf '%s' "$WEEKLY_PROMPT_B64" | base64 -d | tr -d '\r')"
export WEEKLY_PROMPT

MONTHLY_PROMPT_B64="${{ secrets.MONTHLY_PROMPT_B64 }}"
MONTHLY_PROMPT="$(printf '%s' "$MONTHLY_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_PROMPT
MONTHLY_V1_PROMPT_B64="${{ secrets.MONTHLY_V1_PROMPT_B64 }}"
MONTHLY_V1_PROMPT="$(printf '%s' "$MONTHLY_V1_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_V1_PROMPT

MONTHLY_V2_BASELINE_PROMPT_B64="${{ secrets.MONTHLY_V2_BASELINE_PROMPT_B64 }}"
MONTHLY_V2_BASELINE_PROMPT="$(printf '%s' "$MONTHLY_V2_BASELINE_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_V2_BASELINE_PROMPT

EVIDENCE_CARD_PROMPT_B64="${{ secrets.EVIDENCE_CARD_PROMPT_B64 }}"
EVIDENCE_CARD_PROMPT="$(printf '%s' "$EVIDENCE_CARD_PROMPT_B64" | base64 -d | tr -d '\r')"
Expand Down Expand Up @@ -143,7 +147,9 @@ jobs:
ANTHROPIC_API_KEY="${{ secrets.ANTHROPIC_API_KEY }}" \
GOOGLE_GENAI_API_KEY="${{ secrets.GOOGLE_GENAI_API_KEY }}" \
FIREBASE_ADMIN_KEY="${{ secrets.FIREBASE_ADMIN_KEY }}" \
ADMIN_PAGE_PASSWORD="${{ secrets.ADMIN_PAGE_PASSWORD }}" \
DEV_TEST_ACCOUNT_PASSWORD="${{ secrets.DEV_TEST_ACCOUNT_PASSWORD }}" \
nohup java -jar "$JAR_PATH" \
--spring.profiles.active=dev > app.log 2>&1 &

echo "✅ 배포 완료!"
echo "✅ 배포 완료!"
11 changes: 8 additions & 3 deletions .github/workflows/deploy-to-prod-ec2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,13 @@ jobs:
WEEKLY_PROMPT="$(printf '%s' "$WEEKLY_PROMPT_B64" | base64 -d | tr -d '\r')"
export WEEKLY_PROMPT

MONTHLY_PROMPT_B64="${{ secrets.MONTHLY_PROMPT_B64 }}"
MONTHLY_PROMPT="$(printf '%s' "$MONTHLY_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_PROMPT
MONTHLY_V1_PROMPT_B64="${{ secrets.MONTHLY_V1_PROMPT_B64 }}"
MONTHLY_V1_PROMPT="$(printf '%s' "$MONTHLY_V1_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_V1_PROMPT

MONTHLY_V2_BASELINE_PROMPT_B64="${{ secrets.MONTHLY_V2_BASELINE_PROMPT_B64 }}"
MONTHLY_V2_BASELINE_PROMPT="$(printf '%s' "$MONTHLY_V2_BASELINE_PROMPT_B64" | base64 -d | tr -d '\r')"
export MONTHLY_V2_BASELINE_PROMPT

EVIDENCE_CARD_PROMPT_B64="${{ secrets.EVIDENCE_CARD_PROMPT_B64 }}"
EVIDENCE_CARD_PROMPT="$(printf '%s' "$EVIDENCE_CARD_PROMPT_B64" | base64 -d | tr -d '\r')"
Expand Down Expand Up @@ -140,6 +144,7 @@ jobs:
ANTHROPIC_API_KEY="${{ secrets.ANTHROPIC_API_KEY }}" \
GOOGLE_GENAI_API_KEY="${{ secrets.GOOGLE_GENAI_API_KEY }}" \
FIREBASE_ADMIN_KEY="${{ secrets.FIREBASE_ADMIN_KEY }}" \
ADMIN_PAGE_PASSWORD="${{ secrets.ADMIN_PAGE_PASSWORD }}" \
nohup java -jar "$JAR_PATH" \
--spring.profiles.active=prod > app-prod.log 2>&1 &

Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ out/
node_modules

# 오늘의 리포트 프롬프트
/src/main/resources/secret/
/src/main/resources/secret/

AGENTS.md
8 changes: 8 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
"revert"
]],
"scope-enum": [2, "always", [
"admin",
"auth",
"user",
"ai",
Expand All @@ -22,6 +23,7 @@ module.exports = {
"search",
"stats",
"friend",
"social",
"notify",
"infra",
"db",
Expand Down Expand Up @@ -99,6 +101,9 @@ module.exports = {
scope: {
description: '[Scope] 이번 변경이 적용된 범위를 선택해주세요 (범위 생략하려면 empty 선택)',
enum: {
admin: {
description: '🧑‍💻 어드민 도메인 (예: 어드민 페이지)'
},
auth: {
description: '🔐 인증/인가 도메인 (예: OAuth2, JWT, 세션)'
},
Expand All @@ -123,6 +128,9 @@ module.exports = {
friend: {
description: '👥 친구 도메인 (예: 친구 신청, 수락, 목록 관리)'
},
social: {
description: '💬 소셜 도메인 (예: 댓글, 좋아요, 피드 공유)'
},
notify: {
description: '📧 알림/이메일 전송 (예: 질문 알림, 인증 이메일 전송)'
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.devkor.ifive.nadab.domain.admin.api;

import com.devkor.ifive.nadab.domain.admin.api.dto.request.AdminLoginRequest;
import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminAuthStatusResponse;
import com.devkor.ifive.nadab.domain.admin.application.AdminPageAuthCommandService;
import com.devkor.ifive.nadab.domain.admin.infra.security.AdminPageAuthCookieService;
import com.devkor.ifive.nadab.domain.admin.infra.security.AdminPageAuthTokenService;
import com.devkor.ifive.nadab.global.core.response.ApiResponseDto;
import com.devkor.ifive.nadab.global.core.response.ApiResponseEntity;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Hidden
@RestController
@RequestMapping("/admin/api")
@RequiredArgsConstructor
public class AdminAuthController {

private final AdminPageAuthCommandService adminPageAuthCommandService;
private final AdminPageAuthTokenService adminPageAuthTokenService;
private final AdminPageAuthCookieService adminPageAuthCookieService;

@PostMapping("/login")
@PermitAll
public ResponseEntity<ApiResponseDto<Void>> login(
@RequestBody @Valid AdminLoginRequest request,
HttpServletResponse response
) {
adminPageAuthCommandService.validatePassword(request.password());
String token = adminPageAuthTokenService.issueToken();
adminPageAuthCookieService.addCookie(response, token);
return ApiResponseEntity.noContent();
}

@PostMapping("/logout")
public ResponseEntity<ApiResponseDto<Void>> logout(HttpServletResponse response) {
adminPageAuthCookieService.expireCookie(response);
return ApiResponseEntity.noContent();
}

@GetMapping("/auth-status")
public ResponseEntity<ApiResponseDto<AdminAuthStatusResponse>> authStatus() {
return ApiResponseEntity.ok(new AdminAuthStatusResponse(true));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.devkor.ifive.nadab.domain.admin.api;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AdminPageController {

@GetMapping("/admin/login")
public String loginPage() {
return "admin/login";
}

@GetMapping("/admin")
public String adminRoot() {
return "redirect:/admin/tabs/app-versions";
}

@GetMapping("/admin/tabs/app-versions")
public String adminVersionPage() {
return "admin/version";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.devkor.ifive.nadab.domain.admin.api;

import com.devkor.ifive.nadab.domain.admin.api.dto.request.AdminVersionCreateRequest;
import com.devkor.ifive.nadab.domain.admin.api.dto.request.AdminVersionItemUpsertRequest;
import com.devkor.ifive.nadab.domain.admin.api.dto.request.AdminVersionSummaryUpdateRequest;
import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminLatestVersionsResponse;
import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminVersionCreateResponse;
import com.devkor.ifive.nadab.domain.admin.api.dto.response.AdminVersionItemCreateResponse;
import com.devkor.ifive.nadab.domain.admin.application.AdminVersionCommandService;
import com.devkor.ifive.nadab.domain.admin.application.AdminVersionItemCommandService;
import com.devkor.ifive.nadab.domain.admin.application.AdminVersionQueryService;
import com.devkor.ifive.nadab.global.core.response.ApiResponseDto;
import com.devkor.ifive.nadab.global.core.response.ApiResponseEntity;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.validation.Valid;
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.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Hidden
@RestController
@RequestMapping("/admin/api/versions")
@RequiredArgsConstructor
public class AdminVersionController {

private final AdminVersionQueryService adminVersionQueryService;
private final AdminVersionCommandService adminVersionCommandService;
private final AdminVersionItemCommandService adminVersionItemCommandService;

@GetMapping("/latest")
public ResponseEntity<ApiResponseDto<AdminLatestVersionsResponse>> getLatestVersions() {
return ApiResponseEntity.ok(adminVersionQueryService.getLatestVersions());
}

@PostMapping
public ResponseEntity<ApiResponseDto<AdminVersionCreateResponse>> createVersion(
@RequestBody @Valid AdminVersionCreateRequest request
) {
Long appVersionId = adminVersionCommandService.createVersion(request);
return ApiResponseEntity.created(new AdminVersionCreateResponse(appVersionId));
}

@PutMapping("/{appVersionId}/summary")
public ResponseEntity<ApiResponseDto<Void>> updateVersionSummary(
@PathVariable Long appVersionId,
@RequestBody @Valid AdminVersionSummaryUpdateRequest request
) {
adminVersionCommandService.updateSummary(appVersionId, request.summary());
return ApiResponseEntity.noContent();
}

@PostMapping("/{appVersionId}/items")
public ResponseEntity<ApiResponseDto<AdminVersionItemCreateResponse>> createVersionItem(
@PathVariable Long appVersionId,
@RequestBody @Valid AdminVersionItemUpsertRequest request
) {
Long appVersionItemId = adminVersionItemCommandService.createItem(appVersionId, request);
return ApiResponseEntity.created(new AdminVersionItemCreateResponse(appVersionItemId));
}

@PutMapping("/items/{appVersionItemId}")
public ResponseEntity<ApiResponseDto<Void>> updateVersionItem(
@PathVariable Long appVersionItemId,
@RequestBody @Valid AdminVersionItemUpsertRequest request
) {
adminVersionItemCommandService.updateItem(appVersionItemId, request);
return ApiResponseEntity.noContent();
}

@DeleteMapping("/items/{appVersionItemId}")
public ResponseEntity<ApiResponseDto<Void>> deleteVersionItem(
@PathVariable Long appVersionItemId
) {
adminVersionItemCommandService.deleteItem(appVersionItemId);
return ApiResponseEntity.noContent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.request;

import jakarta.validation.constraints.NotBlank;

public record AdminLoginRequest(
@NotBlank(message = "비밀번호는 필수입니다.")
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.request;

import com.devkor.ifive.nadab.domain.appversion.core.entity.AppPlatform;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record AdminVersionCreateRequest(
@NotNull(message = "플랫폼은 필수입니다.")
AppPlatform platform,

@NotBlank(message = "버전은 필수입니다.")
@Size(max = 30, message = "버전은 30자 이하여야 합니다.")
String version,

@NotNull(message = "요약은 null일 수 없습니다.")
@Size(max = 120, message = "요약은 120자 이하여야 합니다.")
String summary
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record AdminVersionItemUpsertRequest(
@NotBlank(message = "업데이트명은 필수입니다.")
@Size(max = 100, message = "업데이트명은 100자 이하여야 합니다.")
String title,

@NotBlank(message = "상세 내용은 필수입니다.")
@Size(max = 500, message = "상세 내용은 500자 이하여야 합니다.")
String description,

@NotNull(message = "displayOrder는 필수입니다.")
@Min(value = 1, message = "displayOrder는 1 이상이어야 합니다.")
@Max(value = 9999, message = "displayOrder는 9999 이하여야 합니다.")
Integer displayOrder
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record AdminVersionSummaryUpdateRequest(
@NotNull(message = "요약은 null일 수 없습니다.")
@Size(max = 120, message = "요약은 120자 이하여야 합니다.")
String summary
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

public record AdminAuthStatusResponse(
boolean authenticated
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

import java.util.List;

public record AdminLatestVersionsResponse(
List<AdminVersionResponse> versions
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

public record AdminVersionCreateResponse(
Long appVersionId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

public record AdminVersionItemCreateResponse(
Long appVersionItemId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

public record AdminVersionItemResponse(
Long id,
String title,
String description,
Integer displayOrder
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.devkor.ifive.nadab.domain.admin.api.dto.response;

import com.devkor.ifive.nadab.domain.appversion.core.entity.AppPlatform;

import java.util.List;

public record AdminVersionResponse(
Long id,
AppPlatform platform,
String version,
String summary,
List<AdminVersionItemResponse> items
) {
}
Loading
Loading