Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e872ca0
feat: 신고 객체에 스냅샷용 컬럼 추가
tishakong Mar 13, 2026
5c262e1
refactor: customReason => detailReason으로 컬럼명 변경
tishakong Mar 13, 2026
cdf6f8c
refactor: post 레거시 객체 삭제
tishakong Mar 13, 2026
4d3b136
refacor: Report 객체 내 from 함수 삭제 및 builder 구현
tishakong Mar 13, 2026
91df90c
feat: Post, Comment 객체에 receiveReport 메서드 추가
tishakong Mar 13, 2026
42c3eaf
refactor: Post, Comment 객체 통합에 따른 TargetType 수정
tishakong Mar 13, 2026
5cd4ff0
comment: 리팩토링 TODO 주석 추가
tishakong Mar 13, 2026
eb7f711
feat: 게시글 신고, 댓글 신고 기능 구현
tishakong Mar 13, 2026
0c3f965
refactor: 기획과 동일하게 ReportReason 설명 수정
tishakong Mar 13, 2026
48ef46a
fix: 관리자 신고 기능 주석처리
tishakong Mar 13, 2026
01051a1
fix: 관리자 신고 기능 주석처리
tishakong Mar 13, 2026
be4bca2
Merge branch 'main' into refactor/#195-post-comment-report
tishakong Mar 13, 2026
bdfe734
Merge pull request #219 from TackitOnboarding/refactor/#195-post-comm…
yeongsinkeem Mar 19, 2026
15f2bfd
HOTFIX: s3 의존성 변경
tishakong Mar 13, 2026
b186569
feat: #214-마이페이지 현재 프로필 조회
yeongsinkeem Mar 12, 2026
75cd46d
feat: #214-마이페이지 api
yeongsinkeem Mar 13, 2026
377e49f
feat: #214-마이페이지 ) 프로필 편집, 프로필 전환 api 구현
yeongsinkeem Mar 19, 2026
ad7f479
refactor: #214-프로필 전환 기능에 따라 로그인 로직 리팩토링
yeongsinkeem Mar 19, 2026
b4d2584
feat: #214-마이페이지 api ) 댓글 조회 쿼리 추가
yeongsinkeem Mar 19, 2026
14658f1
feat: #214-소유자 검증 메서드 및 에러코드 추가(마이페이지 검증용)
yeongsinkeem Mar 22, 2026
8d6c459
refactor: #214-마이페이지 api AI 코드 리뷰 반영(소유자 검증 및 리팩토링)
yeongsinkeem Mar 22, 2026
5c4335a
Merge pull request #220 from TackitOnboarding/feat/#214-mypage
tishakong Mar 31, 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
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
package org.example.tackit.domain.admin.controller;

import lombok.RequiredArgsConstructor;
import org.example.tackit.domain.admin.dto.ReportedPostDTO;
import org.example.tackit.domain.admin.service.ReportedPostService;
import org.example.tackit.domain.entity.Post;
import org.example.tackit.common.dto.PageResponseDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/admin/report")
@RequiredArgsConstructor
public class AdminPostController {
private final Map<Post, ReportedPostService> reportedPostServices;

// 신고된 게시글 조회
@GetMapping("/{postType}/posts")
public ResponseEntity<PageResponseDTO<ReportedPostDTO>> getReportedPosts(
@PathVariable("postType") Post postType,
@PageableDefault(size = 5, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
ReportedPostService service = reportedPostServices.get(postType);

if (service == null) {
throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
}

Page<ReportedPostDTO> posts = service.getDeletedPosts(pageable);
return ResponseEntity.ok(PageResponseDTO.from(posts));
}

// 게시글 완전 삭제
@DeleteMapping("/{postType}/posts/{postId}")
public ResponseEntity<Void> deleteReportedPost(
@PathVariable("postType") Post postType,
@PathVariable("postId") Long postId) {

ReportedPostService service = reportedPostServices.get(postType);
if (service == null) {
throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
}

service.deletePost(postId);
return ResponseEntity.noContent().build();
}

// 게시글 활성화
@PatchMapping("/{postType}/posts/{postId}/activate")
public ResponseEntity<String> activateReportedPost(
@PathVariable("postType") Post postType,
@PathVariable("postId") Long postId) {

ReportedPostService service = reportedPostServices.get(postType);
if (service == null) {
throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
}

service.activatePost(postId);
return ResponseEntity.ok("게시글이 활성화되었습니다.");
}

}
//package org.example.tackit.domain.admin.controller;
//
//import lombok.RequiredArgsConstructor;
//import org.example.tackit.domain.admin.dto.ReportedPostDTO;
//import org.example.tackit.domain.admin.service.ReportedPostService;
//import org.example.tackit.domain.entity.postType;
//import org.example.tackit.common.dto.PageResponseDTO;
//import org.springframework.data.domain.Page;
//import org.springframework.data.domain.Pageable;
//import org.springframework.data.domain.Sort;
//import org.springframework.data.web.PageableDefault;
//import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.*;
//
//import java.util.Map;
//
//@RestController
//@RequestMapping("/api/admin/report")
//@RequiredArgsConstructor
//public class AdminPostController {
// private final Map<Post, ReportedPostService> reportedPostServices;
//
// // 신고된 게시글 조회
// @GetMapping("/{postType}/posts")
// public ResponseEntity<PageResponseDTO<ReportedPostDTO>> getReportedPosts(
// @PathVariable("postType") Post postType,
// @PageableDefault(size = 5, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
// ReportedPostService service = reportedPostServices.get(postType);
//
// if (service == null) {
// throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
// }
//
// Page<ReportedPostDTO> posts = service.getDeletedPosts(pageable);
// return ResponseEntity.ok(PageResponseDTO.from(posts));
// }
//
// // 게시글 완전 삭제
// @DeleteMapping("/{postType}/posts/{postId}")
// public ResponseEntity<Void> deleteReportedPost(
// @PathVariable("postType") Post postType,
// @PathVariable("postId") Long postId) {
//
// ReportedPostService service = reportedPostServices.get(postType);
// if (service == null) {
// throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
// }
//
// service.deletePost(postId);
// return ResponseEntity.noContent().build();
// }
//
// // 게시글 활성화
// @PatchMapping("/{postType}/posts/{postId}/activate")
// public ResponseEntity<String> activateReportedPost(
// @PathVariable("postType") Post postType,
// @PathVariable("postId") Long postId) {
//
// ReportedPostService service = reportedPostServices.get(postType);
// if (service == null) {
// throw new IllegalArgumentException("지원하지 않는 게시글 유형입니다: " + postType);
// }
//
// service.activatePost(postId);
// return ResponseEntity.ok("게시글이 활성화되었습니다.");
// }
//
//}
Comment on lines +1 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

파일 전체가 주석 처리되어 있습니다. 더 이상 사용하지 않는 코드라면 주석으로 남겨두기보다 파일을 삭제하여 코드 베이스를 깔끔하게 유지하는 것을 권장합니다. ExecutiveReportController.java 파일도 동일한 확인이 필요해 보입니다.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -82,51 +83,58 @@ public TokenDto signIn(SignInDto signInDto) {

@Transactional
public SignInResponse signIn(SignInDto signInDto) {
// 인증 토큰 생성

UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(signInDto.getEmail(), signInDto.getPassword());

try {
log.info("로그인 시도: {}", signInDto.getEmail());
Authentication authentication = authenticationManager.authenticate(authenticationToken);
log.info("로그인 성공: {}", authentication.getName());

TokenDto tokenDto = tokenProvider.generateTokenDto(authentication);
redisUtil.save(signInDto.getEmail(), tokenDto.getRefreshToken());

// 멀티 프로필 목록 조회
List<MemberOrg> memberOrgs = memberOrgRepository.findAllByMemberEmail(signInDto.getEmail());

List<MultiProfileDto> profiles = memberOrgs.stream()
.map(org -> {
MultiProfileDto.MultiProfileDtoBuilder builder = MultiProfileDto.builder()
.memberOrgId(org.getId())
.orgName(org.getOrganization().getName())
.nickname(org.getNickname())
.profileImage(org.getProfileImageUrl())
.orgType(org.getOrgType().name())
.memberRole(org.getMemberRole().name())
.memberType(org.getMemberType().name());

if (OrgType.CLUB.name().equals(org.getOrgType().name())
&& org.getOrganization().getUniversity() != null) {
builder.universityName(org.getOrganization().getUniversity().getUniversityName());
}
else {
builder.universityName(null);
}

return builder.build();
}).collect(Collectors.toList());

return new SignInResponse(
tokenDto,
profiles
);
} catch (Exception e) {
log.error("로그인 실패: {}", signInDto.getEmail(), e);
throw e;
}
new UsernamePasswordAuthenticationToken(
signInDto.getEmail(),
signInDto.getPassword()
);

Authentication authentication = authenticationManager.authenticate(authenticationToken);

return buildSignInResponse(signInDto.getEmail(), authentication);
}

// 프로필 전환
@Transactional
public SignInResponse switchProfile() {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String email = authentication.getName();

return buildSignInResponse(email, authentication);
}

// 로그인 응답 객체를 만드는 메서드
public SignInResponse buildSignInResponse(String email, Authentication authentication) {

TokenDto tokenDto = tokenProvider.generateTokenDto(authentication);
redisUtil.save(email, tokenDto.getRefreshToken());

List<MemberOrg> memberOrgs = memberOrgRepository.findAllByMemberEmail(email);

List<MultiProfileDto> profiles = memberOrgs.stream()
.map(org -> {
MultiProfileDto.MultiProfileDtoBuilder builder = MultiProfileDto.builder()
.memberOrgId(org.getId())
.orgName(org.getOrganization().getName())
.nickname(org.getNickname())
.profileImage(org.getProfileImageUrl())
.orgType(org.getOrgType().name())
.memberRole(org.getMemberRole().name())
.memberType(org.getMemberType().name());

if (OrgType.CLUB.name().equals(org.getOrgType().name())
&& org.getOrganization().getUniversity() != null) {
builder.universityName(org.getOrganization().getUniversity().getUniversityName());
} else {
builder.universityName(null);
}

return builder.build();
}).toList();

return new SignInResponse(tokenDto, profiles);
}

// Bearer 제거 및 형식 검증
Expand Down Expand Up @@ -254,7 +262,7 @@ public void resetPassword(String authorizationHeader, String newPassword) {
.orElseThrow(() -> new UsernameNotFoundException(email + " not found"));

String encodedNewPassword = passwordEncoder.encode(newPassword);
member.changePassword(encodedNewPassword);
member.updatePassword(encodedNewPassword);

// 6. Redis에서 토큰 삭제
redisUtil.delete("reset:" + email);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/example/tackit/domain/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void updateNickname(String newNickname) {
*/

// 비밀번호 변경 책임은 Member 도메인 내부에 분리
public void changePassword(String encodedNewPassword) {
public void updatePassword(String encodedNewPassword) {
this.password = encodedNewPassword;
}

Expand Down
5 changes: 0 additions & 5 deletions src/main/java/org/example/tackit/domain/entity/Post.java

This file was deleted.

51 changes: 35 additions & 16 deletions src/main/java/org/example/tackit/domain/entity/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,17 @@
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.tackit.domain.entity.org.MemberOrg;
import org.example.tackit.domain.report.dto.ReportRequestDto;
import org.example.tackit.domain.entity.post.PostType;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "report")
public class Report {
Expand All @@ -46,6 +43,7 @@ public class Report {
@JoinColumn(name = "targetMember_id", nullable = false)
private MemberOrg targetMember;

// POST 혹은 COMMENT의 id
@Column(nullable = false)
private Long targetId;

Expand All @@ -54,30 +52,51 @@ public class Report {
@Column(nullable = false)
private TargetType targetType;

// 타겟 대상 postId comment면 comment가 달린 post의 id, post는 본인.
@Column(nullable = false)
private Long postId;

// 게시판
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 50)
private PostType postType;

// 신고 당시 게시글 제목
@Column(nullable = false, length = 100)
private String reportedPostTitle;

// 신고 당시 타겟 게시글/댓글 내용
@Column(columnDefinition = "LONGTEXT", nullable = false)
private String reportedContent;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ReportReason reportReason;

@Column(columnDefinition = "TEXT")
private String customReason;
private String detailReason;

@CreatedDate
private LocalDateTime reportedAt;

@Enumerated(EnumType.STRING)
private ActiveStatus activeStatus;

//TODO Entity 안에 DTO를 참조하는 메소드가 있는 것이 이상함. 분리해야할듯
public static Report from(ReportRequestDto dto, MemberOrg reporter, MemberOrg targetMember) {
return Report.builder()
.reporter(reporter)
.targetMember(targetMember)
.targetId(dto.getTargetId())
.targetType(dto.getTargetType())
.reportReason(dto.getReason())
.activeStatus(ActiveStatus.ACTIVE) // 초기값 설정
.build();
@Builder
public Report(MemberOrg reporter, MemberOrg targetMember, Long targetId, TargetType targetType,
Long postId, PostType postType, String reportedPostTitle, String reportedContent,
ReportReason reportReason, String detailReason, ActiveStatus activeStatus) {
this.reporter = reporter;
this.targetMember = targetMember;
this.targetId = targetId;
this.targetType = targetType;
this.postId = postId;
this.postType = postType;
this.reportedPostTitle = reportedPostTitle;
this.reportedContent = reportedContent;
this.reportReason = reportReason;
this.detailReason = detailReason;
this.activeStatus = (activeStatus != null) ? activeStatus : ActiveStatus.ACTIVE;
}

}

24 changes: 12 additions & 12 deletions src/main/java/org/example/tackit/domain/entity/ReportReason.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package org.example.tackit.domain.entity;

public enum ReportReason {
ADVERTISEMENT("광고 및 홍보성 게시물"),
DUPLICATE("중복 또는 도배성 게시물"),
FALSE_INFO("허위 정보 또는 사실 왜곡"),
IRRELEVANT("게시판 주제와 관련 없는 내용"),
ETC("기타");
ADVERTISEMENT("광고 및 홍보성 내용"),
DUPLICATE("중복 또는 도배성 내용"),
FALSE_INFO("허위 정보 또는 사실 왜곡"),
IRRELEVANT("게시판 주제와 관련 없는 내용"),
ETC("기타");

private final String description;
private final String description;

ReportReason(String description) {
this.description = description;
}
ReportReason(String description) {
this.description = description;
}

public String getDescription() {
return description;
}
public String getDescription() {
return description;
}
}

Loading