Skip to content

Commit

Permalink
[Feat] 슛 생성 시 멘션이 존재하면 dm이 전송되도록 추가 (#70)
Browse files Browse the repository at this point in the history
* feat : 슛 생성 시 dm 전송

* fix : 슛태그 생성 시 해당 파일 내의 피그마 객체를 찾도록 수정

* refactor : content 변수 삭제
  • Loading branch information
iiqcov authored Nov 23, 2024
1 parent 93b4a75 commit ab82fff
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.domain.member.domain.Member;

@Repository
Expand All @@ -19,5 +22,8 @@ public interface FigmaRepository extends JpaRepository<Figma, Long> {

Optional<Figma> findByFigmaUserIdAndDeletedAtIsNull(String figmaUserId);

Optional<Figma> findByFigmaName(String name);
@Query(
"SELECT f FROM Figma f WHERE f.figmaName = :figmaName AND f.figmaId IN (SELECT a.figma.figmaId FROM Authority a WHERE a.file = :file)")
Optional<Figma> findByFigmaNameAndFile(
@Param("figmaName") String figmaName, @Param("file") File file);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
import org.springframework.transaction.annotation.Transactional;

import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.block.domain.Block;
import gigedi.dev.domain.discord.domain.Discord;
import gigedi.dev.domain.discord.dto.response.AlarmFileResponse;
import gigedi.dev.domain.discord.dto.response.GetAlarmFileListResponse;
import gigedi.dev.domain.figma.application.FigmaService;
import gigedi.dev.domain.file.application.AuthorityService;
import gigedi.dev.domain.file.domain.Authority;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.domain.member.domain.Member;
import gigedi.dev.global.util.MemberUtil;
import gigedi.dev.global.util.ShootUtil;
import lombok.RequiredArgsConstructor;

@Service
Expand All @@ -23,6 +26,7 @@ public class AlarmService {
private final DiscordService discordService;
private final AuthorityService authorityService;
private final FigmaService figmaService;
private final DiscordDmApiService discordDmApiService;
private final MemberUtil memberUtil;

public GetAlarmFileListResponse getAlarmFileList() {
Expand Down Expand Up @@ -54,4 +58,28 @@ private Authority getAuthorityByFileId(Long fileId) {
List<Figma> figmaList = figmaService.getFigmaListByMember(currentMember);
return authorityService.getAuthorityByFileIdAndFigmaList(fileId, figmaList);
}

public void sendAlarmToDiscord(
List<String> tags, Block block, Figma senderFigma, String message) {
String sender = senderFigma.getFigmaName();
String blockTitle = block.getTitle();
String archiveTitle = block.getArchive().getTitle();
File currentFile = block.getArchive().getFile();
List<Figma> receiverList =
authorityService.getAlarmTargetListByFigmaName(currentFile, tags);

receiverList.forEach(
receiver -> {
String channelId = discordService.getDmChannelByMember(receiver.getMember());
if (channelId != null) {
discordDmApiService.sendDMMessage(
channelId,
sender,
receiver.getFigmaName(),
ShootUtil.highlightText(archiveTitle),
ShootUtil.highlightText(blockTitle),
ShootUtil.highlightMentions(message));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import static gigedi.dev.global.common.constants.SecurityConstants.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpHeaders;
Expand All @@ -23,6 +26,9 @@ public class DiscordDmApiService {
private final RestClient restClient;
private final DiscordProperties discordProperties;

private static final String DM_BASE_TITLE = "Please check it in OUR SHOOT !";
private static final int DM_BASE_COLOR = 3447003;

public CreateDMChannelResponse createDMChannel(String userId) {
try {
Map<String, Object> requestBody = Map.of("recipient_id", userId);
Expand All @@ -49,4 +55,64 @@ public CreateDMChannelResponse createDMChannel(String userId) {
throw new CustomException(ErrorCode.DISCORD_DM_CHANNEL_CREATION_FAILED);
}
}

public void sendDMMessage(
String channelId,
String sender,
String receiver,
String archiveTitle,
String blockTitle,
String content) {
try {
Map<String, Object> embed = new HashMap<>();
embed.put("title", DM_BASE_TITLE);
embed.put("color", DM_BASE_COLOR);

List<Map<String, Object>> fields = new ArrayList<>();
fields.add(createField("From", sender, true));
fields.add(createField("To", receiver, true));
fields.add(
createField("In", "ARCHIVE " + archiveTitle + " - BLOCK " + blockTitle, false));
fields.add(createField("Content", content, false));

embed.put("fields", fields);

Map<String, Object> requestBody = new HashMap<>();
requestBody.put("embeds", List.of(embed));

restClient
.post()
.uri(
uriBuilder ->
uriBuilder
.scheme(HTTPS_SCHEME)
.host(DISCORD_HOST)
.path(DISCORD_SEND_DM_URL)
.build(channelId))
.header(
HttpHeaders.AUTHORIZATION,
BOT_TOKEN_PREFIX + discordProperties.botToken())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(requestBody)
.retrieve()
.onStatus(
status -> !status.is2xxSuccessful(),
(request, response) -> {
log.error("Discord DM 메시지 전송 실패: {}", response.getStatusCode());
throw new CustomException(ErrorCode.DISCORD_DM_MESSAGE_SEND_FAILED);
})
.toBodilessEntity();
} catch (Exception e) {
log.error("Discord DM 메시지 전송 중 예외 발생: {}", e.getMessage(), e);
throw new CustomException(ErrorCode.DISCORD_DM_MESSAGE_SEND_FAILED);
}
}

private Map<String, Object> createField(String name, String value, boolean inline) {
Map<String, Object> field = new HashMap<>();
field.put("name", name);
field.put("value", value);
field.put("inline", inline);
return field;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@ public void validateDiscordExistsForMember() {
throw new CustomException(ErrorCode.DISCORD_ACCOUNT_ALREADY_EXISTS);
}
}

@Transactional(readOnly = true)
public String getDmChannelByMember(Member member) {
return discordRepository.findByMember(member).map(Discord::getDmChannel).orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import gigedi.dev.domain.auth.dao.FigmaRepository;
import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.domain.member.domain.Member;
import gigedi.dev.global.error.exception.CustomException;
import gigedi.dev.global.error.exception.ErrorCode;
Expand All @@ -31,9 +32,9 @@ public Figma getFigmaByFigmaId(String figmaId) {
.orElseThrow(() -> new CustomException(ErrorCode.FIGMA_NOT_CONNECTED));
}

public Figma findByTag(String tag) {
public Figma findByTag(String tag, File file) {
return figmaRepository
.findByFigmaName(tag)
.findByFigmaNameAndFile(tag, file)
.orElseThrow(() -> new CustomException(ErrorCode.FIGMA_USER_INFO_NOT_FOUND));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.file.dao.AuthorityRepository;
import gigedi.dev.domain.file.domain.Authority;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.global.error.exception.CustomException;
import gigedi.dev.global.error.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
Expand All @@ -28,4 +29,9 @@ public Authority getAuthorityByFileIdAndFigmaList(Long fileId, List<Figma> figma
.findByFileAndActiveFigma(fileId, figmaList)
.orElseThrow(() -> new CustomException(ErrorCode.AUTHORITY_NOT_FOUND));
}

@Transactional(readOnly = true)
public List<Figma> getAlarmTargetListByFigmaName(File file, List<String> figmaNameList) {
return authorityRepository.getFigmaNamesWithActiveAlarmByFile(file, figmaNameList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.file.domain.Authority;
import gigedi.dev.domain.file.domain.File;

public interface AuthorityRepositoryCustom {
List<Authority> findRelatedAuthorities(Long memberId);

Optional<Authority> findByFileAndActiveFigma(Long fileId, List<Figma> figmaList);

List<Figma> getFigmaNamesWithActiveAlarmByFile(File file, List<String> figmaNames);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.file.domain.Authority;
import gigedi.dev.domain.file.domain.File;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
Expand Down Expand Up @@ -45,4 +46,18 @@ public Optional<Authority> findByFileAndActiveFigma(Long fileId, List<Figma> fig
.and(authority.figma.deletedAt.isNull()))
.fetchOne());
}

@Override
public List<Figma> getFigmaNamesWithActiveAlarmByFile(File file, List<String> figmaNames) {
return queryFactory
.selectFrom(figma)
.join(authority)
.on(authority.figma.eq(figma))
.where(
authority.file.eq(file),
figma.figmaName.in(figmaNames),
authority.alarm.isTrue(),
figma.deletedAt.isNull())
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.block.application.BlockService;
import gigedi.dev.domain.block.domain.Block;
import gigedi.dev.domain.discord.application.AlarmService;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.domain.shoot.dao.ShootRepository;
import gigedi.dev.domain.shoot.dao.ShootStatusRepository;
import gigedi.dev.domain.shoot.domain.Shoot;
Expand All @@ -33,6 +35,7 @@ public class ShootService {
private final FigmaUtil figmaUtil;
private final BlockService blockService;
private final ShootTagService shootTagService;
private final AlarmService alarmService;

private static final String YET = "yet";
private static final String DOING = "doing";
Expand Down Expand Up @@ -66,14 +69,17 @@ public GetShootResponse createShoot(Long blockId, String content) {
final Figma figma = figmaUtil.getCurrentFigma();
Shoot shoot = Shoot.createShoot(content, figma, block);
shootRepository.save(shoot);
processTags(content, shoot);
List<String> tags = processTags(content, shoot);
alarmService.sendAlarmToDiscord(tags, block, figma, content);

return GetShootResponse.of(shoot, null, null, null);
}

private void processTags(String content, Shoot shoot) {
private List<String> processTags(String content, Shoot shoot) {
File currentFile = figmaUtil.getCurrentFile();
List<String> tags = ShootUtil.extractTags(content);
shootTagService.createShootTags(shoot, tags);
shootTagService.createShootTags(shoot, tags, currentFile);
return tags;
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import gigedi.dev.domain.auth.domain.Figma;
import gigedi.dev.domain.figma.application.FigmaService;
import gigedi.dev.domain.file.domain.File;
import gigedi.dev.domain.shoot.dao.ShootTagRepository;
import gigedi.dev.domain.shoot.domain.Shoot;
import gigedi.dev.domain.shoot.domain.ShootTag;
Expand All @@ -17,10 +18,10 @@ public class ShootTagService {
private final ShootTagRepository shootTagRepository;
private final FigmaService figmaService;

public void createShootTags(Shoot shoot, List<String> tags) {
public void createShootTags(Shoot shoot, List<String> tags, File currentFile) {
tags.forEach(
tag -> {
Figma figma = figmaService.findByTag(tag);
Figma figma = figmaService.findByTag(tag, currentFile);
if (figma != null) {
ShootTag shootTag = ShootTag.createShootTag(shoot, figma);
shootTagRepository.save(shootTag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public final class SecurityConstants {
public static final String GOOGLE_WITHDRAWAL_URL =
"https://accounts.google.com/o/oauth2/revoke?token=";

public static final String DISCORD_HOST = "discord.com";
public static final String DISCORD_TOKEN_URL = "https://discord.com/api/oauth2/token";
public static final String DISCORD_USER_INFO_URL = "https://discord.com/api/users/@me";
public static final String DISCORD_CREATE_DM_CHANNEL_URL =
"https://discord.com/api/v9/users/@me/channels";
public static final String DISCORD_GUILD_URL = "https://discord.com/api/v10/guilds";
public static final String DISCORD_DISCONNECT_URL =
"https://discord.com/api/oauth2/token/revoke";
public static final String DISCORD_SEND_DM_URL = "/api/channels/{channelId}/messages";

public static final String FIGMA_HOST = "api.figma.com";
public static final String FIGMA_GET_ID_TOKEN_URL = "https://www.figma.com/api/oauth/token";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public enum ErrorCode {
DISCORD_TOKEN_REISSUE_FAILED(HttpStatus.BAD_REQUEST, "디스코드 토큰 재발급 과정에서 오류가 발생했습니다."),
DISCORD_DISCONNECT_FAILED(HttpStatus.BAD_REQUEST, "디스코드 연결 해제 과정에서 오류가 발생했습니다."),
DISCORD_ACCOUNT_ALREADY_EXISTS(HttpStatus.NOT_FOUND, "연결된 디스코드 계정이 이미 존재합니다."),
DISCORD_DM_MESSAGE_SEND_FAILED(HttpStatus.BAD_REQUEST, "디스코드 DM 전송에 실패했습니다."),

// Authority
AUTHORITY_NOT_FOUND(HttpStatus.NOT_FOUND, "피그마 계정과 파일의 연관 정보가 존재하지 않습니다."),
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/gigedi/dev/global/util/ShootUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
public class ShootUtil {
private static final String SPACE_DELIMITER = "\\s+";
private static final String TAG_PREFIX = "@";
private static final String HIGHLIGHT_FORMAT = "**%s**";

public static List<String> extractTags(String content) {

Expand All @@ -19,4 +20,25 @@ public static List<String> extractTags(String content) {
.map(word -> word.substring(1))
.collect(Collectors.toList());
}

public static String highlightMentions(String content) {
if (content == null || content.isEmpty()) {
return content;
}

return Arrays.stream(content.split(SPACE_DELIMITER))
.map(
word ->
word.startsWith(TAG_PREFIX)
? String.format(HIGHLIGHT_FORMAT, word)
: word)
.collect(Collectors.joining(" "));
}

public static String highlightText(String text) {
if (text == null || text.isEmpty()) {
return text;
}
return String.format(HIGHLIGHT_FORMAT, text);
}
}

0 comments on commit ab82fff

Please sign in to comment.