Skip to content

Commit

Permalink
v1.2.0 배포 🎉
Browse files Browse the repository at this point in the history
v1.2.0 배포 🎉
  • Loading branch information
yeon-06 authored Oct 19, 2022
2 parents b70a966 + cdb3c1d commit d77dcdd
Show file tree
Hide file tree
Showing 322 changed files with 8,093 additions and 5,345 deletions.
49 changes: 28 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
# 사라지는 Slack 메시지, 우리가 주워줄게! 줍줍


## Members 👩🏻‍💻🧑🏻‍💻

| [호프](https://github.com/moonheekim0118) | [꼬재](https://github.com/kkojae91) | [](https://github.com/JangBomi) | [써머](https://github.com/hyewoncc) | [리차드](https://github.com/HJ-Rich) | [연로그](https://github.com/yeon-06) |
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| <img src="https://avatars.githubusercontent.com/u/61469664?v=4" width=200px alt="_"/> | <img src="https://avatars.githubusercontent.com/u/68001045?v=4" width=200px alt="_"/> | <img src="https://avatars.githubusercontent.com/u/55357130?v=4" width=200px alt="_"/> | <img src="https://avatars.githubusercontent.com/u/80666066?v=4" width=200px alt="_"> | <img src="https://avatars.githubusercontent.com/u/62681566?v=4" width=200px alt="_"> | <img src="https://avatars.githubusercontent.com/u/53105735?v=4" width=200px alt="_"> |
| ✨ 프론트엔드 | ✨ 프론트엔드 | 💫 백엔드 | 💫 백엔드 | 💫 백엔드 | 💫 백엔드 |


## 😎 API 명세
https://documenter.getpostman.com/view/13826399/UzJESeDr

## 😎 스토리북
https://62e64dc73aafd7bc9338ba73-fihzwjkqkx.chromatic.com

## 그 외

- [줍줍 팀문화 보러가기](https://richard7.notion.site/db4a276903ab477a8db6591d4413f873)

<div align=center>
<img width="492" alt="스크린샷 2022-09-30 오후 5 31 24" src="https://user-images.githubusercontent.com/80666066/193769096-9162414f-16ff-4c74-878f-5661b0f671cc.png">
<h2> 사라지는 Slack 메세지, 우리가 주워줄게! </h2>
https://jupjup.site/
<br>
<br>
<strong>줍줍</strong>은 연결된 슬랙 워크스페이스 메시지를 실시간 백업하여
<br>
무료 워크스페이스라도 잠길 걱정 없이 언제든 볼 수 있게 해주는 서비스입니다
<br>

</div>

## 📎 바로가기

- 📚 [API 문서](https://dev.jupjup.site/docs)
- 🎁 [스토리북](https://62e64dc73aafd7bc9338ba73-fihzwjkqkx.chromatic.com)
- 🐹 [팀문화](https://selective-archeology-e38.notion.site/858f167439b94c9caee71ab177bce08e)
- 🌎 [깃헙 위키](https://github.com/woowacourse-teams/2022-pickpick/wiki)

<br>

## 팀원 소개 👩🏻‍💻🧑🏻‍💻

| [🐈‍⬛ 호프](https://github.com/moonheekim0118) | [👍 꼬재](https://github.com/kkojae91) | [🌱 봄](https://github.com/JangBomi) | [🏝 써머](https://github.com/hyewoncc) | [🪁 연로그](https://github.com/yeon-06) |
| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
| <a href="https://github.com/moonheekim0118"> <img src="https://avatars.githubusercontent.com/u/61469664?v=4" width=200px alt="_"/> </a> | <a href="https://github.com/kkojae91"> <img src="https://avatars.githubusercontent.com/u/68001045?v=4" width=200px alt="_"/> </a> | <a href="https://github.com/JangBomi"> <img src="https://avatars.githubusercontent.com/u/55357130?v=4" width=200px alt="_"/> </a> | <a href="https://github.com/hyewoncc"> <img src="https://avatars.githubusercontent.com/u/80666066?v=4" width=200px alt="_"/> </a> | <a href="https://github.com/yeon-06"> <img src="https://avatars.githubusercontent.com/u/53105735?v=4" width=200px alt="_"> |
| 프론트엔드 | 프론트엔드 | 백엔드 | 백엔드 | 백엔드 |
4 changes: 4 additions & 0 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ operation::reminder-controller-test/delete[snippets='http-request,request-header
=== 리마인더 수정 API

operation::reminder-controller-test/update[snippets='http-request,request-headers,request-fields,http-response']

= 에러

operation::error-code-docs-test/error-codes[snippets='error-code,http-response']
2 changes: 2 additions & 0 deletions backend/src/main/java/com/pickpick/PickpickApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@EnableJpaAuditing
@ConfigurationPropertiesScan
@SpringBootApplication
public class PickpickApplication {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,87 +1,72 @@
package com.pickpick.auth.application;

import com.pickpick.auth.application.dto.WorkspaceInfoDto;
import com.pickpick.auth.support.JwtTokenProvider;
import com.pickpick.auth.ui.dto.LoginResponse;
import com.pickpick.config.SlackProperties;
import com.pickpick.exception.SlackApiCallException;
import com.pickpick.exception.member.MemberNotFoundException;
import com.pickpick.channel.domain.Channel;
import com.pickpick.channel.domain.ChannelRepository;
import com.pickpick.member.domain.Member;
import com.pickpick.member.domain.MemberRepository;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.oauth.OAuthV2AccessRequest;
import com.slack.api.methods.request.users.UsersIdentityRequest;
import java.io.IOException;
import javax.transaction.Transactional;
import com.pickpick.support.ExternalClient;
import com.pickpick.workspace.domain.Workspace;
import com.pickpick.workspace.domain.WorkspaceRepository;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional(readOnly = true)
public class AuthService {

private final MemberRepository members;
private final MethodsClient slackClient;
private final WorkspaceRepository workspaces;
private final ChannelRepository channels;
private final ExternalClient slackClient;
private final JwtTokenProvider jwtTokenProvider;
private final SlackProperties slackProperties;

public AuthService(final MemberRepository members, final MethodsClient slackClient,
final JwtTokenProvider jwtTokenProvider, final SlackProperties slackProperties) {
public AuthService(final MemberRepository members, final WorkspaceRepository workspaces,
final ChannelRepository channels, final ExternalClient slackClient,
final JwtTokenProvider jwtTokenProvider) {
this.members = members;
this.workspaces = workspaces;
this.channels = channels;
this.slackClient = slackClient;
this.jwtTokenProvider = jwtTokenProvider;
this.slackProperties = slackProperties;
}

public void verifyToken(final String token) {
jwtTokenProvider.validateToken(token);
}

@Transactional
public LoginResponse registerWorkspace(final String code) {
WorkspaceInfoDto workspaceInfoDto = slackClient.callWorkspaceInfo(code);
Workspace workspace = workspaces.save(workspaceInfoDto.toEntity());

List<Member> allWorkspaceMembers = slackClient.findMembersByWorkspace(workspace);
members.saveAll(allWorkspaceMembers);

List<Channel> allWorkspaceChannels = slackClient.findChannelsByWorkspace(workspace);
channels.saveAll(allWorkspaceChannels);

return login(code);
}

@Transactional
public LoginResponse login(final String code) {
String token = requestSlackToken(code);
String memberSlackId = requestMemberSlackId(token);
String userToken = slackClient.callUserToken(code);
String memberSlackId = slackClient.callMemberSlackId(userToken);

Member member = members.findBySlackId(memberSlackId)
.orElseThrow(() -> new MemberNotFoundException(memberSlackId));
Member member = members.getBySlackId(memberSlackId);

boolean isFirstLogin = member.isFirstLogin();
member.markLoggedIn();
member.firstLogin(userToken);

return LoginResponse.builder()
.token(jwtTokenProvider.createToken(String.valueOf(member.getId())))
.firstLogin(isFirstLogin)
.build();
}

private String requestSlackToken(final String code) {
OAuthV2AccessRequest request = OAuthV2AccessRequest.builder()
.clientId(slackProperties.getClientId())
.clientSecret(slackProperties.getClientSecret())
.redirectUri(slackProperties.getRedirectUrl())
.code(code)
.build();

try {
return slackClient.oauthV2Access(request)
.getAuthedUser()
.getAccessToken();
} catch (IOException | SlackApiException e) {
throw new SlackApiCallException("oauthV2Access");
}
}

private String requestMemberSlackId(final String token) {
UsersIdentityRequest request = UsersIdentityRequest.builder()
.token(token)
.build();

try {
return slackClient.usersIdentity(request)
.getUser()
.getId();
} catch (IOException | SlackApiException e) {
throw new SlackApiCallException("usersIdentity");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.pickpick.auth.application.dto;

import com.pickpick.workspace.domain.Workspace;
import lombok.Getter;

@Getter
public class WorkspaceInfoDto {

private final String workspaceSlackId;
private final String botToken;
private final String botSlackId;

public WorkspaceInfoDto(final String workspaceSlackId, final String botToken, final String botSlackId) {
this.workspaceSlackId = workspaceSlackId;
this.botToken = botToken;
this.botSlackId = botSlackId;
}

public Workspace toEntity() {
return new Workspace(workspaceSlackId, botToken, botSlackId);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pickpick.auth.support;

import com.pickpick.exception.auth.ExtractTokenFailException;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;

Expand All @@ -10,6 +11,9 @@ public class AuthorizationExtractor {
private static final String ACCESS_TOKEN_TYPE = AuthorizationExtractor.class.getSimpleName() + ".ACCESS_TOKEN_TYPE";
private static final char COMMA = ',';

private AuthorizationExtractor() {
}

public static String extract(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders(AUTHORIZATION);
while (headers.hasMoreElements()) {
Expand All @@ -25,6 +29,6 @@ public static String extract(HttpServletRequest request) {
}
}

throw new RuntimeException("잘못된 토큰 정보입니다.");
throw new ExtractTokenFailException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ public void verifyToken(final HttpServletRequest request) {
public LoginResponse login(@RequestParam @NotEmpty final String code) {
return authService.login(code);
}

@GetMapping("/slack-workspace")
public LoginResponse registerWorkspace(@RequestParam @NotEmpty final String code) {
return authService.registerWorkspace(code);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
import com.pickpick.channel.domain.ChannelSubscriptionRepository;
import com.pickpick.channel.ui.dto.ChannelResponse;
import com.pickpick.channel.ui.dto.ChannelResponses;
import com.pickpick.exception.member.MemberTokenNotFoundException;
import com.pickpick.member.domain.Member;
import com.pickpick.member.domain.MemberRepository;
import com.pickpick.slackevent.domain.Participation;
import com.pickpick.support.ExternalClient;
import com.pickpick.workspace.domain.Workspace;
import com.querydsl.core.util.StringUtils;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -16,31 +23,57 @@
@Service
public class ChannelService {

private final ExternalClient externalClient;
private final MemberRepository members;
private final ChannelRepository channels;
private final ChannelSubscriptionRepository channelSubscriptions;

public ChannelService(final ChannelRepository channels, final ChannelSubscriptionRepository channelSubscriptions) {
public ChannelService(final ExternalClient externalClient, final MemberRepository members,
final ChannelRepository channels, final ChannelSubscriptionRepository channelSubscriptions) {
this.externalClient = externalClient;
this.members = members;
this.channels = channels;
this.channelSubscriptions = channelSubscriptions;
}

public ChannelResponses findAll(final Long memberId) {
List<Channel> allChannels = channels.findAllByOrderByName();
public ChannelResponses findByWorkspace(final Long memberId) {
Member member = members.getById(memberId);
validateToken(member);

List<Channel> participatingChannels = findParticipatingChannels(member);
Set<Channel> subscribedChannels = findSubscribedChannels(memberId);

List<ChannelResponse> channelResponses = findChannelResponses(allChannels, subscribedChannels);
List<ChannelResponse> channelResponses = generateChannelResponses(participatingChannels, subscribedChannels);
return new ChannelResponses(channelResponses);
}

private void validateToken(final Member member) {
String token = member.getToken();
if (StringUtils.isNullOrEmpty(token)) {
throw new MemberTokenNotFoundException(member.getId(), token);
}
}

private List<Channel> findParticipatingChannels(final Member member) {
String token = member.getToken();
Participation participation = externalClient.findChannelParticipation(token);

Workspace workspace = member.getWorkspace();
return channels.findAllByWorkspaceOrderByName(workspace)
.stream()
.filter(it -> participation.isParticipant(it.getSlackId()))
.collect(Collectors.toList());
}

private Set<Channel> findSubscribedChannels(final Long memberId) {
return channelSubscriptions.findAllByMemberId(memberId)
.stream()
.map(ChannelSubscription::getChannel)
.collect(Collectors.toSet());
}

private List<ChannelResponse> findChannelResponses(final List<Channel> allChannels,
final Set<Channel> subscribedChannels) {
private List<ChannelResponse> generateChannelResponses(final List<Channel> allChannels,
final Set<Channel> subscribedChannels) {
return allChannels.stream()
.map(channel -> ChannelResponse.of(subscribedChannels, channel))
.collect(Collectors.toList());
Expand Down
Loading

0 comments on commit d77dcdd

Please sign in to comment.