diff --git a/.postman/config.json b/.postman/config.json new file mode 100644 index 000000000..49419e4b4 --- /dev/null +++ b/.postman/config.json @@ -0,0 +1,13 @@ +{ + "workspace": { + "id": "616fad72-6885-4535-8e11-c0b53d6f614f" + }, + "entities": { + "environments": [], + "flows": [], + "globals": [], + "mocks": [], + "specs": [], + "collections": [] + } +} \ No newline at end of file diff --git a/postman/collections/New Collection.postman_collection.json b/postman/collections/New Collection.postman_collection.json new file mode 100644 index 000000000..619e0b1b9 --- /dev/null +++ b/postman/collections/New Collection.postman_collection.json @@ -0,0 +1,26 @@ +{ + "info": { + "_postman_id": "f5239c38-be05-4aa1-b610-6795d6547e42", + "name": "New Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "postmen", + "item": [], + "id": "b3319a91-614e-4444-89b0-57f8a2a3725a" + }, + { + "name": "New Request", + "id": "5a85bfe0-7517-4a3b-9505-fd4accb89dfe", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "" + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/postman/collections/postmen.postman_collection.json b/postman/collections/postmen.postman_collection.json new file mode 100644 index 000000000..76c68fab1 --- /dev/null +++ b/postman/collections/postmen.postman_collection.json @@ -0,0 +1,389 @@ +{ + "info": { + "_postman_id": "3290e750-616e-44ca-83e1-bc35b3038479", + "name": "postmen", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "http://localhost:8080/messages", + "id": "323f4e7e-3439-4547-97bb-8d2b7bf095ef", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"message\": {\r\n \"channelId\": \"f2b44f44-294a-458a-8f91-6fb782caa936\",\r\n \"authorId\": \"74b443a9-4625-4ec3-afab-cf1c1c48ea2d\",\r\n \"content\": \"뿌링치즈볼추가\"\r\n },\r\n \"attachments\": []\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/messages", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/channels/public", + "id": "8125d361-c127-445b-b843-cb5fac5c68ef", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"치킨토론방\",\r\n \"description\": \"뿌링클 ㄱㄱ\",\r\n \"userId\": \"74b443a9-4625-4ec3-afab-cf1c1c48ea2d\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/channels/public", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "channels", + "public" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/messages/", + "id": "b902681c-034d-4b26-9c88-9cab293ee95a", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"message\": {\r\n \"channelId\": \"2f3cdee3-d076-4d69-a834-80083769b5f3\",\r\n \"userId\": \"6e08141d-37ce-41b1-97f6-9a729debd3c1\",\r\n \"content\": \"오늘 치킨 뭐먹지?\"\r\n },\r\n \"attachments\": []\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/messages/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages", + "" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/api/user/findAll", + "id": "3b9c718f-40f2-4359-b058-402cb5f3ba60", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/user/findAll", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "user", + "findAll" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/messages/channel/22d5b559-0a00-4f3f-a4b8-2b82ada00bed", + "id": "fafe2726-92cf-4f53-b8f4-8a29cca784cd", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/messages/channel/22d5b559-0a00-4f3f-a4b8-2b82ada00bed", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages", + "channel", + "22d5b559-0a00-4f3f-a4b8-2b82ada00bed" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/api/user/create", + "id": "84a8ae7a-1bdb-4afd-a544-3897cace9f34", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "userCreateRequest", + "value": "{\n \"email\": \"BHC@codeit.com\",\n \"username\": \"BHC\",\n \"password\": \"2222\"\n}", + "type": "text", + "uuid": "789a704d-51bd-4d11-8766-616d38cab39d", + "contentType": "application/json" + }, + { + "key": "profile", + "type": "file", + "uuid": "57cce4ae-120b-4eba-adcc-de664f1ac2a1", + "src": [] + } + ], + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/user/create", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "user", + "create" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/messages/e9b9ee0f-1a6d-4a23-bb65-590cebabc8da", + "id": "8f03227a-ac76-441e-8af3-51b3658a7640", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"content\": \"뿌링클에서 핫후라이드로 변경\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/messages/e9b9ee0f-1a6d-4a23-bb65-590cebabc8da", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages", + "e9b9ee0f-1a6d-4a23-bb65-590cebabc8da" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/messages/channel/f2b44f44-294a-458a-8f91-6fb782caa936", + "id": "6d7332a6-1800-4051-928e-c3dc9aefe014", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/messages/channel/f2b44f44-294a-458a-8f91-6fb782caa936", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages", + "channel", + "f2b44f44-294a-458a-8f91-6fb782caa936" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/channels", + "id": "4af2f3d2-c65d-4c6d-b03a-ed8d8791f229", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/channels?userId=74b443a9-4625-4ec3-afab-cf1c1c48ea2d", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "channels" + ], + "query": [ + { + "key": "userId", + "value": "74b443a9-4625-4ec3-afab-cf1c1c48ea2d", + "uuid": "30a8e361-50bc-444a-8fe7-29bfd21ce1dc" + } + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/api/readStatus/create", + "id": "36d51a02-ff6a-4ea9-8880-2b3462be58d7", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"userId\": \"74b443a9-4625-4ec3-afab-cf1c1c48ea2d\",\r\n \"channelId\": \"f2b44f44-294a-458a-8f91-6fb782caa936\",\r\n \"lastReadAt\": \"2026-02-20T16:40:00Z\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/api/readStatus/create", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "readStatus", + "create" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/messages/channel/f2b44f44-294a-458a-8f91-6fb782caa936", + "id": "0716f44f-595d-4dba-8a8d-e581ece05759", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/messages/channel/f2b44f44-294a-458a-8f91-6fb782caa936", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "messages", + "channel", + "f2b44f44-294a-458a-8f91-6fb782caa936" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/channels/private", + "id": "ba30bc1d-181c-4f8a-ba44-4791a597008f", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text", + "uuid": "0f13933a-ee8e-493b-bb95-39e655ebef78" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"participantIds\": [\r\n \"74b443a9-4625-4ec3-afab-cf1c1c48ea2d\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/channels/private", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "channels", + "private" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/postman/globals/workspace.postman_globals.json b/postman/globals/workspace.postman_globals.json new file mode 100644 index 000000000..f0b66b053 --- /dev/null +++ b/postman/globals/workspace.postman_globals.json @@ -0,0 +1,7 @@ +{ + "id": "ea2830f0-5061-447c-a161-38ff1df425b8", + "name": "Globals", + "values": [], + "_postman_variable_scope": "globals", + "_postman_exported_at": "2026-02-13T10:05:15.483Z" +} \ 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 index ccbdd2f39..bb75460e0 100644 --- a/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java +++ b/src/main/java/com/sprint/mission/discodeit/DiscodeitApplication.java @@ -1,63 +1,14 @@ package com.sprint.mission.discodeit; -import com.sprint.mission.discodeit.dto.request.MessageCreateRequest; -import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; -import com.sprint.mission.discodeit.dto.request.UserCreateRequest; -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; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; - -import java.util.ArrayList; -import java.util.Optional; @SpringBootApplication public class DiscodeitApplication { - static User setupUser(UserService userService) { - UserCreateRequest request = new UserCreateRequest("woody", "woody@codeit.com", "woody1234"); - User user = userService.create(request, Optional.empty()); - return user; - } - - static Channel setupChannel(ChannelService channelService) { - PublicChannelCreateRequest request = new PublicChannelCreateRequest("공지", "공지 채널입니다."); - Channel channel = channelService.create(request); - return channel; - } - - static void messageCreateTest(MessageService messageService, Channel channel, User author) { - MessageCreateRequest request = new MessageCreateRequest("안녕하세요.", channel.getId(), author.getId()); - Message message = messageService.create(request, new ArrayList<>()); - System.out.println("메시지 생성: " + message.getId()); - } public static void main(String[] args) { + SpringApplication.run(DiscodeitApplication.class, 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/controller/AuthController.java b/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java new file mode 100644 index 000000000..5a65eac7f --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/AuthController.java @@ -0,0 +1,29 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.LoginRequest; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.service.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@RequiredArgsConstructor +@Controller +@ResponseBody +@RequestMapping("/api/auth") +public class AuthController { + + private final AuthService authService; + + @RequestMapping(path = "login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + User user = authService.login(loginRequest); + return ResponseEntity + .status(HttpStatus.OK) + .body(user); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java b/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java new file mode 100644 index 000000000..6ad182226 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java @@ -0,0 +1,39 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.entity.BinaryContent; +import com.sprint.mission.discodeit.service.BinaryContentService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/binaryContent") +public class BinaryContentController { + + private final BinaryContentService binaryContentService; + + @RequestMapping(path = "find") + public ResponseEntity find(@RequestParam("binaryContentId") UUID binaryContentId) { + BinaryContent binaryContent = binaryContentService.find(binaryContentId); + return ResponseEntity + .status(HttpStatus.OK) + .body(binaryContent); + } + + @RequestMapping(path = "findAllByIdIn") + public ResponseEntity> findAllByIdIn( + @RequestParam("binaryContentIds") List binaryContentIds) { + List binaryContents = binaryContentService.findAllByIdIn(binaryContentIds); + return ResponseEntity + .status(HttpStatus.OK) + .body(binaryContents); + } + + +} + diff --git a/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java b/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java new file mode 100644 index 000000000..8c4a0d9f3 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java @@ -0,0 +1,62 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.data.ChannelDto; +import com.sprint.mission.discodeit.dto.request.PrivateChannelCreateRequest; +import com.sprint.mission.discodeit.dto.request.PublicChannelCreateRequest; +import com.sprint.mission.discodeit.dto.request.PublicChannelUpdateRequest; +import org.springframework.web.bind.annotation.*; +import com.sprint.mission.discodeit.service.ChannelService; +import lombok.RequiredArgsConstructor; +import com.sprint.mission.discodeit.entity.Channel; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/channels") +public class ChannelController { + + private final ChannelService channelService; + + @RequestMapping(method = RequestMethod.POST, path = "/public") + public Channel createPublicChannel( + @RequestBody PublicChannelCreateRequest request + ) { + return channelService.create(request); + } + + @RequestMapping(method = RequestMethod.POST, path = "/private") + public Channel createPrivateChannel( + @RequestBody PrivateChannelCreateRequest request + ) { + return channelService.create(request); + } + + @RequestMapping(method = RequestMethod.GET) + public List findAllByUserId( + @RequestParam UUID userId + ) { + return channelService.findAllByUserId(userId); + } + + @RequestMapping(method = RequestMethod.GET, path = "/{channelId}") + public ChannelDto findById( + @PathVariable UUID channelId + ) { + return channelService.find(channelId); + } + + @RequestMapping(method = RequestMethod.PUT, path = "/{channelId}") + public Channel updatePublicChannel( + @PathVariable UUID channelId, + @RequestBody PublicChannelUpdateRequest request + ) { + return channelService.update(channelId, request); + } + + @RequestMapping(method = RequestMethod.DELETE, path = "/{channelId}") + public void deleteChannel(@PathVariable UUID channelId) { + channelService.delete(channelId); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java b/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java new file mode 100644 index 000000000..51885d203 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/MessageController.java @@ -0,0 +1,45 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.MessageCreateFullRequest; +import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest; +import com.sprint.mission.discodeit.entity.Message; +import com.sprint.mission.discodeit.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/messages") +public class MessageController { + + private final MessageService messageService; + + @PostMapping + public Message create(@RequestBody MessageCreateFullRequest request) { + return messageService.create( + request.message(), + request.attachments() == null ? List.of() : request.attachments() + ); + } + + @PatchMapping("/{messageId}") + public Message update( + @PathVariable UUID messageId, + @RequestBody MessageUpdateRequest request + ) { + return messageService.update(messageId, request); + } + + @DeleteMapping("/{messageId}") + public void delete(@PathVariable UUID messageId) { + messageService.delete(messageId); + } + + @GetMapping("/channel/{channelId}") + public List findAllByChannel(@PathVariable UUID channelId) { + return messageService.findAllByChannelId(channelId); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java b/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java new file mode 100644 index 000000000..e0cf53f30 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java @@ -0,0 +1,51 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.request.ReadStatusCreateRequest; +import com.sprint.mission.discodeit.dto.request.ReadStatusUpdateRequest; +import com.sprint.mission.discodeit.entity.ReadStatus; +import com.sprint.mission.discodeit.service.ReadStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@Controller +@ResponseBody +@RequestMapping("/api/readStatus") +public class ReadStatusController { + + private final ReadStatusService readStatusService; + + @RequestMapping(path = "create") + public ResponseEntity create(@RequestBody ReadStatusCreateRequest request) { + ReadStatus createdReadStatus = readStatusService.create(request); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdReadStatus); + } + + @RequestMapping(path = "update") + public ResponseEntity update(@RequestParam("readStatusId") UUID readStatusId, + @RequestBody ReadStatusUpdateRequest request) { + ReadStatus updatedReadStatus = readStatusService.update(readStatusId, request); + return ResponseEntity + .status(HttpStatus.OK) + .body(updatedReadStatus); + } + + @RequestMapping(path = "findAllByUserId") + public ResponseEntity> findAllByUserId(@RequestParam("userId") UUID userId) { + List readStatuses = readStatusService.findAllByUserId(userId); + return ResponseEntity + .status(HttpStatus.OK) + .body(readStatuses); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/controller/UserController.java b/src/main/java/com/sprint/mission/discodeit/controller/UserController.java new file mode 100644 index 000000000..e3efb6767 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/controller/UserController.java @@ -0,0 +1,107 @@ +package com.sprint.mission.discodeit.controller; + +import com.sprint.mission.discodeit.dto.data.UserDto; +import com.sprint.mission.discodeit.dto.request.BinaryContentCreateRequest; +import com.sprint.mission.discodeit.dto.request.UserCreateRequest; +import com.sprint.mission.discodeit.dto.request.UserStatusUpdateRequest; +import com.sprint.mission.discodeit.dto.request.UserUpdateRequest; +import com.sprint.mission.discodeit.entity.User; +import com.sprint.mission.discodeit.entity.UserStatus; +import com.sprint.mission.discodeit.service.UserService; +import com.sprint.mission.discodeit.service.UserStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.io.IOException; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/user") +public class UserController { + + private final UserService userService; + private final UserStatusService userStatusService; + + @RequestMapping( + path = "create", + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} + ) + public ResponseEntity createUser( + @RequestPart("userCreateRequest") UserCreateRequest userCreateRequest, + @RequestPart(value = "profile", required = false) MultipartFile profile + ) { + Optional profileRequest = Optional.ofNullable(profile) + .flatMap(this::resolveProfileRequest); + User createdUser = userService.create(userCreateRequest, profileRequest); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdUser); + } + + @RequestMapping( + path = "update", + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} + ) + public ResponseEntity update( + @RequestParam("userId") UUID userId, + @RequestPart("userUpdateRequest") UserUpdateRequest userUpdateRequest, + @RequestPart(value = "profile", required = false) MultipartFile profile + ) { + Optional profileRequest = Optional.ofNullable(profile) + .flatMap(this::resolveProfileRequest); + User updatedUser = userService.update(userId, userUpdateRequest, profileRequest); + return ResponseEntity + .status(HttpStatus.OK) + .body(updatedUser); + } + + @RequestMapping(path = "updateUserStatusByUserId") + public ResponseEntity updateUserStatusByUserId(@RequestParam("userId") UUID userId, + @RequestBody UserStatusUpdateRequest request) { + UserStatus updatedUserStatus = userStatusService.updateByUserId(userId, request); + return ResponseEntity + .status(HttpStatus.OK) + .body(updatedUserStatus); + } + + @RequestMapping(path = "findAll") + public ResponseEntity> findAll() { + List users = userService.findAll(); + return ResponseEntity + .status(HttpStatus.OK) + .body(users); + } + + @RequestMapping(path = "delete") + public ResponseEntity delete(@RequestParam("userId") UUID userId) { + userService.delete(userId); + return ResponseEntity + .status(HttpStatus.NO_CONTENT) + .build(); + } + + private Optional resolveProfileRequest(MultipartFile profileFile) { + if (profileFile.isEmpty()) { + return Optional.empty(); + } else { + try { + BinaryContentCreateRequest binaryContentCreateRequest = new BinaryContentCreateRequest( + profileFile.getOriginalFilename(), + profileFile.getContentType(), + profileFile.getBytes() + ); + return Optional.of(binaryContentCreateRequest); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateFullRequest.java b/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateFullRequest.java new file mode 100644 index 000000000..49eea64e9 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateFullRequest.java @@ -0,0 +1,10 @@ +package com.sprint.mission.discodeit.dto.request; + +import java.util.List; + +public record MessageCreateFullRequest( + MessageCreateRequest message, + List attachments +) { +} + diff --git a/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..59dc84c01 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java @@ -0,0 +1,35 @@ +package com.sprint.mission.discodeit.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.NoSuchElementException; + +@ControllerAdvice +@ResponseBody +public class GlobalExceptionHandler { + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleException(IllegalArgumentException e) + { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleException(NoSuchElementException e) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(e.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e){ + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(e.getMessage()); + } +} diff --git a/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java b/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java new file mode 100644 index 000000000..41f674966 --- /dev/null +++ b/src/main/java/com/sprint/mission/discodeit/repository/file/FileLockProvider.java @@ -0,0 +1,16 @@ +package com.sprint.mission.discodeit.repository.file; + +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; +import org.springframework.stereotype.Component; + +@Component +public class FileLockProvider { + private final Map locks = new ConcurrentHashMap<>(); + + public ReentrantLock getLock(Path path) { + return locks.computeIfAbsent(path, k -> new ReentrantLock()); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8329de47c..a0c7d1401 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,3 +1,4 @@ -spring: - application: - name: discodeit + +server: + port: 8080 + diff --git a/src/main/resources/static/BHC.png b/src/main/resources/static/BHC.png new file mode 100644 index 000000000..6ff4851f5 Binary files /dev/null and b/src/main/resources/static/BHC.png differ diff --git a/src/main/resources/static/GOOBNE.png b/src/main/resources/static/GOOBNE.png new file mode 100644 index 000000000..2bae12918 Binary files /dev/null and b/src/main/resources/static/GOOBNE.png differ diff --git a/src/main/resources/static/NENE.png b/src/main/resources/static/NENE.png new file mode 100644 index 000000000..dc4649021 Binary files /dev/null and b/src/main/resources/static/NENE.png differ diff --git a/src/main/resources/static/default-avatar.png b/src/main/resources/static/default-avatar.png new file mode 100644 index 000000000..aecb73692 Binary files /dev/null and b/src/main/resources/static/default-avatar.png differ diff --git a/src/main/resources/static/script.js b/src/main/resources/static/script.js new file mode 100644 index 000000000..e63118b89 --- /dev/null +++ b/src/main/resources/static/script.js @@ -0,0 +1,67 @@ +// API endpoints +const API_BASE_URL = '/api'; +const ENDPOINTS = { + USERS: `${API_BASE_URL}/user/findAll`, + BINARY_CONTENT: `${API_BASE_URL}/binaryContent/find` +}; + +// Initialize the application +document.addEventListener('DOMContentLoaded', () => { + fetchAndRenderUsers(); +}); + +// Fetch users from the API +async function fetchAndRenderUsers() { + try { + const response = await fetch(ENDPOINTS.USERS); + if (!response.ok) throw new Error('Failed to fetch users'); + const users = await response.json(); + renderUserList(users); + } catch (error) { + console.error('Error fetching users:', error); + } +} + +// Fetch user profile image +async function fetchUserProfile(profileId) { + try { + const response = await fetch(`${ENDPOINTS.BINARY_CONTENT}?binaryContentId=${profileId}`); + if (!response.ok) throw new Error('Failed to fetch profile'); + const profile = await response.json(); + + // Convert base64 encoded bytes to data URL + return `data:${profile.contentType};base64,${profile.bytes}`; + } catch (error) { + console.error('Error fetching profile:', error); + return '/default-avatar.png'; // Fallback to default avatar + } +} + +// Render user list +async function renderUserList(users) { + const userListElement = document.getElementById('userList'); + userListElement.innerHTML = ''; // Clear existing content + + for (const user of users) { + const userElement = document.createElement('div'); + userElement.className = 'user-item'; + + // Get profile image URL + const profileUrl = user.profileId ? + await fetchUserProfile(user.profileId) : + '/default-avatar.png'; + + userElement.innerHTML = ` + ${user.username} + +
+ ${user.online ? '온라인' : '오프라인'} +
+ `; + + userListElement.appendChild(userElement); + } +} \ No newline at end of file diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css new file mode 100644 index 000000000..b45f4e704 --- /dev/null +++ b/src/main/resources/static/styles.css @@ -0,0 +1,80 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} + +h1 { + text-align: center; + margin-bottom: 30px; + color: #333; +} + +.user-list { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.user-item { + display: flex; + align-items: center; + padding: 20px; + border-bottom: 1px solid #eee; +} + +.user-item:last-child { + border-bottom: none; +} + +.user-avatar { + width: 60px; + height: 60px; + border-radius: 50%; + margin-right: 20px; + object-fit: cover; +} + +.user-info { + flex-grow: 1; +} + +.user-name { + font-size: 18px; + font-weight: bold; + color: #333; + margin-bottom: 5px; +} + +.user-email { + font-size: 14px; + color: #666; +} + +.status-badge { + padding: 6px 12px; + border-radius: 20px; + font-size: 14px; + font-weight: bold; +} + +.online { + background-color: #4CAF50; + color: white; +} + +.offline { + background-color: #9e9e9e; + color: white; +} \ No newline at end of file diff --git a/src/main/resources/static/user-list.html b/src/main/resources/static/user-list.html new file mode 100644 index 000000000..f3acfdb59 --- /dev/null +++ b/src/main/resources/static/user-list.html @@ -0,0 +1,18 @@ + + + + + + 사용자 목록 + + + +
+

사용자 목록

+
+ +
+
+ + + \ No newline at end of file