Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7683d14
[FE] refactor: 코치 시간 생성 달력과 크루 시간 선택 달력을 분리한다. (#458)
sanaandmomo Oct 14, 2022
862548d
[BE] refactor: 중복된 멤버 이름으로 닉네임 수정 시 검증 로직 추가 (#452)
soominsohn Oct 14, 2022
b6a0a33
[BE] Custom exception 을 리팩터링한다. (#463)
her0807 Oct 15, 2022
bcbb841
[BE] refactor: @Builder 어노테이션 buildMethodName 속성 제거 (#467)
HyeonbinSa Oct 15, 2022
170ad0e
[BE] docs: 데모용 리드미 작성 (#482)
Juhyung990122 Oct 17, 2022
29fcfcb
chore: 모니터링 설정 추가 (#485)
her0807 Oct 17, 2022
ac2c360
[FE] QA를 진행하다 나온 버그를 수정한다. (#476)
lokba Oct 17, 2022
155e90e
[FE] refactor: 디렉토리 구조를 개편한다. (#489)
lokba Oct 18, 2022
28215b2
Release v1.2.2
dongho108 Oct 20, 2022
a66a370
feat: 메일기능 주석처리, 슬랙알람기능 주석처리, 크루로그인 코치로그인 생성
soominsohn Oct 13, 2022
a71b9d2
refactor: DatabaseInitializer profile deomo 추가
soominsohn Oct 13, 2022
799cc6e
refactor: DatabaseInitializer profile deomo 추가
soominsohn Oct 13, 2022
d5d4f75
refactor: DemoDatabaseInitializer profile mockData 추가
soominsohn Oct 13, 2022
703b005
refactor: available_dat_time mock data 추가
soominsohn Oct 13, 2022
4093dcd
refactor: crewId 증가 로직 변경
soominsohn Oct 14, 2022
04f7ad0
refactor: Atomic Long 으로 변경
dongho108 Oct 14, 2022
2f10563
fix: crewId 제대로 시작하도록 설정
dongho108 Oct 14, 2022
210c4b3
refactor: COMMENT상태 인터뷰 등록
soominsohn Oct 14, 2022
911f818
refactor: 인터뷰 상태 변경
soominsohn Oct 14, 2022
6d4beed
refactor: 인터뷰 mock data 추가
soominsohn Oct 14, 2022
5916178
refactor: 인터뷰 상태 FIXED로 변경
soominsohn Oct 14, 2022
ad378bd
refactor: 완료 코멘트 확인
soominsohn Oct 14, 2022
ec355bd
feat: 멤버데이터 추가
dongho108 Oct 15, 2022
599f001
feat: 임시유저 데이터 변경
dongho108 Oct 15, 2022
8542d3f
feat: 랜덤 유저 id 생성기 추가
dongho108 Oct 15, 2022
9287ced
refactor: conflict 막도록 수정
dongho108 Oct 17, 2022
269246a
fix: 인터뷰 저장시 면담 가능시간 상태 USED로 변경
dongho108 Oct 20, 2022
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
71 changes: 67 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,71 @@
# 2022-Ternoko
### 면담은 터놓고
# 터놓고, Ternoko
## 프로젝트 소개
면담은 찐하게 예약은 손쉽게 올인원 면담 예약 서비스 터놓고
<img width="300" alt="Screen Shot 2022-10-11 at 2 49 34 PM" src="https://user-images.githubusercontent.com/83059234/195292032-346867ea-256a-4db8-82c4-0efb5b569ef3.jpeg">
## 핵심 기능

### 🚀 크루 - 면담 예약을 손쉽게 할 수 있어요!

코치의 면담 예약 가능 시간 `조회`, 면담 사전 질문 `작성`, `면담 예약`, `알림`까지!
한번에 터놓고에서 관리할 수 있어요.
코치와의 찐한 면담 시간이 당신을 기다리고 있습니다.
![ezgif com-gif-maker (2)](https://user-images.githubusercontent.com/83059234/195489810-696cfa68-3966-4188-827c-445440296cd8.gif)


<br>

### 🚀 코치 - 면담 관리를 손쉽게 할 수 있어요!
면담 예약 시간 `열기`, 면담 신청 `조회`, 면담 내역 `관리`, `알림`까지
한번에 터놓고에서 관리할 수 있어요.
나의 크루와의 찐한 면담 시간이 당신을 기다리고 있습니다.
![ezgif com-gif-maker (1)](https://user-images.githubusercontent.com/83059234/195294288-631fcb7c-43c7-4eed-9e8b-c95cb8dee59f.gif)


## 프로젝트 기술 스택
### 프론트엔드
<img width="884" alt="스크린샷 2022-10-13 오전 12 10 42" src="https://user-images.githubusercontent.com/43205258/195761355-073d69f5-429d-400f-a30d-183426c6b5b0.png">

### 백엔드
<img width="879" alt="image" src="https://user-images.githubusercontent.com/43205258/195761514-4092d0b9-0471-4391-989a-aff3ce077977.png">

### 인프라
<img width="876" alt="스크린샷 2022-10-13 오전 12 11 16" src="https://user-images.githubusercontent.com/43205258/195761393-85bbe532-e1f9-4943-a229-5ccab2becdca.png">


## 프로젝트 아키텍쳐
### 프론트엔드
<img width="882" alt="스크린샷 2022-10-13 오전 10 56 27" src="https://user-images.githubusercontent.com/43205258/195761140-331c881c-115c-4039-9881-837b5de4acfc.png">

### 백엔드
<img width="884" alt="image" src="https://user-images.githubusercontent.com/43205258/195761556-e13424d6-478c-408c-8fc2-3bc370ea7945.png">


## 터놓고 서비스 이용하기
### 터놓고 방문하기

👉  https://ternoko.site

### 터놓고 Tech **Wiki 📑**

👉  [위키페이지로 이동](https://github.com/woowacourse-teams/2022-ternoko/wiki)


### Youtube 📺

| 1차 데모데이 | 2차 데모데이 | 3차 데모데이 | 4차 데모데이 | 5차 데모데이
| --- | --- | --- | --- | --- |
| [<img width="200px" src="https://i.ytimg.com/vi/mKV3osPRtdc/hq720.jpg" />](https://youtu.be/mKV3osPRtdc) | [<img width="200px" src="https://i.ytimg.com/vi/LQRxmFMnFfo/hq720.jpg" />](https://youtu.be/LQRxmFMnFfo) | [<img width="200px" src="https://i.ytimg.com/vi/y2cudTZ8seY/hq720.jpg" />](https://youtu.be/y2cudTZ8seY) | [<img width="200px" src="https://i.ytimg.com/vi/-Y4DfIsRrzA/hqdefault.jpg" />](https://youtu.be/-Y4DfIsRrzA) | [<img width="200px" src="https://i.ytimg.com/vi/mKV3osPRtdc/hq720.jpg" />](https://youtu.be/mKV3osPRtdc) |


### 팀원👨‍💻👩‍💻



|FRONTEND|FRONTEND|BACKEND|BACKEND|BACKEND|BACKEND|BACKEND
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|![](https://github.com/lokba.png?size=100)|![](https://github.com/sanaandmomo.png?size=100)|![](https://github.com/her0807.png?size=100)|![](https://github.com/dongho108.png?size=100)|![](https://github.com/HyeonbinSa.png?size=100)|![](https://github.com/soominsohn.png?size=100)|![](https://github.com/Juhyung990122.png?size=100)
|[록바](https://github.com/lokba)|[아놀드](https://github.com/sanaandmomo)|[수달](https://github.com/her0807)|[애쉬](https://github.com/dongho108)|[바니](https://github.com/HyeonbinSa)|[앤지](https://github.com/soominsohn)|[열음](https://github.com/Juhyung990122)
|<img width="120px" src="https://avatars.githubusercontent.com/u/19251499?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/38878617?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/26570275?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/54317630?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/36189291?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/83059234?s=100&v=4" />|<img width="120px" src="https://avatars.githubusercontent.com/u/43205258?s=100&v=4" />
|[록바](https://github.com/lokba)|[아놀드](https://github.com/sanaandmomo)|[수달](https://github.com/her0807)|[애쉬](https://github.com/dongho108)|[바니](https://github.com/HyeonbinSa)|[앤지](https://github.com/soominsohn)|[열음](https://github.com/Juhyung990122)|


## 👨‍👨‍👦‍👦 터놓고의 팀.
<img width="915" alt="Screen Shot 2022-10-11 at 2 49 34 PM" src="https://user-images.githubusercontent.com/83059234/195291896-ca005fa9-dff4-44ca-96af-938971891ce9.png">
10 changes: 7 additions & 3 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,19 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'

//sonarqube
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'

// monitor
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

// test
testImplementation 'com.google.guava:guava:31.1-jre'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:4.4.0'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'

//sonarqube t
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
}

ext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.slack.api.methods.response.openid.connect.OpenIDConnectUserInfoResponse;
import com.woowacourse.ternoko.auth.dto.response.LoginResponse;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.InvalidTokenException;
import com.woowacourse.ternoko.common.exception.TokenInvalidException;
import com.woowacourse.ternoko.core.domain.member.Member;
import com.woowacourse.ternoko.core.domain.member.MemberRepository;
import com.woowacourse.ternoko.core.domain.member.MemberType;
Expand All @@ -20,6 +20,7 @@
import com.woowacourse.ternoko.core.domain.member.crew.Crew;
import com.woowacourse.ternoko.core.domain.member.crew.CrewRepository;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
Expand All @@ -42,6 +43,8 @@ public class AuthService {
private final String clientId;
private final String clientSecret;

private final RandomMemberIdGenerator memberIdGenerator = RandomMemberIdGenerator.of(111, 210, 11, 110);

public AuthService(final MethodsClientImpl slackMethodClient,
final MemberRepository memberRepository,
final CoachRepository coachRepository,
Expand All @@ -58,6 +61,22 @@ public AuthService(final MethodsClientImpl slackMethodClient,
this.clientSecret = clientSecret;
}

public LoginResponse loginCoach() {
final Long coachId = memberIdGenerator.getRandomCoachId();
final Member member = memberRepository.findById(coachId)
.orElseThrow(() -> new NoSuchElementException("로그인할 coachId가 존재하지 않습니다."));

return LoginResponse.of(COACH, jwtProvider.createToken(member), true);
}

public LoginResponse loginCrew() {
final Long crewId = memberIdGenerator.getRandomCrewId();
final Member member = memberRepository.findById(crewId)
.orElseThrow(() -> new NoSuchElementException("로그인할 crewId가 존재하지 않습니다."));

return LoginResponse.of(CREW, jwtProvider.createToken(member), true);
}

public LoginResponse login(final String code, String redirectUrl) throws SlackApiException, IOException {
final OpenIDConnectUserInfoResponse userInfoResponse = getUserInfoResponseBySlack(code, redirectUrl);
final Optional<Member> member = memberRepository.findByEmail(userInfoResponse.getEmail());
Expand Down Expand Up @@ -133,19 +152,19 @@ public void checkMemberType(final Long id, final String type) {

private void validateType(String type) {
if (!MemberType.existType(type)) {
throw new InvalidTokenException(ExceptionType.INVALID_TOKEN);
throw new TokenInvalidException(ExceptionType.INVALID_TOKEN);
}
}

private void validateCoachTypeByMemberId(final Long id, final String type) {
if (COACH.matchType(type) && !coachRepository.existsById(id)) {
throw new InvalidTokenException(ExceptionType.INVALID_TOKEN);
throw new TokenInvalidException(ExceptionType.INVALID_TOKEN);
}
}

private void validateCrewTypeByMemberId(final Long id, final String type) {
if (CREW.matchType(type) && !crewRepository.existsById(id)) {
throw new InvalidTokenException(ExceptionType.INVALID_TOKEN);
throw new TokenInvalidException(ExceptionType.INVALID_TOKEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import com.woowacourse.ternoko.auth.exception.TokenNotValidException;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.ExpiredTokenException;
import com.woowacourse.ternoko.common.exception.InvalidTokenException;
import com.woowacourse.ternoko.common.exception.TokenInvalidException;
import com.woowacourse.ternoko.core.domain.member.Member;
import com.woowacourse.ternoko.core.domain.member.MemberType;
import io.jsonwebtoken.ExpiredJwtException;
Expand Down Expand Up @@ -49,9 +48,9 @@ public void validateToken(final String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
} catch (ExpiredJwtException e) {
throw new ExpiredTokenException(ExceptionType.EXPIRED_TOKEN);
throw new TokenInvalidException(ExceptionType.EXPIRED_TOKEN);
} catch (Exception e) {
throw new InvalidTokenException(ExceptionType.INVALID_TOKEN);
throw new TokenInvalidException(ExceptionType.INVALID_TOKEN);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.woowacourse.ternoko.auth.application;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import org.jetbrains.annotations.NotNull;

public class RandomMemberIdGenerator {

private final static Random RANDOM = new Random(System.currentTimeMillis());

private final long startCrewId;
private final long endCrewId;
private final long startCoachId;
private final long endCoachId;
private List<Long> crewIds;
private List<Long> coachIds;

private RandomMemberIdGenerator(final long startCrewId,
final long endCrewId,
final long startCoachId,
final long endCoachId,
final List<Long> crewIds, final List<Long> coachIds) {
this.startCrewId = startCrewId;
this.endCrewId = endCrewId;
this.startCoachId = startCoachId;
this.endCoachId = endCoachId;
this.crewIds = crewIds;
this.coachIds = coachIds;
}

public static RandomMemberIdGenerator of(final long startCrewId,
final long endCrewId,
final long startCoachId,
final long endCoachId) {
final List<Long> crewIds = getIds(startCrewId, endCrewId);

final List<Long> coachIds = new ArrayList<>();
for (long i = startCoachId; i <= endCoachId; i++) {
coachIds.add(i);
}

return new RandomMemberIdGenerator(startCrewId, endCrewId, startCoachId, endCoachId, crewIds, coachIds);
}

@NotNull
private static List<Long> getIds(final long startId, final long endId) {
final List<Long> ids = Collections.synchronizedList(new ArrayList<>());
for (long i = startId; i <= endId; i++) {
ids.add(i);
}
return ids;
}

public Long getRandomCrewId() {
if (crewIds.size() == 0) {
crewIds = getIds(startCrewId, endCrewId);
}

final int randomIndex = RANDOM.nextInt(crewIds.size());
return crewIds.remove(randomIndex);
}

public Long getRandomCoachId() {
if (coachIds.size() == 0) {
coachIds = getIds(startCoachId, endCoachId);
}

final int randomIndex = RANDOM.nextInt(coachIds.size());
return coachIds.remove(randomIndex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Builder(builderMethodName = "loginResponseBuilder")
public class LoginResponse {

private boolean hasNickname;
private MemberType memberRole;
private String accessToken;

public static LoginResponse of(final MemberType memberRole, final String accessToken, final boolean hasNickname) {
return LoginResponse.loginResponseBuilder()
return LoginResponse.builder()
.memberRole(memberRole)
.accessToken(accessToken)
.hasNickname(hasNickname)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.woowacourse.ternoko.auth.exception;

import com.woowacourse.ternoko.common.exception.CommonException;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.advice.CommonException;
import org.springframework.http.HttpStatus;

public class CoachNotAllowedException extends CommonException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.woowacourse.ternoko.auth.exception;

import com.woowacourse.ternoko.common.exception.CommonException;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.advice.CommonException;
import org.springframework.http.HttpStatus;

public class CrewNotAllowedException extends CommonException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.woowacourse.ternoko.auth.exception;

import com.woowacourse.ternoko.common.exception.CommonException;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.advice.CommonException;
import org.springframework.http.HttpStatus;

public class TokenNotValidException extends CommonException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ public class AuthController {
private static final String ALL = "ALL";
private final AuthService authService;

@GetMapping
public ResponseEntity<LoginResponse> login(@RequestParam final String code, @RequestParam final String redirectUrl)
@GetMapping("/coach")
public ResponseEntity<LoginResponse> loginCoach()
throws SlackApiException, IOException {
final LoginResponse loginResponse = authService.login(code, redirectUrl);
final LoginResponse loginResponse = authService.loginCoach();
return ResponseEntity.ok(loginResponse);
}

@GetMapping("/crew")
public ResponseEntity<LoginResponse> loginCrew()
throws SlackApiException, IOException {
final LoginResponse loginResponse = authService.loginCrew();
return ResponseEntity.ok(loginResponse);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import com.woowacourse.ternoko.auth.application.AuthService;
import com.woowacourse.ternoko.common.exception.ExceptionType;
import com.woowacourse.ternoko.common.exception.InvalidTokenException;
import com.woowacourse.ternoko.common.exception.TokenInvalidException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,7 +25,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
}
String header = request.getHeader(AUTHORIZATION);
if (header == null) {
throw new InvalidTokenException(ExceptionType.UNAUTHORIZED_MEMBER);
throw new TokenInvalidException(ExceptionType.UNAUTHORIZED_MEMBER);
}
return authService.isValid(header);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.woowacourse.ternoko.common.exception;

import com.woowacourse.ternoko.common.exception.advice.CommonException;

public class AvailableDateTimeInvalidException extends CommonException {
public AvailableDateTimeInvalidException(final ExceptionType exceptionType) {
super(exceptionType.getHttpStatus(), exceptionType.getStatusCode(), exceptionType.getMessage());
}

public AvailableDateTimeInvalidException(final ExceptionType exceptionType, final Long prefix) {
super(exceptionType.getHttpStatus(), exceptionType.getStatusCode(), prefix + exceptionType.getMessage());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.woowacourse.ternoko.common.exception;

import com.woowacourse.ternoko.common.exception.advice.CommonException;

public class CoachInvalidException extends CommonException {

public CoachInvalidException(final ExceptionType exceptionType, final Long coachId) {
super(exceptionType.getHttpStatus(), exceptionType.getStatusCode(), coachId + exceptionType.getMessage());
}
}

This file was deleted.

Loading