Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[201800288 채다영] Level8-9 미션 제출합니다 #47

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
89ea1b3
base code
shin-mallang Feb 20, 2024
b29f341
level1-complete
shin-mallang Feb 27, 2024
915b848
level1-add test
shin-mallang Feb 28, 2024
be78c19
level2-complete
shin-mallang Feb 28, 2024
0aa625b
level3-complete
shin-mallang Feb 28, 2024
cfcbcea
level4-complete
shin-mallang Feb 28, 2024
7223679
level4 refactor package
shin-mallang Feb 28, 2024
cad9c65
level4 add controller test
shin-mallang Feb 28, 2024
5761d76
level5-complete
shin-mallang Feb 28, 2024
0914d29
level6-complete
shin-mallang Feb 28, 2024
c9c2906
level7-complete
shin-mallang Feb 28, 2024
8110718
level7-필요없는 컨트롤러 제거
shin-mallang Feb 28, 2024
06436ce
level8-complete
shin-mallang Feb 28, 2024
cd14bf6
level9-코드만 작성, 테스트코드 작성 필요
shin-mallang Feb 28, 2024
f9777cc
level9-initial
Feb 29, 2024
78d45e6
feat : TodoCreateRequest에서 goalId, content, date를 추출하여 todoService의 s…
CHAEGODA May 2, 2024
41d16a5
feat : todoId와 memberId를 todoService의 check 메소드에 전달하여 Todo를 완료 상태로 변경…
CHAEGODA May 2, 2024
4d75fbe
feat : todoId와 memberId를 todoService의 uncheck 메소드에 전달하여 Todo를 미완료 상태로…
CHAEGODA May 2, 2024
87270a0
feat : TodoUpdateRequest에서 content, date를 추출하고, todoId와 memberId를 함께 …
CHAEGODA May 2, 2024
f374c1d
feat : todoId와 memberId를 todoService의 delete 메소드에 전달하여 Todo를 삭제하도록 구현
CHAEGODA May 2, 2024
b54ee12
feat : memberId와 YearMonth.of(year, month)를 todoService의 findAllByMe…
CHAEGODA May 2, 2024
349fc7d
feat : 오류해결
CHAEGODA May 2, 2024
69a820b
feat : 오류해결
CHAEGODA May 15, 2024
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
34 changes: 19 additions & 15 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'backend.likelion'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '21'
sourceCompatibility = '21'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

// JWT
implementation 'com.auth0:java-jwt:4.4.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
8 changes: 5 additions & 3 deletions src/main/java/backend/likelion/todos/TodosApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@ConfigurationPropertiesScan
@SpringBootApplication
public class TodosApplication {

public static void main(String[] args) {
SpringApplication.run(TodosApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(TodosApplication.class, args);
}

}
11 changes: 11 additions & 0 deletions src/main/java/backend/likelion/todos/auth/Auth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.likelion.todos.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package backend.likelion.todos.auth;

import backend.likelion.todos.auth.jwt.JwtService;
import backend.likelion.todos.common.UnAuthorizedException;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@RequiredArgsConstructor
@Component
public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

private final JwtService jwtService;

@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean b = parameter.hasParameterAnnotation(Auth.class);
boolean equals = parameter.getParameterType().equals(Long.class);
return b && equals;
}

@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
String accessToken = extractAccessToken(webRequest);
return jwtService.extractMemberId(accessToken);
}

private static String extractAccessToken(NativeWebRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
throw new UnAuthorizedException("로그인 후 접근할 수 있습니다.");
}
}
19 changes: 19 additions & 0 deletions src/main/java/backend/likelion/todos/auth/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package backend.likelion.todos.auth;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@RequiredArgsConstructor
@Configuration
public class AuthConfig implements WebMvcConfigurer {

private final AuthArgumentResolver authArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authArgumentResolver);
}
}
17 changes: 17 additions & 0 deletions src/main/java/backend/likelion/todos/auth/AuthTestController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package backend.likelion.todos.auth;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class AuthTestController {

@GetMapping("/auth")
public String test(
@Auth Long memberId
) {
return "아이디가 " + memberId + "인 회원 인증 성공!";
}
}
10 changes: 10 additions & 0 deletions src/main/java/backend/likelion/todos/auth/jwt/JwtProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package backend.likelion.todos.auth.jwt;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("jwt")
public record JwtProperty(
String secretKey,
Long accessTokenExpirationDay
) {
}
46 changes: 46 additions & 0 deletions src/main/java/backend/likelion/todos/auth/jwt/JwtService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package backend.likelion.todos.auth.jwt;

import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import backend.likelion.todos.common.UnAuthorizedException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import java.util.Date;
import org.springframework.stereotype.Service;

@Service
public class JwtService {

private final long accessTokenExpirationDayToMills;
private final Algorithm algorithm;

public JwtService(JwtProperty jwtProperty) {
this.accessTokenExpirationDayToMills =
MILLISECONDS.convert(jwtProperty.accessTokenExpirationDay(), DAYS);
this.algorithm = Algorithm.HMAC512(jwtProperty.secretKey());
}

public String createToken(Long memberId) {
return JWT.create()
.withExpiresAt(new Date(
accessTokenExpirationDayToMills + System.currentTimeMillis()
))
.withIssuedAt(new Date())
.withClaim("memberId", memberId)
.sign(algorithm);
}

public Long extractMemberId(String token) {
try {
return JWT.require(algorithm)
.build()
.verify(token)
.getClaim("memberId")
.asLong();
} catch (JWTVerificationException e) {
throw new UnAuthorizedException("유효하지 않은 토큰입니다.");
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/backend/likelion/todos/goal/Goal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package backend.likelion.todos.goal;

import backend.likelion.todos.common.ForbiddenException;
import backend.likelion.todos.member.Member;
import lombok.Getter;

@Getter
public class Goal {

private Long id;
private String name;
private String color;
private Member member;

public Goal(String name, String color, Member member) {
this.name = name;
this.color = color;
this.member = member;
}

public void setId(Long id) {
this.id = id;
}

public void validateMember(Member member) {
if (!this.member.equals(member)) {
throw new ForbiddenException("해당 목표에 대한 권한이 없습니다.");
}
}

public void update(String name, String color) {
this.name = name;
this.color = color;
}
}
59 changes: 59 additions & 0 deletions src/main/java/backend/likelion/todos/goal/GoalController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package backend.likelion.todos.goal;

import backend.likelion.todos.auth.Auth;
import java.net.URI;
import java.util.List;
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.PathVariable;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/goals")
@RestController
public class GoalController {

private final GoalService goalService;

@PostMapping
public ResponseEntity<Void> create(
@Auth Long memberId,
@RequestBody GoalCreateRequest request
) {
Long goalId = goalService.save(request.name(), request.color(), memberId);
return ResponseEntity.created(URI.create("/goals/" + goalId)).build();
}

@PutMapping("/{id}")
public ResponseEntity<Void> update(
@PathVariable("id") Long id,
@Auth Long memberId,
@RequestBody GoalUpdateRequest request
) {
goalService.update(id, request.name(), request.color(), memberId);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(
@PathVariable("id") Long id,
@Auth Long memberId
) {
goalService.delete(id, memberId);
return ResponseEntity.ok().build();
}

@GetMapping("/my")
public ResponseEntity<List<GoalResponse>> findMine(
@Auth Long memberId
) {
List<GoalResponse> result = goalService.findAllByMemberId(memberId);
return ResponseEntity.ok(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package backend.likelion.todos.goal;

public record GoalCreateRequest(
String name,
String color
) {
}
39 changes: 39 additions & 0 deletions src/main/java/backend/likelion/todos/goal/GoalRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package backend.likelion.todos.goal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Repository;

@Repository
public class GoalRepository {

private final Map<Long, Goal> goals = new HashMap<>();
private Long id = 1L;

public Goal save(Goal goal) {
goal.setId(id);
goals.put(id++, goal);
return goal;
}

public Optional<Goal> findById(Long id) {
return Optional.ofNullable(goals.get(id));
}

public void clear() {
goals.clear();
}

public void delete(Goal goal) {
goals.remove(goal.getId());
}

public List<Goal> findAllByMemberId(Long memberId) {
return goals.values()
.stream()
.filter(it -> it.getMember().getId().equals(memberId))
.toList();
}
}
17 changes: 17 additions & 0 deletions src/main/java/backend/likelion/todos/goal/GoalResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package backend.likelion.todos.goal;

import lombok.Getter;

@Getter
public class GoalResponse {

private final Long id;
private final String name;
private final String color;

public GoalResponse(Long id, String name, String color) {
this.id = id;
this.name = name;
this.color = color;
}
}
Loading