Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
aa73ef4
test: MAP 동시성 비교 테스트
seonghoo1217 Dec 11, 2023
13775ce
feat: 기본 투두 리스트 데이터 모델 코드 작성
seonghoo1217 Dec 12, 2023
034adf3
refactor: 투두 엔티티 필요 어노테이션 설정
seonghoo1217 Dec 13, 2023
0b08d2a
feat: 투두 ConcurrencyHashMap 기반의 인메모리 레포지토리 CRUD 로직 작성
seonghoo1217 Dec 13, 2023
d38282c
refactor: 투두 필드명 목적에 맞게 변경
seonghoo1217 Dec 13, 2023
28efaba
refactor: 투두 생성자 생성
seonghoo1217 Dec 13, 2023
e88d57d
refactor: 투두 생성자 변경 및 Embedded한 값 생성자 생성
seonghoo1217 Dec 13, 2023
7ce0717
refactor: Repository Layer 어노테이션 추가
seonghoo1217 Dec 13, 2023
3067575
chore: assertj core dependency 추가
seonghoo1217 Dec 14, 2023
dc709cc
refactor: ToDo PK값 필드 제거
seonghoo1217 Dec 14, 2023
73b262e
test: ToDo 인메모리 DB 저장시 ID Auto Increment 구현
seonghoo1217 Dec 14, 2023
9c805f2
test: ToDo 인메모리 DB findAll 메서드 로직 테스트
seonghoo1217 Dec 14, 2023
beb119f
feat: ToDo 인메모리 DB deleteAll 메서드 추가
seonghoo1217 Dec 14, 2023
f6a7c0e
test: Unit 테스트 직전 데이터 초기화 추가
seonghoo1217 Dec 14, 2023
15da205
style: 불필요한 import 제거
seonghoo1217 Dec 14, 2023
47c5a0e
feat: 봉투패턴 적용 대상 전역 DTO 생성
seonghoo1217 Dec 15, 2023
ccc04ae
chore: build.gradle Validation Dependency 추가
seonghoo1217 Dec 15, 2023
d191ccd
feat: 예외 전역 처리 및 예외 Enum 클래스로 관리
seonghoo1217 Dec 15, 2023
7786745
refactor: Member 엔티티 자기생성자 추가
seonghoo1217 Dec 15, 2023
9b2ed4b
feat: TODO 생성 API 작성
seonghoo1217 Dec 15, 2023
64b6af9
feat: TODO 단건 ID 조회 API 작성
seonghoo1217 Dec 15, 2023
3edfed1
refactor: ToDo UUID 필드 값 추가 및 ID PK 보호
seonghoo1217 Dec 15, 2023
6a50ee2
feat: ToDo 삭제 API 구현
seonghoo1217 Dec 15, 2023
f9b4daa
refactor: In-Memory 기반 ToDo API URL 변경
seonghoo1217 Dec 15, 2023
6354497
refactor: Exception 네이밍 변경 및 에러 전역 처리 포함
seonghoo1217 Dec 15, 2023
b1d55d5
docs: 1주차 과제 분석 추가
seonghoo1217 Dec 16, 2023
4a9fb94
Merge pull request #1 from seonghoo1217/week1-seonghoo1217
seonghoo1217 Dec 17, 2023
3b65ecb
chore: Mysql 관련 Connection 설정
seonghoo1217 Dec 20, 2023
3a3b6cc
chore: Data JPA Dependency 추가
seonghoo1217 Dec 20, 2023
5d1e642
refactor: ToDo - Member mapping 및 필드값 Embeddable로 관리
seonghoo1217 Dec 21, 2023
8b9c5df
refactor: Enumerated를 이용한 카테고리 매핑
seonghoo1217 Dec 21, 2023
dad2020
refactor: ToDo ServiceLayer Transaction 추가
seonghoo1217 Dec 22, 2023
e33e042
chore: Spring Security Dependency 추가
seonghoo1217 Dec 22, 2023
a043160
feat: Password Validation Annotation으로 작성
seonghoo1217 Dec 23, 2023
0d18877
refactor: 인메모리 DB 로직 제거 및 Repository 상속 변경
seonghoo1217 Dec 24, 2023
a7a3983
feat: 회원 생성 API 구현
seonghoo1217 Dec 24, 2023
beebc49
refactor: 비밀번호 양식 검사 로직 변경
seonghoo1217 Dec 25, 2023
d42a3da
feat: Spring Security 인가처리 작성
seonghoo1217 Dec 25, 2023
93e5247
test: 패스워드 양식 검사 테스트 코드 작성
seonghoo1217 Dec 25, 2023
44e61fd
refactor: 회원 생성시 Create Reponse 반환
seonghoo1217 Dec 25, 2023
592d8bc
feat: 회원 중복가입 방지 및 예외 처리
seonghoo1217 Dec 26, 2023
ca9859f
refactor: 비밀번호 unique 컬럼 제거
seonghoo1217 Dec 26, 2023
07ae8f2
refactor: 시큐리티 설정 값 변경
seonghoo1217 Jan 2, 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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,6 @@
- @seonghoo1217
- @jjinwo0
- @jhnyuk

## Docs
- [1주차 과제 분석](https://github.com/seonghoo1217/Todo-List-Study/wiki/1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%A7%84%ED%96%89%EC%83%81%ED%99%A9)
33 changes: 19 additions & 14 deletions todolist/build.gradle
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'study'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
sourceCompatibility = '17'
}

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

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.24.2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly "com.mysql:mysql-connector-j:$mysqlVersion"
implementation 'org.springframework.boot:spring-boot-starter-security'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
1 change: 1 addition & 0 deletions todolist/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mysqlVersion=8.0.33
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package study.todolist.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.todolist.domain.Member;
import study.todolist.domain.repository.MemberRepository;

@Service
@RequiredArgsConstructor
@Transactional
public class MemberCommandService {

private final MemberRepository memberRepository;

public Long createMember(String email, String password) {
Member member = new Member(email, password);
memberRepository.save(member);

return member.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package study.todolist.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.todolist.domain.Category;
import study.todolist.domain.Member;
import study.todolist.domain.ToDo;
import study.todolist.domain.exception.ToDoNotFoundException;
import study.todolist.domain.repository.ToDoRepository;

import java.time.ZonedDateTime;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Transactional
public class ToDoCommandService {

private final ToDoRepository toDoRepository;
public static final Long DEFAULT_ID = 1L;

public UUID createToDo(String title, String contents, Category category, ZonedDateTime postTime) {
ToDo toDo = toDoRepository.save(DEFAULT_ID, new ToDo(UUID.randomUUID(), title, contents, category, postTime, 0);

return toDo.getUuid();
}

public void deleteToDoByUuid(UUID uuid) {
ToDo toDo = toDoRepository.findByUuid(uuid).orElseThrow(ToDoNotFoundException::new);

toDoRepository.deleteByUuid(toDo.getUuid());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package study.todolist.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.todolist.domain.ToDo;
import study.todolist.domain.exception.ToDoNotFoundException;
import study.todolist.domain.repository.ToDoRepository;
import study.todolist.presentation.dto.res.ToDoDetailRes;

import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ToDoQueryService {

private final ToDoRepository toDoRepository;


public ToDoDetailRes findToDoByUuid(UUID uuid) {
ToDo todo = toDoRepository.findByUuid(uuid).orElseThrow(ToDoNotFoundException::new);

return new ToDoDetailRes(uuid,
todo.getToDoEssential().getTitle(),
todo.getToDoEssential().getContents(),
todo.getToDoEssential().getCategory(),
todo.getToDoEssential().getPostTime(),
todo.getToDoEssential().getDayOfWeek(),
todo.getViewer());
}

public List<ToDoDetailRes> findAllToDo() {
List<ToDo> toDoList = toDoRepository.findAll();

return toDoList.stream().map(toDo ->
new ToDoDetailRes(
toDo.getUuid(),
toDo.getToDoEssential().getTitle(),
toDo.getToDoEssential().getContents(),
toDo.getToDoEssential().getCategory(),
toDo.getToDoEssential().getPostTime(),
toDo.getToDoEssential().getDayOfWeek(),
toDo.getViewer()
)).toList();
}
}
5 changes: 5 additions & 0 deletions todolist/src/main/java/study/todolist/domain/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package study.todolist.domain;

public enum Category {
SIMPLE, OTHER;
}
37 changes: 37 additions & 0 deletions todolist/src/main/java/study/todolist/domain/Member.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package study.todolist.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
public class Member {

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

private String email;

private String password;

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ToDo> toDos;

public Member(String email, String password) {
this.email = email;
this.password = password;
}

public Member(Long id) {
this.id = id;
}

}
38 changes: 38 additions & 0 deletions todolist/src/main/java/study/todolist/domain/ToDo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package study.todolist.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.ZonedDateTime;
import java.util.UUID;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class ToDo {

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

private UUID uuid;

private ToDoEssential toDoEssential;

private int viewer;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "member_id")
private Member member;

public ToDo(UUID uuid, String title, String contents, Category category, ZonedDateTime postTime, int viewer, Member member) {
this.uuid = uuid;
this.toDoEssential = new ToDoEssential(title, contents, category, postTime, postTime.getDayOfWeek());
this.viewer = viewer;
this.member = member;
}
}
48 changes: 48 additions & 0 deletions todolist/src/main/java/study/todolist/domain/ToDoEssential.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package study.todolist.domain;

import jakarta.persistence.Embeddable;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import study.todolist.domain.exception.ToDoNotPostAbleException;

import java.time.DayOfWeek;
import java.time.ZonedDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Embeddable
public class ToDoEssential {
private String title;

private String contents;

@Enumerated(EnumType.STRING)
private Category category;

private ZonedDateTime postTime;

private DayOfWeek dayOfWeek;

public ToDoEssential(String title, String contents, Category category, ZonedDateTime postTime, DayOfWeek dayOfWeek) {
if (isValid(postTime)) {
throw new ToDoNotPostAbleException();
}
this.title = title;
this.contents = contents;
this.category = category;
this.postTime = postTime;
this.dayOfWeek = dayOfWeek;
}


private boolean isValid(ZonedDateTime postTime) {
return isPostAble();
}

public boolean isPostAble() {
return postTime.isBefore(ZonedDateTime.now());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package study.todolist.domain.exception;

public class ToDoNotFoundException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package study.todolist.domain.exception;

public class ToDoNotPostAbleException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package study.todolist.domain.repository;

import org.springframework.data.repository.Repository;
import study.todolist.domain.Member;

public interface MemberRepository extends Repository<Member, Long> {

Member save(Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package study.todolist.domain.repository;

import org.springframework.data.repository.Repository;
import study.todolist.domain.ToDo;

public interface ToDoRepository extends Repository<ToDo, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package study.todolist.global.annotation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import study.todolist.global.annotation.validator.MinutePreciseUTCValidator;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = MinutePreciseUTCValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface MinutePreciseUTC {
String message() default "시간 형식이 UTC이어야 하며, 분 단위까지만 입력 가능합니다.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package study.todolist.global.annotation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import study.todolist.global.annotation.validator.PasswordPatternValidator;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = PasswordPatternValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 인터페이스에 왜 @Target 어노테이션을 붙인 이유가 뭔지 궁금합니다 !

@Retention(RUNTIME)
public @interface PasswordValidate {

String message() default "비밀번호는 영문 대소문자, 숫자, 특수문자를 포함하여 8자 이상 20자 이하여야 합니다.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Loading