Skip to content
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'me.paulschwarz:spring-dotenv:4.0.0'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // 3.3.x 버전으로 변경 없음
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@SpringBootApplication
@EnableMongoAuditing
public class DevlinkBackendApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.mtvs.devlinkbackend.channel.controller;

import com.mtvs.devlinkbackend.channel.dto.ChannelRegistRequestDTO;
import com.mtvs.devlinkbackend.channel.dto.ChannelUpdateRequestDTO;
import com.mtvs.devlinkbackend.channel.entity.Channel;
import com.mtvs.devlinkbackend.channel.service.ChannelService;
import com.mtvs.devlinkbackend.config.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/channel")
public class ChannelController {
private final ChannelService channelService;
private final JwtUtil jwtUtil;

public ChannelController(ChannelService channelService, JwtUtil jwtUtil) {
this.channelService = channelService;
this.jwtUtil = jwtUtil;
}

// 새 채널 생성
@PostMapping
public ResponseEntity<Channel> createChannel(
@RequestBody ChannelRegistRequestDTO channelRegistRequestDTO,
@RequestHeader(name = "Authorization") String authorizationHeader) throws Exception {

String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader);
Channel savedChannel = channelService.saveChannel(channelRegistRequestDTO, accountId);
return ResponseEntity.ok(savedChannel);
}

// 모든 채널 조회
@GetMapping
public ResponseEntity<List<Channel>> getAllChannels() {
List<Channel> channels = channelService.findAllChannels();
return ResponseEntity.ok(channels);
}

// 특정 채널 조회
@GetMapping("/{channelId}")
public ResponseEntity<Channel> getChannelById(@PathVariable String channelId) {
return channelService.findChannelByChannelId(channelId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

// 채널 업데이트
@PatchMapping
public ResponseEntity<Channel> updateChannel(
@RequestBody ChannelUpdateRequestDTO channelUpdateRequestDTO,
@RequestHeader(name = "Authorization") String authorizationHeader) throws Exception {

String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader);
try {
Channel updatedChannel = channelService.updateChannel(channelUpdateRequestDTO, accountId);
return ResponseEntity.ok(updatedChannel);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(null);
}
}

// 채널 삭제
@DeleteMapping("/{channelId}")
public ResponseEntity<Void> deleteChannel(@PathVariable String channelId) {
channelService.deleteChannel(channelId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mtvs.devlinkbackend.channel.dto;

import com.mtvs.devlinkbackend.channel.entity.PositionType;
import lombok.*;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Data
public class ChannelRegistRequestDTO {
private List<PositionType> positionTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.mtvs.devlinkbackend.channel.dto;

import com.mtvs.devlinkbackend.channel.entity.PositionType;
import lombok.*;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Data
public class ChannelUpdateRequestDTO {
private String channelId;
private List<PositionType> positionTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.mtvs.devlinkbackend.channel.entity;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.annotation.Collation;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

@Data
@NoArgsConstructor
@ToString
@Document(collection = "channel")
public class Channel {
@Id
private String channelId;

@Field(name = "owner_id")
private String ownerId;

private List<PositionType> positionTypes; // 여러 개의 position과 type 저장

@CreatedDate
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime modifiedAt;

public Channel(String ownerId, List<PositionType> positionTypes) {
this.channelId = UUID.randomUUID().toString();
this.ownerId = ownerId;
this.positionTypes = positionTypes;
}

public void setPositionTypes(List<PositionType> positionTypes) {
this.positionTypes = positionTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.mtvs.devlinkbackend.channel.entity;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.mongodb.core.mapping.Field;

@Data
@NoArgsConstructor
public class PositionType {
private Position position;

@Field("type")
private String type;

public PositionType(Position position, String type) {
this.position = position;
this.type = type;
}

@Data
@NoArgsConstructor
public static class Position {
private int x;
private int y;
private int z;

public Position(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mtvs.devlinkbackend.channel.repository;

import com.mtvs.devlinkbackend.channel.entity.Channel;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ChannelRepository extends MongoRepository<Channel, String> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.mtvs.devlinkbackend.channel.service;

import com.mtvs.devlinkbackend.channel.dto.ChannelRegistRequestDTO;
import com.mtvs.devlinkbackend.channel.dto.ChannelUpdateRequestDTO;
import com.mtvs.devlinkbackend.channel.entity.Channel;
import com.mtvs.devlinkbackend.channel.repository.ChannelRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
public class ChannelService {
private final ChannelRepository channelRepository;

public ChannelService(ChannelRepository channelRepository) {
this.channelRepository = channelRepository;
}

// 새 채널 저장
@Transactional
public Channel saveChannel(ChannelRegistRequestDTO channelRegistRequestDTO, String accountId) {
return channelRepository.save(new Channel(
accountId,
channelRegistRequestDTO.getPositionTypes()
));
}

// 모든 채널 조회

public List<Channel> findAllChannels() {
return channelRepository.findAll();
}

// ID로 특정 채널 조회
public Optional<Channel> findChannelByChannelId(String channelId) {
return channelRepository.findById(channelId);
}

// ID로 채널 업데이트
@Transactional
public Channel updateChannel(ChannelUpdateRequestDTO channelUpdateRequestDTO, String accountId) {
Optional<Channel> channel = channelRepository.findById(channelUpdateRequestDTO.getChannelId());
if (channel.isPresent()) {
Channel foundChannel = channel.get();
if(foundChannel.getOwnerId().equals(accountId)) {
foundChannel.setPositionTypes(channelUpdateRequestDTO.getPositionTypes());
return foundChannel;
} else throw new IllegalArgumentException("주인이 아닌 다른 사용자 계정으로 채널 수정 시도중");
} else throw new IllegalArgumentException("요청한 channelId로 해당 채널이 존재하지 않음");
}

// ID로 채널 삭제
public void deleteChannel(String channelId) {
channelRepository.deleteById(channelId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ResponseEntity<Comment> registComment(
@RequestBody CommentRegistRequestDTO commentRegistRequestDTO,
@RequestHeader(name = "Authorization") String authorizationHeader) throws Exception {

String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader);
String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader);
Comment comment = commentService.registComment(commentRegistRequestDTO, accountId);
return ResponseEntity.status(HttpStatus.CREATED).body(comment);
}
Expand Down Expand Up @@ -65,7 +65,7 @@ public ResponseEntity<List<Comment>> findCommentsByRequestId(@PathVariable Long
public ResponseEntity<List<Comment>> findCommentsByAccountId(
@RequestHeader(name = "Authorization") String authorizationHeader) throws Exception {

String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader);
String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader);
List<Comment> comments = commentService.findCommentsByAccountId(accountId);
return ResponseEntity.ok(comments);
}
Expand All @@ -81,7 +81,7 @@ public ResponseEntity<Comment> updateComment(
@RequestBody CommentUpdateRequestDTO commentUpdateRequestDTO,
@RequestHeader(name = "Authorization") String authorizationHeader) throws Exception {

String accountId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader);
String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader);
Comment updatedComment = commentService.updateComment(commentUpdateRequestDTO, accountId);
return ResponseEntity.ok(updatedComment);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.mtvs.devlinkbackend.config;

import com.mtvs.devlinkbackend.oauth2.service.EpicGamesTokenService;
import com.mtvs.devlinkbackend.oauth2.service.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
Expand All @@ -17,11 +17,11 @@
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final EpicGamesTokenService epicGamesTokenService;
private final UserService userService;

public JwtAuthenticationFilter(JwtUtil jwtUtil, EpicGamesTokenService epicGamesTokenService) {
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserService userService) {
this.jwtUtil = jwtUtil;
this.epicGamesTokenService = epicGamesTokenService;
this.userService = userService;
}

@Override
Expand All @@ -38,30 +38,24 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 부분 추출
} else {
// 헤더에 액세스 토큰이 없는 경우, 쿠키에서 리프레시 토큰 추출
token = getRefreshTokenFromCookies(request);
if (token != null) {
// 리프레시 토큰으로 새 액세스 토큰 발급
token = epicGamesTokenService.getAccessTokenByRefreshToken(token);

// 새로 발급된 액세스 토큰을 Authorization 헤더에 추가
response.setHeader("Authorization", "Bearer " + token);
} else {
System.out.println("refrehToken으로 accessToken 발급하려다가 refreshToken 없어서 실패");
return;
}
response.setStatus(450); // 잘못된 인증 헤더
return;
}

try {
// 토큰 검증 | 검증 성공 시 SecurityContext에 인증 정보 저장
String userPrincipal = jwtUtil.getSubjectFromTokenWithAuth(token);
String accountId = jwtUtil.getSubjectFromAuthHeaderWithAuth(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userPrincipal, null, null);
new UsernamePasswordAuthenticationToken(accountId, null, null);

SecurityContextHolder.getContext().setAuthentication(authentication);

} catch (Exception e) {
// 검증 실패 시 401 에러 설정
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
if(e.getMessage().equals("JWT is expired"))
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
else
response.setStatus(449); // 헤더에 들어 있는 토큰이 잘못됨
return;
}

Expand Down
Loading