Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2914906
feat: Cookie 제거 및 개발용 로그인 개선 (#293)
kdkdhoho Sep 2, 2024
309c270
refactor: 기존 최신리스트API -> 최신리스트와 팔로잉리스트 API로 분리 (#292)
pparkjs Sep 7, 2024
1226e69
refactor: 내 피드 리스트 공개 상태 여부 정책 변경에 따른 API 수정 (#290)
pparkjs Sep 8, 2024
9336952
feat: 리스트 상세 조회 시 가장 최신 댓글 1개를 응답에 포함하도록 구현 (#300)
kdkdhoho Oct 12, 2024
8c987be
infra: gradle-cache-action 버전 업그레이드 (#310) (#311)
kdkdhoho Oct 12, 2024
29f7ee2
feat: 폴더 관련 CRUD API 구현 및 이에 따른 콜렉션 API 변경 (#297)
pparkjs Oct 13, 2024
43cd41a
feat: 멘션 CRUD 기능 구현 (#309)
kdkdhoho Oct 13, 2024
746c171
infra: CD 워크플로우의 gradle-cache-action 버전 업그레이드
kdkdhoho Oct 13, 2024
954b943
feat: 알람 ver3.0 구현 (#315)
kdkdhoho Oct 26, 2024
8968ec7
feat: 리액션 API 추가 및 추천리스트 API, 리스트 상세조회 API 재구현 (#304)
pparkjs Oct 27, 2024
91b3112
feat: 사용자 추천 API 정책 변경으로 비회원도 사용자 추천 나타나도록 API 수정 (#314)
pparkjs Oct 27, 2024
ae559d3
feat: 사용자용 요청 주제(토픽) 생성/조회 API 구현 (#318)
kdkdhoho Oct 27, 2024
ed62e0c
feat: 관리자용 요청 주제(토픽) 조회 및 수정 API 구현 (#307) (#320)
kdkdhoho Oct 27, 2024
65b17d0
hotfix: 토픽 생성시 노출 여부 true로 고정 (#dev)
kdkdhoho Oct 27, 2024
1329093
feat: 공지 CRUD API 구현 (#323)
kdkdhoho Oct 30, 2024
f127099
hotfix: 리스트 추천 uri 변경
pparkjs Oct 30, 2024
a6fb7ed
feat: 공지 이미지 업로드 API 구현 (#326)
kdkdhoho Nov 5, 2024
e44fdd5
feat: 리스트 생성 시에 카테고리 입력을 소문자로 해도 생성이 되도록 구현
kdkdhoho Nov 10, 2024
ebaa7aa
feat: 공지 알람 발송 및 조회 API 구현 (#324)
kdkdhoho Nov 11, 2024
de49ea4
리스트 배경색 네이밍 변경 및 추가 (#328)
pparkjs Nov 18, 2024
7288140
feat: 닉네임 유효성 검증 API 구현 (#329) (#330)
kdkdhoho Nov 18, 2024
c28b78a
feat: 게시물 생성 시 응답에 id를 포함하도록 변경
kdkdhoho Nov 19, 2024
f8e2289
fix: 잘못 기입된 응답값 수정 (#dev)
kdkdhoho Nov 27, 2024
2dba698
feat: 댓글/답글에서 멘션 당한 유저가 탈퇴한 경우 보여지는 값 수정 (#313) (#332)
kdkdhoho Dec 14, 2024
8607d84
feat: 노출 처리가 된 무작위의 토픽을 조회하는 API 구현 (#333) (#334)
kdkdhoho Dec 14, 2024
32f7ae9
feat: 관리자용 토픽 전체 조회 API의 응답값 수정
kdkdhoho Dec 15, 2024
ad408bd
fix: 사용자용 공지 전체 조회 시 NPE 발생 해결
kdkdhoho Dec 16, 2024
5b54f35
feat: 리액션 알람 기능 구현 (#302) (#335)
kdkdhoho Dec 31, 2024
1288458
feat: 로그인 페이지 요청 API 구현 (#322)
kdkdhoho Jan 10, 2025
547ad24
feat: 어드민 로그인 구현 (#322)
kdkdhoho Jan 19, 2025
5abdeca
test: 댓글 작성 시, 멘션 ID에 Null일 경우 테스트 코드 작성 (#338)
kdkdhoho Jan 20, 2025
434bada
feat: 탈퇴 처리를 30일 이전의 유저를 삭제하는 기능 구현 (#339)
kdkdhoho Jan 20, 2025
1224455
feat: 리스트 검색, 리스트 상세 조회 API 요청 및 응답값 수정 (#340)
kdkdhoho Jan 20, 2025
a6d4682
refactor: 카테고리 코드로 리스트 검색하는 부분 리팩터링 및 테스트 코드 수정
kdkdhoho Jan 20, 2025
ba123eb
feat: 토픽 추천 API 응답값 수정 (#341) (#347)
kdkdhoho Jan 28, 2025
dbd1691
feat: 어드민 비밀번호 수정 API 구현 (#346)
kdkdhoho Jan 28, 2025
acd9a7d
feat: 어드민 전용 API에 인증 로직 추가 (#350)
kdkdhoho Jan 28, 2025
60d4c47
feat: 사용자 검색 API 응답값 수정 (#348)
kdkdhoho Jan 28, 2025
5e90703
fix(Notice): 칼럼명 변경
kdkdhoho Jan 31, 2025
8860a31
fix: 초기 비밀번호 수정
kdkdhoho Feb 6, 2025
13e8461
fix: 프로필 이름 변경
kdkdhoho Mar 4, 2025
b699369
hotfix: 리스트 상세에 isFollowing 응답 추가
pparkjs Mar 27, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
distribution: 'corretto'

- name: Gradle 셋업, 빌드, 캐시
uses: burrunan/gradle-cache-action@3bf23b8dd95e7d2bacf2470132454fe893a178a1
uses: burrunan/gradle-cache-action@c15634bb25b7284dc084f38dff4e838048b7feaf
with:
arguments: build
properties: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dev-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
run: cd /home/runner/work/ListyWave-back/ListyWave-back/

- name: Gradle 셋업, 빌드, 캐싱
uses: burrunan/gradle-cache-action@3bf23b8dd95e7d2bacf2470132454fe893a178a1
uses: burrunan/gradle-cache-action@c15634bb25b7284dc084f38dff4e838048b7feaf
with:
arguments: bootJar

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prod-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
run: cd /home/runner/work/ListyWave-back/ListyWave-back/

- name: Gradle 셋업, 빌드, 캐싱
uses: burrunan/gradle-cache-action@3bf23b8dd95e7d2bacf2470132454fe893a178a1
uses: burrunan/gradle-cache-action@c15634bb25b7284dc084f38dff4e838048b7feaf
with:
arguments: bootJar

Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.4'
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/listywave/admin/Admin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.listywave.admin;

import static com.listywave.common.exception.ErrorCode.INVALID_ACCESS;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import com.listywave.common.exception.CustomException;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
public class Admin {

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

@Column(nullable = false, length = 40, unique = true)
private String ip;

@Column(nullable = false, length = 50, unique = true)
private String account;

@Column(nullable = false, length = 50)
private String password;

public void validatePassword(String password) {
if (this.password.equals(password)) {
return;
}
throw new CustomException(INVALID_ACCESS);
}

public void update(String password) {
this.password = password;
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/listywave/admin/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.listywave.admin;

import com.listywave.common.auth.Auth;
import com.listywave.common.exception.CustomException;
import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
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;

@Controller
@RequiredArgsConstructor
public class AdminController {

private final Environment environment;
private final AdminService adminService;

@GetMapping("/admin")
String redirectLoginPage(HttpServletRequest request) {
String clientIp = request.getHeader("X-Forwarded-For");

if (adminService.isValidIp(clientIp)) {
String[] activeProfiles = environment.getActiveProfiles();

for (String activeProfile : activeProfiles) {
switch (activeProfile) {
case "dev", "default" -> {
return "redirect:http://localhost:3000/admin/login";
}
case "prod" -> {
return "redirect:https://listywave.com/admin/login";
}
}
}
}
throw new CustomException(RESOURCE_NOT_FOUND);
}

@PostMapping("/admin/login")
ResponseEntity<AdminLoginResponse> login(@RequestBody AdminLoginRequest adminLoginRequest) {
AdminLoginResponse result = adminService.login(adminLoginRequest.account(), adminLoginRequest.password());
return ResponseEntity.ok(result);
}

@PutMapping("/admin")
ResponseEntity<Void> updateInfo(@Auth Long adminId, @RequestBody AdminUpdateRequest request) {
adminService.update(adminId, request.password());
return ResponseEntity.ok().build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/listywave/admin/AdminLoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.listywave.admin;

public record AdminLoginRequest(
String account,
String password
) {
}
7 changes: 7 additions & 0 deletions src/main/java/com/listywave/admin/AdminLoginResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.listywave.admin;

public record AdminLoginResponse(
String accessToken,
String refreshToken
) {
}
18 changes: 18 additions & 0 deletions src/main/java/com/listywave/admin/AdminRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.listywave.admin;

import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND;

import com.listywave.common.exception.CustomException;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AdminRepository extends JpaRepository<Admin, Long> {

default Admin getById(Long id) {
return findById(id).orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND));
}

boolean existsByIp(String ip);

Optional<Admin> findByAccount(String account);
}
51 changes: 51 additions & 0 deletions src/main/java/com/listywave/admin/AdminService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.listywave.admin;

import static com.listywave.common.exception.ErrorCode.INVALID_ACCESS;

import com.listywave.auth.application.domain.JwtManager;
import com.listywave.common.encrypt.Sha256Cipher;
import com.listywave.common.exception.CustomException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class AdminService {

private final JwtManager jwtManager;
private final Sha256Cipher sha256Cipher;
private final AdminRepository adminRepository;

@Transactional(readOnly = true)
public boolean isValidIp(String ip) {
return adminRepository.existsByIp(ip);
}

@Transactional(readOnly = true)
public AdminLoginResponse login(String account, String password) {
Optional<Admin> optionalAdmin = adminRepository.findByAccount(account);
if (optionalAdmin.isPresent()) {
Admin admin = optionalAdmin.get();

// 암호화 적용으로 인해, 임시로 작성해둔 코드입니다.
// 모든 어드민이 암호를 변경하면 if 조건식만 제거합니다.
if (!password.equals("1234")) {
admin.validatePassword(sha256Cipher.encrypt(password)); // 해당 라인은 제거하지 않습니다.
}

String accessToken = jwtManager.createAdminAccessToken(admin.getId());
String refreshToken = jwtManager.createAdminRefreshToken(admin.getId());
return new AdminLoginResponse(accessToken, refreshToken);
}
throw new CustomException(INVALID_ACCESS);
}

public void update(Long adminId, String password) {
Admin admin = adminRepository.getById(adminId);
String encryptedNewPassword = sha256Cipher.encrypt(password);
admin.update(encryptedNewPassword);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/listywave/admin/AdminUpdateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.listywave.admin;

public record AdminUpdateRequest(
String password
) {
}
31 changes: 23 additions & 8 deletions src/main/java/com/listywave/alarm/application/domain/Alarm.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
import static jakarta.persistence.TemporalType.TIMESTAMP;
import static lombok.AccessLevel.PROTECTED;

import com.listywave.list.application.domain.comment.Comment;
import com.listywave.list.application.domain.list.ListEntity;
import com.listywave.list.application.domain.reply.Reply;
import com.listywave.notice.application.domain.Notice;
import com.listywave.user.application.domain.User;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.Enumerated;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
Expand All @@ -37,17 +42,27 @@ public class Alarm {
private Long id;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "send_user_id")
private User user;
@JoinColumn(name = "send_user_id", nullable = false)
private User sendUser;

@Column(nullable = false)
@Column(nullable = true)
private Long receiveUserId;

@Column(nullable = true)
private Long listId;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "list_id", nullable = true, foreignKey = @ForeignKey(name = "alarm_list_fk"))
private ListEntity list;

@Column(nullable = true)
private Long commentId;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "comment_id", nullable = true, foreignKey = @ForeignKey(name = "alarm_comment_fk"))
private Comment comment;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "reply_id", nullable = true, foreignKey = @ForeignKey(name = "alarm_reply_fk"))
private Reply reply;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "notice_id", nullable = true, foreignKey = @ForeignKey(name = "alarm_notice_fk"))
private Notice notice;

@Column(nullable = false)
@Enumerated(value = STRING)
Expand All @@ -61,7 +76,7 @@ public class Alarm {
@Column(updatable = false)
private LocalDateTime createdDate;

public void readAlarm() {
public void check() {
this.isChecked = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.listywave.alarm.application.domain;

import static com.listywave.alarm.application.domain.AlarmType.COLLECT;
import static com.listywave.alarm.application.domain.AlarmType.COMMENT;
import static com.listywave.alarm.application.domain.AlarmType.FOLLOW;
import static com.listywave.alarm.application.domain.AlarmType.NOTICE;
import static com.listywave.alarm.application.domain.AlarmType.REACTION;
import static com.listywave.alarm.application.domain.AlarmType.REPLY;

import com.listywave.list.application.domain.comment.Comment;
import com.listywave.list.application.domain.list.ListEntity;
import com.listywave.list.application.domain.reply.Reply;
import com.listywave.mention.Mention;
import com.listywave.notice.application.domain.Notice;
import com.listywave.user.application.domain.User;
import java.util.List;
import lombok.Builder;

@Builder
public record AlarmCreateEvent(
User publisher,
Long listenerId,
ListEntity list,
Comment comment,
Reply reply,
List<Mention> mentions,
Notice notice,
AlarmType alarmType
) {

public Alarm toEntity() {
return Alarm.builder()
.sendUser(publisher)
.receiveUserId(listenerId)
.list(list)
.comment(comment)
.reply(reply)
.type(alarmType)
.notice(notice)
.isChecked(false)
.build();
}

public static AlarmCreateEvent comment(ListEntity list, Comment comment, List<Mention> mentions) {
return AlarmCreateEvent.builder()
.publisher(comment.getUser())
.listenerId(list.getUser().getId())
.list(list)
.comment(comment)
.mentions(mentions)
.alarmType(COMMENT)
.build();
}

public static AlarmCreateEvent reply(Comment comment, Reply reply, List<Mention> mentions) {
return AlarmCreateEvent.builder()
.publisher(reply.getUser())
.listenerId(comment.getUser().getId())
.list(comment.getList())
.comment(comment)
.reply(reply)
.mentions(mentions)
.alarmType(REPLY)
.build();
}

public static AlarmCreateEvent follow(User publisher, User listenerUser) {
return AlarmCreateEvent.builder()
.publisher(publisher)
.listenerId(listenerUser.getId())
.alarmType(FOLLOW)
.build();
}

public static AlarmCreateEvent collect(User publisher, ListEntity list) {
return AlarmCreateEvent.builder()
.publisher(publisher)
.listenerId(list.getUser().getId())
.list(list)
.alarmType(COLLECT)
.build();
}

public static AlarmCreateEvent notice(User user, Notice notice) {
return AlarmCreateEvent.builder()
.publisher(user)
.notice(notice)
.alarmType(NOTICE)
.build();
}

public static AlarmCreateEvent reaction(User publisher, ListEntity list) {
return AlarmCreateEvent.builder()
.publisher(publisher)
.listenerId(list.getUser().getId())
.list(list)
.alarmType(REACTION)
.build();
}

public boolean isToMyself() {
return this.publisher.isSame(listenerId);
}
}
Loading
Loading