diff --git a/.github/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java b/.github/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java new file mode 100644 index 000000000..4315d3f06 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -0,0 +1,51 @@ +//package com.sprint.mission.discodeit; +// +//import com.sprint.mission.discodeit.entity.Channel; +//import com.sprint.mission.discodeit.entity.ChannelType; +//import com.sprint.mission.discodeit.entity.Message; +//import com.sprint.mission.discodeit.entity.User; +//import com.sprint.mission.discodeit.service.ChannelService; +//import com.sprint.mission.discodeit.service.MessageService; +//import com.sprint.mission.discodeit.service.UserService; +//import com.sprint.mission.discodeit.service.basic.BasicChannelService; +//import com.sprint.mission.discodeit.service.basic.BasicMessageService; +//import com.sprint.mission.discodeit.service.basic.BasicUserService; +//import org.springframework.boot.SpringApplication; +//import org.springframework.boot.autoconfigure.SpringBootApplication; +//import org.springframework.context.ConfigurableApplicationContext; +// +//@SpringBootApplication +//public class DiscodeitApplication { +// +// static User setupUser(UserService userService) { +// User user = userService.create("woody", "woody@codeit.com", "woody1234"); +// return user; +// } +// +// static Channel setupChannel(ChannelService channelService) { +// Channel channel = channelService.create(ChannelType.PUBLIC, "공지", "공지 채널입니다."); +// return channel; +// } +// +// static void messageCreateTest(MessageService messageService, Channel channel, User author) { +// Message message = messageService.create("안녕하세요.", channel.getId(), author.getId()); +// System.out.println("메시지 생성: [" + author.getUsername() + "] " + message.getContent()); +// } +// +// public static void main(String[] args) { +// ConfigurableApplicationContext context = SpringApplication.run(DiscodeitApplication.class, args); +// +// // 서비스 초기화 +// UserService userService = context.getBean(UserService.class); +// ChannelService channelService = context.getBean(ChannelService.class); +// MessageService messageService = context.getBean(MessageService.class); +// +// // 셋업 +// User user = setupUser(userService); +// Channel channel = setupChannel(channelService); +// +// // 테스트 +// messageCreateTest(messageService, channel, user); +// } +// +//} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/JavaApplication.java b/.github/src/main/java/com/sprint/mission/discodeit/JavaApplication.java new file mode 100644 index 000000000..c8eb112d5 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/JavaApplication.java @@ -0,0 +1,38 @@ +//package com.sprint.mission.discodeit; +// +//import com.sprint.mission.discodeit.entity.Channel; +//import com.sprint.mission.discodeit.entity.ChannelType; +//import com.sprint.mission.discodeit.entity.Message; +//import com.sprint.mission.discodeit.entity.User; +//import com.sprint.mission.discodeit.repository.ChannelRepository; +//import com.sprint.mission.discodeit.repository.MessageRepository; +//import com.sprint.mission.discodeit.repository.UserRepository; +//import com.sprint.mission.discodeit.repository.file.FileChannelRepository; +//import com.sprint.mission.discodeit.repository.file.FileMessageRepository; +//import com.sprint.mission.discodeit.repository.file.FileUserRepository; +//import com.sprint.mission.discodeit.service.ChannelService; +//import com.sprint.mission.discodeit.service.MessageService; +//import com.sprint.mission.discodeit.service.UserService; +//import com.sprint.mission.discodeit.service.basic.BasicChannelService; +//import com.sprint.mission.discodeit.service.basic.BasicMessageService; +//import com.sprint.mission.discodeit.service.basic.BasicUserService; +// +//public class JavaApplication { +// public static void main(String[] args) { +// // 레포지토리 초기화 +// UserRepository userRepository = new FileUserRepository(); +// ChannelRepository channelRepository = new FileChannelRepository(); +// MessageRepository messageRepository = new FileMessageRepository(); +// +// // 서비스 초기화 +// UserService userService = new BasicUserService(userRepository); +// ChannelService channelService = new BasicChannelService(channelRepository); +// MessageService messageService = new BasicMessageService(messageRepository, channelRepository, userRepository); +// +// // 셋업 +// User user = setupUser(userService); +// Channel channel = setupChannel(channelService); +// // 테스트 +// messageCreateTest(messageService, channel, user); +// } +//} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java new file mode 100644 index 000000000..2cc77bd3f --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.dto; + +public class AuthDto { + public record LoginRequest( + String username, + String password + ) {} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java new file mode 100644 index 000000000..a4e0808f6 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java @@ -0,0 +1,19 @@ +package com.sprint.mission.discodeit.dto; + +import java.util.UUID; + +public class BinaryContentDto { + public record CreateRequest( + byte[] bytes, + String contentType, + String fileName + ){} + + public record Response( + UUID id, + byte[] bytes, + String contentType, + String fileName + ){} + +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java new file mode 100644 index 000000000..3bee70451 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java @@ -0,0 +1,34 @@ +package com.sprint.mission.discodeit.dto; + +import com.sprint.mission.discodeit.entity.ChannelType; +import lombok.Builder; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +public class ChannelDto { + public record CreateRequest( + ChannelType type, + String name, + String description + ) {} + + + @Builder + public record Response( + UUID id, + ChannelType type, + String name, + String description, + Instant lastMessageAt, + List memberIdList + + ) {} + + public record UpdateRequest ( + UUID channelId, + String newName, + String newDescription + ) {} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java new file mode 100644 index 000000000..7f9e9b132 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java @@ -0,0 +1,37 @@ +package com.sprint.mission.discodeit.dto; + +import lombok.Builder; + +import java.util.List; +import java.util.UUID; + +public class MessageDto { + public record CreateRequest( + String content, + UUID channelId, + UUID authorId, + List attachments // 첨부파일(선택) + ) {} + + public record BinaryContentDto ( + byte[] data, + String fileType, + String fileName + ) {} + + + @Builder + public record Response( + UUID id, + UUID channelId, + UUID authorId, + String content, + List attachmentId + ) {} + + public record UpdateRequest ( + UUID id, + String content, + List attachments + ) {} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java new file mode 100644 index 000000000..4f27dd7b8 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java @@ -0,0 +1,25 @@ +package com.sprint.mission.discodeit.dto; + +import java.time.Instant; +import java.util.UUID; + +public class ReadStatusDto { + public record CreateRequest( + UUID userId, + UUID channelId + ){} + + public record Response( + UUID id, + UUID userId, + UUID channelId, + UUID lastMessageReadId, + Instant updatedAt + + ){} + + public record UpdateRequest( + UUID id, + UUID lastReadMessageId + ){} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java new file mode 100644 index 000000000..4d751b2d4 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java @@ -0,0 +1,37 @@ +package com.sprint.mission.discodeit.dto; + +import lombok.Builder; + +import java.util.UUID; + +public class UserDto { + public record CreateRequest( + String username, + String email, + String password, + BinaryContentDto profileImage + ) {} + + public record BinaryContentDto ( + byte[] data, + String fileType, + String fileName + ) {} + + @Builder + public record Response( + UUID id, + String username, + String email, + boolean isOnline, + UUID profileImageId + ) {} + + public record UpdateRequest ( + UUID id, + String username, + String email, + String password, + BinaryContentDto profileImage + ) {} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java b/.github/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java new file mode 100644 index 000000000..c7a160b39 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.dto; + +import java.time.Instant; +import java.util.UUID; + +public class UserStatusDto { + public record CreateRequest( + UUID userId + ){} + + public record Response( + UUID id, + UUID userId, + Instant updatedAt, + boolean isOnline + ){} + + public record UpdateRequest( + UUID id, + UUID userId + ){} +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java new file mode 100644 index 000000000..ac38adefc --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java @@ -0,0 +1,26 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.AllArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; +import java.time.Instant; +import java.util.UUID; + +@SuperBuilder +@AllArgsConstructor +public abstract class BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + protected final UUID id; + protected final Instant createdAt; + + public BaseEntity() { + this.id = UUID.randomUUID(); + this.createdAt = Instant.now(); + } + + // 공통 Getter + public UUID getId() { return id; } + public Instant getCreatedAt() { return createdAt; } + +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java new file mode 100644 index 000000000..5989e3fb3 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java @@ -0,0 +1,20 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.UUID; + +@Getter +public class BinaryContent extends BaseEntity{ + private final byte[] bytes; + private final String contentType; // 예: image/png, application/pdf + private final String fileName; + + public BinaryContent(byte[] bytes, String contentType, String fileName) { + super(); + this.bytes = bytes; + this.contentType = contentType; + this.fileName = fileName; + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/Channel.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/Channel.java new file mode 100644 index 000000000..ea4ac80a7 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/Channel.java @@ -0,0 +1,40 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.UUID; + +@Getter +@Setter +public class Channel extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + private Instant updatedAt; + private ChannelType type; + private String name; + private String description; + + public Channel(ChannelType type, String name, String description) { + this.type = type; + this.name = name; + this.description = description; + } + + public void update(String newName, String newDescription) { + boolean anyValueUpdated = false; + if (newName != null && !newName.equals(this.name)) { + this.name = newName; + anyValueUpdated = true; + } + if (newDescription != null && !newDescription.equals(this.description)) { + this.description = newDescription; + anyValueUpdated = true; + } + + if (anyValueUpdated) { + this.updatedAt = Instant.now(); + } + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java new file mode 100644 index 000000000..9a2ff3f0f --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java @@ -0,0 +1,6 @@ +package com.sprint.mission.discodeit.entity; + +public enum ChannelType { + PUBLIC, + PRIVATE, +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/Message.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/Message.java new file mode 100644 index 000000000..61ce1d783 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/Message.java @@ -0,0 +1,56 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Builder +@AllArgsConstructor +public class Message extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + private Instant updatedAt; + // + private String content; + // + private UUID channelId; + private UUID authorId; + private List attachmentIds; + + public Message(String content, UUID channelId, UUID authorId, List attachmentId) { + this.content = content; + this.channelId = channelId; + this.authorId = authorId; + this.attachmentIds = (attachmentId!= null) ? attachmentId: new ArrayList<>(); // 내용을 받지 않았을 때에는 빈 리스트로 초기화 + } + + public void update(String newContent, List newAttachmentIds) { + boolean[] isUpdated = {false}; + + Optional.ofNullable(newContent) + .filter(c -> !c.equals(this.content)) + .ifPresent(c -> { + this.content = c; + isUpdated[0] = true; + }); + + Optional.ofNullable(newAttachmentIds) + .filter(ids -> !ids.equals(this.attachmentIds)) + .ifPresent(ids -> { + this.attachmentIds = ids; + isUpdated[0] = true; + }); + + // 하나라도 바뀌었으면 시간 갱신 + if (isUpdated[0]) { + this.updatedAt = Instant.now(); + } + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java new file mode 100644 index 000000000..eb84ecea4 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java @@ -0,0 +1,29 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Getter +public class ReadStatus extends BaseEntity { + private final UUID userId; + private final UUID channelId; + private Instant updatedAt; + private UUID lastReadMessageId; + + public ReadStatus(UUID userId, UUID channelId) { + this.userId = userId; + this.channelId = channelId; + } + + public void updateLastReadMessage(UUID lastReadMessageId) { + Optional.ofNullable(lastReadMessageId) + .filter(id -> !id.equals(this.lastReadMessageId)) + .ifPresent(id -> { + this.lastReadMessageId = id; + this.updatedAt = Instant.now(); + }); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/User.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/User.java new file mode 100644 index 000000000..d64313e2c --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/User.java @@ -0,0 +1,35 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +public class User extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + + private Instant updatedAt; + + private String username; + private String email; + private String password; + + private UUID profileImageId; + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } + + public void update(String newUsername, String newEmail, String newPassword) { + Optional.ofNullable(newUsername).filter(s -> !s.isBlank()).ifPresent(this::setUsername); + Optional.ofNullable(newEmail).filter(s -> !s.isBlank()).ifPresent(this::setEmail); + Optional.ofNullable(newPassword).filter(s -> !s.isBlank()).ifPresent(this::setPassword); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java b/.github/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java new file mode 100644 index 000000000..4991751c7 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java @@ -0,0 +1,27 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.UUID; + +@Getter +public class UserStatus extends BaseEntity { + private final UUID userId; + private Instant updatedAt; + + public UserStatus(UUID userId) { + this.userId = userId; + } + + // 5분 이내 접속 여부 판단 메소드 + public boolean isOnline() { + // 현재 시간 - 5분(300초) 이후 접속했다면 true + return updatedAt.isAfter(Instant.now().minusSeconds(300)); + } + + public void updateActiveTime() { + this.updatedAt = Instant.now(); + } + +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java new file mode 100644 index 000000000..03c42f422 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java @@ -0,0 +1,21 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface BinaryContentRepository { + BinaryContent save(BinaryContent binaryContent); + + Optional findById(UUID id); + List findAllById(List idList); + + boolean existsById(UUID id); + + void deleteById(UUID id); + void deleteAllById(List idList); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java new file mode 100644 index 000000000..c8b9faa77 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java @@ -0,0 +1,17 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ChannelRepository { + Channel save(Channel channel); + Optional findById(UUID id); + List findAll(); + boolean existsById(UUID id); + void deleteById(UUID id); + List findAllByType(ChannelType channelType); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java new file mode 100644 index 000000000..a34ce4f0c --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Message; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface MessageRepository { + Message save(Message message); + + Optional findById(UUID id); + Optional findLastMessageOfChannel(UUID channelId); + + List findAll(); + List findAllByChannelId(UUID channelId); + + boolean existsById(UUID id); + + void deleteById(UUID id); + void deleteAllByChannelId(UUID channelId); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java new file mode 100644 index 000000000..29b6bb9f1 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java @@ -0,0 +1,27 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.UserStatus; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ReadStatusRepository { + ReadStatus save(ReadStatus readStatus); + Optional findById(UUID id); + + Optional findByUserAndChannelId(UUID userId, UUID channelId); + + void deleteById(UUID id); + void deleteAllByChannelId(UUID channelId); + public List findAll(); + + List findAllByChannelId(UUID channelId); + List findAllByUserId(UUID userId); + + boolean existsByChannelIdAndUserId(UUID channelId, UUID userId); + boolean existsById(UUID id); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java new file mode 100644 index 000000000..867ab1bf5 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java @@ -0,0 +1,20 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.User; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface UserRepository { + User save(User user); + Optional findById(UUID id); + List findAll(); + boolean existsById(UUID id); + void deleteById(UUID id); + + boolean usernameExistence(String username); + boolean emailExistence(String email); + + Optional findByUsername(String username); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java new file mode 100644 index 000000000..cae485b96 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.UserStatus; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserStatusRepository { + UserStatus save(UserStatus userStatus); + + boolean existsByUserId(UUID userId); + boolean existsById(UUID id); + + Optional findById(UUID id); + Optional findByUserId(UUID userId); + List findAll(); + + void deleteById(UUID id); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java new file mode 100644 index 000000000..eb01deaa9 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java @@ -0,0 +1,117 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileBinaryContentRepository implements BinaryContentRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileBinaryContentRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", BinaryContent.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public BinaryContent save(BinaryContent binaryContent) { + Path path = resolvePath(binaryContent.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(binaryContent); + } catch (IOException e) { + throw new RuntimeException(e); + } + return binaryContent; + } + + @Override + public Optional findById(UUID id) { + BinaryContent binaryContentNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + binaryContentNullable = (BinaryContent) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(binaryContentNullable); + } + + @Override + public List findAllById(List idList) { + return idList.stream() + .map(this::resolvePath) + .filter(Files::exists) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (BinaryContent) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllById(List idList) { + idList.forEach(id -> { + Path path = resolvePath(id); + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new RuntimeException("파일 삭제 실패: " + id, e); + } + }); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java new file mode 100644 index 000000000..90ef855e9 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java @@ -0,0 +1,116 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileChannelRepository implements ChannelRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileChannelRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", Channel.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public Channel save(Channel channel) { + Path path = resolvePath(channel.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(channel); + } catch (IOException e) { + throw new RuntimeException(e); + } + return channel; + } + + @Override + public Optional findById(UUID id) { + Channel channelNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + channelNullable = (Channel) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(channelNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Channel) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByType(ChannelType channelType) { + return findAll().stream() + .filter(channel -> channel.getType().equals(channelType)) + .toList(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java new file mode 100644 index 000000000..9c2c40342 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java @@ -0,0 +1,165 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.MessageRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileMessageRepository implements MessageRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileMessageRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", Message.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public Message save(Message message) { + Path path = resolvePath(message.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(message); + } catch (IOException e) { + throw new RuntimeException(e); + } + return message; + } + + @Override + public Optional findById(UUID id) { + Message messageNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + messageNullable = (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(messageNullable); + } + + @Override + public Optional findLastMessageOfChannel(UUID channelId) { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .filter(message -> message.getChannelId().equals(channelId)) + .sorted(Comparator.comparing(Message::getCreatedAt).reversed()) + .findFirst(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + try { + Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .forEach(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Message message = (Message) ois.readObject(); + if (message.getChannelId().equals(channelId)) { + ois.close(); + fis.close(); + Files.delete(path); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("파일 삭제 중 오류 발생: " + path, e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java new file mode 100644 index 000000000..b1860eac9 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java @@ -0,0 +1,162 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileReadStatusRepository implements ReadStatusRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileReadStatusRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", ReadStatus.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public ReadStatus save(ReadStatus readStatus) { + Path path = resolvePath(readStatus.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(readStatus); + } catch (IOException e) { + throw new RuntimeException(e); + } + return readStatus; + } + + @Override + public Optional findById(UUID id) { + ReadStatus readStatusNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + readStatusNullable = (ReadStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(readStatusNullable); + } + + @Override + public Optional findByUserAndChannelId(UUID userId, UUID channelId) { + return findAll().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId) && readStatus.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + try { + Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .forEach(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Message message = (Message) ois.readObject(); + if (message.getChannelId().equals(channelId)) { + ois.close(); + fis.close(); + Files.delete(path); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("파일 삭제 중 오류 발생: " + path, e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (ReadStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public List findAllByUserId(UUID userId) { + return findAll().stream() + .filter(readStatus -> readStatus.getChannelId().equals(userId)) + .toList(); + } + + @Override + public boolean existsByChannelIdAndUserId(UUID channelId, UUID userId) { + return findByUserAndChannelId(userId, channelId).isPresent(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + +} \ No newline at end of file diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java new file mode 100644 index 000000000..42618daa0 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java @@ -0,0 +1,127 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileUserRepository implements UserRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileUserRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", User.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public User save(User user) { + Path path = resolvePath(user.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(user); + } catch (IOException e) { + throw new RuntimeException(e); + } + return user; + } + + @Override + public Optional findById(UUID id) { + User userNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userNullable = (User) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (User) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean usernameExistence(String username) { + return findAll().stream() + .anyMatch(user -> user.getUsername().equals(username)); + } + + @Override + public boolean emailExistence(String email) { + return findAll().stream() + .anyMatch(user -> user.getEmail().equals(email)); + } + + @Override + public Optional findByUsername(String username) { + return findAll().stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java new file mode 100644 index 000000000..5762b2e68 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java @@ -0,0 +1,133 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileUserStatusRepository implements UserStatusRepository { + + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileUserStatusRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", UserStatus.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public UserStatus save(UserStatus userStatus) { + Path path = resolvePath(userStatus.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(userStatus); + } catch (IOException e) { + throw new RuntimeException(e); + } + return userStatus; + } + + @Override + public boolean existsByUserId(UUID userId) { + Path path = resolvePath(userId); + return Files.exists(path); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public Optional findById(UUID id) { + UserStatus userStatusNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userStatusNullable = (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userStatusNullable); + } + + @Override + public Optional findByUserId(UUID userId) { + UserStatus userStatusNullable = null; + Path path = resolvePath(userId); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userStatusNullable = (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userStatusNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java new file mode 100644 index 000000000..d6e5b4a2a --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java @@ -0,0 +1,55 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFBinaryContentRepository implements BinaryContentRepository { + private final Map data; + + public JCFBinaryContentRepository() { + this.data = new HashMap<>(); + } + + @Override + public BinaryContent save(BinaryContent binaryContent) { + this.data.put(binaryContent.getId(), binaryContent); + return binaryContent; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAllById(List idList) { + return data.values().stream() + .filter(binaryContent -> idList.contains(binaryContent.getId())) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllById(List idList) { + data.values().removeIf(binaryContent -> idList.contains(binaryContent.getId())); + + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java new file mode 100644 index 000000000..785fac572 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java @@ -0,0 +1,55 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFChannelRepository implements ChannelRepository { + private final Map data; + + public JCFChannelRepository() { + this.data = new HashMap<>(); + } + + @Override + public Channel save(Channel channel) { + this.data.put(channel.getId(), channel); + return channel; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public List findAllByType(ChannelType channelType) { + return data.values().stream() + .filter(channel -> channel.getType().equals(channelType)) + .toList(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java new file mode 100644 index 000000000..8f28c548f --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java @@ -0,0 +1,67 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.MessageRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFMessageRepository implements MessageRepository { + private final Map data; + + public JCFMessageRepository() { + this.data = new HashMap<>(); + } + + @Override + public Message save(Message message) { + this.data.put(message.getId(), message); + return message; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findLastMessageOfChannel(UUID channelId) { + return data.values().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .sorted(Comparator.comparing(Message::getCreatedAt).reversed()) + .findFirst(); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.values().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + data.values().removeIf(message -> message.getChannelId().equals(channelId)); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java new file mode 100644 index 000000000..03db7eaec --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java @@ -0,0 +1,82 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFReadStatusRepository implements ReadStatusRepository { + + private final Map data; + public JCFReadStatusRepository() { + this.data = new HashMap<>(); + } + + @Override + public ReadStatus save(ReadStatus readStatus) { + this.data.put(readStatus.getId(), readStatus); + return readStatus; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findByUserAndChannelId(UUID userId, UUID channelId) { + return data.values().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId)) + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + data.values().removeIf(readStatus -> readStatus.getChannelId().equals(channelId)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.values().stream() + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public List findAllByUserId(UUID userId) { + return data.values().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId)) + .toList(); + } + + @Override + public boolean existsByChannelIdAndUserId(UUID channelId, UUID userId) { + return data.values().stream() + .anyMatch(readStatus -> + readStatus.getUserId().equals(userId) && readStatus.getChannelId().equals(channelId) + ); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java new file mode 100644 index 000000000..3264d1e9d --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java @@ -0,0 +1,66 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFUserRepository implements UserRepository { + private final Map data; + + public JCFUserRepository() { + this.data = new HashMap<>(); + } + + @Override + public User save(User user) { + this.data.put(user.getId(), user); + return user; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public boolean usernameExistence(String username) { + return data.values().stream() + .anyMatch(user -> user.getUsername().equals(username)); + } + + @Override + public boolean emailExistence(String email) { + return data.values().stream() + .anyMatch(user -> user.getEmail().equals(email)); + } + + @Override + public Optional findByUsername(String username) { + return data.values().stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java new file mode 100644 index 000000000..cfc8df1ef --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java @@ -0,0 +1,61 @@ +package com.sprint.mission.discodeit.repository.jcf; + + +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserStatusService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFUserStatusRepository implements UserStatusRepository { + private final Map data; + + public JCFUserStatusRepository() { + this.data = new HashMap<>(); + } + + + @Override + public UserStatus save(UserStatus userStatus) { + this.data.put(userStatus.getId(), userStatus); + return userStatus; + } + + @Override + public boolean existsByUserId(UUID userId) { + return this.data.containsKey(userId); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findByUserId(UUID userId) { + return Optional.ofNullable(this.data.get(userId)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/AuthService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/AuthService.java new file mode 100644 index 000000000..d68baee7f --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/AuthService.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.AuthDto; +import com.sprint.mission.discodeit.dto.UserDto; + +public interface AuthService { + UserDto.Response login(AuthDto.LoginRequest request); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java new file mode 100644 index 000000000..6a1341f0f --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.BinaryContentDto; +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; + +import java.util.List; +import java.util.UUID; + +public interface BinaryContentService { + BinaryContentDto.Response create(BinaryContentDto.CreateRequest request); + BinaryContentDto.Response find(UUID userId); + List findAllByIdIn(List idList); + void delete(UUID id); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java new file mode 100644 index 000000000..af357ecd5 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java @@ -0,0 +1,16 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ChannelDto; +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; + +import java.util.List; +import java.util.UUID; + +public interface ChannelService { + ChannelDto.Response create(ChannelDto.CreateRequest request); + ChannelDto.Response find(UUID channelId); + List findAllByUserId(UUID userId); + ChannelDto.Response update(ChannelDto.UpdateRequest request); + void delete(UUID channelId); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/MessageService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/MessageService.java new file mode 100644 index 000000000..723f49053 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/MessageService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.MessageDto; +import com.sprint.mission.discodeit.entity.Message; + +import java.util.List; +import java.util.UUID; + +public interface MessageService { + MessageDto.Response create(MessageDto.CreateRequest request); + List findAllByChannelId(UUID channelId); +// List findAll(); + MessageDto.Response update(MessageDto.UpdateRequest request); + void delete(UUID messageId); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java new file mode 100644 index 000000000..a02957986 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java @@ -0,0 +1,18 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.User; +import lombok.Locked; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ReadStatusService { + ReadStatusDto.Response create(ReadStatusDto.CreateRequest request); + ReadStatusDto.Response find(UUID id); + List findAllByUserId(UUID userId); + ReadStatusDto.Response update(ReadStatusDto.UpdateRequest request); + void delete(UUID id); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/UserService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/UserService.java new file mode 100644 index 000000000..3186f3ffb --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/UserService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.User; + +import java.util.List; +import java.util.UUID; + +public interface UserService { + UserDto.Response create(UserDto.CreateRequest request); + UserDto.Response find(UUID userId); + List findAll(); + UserDto.Response update(UserDto.UpdateRequest request); + void delete(UUID userId); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java new file mode 100644 index 000000000..ff497c9a0 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java @@ -0,0 +1,16 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; + +import java.util.List; +import java.util.UUID; + +public interface UserStatusService { + UserStatusDto.Response create(UserStatusDto.CreateRequest request); + UserStatusDto.Response find(UUID id); + List findAll(); + UserStatusDto.Response update(UserStatusDto.UpdateRequest request); + UserStatusDto.Response updateByUserId(UserStatusDto.UpdateRequest request); + void delete(UUID id); +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java new file mode 100644 index 000000000..0c9010a4d --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java @@ -0,0 +1,39 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.AuthDto; +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.AuthService; +import org.springframework.stereotype.Service; + +import java.util.NoSuchElementException; + +@Service +public class BasicAuthService implements AuthService { + + private final UserRepository userRepository; + + public BasicAuthService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDto.Response login(AuthDto.LoginRequest request) { + // username: 유저 존재 확인 + User user = userRepository.findByUsername(request.username()) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 유저입니다.")); + + // password: 비밀번호 일치 여부 확인 + if (!user.getPassword().equals(request.password())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .build(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java new file mode 100644 index 000000000..7997973d9 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java @@ -0,0 +1,59 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.BinaryContentDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.service.BinaryContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicBinaryContentService implements BinaryContentService { + private final BinaryContentRepository binaryContentRepository; + + @Override + public BinaryContentDto.Response create(BinaryContentDto.CreateRequest request) { + BinaryContent content = new BinaryContent( + request.bytes(), + request.contentType(), + request.fileName() + ); + return convertToResponse(binaryContentRepository.save(content)); + } + + @Override + public BinaryContentDto.Response find(UUID userId) { + return binaryContentRepository.findById(userId) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 파일이 존재하지 않습니다.")); + } + + @Override + public List findAllByIdIn(List idList) { + return binaryContentRepository.findAllById(idList).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public void delete(UUID id) { + if (!binaryContentRepository.existsById(id)) { + throw new NoSuchElementException("해당 ID의 파일이 존재하지 않습니다."); + } + binaryContentRepository.deleteById(id); + } + + private BinaryContentDto.Response convertToResponse(BinaryContent content) { + return new BinaryContentDto.Response( + content.getId(), + content.getBytes(), + content.getContentType(), + content.getFileName() + ); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java new file mode 100644 index 000000000..d650f962e --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java @@ -0,0 +1,124 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ChannelDto; +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.service.ChannelService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.validation.MessageCodesResolver; + +import java.time.Instant; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class BasicChannelService implements ChannelService { + private final ChannelRepository channelRepository; + private final ReadStatusRepository readStatusRepository; + private final MessageRepository messageRepository; + + + @Override + public ChannelDto.Response create(ChannelDto.CreateRequest request) { + // PUBLIC 채널 생성 시 기존 로직 유지, PRIVATE 채널 생성 시 생략 + String name = (request.type() == ChannelType.PUBLIC) ? request.name() : null; + String description = (request.type() == ChannelType.PUBLIC) ? request.description() : null; + + Channel channel = new Channel(request.type(), name, description); + channelRepository.save(channel); + + return convertToResponse(channel, null, null); // 초기 생성 시 최근 메시지, 유저 목록 없음 + } + + + @Override + public ChannelDto.Response find(UUID channelId) { + Channel channel = channelRepository.findById(channelId) + .orElseThrow(() -> new NoSuchElementException("Channel with id " + channelId + " not found")); + + // 해당 채널의 가장 최근 메시지의 시간 정보 + Instant lastTime = messageRepository.findLastMessageOfChannel(channelId) + .map(Message::getCreatedAt).orElse(null); + + // PRIVATE 채널의 경우 참여한 유저의 id 포함 + List memberList = null; + if (channel.getType() == ChannelType.PRIVATE) { + memberList = readStatusRepository.findAllByChannelId(channelId).stream() + .map(ReadStatus::getUserId) + .toList(); + } + + return convertToResponse(channel, lastTime, memberList); + + } + + @Override + public List findAllByUserId (UUID userId){ + // PUBLIC 채널: 전체 조회 + List publicChannels = channelRepository.findAllByType(ChannelType.PUBLIC); + + // PRIVATE: 유저가 참여한 채널만 조회 + List myPrivateChannels = readStatusRepository.findAllByUserId(userId).stream() + .map(readStatus -> channelRepository.findById(readStatus.getChannelId()).orElse(null)) + .filter(Objects::nonNull) + .filter(channel -> channel.getType() == ChannelType.PRIVATE) + .toList(); + + // 참여한 PUBLIC과 PRIVATE 채널 합치기: 특정 유저가 볼 수 있는 채널 목록 조회 가능 + List myAvailableChannelList = new ArrayList<>(); + myAvailableChannelList.addAll(publicChannels); + myAvailableChannelList.addAll(myPrivateChannels); + + // find() 재사용: 최근 메시지 시간 정보, PRIVATE 채널의 유저 Id 정보 포함 + return myAvailableChannelList.stream() + .map(channel -> find(channel.getId())) + .toList(); + } + + @Override + public ChannelDto.Response update (ChannelDto.UpdateRequest request) { + Channel channel = channelRepository.findById(request.channelId()) + .orElseThrow(() -> new NoSuchElementException("Channel with id " + request.channelId() + " not found")); + + // PRIVATE 채널은 수정 불가 + if (channel.getType() == ChannelType.PRIVATE) { + throw new IllegalStateException("PRIVATE 채널은 수정할 수 없습니다."); + } + + channel.setName(request.newName()); + channel.setDescription(request.newDescription()); + + channelRepository.save(channel); + return convertToResponse(channel, null, null); + } + + @Override + public void delete (UUID channelId) { + Channel channel = channelRepository.findById(channelId).orElseThrow(); + + // 관련 도메인(Message, ReadStatus) 삭제 + messageRepository.deleteAllByChannelId(channelId); + readStatusRepository.deleteAllByChannelId(channelId); + + channelRepository.deleteById(channelId); + } + + private ChannelDto.Response convertToResponse (Channel channel, Instant lastMessageTime, List memberId) { + return ChannelDto.Response.builder() + .id(channel.getId()) + .name(channel.getName()) + .lastMessageAt(lastMessageTime) + .memberIdList(memberId) + .build(); + } + } + + + + diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java new file mode 100644 index 000000000..87985e10d --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java @@ -0,0 +1,111 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.MessageDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +public class BasicMessageService implements MessageService { + private final MessageRepository messageRepository; + private final ChannelRepository channelRepository; + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + + @Override + public MessageDto.Response create(MessageDto.CreateRequest request) { + + List attachmentIds = new ArrayList<>(); + + if (request.attachments() != null && !request.attachments().isEmpty()) { + attachmentIds = request.attachments().stream() + .map(fileRequest -> { + BinaryContent content = new BinaryContent( + fileRequest.data(), + fileRequest.fileType(), + fileRequest.fileName() + ); + return binaryContentRepository.save(content).getId(); + }) + .toList(); + } + + Message message = new Message( + request.content(), + request.channelId(), + request.authorId(), + attachmentIds + ); + + messageRepository.save(message); + + return convertToResponse(message); + + } + + @Override + public List findAllByChannelId(UUID channelId) { + return messageRepository.findAllByChannelId(channelId).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public MessageDto.Response update(MessageDto.UpdateRequest request) { + Message message = messageRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("Message with id " + request.id() + " not found")); + + List newIds = Optional.ofNullable(request.attachments()) + .filter(attachments -> !attachments.isEmpty()) + .map(attachments -> attachments.stream() + .map(dto -> { + BinaryContent content = new BinaryContent( + dto.data(), + dto.fileType(), + dto.fileName() + ); + return binaryContentRepository.save(content).getId(); + }) + .toList() + ) + .orElse(null); + message.update(request.content(), newIds); + + return convertToResponse(messageRepository.save(message)); + + } + + + @Override + public void delete(UUID id) { + Message message = messageRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("Message with id " + id + " not found")); + + // 첨부파일 삭제 + if (message.getAttachmentIds() != null && !message.getAttachmentIds().isEmpty()) { + binaryContentRepository.deleteAllById(message.getAttachmentIds()); + } + + messageRepository.deleteById(id); + + } + + private MessageDto.Response convertToResponse(Message message) { + return MessageDto.Response.builder() + .id(message.getId()) + .channelId(message.getChannelId()) + .authorId(message.getAuthorId()) + .content(message.getContent()) + .attachmentId(message.getAttachmentIds()) + .build(); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java new file mode 100644 index 000000000..cc82371e1 --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java @@ -0,0 +1,87 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.ReadStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicReadStatusService implements ReadStatusService { + private final ReadStatusRepository readStatusRepository; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + + @Override + public ReadStatusDto.Response create(ReadStatusDto.CreateRequest request) { + // 관련된 채널이나 유저가 존재하지 않을 때 예외 발생 + channelRepository.findById(request.channelId()) + .orElseThrow(() -> new NoSuchElementException("채널이 존재하지 않습니다.")); + + userRepository.findById(request.userId()) + .orElseThrow(() -> new NoSuchElementException("유저가 존재하지 않습니다.")); + + // 같은 채널과 유저와 관련된 객체가 이미 존재하면 예외 발생 + if(readStatusRepository.existsByChannelIdAndUserId(request.channelId(), request.userId())) { + throw new IllegalStateException("이미 채널과 유저의 상태가 존재합니다."); + } + + ReadStatus readStatus = new ReadStatus(request.channelId(), request.userId()); + return convertToResponse(readStatusRepository.save(readStatus)); + + } + + @Override + public ReadStatusDto.Response find(UUID id) { + return readStatusRepository.findById(id) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + } + + @Override + public List findAllByUserId(UUID userId) { + return readStatusRepository.findAllByUserId(userId).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public ReadStatusDto.Response update(ReadStatusDto.UpdateRequest request) { + ReadStatus readStatus = readStatusRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + + Optional.ofNullable(request.lastReadMessageId()) + .ifPresent(readStatus::updateLastReadMessage); + + return convertToResponse(readStatusRepository.save(readStatus)); + } + + @Override + public void delete(UUID id) { + if(!readStatusRepository.existsById(id)) { + throw new NoSuchElementException("존재하지 않는 Id 입니다."); + } + readStatusRepository.deleteById(id); + + } + private ReadStatusDto.Response convertToResponse(ReadStatus readStatus) { + return new ReadStatusDto.Response( + readStatus.getId(), + readStatus.getUserId(), + readStatus.getChannelId(), + readStatus.getLastReadMessageId(), + readStatus.getUpdatedAt() + ); + } + +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java new file mode 100644 index 000000000..ee70a6eaa --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java @@ -0,0 +1,137 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicUserService implements UserService { + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + private final UserStatusRepository userStatusRepository; + + @Override + public UserDto.Response create(UserDto.CreateRequest request) { + // 프로필 이미지 처리(선택적) + UUID imageId = null; + if (request.profileImage() != null) { + BinaryContent image = new BinaryContent( + request.profileImage().data(), + request.profileImage().fileType(), + request.profileImage().fileName()); + imageId = binaryContentRepository.save(image).getId(); + } + + // 중복 검증 + if (userRepository.usernameExistence(request.username())) throw new RuntimeException("이미 존재하는 이름입니다."); + if (userRepository.emailExistence(request.email())) throw new RuntimeException("이미 존재하는 이메일입니다."); + + // 유저 등록 + User user = new User(request.username(), request.email(), request.password()); + if (imageId != null) { + user.setProfileImageId(imageId); + } + User savedUser = userRepository.save(user); + userStatusRepository.save(new UserStatus(savedUser.getId())); + + return UserDto.Response.builder() + .id(savedUser.getId()) + .username(savedUser.getUsername()) + .email(savedUser.getEmail()) + .isOnline(true) + .profileImageId(savedUser.getProfileImageId()) + .build(); + + + } + + @Override + public UserDto.Response find(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User with id " + userId + " not found")); + + // 유저 온라인 상태 정보 포함(패스워드 제외) + boolean isOnline = userStatusRepository.findByUserId(userId) + .map(UserStatus::isOnline) + .orElse(false); + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .isOnline(isOnline) + .build(); + } + + @Override + public List findAll() { + return userRepository.findAll().stream() + .map(user -> { + boolean isOnline = userStatusRepository.findByUserId(user.getId()) + .map(UserStatus::isOnline) + .orElse(false); + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .isOnline(isOnline) + .build(); + }) + .toList(); + } + + @Override + public UserDto.Response update(UserDto.UpdateRequest updateRequest) { + User user = userRepository.findById(updateRequest.id()) + .orElseThrow(() -> new NoSuchElementException("User with id " + updateRequest.id()+ " not found")); + + // 프로필 이미지 대체 (선택) + Optional.ofNullable(updateRequest.profileImage()) + .ifPresent(imageDto -> { + if (user.getProfileImageId() != null) { + binaryContentRepository.deleteById(user.getProfileImageId()); + } + BinaryContent newImage = new BinaryContent( + imageDto.data(), imageDto.fileType(), imageDto.fileName() + ); + user.setProfileImageId(binaryContentRepository.save(newImage).getId()); + }); + + user.update(updateRequest.username(), updateRequest.email(), updateRequest.password()); + userRepository.save(user); + return find(user.getId()); + } + + @Override + public void delete(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User not found with id: " + userId)); + + // BinaryContent 삭제 + if (user.getProfileImageId() != null) { + binaryContentRepository.deleteById(user.getProfileImageId()); + } + + // UserStatus 삭제 + userStatusRepository.deleteById(userId); + + // 유저 삭제 + userRepository.deleteById(userId); + } +} diff --git a/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java new file mode 100644 index 000000000..eda29109c --- /dev/null +++ b/.github/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java @@ -0,0 +1,93 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicUserStatusService implements UserStatusService { + + private final UserStatusRepository userStatusRepository; + private final UserRepository userRepository; + + + @Override + public UserStatusDto.Response create(UserStatusDto.CreateRequest request) { + // 관련된 유저 존재 X + userRepository.findById(request.userId()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 유저가 존재하지 않습니다.")); + + // 같은 유저와 관련된 객체가 이미 존재 + if (userStatusRepository.existsByUserId(request.userId())) { + throw new IllegalStateException("해당 ID의 상태가 이미 존재합니다."); + } + + UserStatus userStatus = new UserStatus( + request.userId() + ); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public UserStatusDto.Response find(UUID id) { + return userStatusRepository.findById(id) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + } + + @Override + public List findAll() { + return userStatusRepository.findAll().stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public UserStatusDto.Response update(UserStatusDto.UpdateRequest request) { + UserStatus userStatus = userStatusRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + + userStatus.updateActiveTime(); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public UserStatusDto.Response updateByUserId(UserStatusDto.UpdateRequest request) { + UserStatus userStatus = userStatusRepository.findByUserId(request.userId()) + .orElseThrow(() -> new NoSuchElementException("해당 유저의 상태 정보가 존재하지 않습니다.")); + + userStatus.updateActiveTime(); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public void delete(UUID id) { + if (!userStatusRepository.existsById(id)) { + throw new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다."); + } + userStatusRepository.deleteById(id); + + } + + private UserStatusDto.Response convertToResponse(UserStatus userStatus) { + return new UserStatusDto.Response( + userStatus.getId(), + userStatus.getUserId(), + userStatus.getUpdatedAt(), + userStatus.isOnline() + ); + } +} diff --git a/.github/src/main/resources/application.yaml b/.github/src/main/resources/application.yaml new file mode 100644 index 000000000..4fe0afb2e --- /dev/null +++ b/.github/src/main/resources/application.yaml @@ -0,0 +1,4 @@ +discodeit: + repository: + type: jcf + file-directory: .discodeit diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1fac4d56a --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..987919843 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 쿼리 파일을 포함한 무시된 디폴트 폴더 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..e07238326 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +0-sprint-mission \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..a55e7a179 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..2a65317ef --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..913a8d6a7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..288b36b1e --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/HELP.md b/HELP.md new file mode 100644 index 000000000..1337c63bd --- /dev/null +++ b/HELP.md @@ -0,0 +1,25 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Gradle documentation](https://docs.gradle.org) +* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.5.10/gradle-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.5.10/gradle-plugin/packaging-oci-image.html) +* [Spring Web](https://docs.spring.io/spring-boot/3.5.10/reference/web/servlet.html) + +### Guides + +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) + +### Additional Links + +These additional references should also help you: + +* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) + diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..973cc4853 --- /dev/null +++ b/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'java' +} + +group = 'org.example' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..249e5832f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..04ff3654e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jan 09 10:50:41 KST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..2b0b99ee7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = '10-sprint-mission' \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java new file mode 100644 index 000000000..5cb5983c1 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -0,0 +1,51 @@ +package com.sprint.mission.discodeit;//package com.sprint.mission.discodeit; +// +//import com.sprint.mission.discodeit.entity.Channel; +//import com.sprint.mission.discodeit.entity.ChannelType; +//import com.sprint.mission.discodeit.entity.Message; +//import com.sprint.mission.discodeit.entity.User; +//import com.sprint.mission.discodeit.service.ChannelService; +//import com.sprint.mission.discodeit.service.MessageService; +//import com.sprint.mission.discodeit.service.UserService; +//import com.sprint.mission.discodeit.service.basic.BasicChannelService; +//import com.sprint.mission.discodeit.service.basic.BasicMessageService; +//import com.sprint.mission.discodeit.service.basic.BasicUserService; +//import org.springframework.boot.SpringApplication; +//import org.springframework.boot.autoconfigure.SpringBootApplication; +//import org.springframework.context.ConfigurableApplicationContext; +// +//@SpringBootApplication +//public class DiscodeitApplication { +// +// static User setupUser(UserService userService) { +// User user = userService.create("woody", "woody@codeit.com", "woody1234"); +// return user; +// } +// +// static Channel setupChannel(ChannelService channelService) { +// Channel channel = channelService.create(ChannelType.PUBLIC, "공지", "공지 채널입니다."); +// return channel; +// } +// +// static void messageCreateTest(MessageService messageService, Channel channel, User author) { +// Message message = messageService.create("안녕하세요.", channel.getId(), author.getId()); +// System.out.println("메시지 생성: [" + author.getUsername() + "] " + message.getContent()); +// } +// +// public static void main(String[] args) { +// ConfigurableApplicationContext context = SpringApplication.run(DiscodeitApplication.class, args); +// +// // 서비스 초기화 +// UserService userService = context.getBean(UserService.class); +// ChannelService channelService = context.getBean(ChannelService.class); +// MessageService messageService = context.getBean(MessageService.class); +// +// // 셋업 +// User user = setupUser(userService); +// Channel channel = setupChannel(channelService); +// +// // 테스트 +// messageCreateTest(messageService, channel, user); +// } +// +//} diff --git a/src/main/java/com/sprint/mission/discodeit/JavaApplication.java b/src/main/java/com/sprint/mission/discodeit/JavaApplication.java new file mode 100644 index 000000000..2392c9167 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/JavaApplication.java @@ -0,0 +1,38 @@ +package com.sprint.mission.discodeit;//package com.sprint.mission.discodeit; +// +//import com.sprint.mission.discodeit.entity.Channel; +//import com.sprint.mission.discodeit.entity.ChannelType; +//import com.sprint.mission.discodeit.entity.Message; +//import com.sprint.mission.discodeit.entity.User; +//import com.sprint.mission.discodeit.repository.ChannelRepository; +//import com.sprint.mission.discodeit.repository.MessageRepository; +//import com.sprint.mission.discodeit.repository.UserRepository; +//import com.sprint.mission.discodeit.repository.file.FileChannelRepository; +//import com.sprint.mission.discodeit.repository.file.FileMessageRepository; +//import com.sprint.mission.discodeit.repository.file.FileUserRepository; +//import com.sprint.mission.discodeit.service.ChannelService; +//import com.sprint.mission.discodeit.service.MessageService; +//import com.sprint.mission.discodeit.service.UserService; +//import com.sprint.mission.discodeit.service.basic.BasicChannelService; +//import com.sprint.mission.discodeit.service.basic.BasicMessageService; +//import com.sprint.mission.discodeit.service.basic.BasicUserService; +// +//public class JavaApplication { +// public static void main(String[] args) { +// // 레포지토리 초기화 +// UserRepository userRepository = new FileUserRepository(); +// ChannelRepository channelRepository = new FileChannelRepository(); +// MessageRepository messageRepository = new FileMessageRepository(); +// +// // 서비스 초기화 +// UserService userService = new BasicUserService(userRepository); +// ChannelService channelService = new BasicChannelService(channelRepository); +// MessageService messageService = new BasicMessageService(messageRepository, channelRepository, userRepository); +// +// // 셋업 +// User user = setupUser(userService); +// Channel channel = setupChannel(channelService); +// // 테스트 +// messageCreateTest(messageService, channel, user); +// } +//} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java b/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java new file mode 100644 index 000000000..2cc77bd3f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/AuthDto.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.dto; + +public class AuthDto { + public record LoginRequest( + String username, + String password + ) {} +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java b/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java new file mode 100644 index 000000000..a4e0808f6 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/BinaryContentDto.java @@ -0,0 +1,19 @@ +package com.sprint.mission.discodeit.dto; + +import java.util.UUID; + +public class BinaryContentDto { + public record CreateRequest( + byte[] bytes, + String contentType, + String fileName + ){} + + public record Response( + UUID id, + byte[] bytes, + String contentType, + String fileName + ){} + +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java b/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java new file mode 100644 index 000000000..3bee70451 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/ChannelDto.java @@ -0,0 +1,34 @@ +package com.sprint.mission.discodeit.dto; + +import com.sprint.mission.discodeit.entity.ChannelType; +import lombok.Builder; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +public class ChannelDto { + public record CreateRequest( + ChannelType type, + String name, + String description + ) {} + + + @Builder + public record Response( + UUID id, + ChannelType type, + String name, + String description, + Instant lastMessageAt, + List memberIdList + + ) {} + + public record UpdateRequest ( + UUID channelId, + String newName, + String newDescription + ) {} +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java b/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java new file mode 100644 index 000000000..7f9e9b132 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/MessageDto.java @@ -0,0 +1,37 @@ +package com.sprint.mission.discodeit.dto; + +import lombok.Builder; + +import java.util.List; +import java.util.UUID; + +public class MessageDto { + public record CreateRequest( + String content, + UUID channelId, + UUID authorId, + List attachments // 첨부파일(선택) + ) {} + + public record BinaryContentDto ( + byte[] data, + String fileType, + String fileName + ) {} + + + @Builder + public record Response( + UUID id, + UUID channelId, + UUID authorId, + String content, + List attachmentId + ) {} + + public record UpdateRequest ( + UUID id, + String content, + List attachments + ) {} +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java b/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java new file mode 100644 index 000000000..4f27dd7b8 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/ReadStatusDto.java @@ -0,0 +1,25 @@ +package com.sprint.mission.discodeit.dto; + +import java.time.Instant; +import java.util.UUID; + +public class ReadStatusDto { + public record CreateRequest( + UUID userId, + UUID channelId + ){} + + public record Response( + UUID id, + UUID userId, + UUID channelId, + UUID lastMessageReadId, + Instant updatedAt + + ){} + + public record UpdateRequest( + UUID id, + UUID lastReadMessageId + ){} +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java b/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java new file mode 100644 index 000000000..4d751b2d4 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/UserDto.java @@ -0,0 +1,37 @@ +package com.sprint.mission.discodeit.dto; + +import lombok.Builder; + +import java.util.UUID; + +public class UserDto { + public record CreateRequest( + String username, + String email, + String password, + BinaryContentDto profileImage + ) {} + + public record BinaryContentDto ( + byte[] data, + String fileType, + String fileName + ) {} + + @Builder + public record Response( + UUID id, + String username, + String email, + boolean isOnline, + UUID profileImageId + ) {} + + public record UpdateRequest ( + UUID id, + String username, + String email, + String password, + BinaryContentDto profileImage + ) {} +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java b/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java new file mode 100644 index 000000000..c7a160b39 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/UserStatusDto.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.dto; + +import java.time.Instant; +import java.util.UUID; + +public class UserStatusDto { + public record CreateRequest( + UUID userId + ){} + + public record Response( + UUID id, + UUID userId, + Instant updatedAt, + boolean isOnline + ){} + + public record UpdateRequest( + UUID id, + UUID userId + ){} +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java b/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java new file mode 100644 index 000000000..ac38adefc --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/BaseEntity.java @@ -0,0 +1,26 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.AllArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; +import java.time.Instant; +import java.util.UUID; + +@SuperBuilder +@AllArgsConstructor +public abstract class BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + protected final UUID id; + protected final Instant createdAt; + + public BaseEntity() { + this.id = UUID.randomUUID(); + this.createdAt = Instant.now(); + } + + // 공통 Getter + public UUID getId() { return id; } + public Instant getCreatedAt() { return createdAt; } + +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java b/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java new file mode 100644 index 000000000..5989e3fb3 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/BinaryContent.java @@ -0,0 +1,20 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.UUID; + +@Getter +public class BinaryContent extends BaseEntity{ + private final byte[] bytes; + private final String contentType; // 예: image/png, application/pdf + private final String fileName; + + public BinaryContent(byte[] bytes, String contentType, String fileName) { + super(); + this.bytes = bytes; + this.contentType = contentType; + this.fileName = fileName; + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/Channel.java b/src/main/java/com/sprint/mission/discodeit/entity/Channel.java new file mode 100644 index 000000000..ea4ac80a7 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/Channel.java @@ -0,0 +1,40 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.UUID; + +@Getter +@Setter +public class Channel extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + private Instant updatedAt; + private ChannelType type; + private String name; + private String description; + + public Channel(ChannelType type, String name, String description) { + this.type = type; + this.name = name; + this.description = description; + } + + public void update(String newName, String newDescription) { + boolean anyValueUpdated = false; + if (newName != null && !newName.equals(this.name)) { + this.name = newName; + anyValueUpdated = true; + } + if (newDescription != null && !newDescription.equals(this.description)) { + this.description = newDescription; + anyValueUpdated = true; + } + + if (anyValueUpdated) { + this.updatedAt = Instant.now(); + } + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java b/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java new file mode 100644 index 000000000..9a2ff3f0f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/ChannelType.java @@ -0,0 +1,6 @@ +package com.sprint.mission.discodeit.entity; + +public enum ChannelType { + PUBLIC, + PRIVATE, +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/Message.java b/src/main/java/com/sprint/mission/discodeit/entity/Message.java new file mode 100644 index 000000000..61ce1d783 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/Message.java @@ -0,0 +1,56 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Builder +@AllArgsConstructor +public class Message extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + private Instant updatedAt; + // + private String content; + // + private UUID channelId; + private UUID authorId; + private List attachmentIds; + + public Message(String content, UUID channelId, UUID authorId, List attachmentId) { + this.content = content; + this.channelId = channelId; + this.authorId = authorId; + this.attachmentIds = (attachmentId!= null) ? attachmentId: new ArrayList<>(); // 내용을 받지 않았을 때에는 빈 리스트로 초기화 + } + + public void update(String newContent, List newAttachmentIds) { + boolean[] isUpdated = {false}; + + Optional.ofNullable(newContent) + .filter(c -> !c.equals(this.content)) + .ifPresent(c -> { + this.content = c; + isUpdated[0] = true; + }); + + Optional.ofNullable(newAttachmentIds) + .filter(ids -> !ids.equals(this.attachmentIds)) + .ifPresent(ids -> { + this.attachmentIds = ids; + isUpdated[0] = true; + }); + + // 하나라도 바뀌었으면 시간 갱신 + if (isUpdated[0]) { + this.updatedAt = Instant.now(); + } + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java b/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java new file mode 100644 index 000000000..eb84ecea4 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/ReadStatus.java @@ -0,0 +1,29 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Getter +public class ReadStatus extends BaseEntity { + private final UUID userId; + private final UUID channelId; + private Instant updatedAt; + private UUID lastReadMessageId; + + public ReadStatus(UUID userId, UUID channelId) { + this.userId = userId; + this.channelId = channelId; + } + + public void updateLastReadMessage(UUID lastReadMessageId) { + Optional.ofNullable(lastReadMessageId) + .filter(id -> !id.equals(this.lastReadMessageId)) + .ifPresent(id -> { + this.lastReadMessageId = id; + this.updatedAt = Instant.now(); + }); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/User.java b/src/main/java/com/sprint/mission/discodeit/entity/User.java new file mode 100644 index 000000000..d64313e2c --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/User.java @@ -0,0 +1,35 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +public class User extends BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + + private Instant updatedAt; + + private String username; + private String email; + private String password; + + private UUID profileImageId; + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } + + public void update(String newUsername, String newEmail, String newPassword) { + Optional.ofNullable(newUsername).filter(s -> !s.isBlank()).ifPresent(this::setUsername); + Optional.ofNullable(newEmail).filter(s -> !s.isBlank()).ifPresent(this::setEmail); + Optional.ofNullable(newPassword).filter(s -> !s.isBlank()).ifPresent(this::setPassword); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java b/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java new file mode 100644 index 000000000..4991751c7 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/entity/UserStatus.java @@ -0,0 +1,27 @@ +package com.sprint.mission.discodeit.entity; + +import lombok.Getter; + +import java.time.Instant; +import java.util.UUID; + +@Getter +public class UserStatus extends BaseEntity { + private final UUID userId; + private Instant updatedAt; + + public UserStatus(UUID userId) { + this.userId = userId; + } + + // 5분 이내 접속 여부 판단 메소드 + public boolean isOnline() { + // 현재 시간 - 5분(300초) 이후 접속했다면 true + return updatedAt.isAfter(Instant.now().minusSeconds(300)); + } + + public void updateActiveTime() { + this.updatedAt = Instant.now(); + } + +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java new file mode 100644 index 000000000..03c42f422 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/BinaryContentRepository.java @@ -0,0 +1,21 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface BinaryContentRepository { + BinaryContent save(BinaryContent binaryContent); + + Optional findById(UUID id); + List findAllById(List idList); + + boolean existsById(UUID id); + + void deleteById(UUID id); + void deleteAllById(List idList); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java new file mode 100644 index 000000000..c8b9faa77 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java @@ -0,0 +1,17 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ChannelRepository { + Channel save(Channel channel); + Optional findById(UUID id); + List findAll(); + boolean existsById(UUID id); + void deleteById(UUID id); + List findAllByType(ChannelType channelType); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java new file mode 100644 index 000000000..a34ce4f0c --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Message; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface MessageRepository { + Message save(Message message); + + Optional findById(UUID id); + Optional findLastMessageOfChannel(UUID channelId); + + List findAll(); + List findAllByChannelId(UUID channelId); + + boolean existsById(UUID id); + + void deleteById(UUID id); + void deleteAllByChannelId(UUID channelId); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java new file mode 100644 index 000000000..29b6bb9f1 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/ReadStatusRepository.java @@ -0,0 +1,27 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.UserStatus; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ReadStatusRepository { + ReadStatus save(ReadStatus readStatus); + Optional findById(UUID id); + + Optional findByUserAndChannelId(UUID userId, UUID channelId); + + void deleteById(UUID id); + void deleteAllByChannelId(UUID channelId); + public List findAll(); + + List findAllByChannelId(UUID channelId); + List findAllByUserId(UUID userId); + + boolean existsByChannelIdAndUserId(UUID channelId, UUID userId); + boolean existsById(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java new file mode 100644 index 000000000..867ab1bf5 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java @@ -0,0 +1,20 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.User; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface UserRepository { + User save(User user); + Optional findById(UUID id); + List findAll(); + boolean existsById(UUID id); + void deleteById(UUID id); + + boolean usernameExistence(String username); + boolean emailExistence(String email); + + Optional findByUsername(String username); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java new file mode 100644 index 000000000..cae485b96 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/UserStatusRepository.java @@ -0,0 +1,22 @@ +package com.sprint.mission.discodeit.repository; + +import com.sprint.mission.discodeit.entity.UserStatus; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserStatusRepository { + UserStatus save(UserStatus userStatus); + + boolean existsByUserId(UUID userId); + boolean existsById(UUID id); + + Optional findById(UUID id); + Optional findByUserId(UUID userId); + List findAll(); + + void deleteById(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java new file mode 100644 index 000000000..eb01deaa9 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileBinaryContentRepository.java @@ -0,0 +1,117 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileBinaryContentRepository implements BinaryContentRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileBinaryContentRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", BinaryContent.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public BinaryContent save(BinaryContent binaryContent) { + Path path = resolvePath(binaryContent.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(binaryContent); + } catch (IOException e) { + throw new RuntimeException(e); + } + return binaryContent; + } + + @Override + public Optional findById(UUID id) { + BinaryContent binaryContentNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + binaryContentNullable = (BinaryContent) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(binaryContentNullable); + } + + @Override + public List findAllById(List idList) { + return idList.stream() + .map(this::resolvePath) + .filter(Files::exists) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (BinaryContent) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllById(List idList) { + idList.forEach(id -> { + Path path = resolvePath(id); + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new RuntimeException("파일 삭제 실패: " + id, e); + } + }); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java new file mode 100644 index 000000000..90ef855e9 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileChannelRepository.java @@ -0,0 +1,116 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileChannelRepository implements ChannelRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileChannelRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", Channel.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public Channel save(Channel channel) { + Path path = resolvePath(channel.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(channel); + } catch (IOException e) { + throw new RuntimeException(e); + } + return channel; + } + + @Override + public Optional findById(UUID id) { + Channel channelNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + channelNullable = (Channel) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(channelNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Channel) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByType(ChannelType channelType) { + return findAll().stream() + .filter(channel -> channel.getType().equals(channelType)) + .toList(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java new file mode 100644 index 000000000..9c2c40342 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileMessageRepository.java @@ -0,0 +1,165 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.MessageRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileMessageRepository implements MessageRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileMessageRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", Message.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public Message save(Message message) { + Path path = resolvePath(message.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(message); + } catch (IOException e) { + throw new RuntimeException(e); + } + return message; + } + + @Override + public Optional findById(UUID id) { + Message messageNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + messageNullable = (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(messageNullable); + } + + @Override + public Optional findLastMessageOfChannel(UUID channelId) { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .filter(message -> message.getChannelId().equals(channelId)) + .sorted(Comparator.comparing(Message::getCreatedAt).reversed()) + .findFirst(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (Message) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + try { + Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .forEach(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Message message = (Message) ois.readObject(); + if (message.getChannelId().equals(channelId)) { + ois.close(); + fis.close(); + Files.delete(path); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("파일 삭제 중 오류 발생: " + path, e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java new file mode 100644 index 000000000..b1860eac9 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileReadStatusRepository.java @@ -0,0 +1,162 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileReadStatusRepository implements ReadStatusRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileReadStatusRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", ReadStatus.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public ReadStatus save(ReadStatus readStatus) { + Path path = resolvePath(readStatus.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(readStatus); + } catch (IOException e) { + throw new RuntimeException(e); + } + return readStatus; + } + + @Override + public Optional findById(UUID id) { + ReadStatus readStatusNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + readStatusNullable = (ReadStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(readStatusNullable); + } + + @Override + public Optional findByUserAndChannelId(UUID userId, UUID channelId) { + return findAll().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId) && readStatus.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + try { + Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .forEach(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + Message message = (Message) ois.readObject(); + if (message.getChannelId().equals(channelId)) { + ois.close(); + fis.close(); + Files.delete(path); + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("파일 삭제 중 오류 발생: " + path, e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (ReadStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List findAllByChannelId(UUID channelId) { + return findAll().stream() + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public List findAllByUserId(UUID userId) { + return findAll().stream() + .filter(readStatus -> readStatus.getChannelId().equals(userId)) + .toList(); + } + + @Override + public boolean existsByChannelIdAndUserId(UUID channelId, UUID userId) { + return findByUserAndChannelId(userId, channelId).isPresent(); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + +} \ No newline at end of file diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java new file mode 100644 index 000000000..42618daa0 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserRepository.java @@ -0,0 +1,127 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileUserRepository implements UserRepository { + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileUserRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", User.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public User save(User user) { + Path path = resolvePath(user.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(user); + } catch (IOException e) { + throw new RuntimeException(e); + } + return user; + } + + @Override + public Optional findById(UUID id) { + User userNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userNullable = (User) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (User) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean usernameExistence(String username) { + return findAll().stream() + .anyMatch(user -> user.getUsername().equals(username)); + } + + @Override + public boolean emailExistence(String email) { + return findAll().stream() + .anyMatch(user -> user.getEmail().equals(email)); + } + + @Override + public Optional findByUsername(String username) { + return findAll().stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java new file mode 100644 index 000000000..5762b2e68 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileUserStatusRepository.java @@ -0,0 +1,133 @@ +package com.sprint.mission.discodeit.repository.file; + +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "file" +) +public class FileUserStatusRepository implements UserStatusRepository { + + private final Path DIRECTORY; + private final String EXTENSION = ".ser"; + + public FileUserStatusRepository(@Value("${discodeit.repository.file-directory:file-data-map}") String baseDirectory) { + this.DIRECTORY = Paths.get(System.getProperty("user.dir"), "file-data-map", UserStatus.class.getSimpleName()); + if (Files.notExists(DIRECTORY)) { + try { + Files.createDirectories(DIRECTORY); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private Path resolvePath(UUID id) { + return DIRECTORY.resolve(id + EXTENSION); + } + + @Override + public UserStatus save(UserStatus userStatus) { + Path path = resolvePath(userStatus.getId()); + try ( + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos) + ) { + oos.writeObject(userStatus); + } catch (IOException e) { + throw new RuntimeException(e); + } + return userStatus; + } + + @Override + public boolean existsByUserId(UUID userId) { + Path path = resolvePath(userId); + return Files.exists(path); + } + + @Override + public boolean existsById(UUID id) { + Path path = resolvePath(id); + return Files.exists(path); + } + + @Override + public Optional findById(UUID id) { + UserStatus userStatusNullable = null; + Path path = resolvePath(id); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userStatusNullable = (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userStatusNullable); + } + + @Override + public Optional findByUserId(UUID userId) { + UserStatus userStatusNullable = null; + Path path = resolvePath(userId); + if (Files.exists(path)) { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + userStatusNullable = (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return Optional.ofNullable(userStatusNullable); + } + + @Override + public List findAll() { + try { + return Files.list(DIRECTORY) + .filter(path -> path.toString().endsWith(EXTENSION)) + .map(path -> { + try ( + FileInputStream fis = new FileInputStream(path.toFile()); + ObjectInputStream ois = new ObjectInputStream(fis) + ) { + return (UserStatus) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteById(UUID id) { + Path path = resolvePath(id); + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java new file mode 100644 index 000000000..d6e5b4a2a --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFBinaryContentRepository.java @@ -0,0 +1,55 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFBinaryContentRepository implements BinaryContentRepository { + private final Map data; + + public JCFBinaryContentRepository() { + this.data = new HashMap<>(); + } + + @Override + public BinaryContent save(BinaryContent binaryContent) { + this.data.put(binaryContent.getId(), binaryContent); + return binaryContent; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAllById(List idList) { + return data.values().stream() + .filter(binaryContent -> idList.contains(binaryContent.getId())) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllById(List idList) { + data.values().removeIf(binaryContent -> idList.contains(binaryContent.getId())); + + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java new file mode 100644 index 000000000..785fac572 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFChannelRepository.java @@ -0,0 +1,55 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFChannelRepository implements ChannelRepository { + private final Map data; + + public JCFChannelRepository() { + this.data = new HashMap<>(); + } + + @Override + public Channel save(Channel channel) { + this.data.put(channel.getId(), channel); + return channel; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public List findAllByType(ChannelType channelType) { + return data.values().stream() + .filter(channel -> channel.getType().equals(channelType)) + .toList(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java new file mode 100644 index 000000000..8f28c548f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFMessageRepository.java @@ -0,0 +1,67 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.MessageRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFMessageRepository implements MessageRepository { + private final Map data; + + public JCFMessageRepository() { + this.data = new HashMap<>(); + } + + @Override + public Message save(Message message) { + this.data.put(message.getId(), message); + return message; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findLastMessageOfChannel(UUID channelId) { + return data.values().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .sorted(Comparator.comparing(Message::getCreatedAt).reversed()) + .findFirst(); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.values().stream() + .filter(message -> message.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + data.values().removeIf(message -> message.getChannelId().equals(channelId)); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java new file mode 100644 index 000000000..03db7eaec --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFReadStatusRepository.java @@ -0,0 +1,82 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFReadStatusRepository implements ReadStatusRepository { + + private final Map data; + public JCFReadStatusRepository() { + this.data = new HashMap<>(); + } + + @Override + public ReadStatus save(ReadStatus readStatus) { + this.data.put(readStatus.getId(), readStatus); + return readStatus; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findByUserAndChannelId(UUID userId, UUID channelId) { + return data.values().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId)) + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .findFirst(); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public void deleteAllByChannelId(UUID channelId) { + data.values().removeIf(readStatus -> readStatus.getChannelId().equals(channelId)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public List findAllByChannelId(UUID channelId) { + return data.values().stream() + .filter(readStatus -> readStatus.getChannelId().equals(channelId)) + .toList(); + } + + @Override + public List findAllByUserId(UUID userId) { + return data.values().stream() + .filter(readStatus -> readStatus.getUserId().equals(userId)) + .toList(); + } + + @Override + public boolean existsByChannelIdAndUserId(UUID channelId, UUID userId) { + return data.values().stream() + .anyMatch(readStatus -> + readStatus.getUserId().equals(userId) && readStatus.getChannelId().equals(channelId) + ); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java new file mode 100644 index 000000000..3264d1e9d --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserRepository.java @@ -0,0 +1,66 @@ +package com.sprint.mission.discodeit.repository.jcf; + +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFUserRepository implements UserRepository { + private final Map data; + + public JCFUserRepository() { + this.data = new HashMap<>(); + } + + @Override + public User save(User user) { + this.data.put(user.getId(), user); + return user; + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } + + @Override + public boolean usernameExistence(String username) { + return data.values().stream() + .anyMatch(user -> user.getUsername().equals(username)); + } + + @Override + public boolean emailExistence(String email) { + return data.values().stream() + .anyMatch(user -> user.getEmail().equals(email)); + } + + @Override + public Optional findByUsername(String username) { + return data.values().stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java new file mode 100644 index 000000000..cfc8df1ef --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/jcf/JCFUserStatusRepository.java @@ -0,0 +1,61 @@ +package com.sprint.mission.discodeit.repository.jcf; + + +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserStatusService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +@ConditionalOnProperty( + name = "discodeit.repository.type", + havingValue = "jcf", + matchIfMissing = true +) +public class JCFUserStatusRepository implements UserStatusRepository { + private final Map data; + + public JCFUserStatusRepository() { + this.data = new HashMap<>(); + } + + + @Override + public UserStatus save(UserStatus userStatus) { + this.data.put(userStatus.getId(), userStatus); + return userStatus; + } + + @Override + public boolean existsByUserId(UUID userId) { + return this.data.containsKey(userId); + } + + @Override + public boolean existsById(UUID id) { + return this.data.containsKey(id); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(this.data.get(id)); + } + + @Override + public Optional findByUserId(UUID userId) { + return Optional.ofNullable(this.data.get(userId)); + } + + @Override + public List findAll() { + return this.data.values().stream().toList(); + } + + @Override + public void deleteById(UUID id) { + this.data.remove(id); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/AuthService.java b/src/main/java/com/sprint/mission/discodeit/service/AuthService.java new file mode 100644 index 000000000..d68baee7f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/AuthService.java @@ -0,0 +1,8 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.AuthDto; +import com.sprint.mission.discodeit.dto.UserDto; + +public interface AuthService { + UserDto.Response login(AuthDto.LoginRequest request); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java new file mode 100644 index 000000000..6a1341f0f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/BinaryContentService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.BinaryContentDto; +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; + +import java.util.List; +import java.util.UUID; + +public interface BinaryContentService { + BinaryContentDto.Response create(BinaryContentDto.CreateRequest request); + BinaryContentDto.Response find(UUID userId); + List findAllByIdIn(List idList); + void delete(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java b/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java new file mode 100644 index 000000000..af357ecd5 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/ChannelService.java @@ -0,0 +1,16 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ChannelDto; +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; + +import java.util.List; +import java.util.UUID; + +public interface ChannelService { + ChannelDto.Response create(ChannelDto.CreateRequest request); + ChannelDto.Response find(UUID channelId); + List findAllByUserId(UUID userId); + ChannelDto.Response update(ChannelDto.UpdateRequest request); + void delete(UUID channelId); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/MessageService.java b/src/main/java/com/sprint/mission/discodeit/service/MessageService.java new file mode 100644 index 000000000..723f49053 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/MessageService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.MessageDto; +import com.sprint.mission.discodeit.entity.Message; + +import java.util.List; +import java.util.UUID; + +public interface MessageService { + MessageDto.Response create(MessageDto.CreateRequest request); + List findAllByChannelId(UUID channelId); +// List findAll(); + MessageDto.Response update(MessageDto.UpdateRequest request); + void delete(UUID messageId); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java new file mode 100644 index 000000000..a02957986 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/ReadStatusService.java @@ -0,0 +1,18 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.User; +import lombok.Locked; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ReadStatusService { + ReadStatusDto.Response create(ReadStatusDto.CreateRequest request); + ReadStatusDto.Response find(UUID id); + List findAllByUserId(UUID userId); + ReadStatusDto.Response update(ReadStatusDto.UpdateRequest request); + void delete(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/UserService.java b/src/main/java/com/sprint/mission/discodeit/service/UserService.java new file mode 100644 index 000000000..3186f3ffb --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/UserService.java @@ -0,0 +1,15 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.User; + +import java.util.List; +import java.util.UUID; + +public interface UserService { + UserDto.Response create(UserDto.CreateRequest request); + UserDto.Response find(UUID userId); + List findAll(); + UserDto.Response update(UserDto.UpdateRequest request); + void delete(UUID userId); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java new file mode 100644 index 000000000..ff497c9a0 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/UserStatusService.java @@ -0,0 +1,16 @@ +package com.sprint.mission.discodeit.service; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; + +import java.util.List; +import java.util.UUID; + +public interface UserStatusService { + UserStatusDto.Response create(UserStatusDto.CreateRequest request); + UserStatusDto.Response find(UUID id); + List findAll(); + UserStatusDto.Response update(UserStatusDto.UpdateRequest request); + UserStatusDto.Response updateByUserId(UserStatusDto.UpdateRequest request); + void delete(UUID id); +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java new file mode 100644 index 000000000..0c9010a4d --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java @@ -0,0 +1,39 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.AuthDto; +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.AuthService; +import org.springframework.stereotype.Service; + +import java.util.NoSuchElementException; + +@Service +public class BasicAuthService implements AuthService { + + private final UserRepository userRepository; + + public BasicAuthService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDto.Response login(AuthDto.LoginRequest request) { + // username: 유저 존재 확인 + User user = userRepository.findByUsername(request.username()) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 유저입니다.")); + + // password: 비밀번호 일치 여부 확인 + if (!user.getPassword().equals(request.password())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .build(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java new file mode 100644 index 000000000..7997973d9 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java @@ -0,0 +1,59 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.BinaryContentDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.service.BinaryContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicBinaryContentService implements BinaryContentService { + private final BinaryContentRepository binaryContentRepository; + + @Override + public BinaryContentDto.Response create(BinaryContentDto.CreateRequest request) { + BinaryContent content = new BinaryContent( + request.bytes(), + request.contentType(), + request.fileName() + ); + return convertToResponse(binaryContentRepository.save(content)); + } + + @Override + public BinaryContentDto.Response find(UUID userId) { + return binaryContentRepository.findById(userId) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 파일이 존재하지 않습니다.")); + } + + @Override + public List findAllByIdIn(List idList) { + return binaryContentRepository.findAllById(idList).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public void delete(UUID id) { + if (!binaryContentRepository.existsById(id)) { + throw new NoSuchElementException("해당 ID의 파일이 존재하지 않습니다."); + } + binaryContentRepository.deleteById(id); + } + + private BinaryContentDto.Response convertToResponse(BinaryContent content) { + return new BinaryContentDto.Response( + content.getId(), + content.getBytes(), + content.getContentType(), + content.getFileName() + ); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java new file mode 100644 index 000000000..d650f962e --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java @@ -0,0 +1,124 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ChannelDto; +import com.sprint.mission.discodeit.entity.Channel; +import com.sprint.mission.discodeit.entity.ChannelType; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.service.ChannelService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.validation.MessageCodesResolver; + +import java.time.Instant; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class BasicChannelService implements ChannelService { + private final ChannelRepository channelRepository; + private final ReadStatusRepository readStatusRepository; + private final MessageRepository messageRepository; + + + @Override + public ChannelDto.Response create(ChannelDto.CreateRequest request) { + // PUBLIC 채널 생성 시 기존 로직 유지, PRIVATE 채널 생성 시 생략 + String name = (request.type() == ChannelType.PUBLIC) ? request.name() : null; + String description = (request.type() == ChannelType.PUBLIC) ? request.description() : null; + + Channel channel = new Channel(request.type(), name, description); + channelRepository.save(channel); + + return convertToResponse(channel, null, null); // 초기 생성 시 최근 메시지, 유저 목록 없음 + } + + + @Override + public ChannelDto.Response find(UUID channelId) { + Channel channel = channelRepository.findById(channelId) + .orElseThrow(() -> new NoSuchElementException("Channel with id " + channelId + " not found")); + + // 해당 채널의 가장 최근 메시지의 시간 정보 + Instant lastTime = messageRepository.findLastMessageOfChannel(channelId) + .map(Message::getCreatedAt).orElse(null); + + // PRIVATE 채널의 경우 참여한 유저의 id 포함 + List memberList = null; + if (channel.getType() == ChannelType.PRIVATE) { + memberList = readStatusRepository.findAllByChannelId(channelId).stream() + .map(ReadStatus::getUserId) + .toList(); + } + + return convertToResponse(channel, lastTime, memberList); + + } + + @Override + public List findAllByUserId (UUID userId){ + // PUBLIC 채널: 전체 조회 + List publicChannels = channelRepository.findAllByType(ChannelType.PUBLIC); + + // PRIVATE: 유저가 참여한 채널만 조회 + List myPrivateChannels = readStatusRepository.findAllByUserId(userId).stream() + .map(readStatus -> channelRepository.findById(readStatus.getChannelId()).orElse(null)) + .filter(Objects::nonNull) + .filter(channel -> channel.getType() == ChannelType.PRIVATE) + .toList(); + + // 참여한 PUBLIC과 PRIVATE 채널 합치기: 특정 유저가 볼 수 있는 채널 목록 조회 가능 + List myAvailableChannelList = new ArrayList<>(); + myAvailableChannelList.addAll(publicChannels); + myAvailableChannelList.addAll(myPrivateChannels); + + // find() 재사용: 최근 메시지 시간 정보, PRIVATE 채널의 유저 Id 정보 포함 + return myAvailableChannelList.stream() + .map(channel -> find(channel.getId())) + .toList(); + } + + @Override + public ChannelDto.Response update (ChannelDto.UpdateRequest request) { + Channel channel = channelRepository.findById(request.channelId()) + .orElseThrow(() -> new NoSuchElementException("Channel with id " + request.channelId() + " not found")); + + // PRIVATE 채널은 수정 불가 + if (channel.getType() == ChannelType.PRIVATE) { + throw new IllegalStateException("PRIVATE 채널은 수정할 수 없습니다."); + } + + channel.setName(request.newName()); + channel.setDescription(request.newDescription()); + + channelRepository.save(channel); + return convertToResponse(channel, null, null); + } + + @Override + public void delete (UUID channelId) { + Channel channel = channelRepository.findById(channelId).orElseThrow(); + + // 관련 도메인(Message, ReadStatus) 삭제 + messageRepository.deleteAllByChannelId(channelId); + readStatusRepository.deleteAllByChannelId(channelId); + + channelRepository.deleteById(channelId); + } + + private ChannelDto.Response convertToResponse (Channel channel, Instant lastMessageTime, List memberId) { + return ChannelDto.Response.builder() + .id(channel.getId()) + .name(channel.getName()) + .lastMessageAt(lastMessageTime) + .memberIdList(memberId) + .build(); + } + } + + + + diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java new file mode 100644 index 000000000..87985e10d --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java @@ -0,0 +1,111 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.MessageDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.MessageRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +public class BasicMessageService implements MessageService { + private final MessageRepository messageRepository; + private final ChannelRepository channelRepository; + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + + @Override + public MessageDto.Response create(MessageDto.CreateRequest request) { + + List attachmentIds = new ArrayList<>(); + + if (request.attachments() != null && !request.attachments().isEmpty()) { + attachmentIds = request.attachments().stream() + .map(fileRequest -> { + BinaryContent content = new BinaryContent( + fileRequest.data(), + fileRequest.fileType(), + fileRequest.fileName() + ); + return binaryContentRepository.save(content).getId(); + }) + .toList(); + } + + Message message = new Message( + request.content(), + request.channelId(), + request.authorId(), + attachmentIds + ); + + messageRepository.save(message); + + return convertToResponse(message); + + } + + @Override + public List findAllByChannelId(UUID channelId) { + return messageRepository.findAllByChannelId(channelId).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public MessageDto.Response update(MessageDto.UpdateRequest request) { + Message message = messageRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("Message with id " + request.id() + " not found")); + + List newIds = Optional.ofNullable(request.attachments()) + .filter(attachments -> !attachments.isEmpty()) + .map(attachments -> attachments.stream() + .map(dto -> { + BinaryContent content = new BinaryContent( + dto.data(), + dto.fileType(), + dto.fileName() + ); + return binaryContentRepository.save(content).getId(); + }) + .toList() + ) + .orElse(null); + message.update(request.content(), newIds); + + return convertToResponse(messageRepository.save(message)); + + } + + + @Override + public void delete(UUID id) { + Message message = messageRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("Message with id " + id + " not found")); + + // 첨부파일 삭제 + if (message.getAttachmentIds() != null && !message.getAttachmentIds().isEmpty()) { + binaryContentRepository.deleteAllById(message.getAttachmentIds()); + } + + messageRepository.deleteById(id); + + } + + private MessageDto.Response convertToResponse(Message message) { + return MessageDto.Response.builder() + .id(message.getId()) + .channelId(message.getChannelId()) + .authorId(message.getAuthorId()) + .content(message.getContent()) + .attachmentId(message.getAttachmentIds()) + .build(); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java new file mode 100644 index 000000000..cc82371e1 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java @@ -0,0 +1,87 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.repository.ChannelRepository; +import com.sprint.mission.discodeit.repository.ReadStatusRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.service.ReadStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicReadStatusService implements ReadStatusService { + private final ReadStatusRepository readStatusRepository; + private final UserRepository userRepository; + private final ChannelRepository channelRepository; + + @Override + public ReadStatusDto.Response create(ReadStatusDto.CreateRequest request) { + // 관련된 채널이나 유저가 존재하지 않을 때 예외 발생 + channelRepository.findById(request.channelId()) + .orElseThrow(() -> new NoSuchElementException("채널이 존재하지 않습니다.")); + + userRepository.findById(request.userId()) + .orElseThrow(() -> new NoSuchElementException("유저가 존재하지 않습니다.")); + + // 같은 채널과 유저와 관련된 객체가 이미 존재하면 예외 발생 + if(readStatusRepository.existsByChannelIdAndUserId(request.channelId(), request.userId())) { + throw new IllegalStateException("이미 채널과 유저의 상태가 존재합니다."); + } + + ReadStatus readStatus = new ReadStatus(request.channelId(), request.userId()); + return convertToResponse(readStatusRepository.save(readStatus)); + + } + + @Override + public ReadStatusDto.Response find(UUID id) { + return readStatusRepository.findById(id) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + } + + @Override + public List findAllByUserId(UUID userId) { + return readStatusRepository.findAllByUserId(userId).stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public ReadStatusDto.Response update(ReadStatusDto.UpdateRequest request) { + ReadStatus readStatus = readStatusRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + + Optional.ofNullable(request.lastReadMessageId()) + .ifPresent(readStatus::updateLastReadMessage); + + return convertToResponse(readStatusRepository.save(readStatus)); + } + + @Override + public void delete(UUID id) { + if(!readStatusRepository.existsById(id)) { + throw new NoSuchElementException("존재하지 않는 Id 입니다."); + } + readStatusRepository.deleteById(id); + + } + private ReadStatusDto.Response convertToResponse(ReadStatus readStatus) { + return new ReadStatusDto.Response( + readStatus.getId(), + readStatus.getUserId(), + readStatus.getChannelId(), + readStatus.getLastReadMessageId(), + readStatus.getUpdatedAt() + ); + } + +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java new file mode 100644 index 000000000..ee70a6eaa --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java @@ -0,0 +1,137 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.UserDto; +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.BinaryContentRepository; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicUserService implements UserService { + private final UserRepository userRepository; + private final BinaryContentRepository binaryContentRepository; + private final UserStatusRepository userStatusRepository; + + @Override + public UserDto.Response create(UserDto.CreateRequest request) { + // 프로필 이미지 처리(선택적) + UUID imageId = null; + if (request.profileImage() != null) { + BinaryContent image = new BinaryContent( + request.profileImage().data(), + request.profileImage().fileType(), + request.profileImage().fileName()); + imageId = binaryContentRepository.save(image).getId(); + } + + // 중복 검증 + if (userRepository.usernameExistence(request.username())) throw new RuntimeException("이미 존재하는 이름입니다."); + if (userRepository.emailExistence(request.email())) throw new RuntimeException("이미 존재하는 이메일입니다."); + + // 유저 등록 + User user = new User(request.username(), request.email(), request.password()); + if (imageId != null) { + user.setProfileImageId(imageId); + } + User savedUser = userRepository.save(user); + userStatusRepository.save(new UserStatus(savedUser.getId())); + + return UserDto.Response.builder() + .id(savedUser.getId()) + .username(savedUser.getUsername()) + .email(savedUser.getEmail()) + .isOnline(true) + .profileImageId(savedUser.getProfileImageId()) + .build(); + + + } + + @Override + public UserDto.Response find(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User with id " + userId + " not found")); + + // 유저 온라인 상태 정보 포함(패스워드 제외) + boolean isOnline = userStatusRepository.findByUserId(userId) + .map(UserStatus::isOnline) + .orElse(false); + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .isOnline(isOnline) + .build(); + } + + @Override + public List findAll() { + return userRepository.findAll().stream() + .map(user -> { + boolean isOnline = userStatusRepository.findByUserId(user.getId()) + .map(UserStatus::isOnline) + .orElse(false); + + return UserDto.Response.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .profileImageId(user.getProfileImageId()) + .isOnline(isOnline) + .build(); + }) + .toList(); + } + + @Override + public UserDto.Response update(UserDto.UpdateRequest updateRequest) { + User user = userRepository.findById(updateRequest.id()) + .orElseThrow(() -> new NoSuchElementException("User with id " + updateRequest.id()+ " not found")); + + // 프로필 이미지 대체 (선택) + Optional.ofNullable(updateRequest.profileImage()) + .ifPresent(imageDto -> { + if (user.getProfileImageId() != null) { + binaryContentRepository.deleteById(user.getProfileImageId()); + } + BinaryContent newImage = new BinaryContent( + imageDto.data(), imageDto.fileType(), imageDto.fileName() + ); + user.setProfileImageId(binaryContentRepository.save(newImage).getId()); + }); + + user.update(updateRequest.username(), updateRequest.email(), updateRequest.password()); + userRepository.save(user); + return find(user.getId()); + } + + @Override + public void delete(UUID userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("User not found with id: " + userId)); + + // BinaryContent 삭제 + if (user.getProfileImageId() != null) { + binaryContentRepository.deleteById(user.getProfileImageId()); + } + + // UserStatus 삭제 + userStatusRepository.deleteById(userId); + + // 유저 삭제 + userRepository.deleteById(userId); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java new file mode 100644 index 000000000..eda29109c --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java @@ -0,0 +1,93 @@ +package com.sprint.mission.discodeit.service.basic; + +import com.sprint.mission.discodeit.dto.ReadStatusDto; +import com.sprint.mission.discodeit.dto.UserStatusDto; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.repository.UserRepository; +import com.sprint.mission.discodeit.repository.UserStatusRepository; +import com.sprint.mission.discodeit.service.UserStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class BasicUserStatusService implements UserStatusService { + + private final UserStatusRepository userStatusRepository; + private final UserRepository userRepository; + + + @Override + public UserStatusDto.Response create(UserStatusDto.CreateRequest request) { + // 관련된 유저 존재 X + userRepository.findById(request.userId()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 유저가 존재하지 않습니다.")); + + // 같은 유저와 관련된 객체가 이미 존재 + if (userStatusRepository.existsByUserId(request.userId())) { + throw new IllegalStateException("해당 ID의 상태가 이미 존재합니다."); + } + + UserStatus userStatus = new UserStatus( + request.userId() + ); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public UserStatusDto.Response find(UUID id) { + return userStatusRepository.findById(id) + .map(this::convertToResponse) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + } + + @Override + public List findAll() { + return userStatusRepository.findAll().stream() + .map(this::convertToResponse) + .toList(); + } + + @Override + public UserStatusDto.Response update(UserStatusDto.UpdateRequest request) { + UserStatus userStatus = userStatusRepository.findById(request.id()) + .orElseThrow(() -> new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다.")); + + userStatus.updateActiveTime(); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public UserStatusDto.Response updateByUserId(UserStatusDto.UpdateRequest request) { + UserStatus userStatus = userStatusRepository.findByUserId(request.userId()) + .orElseThrow(() -> new NoSuchElementException("해당 유저의 상태 정보가 존재하지 않습니다.")); + + userStatus.updateActiveTime(); + + return convertToResponse(userStatusRepository.save(userStatus)); + } + + @Override + public void delete(UUID id) { + if (!userStatusRepository.existsById(id)) { + throw new NoSuchElementException("해당 ID의 상태가 존재하지 않습니다."); + } + userStatusRepository.deleteById(id); + + } + + private UserStatusDto.Response convertToResponse(UserStatus userStatus) { + return new UserStatusDto.Response( + userStatus.getId(), + userStatus.getUserId(), + userStatus.getUpdatedAt(), + userStatus.isOnline() + ); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 000000000..4fe0afb2e --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,4 @@ +discodeit: + repository: + type: jcf + file-directory: .discodeit