├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── back-main-deploy.yml │ ├── back-main-test.yml │ ├── back-release-deploy.yml │ ├── back-release-test.yml │ ├── front-main.yml │ ├── front-release-deploy.yml │ ├── front-release-test.yml │ └── sonarcloud-analysis.yml ├── .gitignore ├── PREVIEW.md ├── README.md ├── back ├── babble │ ├── .gitignore │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ ├── docs │ │ ├── asciidoc │ │ │ └── api-docs.adoc │ │ └── custom-snippets │ │ │ ├── chat-receive │ │ │ ├── web-socket-request.adoc │ │ │ └── web-socket-response.adoc │ │ │ ├── chat-room-connect │ │ │ ├── web-socket-request.adoc │ │ │ └── web-socket-response.adoc │ │ │ ├── chat-room-update │ │ │ ├── web-socket-request.adoc │ │ │ └── web-socket-response.adoc │ │ │ ├── chat-send │ │ │ ├── web-socket-request.adoc │ │ │ └── web-socket-response.adoc │ │ │ └── user-list │ │ │ ├── web-socket-request.adoc │ │ │ └── web-socket-response.adoc │ │ ├── main │ │ ├── java │ │ │ └── gg │ │ │ │ └── babble │ │ │ │ └── babble │ │ │ │ ├── BabbleApplication.java │ │ │ │ ├── config │ │ │ │ ├── AdminAccessInterceptor.java │ │ │ │ ├── AmazonConfig.java │ │ │ │ ├── EmbeddedRedisConfig.java │ │ │ │ ├── PageableConfig.java │ │ │ │ ├── RedisConfig.java │ │ │ │ ├── StompHandler.java │ │ │ │ ├── WebConfig.java │ │ │ │ ├── WebSocketConfig.java │ │ │ │ └── datasource │ │ │ │ │ ├── ReplicationDataSourceConfig.java │ │ │ │ │ ├── ReplicationDataSourceProperties.java │ │ │ │ │ └── ReplicationRoutingDataSource.java │ │ │ │ ├── controller │ │ │ │ ├── BabbleAdvice.java │ │ │ │ ├── ChatController.java │ │ │ │ ├── DocsController.java │ │ │ │ ├── EntryController.java │ │ │ │ ├── GameController.java │ │ │ │ ├── ImageController.java │ │ │ │ ├── PostController.java │ │ │ │ ├── RoomController.java │ │ │ │ ├── SliderController.java │ │ │ │ ├── TagController.java │ │ │ │ ├── UserController.java │ │ │ │ ├── auth │ │ │ │ │ └── AdministratorController.java │ │ │ │ └── beta │ │ │ │ │ ├── GameBetaController.java │ │ │ │ │ └── TagBetaController.java │ │ │ │ ├── domain │ │ │ │ ├── FileName.java │ │ │ │ ├── Session.java │ │ │ │ ├── TagRegistration.java │ │ │ │ ├── admin │ │ │ │ │ ├── Administrator.java │ │ │ │ │ └── Ip.java │ │ │ │ ├── game │ │ │ │ │ ├── AlternativeGameName.java │ │ │ │ │ ├── AlternativeGameNames.java │ │ │ │ │ ├── Game.java │ │ │ │ │ └── Games.java │ │ │ │ ├── image │ │ │ │ │ ├── AutoGeneratedSize.java │ │ │ │ │ ├── ImageFile.java │ │ │ │ │ ├── ImageResolver.java │ │ │ │ │ ├── ImageSize.java │ │ │ │ │ └── JpegImage.java │ │ │ │ ├── message │ │ │ │ │ └── Content.java │ │ │ │ ├── post │ │ │ │ │ ├── Account.java │ │ │ │ │ ├── Category.java │ │ │ │ │ ├── Post.java │ │ │ │ │ └── PostSearchType.java │ │ │ │ ├── repository │ │ │ │ │ ├── AdministratorRepository.java │ │ │ │ │ ├── AlternativeGameNameRepository.java │ │ │ │ │ ├── AlternativeTagNameRepository.java │ │ │ │ │ ├── GameRepository.java │ │ │ │ │ ├── PostRepository.java │ │ │ │ │ ├── RoomRepository.java │ │ │ │ │ ├── S3Repository.java │ │ │ │ │ ├── SessionRepository.java │ │ │ │ │ ├── SliderRepository.java │ │ │ │ │ ├── TagRepository.java │ │ │ │ │ ├── UserRepository.java │ │ │ │ │ └── s3repository │ │ │ │ │ │ ├── AbstractS3Repository.java │ │ │ │ │ │ ├── DefaultS3Repository.java │ │ │ │ │ │ └── InMemoryS3Repository.java │ │ │ │ ├── room │ │ │ │ │ ├── MaxHeadCount.java │ │ │ │ │ ├── Room.java │ │ │ │ │ ├── Rooms.java │ │ │ │ │ ├── Sessions.java │ │ │ │ │ └── TagRegistrationsOfRoom.java │ │ │ │ ├── slider │ │ │ │ │ ├── ResourceUrl.java │ │ │ │ │ ├── Slider.java │ │ │ │ │ └── Sliders.java │ │ │ │ ├── tag │ │ │ │ │ ├── AlternativeTagName.java │ │ │ │ │ ├── AlternativeTagNames.java │ │ │ │ │ ├── Tag.java │ │ │ │ │ ├── TagName.java │ │ │ │ │ └── TagRegistrationsOfTag.java │ │ │ │ └── user │ │ │ │ │ ├── Nickname.java │ │ │ │ │ └── User.java │ │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── AdministratorRequest.java │ │ │ │ │ ├── GameCreateRequest.java │ │ │ │ │ ├── GameUpdateRequest.java │ │ │ │ │ ├── MessageRequest.java │ │ │ │ │ ├── RoomRequest.java │ │ │ │ │ ├── SessionRequest.java │ │ │ │ │ ├── SliderOrderRequest.java │ │ │ │ │ ├── SliderRequest.java │ │ │ │ │ ├── TagCreateRequest.java │ │ │ │ │ ├── TagRequest.java │ │ │ │ │ ├── TagUpdateRequest.java │ │ │ │ │ ├── UserRequest.java │ │ │ │ │ └── post │ │ │ │ │ │ ├── PostCreateRequest.java │ │ │ │ │ │ ├── PostDeleteRequest.java │ │ │ │ │ │ └── PostUpdateRequest.java │ │ │ │ └── response │ │ │ │ │ ├── AdministratorResponse.java │ │ │ │ │ ├── AlternativeGameNameResponse.java │ │ │ │ │ ├── AlternativeTagNameResponse.java │ │ │ │ │ ├── ChatResponse.java │ │ │ │ │ ├── CreatedRoomResponse.java │ │ │ │ │ ├── EntryResponse.java │ │ │ │ │ ├── FoundRoomResponse.java │ │ │ │ │ ├── GameImageResponse.java │ │ │ │ │ ├── GameNameResponse.java │ │ │ │ │ ├── GameResponse.java │ │ │ │ │ ├── GameWithImageResponse.java │ │ │ │ │ ├── HeadCountResponse.java │ │ │ │ │ ├── IndexPageGameResponse.java │ │ │ │ │ ├── MessageResponse.java │ │ │ │ │ ├── PostBaseResponse.java │ │ │ │ │ ├── PostResponse.java │ │ │ │ │ ├── PostSearchResponse.java │ │ │ │ │ ├── PostWithoutContentResponse.java │ │ │ │ │ ├── SessionsResponse.java │ │ │ │ │ ├── SliderResponse.java │ │ │ │ │ ├── TagNameResponse.java │ │ │ │ │ ├── TagResponse.java │ │ │ │ │ └── UserResponse.java │ │ │ │ ├── exception │ │ │ │ ├── BabbleAuthenticationException.java │ │ │ │ ├── BabbleDuplicatedException.java │ │ │ │ ├── BabbleException.java │ │ │ │ ├── BabbleIOException.java │ │ │ │ ├── BabbleIllegalArgumentException.java │ │ │ │ ├── BabbleIllegalStatementException.java │ │ │ │ ├── BabbleLengthException.java │ │ │ │ └── BabbleNotFoundException.java │ │ │ │ ├── service │ │ │ │ ├── ChatService.java │ │ │ │ ├── EntryService.java │ │ │ │ ├── GameService.java │ │ │ │ ├── ImageService.java │ │ │ │ ├── PostService.java │ │ │ │ ├── RoomService.java │ │ │ │ ├── SliderService.java │ │ │ │ ├── TagService.java │ │ │ │ ├── UserService.java │ │ │ │ ├── auth │ │ │ │ │ ├── AdministratorService.java │ │ │ │ │ └── SubscribeAuthService.java │ │ │ │ └── redis │ │ │ │ │ ├── ChatSubscriber.java │ │ │ │ │ ├── EntrySubscriber.java │ │ │ │ │ └── RedisListener.java │ │ │ │ └── util │ │ │ │ └── UrlParser.java │ │ └── resources │ │ │ ├── application-dev.yml │ │ │ ├── application-local.yml │ │ │ ├── application-prod.yml │ │ │ ├── application-test.yml │ │ │ ├── application.yml │ │ │ ├── db │ │ │ └── migration │ │ │ │ ├── V10__tag_add_column_deleted.sql │ │ │ │ ├── V11__soft_delete.sql │ │ │ │ ├── V12__change_timestamp.sql │ │ │ │ ├── V13__add_table_post.sql │ │ │ │ ├── V14__refactoring.sql │ │ │ │ ├── V15__refactoring.sql │ │ │ │ ├── V1__init.sql │ │ │ │ ├── V2__add_column_deleted.sql │ │ │ │ ├── V3__add_table_administrator.sql │ │ │ │ ├── V4__refactoring.sql │ │ │ │ ├── V5__add_table_alternativeGameName.sql │ │ │ │ ├── V6__delete_column_roomId.sql │ │ │ │ ├── V7__add_table_alternativeTagName.sql │ │ │ │ ├── V8__add_table_gameImages.sql │ │ │ │ └── V9__add_table_slider.sql │ │ │ ├── logback-access.xml │ │ │ ├── logback-spring.xml │ │ │ └── logback │ │ │ ├── console-appender │ │ │ ├── console-access-appender.xml │ │ │ ├── console-db-appender.xml │ │ │ └── console-log-appender.xml │ │ │ └── file-appender │ │ │ ├── file-access-appender.xml │ │ │ ├── file-debug-appender.xml │ │ │ ├── file-error-appender.xml │ │ │ ├── file-info-appender.xml │ │ │ └── file-warn-appender.xml │ │ └── test │ │ ├── java │ │ └── gg │ │ │ └── babble │ │ │ └── babble │ │ │ ├── ApplicationTest.java │ │ │ ├── domain │ │ │ ├── FileNameTest.java │ │ │ ├── SessionTest.java │ │ │ ├── UserTest.java │ │ │ ├── game │ │ │ │ ├── AlternativeGameNameTest.java │ │ │ │ ├── AlternativeGameNamesTest.java │ │ │ │ ├── GameTest.java │ │ │ │ └── GamesTest.java │ │ │ ├── image │ │ │ │ ├── ImageResolverTest.java │ │ │ │ ├── ImageSizeTest.java │ │ │ │ └── JpegImageTest.java │ │ │ ├── post │ │ │ │ └── PostTest.java │ │ │ ├── repository │ │ │ │ ├── AdministratorRepositoryTest.java │ │ │ │ ├── GameRepositoryTest.java │ │ │ │ ├── PostRepositoryTest.java │ │ │ │ ├── RoomRepositoryTest.java │ │ │ │ ├── S3RepositoryTest.java │ │ │ │ ├── SessionRepositoryTest.java │ │ │ │ ├── TagRepositoryTest.java │ │ │ │ └── UserRepositoryTest.java │ │ │ ├── room │ │ │ │ ├── MaxHeadCountTest.java │ │ │ │ ├── RoomTest.java │ │ │ │ └── RoomsTest.java │ │ │ ├── slider │ │ │ │ └── SlidersTest.java │ │ │ └── tag │ │ │ │ ├── AlternativeTagNameTest.java │ │ │ │ ├── AlternativeTagNamesTest.java │ │ │ │ ├── TagNameTest.java │ │ │ │ └── TagTest.java │ │ │ ├── restdocs │ │ │ ├── AcceptanceTest.java │ │ │ ├── AdministratorApiDocumentTest.java │ │ │ ├── GameApiDocumentTest.java │ │ │ ├── ImageApiDocumentTest.java │ │ │ ├── PostApiDocumentTest.java │ │ │ ├── RoomApiDocumentTest.java │ │ │ ├── SliderApiDocumentTest.java │ │ │ ├── TagApiDocumentTest.java │ │ │ ├── UserApiDocumentTest.java │ │ │ ├── client │ │ │ │ ├── Identifiable.java │ │ │ │ └── ResponseRepository.java │ │ │ ├── preprocessor │ │ │ │ ├── BigListPreprocessor.java │ │ │ │ └── ImageBodyPreprocessor.java │ │ │ └── websocket │ │ │ │ ├── ChattingTest.java │ │ │ │ └── CustomWebSocketTransport.java │ │ │ ├── service │ │ │ ├── EnterExitServiceTest.java │ │ │ ├── GameServiceTest.java │ │ │ ├── PostServiceTest.java │ │ │ ├── RoomServiceTest.java │ │ │ ├── SliderServiceTest.java │ │ │ ├── SubscribeAuthServiceTest.java │ │ │ ├── TagServiceTest.java │ │ │ └── UserServiceTest.java │ │ │ └── util │ │ │ └── UrlParserTest.java │ │ └── resources │ │ ├── application.yml │ │ └── test-image.jpg └── deploy │ └── was │ └── Dockerfile ├── front ├── .babelrc ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .releaserc.json ├── .storybook │ ├── babbleTheme.js │ ├── main.js │ ├── manager.js │ └── preview.js ├── .vscode │ └── settings.json ├── CHANGELOG.md ├── cSpell.json ├── cypress.json ├── cypress │ ├── fixtures │ │ └── example.json │ ├── integration │ │ └── App.spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js ├── index.html ├── index.js ├── mockData.json ├── package-lock.json ├── package.json ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── manifest.json │ ├── ogimg.png │ └── readme │ │ └── github_readme.png ├── src │ ├── App.js │ ├── chunks │ │ ├── AdminManagement │ │ │ ├── AdminManagement.js │ │ │ ├── AdminManagement.scss │ │ │ └── AdminManagement.stories.js │ │ ├── Chatbox │ │ │ ├── Chatbox.js │ │ │ ├── Chatbox.scss │ │ │ └── Chatbox.stories.js │ │ ├── GameManagement │ │ │ ├── GameManagement.js │ │ │ ├── GameManagement.scss │ │ │ └── GameManagement.stories.js │ │ ├── LikeAndView │ │ │ ├── LikeAndView.js │ │ │ ├── LikeAndView.scss │ │ │ └── LikeAndView.stories.js │ │ ├── Participants │ │ │ ├── Participants.js │ │ │ ├── Participants.scss │ │ │ └── Participants.stories.js │ │ ├── TableContent │ │ │ ├── TableContent.js │ │ │ ├── TableContent.scss │ │ │ └── TableContent.stories.js │ │ ├── TagList │ │ │ ├── TagList.js │ │ │ ├── TagList.scss │ │ │ └── TagList.stories.js │ │ ├── TagManagement │ │ │ ├── TagManagement.js │ │ │ ├── TagManagement.scss │ │ │ └── TagManagement.stories.js │ │ ├── WritingBlock │ │ │ ├── WritingBlock.js │ │ │ ├── WritingBlock.scss │ │ │ └── WritingBlock.stories.js │ │ └── index.js │ ├── components │ │ ├── Avatar │ │ │ ├── Avatar.js │ │ │ ├── Avatar.scss │ │ │ ├── Avatar.stories.js │ │ │ └── AvatarImage.js │ │ ├── Badge │ │ │ ├── Badge.js │ │ │ ├── Badge.scss │ │ │ ├── Badge.stories.js │ │ │ └── BadgeClickable.js │ │ ├── Button │ │ │ ├── Button.scss │ │ │ ├── Button.stories.js │ │ │ ├── RoundButton.js │ │ │ └── SquareButton.js │ │ ├── ChattingForm │ │ │ ├── ChattingForm.js │ │ │ ├── ChattingForm.scss │ │ │ └── ChattingForm.stories.js │ │ ├── ErrorBoundary │ │ │ ├── ErrorBoundary.js │ │ │ └── ErrorComponent.js │ │ ├── Footer │ │ │ ├── Footer.js │ │ │ └── Footer.scss │ │ ├── GameCard │ │ │ ├── GameCard.js │ │ │ ├── GameCard.scss │ │ │ └── GameCard.stories.js │ │ ├── ImagePreview │ │ │ ├── ImagePreview.js │ │ │ ├── ImagePreview.scss │ │ │ └── ImagePreview.stories.js │ │ ├── ImageRegister │ │ │ ├── ImageRegister.js │ │ │ ├── ImageRegister.scss │ │ │ └── ImageRegister.stories.js │ │ ├── InfoWithIcon │ │ │ ├── InfoWithIcon.js │ │ │ ├── InfoWithIcon.scss │ │ │ └── InfoWithIcon.stories.js │ │ ├── KakaoShareButton │ │ │ ├── KakaoShareButton.js │ │ │ └── KakaoShareButton.scss │ │ ├── Main │ │ │ ├── Main.js │ │ │ └── Main.scss │ │ ├── MainImage │ │ │ ├── MainImage.js │ │ │ ├── MainImage.scss │ │ │ └── MainImage.stories.js │ │ ├── Modal │ │ │ ├── Modal.js │ │ │ ├── Modal.scss │ │ │ ├── ModalAlert.js │ │ │ ├── ModalConfirm.js │ │ │ ├── ModalCustom.scss │ │ │ ├── ModalError.js │ │ │ ├── ModalMinimized.js │ │ │ └── ModalSubmit.js │ │ ├── NameList │ │ │ ├── NameList.js │ │ │ ├── NameList.scss │ │ │ └── NameList.stories.js │ │ ├── NavBar │ │ │ ├── NavBar.js │ │ │ └── NavBar.scss │ │ ├── NicknameSection │ │ │ ├── NicknameSection.js │ │ │ ├── NicknameSection.scss │ │ │ └── NicknameSection.stories.js │ │ ├── NotFound │ │ │ ├── NotFound.js │ │ │ └── NotFound.scss │ │ ├── Room │ │ │ ├── Room.js │ │ │ ├── Room.scss │ │ │ └── Room.stories.js │ │ ├── ScrollToTop │ │ │ └── ScrollToTop.js │ │ ├── SearchInput │ │ │ ├── DropdownInput.js │ │ │ ├── SearchInput.js │ │ │ ├── SearchInput.scss │ │ │ ├── SearchInput.stories.js │ │ │ ├── TextInput.js │ │ │ ├── TextSearchInput.js │ │ │ └── service │ │ │ │ ├── constant.js │ │ │ │ ├── escapeRegExp.js │ │ │ │ ├── getKorRegExp.js │ │ │ │ └── getPhonemes.js │ │ ├── Slider │ │ │ ├── Slider.js │ │ │ ├── Slider.scss │ │ │ └── Slider.stories.js │ │ ├── SpeechBubble │ │ │ ├── SpeechBubble.js │ │ │ ├── SpeechBubble.scss │ │ │ ├── SpeechBubble.stories.js │ │ │ └── SpeechBubbleWithAvatar.js │ │ ├── Tag │ │ │ ├── Tag.js │ │ │ ├── Tag.scss │ │ │ ├── Tag.stories.js │ │ │ └── TagErasable.js │ │ ├── ThemeMode │ │ │ ├── ThemeMode.js │ │ │ └── ThemeMode.scss │ │ ├── ToggleNotification │ │ │ ├── ToggleNotification.js │ │ │ └── ToggleNotification.scss │ │ └── index.js │ ├── constants │ │ ├── api.js │ │ ├── board.js │ │ ├── chat.js │ │ ├── event.js │ │ ├── i18n.js │ │ ├── notification.js │ │ ├── path.js │ │ └── regex.js │ ├── contexts │ │ ├── ChattingModalProvider.js │ │ ├── DefaultModalProvider.js │ │ ├── PushNotificationProvider.js │ │ ├── ThemeChangeProvider.js │ │ └── UserProvider.js │ ├── core │ │ ├── Layout │ │ │ ├── LinearLayout.js │ │ │ ├── LinearLayout.scss │ │ │ ├── PageLayout.js │ │ │ └── PageLayout.scss │ │ ├── Logo │ │ │ ├── Logo.js │ │ │ ├── Logo.scss │ │ │ └── Logo.stories.js │ │ └── Typography │ │ │ ├── Body1.js │ │ │ ├── Body2.js │ │ │ ├── Caption1.js │ │ │ ├── Caption2.js │ │ │ ├── Headline1.js │ │ │ ├── Headline2.js │ │ │ ├── Subtitle1.js │ │ │ ├── Subtitle2.js │ │ │ ├── Subtitle3.js │ │ │ ├── Typography.scss │ │ │ ├── Typography.stories.js │ │ │ └── index.js │ ├── hooks │ │ ├── useDebounce.js │ │ ├── useInterval.js │ │ ├── useScript.js │ │ ├── useTabNotification.js │ │ ├── useTheme.js │ │ ├── useThrottle.js │ │ └── useUpdateEffect.js │ ├── pages │ │ ├── BabbleManagement │ │ │ ├── BabbleManagement.js │ │ │ └── BabbleManagement.scss │ │ ├── Board │ │ │ ├── Board.js │ │ │ └── Board.scss │ │ ├── ChangeNickname │ │ │ ├── ChangeNickname.js │ │ │ └── ChangeNickname.scss │ │ ├── ChattingRoom │ │ │ ├── ChattingRoom.js │ │ │ └── ChattingRoom.scss │ │ ├── GameList │ │ │ ├── GameList.js │ │ │ └── GameList.scss │ │ ├── MakeRoom │ │ │ ├── MakeRoom.js │ │ │ └── MakeRoom.scss │ │ ├── RoomList │ │ │ ├── RoomList.js │ │ │ └── RoomList.scss │ │ ├── ViewPost │ │ │ ├── ViewPost.js │ │ │ └── ViewPost.scss │ │ └── WritePost │ │ │ ├── WritePost.js │ │ │ └── WritePost.scss │ └── utils │ │ ├── id.js │ │ └── storage.js ├── style │ ├── _animations.scss │ ├── _functions.scss │ ├── _mixins.scss │ ├── _variables.scss │ ├── fonts.scss │ ├── fonts │ │ ├── SpoqaHanSansNeoBold.ttf │ │ ├── SpoqaHanSansNeoBold.woff │ │ ├── SpoqaHanSansNeoBold.woff2 │ │ ├── SpoqaHanSansNeoMedium.ttf │ │ ├── SpoqaHanSansNeoMedium.woff │ │ ├── SpoqaHanSansNeoMedium.woff2 │ │ ├── SpoqaHanSansNeoRegular.ttf │ │ ├── SpoqaHanSansNeoRegular.woff │ │ └── SpoqaHanSansNeoRegular.woff2 │ └── global.scss └── webpack.config.js ├── images ├── Desktop.gif ├── babble_be_cicd.png ├── babble_fe_cicd.png ├── babble_production_infra.png ├── babble_tech_stack.png ├── 게시글검색.gif ├── 게시글삭제.gif ├── 게시글수정.gif ├── 게시글작성.gif ├── 게시글조회.gif ├── 게임검색.gif ├── 닉네임변경.gif ├── 댓글작성.gif ├── 방검색.gif ├── 방생성.gif ├── 채팅방.gif └── 태그.gif └── infrastructure ├── db └── babble.sql ├── sonarqube └── docker-compose.yml ├── was-logs └── config.json └── web-server ├── Dockerfile ├── awslogs-agent-setup.py ├── docker-compose.yml └── nginx.conf /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Closes # 2 | 3 | ## 😎 캡쳐본(프론트) 4 | 5 | ## 🔥 구현 내용 요약 6 | 7 | ## ☠ 트러블 슈팅 8 | -------------------------------------------------------------------------------- /.github/workflows/back-main-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Backend main branch test with gradle 5 | 6 | on: 7 | pull_request: 8 | branches: [ main ] 9 | paths: 10 | - 'back/**' 11 | 12 | jobs: 13 | deploy-build: 14 | runs-on: deploy 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 8 19 | uses: actions/setup-java@v2 20 | with: 21 | java-version: '8' 22 | distribution: 'adopt' 23 | 24 | - name: gradlew 권한 변경 25 | working-directory: ./back/babble 26 | run: chmod +x gradlew 27 | 28 | - name: 테스트 진행 29 | working-directory: ./back/babble 30 | run: SPRING_DATASOURCE_PASSWORD=${{ secrets.JUNIT_DB_PASSWORD }} SPRING_REDIS_PASSWORD=${{ secrets.SPRING_REDIS_PASSWORD }} ./gradlew test 31 | -------------------------------------------------------------------------------- /.github/workflows/back-release-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Backend release branch test with gradle 5 | 6 | on: 7 | pull_request: 8 | branches: [ release ] 9 | paths: 10 | - 'back/**' 11 | 12 | jobs: 13 | deploy-build: 14 | runs-on: deploy 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 8 19 | uses: actions/setup-java@v2 20 | with: 21 | java-version: '8' 22 | distribution: 'adopt' 23 | 24 | - name: gradlew 권한 변경 25 | working-directory: ./back/babble 26 | run: chmod +x gradlew 27 | 28 | - name: 테스트 진행 29 | working-directory: ./back/babble 30 | run: SPRING_DATASOURCE_PASSWORD=${{ secrets.JUNIT_DB_PASSWORD }} SPRING_REDIS_PASSWORD=${{ secrets.SPRING_REDIS_PASSWORD }} ./gradlew test 31 | -------------------------------------------------------------------------------- /.github/workflows/front-release-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | push: 5 | branches: [release] 6 | paths: 7 | - 'front/**' 8 | 9 | jobs: 10 | build: 11 | runs-on: deploy 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: 14 17 | 18 | - name: env 파일 만들기 19 | working-directory: ./front 20 | shell: bash 21 | env: 22 | ENV_API_KEY: ${{ secrets.KAKAO_KEY }} 23 | ENV_DARASS_KEY: ${{ secrets.DARASS_KEY }} 24 | run: | 25 | touch .env 26 | echo REACT_APP_KAKAO_KEY="$ENV_API_KEY" >> .env 27 | echo REACT_APP_DARASS_KEY="$ENV_DARASS_KEY" >> .env 28 | cat .env 29 | 30 | - name: 패키지 설치 31 | working-directory: ./front 32 | run: npm install 33 | 34 | - name: 빌드 35 | working-directory: ./front 36 | run: npm run prod 37 | 38 | - name: 배포 39 | working-directory: ./front 40 | run: aws s3 sync ./dist s3://bucket-babble-front-test/ 41 | 42 | - name: 캐시 무효화 43 | working-directory: ./front 44 | run: aws cloudfront create-invalidation --distribution-id ${{ secrets.RELEASE_DISTRIBUTION_ID }} --paths "/index.html" "/bundle.js" 45 | -------------------------------------------------------------------------------- /.github/workflows/front-release-test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | pull_request: 5 | branches: [release] 6 | paths: 7 | - 'front/**' 8 | 9 | jobs: 10 | build: 11 | runs-on: deploy 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: 14 17 | 18 | - name: env 파일 만들기 19 | working-directory: ./front 20 | shell: bash 21 | env: 22 | ENV_API_KEY: ${{ secrets.KAKAO_KEY }} 23 | ENV_DARASS_KEY: ${{ secrets.DARASS_KEY }} 24 | run: | 25 | touch .env 26 | echo REACT_APP_KAKAO_KEY="$ENV_API_KEY" >> .env 27 | echo REACT_APP_DARASS_KEY="$ENV_DARASS_KEY" >> .env 28 | cat .env 29 | 30 | - name: 패키지 설치 31 | working-directory: ./front 32 | run: npm install 33 | 34 | - name: 빌드 35 | working-directory: ./front 36 | run: npm run prod 37 | 38 | - name: Chrome E2E Test 39 | uses: cypress-io/github-action@v2 40 | with: 41 | working-directory: front 42 | start: npm run start 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | .DS_* 4 | *.log -------------------------------------------------------------------------------- /PREVIEW.md: -------------------------------------------------------------------------------- 1 | ## 세부 기능 보기 2 | 3 | | **게임 검색** | **채팅방 검색** | 4 | | :------------------------------------: | :------------------------------------: | 5 | | ![게임검색](./images/게임검색.gif) | ![방검색](./images/방검색.gif) | 6 | | **닉네임 변경** | **채팅 진행** | 7 | | ![닉네임변경](./images/닉네임변경.gif) | ![채팅방](./images/채팅방.gif) | 8 | | **게시글 작성** | **게시물 조회** | 9 | | ![게시글작성](./images/게시글작성.gif) | ![게시글조회](./images/게시글조회.gif) | 10 | | **게시글 수정** | **게시물 삭제** | 11 | | ![게시글수정](./images/게시글수정.gif) | ![게시글삭제](./images/게시글삭제.gif) | 12 | | **채팅방 생성** | **게시글 검색** | 13 | | ![방생성](./images/방생성.gif) | ![게시글검색](./images/게시글검색.gif) | 14 | | **댓글 작성** | **관리자 - 태그 등록** | 15 | | ![댓글작성](./images/댓글작성.gif) | ![태그 등록](./images/태그.gif) | 16 | -------------------------------------------------------------------------------- /back/babble/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### logs ### 40 | /was-logs/ 41 | -------------------------------------------------------------------------------- /back/babble/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/back/babble/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /back/babble/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | org.gradle.jvmargs=log4j2.formatMsgNoLookups=true 7 | -------------------------------------------------------------------------------- /back/babble/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'babble' 2 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-receive/web-socket-request.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | GET /topic/rooms/1/chat HTTP/1.1 4 | ---- 5 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-receive/web-socket-response.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | HTTP/1.1 200 OK 4 | Vary: Origin 5 | Vary: Access-Control-Request-Method 6 | Vary: Access-Control-Request-Headers 7 | Location: /topic/rooms/1/chat 8 | Content-Type: application/json 9 | Content-Length: 144 10 | 11 | { 12 | "user": { 13 | "id": 1, 14 | "nickname": "그루밍", 15 | "avatar" : https://d2bidcnq0n74fu.cloudfront.net/1.jpg 16 | }, 17 | "content": "그루밍은 졸려" 18 | } 19 | ---- 20 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-room-connect/web-socket-request.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | GET /connection HTTP/1.1 4 | ---- 5 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-room-connect/web-socket-response.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | HTTP/1.1 101 Switching Protocols 4 | ---- 5 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-room-update/web-socket-request.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | SEND /ws/rooms/1/users 4 | Content-Type: application/json 5 | 6 | { 7 | "userId": 1, 8 | "sessionId": "35B1UD9A" 9 | } 10 | ---- 11 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-room-update/web-socket-response.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | HTTP/1.1 200 OK 4 | Vary: Origin 5 | Vary: Access-Control-Request-Method 6 | Vary: Access-Control-Request-Headers 7 | Location: /ws/rooms/1/users 8 | Content-Type: application/json 9 | Content-Length: 48 10 | 11 | { 12 | "userId": 1, 13 | "sessionId": "35B1UD9A" 14 | } 15 | ---- 16 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-send/web-socket-request.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | /ws/rooms/1/chat 4 | Content-Type: application/json 5 | 6 | { 7 | "userId": 1, 8 | "content": "그루밍은 졸려", 9 | "type" : "chat" 10 | } 11 | ---- 12 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/chat-send/web-socket-response.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | SUBSCRIBE /topic/rooms/1/chat 4 | Content-Type: application/json 5 | 6 | { 7 | "user": { 8 | "id": 1, 9 | "nickname": "그루밍", 10 | "avatar" : https://d2bidcnq0n74fu.cloudfront.net/1.jpg 11 | }, 12 | "content": "그루밍은 졸려", 13 | "type" : "chat" 14 | } 15 | ---- 16 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/user-list/web-socket-request.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | GET /topic/rooms/1/users HTTP/1.1 4 | ---- 5 | -------------------------------------------------------------------------------- /back/babble/src/docs/custom-snippets/user-list/web-socket-response.adoc: -------------------------------------------------------------------------------- 1 | [source,http,options="nowrap"] 2 | ---- 3 | SUBSCRIBE /topic/rooms/1/users 4 | Content-Type: application/json 5 | 6 | { 7 | "host" : { 8 | "id": 1, 9 | "nickname": "wilder", 10 | "avatar" : https://d2bidcnq0n74fu.cloudfront.net/avatar-1.jpg 11 | }, 12 | "guests": [ 13 | { 14 | "id" : 2, 15 | "nickname": "hyeon9mak", 16 | "avatar" : https://d2bidcnq0n74fu.cloudfront.net/avatar-2.jpg 17 | }, 18 | { 19 | "id" : 3, 20 | "nickname": "jason", 21 | "avatar" : https://d2bidcnq0n74fu.cloudfront.net/avatar-3.jpg 22 | } 23 | ] 24 | } 25 | ---- 26 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/BabbleApplication.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | @EnableJpaAuditing 8 | @SpringBootApplication 9 | public class BabbleApplication { 10 | 11 | public static void main(final String[] args) { 12 | SpringApplication.run(BabbleApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/config/AmazonConfig.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.config; 2 | 3 | import com.amazonaws.services.s3.AmazonS3; 4 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Profile; 9 | 10 | @Profile("!local") 11 | @Configuration 12 | public class AmazonConfig { 13 | 14 | @Value("${cloud.aws.region.static}") 15 | private String region; 16 | 17 | @Bean 18 | public AmazonS3 amazonS3() { 19 | return AmazonS3ClientBuilder.standard() 20 | .withRegion(region) 21 | .build(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/config/EmbeddedRedisConfig.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.config; 2 | 3 | import javax.annotation.PostConstruct; 4 | import javax.annotation.PreDestroy; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Profile; 8 | import redis.embedded.RedisServer; 9 | 10 | @Profile("local") 11 | @Configuration 12 | public class EmbeddedRedisConfig { 13 | 14 | @Value("${spring.redis.port}") 15 | private int redisPort; 16 | 17 | private RedisServer redisServer; 18 | 19 | @PostConstruct 20 | public void postConstruct() { 21 | redisServer = new RedisServer(redisPort); 22 | redisServer.start(); 23 | } 24 | 25 | @PreDestroy 26 | public void preDestroy() { 27 | redisServer.stop(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/config/PageableConfig.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.config; 2 | 3 | import java.util.List; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.web.PageableHandlerMethodArgumentResolver; 6 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class PageableConfig implements WebMvcConfigurer { 11 | 12 | private static final int MAX_PAGE_SIZE = 16; 13 | 14 | @Override 15 | public void addArgumentResolvers(List argumentResolvers) { 16 | PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(); 17 | resolver.setOneIndexedParameters(true); 18 | resolver.setMaxPageSize(MAX_PAGE_SIZE); 19 | argumentResolvers.add(resolver); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.config; 2 | 3 | import gg.babble.babble.service.auth.AdministratorService; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class WebConfig implements WebMvcConfigurer { 11 | 12 | private final AdministratorService administratorService; 13 | 14 | public WebConfig(final AdministratorService administratorService) { 15 | this.administratorService = administratorService; 16 | } 17 | 18 | @Override 19 | public void addInterceptors(InterceptorRegistry registry) { 20 | registry.addInterceptor(new AdminAccessInterceptor(administratorService)) 21 | .addPathPatterns("/api/games/**", "/api/tags/**", "/api/sliders/**", "/api/admins/**"); 22 | } 23 | 24 | @Override 25 | public void addCorsMappings(final CorsRegistry registry) { 26 | registry.addMapping("/**") 27 | .allowedMethods("*") 28 | .allowedOriginPatterns("*"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/config/datasource/ReplicationDataSourceProperties.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.config.datasource; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | 9 | @Setter 10 | @Getter 11 | @ConfigurationProperties(prefix = "spring.datasource") 12 | public class ReplicationDataSourceProperties { 13 | 14 | private String driverClassName; 15 | private String url; 16 | private String username; 17 | private String password; 18 | private final Map slaves = new HashMap<>(); 19 | 20 | @Setter 21 | @Getter 22 | public static class Slave { 23 | 24 | private String name; 25 | private String driverClassName; 26 | private String url; 27 | private String username; 28 | private String password; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/controller/ChatController.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.controller; 2 | 3 | import gg.babble.babble.dto.request.MessageRequest; 4 | import gg.babble.babble.service.ChatService; 5 | import javax.validation.Valid; 6 | import org.springframework.messaging.handler.annotation.DestinationVariable; 7 | import org.springframework.messaging.handler.annotation.MessageMapping; 8 | import org.springframework.stereotype.Controller; 9 | 10 | @Controller 11 | public class ChatController { 12 | 13 | private final ChatService chatService; 14 | 15 | public ChatController(final ChatService chatService) { 16 | this.chatService = chatService; 17 | } 18 | 19 | @MessageMapping("/rooms/{roomId}/chat") 20 | public void chat(@DestinationVariable final Long roomId, @Valid final MessageRequest messageRequest) { 21 | chatService.sendChatMessage(roomId, messageRequest); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/controller/DocsController.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @Controller 7 | public class DocsController { 8 | 9 | private static final String API_DOCS_PATH = "docs/api-docs.html"; 10 | 11 | @GetMapping("/") 12 | public String getApiDocs() { 13 | return API_DOCS_PATH; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/controller/EntryController.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.controller; 2 | 3 | import gg.babble.babble.dto.request.SessionRequest; 4 | import gg.babble.babble.service.EntryService; 5 | import javax.validation.Valid; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.messaging.handler.annotation.DestinationVariable; 8 | import org.springframework.messaging.handler.annotation.MessageMapping; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.socket.messaging.SessionDisconnectEvent; 11 | 12 | @Controller 13 | public class EntryController { 14 | 15 | private final EntryService entryService; 16 | 17 | public EntryController(final EntryService entryService) { 18 | this.entryService = entryService; 19 | } 20 | 21 | @MessageMapping("/rooms/{roomId}/users") 22 | public void enter(@DestinationVariable final Long roomId, @Valid final SessionRequest sessionRequest) { 23 | entryService.enter(roomId, sessionRequest); 24 | } 25 | 26 | @EventListener 27 | public void exit(final SessionDisconnectEvent event) { 28 | entryService.exit(event.getSessionId()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.controller; 2 | 3 | import gg.babble.babble.dto.request.UserRequest; 4 | import gg.babble.babble.dto.response.UserResponse; 5 | import gg.babble.babble.service.UserService; 6 | import javax.validation.Valid; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RequestMapping(value = "/api/users") 14 | @RestController 15 | public class UserController { 16 | 17 | private final UserService userService; 18 | 19 | public UserController(final UserService userService) { 20 | this.userService = userService; 21 | } 22 | 23 | @PostMapping 24 | public ResponseEntity create(@Valid @RequestBody final UserRequest userRequest) { 25 | return ResponseEntity.ok(userService.save(userRequest)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/controller/beta/TagBetaController.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.controller.beta; 2 | 3 | import gg.babble.babble.dto.response.TagNameResponse; 4 | import gg.babble.babble.service.TagService; 5 | import java.util.List; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RequestMapping(value = "/api/beta/tags") 13 | @RestController 14 | public class TagBetaController { 15 | 16 | private final TagService tagService; 17 | 18 | public TagBetaController(final TagService tagService) { 19 | this.tagService = tagService; 20 | } 21 | 22 | @GetMapping("/names") 23 | public ResponseEntity> findTagNames(@RequestParam(defaultValue = "") final String keyword) { 24 | return ResponseEntity.ok(tagService.findTagNames(keyword)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/FileName.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain; 2 | 3 | import gg.babble.babble.exception.BabbleIllegalArgumentException; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | public class FileName { 10 | 11 | private static final char FILE_EXTENSION_DELIMITER = '.'; 12 | private static final int SIMPLE_NAME_INDEX = 0; 13 | private static final int EXTENSION_INDEX = 1; 14 | private static final int SPLIT_SIZE = 2; 15 | 16 | private final String simpleName; 17 | private final String extension; 18 | 19 | public static FileName of(final String fullName) { 20 | int extensionDelimiterIndex = fullName.lastIndexOf(FILE_EXTENSION_DELIMITER); 21 | if (extensionDelimiterIndex == -1) { 22 | throw new BabbleIllegalArgumentException(String.format("올바른 파일 이름 형식이 아닙니다.(%s)", fullName)); 23 | } 24 | 25 | String simpleName = fullName.substring(0, extensionDelimiterIndex); 26 | String extension = fullName.substring(extensionDelimiterIndex + 1); 27 | 28 | return new FileName(simpleName, extension); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return simpleName + FILE_EXTENSION_DELIMITER + extension; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/admin/Administrator.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.admin; 2 | 3 | import javax.persistence.Embedded; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.validation.constraints.NotNull; 9 | import lombok.AccessLevel; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import org.hibernate.annotations.SQLDelete; 13 | import org.hibernate.annotations.Where; 14 | 15 | @Getter 16 | @SQLDelete(sql = "UPDATE administrator SET deleted = true WHERE id=?") 17 | @Where(clause = "deleted=false") 18 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 19 | @Entity 20 | public class Administrator { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | @Embedded 26 | private Ip ip; 27 | @NotNull 28 | private String name; 29 | 30 | private final boolean deleted = false; 31 | 32 | public Administrator(final String ip, final String name) { 33 | this(null, new Ip(ip), name); 34 | } 35 | 36 | public Administrator(final Long id, final Ip ip, final String name) { 37 | this.id = id; 38 | this.ip = ip; 39 | this.name = name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/admin/Ip.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.admin; 2 | 3 | import gg.babble.babble.exception.BabbleIllegalArgumentException; 4 | import javax.persistence.Column; 5 | import javax.persistence.Embeddable; 6 | import javax.validation.constraints.NotNull; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import org.apache.commons.validator.routines.InetAddressValidator; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @Embeddable 14 | public class Ip { 15 | 16 | private static final InetAddressValidator IP_VALIDATOR = InetAddressValidator.getInstance(); 17 | 18 | @Column(name = "ip", unique = true) 19 | @NotNull 20 | private String value; 21 | 22 | public Ip(final String value) { 23 | validateToConstruct(value); 24 | this.value = value; 25 | } 26 | 27 | private void validateToConstruct(final String value) { 28 | if (!IP_VALIDATOR.isValid(value)) { 29 | throw new BabbleIllegalArgumentException(String.format("%s는 IP 형식에 맞지 않습니다.", value)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/game/Games.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.game; 2 | 3 | import java.util.Comparator; 4 | import java.util.List; 5 | 6 | public class Games { 7 | 8 | private final List elements; 9 | 10 | public Games(final List elements) { 11 | this.elements = elements; 12 | } 13 | 14 | public void sortedByHeadCount() { 15 | elements.sort(Comparator.comparing(Game::userHeadCount).reversed()); 16 | } 17 | 18 | public List toList() { 19 | return elements; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/image/AutoGeneratedSize.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.image; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | public enum AutoGeneratedSize { 8 | X1920(1920), 9 | X1280(1280), 10 | X640(640); 11 | 12 | private final int size; 13 | 14 | AutoGeneratedSize(final int size) { 15 | this.size = size; 16 | } 17 | 18 | public static List getAutoGeneratedSizes() { 19 | return Arrays.stream(values()) 20 | .map(value -> value.size) 21 | .collect(Collectors.toList()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/image/ImageFile.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.image; 2 | 3 | import gg.babble.babble.domain.FileName; 4 | import java.util.Arrays; 5 | import java.util.Objects; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | public class ImageFile { 12 | 13 | private final FileName name; 14 | private final byte[] data; 15 | 16 | @Override 17 | public boolean equals(final Object o) { 18 | if (this == o) { 19 | return true; 20 | } 21 | if (o == null || getClass() != o.getClass()) { 22 | return false; 23 | } 24 | final ImageFile imageFile = (ImageFile) o; 25 | return Objects.equals(name, imageFile.name) && Arrays.equals(data, imageFile.data); 26 | } 27 | 28 | @Override 29 | public int hashCode() { 30 | int result = Objects.hash(name); 31 | result = 31 * result + Arrays.hashCode(data); 32 | return result; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/message/Content.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.message; 2 | 3 | import gg.babble.babble.exception.BabbleLengthException; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class Content { 8 | 9 | public static final int MIN_LENGTH = 1; 10 | public static final int MAX_LENGTH = 300; 11 | private final String value; 12 | 13 | public Content(final String value) { 14 | validateContent(value); 15 | this.value = value; 16 | } 17 | 18 | private static void validateContent(final String value) { 19 | if (value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) { 20 | throw new BabbleLengthException( 21 | String.format("메시지는 %d자 이상 %d자 이하 입니다. 현재 길이: (%d)", MIN_LENGTH, MAX_LENGTH, value.length()) 22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/post/Account.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.post; 2 | 3 | import javax.persistence.Embeddable; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @Embeddable 9 | @NoArgsConstructor 10 | public class Account { 11 | 12 | public static final int MIN_PASSWORD_LENGTH = 4; 13 | public static final int MAX_PASSWORD_LENGTH = 20; 14 | 15 | private String nickname; 16 | private String password; 17 | 18 | public Account(final String nickname, final String password) { 19 | this.nickname = nickname; 20 | this.password = password; 21 | } 22 | 23 | public boolean isWrongPassword(final String password) { 24 | return !this.password.equals(password); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/post/Category.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.post; 2 | 3 | import gg.babble.babble.exception.BabbleNotFoundException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public enum Category { 8 | FREE("자유"), 9 | SUGGESTIONS("건의"), 10 | GAME("게임"), 11 | NOTICE("공지"); 12 | 13 | private static final Map CATEGORY_MAP = new HashMap<>(); 14 | 15 | static { 16 | for (Category category : values()) { 17 | CATEGORY_MAP.put(category.name, category); 18 | } 19 | } 20 | 21 | private final String name; 22 | 23 | Category(final String name) { 24 | this.name = name; 25 | } 26 | 27 | public static Category getCategoryByName(final String name) { 28 | if (!CATEGORY_MAP.containsKey(name)) { 29 | throw new BabbleNotFoundException(String.format("[%s] 이름의 카테고리는 존재하지 않습니다.", name)); 30 | } 31 | return CATEGORY_MAP.get(name); 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public boolean isNotice() { 39 | return this == NOTICE; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/AdministratorRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.admin.Administrator; 4 | import gg.babble.babble.domain.admin.Ip; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface AdministratorRepository extends JpaRepository { 8 | 9 | boolean existsAdministratorByIp(final Ip ip); 10 | } 11 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/AlternativeGameNameRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.game.AlternativeGameName; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface AlternativeGameNameRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/AlternativeTagNameRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.tag.AlternativeTagName; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface AlternativeTagNameRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/RoomRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.room.Room; 4 | import java.util.List; 5 | import java.util.Set; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | public interface RoomRepository extends JpaRepository { 11 | 12 | @Query("select tr.room " 13 | + "from TagRegistration tr " 14 | + "where tr.room.deleted = false and tr.room.sessions.sessions.size > 0 and tr.room.game.id = ?1 and tr.tag.id IN (?2) " 15 | + "group by tr.room.id " 16 | + "having count(tr.tag.id) = ?3 " 17 | + "order by tr.room.createdAt desc") 18 | List findByGameIdAndTagIdsAndDeletedFalse(final Long gameId, final Set tagIds, final Long matchingCount, final Pageable pageable); 19 | 20 | @Query("select r " 21 | + "from Room r " 22 | + "where r.deleted = false and r.sessions.sessions.size > 0 and r.game.id = ?1 " 23 | + "order by r.createdAt desc") 24 | List findByGameIdAndDeletedFalse(final Long gameId, final Pageable pageable); 25 | } 26 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/S3Repository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import java.util.List; 4 | 5 | public interface S3Repository { 6 | 7 | List findAllImages(); 8 | 9 | void save(final String fileName, final byte[] content); 10 | 11 | void delete(final String... fileNames); 12 | } 13 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/SessionRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.Session; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | public interface SessionRepository extends JpaRepository { 9 | 10 | @Query(value = "select s " 11 | + "from Session s join fetch s.user join fetch s.room " 12 | + "where s.sessionId = ?1") 13 | Optional findBySessionId(final String sessionId); 14 | } 15 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/SliderRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.slider.Slider; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface SliderRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/TagRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.tag.Tag; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.data.repository.query.Param; 10 | 11 | public interface TagRepository extends JpaRepository { 12 | 13 | List findByDeletedFalse(); 14 | 15 | Optional findByIdAndDeletedFalse(final Long id); 16 | 17 | @Query(value = "select tag.id, tag.name, tag.deleted \n" 18 | + "from tag\n" 19 | + " left join alternative_tag_name on alternative_tag_name.deleted = false and alternative_tag_name.tag_id = tag.id\n" 20 | + "where tag.name like %:keyword%\n" 21 | + " or alternative_tag_name.name like %:keyword%", nativeQuery = true) 22 | List findAllByKeyword(@Param("keyword") final String keyword, final Pageable page); 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import gg.babble.babble.domain.user.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/s3repository/AbstractS3Repository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository.s3repository; 2 | 3 | import gg.babble.babble.domain.repository.S3Repository; 4 | import java.net.URLConnection; 5 | import java.util.Objects; 6 | 7 | public abstract class AbstractS3Repository implements S3Repository { 8 | 9 | private static final String IMAGE_CONTENT_TYPE = "image"; 10 | 11 | protected boolean isImageFile(final String fileName) { 12 | String contentType = URLConnection.guessContentTypeFromName(fileName); 13 | return Objects.nonNull(contentType) && contentType.contains(IMAGE_CONTENT_TYPE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/repository/s3repository/InMemoryS3Repository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository.s3repository; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.stream.Collectors; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.stereotype.Repository; 9 | 10 | @Profile("local") 11 | @Repository 12 | public class InMemoryS3Repository extends AbstractS3Repository { 13 | 14 | private final Map files = new HashMap<>(); 15 | 16 | @Override 17 | public List findAllImages() { 18 | return files.keySet() 19 | .stream() 20 | .filter(this::isImageFile) 21 | .collect(Collectors.toList()); 22 | } 23 | 24 | @Override 25 | public void save(final String fileName, final byte[] content) { 26 | files.put(fileName, content); 27 | } 28 | 29 | @Override 30 | public void delete(final String... fileNames) { 31 | for (String fileName : fileNames) { 32 | files.remove(fileName); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/room/MaxHeadCount.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.room; 2 | 3 | import gg.babble.babble.exception.BabbleIllegalArgumentException; 4 | import javax.persistence.Column; 5 | import javax.persistence.Embeddable; 6 | import lombok.AccessLevel; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | @Embeddable 13 | public class MaxHeadCount { 14 | 15 | private static final int MIN_VALUE = 2; 16 | private static final int MAX_VALUE = 20; 17 | 18 | @Column(name = "max_headcount") 19 | private int value; 20 | 21 | public MaxHeadCount(final int value) { 22 | validateToConstruct(value); 23 | this.value = value; 24 | } 25 | 26 | private static void validateToConstruct(final int value) { 27 | if (value < MIN_VALUE || MAX_VALUE < value) { 28 | throw new BabbleIllegalArgumentException( 29 | String.format("방 참가인원은 %d명에서 %d명 사이여야 합니다. 현재 생성하려고 한 방 인원 : %d", MIN_VALUE, MAX_VALUE, value) 30 | ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/room/Rooms.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.room; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import javax.persistence.Embeddable; 6 | import javax.persistence.FetchType; 7 | import javax.persistence.OneToMany; 8 | 9 | @Embeddable 10 | public class Rooms { 11 | 12 | @OneToMany(mappedBy = "game", fetch = FetchType.LAZY) 13 | private final List rooms; 14 | 15 | public Rooms() { 16 | this(new ArrayList<>()); 17 | } 18 | 19 | public Rooms(final List rooms) { 20 | this.rooms = rooms; 21 | } 22 | 23 | public int totalHeadCount() { 24 | return rooms.stream() 25 | .mapToInt(Room::currentHeadCount) 26 | .sum(); 27 | } 28 | 29 | public void addRoom(Room room) { 30 | rooms.add(room); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/room/TagRegistrationsOfRoom.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.room; 2 | 3 | import gg.babble.babble.domain.TagRegistration; 4 | import gg.babble.babble.domain.tag.Tag; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Embeddable; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.OneToMany; 11 | import lombok.NoArgsConstructor; 12 | 13 | @NoArgsConstructor 14 | @Embeddable 15 | public class TagRegistrationsOfRoom { 16 | 17 | @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 18 | private List tagRegistrations; 19 | 20 | public TagRegistrationsOfRoom(final Room room, final List tags) { 21 | this.tagRegistrations = tagRegistrationsFromTag(room, tags); 22 | } 23 | 24 | private List tagRegistrationsFromTag(final Room room, final List tags) { 25 | return tags.stream() 26 | .map(tag -> new TagRegistration(room, tag)) 27 | .collect(Collectors.toList()); 28 | } 29 | 30 | public List tags() { 31 | return tagRegistrations.stream() 32 | .map(TagRegistration::getTag) 33 | .collect(Collectors.toList()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/slider/ResourceUrl.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.slider; 2 | 3 | import javax.persistence.Embeddable; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | @Embeddable 13 | public class ResourceUrl { 14 | 15 | private String url; 16 | } 17 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/domain/tag/TagRegistrationsOfTag.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.tag; 2 | 3 | import gg.babble.babble.domain.TagRegistration; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Embeddable; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.OneToMany; 10 | 11 | @Embeddable 12 | public class TagRegistrationsOfTag { 13 | 14 | @OneToMany(mappedBy = "tag", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY) 15 | private List tagRegistrations = new ArrayList<>(); 16 | 17 | public boolean isNotEmpty() { 18 | return !tagRegistrations.isEmpty(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/AdministratorRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class AdministratorRequest { 12 | 13 | @NotNull 14 | private String ip; 15 | 16 | @NotNull 17 | private String name; 18 | } 19 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/GameCreateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import gg.babble.babble.domain.game.Game; 4 | import java.util.List; 5 | import javax.validation.constraints.NotNull; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | @Setter 12 | @Getter 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class GameCreateRequest { 16 | 17 | @NotNull(message = "게임 이름은 Null 일 수 없습니다.") 18 | private String name; 19 | 20 | @NotNull(message = "게임 썸네일 경로는 Null 일 수 없습니다.") 21 | private List images; 22 | 23 | @NotNull(message = "대안 이름은 Null 일 수 없습니다.") 24 | private List alternativeNames; 25 | 26 | public Game toEntity() { 27 | final Game game = new Game(name, images); 28 | game.addNames(alternativeNames); 29 | 30 | return game; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/GameUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import java.util.List; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | @Setter 11 | @Getter 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class GameUpdateRequest { 15 | 16 | @NotNull(message = "게임 이름은 Null 일 수 없습니다.") 17 | private String name; 18 | 19 | @NotNull(message = "게임 썸네일 경로는 Null 일 수 없습니다.") 20 | private List images; 21 | 22 | @NotNull(message = "대안 이름은 Null 일 수 없습니다.") 23 | private List alternativeNames; 24 | } 25 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/MessageRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | 4 | import gg.babble.babble.domain.message.Content; 5 | import javax.validation.constraints.NotNull; 6 | import javax.validation.constraints.Positive; 7 | import javax.validation.constraints.Size; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | 13 | @Setter 14 | @Getter 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class MessageRequest { 18 | 19 | @NotNull(message = "userId는 비어있을 수 없습니다.") 20 | @Positive(message = "userId는 음수일 수 없습니다.") 21 | private Long userId; 22 | 23 | @NotNull(message = "채팅 메시지는 Null 일 수 없습니다.") 24 | @Size(min = Content.MIN_LENGTH, max = Content.MAX_LENGTH) 25 | private String content; 26 | 27 | @NotNull(message = "채팅 타입은 Null 일 수 없습니다.") 28 | private String type; 29 | } 30 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/RoomRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import java.util.List; 4 | import javax.validation.constraints.Max; 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.NotNull; 7 | import javax.validation.constraints.Positive; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Getter 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class RoomRequest { 16 | 17 | @NotNull(message = "게임 ID는 비어있을 수 없습니다.") 18 | @Positive(message = "게임 ID는 1보다 큰 수여야 합니다.") 19 | private Long gameId; 20 | @NotNull(message = "태그 목록은 Null 일 수 없습니다.") 21 | private List tags; 22 | @Min(value = 2, message = "방 최대 참가 인원 최소 2인 이상이어야 합니다.") 23 | @Max(value = 20, message = "방 최대 참가 인원 최대 20인 이하여야 합니다.") 24 | private int maxHeadCount; 25 | } 26 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/SessionRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import javax.validation.constraints.NotNull; 5 | import javax.validation.constraints.Positive; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | @Setter 12 | @Getter 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class SessionRequest { 16 | 17 | @NotNull(message = "userId는 비어있을 수 없습니다.") 18 | @Positive(message = "userId는 음수일 수 없습니다.") 19 | private Long userId; 20 | 21 | @NotBlank(message = "sessionId는 비어있을 수 없습니다.") 22 | private String sessionId; 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/SliderOrderRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class SliderOrderRequest { 14 | 15 | private List ids; 16 | } 17 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/SliderRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import gg.babble.babble.domain.slider.Slider; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | @Setter 11 | @Getter 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class SliderRequest { 15 | 16 | @NotNull 17 | private String sliderUrl; 18 | 19 | public Slider toEntity() { 20 | return new Slider(sliderUrl); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/TagCreateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import gg.babble.babble.domain.tag.Tag; 4 | import java.util.List; 5 | import javax.validation.constraints.NotNull; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Getter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class TagCreateRequest { 14 | 15 | @NotNull 16 | private String name; 17 | @NotNull 18 | private List alternativeNames; 19 | 20 | public Tag toEntity() { 21 | Tag tag = new Tag(name); 22 | tag.addNames(alternativeNames); 23 | 24 | return tag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/TagRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class TagRequest { 12 | 13 | @NotNull(message = "태그 Id는 Null 일 수 없습니다.") 14 | private Long id; 15 | } -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/TagUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import java.util.List; 4 | import javax.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class TagUpdateRequest { 13 | 14 | @NotNull 15 | private String name; 16 | @NotNull 17 | private List alternativeNames; 18 | } 19 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/UserRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request; 2 | 3 | import gg.babble.babble.domain.user.Nickname; 4 | import javax.validation.constraints.NotNull; 5 | import javax.validation.constraints.Pattern; 6 | import javax.validation.constraints.Size; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserRequest { 15 | 16 | @NotNull 17 | @Size(min = Nickname.MIN_NICKNAME_LENGTH, max = Nickname.MAX_NICKNAME_LENGTH, message = "유저 닉네임은 {min}자 이상 {max}자 이하입니다. 현재 닉네임: ${validatedValue}") 18 | @Pattern(regexp = Nickname.NICKNAME_REGEXP, message = "닉네임은 한글, 영어, 숫자, 공백만 포함 가능합니다. 입력 닉네임: ${validatedValue}") 19 | private String nickname; 20 | } 21 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/post/PostCreateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request.post; 2 | 3 | import static gg.babble.babble.domain.post.Account.MAX_PASSWORD_LENGTH; 4 | import static gg.babble.babble.domain.post.Account.MIN_PASSWORD_LENGTH; 5 | 6 | import gg.babble.babble.domain.post.Post; 7 | import javax.validation.constraints.NotEmpty; 8 | import javax.validation.constraints.Size; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.Setter; 13 | 14 | @Setter 15 | @Getter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class PostCreateRequest { 19 | 20 | @NotEmpty(message = "게시글 제목은 공백일 수 없습니다.") 21 | private String title; 22 | 23 | @NotEmpty(message = "내용이 작성되지 않았습니다.") 24 | private String content; 25 | 26 | @NotEmpty(message = "카테고리가 선택되지 않았습니다.") 27 | private String category; 28 | 29 | @NotEmpty(message = "닉네임은 공백일 수 없습니다.") 30 | private String nickname; 31 | 32 | @Size(min = MIN_PASSWORD_LENGTH, max = MAX_PASSWORD_LENGTH) 33 | private String password; 34 | 35 | public Post toEntity() { 36 | return new Post(title, content, category, nickname, password); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/post/PostDeleteRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request.post; 2 | 3 | import static gg.babble.babble.domain.post.Account.MAX_PASSWORD_LENGTH; 4 | import static gg.babble.babble.domain.post.Account.MIN_PASSWORD_LENGTH; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.NotNull; 8 | import javax.validation.constraints.Size; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.Setter; 13 | 14 | @Setter 15 | @Getter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class PostDeleteRequest { 19 | 20 | @NotNull(message = "게시글 id 가 비어있습니다.") 21 | private Long id; 22 | 23 | @NotEmpty(message = "해당 게시글의 비밀번호가 입력되지 않았습니다.") 24 | @Size(min = MIN_PASSWORD_LENGTH, max = MAX_PASSWORD_LENGTH) 25 | private String password; 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/request/post/PostUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.request.post; 2 | 3 | import static gg.babble.babble.domain.post.Account.MAX_PASSWORD_LENGTH; 4 | import static gg.babble.babble.domain.post.Account.MIN_PASSWORD_LENGTH; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.NotNull; 8 | import javax.validation.constraints.Size; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | import lombok.Setter; 13 | 14 | @Setter 15 | @Getter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class PostUpdateRequest { 19 | 20 | @NotNull 21 | private Long id; 22 | 23 | @NotEmpty(message = "게시글 제목은 공백일 수 없습니다.") 24 | private String title; 25 | 26 | @NotEmpty(message = "내용이 작성되지 않았습니다.") 27 | private String content; 28 | 29 | @NotEmpty(message = "카테고리가 선택되지 않았습니다.") 30 | private String category; 31 | 32 | @NotEmpty(message = "해당 게시글의 비밀번호가 입력되지 않았습니다.") 33 | @Size(min = MIN_PASSWORD_LENGTH, max = MAX_PASSWORD_LENGTH) 34 | private String password; 35 | } 36 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/AdministratorResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.admin.Administrator; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class AdministratorResponse { 12 | 13 | private Long id; 14 | private String ip; 15 | private String name; 16 | 17 | public static AdministratorResponse from(final Administrator administrator) { 18 | return new AdministratorResponse(administrator.getId(), administrator.getIp().getValue(), administrator.getName()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/AlternativeGameNameResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.game.AlternativeGameName; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class AlternativeGameNameResponse { 12 | 13 | private Long id; 14 | private String name; 15 | 16 | public static AlternativeGameNameResponse from(AlternativeGameName alternativeGameName) { 17 | return new AlternativeGameNameResponse( 18 | alternativeGameName.getId(), 19 | alternativeGameName.getValue() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/AlternativeTagNameResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.tag.AlternativeTagName; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class AlternativeTagNameResponse { 12 | 13 | private Long id; 14 | private String name; 15 | 16 | public static AlternativeTagNameResponse from(AlternativeTagName alternativeTagName) { 17 | return new AlternativeTagNameResponse( 18 | alternativeTagName.getId(), 19 | alternativeTagName.getName() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/ChatResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class ChatResponse { 12 | 13 | private Long roomId; 14 | private MessageResponse messageResponse; 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/EntryResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class EntryResponse { 11 | 12 | private Long roomId; 13 | private SessionsResponse sessionsResponse; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/GameImageResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.game.Game; 4 | import java.util.List; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class GameImageResponse { 13 | 14 | private Long gameId; 15 | private List images; 16 | 17 | public static GameImageResponse from(final Game game) { 18 | return new GameImageResponse(game.getId(), game.getImages()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/GameNameResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.game.Game; 4 | import gg.babble.babble.domain.game.Games; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class GameNameResponse { 15 | 16 | private Long id; 17 | private String name; 18 | 19 | public static List listFrom(final Games games) { 20 | return games.toList().stream() 21 | .map(GameNameResponse::from) 22 | .collect(Collectors.toList()); 23 | } 24 | 25 | private static GameNameResponse from(final Game game) { 26 | return new GameNameResponse(game.getId(), game.getName()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/GameResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.game.Game; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class GameResponse { 12 | 13 | private Long id; 14 | private String name; 15 | 16 | public static GameResponse from(final Game game) { 17 | return new GameResponse(game.getId(), game.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/GameWithImageResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.game.AlternativeGameNames; 4 | import gg.babble.babble.domain.game.Game; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class GameWithImageResponse { 15 | 16 | private Long id; 17 | private String name; 18 | private List images; 19 | private List alternativeNames; 20 | 21 | public static GameWithImageResponse from(final Game game) { 22 | return new GameWithImageResponse( 23 | game.getId(), 24 | game.getName(), 25 | game.getImages(), 26 | convertToResponse(game.getAlternativeGameNames()) 27 | ); 28 | } 29 | 30 | private static List convertToResponse(AlternativeGameNames alternativeGameNames) { 31 | return alternativeGameNames.getElements() 32 | .stream() 33 | .map(AlternativeGameNameResponse::from) 34 | .collect(Collectors.toList()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/HeadCountResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.room.Room; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class HeadCountResponse { 12 | 13 | private int current; 14 | private int max; 15 | 16 | public static HeadCountResponse from(final Room room) { 17 | return new HeadCountResponse(room.currentHeadCount(), room.maxHeadCount()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/MessageResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.user.User; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class MessageResponse { 14 | 15 | private UserResponse user; 16 | private String content; 17 | private String type; 18 | 19 | public static MessageResponse from(final User user, final String content, final String type) { 20 | return new MessageResponse(UserResponse.from(user), content, type); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/PostResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.post.Post; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class PostResponse { 14 | 15 | private Long id; 16 | private String title; 17 | private String content; 18 | private String category; 19 | private String nickname; 20 | private String createdAt; 21 | private String updatedAt; 22 | private Boolean notice; 23 | 24 | private Long view; 25 | private Long like; 26 | 27 | public static PostResponse from(final Post post) { 28 | return new PostResponse(post.getId(), post.getTitle(), post.getContent(), post.category(), post.nickname(), post.createdAt(), post.updatedAt(), 29 | post.isNotice(), post.getView(), post.getLike()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/PostSearchResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class PostSearchResponse { 14 | 15 | private List results; 16 | private String keyword; 17 | private String type; 18 | 19 | public static PostSearchResponse from(final List postResponses, final String keyword, final String type) { 20 | return new PostSearchResponse(postResponses, keyword, type); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/PostWithoutContentResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.post.Post; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class PostWithoutContentResponse { 14 | 15 | private Long id; 16 | private String title; 17 | private String category; 18 | private String nickname; 19 | private String createdAt; 20 | private String updatedAt; 21 | private Boolean notice; 22 | 23 | private Long view; 24 | private Long like; 25 | 26 | public static PostWithoutContentResponse from(final Post post) { 27 | return new PostWithoutContentResponse(post.getId(), post.getTitle(), post.category(), post.nickname(), post.createdAt(), post.updatedAt(), 28 | post.isNotice(), post.getView(), post.getLike()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/SessionsResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.room.Room; 4 | import gg.babble.babble.domain.user.User; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.Setter; 11 | 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class SessionsResponse { 17 | 18 | private UserResponse host; 19 | private List guests; 20 | 21 | public static SessionsResponse empty() { 22 | return new SessionsResponse(null, null); 23 | } 24 | 25 | public static SessionsResponse of(final Room room) { 26 | if (room.isEmpty()) { 27 | return empty(); 28 | } 29 | return SessionsResponse.of(room.getHost(), room.getGuests()); 30 | } 31 | 32 | private static SessionsResponse of(final User host, final List guests) { 33 | List guestResponses = guests.stream() 34 | .map(UserResponse::from) 35 | .collect(Collectors.toList()); 36 | 37 | return new SessionsResponse(UserResponse.from(host), guestResponses); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/SliderResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.slider.Slider; 4 | import gg.babble.babble.domain.slider.Sliders; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | 13 | @Setter 14 | @Getter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class SliderResponse { 18 | 19 | private Long id; 20 | private String url; 21 | 22 | public static SliderResponse of(final Slider slider) { 23 | return new SliderResponse(slider.getId(), slider.url()); 24 | } 25 | 26 | public static List of(final Sliders sliders) { 27 | return sliders.getValues() 28 | .stream() 29 | .sorted(Comparator.comparingInt(Slider::getSortingIndex)) 30 | .map(SliderResponse::of) 31 | .collect(Collectors.toList()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/TagNameResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.tag.Tag; 4 | import gg.babble.babble.domain.tag.TagName; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class TagNameResponse { 15 | 16 | private Long id; 17 | private String name; 18 | 19 | public static List listFrom(final List tags) { 20 | return tags.stream() 21 | .map(TagNameResponse::from) 22 | .collect(Collectors.toList()); 23 | } 24 | 25 | public static TagNameResponse from(final Tag tag) { 26 | return new TagNameResponse(tag.getId(), tag.getName()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/TagResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.tag.AlternativeTagNames; 4 | import gg.babble.babble.domain.tag.Tag; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class TagResponse { 15 | 16 | private Long id; 17 | private String name; 18 | private List alternativeNames; 19 | 20 | public static TagResponse from(final Tag tag) { 21 | return new TagResponse( 22 | tag.getId(), 23 | tag.getName(), 24 | convertToResponse(tag.getAlternativeTagNames()) 25 | ); 26 | } 27 | 28 | private static List convertToResponse(AlternativeTagNames alternativeTagNames) { 29 | return alternativeTagNames.getElements() 30 | .stream() 31 | .map(AlternativeTagNameResponse::from) 32 | .collect(Collectors.toList()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/dto/response/UserResponse.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.dto.response; 2 | 3 | import gg.babble.babble.domain.user.User; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UserResponse { 14 | 15 | private Long id; 16 | private String nickname; 17 | private String avatar; 18 | 19 | public static UserResponse from(final User user) { 20 | return new UserResponse(user.getId(), user.getNickname(), user.getAvatar()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleAuthenticationException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleAuthenticationException extends BabbleException { 6 | 7 | public BabbleAuthenticationException(final String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public HttpStatus status() { 13 | return HttpStatus.UNAUTHORIZED; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleDuplicatedException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleDuplicatedException extends BabbleException { 6 | 7 | public BabbleDuplicatedException(final String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public HttpStatus status() { 13 | return HttpStatus.CONFLICT; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public abstract class BabbleException extends RuntimeException { 6 | 7 | public BabbleException() { 8 | } 9 | 10 | public BabbleException(final String message) { 11 | super(message); 12 | } 13 | 14 | public abstract HttpStatus status(); 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleIOException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleIOException extends BabbleException { 6 | 7 | public BabbleIOException(final String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public HttpStatus status() { 13 | return HttpStatus.INTERNAL_SERVER_ERROR; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleIllegalArgumentException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleIllegalArgumentException extends BabbleException { 6 | 7 | public BabbleIllegalArgumentException(final String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public HttpStatus status() { 13 | return HttpStatus.BAD_REQUEST; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleIllegalStatementException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleIllegalStatementException extends BabbleException { 6 | 7 | public BabbleIllegalStatementException(final String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public HttpStatus status() { 13 | return HttpStatus.BAD_REQUEST; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleLengthException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleLengthException extends BabbleException { 6 | 7 | public BabbleLengthException() { 8 | super(); 9 | } 10 | 11 | public BabbleLengthException(final String message) { 12 | super(message); 13 | } 14 | 15 | @Override 16 | public HttpStatus status() { 17 | return HttpStatus.BAD_REQUEST; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/exception/BabbleNotFoundException.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BabbleNotFoundException extends BabbleException { 6 | 7 | public BabbleNotFoundException() { 8 | super(); 9 | } 10 | 11 | public BabbleNotFoundException(final String message) { 12 | super(message); 13 | } 14 | 15 | @Override 16 | public HttpStatus status() { 17 | return HttpStatus.NOT_FOUND; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/service/UserService.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.service; 2 | 3 | import gg.babble.babble.domain.repository.UserRepository; 4 | import gg.babble.babble.domain.user.User; 5 | import gg.babble.babble.dto.request.UserRequest; 6 | import gg.babble.babble.dto.response.UserResponse; 7 | import gg.babble.babble.exception.BabbleNotFoundException; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | @Service 12 | @Transactional(readOnly = true) 13 | public class UserService { 14 | 15 | private final UserRepository userRepository; 16 | 17 | public UserService(final UserRepository userRepository) { 18 | this.userRepository = userRepository; 19 | } 20 | 21 | public User findById(final Long id) { 22 | return userRepository.findById(id) 23 | .orElseThrow(() -> new BabbleNotFoundException(String.format("존재하지 않는 유저 Id(%s) 입니다.", id))); 24 | } 25 | 26 | @Transactional 27 | public UserResponse save(final UserRequest request) { 28 | User user = userRepository.save(new User(request.getNickname())); 29 | return UserResponse.from(user); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/service/auth/SubscribeAuthService.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.service.auth; 2 | 3 | import gg.babble.babble.exception.BabbleIllegalStatementException; 4 | import gg.babble.babble.service.RoomService; 5 | import gg.babble.babble.util.UrlParser; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SubscribeAuthService { 10 | 11 | public static final String ROOM_UPDATE_SUBSCRIBE_PREFIX = "users"; 12 | 13 | private final RoomService roomService; 14 | 15 | public SubscribeAuthService(final RoomService roomService) { 16 | this.roomService = roomService; 17 | } 18 | 19 | public void validate(final String destinationUrl) { 20 | if (destinationUrl.contains(ROOM_UPDATE_SUBSCRIBE_PREFIX)) { 21 | validateHeadCount(destinationUrl); 22 | } 23 | } 24 | 25 | private void validateHeadCount(final String destinationUrl) { 26 | Long roomId = UrlParser.getRoomId(destinationUrl); 27 | if (roomService.isFullRoom(roomId)) { 28 | throw new BabbleIllegalStatementException(String.format("%d번방은 가득차 있어 입장할 수 없습니다.", roomId)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /back/babble/src/main/java/gg/babble/babble/service/redis/RedisListener.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.service.redis; 2 | 3 | import org.springframework.data.redis.connection.MessageListener; 4 | import org.springframework.data.redis.listener.ChannelTopic; 5 | 6 | public interface RedisListener extends MessageListener { 7 | 8 | ChannelTopic getChannelTopic(); 9 | } 10 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | 3 | datasource: 4 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL 5 | driver-class-name: org.h2.Driver 6 | username: sa 7 | password: password 8 | 9 | test: 10 | database: 11 | replace: NONE 12 | 13 | jpa: 14 | database-platform: org.hibernate.dialect.H2Dialect 15 | hibernate: 16 | ddl-auto: validate 17 | generate-ddl: true 18 | properties: 19 | hibernate: 20 | dialect: org.hibernate.dialect.MySQL5Dialect 21 | show_sql: false 22 | format_sql: true 23 | use_sql_comment: false 24 | 25 | flyway: 26 | enabled: true 27 | 28 | servlet: 29 | multipart: 30 | enabled: true 31 | max-file-size: 10MB 32 | max-request-size: 11MB 33 | location: ${java:io:tmpdir} 34 | 35 | redis: 36 | host: localhost 37 | port: 6379 38 | password: 39 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | 3 | datasource: 4 | driver-class-name: org.mariadb.jdbc.Driver 5 | url: jdbc:mariadb://192.168.2.59:3306/babble 6 | username: babble 7 | 8 | test: 9 | database: 10 | replace: NONE 11 | 12 | servlet: 13 | multipart: 14 | enabled: true 15 | max-file-size: 10MB 16 | max-request-size: 11MB 17 | location: ${java:io:tmpdir} 18 | 19 | jpa: 20 | hibernate: 21 | ddl-auto: validate 22 | 23 | redis: 24 | host: 192.168.2.135 25 | port: 6379 26 | 27 | cloud: 28 | aws: 29 | region: 30 | static: ap-northeast-2 31 | s3: 32 | bucket: 33 | name: babble-back-test 34 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | properties: 4 | hibernate.default_batch_fetch_size: 1000 5 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V10__tag_add_column_deleted.sql: -------------------------------------------------------------------------------- 1 | alter table tag add deleted boolean not null; 2 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V11__soft_delete.sql: -------------------------------------------------------------------------------- 1 | alter table administrator add deleted boolean not null default false; 2 | alter table tag_registration add deleted boolean not null default false; 3 | alter table user add deleted boolean not null default false; 4 | 5 | alter table alternative_game_name change is_deleted deleted boolean not null default false; 6 | alter table alternative_tag_name change is_deleted deleted boolean not null default false; 7 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V12__change_timestamp.sql: -------------------------------------------------------------------------------- 1 | alter table room change created_at created_at timestamp(3); 2 | alter table session change created_at created_at timestamp(3); 3 | alter table session change deleted_at deleted_at timestamp(3); 4 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V13__add_table_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | id bigint auto_increment, 4 | title varchar(255) not null, 5 | content varchar(8000), 6 | view_count bigint, 7 | like_count bigint, 8 | nickname varchar(255), 9 | password varchar(255), 10 | category varchar(255) not null, 11 | created_at timestamp, 12 | updated_at timestamp, 13 | deleted boolean not null, 14 | 15 | primary key (id) 16 | ); 17 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V14__refactoring.sql: -------------------------------------------------------------------------------- 1 | alter table post change created_at created_at timestamp(3); 2 | alter table post change updated_at updated_at timestamp(3); 3 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V15__refactoring.sql: -------------------------------------------------------------------------------- 1 | alter table post change content content varchar(8000); 2 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V2__add_column_deleted.sql: -------------------------------------------------------------------------------- 1 | alter table game add deleted boolean not null; 2 | 3 | alter table room change is_deleted deleted boolean not null; -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V3__add_table_administrator.sql: -------------------------------------------------------------------------------- 1 | create table administrator 2 | ( 3 | id bigint auto_increment, 4 | ip varchar(255) not null unique, 5 | name varchar(255) not null, 6 | primary key (id) 7 | ); -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V4__refactoring.sql: -------------------------------------------------------------------------------- 1 | alter table room change created_date created_at timestamp; 2 | alter table tag_registration change tag_name tag_id bigint not null; 3 | 4 | alter table session add created_at timestamp; 5 | alter table session add deleted_at timestamp null; 6 | alter table session add deleted boolean not null default false; 7 | 8 | alter table user drop column joined_at; -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V5__add_table_alternativeGameName.sql: -------------------------------------------------------------------------------- 1 | create table alternative_game_name 2 | ( 3 | id bigint auto_increment, 4 | name varchar(255) not null, 5 | game_id bigint not null, 6 | is_deleted boolean not null, 7 | primary key (id) 8 | ); 9 | 10 | alter table alternative_game_name 11 | add constraint fk_alternative_game_name_game foreign key (game_id) references game (id); -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V6__delete_column_roomId.sql: -------------------------------------------------------------------------------- 1 | alter table user drop foreign key fk_user_room; 2 | alter table user drop column room_id; -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V7__add_table_alternativeTagName.sql: -------------------------------------------------------------------------------- 1 | create table alternative_tag_name 2 | ( 3 | id bigint auto_increment, 4 | name varchar(255) not null, 5 | tag_id bigint not null, 6 | is_deleted boolean not null, 7 | primary key (id) 8 | ); 9 | 10 | alter table alternative_tag_name 11 | add constraint fk_alternative_tag_name_game foreign key (tag_id) references tag (id); -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V8__add_table_gameImages.sql: -------------------------------------------------------------------------------- 1 | alter table game drop image; 2 | 3 | create table game_images 4 | ( 5 | game_id bigint, 6 | game_image varchar(255) not null 7 | ); 8 | 9 | alter table game_images 10 | add constraint fk_game_images_game foreign key (game_id) references game (id); -------------------------------------------------------------------------------- /back/babble/src/main/resources/db/migration/V9__add_table_slider.sql: -------------------------------------------------------------------------------- 1 | create table slider 2 | ( 3 | id bigint auto_increment, 4 | url varchar(255) not null, 5 | sorting_index integer not null, 6 | primary key (id) 7 | ); 8 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback-access.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/console-appender/console-access-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %n###### HTTP Request ###### %n%fullRequest###### HTTP Response ###### %n%fullResponse 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/console-appender/console-db-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %magenta([%d{yyyy-MM-dd HH:mm:ss}:%-4relative]) %highlight(%-5level) %yellow([%C.%M]:%boldWhite(%L)]) %msg%n%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | utf8 15 | 16 | %green( > %msg%n) 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/console-appender/console-log-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %cyan([%d{yyyy-MM-dd HH:mm:ss}:%-4relative]) %highlight(%-5level) %yellow([%C.%M]:%boldWhite(%L)]) %n > %msg%n 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/file-appender/file-access-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}access.log 6 | 7 | ${home}access-%d{yyyyMMdd}-%i.log 8 | 9 | 100MB 10 | 11 | 180 12 | 13 | 14 | utf8 15 | 16 | %n###### HTTP Request ###### %n%fullRequest###### HTTP Response ###### %n%fullResponse 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/file-appender/file-debug-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}debug.log 6 | 7 | DEBUG 8 | ACCEPT 9 | DENY 10 | 11 | 12 | ${home}debug-%d{yyyyMMdd}-%i.log 13 | 14 | 100MB 15 | 16 | 180 17 | 18 | 19 | utf8 20 | 21 | [%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %-5level [%C.%M]:%L] %n > %msg%n 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/file-appender/file-error-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}error.log 6 | 7 | ERROR 8 | ACCEPT 9 | DENY 10 | 11 | 12 | ${home}error-%d{yyyyMMdd}-%i.log 13 | 14 | 100MB 15 | 16 | 180 17 | 18 | 19 | utf8 20 | 21 | [%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %-5level [%C.%M]:%L] %n > %msg%n 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/file-appender/file-info-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}info.log 6 | 7 | INFO 8 | ACCEPT 9 | DENY 10 | 11 | 12 | ${home}info-%d{yyyyMMdd}-%i.log 13 | 14 | 100MB 15 | 16 | 180 17 | 18 | 19 | utf8 20 | 21 | [%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %-5level [%C.%M]:%L] %n > %msg%n 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /back/babble/src/main/resources/logback/file-appender/file-warn-appender.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}warn.log 6 | 7 | WARN 8 | ACCEPT 9 | DENY 10 | 11 | 12 | ${home}warn-%d{yyyyMMdd}-%i.log 13 | 14 | 100MB 15 | 16 | 180 17 | 18 | 19 | utf8 20 | 21 | [%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %-5level [%C.%M]:%L] %n > %msg%n 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/FileNameTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | 6 | import gg.babble.babble.exception.BabbleIllegalArgumentException; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class FileNameTest { 11 | 12 | @DisplayName("파일 이름 파싱 테스트") 13 | @Test 14 | void of() { 15 | FileName fileName = FileName.of("abc.txt.jpg"); 16 | assertThat(fileName.getSimpleName()).isEqualTo("abc.txt"); 17 | assertThat(fileName.getExtension()).isEqualTo("jpg"); 18 | } 19 | 20 | @DisplayName("올바른 파일 이름 형식이 아닌 경우 예외처리") 21 | @Test 22 | void invlaidFileName() { 23 | assertThatThrownBy(() -> FileName.of("abc")).isInstanceOf(BabbleIllegalArgumentException.class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/SessionTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import gg.babble.babble.domain.game.Game; 6 | import gg.babble.babble.domain.room.MaxHeadCount; 7 | import gg.babble.babble.domain.room.Room; 8 | import gg.babble.babble.domain.tag.Tag; 9 | import gg.babble.babble.domain.user.User; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import org.junit.jupiter.api.DisplayName; 13 | import org.junit.jupiter.api.Test; 14 | 15 | class SessionTest { 16 | 17 | @DisplayName("세션을 통해 유저의 ID를 획득한다.") 18 | @Test 19 | void getUserId() { 20 | // given 21 | Game game = new Game(1L, "게임 이름", Collections.singletonList("게임 이미지")); 22 | List tags = Collections.singletonList(new Tag(1L, "초보만")); 23 | MaxHeadCount maxHeadCount = new MaxHeadCount(4); 24 | Room room = new Room(1L, game, tags, maxHeadCount); 25 | User user = new User(1L, "코 파는 알리스타"); 26 | Session session = new Session(1L, "1A2B3C4D", user, room); 27 | 28 | // when 29 | Long userId = session.getUserId(); 30 | 31 | // then 32 | assertThat(userId).isEqualTo(user.getId()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/UserTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import gg.babble.babble.domain.user.User; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class UserTest { 10 | 11 | @DisplayName("같은 닉네임이면 같은 아바타가 나온다.") 12 | @Test 13 | void avatarByNickname() { 14 | // when 15 | User user1 = new User("루트"); 16 | User user2 = new User("루트"); 17 | 18 | // then 19 | assertThat(user1.getAvatar()).isEqualTo(user2.getAvatar()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/game/AlternativeGameNameTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.game; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Collections; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class AlternativeGameNameTest { 11 | 12 | private Game game; 13 | private AlternativeGameName alternativeGameName; 14 | 15 | @BeforeEach 16 | void setUp() { 17 | game = new Game("디지투온", Collections.singletonList("화려한 이미지")); 18 | alternativeGameName = new AlternativeGameName("EZ2ON", game); 19 | game.addAlternativeName(alternativeGameName); 20 | } 21 | 22 | @DisplayName("AlternativeGameName 생성 후 Game 추가") 23 | @Test 24 | void constructAlternativeGameName() { 25 | assertThat(game.getAlternativeGameNames().contains(alternativeGameName.getValue())).isTrue(); 26 | } 27 | 28 | @DisplayName("대체 이름 삭제") 29 | @Test 30 | void delete() { 31 | // when 32 | alternativeGameName.delete(); 33 | // then 34 | assertThat(alternativeGameName.isDeleted()).isTrue(); 35 | } 36 | } -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/image/ImageSizeTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.image; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class ImageSizeTest { 9 | 10 | @DisplayName("해당 픽셀 수를 최대 길이로 가지는 사이즈 계산") 11 | @Test 12 | void calculateSizeContaining() { 13 | ImageSize imageSize = new ImageSize(10, 12); 14 | 15 | assertThat(imageSize.calculateSizeContaining(120)).isEqualTo(new ImageSize(100, 120)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/image/JpegImageTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.image; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.util.Objects; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | 12 | class JpegImageTest { 13 | 14 | @DisplayName("이미지 리사이징") 15 | @Test 16 | void resize() throws IOException { 17 | ClassLoader classLoader = getClass().getClassLoader(); 18 | File file = new File(Objects.requireNonNull(classLoader.getResource("test-image.jpg")).getFile()); 19 | 20 | JpegImage jpegImage = JpegImage.of(Files.readAllBytes(file.toPath())); 21 | JpegImage actual = jpegImage.resize(new ImageSize(12, 10)); 22 | 23 | assertThat(actual.getWidth()).isEqualTo(12); 24 | assertThat(actual.getHeight()).isEqualTo(10); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/repository/AdministratorRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import gg.babble.babble.domain.admin.Administrator; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 9 | 10 | @DataJpaTest 11 | class AdministratorRepositoryTest { 12 | 13 | @Autowired 14 | private AdministratorRepository administratorRepository; 15 | 16 | @Test 17 | void existsAdministratorByIp() { 18 | Administrator junroot = administratorRepository.save(new Administrator("1.1.1.1", "junroot")); 19 | administratorRepository.deleteById(junroot.getId()); 20 | 21 | assertThat(administratorRepository.existsAdministratorByIp(junroot.getIp())).isFalse(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/repository/UserRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.repository; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import gg.babble.babble.domain.user.User; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 10 | 11 | @DataJpaTest 12 | public class UserRepositoryTest { 13 | 14 | @Autowired 15 | private UserRepository userRepository; 16 | 17 | @DisplayName("유저를 생성한다.") 18 | @Test 19 | void saveTag() { 20 | // given 21 | User user = new User("새로운 유저"); 22 | 23 | // when 24 | User savedUser = userRepository.save(user); 25 | 26 | // then 27 | assertThat(savedUser.getId()).isNotNull(); 28 | assertThat(savedUser.getNickname()).isEqualTo(user.getNickname()); 29 | assertThat(savedUser.getAvatar()).isEqualTo(user.getAvatar()); 30 | assertThat(savedUser.getSession()).isEqualTo(user.getSession()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/room/MaxHeadCountTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.room; 2 | 3 | import static org.assertj.core.api.Assertions.assertThatCode; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | 6 | import gg.babble.babble.exception.BabbleIllegalArgumentException; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | public class MaxHeadCountTest { 13 | 14 | @DisplayName("최대 참가 인원은 2이상 20이하의 자연수다.") 15 | @ParameterizedTest 16 | @ValueSource(ints = {2, 10, 20}) 17 | void constructMaxHeadCountTest(int value) { 18 | assertThatCode(() -> new MaxHeadCount(value)).doesNotThrowAnyException(); 19 | } 20 | 21 | @DisplayName("최대 참가 인원이 2미만 또는 20초과이면 예외 처리한다.") 22 | @Test 23 | void constructMaxHeadCountExceptionTest() { 24 | assertThatThrownBy(() -> new MaxHeadCount(1)).isInstanceOf(BabbleIllegalArgumentException.class); 25 | assertThatThrownBy(() -> new MaxHeadCount(21)).isInstanceOf(BabbleIllegalArgumentException.class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/tag/AlternativeTagNameTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.tag; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class AlternativeTagNameTest { 10 | 11 | private Tag tag; 12 | private AlternativeTagName alternativeTagName; 13 | 14 | @BeforeEach 15 | void setUp() { 16 | tag = new Tag("1시간"); 17 | alternativeTagName = new AlternativeTagName(new TagName("1hour"), tag); 18 | tag.addAlternativeName(alternativeTagName); 19 | } 20 | 21 | @DisplayName("AlternativeTagName을 생성 후 Tag 추가") 22 | @Test 23 | void constructAlternativeGameName() { 24 | assertThat(tag.getAlternativeTagNames().contains(alternativeTagName.getValue())).isTrue(); 25 | } 26 | 27 | @DisplayName("대체 이름 삭제") 28 | @Test 29 | void delete() { 30 | // when 31 | alternativeTagName.delete(); 32 | 33 | // then 34 | assertThat(tag.hasName(alternativeTagName.getValue())).isFalse(); 35 | assertThat(alternativeTagName.isDeleted()).isTrue(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/domain/tag/TagNameTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.domain.tag; 2 | 3 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; 4 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 5 | 6 | import gg.babble.babble.exception.BabbleLengthException; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.ValueSource; 10 | 11 | class TagNameTest { 12 | 13 | @DisplayName("1자이상 20자이하의 길이일 경우 생성") 14 | @ValueSource(strings = {"고기가가가기고", "ㅋ", "very long tag name ."}) 15 | @ParameterizedTest 16 | void tagNameLengthTest(final String name) { 17 | assertThatCode(() -> new TagName(name)).doesNotThrowAnyException(); 18 | } 19 | 20 | @DisplayName("1자보다 짧거나 20자보다 길 경우 예외처리") 21 | @ValueSource(strings = {"", "very long tag name w."}) 22 | @ParameterizedTest 23 | void InvalidTagNameLengthTest(final String name) { 24 | assertThatExceptionOfType(BabbleLengthException.class).isThrownBy(() -> new TagName(name)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/restdocs/client/Identifiable.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.restdocs.client; 2 | 3 | public interface Identifiable { 4 | 5 | ID getId(T object); 6 | } 7 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/restdocs/client/ResponseRepository.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.restdocs.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedHashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class ResponseRepository { 9 | 10 | private final Map elements = new LinkedHashMap<>(); 11 | private final Identifiable identifiable; 12 | 13 | public ResponseRepository(final Identifiable identifiable) { 14 | this.identifiable = identifiable; 15 | } 16 | 17 | public void add(final T response) { 18 | elements.put(identifiable.getId(response), response); 19 | } 20 | 21 | public ID getAnyId() { 22 | return elements.keySet().iterator().next(); 23 | } 24 | 25 | public T get(final ID id) { 26 | return elements.get(id); 27 | } 28 | 29 | public List getAllIds() { 30 | return new ArrayList<>(elements.keySet()); 31 | } 32 | 33 | public int getSize() { 34 | return elements.keySet().size(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/restdocs/websocket/CustomWebSocketTransport.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.restdocs.websocket; 2 | 3 | import org.springframework.util.concurrent.ListenableFuture; 4 | import org.springframework.web.socket.WebSocketHandler; 5 | import org.springframework.web.socket.WebSocketSession; 6 | import org.springframework.web.socket.client.WebSocketClient; 7 | import org.springframework.web.socket.sockjs.client.TransportRequest; 8 | import org.springframework.web.socket.sockjs.client.WebSocketTransport; 9 | 10 | public class CustomWebSocketTransport extends WebSocketTransport { 11 | 12 | private String sessionId; 13 | 14 | public CustomWebSocketTransport(final WebSocketClient webSocketClient) { 15 | super(webSocketClient); 16 | } 17 | 18 | @Override 19 | public ListenableFuture connect(TransportRequest request, WebSocketHandler handler) { 20 | sessionId = request.getSockJsUrlInfo().getSessionId(); 21 | return super.connect(request, handler); 22 | } 23 | 24 | public String getSessionId() { 25 | return sessionId; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /back/babble/src/test/java/gg/babble/babble/util/UrlParserTest.java: -------------------------------------------------------------------------------- 1 | package gg.babble.babble.util; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | 6 | import gg.babble.babble.exception.BabbleIllegalStatementException; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class UrlParserTest { 11 | 12 | @DisplayName("올바른 구독 관련 url 주소를 넣으면, roomId를 파싱해서 리턴한다.") 13 | @Test 14 | void getRoomIdTest() { 15 | String userSubscribeUrl = "/topic/rooms/3/users"; 16 | String chatSubscribeUrl = "/topic/rooms/3/chat"; 17 | 18 | assertThat(UrlParser.getRoomId(userSubscribeUrl)).isEqualTo(3L); 19 | assertThat(UrlParser.getRoomId(chatSubscribeUrl)).isEqualTo(3L); 20 | } 21 | 22 | @DisplayName("올바르지 않은 url 주소를 넣으면, 예외가 발생한다.") 23 | @Test 24 | void wrongUrlException() { 25 | String wrongUrl = "/topic/jasonManse"; 26 | assertThatThrownBy(() -> { 27 | UrlParser.getRoomId(wrongUrl); 28 | }).isInstanceOf(BabbleIllegalStatementException.class).hasMessageContaining("roomId를 파싱할 수 없는 url 입니다."); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /back/babble/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: test 4 | jpa: 5 | properties: 6 | hibernate.default_batch_fetch_size: 1000 7 | -------------------------------------------------------------------------------- /back/babble/src/test/resources/test-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/back/babble/src/test/resources/test-image.jpg -------------------------------------------------------------------------------- /back/deploy/was/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | COPY babble-0.0.1-SNAPSHOT.jar app.jar 3 | ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=${PROFILES_ACTIVE}", "-Dspring.datasource.password=${DATASOURCE_PASSWORD}", "-Dspring.datasource.slaves.slave1.password=${DATASOURCE_PASSWORD}", "-Dspring.datasource.slaves.slave2.password=${DATASOURCE_PASSWORD}", "-Dspring.redis.password=${REDIS_PASSWORD}", "-Duser.timezone=Asia/Seoul", "app.jar"] -------------------------------------------------------------------------------- /front/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": { 8 | "version": 3 9 | } 10 | } 11 | ], 12 | 13 | "@babel/preset-react" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /front/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true, 5 | "node": true 6 | }, 7 | 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react/recommended", 11 | "plugin:prettier/recommended" 12 | ], 13 | 14 | "settings": { 15 | "react": { 16 | "version": "latest" 17 | } 18 | }, 19 | 20 | "parser": "babel-eslint", 21 | 22 | "parserOptions": { 23 | "ecmaVersion": 12, 24 | "sourceType": "module", 25 | "ecmaFeatures": { 26 | "jsx": true 27 | } 28 | }, 29 | 30 | "ignorePatterns": ["dist/", "node_modules/"], 31 | 32 | "plugins": ["react", "prettier"], 33 | 34 | "rules": { 35 | "prettier/prettier": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | .env 4 | .DS_* 5 | *.log 6 | logs 7 | **/*.backup.* 8 | **/*.back.* 9 | 10 | node_modules 11 | bower_components 12 | 13 | cypress/screenshots 14 | cypress/videos 15 | 16 | psd 17 | thumb 18 | sketch 19 | /dist -------------------------------------------------------------------------------- /front/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "jsxSingleQuote": true, 4 | "printWidth": 80, 5 | "quoteProps": "consistent", 6 | "semi": true, 7 | "singleQuote": true, 8 | "tabWidth": 2, 9 | "trailingComma": "es5", 10 | "useTabs": false, 11 | "endOfLine": "auto" 12 | } 13 | -------------------------------------------------------------------------------- /front/.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main", "release"], 3 | "repositoryUrl": "https://github.com/woowacourse-teams/2021-babble.git", 4 | "debug": "true", 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/npm", 9 | [ 10 | "@semantic-release/changelog", 11 | { 12 | "changelogFile": "CHANGELOG.md" 13 | } 14 | ], 15 | [ 16 | "@semantic-release/git", 17 | { 18 | "assets": ["package.json", "package-lock.json", "CHANGELOG.md"], 19 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 20 | } 21 | ] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /front/.storybook/babbleTheme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export default create({ 4 | base: 'light', 5 | 6 | barTextColor: 'white', 7 | barSelectedColor: 'black', 8 | barBg: '#ff005c', 9 | 10 | textInverseColor: 'rgb(255,255,255)', 11 | 12 | brandTitle: 'Babble', 13 | brandUrl: 'https://babble.gg', 14 | brandImage: 'https://d2bidcnq0n74fu.cloudfront.net/img/logos/logo.png', 15 | }); 16 | -------------------------------------------------------------------------------- /front/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], 3 | addons: [ 4 | '@storybook/addon-links', 5 | '@storybook/addon-essentials', 6 | '@storybook/preset-scss', 7 | '@storybook/addon-viewport', 8 | '@storybook/addon-knobs', 9 | '@storybook/addon-a11y', 10 | ], 11 | core: { 12 | builder: 'webpack5', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /front/.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | import babbleTheme from './babbleTheme'; 3 | 4 | addons.setConfig({ 5 | theme: babbleTheme, 6 | }); 7 | -------------------------------------------------------------------------------- /front/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import '../style/global.scss'; 2 | 3 | import ChattingModalProvider from '../src/contexts/ChattingModalProvider'; 4 | import DefaultModalProvider from '../src/contexts/DefaultModalProvider'; 5 | import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; 6 | import React from 'react'; 7 | import UserProvider from '../src/contexts/UserProvider'; 8 | import { addParameters } from '@storybook/react'; 9 | 10 | // import { addons } from '@storybook/addons'; 11 | // import theme from './theme'; 12 | 13 | export const decorators = [ 14 | (Story) => ( 15 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ), 30 | ]; 31 | 32 | export const parameters = { 33 | actions: { argTypesRegex: '^on[A-Z].*' }, 34 | controls: { 35 | matchers: { 36 | color: /(background|color)$/i, 37 | date: /Date$/, 38 | }, 39 | }, 40 | }; 41 | 42 | addParameters({ 43 | viewport: { 44 | defaultViewport: 'desktop', 45 | viewports: INITIAL_VIEWPORTS, 46 | }, 47 | }); 48 | 49 | // addons.setConfig({ theme: theme }); 50 | -------------------------------------------------------------------------------- /front/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.autoSave": "onFocusChange", 3 | "files.trimTrailingWhitespace": true, 4 | 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true 9 | }, 10 | "prettier.useEditorConfig": true, 11 | "editor.tabSize": 2 12 | } 13 | -------------------------------------------------------------------------------- /front/cSpell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "language": "en", 4 | "ignorePaths": ["node_modules/**", ".babelrc", "package.json", ".vscode/**"] 5 | } 6 | -------------------------------------------------------------------------------- /front/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": { 3 | "testFiles": "**/*.test.{js,ts,jsx,tsx}", 4 | "componentFolder": "src", 5 | "pluginsFile": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /front/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "roomId": 0, 4 | "createdDate": "2021-08-07T01:37:22.021", 5 | "game": { 6 | "id": 1, 7 | "name": "League Of Legends" 8 | }, 9 | "host": { 10 | "id": 7, 11 | "nickname": "user0", 12 | "avatar": "https://hyeon9mak.github.io/assets/images/9vatar.png" 13 | }, 14 | "headCount": { 15 | "current": 1, 16 | "max": 4 17 | }, 18 | "tags": [ 19 | { 20 | "id": 1, 21 | "name": "실버" 22 | }, 23 | { 24 | "id": 2, 25 | "name": "2시간" 26 | } 27 | ] 28 | }, 29 | { 30 | "roomId": 1, 31 | "createdDate": "2021-08-11T01:37:22.021", 32 | "game": { 33 | "id": 1, 34 | "name": "League Of Legends" 35 | }, 36 | "host": { 37 | "id": 10, 38 | "nickname": "user1", 39 | "avatar": "https://hyeon9mak.github.io/assets/images/9vatar.png" 40 | }, 41 | "headCount": { 42 | "current": 10, 43 | "max": 20 44 | }, 45 | "tags": [ 46 | { 47 | "id": 1, 48 | "name": "다이아몬드" 49 | } 50 | ] 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /front/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /front/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /front/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | 18 | import './commands'; 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | Cypress.on('uncaught:exception', (err, runnable) => { 23 | // returning false here prevents Cypress from 24 | // failing the test 25 | // if (err.message.includes('list not defined')) { 26 | return false; 27 | // } 28 | }); 29 | -------------------------------------------------------------------------------- /front/index.js: -------------------------------------------------------------------------------- 1 | import App from './src/App'; 2 | import ChattingModalProvider from './src/contexts/ChattingModalProvider'; 3 | import DefaultModalProvider from './src/contexts/DefaultModalProvider'; 4 | import ErrorBoundary from './src/components/ErrorBoundary/ErrorBoundary'; 5 | import PushNotificationProvider from './src/contexts/PushNotificationProvider'; 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import { BrowserRouter as Router } from 'react-router-dom'; 9 | import ScrollToTop from './src/components/ScrollToTop/ScrollToTop'; 10 | import { ThemeChangeContextProvider } from './src/contexts/ThemeChangeProvider'; 11 | import UserProvider from './src/contexts/UserProvider'; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | , 30 | document.getElementById('root') 31 | ); 32 | -------------------------------------------------------------------------------- /front/mockData.json: -------------------------------------------------------------------------------- 1 | { 2 | "roomInfo": { 3 | "roomId": 1, 4 | "createdDate": "2021-07-05T00:00:00.000Z", 5 | "game": { 6 | "id": 10, 7 | "name": "Apex Legend" 8 | }, 9 | "host": { 10 | "id": 1234, 11 | "name": "wilder", 12 | "avatar": "https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg" 13 | }, 14 | "tags": [ 15 | "실버", 16 | "2시간", 17 | "솔로랭크", 18 | "음성채팅 가능", 19 | "3대 500", 20 | "문신 있음", 21 | "흡연자", 22 | "75kg" 23 | ] 24 | }, 25 | "guests": [ 26 | { 27 | "avatar": "https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg", 28 | "nickName": "Hyeon9mak" 29 | }, 30 | { 31 | "avatar": "https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg", 32 | "nickName": "root" 33 | }, 34 | { 35 | "avatar": "https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg", 36 | "nickName": "fortune" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /front/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /front/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /front/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/apple-touch-icon.png -------------------------------------------------------------------------------- /front/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/favicon-16x16.png -------------------------------------------------------------------------------- /front/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/favicon-32x32.png -------------------------------------------------------------------------------- /front/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/favicon.ico -------------------------------------------------------------------------------- /front/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Babble", 3 | "name": "Babble, Fast Team Matching Platform", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "type": "image/x-icon" 8 | }, 9 | { 10 | "src": "/android-chrome-192x192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "/android-chrome-512x512.png", 16 | "sizes": "512x512", 17 | "type": "image/png" 18 | } 19 | ], 20 | "display": "standalone", 21 | "start_url": "https://babble.gg" 22 | } 23 | -------------------------------------------------------------------------------- /front/public/ogimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/ogimg.png -------------------------------------------------------------------------------- /front/public/readme/github_readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/public/readme/github_readme.png -------------------------------------------------------------------------------- /front/src/chunks/AdminManagement/AdminManagement.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | @use '../../../style/mixins' as m; 3 | 4 | .admin-management-container { 5 | .name-list-container { 6 | border: 0; 7 | padding: 0; 8 | 9 | .tag-container { 10 | padding: 1.5rem; 11 | 12 | .tag-content { 13 | font-size: 1.5rem; 14 | } 15 | } 16 | } 17 | } 18 | 19 | @include m.media(mobile, mobile-down) { 20 | .admin-management-container { 21 | .name-list-container { 22 | margin-top: 1rem; 23 | 24 | .tag-container { 25 | padding: 1rem; 26 | 27 | .tag-content { 28 | font-size: 1.2rem; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front/src/chunks/AdminManagement/AdminManagement.stories.js: -------------------------------------------------------------------------------- 1 | import AdminManagement from './AdminManagement'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'chunks/AdminManagement', 6 | component: AdminManagement, 7 | }; 8 | 9 | const AdminManagementTemplate = (args) => ; 10 | 11 | export const Default = AdminManagementTemplate.bind({}); 12 | 13 | Default.args = { 14 | admins: [ 15 | { id: 0, ip: '127.0.0.1', name: 'fortune' }, 16 | { id: 1, ip: '127.0.0.1', name: 'root' }, 17 | { id: 2, ip: '127.0.0.1', name: 'wilder' }, 18 | { id: 3, ip: '127.0.0.1', name: 'hyeon9mak' }, 19 | { id: 4, ip: '127.0.0.1', name: 'grooming' }, 20 | { id: 5, ip: '127.0.0.1', name: 'peter' }, 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /front/src/chunks/Chatbox/Chatbox.js: -------------------------------------------------------------------------------- 1 | import './Chatbox.scss'; 2 | 3 | import React, { useEffect, useRef } from 'react'; 4 | 5 | import { ChattingForm } from '../../components'; 6 | import PropTypes from 'prop-types'; 7 | 8 | const Chatbox = ({ onSubmit, children }) => { 9 | const chattingsRef = useRef(null); 10 | 11 | useEffect(() => { 12 | chattingsRef.current.scrollTop = chattingsRef.current.scrollHeight; 13 | }, [children]); 14 | 15 | return ( 16 |
17 |
18 |
{children}
19 |
20 |
21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | Chatbox.propTypes = { 28 | onSubmit: PropTypes.func, 29 | children: PropTypes.node, 30 | }; 31 | 32 | export default Chatbox; 33 | -------------------------------------------------------------------------------- /front/src/chunks/Chatbox/Chatbox.scss: -------------------------------------------------------------------------------- 1 | .chatbox-container { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | height: 100%; 6 | justify-content: space-between; 7 | padding: 0.8rem; 8 | position: relative; 9 | 10 | .chattings { 11 | display: flex; 12 | flex-direction: column; 13 | overflow-y: auto; 14 | width: 100%; 15 | height: 100%; 16 | margin-bottom: 1rem; 17 | 18 | &::-webkit-scrollbar { 19 | display: none; 20 | } 21 | } 22 | 23 | .chatting-contents { 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: flex-start; 27 | height: 100%; 28 | width: 100%; 29 | 30 | & > :first-child { 31 | margin-top: auto; 32 | } 33 | 34 | .mine-container { 35 | display: flex; 36 | justify-content: flex-end; 37 | margin-bottom: 1.5rem; 38 | 39 | &:last-child { 40 | padding-bottom: 1rem; 41 | } 42 | 43 | &.no-time { 44 | margin-bottom: 0.5rem; 45 | } 46 | } 47 | 48 | .others-container { 49 | &.no-time { 50 | margin-bottom: -1.1rem; 51 | } 52 | } 53 | } 54 | 55 | .chatting-form-container { 56 | display: flex; 57 | flex-direction: row; 58 | width: 100%; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /front/src/chunks/Chatbox/Chatbox.stories.js: -------------------------------------------------------------------------------- 1 | import { SpeechBubble, SpeechBubbleWithAvatar } from '../../components'; 2 | 3 | import Chatbox from './Chatbox'; 4 | import React from 'react'; 5 | import { withA11y } from '@storybook/addon-a11y'; 6 | 7 | export default { 8 | title: 'chunks/Chatbox', 9 | decorators: [withA11y], 10 | component: Chatbox, 11 | }; 12 | 13 | const user = { 14 | nickname: 'peter', 15 | }; 16 | 17 | const ChatboxTemplate = (args) => ( 18 |
19 | 20 | 21 | 안녕하세요 22 | 23 |
30 | 31 | 오 32 | 33 | 34 | 안녕하세요 35 | 36 |
37 |
38 |
39 | ); 40 | 41 | export const Default = ChatboxTemplate.bind({}); 42 | 43 | Default.args = { 44 | roomId: 10, 45 | createdAt: '2021-07-05T00:00:00.000Z', 46 | }; 47 | -------------------------------------------------------------------------------- /front/src/chunks/GameManagement/GameManagement.stories.js: -------------------------------------------------------------------------------- 1 | import GameManagement from './GameManagement'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'chunks/GameManagement', 6 | component: GameManagement, 7 | }; 8 | 9 | const GameManagementTemplate = (args) => ( 10 |
11 | 12 |
13 | ); 14 | 15 | export const Default = GameManagementTemplate.bind({}); 16 | 17 | Default.args = {}; 18 | -------------------------------------------------------------------------------- /front/src/chunks/LikeAndView/LikeAndView.js: -------------------------------------------------------------------------------- 1 | import './LikeAndView.scss'; 2 | 3 | import { AiOutlineEye } from '@react-icons/all-files/ai/AiOutlineEye'; 4 | import { AiOutlineLike } from '@react-icons/all-files/ai/AiOutlineLike'; 5 | import InfoWithIcon from '../../components/InfoWithIcon/InfoWithIcon'; 6 | import PropTypes from 'prop-types'; 7 | import React from 'react'; 8 | 9 | const LikeAndView = ({ like, view }) => { 10 | return ( 11 | 12 | } content={like} /> 13 | } content={view} /> 14 | 15 | ); 16 | }; 17 | 18 | LikeAndView.propTypes = { 19 | like: PropTypes.number, 20 | view: PropTypes.number, 21 | }; 22 | 23 | export default LikeAndView; 24 | -------------------------------------------------------------------------------- /front/src/chunks/LikeAndView/LikeAndView.scss: -------------------------------------------------------------------------------- 1 | .like-and-view-container { 2 | display: flex; 3 | 4 | .watch-container { 5 | margin-right: 0.8rem; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /front/src/chunks/LikeAndView/LikeAndView.stories.js: -------------------------------------------------------------------------------- 1 | import LikeAndView from './LikeAndView'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'chunks/LikeAndView', 6 | component: LikeAndView, 7 | }; 8 | 9 | const LikeAndViewTemplate = (args) => ; 10 | 11 | export const Default = LikeAndViewTemplate.bind({}); 12 | 13 | Default.args = { 14 | like: '12938', 15 | view: '18792', 16 | }; 17 | -------------------------------------------------------------------------------- /front/src/chunks/TableContent/TableContent.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TableContent from './TableContent'; 3 | 4 | export default { 5 | title: 'chunks/TableContent', 6 | component: TableContent, 7 | }; 8 | 9 | const TableContentTemplate = (args) => ; 10 | 11 | export const Default = TableContentTemplate.bind({}); 12 | 13 | Default.args = { 14 | boardDetails: { 15 | title: '롤드컵 존잼;; ㄷㄷ', 16 | category: '자유', 17 | author: '그루밍', 18 | createdAt: '10/18 09:23', 19 | viewCount: '18234', 20 | likeCount: '2918', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /front/src/chunks/TagList/TagList.js: -------------------------------------------------------------------------------- 1 | import './TagList.scss'; 2 | 3 | import { Tag, TagErasable } from '../../components'; 4 | 5 | import { Caption1 } from '../../core/Typography'; 6 | import LinearLayout from '../../core/Layout/LinearLayout'; 7 | import PropTypes from 'prop-types'; 8 | import React from 'react'; 9 | 10 | const TagList = ({ tags, onDeleteTag, erasable = false }) => { 11 | return ( 12 |
13 | 14 | {erasable 15 | ? tags.map((tag, index) => ( 16 | 17 | {tag.name} 18 | 19 | )) 20 | : tags.map((tag, index) => ( 21 | 22 | {tag.name} 23 | 24 | ))} 25 | 26 |
27 | ); 28 | }; 29 | 30 | TagList.propTypes = { 31 | tags: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string })), 32 | onDeleteTag: PropTypes.func, 33 | erasable: PropTypes.bool, 34 | useWheel: PropTypes.bool, 35 | }; 36 | 37 | export default TagList; 38 | -------------------------------------------------------------------------------- /front/src/chunks/TagList/TagList.scss: -------------------------------------------------------------------------------- 1 | .tag-list-container { 2 | & { 3 | .linear-layout { 4 | flex-wrap: wrap; 5 | gap: 1rem 0; 6 | } 7 | } 8 | 9 | .tag-container { 10 | margin-right: 1rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /front/src/chunks/TagList/TagList.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TagList from './TagList'; 3 | 4 | export default { 5 | title: 'chunks/TagList', 6 | component: TagList, 7 | }; 8 | 9 | const TagListTemplate = (args) => ; 10 | 11 | export const Default = TagListTemplate.bind({}); 12 | 13 | Default.args = { 14 | tags: [ 15 | { name: '실버' }, 16 | { name: '골드 승급전' }, 17 | { name: '원딜' }, 18 | { name: '솔로랭크' }, 19 | { name: '음성채팅 가능' }, 20 | { name: '버스태워줄 사람' }, 21 | { name: '문신있음' }, 22 | { name: '85kg' }, 23 | { name: '190cm' }, 24 | { name: '노스페이스 패딩' }, 25 | { name: '뉴발란스 신발' }, 26 | { name: '캘빈클라인 벨트' }, 27 | { name: '자가 집 보유' }, 28 | { name: '자동차 보유' }, 29 | ], 30 | erasable: false, 31 | }; 32 | -------------------------------------------------------------------------------- /front/src/chunks/TagManagement/TagManagement.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TagManagement from './TagManagement'; 3 | 4 | export default { 5 | title: 'chunks/TagManagement', 6 | component: TagManagement, 7 | }; 8 | 9 | const TagManagementTemplate = (args) => ; 10 | 11 | export const Default = TagManagementTemplate.bind({}); 12 | 13 | Default.args = { 14 | admins: [ 15 | { id: 0, name: '1 시간', alternativeNames: ['한 시간', '1 hour'] }, 16 | { id: 1, name: '2 시간', alternativeNames: ['두 시간', '2 hour'] }, 17 | { id: 2, name: '솔로 랭크', alternativeNames: ['솔랭'] }, 18 | { id: 3, name: '듀오 랭크', alternativeNames: ['듀오랭'] }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /front/src/chunks/WritingBlock/WritingBlock.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WritingBlock from './WritingBlock'; 3 | 4 | export default { 5 | title: 'chunks/WritingBlock', 6 | component: WritingBlock, 7 | }; 8 | 9 | const WritingBlockTemplate = (args) => ; 10 | 11 | export const Default = WritingBlockTemplate.bind({}); 12 | 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /front/src/chunks/index.js: -------------------------------------------------------------------------------- 1 | export { default as Chatbox } from './Chatbox/Chatbox'; 2 | export { default as Participants } from './Participants/Participants'; 3 | export { default as TagList } from './TagList/TagList'; 4 | export { default as GameManagement } from './GameManagement/GameManagement'; 5 | export { default as TagManagement } from './TagManagement/TagManagement'; 6 | export { default as AdminManagement } from './AdminManagement/AdminManagement'; 7 | export { default as WritingBlock } from './WritingBlock/WritingBlock'; 8 | -------------------------------------------------------------------------------- /front/src/components/Avatar/Avatar.js: -------------------------------------------------------------------------------- 1 | import './Avatar.scss'; 2 | 3 | import AvatarImage from './AvatarImage'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | 7 | const Avatar = ({ size = 'small', direction = 'col', imageSrc, children }) => { 8 | return ( 9 |
10 | 11 |
{children}
12 |
13 | ); 14 | }; 15 | 16 | Avatar.propTypes = { 17 | size: PropTypes.string, 18 | direction: PropTypes.string, 19 | imageSrc: PropTypes.string, 20 | children: PropTypes.node, 21 | }; 22 | 23 | export default Avatar; 24 | -------------------------------------------------------------------------------- /front/src/components/Avatar/AvatarImage.js: -------------------------------------------------------------------------------- 1 | import './Avatar.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const AvatarImage = ({ 7 | imageSrc = 'https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg', 8 | size, 9 | visibility, 10 | }) => { 11 | return ( 12 |
13 | Avatar Image 14 |
15 | ); 16 | }; 17 | 18 | AvatarImage.propTypes = { 19 | size: PropTypes.string, 20 | imageSrc: PropTypes.string, 21 | visibility: PropTypes.string, 22 | }; 23 | 24 | export default AvatarImage; 25 | -------------------------------------------------------------------------------- /front/src/components/Badge/Badge.js: -------------------------------------------------------------------------------- 1 | import './Badge.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Badge = ({ colored = false, children }) => { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | }; 13 | 14 | Badge.propTypes = { 15 | colored: PropTypes.bool, 16 | children: PropTypes.node, 17 | }; 18 | 19 | export default Badge; 20 | -------------------------------------------------------------------------------- /front/src/components/Badge/Badge.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | @use '../../../style/variables' as v; 3 | 4 | .badge-container { 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | margin-left: 0.5rem; 9 | color: f.grey-scale(10); 10 | 11 | &.colored { 12 | color: f.primary(babble-pink); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /front/src/components/Badge/Badge.stories.js: -------------------------------------------------------------------------------- 1 | import { Caption2, Subtitle3 } from '../../core/Typography'; 2 | 3 | import Avatar from '../Avatar/Avatar'; 4 | import Badge from './Badge'; 5 | import React from 'react'; 6 | import { RiVipCrown2Fill } from '@react-icons/all-files/ri/RiVipCrown2Fill'; 7 | import { VscCircleFilled } from '@react-icons/all-files/vsc/VscCircleFilled'; 8 | 9 | export default { 10 | title: 'components/Badge', 11 | component: Badge, 12 | }; 13 | 14 | const BadgeTemplate = () => ( 15 |
16 | Badges used with participants 17 |
18 |
19 | 20 | jason 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | 29 | wilder 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 | ); 38 | 39 | export const Default = BadgeTemplate.bind({}); 40 | 41 | Default.args = {}; 42 | -------------------------------------------------------------------------------- /front/src/components/Badge/BadgeClickable.js: -------------------------------------------------------------------------------- 1 | import './Badge.scss'; 2 | 3 | import Badge from './Badge'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | 7 | const BadgeClickable = ({ onClickBadge, ariaLabel, children }) => { 8 | return ( 9 | 12 | ); 13 | }; 14 | 15 | BadgeClickable.propTypes = { 16 | onClickBadge: PropTypes.func, 17 | ariaLabel: PropTypes.string, 18 | children: PropTypes.node, 19 | }; 20 | 21 | export default BadgeClickable; 22 | -------------------------------------------------------------------------------- /front/src/components/Button/RoundButton.js: -------------------------------------------------------------------------------- 1 | import './Button.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const RoundButton = ({ 7 | size = 'medium', 8 | type = 'button', 9 | colored = false, 10 | name, 11 | onClickButton, 12 | children, 13 | }) => { 14 | return ( 15 | 23 | ); 24 | }; 25 | 26 | RoundButton.propTypes = { 27 | size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'block']), 28 | type: PropTypes.oneOf(['button', 'submit', 'reset']), 29 | name: PropTypes.string, 30 | colored: PropTypes.bool, 31 | onClickButton: PropTypes.func, 32 | children: PropTypes.node, 33 | }; 34 | 35 | export default RoundButton; 36 | -------------------------------------------------------------------------------- /front/src/components/Button/SquareButton.js: -------------------------------------------------------------------------------- 1 | import './Button.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const SquareButton = ({ 7 | size = 'medium', 8 | type = 'button', 9 | colored = true, 10 | name, 11 | children, 12 | onClickButton = () => {}, 13 | ...rest 14 | }) => { 15 | return ( 16 | 25 | ); 26 | }; 27 | 28 | SquareButton.propTypes = { 29 | size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'block']), 30 | type: PropTypes.oneOf(['button', 'submit', 'reset']), 31 | name: PropTypes.string, 32 | colored: PropTypes.bool, 33 | onClickButton: PropTypes.func, 34 | children: PropTypes.node, 35 | }; 36 | 37 | export default SquareButton; 38 | -------------------------------------------------------------------------------- /front/src/components/ChattingForm/ChattingForm.stories.js: -------------------------------------------------------------------------------- 1 | import ChattingForm from './ChattingForm'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/ChattingForm', 6 | component: ChattingForm, 7 | }; 8 | 9 | const ChattingFormTemplate = (args) => ; 10 | 11 | export const Default = ChattingFormTemplate.bind({}); 12 | 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /front/src/components/ErrorBoundary/ErrorBoundary.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | class ErrorBoundary extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { hasError: false }; 8 | } 9 | 10 | static getDerivedStateFromError(error) { 11 | console.error(error.message); 12 | 13 | return { hasError: true }; 14 | } 15 | 16 | componentDidCatch(error, errorInfo) { 17 | console.error(error, errorInfo); 18 | } 19 | 20 | render() { 21 | if (this.state.hasError) { 22 | return

Something went wrong.

; 23 | } 24 | 25 | return this.props.children; 26 | } 27 | } 28 | 29 | ErrorBoundary.propTypes = { 30 | children: PropTypes.node, 31 | }; 32 | 33 | export default ErrorBoundary; 34 | -------------------------------------------------------------------------------- /front/src/components/ErrorBoundary/ErrorComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ErrorComponent = () => { 4 | return
; 5 | }; 6 | 7 | export default ErrorComponent; 8 | -------------------------------------------------------------------------------- /front/src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import './Footer.scss'; 2 | 3 | import { BABBLE_URL } from '../../constants/api'; 4 | import { Caption2 } from '../../core/Typography'; 5 | import React from 'react'; 6 | 7 | const Footer = () => { 8 | return ( 9 |
10 | 11 | footer logo 17 | 18 |
19 | 20 | Babble은 게임 종류에 관계없이 21 |
22 | 모든 게이머들이 하나로 연결될 수 있는 플랫폼을 지향합니다. 23 |
24 |
25 |
26 | Babble 27 | | 28 | 대표자 Hyun Cheol An, CEO 29 |
30 | 서울특별시 송파구 올림픽로35다길 42 루터회관 31 | mlch1603@naver.com 32 | 33 | Copyright © 2021 Babble All Rights Reserved 34 | 35 |
36 | ); 37 | }; 38 | 39 | export default Footer; 40 | -------------------------------------------------------------------------------- /front/src/components/Footer/Footer.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | @use '../../../style/variables' as v; 3 | @use '../../../style/mixins' as m; 4 | 5 | footer { 6 | color: f.grey-scale(11); 7 | border-top: 1px solid f.grey-scale(4); 8 | width: 100%; 9 | padding: 3rem; 10 | font-size: f.font-size(footer); 11 | margin-top: 10rem; 12 | 13 | & > * { 14 | display: block; 15 | 16 | &:not(:last-child) { 17 | margin-bottom: 0.5rem; 18 | } 19 | } 20 | 21 | .goal { 22 | margin-bottom: 1rem; 23 | } 24 | 25 | .company { 26 | display: flex; 27 | 28 | & > *:not(:last-child) { 29 | margin-right: 0.5rem; 30 | } 31 | } 32 | 33 | .footer-logo-image { 34 | width: 3rem; 35 | display: inline-block; 36 | 37 | img { 38 | width: 100%; 39 | } 40 | } 41 | } 42 | 43 | @include m.media(tablet) { 44 | footer { 45 | margin-top: 8rem; 46 | } 47 | } 48 | 49 | @include m.media(mobile-down, mobile) { 50 | footer { 51 | margin-top: 5rem; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /front/src/components/GameCard/GameCard.js: -------------------------------------------------------------------------------- 1 | import './GameCard.scss'; 2 | 3 | import { Body2, Caption1 } from '../../core/Typography'; 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | 8 | const GameCard = ({ gameName, imageSrc, participants, ...rest }) => { 9 | return ( 10 |
11 |
12 | {`${gameName} 13 |
14 |
15 | 16 | {gameName} 17 | 18 | 19 | {`${participants}명 `}참여 20 | 중 21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | GameCard.propTypes = { 28 | gameName: PropTypes.string, 29 | imageSrc: PropTypes.string, 30 | participants: PropTypes.number, 31 | }; 32 | 33 | export default GameCard; 34 | -------------------------------------------------------------------------------- /front/src/components/GameCard/GameCard.stories.js: -------------------------------------------------------------------------------- 1 | import GameCard from './GameCard'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/GameCard', 6 | component: GameCard, 7 | }; 8 | 9 | const GameCardTemplate = (args) => ( 10 |
11 | 12 |
13 | ); 14 | 15 | export const Default = GameCardTemplate.bind({}); 16 | Default.args = { 17 | gameName: 'League of Legends', 18 | imageSrc: 'https://images.igdb.com/igdb/image/upload/t_cover_big/co254s.jpg', 19 | participants: 1000, 20 | }; 21 | -------------------------------------------------------------------------------- /front/src/components/ImagePreview/ImagePreview.stories.js: -------------------------------------------------------------------------------- 1 | import ImagePreview from './ImagePreview'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/ImagePreview', 6 | component: [ImagePreview], 7 | }; 8 | 9 | const imageList = [ 10 | 'https://d2bidcnq0n74fu.cloudfront.net/img/games/title/League-of-Legends.png', 11 | 'https://d2bidcnq0n74fu.cloudfront.net/img/games/title/League-of-Legends.png', 12 | 'https://d2bidcnq0n74fu.cloudfront.net/img/games/title/League-of-Legends.png', 13 | ]; 14 | 15 | const ImagePreviewTemplate = (args) => ( 16 |
17 | 18 |
19 | ); 20 | 21 | export const General = ImagePreviewTemplate.bind({}); 22 | -------------------------------------------------------------------------------- /front/src/components/ImageRegister/ImageRegister.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | 3 | .input-register-container { 4 | .input-drag-drop { 5 | margin-top: 1rem; 6 | 7 | .caption1 { 8 | display: block; 9 | float: right; 10 | margin-top: 0.5rem; 11 | margin-right: 1rem; 12 | } 13 | 14 | input[type='file'] { 15 | position: absolute; 16 | opacity: 0; 17 | top: 0; 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | width: 100%; 22 | height: 100%; 23 | cursor: pointer; 24 | } 25 | 26 | .drag-drop-zone { 27 | position: relative; 28 | width: 100%; 29 | min-height: 10rem; 30 | max-height: 30rem; 31 | border-radius: f.border-radius(image-preview); 32 | border: 1px solid f.grey-scale(5); 33 | background: f.grey-scale(1); 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | overflow: hidden; 38 | } 39 | 40 | .input-image-preview { 41 | width: 100%; 42 | height: 30rem; 43 | 44 | img { 45 | width: 100%; 46 | height: 100%; 47 | object-fit: contain; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /front/src/components/ImageRegister/ImageRegister.stories.js: -------------------------------------------------------------------------------- 1 | import ImageRegister from './ImageRegister'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/ImageRegister', 6 | component: [ImageRegister], 7 | }; 8 | 9 | const ImagePreviewTemplate = (args) => ( 10 |
11 | 12 |
13 | ); 14 | 15 | export const General = ImagePreviewTemplate.bind({}); 16 | -------------------------------------------------------------------------------- /front/src/components/InfoWithIcon/InfoWithIcon.js: -------------------------------------------------------------------------------- 1 | import './InfoWithIcon.scss'; 2 | 3 | import { Caption2 } from '../../core/Typography'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | 7 | const InfoWithIcon = ({ icon, content = '', color = 'grey' }) => { 8 | return ( 9 | 10 | {icon} 11 | {content} 12 | 13 | ); 14 | }; 15 | 16 | InfoWithIcon.propTypes = { 17 | icon: PropTypes.node, 18 | color: PropTypes.string, 19 | content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 20 | }; 21 | export default InfoWithIcon; 22 | -------------------------------------------------------------------------------- /front/src/components/InfoWithIcon/InfoWithIcon.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | 3 | .info-with-icon-container { 4 | display: flex; 5 | align-items: center; 6 | user-select: none; 7 | 8 | *:nth-child(2) { 9 | margin-right: 0.8rem; 10 | } 11 | 12 | &.grey { 13 | color: f.grey-scale(10); 14 | } 15 | 16 | &.babble-pink { 17 | color: f.primary(babble-pink); 18 | } 19 | 20 | &.red { 21 | color: f.primary(babble-pink-3); 22 | } 23 | 24 | .caption2 { 25 | margin-left: 0.2rem; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /front/src/components/InfoWithIcon/InfoWithIcon.stories.js: -------------------------------------------------------------------------------- 1 | import { AiOutlineEye } from '@react-icons/all-files/ai/AiOutlineEye'; 2 | import { AiOutlineLike } from '@react-icons/all-files/ai/AiOutlineLike'; 3 | import { BiTimeFive } from '@react-icons/all-files/bi/BiTimeFive'; 4 | import { BsPersonFill } from '@react-icons/all-files/bs/BsPersonFill'; 5 | import InfoWithIcon from './InfoWithIcon'; 6 | import React from 'react'; 7 | 8 | export default { 9 | title: 'components/InfoWithIcon', 10 | component: InfoWithIcon, 11 | }; 12 | 13 | const InfoWithIconTemplate = (args) => ( 14 |
15 | 16 |
17 | ); 18 | 19 | export const Views = InfoWithIconTemplate.bind({}); 20 | 21 | Views.args = { 22 | icon: , 23 | content: '18792', 24 | }; 25 | 26 | export const Like = InfoWithIconTemplate.bind({}); 27 | 28 | Like.args = { 29 | icon: , 30 | content: '10000', 31 | }; 32 | 33 | export const Person = InfoWithIconTemplate.bind({}); 34 | 35 | Person.args = { 36 | icon: , 37 | content: '그루밍', 38 | }; 39 | 40 | export const Time = InfoWithIconTemplate.bind({}); 41 | 42 | Time.args = { 43 | icon: , 44 | content: '10/18 09:23', 45 | }; 46 | -------------------------------------------------------------------------------- /front/src/components/KakaoShareButton/KakaoShareButton.scss: -------------------------------------------------------------------------------- 1 | .kakao-share-button { 2 | width: 25px; 3 | span { 4 | display: inline-block; 5 | } 6 | 7 | .share-button { 8 | img { 9 | width: 20px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /front/src/components/Main/Main.js: -------------------------------------------------------------------------------- 1 | import './Main.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Main = ({ children }) => { 7 | return
{children}
; 8 | }; 9 | 10 | Main.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Main; 15 | -------------------------------------------------------------------------------- /front/src/components/Main/Main.scss: -------------------------------------------------------------------------------- 1 | .main-container { 2 | min-height: calc(100vh - 8.3rem); 3 | } 4 | -------------------------------------------------------------------------------- /front/src/components/MainImage/MainImage.js: -------------------------------------------------------------------------------- 1 | import './MainImage.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const MainImage = ({ imageSrc }) => { 7 | return ( 8 |
9 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | MainImage.propTypes = { 16 | imageSrc: PropTypes.string, 17 | }; 18 | 19 | export default MainImage; 20 | -------------------------------------------------------------------------------- /front/src/components/MainImage/MainImage.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/variables' as v; 2 | @use '../../../style/functions' as f; 3 | @use '../../../style/mixins' as m; 4 | 5 | .main-image-container { 6 | width: 100%; 7 | height: 32rem; 8 | position: relative; 9 | user-select: none; 10 | z-index: f.z-index(layer-back-1); 11 | 12 | .gradient { 13 | position: absolute; 14 | z-index: f.z-index(layer-front-1); 15 | width: 100%; 16 | height: calc(100% + 2px); 17 | background: linear-gradient( 18 | to bottom, 19 | rgba(255, 255, 255, 0) 0%, 20 | rgba(255, 255, 255, 0.7) 70%, 21 | rgba(255, 255, 255, 1) 100% 22 | ); 23 | } 24 | 25 | .main-image { 26 | z-index: f.z-index(layer-middle); 27 | width: 100%; 28 | height: 100%; 29 | object-fit: cover; 30 | background-position: center; 31 | } 32 | } 33 | 34 | @include m.media(mobile-down, mobile) { 35 | .main-image-container { 36 | position: absolute; 37 | height: 20rem; 38 | 39 | .gradient { 40 | height: calc(100% + 2px); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front/src/components/MainImage/MainImage.stories.js: -------------------------------------------------------------------------------- 1 | import MainImage from './MainImage'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/MainImage', 6 | component: MainImage, 7 | }; 8 | 9 | const MainImageTemplate = (args) => ( 10 |
11 | 12 |
13 | ); 14 | 15 | export const Default = MainImageTemplate.bind({}); 16 | 17 | Default.args = { 18 | imageSrc: 'https://babble.gg/img/games/title/League%20of%20Legends-x1920.jpg', 19 | }; 20 | -------------------------------------------------------------------------------- /front/src/components/Modal/Modal.js: -------------------------------------------------------------------------------- 1 | import './Modal.scss'; 2 | 3 | import React, { forwardRef } from 'react'; 4 | 5 | import LinearLayout from '../../core/Layout/LinearLayout'; 6 | import { MODAL_TYPE_DEFAULT } from '../../constants/chat'; 7 | import PropTypes from 'prop-types'; 8 | import ReactDOM from 'react-dom'; 9 | 10 | const Modal = forwardRef( 11 | ( 12 | { type = MODAL_TYPE_DEFAULT, isMinimized, onEscapeKeyDown, children }, 13 | ref 14 | ) => { 15 | const content = ( 16 |
21 | {children} 22 |
23 | ); 24 | 25 | return ReactDOM.createPortal(content, document.getElementById('modal')); 26 | } 27 | ); 28 | 29 | Modal.displayName = 'Modal'; 30 | 31 | Modal.propTypes = { 32 | type: PropTypes.oneOf(['default', 'chatting']), 33 | isMinimized: PropTypes.bool, 34 | onEscapeKeyDown: PropTypes.func, 35 | children: PropTypes.node, 36 | }; 37 | 38 | export default Modal; 39 | -------------------------------------------------------------------------------- /front/src/components/Modal/ModalAlert.js: -------------------------------------------------------------------------------- 1 | import './ModalCustom.scss'; 2 | 3 | import { Body2, Caption1, Subtitle2 } from '../../core/Typography'; 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import { RoundButton } from '../../components'; 8 | import { useDefaultModal } from '../../contexts/DefaultModalProvider'; 9 | 10 | const ModalAlert = ({ children }) => { 11 | const { closeModal } = useDefaultModal(); 12 | 13 | return ( 14 |
15 | 안내 16 | {children} 17 | 18 | 확인하기 19 | 20 |
21 | ); 22 | }; 23 | 24 | ModalAlert.propTypes = { 25 | children: PropTypes.node, 26 | }; 27 | 28 | export default ModalAlert; 29 | -------------------------------------------------------------------------------- /front/src/components/Modal/ModalConfirm.js: -------------------------------------------------------------------------------- 1 | import './ModalCustom.scss'; 2 | 3 | import { Body2, Caption1, Subtitle2 } from '../../core/Typography'; 4 | 5 | import LinearLayout from '../../core/Layout/LinearLayout'; 6 | import PropTypes from 'prop-types'; 7 | import React from 'react'; 8 | import { RoundButton } from '../../components'; 9 | import { useDefaultModal } from '../../contexts/DefaultModalProvider'; 10 | 11 | const ModalConfirm = ({ confirmCallback, children }) => { 12 | const { closeModal } = useDefaultModal(); 13 | 14 | const confirm = () => { 15 | confirmCallback?.(); 16 | closeModal(); 17 | }; 18 | 19 | return ( 20 |
21 | 확인하기 22 | {children} 23 | 24 | 25 | 취소하기 26 | 27 | 28 | 확인하기 29 | 30 | 31 |
32 | ); 33 | }; 34 | 35 | ModalConfirm.propTypes = { 36 | confirmCallback: PropTypes.func, 37 | children: PropTypes.node, 38 | }; 39 | 40 | export default ModalConfirm; 41 | -------------------------------------------------------------------------------- /front/src/components/Modal/ModalError.js: -------------------------------------------------------------------------------- 1 | import './ModalCustom.scss'; 2 | 3 | import { Body2, Caption1, Subtitle2 } from '../../core/Typography'; 4 | 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import { RoundButton } from '../../components'; 8 | import { useDefaultModal } from '../../contexts/DefaultModalProvider'; 9 | 10 | const ModalError = ({ children }) => { 11 | const { closeModal } = useDefaultModal(); 12 | 13 | return ( 14 |
15 | ERROR 16 | {children} 17 | 18 | 확인하기 19 | 20 |
21 | ); 22 | }; 23 | 24 | ModalError.propTypes = { 25 | children: PropTypes.node, 26 | }; 27 | 28 | export default ModalError; 29 | -------------------------------------------------------------------------------- /front/src/components/Modal/ModalMinimized.js: -------------------------------------------------------------------------------- 1 | import './Modal.scss'; 2 | 3 | import { IoCloseOutline } from '@react-icons/all-files/io5/IoCloseOutline'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import { VscChevronUp } from '@react-icons/all-files/vsc/VscChevronUp'; 8 | 9 | const ModalMinimized = ({ maximize, close }) => { 10 | const content = ( 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | ); 22 | 23 | return ReactDOM.createPortal(content, document.querySelector('#modal')); 24 | }; 25 | 26 | ModalMinimized.propTypes = { 27 | maximize: PropTypes.func, 28 | close: PropTypes.func, 29 | }; 30 | 31 | export default ModalMinimized; 32 | -------------------------------------------------------------------------------- /front/src/components/NameList/NameList.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/variables' as v; 2 | @use '../../../style/functions' as f; 3 | @use '../../../style/mixins' as m; 4 | 5 | .name-list-container { 6 | width: 100%; 7 | padding: 1.8rem 1.2rem; 8 | max-height: 16rem; 9 | border-radius: f.border-radius(large-container); 10 | border: 1px solid f.grey-scale(3); 11 | font-size: f.font-size(chat); 12 | display: flex; 13 | flex-wrap: wrap; 14 | overflow-y: auto; 15 | 16 | @include m.custom-scrollbar; 17 | 18 | .no-data { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | width: 100%; 23 | } 24 | 25 | .tag-container { 26 | border: 0; 27 | background-color: #eff1f3; 28 | border-radius: 8px; 29 | text-shadow: 0px 1px 2px rgb(0 0 0 / 20%); 30 | height: 2.8rem; 31 | margin: 0.5rem 1rem; 32 | 33 | .tag-content { 34 | span { 35 | cursor: pointer; 36 | 37 | &:hover { 38 | color: f.primary(babble-pink); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /front/src/components/NameList/NameList.stories.js: -------------------------------------------------------------------------------- 1 | import NameList from './NameList'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/NameList', 6 | component: NameList, 7 | }; 8 | 9 | const NameListTemplate = (args) => ; 10 | 11 | export const Default = NameListTemplate.bind({}); 12 | 13 | Default.args = { 14 | list: [ 15 | 'League of Legends', 16 | 'Battlegrounds', 17 | 'Overwatch', 18 | 'Apex Legends', 19 | 'Sudden Attack', 20 | 'Valorant', 21 | 'Kartrider Rush +', 22 | 'Minecraft', 23 | 'Hearthstone', 24 | 'Tekken 7', 25 | 'Maplestory', 26 | 'Maplestory2', 27 | 'Escape from Tarkov', 28 | 'Monster Hunter Iceborne', 29 | 'Genshin Impact', 30 | 'VR Chat', 31 | 'Beat Saber', 32 | 'Super Mario Maker', 33 | 'Pokemon Unite', 34 | 'Unrailed', 35 | 'Fortnite', 36 | 'Lost Ark', 37 | 'Diablo 2 Resurrected', 38 | 'Diablo 3', 39 | 'Starcraft', 40 | 'Starcraft 2', 41 | 'Warcraft 3', 42 | 'FIFA 22', 43 | 'Teamfight Tactics', 44 | 'Dead by Daylight', 45 | 'Soulworker', 46 | 'World of Warcraft', 47 | 'Grand Theft Auto 5', 48 | 'Roblox', 49 | ], 50 | erasable: true, 51 | onClickNames: () => console.log('Tag name clicked!'), 52 | }; 53 | -------------------------------------------------------------------------------- /front/src/components/NavBar/NavBar.js: -------------------------------------------------------------------------------- 1 | import './NavBar.scss'; 2 | 3 | import { Body1 } from '../../core/Typography'; 4 | import { Link } from 'react-router-dom'; 5 | import Logo from '../../core/Logo/Logo'; 6 | import PATH from '../../constants/path'; 7 | import PageLayout from '../../core/Layout/PageLayout'; 8 | import React from 'react'; 9 | import ThemeMode from '../ThemeMode/ThemeMode'; 10 | 11 | const NavBar = () => { 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | 게임 매칭 21 | 22 | 23 | 익명 게시판 24 | 25 | 26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default NavBar; 33 | -------------------------------------------------------------------------------- /front/src/components/NicknameSection/NicknameSection.stories.js: -------------------------------------------------------------------------------- 1 | import NicknameSection from './NicknameSection'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/NicknameSection', 6 | component: NicknameSection, 7 | }; 8 | 9 | const NicknameSectionTemplate = () => ( 10 |
11 | 12 |
13 | ); 14 | 15 | export const Default = NicknameSectionTemplate.bind({}); 16 | -------------------------------------------------------------------------------- /front/src/components/NotFound/NotFound.js: -------------------------------------------------------------------------------- 1 | import './NotFound.scss'; 2 | 3 | import { BABBLE_URL } from '../../constants/api'; 4 | import { Body2 } from '../../core/Typography'; 5 | import { Headline1 } from '../../core/Typography'; 6 | import React from 'react'; 7 | import { useHistory } from 'react-router-dom'; 8 | 9 | const NotFound = () => { 10 | const history = useHistory(); 11 | 12 | return ( 13 |
14 |
15 | Oops! 16 | 17 | 404 Not Found 에러 발생! 18 | 19 | {{history.location.pathname}}에 해당하는 20 | 페이지를 찾지 못했어요. 21 | 22 |
23 | 404 image 24 |
25 | ); 26 | }; 27 | 28 | export default NotFound; 29 | -------------------------------------------------------------------------------- /front/src/components/NotFound/NotFound.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | 3 | .not-found-container { 4 | width: 100%; 5 | height: calc(100vh - 15rem); 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | user-select: none; 10 | 11 | .content { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: flex-start; 16 | 17 | .headline1 { 18 | margin: 1rem; 19 | } 20 | 21 | .body2 { 22 | margin: 0.2rem 1rem; 23 | 24 | .path { 25 | color: f.primary(babble-pink); 26 | } 27 | } 28 | } 29 | 30 | img { 31 | width: 50rem; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front/src/components/Room/Room.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Room from './Room'; 3 | 4 | export default { 5 | title: 'components/Room', 6 | component: Room, 7 | }; 8 | 9 | const RoomTemplate = (args) => ( 10 | <> 11 | 12 | 13 | ); 14 | 15 | export const Column = RoomTemplate.bind({}); 16 | 17 | Column.args = { 18 | imageSrc: 19 | 'https://i.pinimg.com/474x/1c/4b/f0/1c4bf0cdcc3102126b7caeb8749f5c55.jpg', 20 | onClickRoom: () => {}, 21 | room: { 22 | roomId: 1, 23 | host: { 24 | id: 1, 25 | nickname: 'nickname', 26 | }, 27 | headCount: { 28 | current: 2, 29 | max: 3, 30 | }, 31 | tags: [ 32 | { name: '골드' }, 33 | { name: '초보' }, 34 | { name: '2시간' }, 35 | { name: '음성 가능' }, 36 | ], 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /front/src/components/ScrollToTop/ScrollToTop.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | const ScrollToTop = () => { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | window.scrollTo(0, 0); 9 | }, [pathname]); 10 | 11 | return null; 12 | }; 13 | 14 | export default ScrollToTop; 15 | -------------------------------------------------------------------------------- /front/src/components/SearchInput/TextSearchInput.js: -------------------------------------------------------------------------------- 1 | import { FiSearch } from '@react-icons/all-files/fi/FiSearch'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | const TextSearchInput = ({ 6 | placeholder = '키워드를 입력해주세요.', 7 | onSearchButtonClick = () => {}, 8 | }) => { 9 | return ( 10 |
11 | 17 | 20 |
21 | ); 22 | }; 23 | 24 | TextSearchInput.propTypes = { 25 | placeholder: PropTypes.string, 26 | onSearchButtonClick: PropTypes.func, 27 | }; 28 | 29 | export default TextSearchInput; 30 | -------------------------------------------------------------------------------- /front/src/components/SearchInput/service/escapeRegExp.js: -------------------------------------------------------------------------------- 1 | function escapeRegExp(string) { 2 | return string && /[\\^$.*+?()[\]{}|]/g.test(string) 3 | ? string.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') 4 | : string || ''; 5 | } 6 | 7 | export default escapeRegExp; 8 | -------------------------------------------------------------------------------- /front/src/components/SearchInput/service/getPhonemes.js: -------------------------------------------------------------------------------- 1 | import { BASE, FINALES, INITIALS, MEDIALS } from './constant'; 2 | 3 | function getPhonemes(char) { 4 | let initial = ''; 5 | let medial = ''; 6 | let finale = ''; 7 | let initialOffset = -1; 8 | let medialOffset = -1; 9 | let finaleOffset = -1; 10 | 11 | if (char.match(/[ㄱ-ㅎ]/)) { 12 | initial = char; 13 | initialOffset = INITIALS.join('').search(char); 14 | } else if (char.match(/[가-힣]/)) { 15 | const tmp = char.charCodeAt(0) - BASE; 16 | finaleOffset = tmp % FINALES.length; 17 | medialOffset = ((tmp - finaleOffset) / FINALES.length) % MEDIALS.length; 18 | initialOffset = 19 | ((tmp - finaleOffset) / FINALES.length - medialOffset) / MEDIALS.length; 20 | initial = INITIALS[initialOffset]; 21 | medial = MEDIALS[medialOffset]; 22 | finale = FINALES[finaleOffset]; 23 | } 24 | return { initial, medial, finale, initialOffset, medialOffset, finaleOffset }; 25 | } 26 | 27 | export default getPhonemes; 28 | -------------------------------------------------------------------------------- /front/src/components/Slider/Slider.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Slider from './Slider'; 3 | 4 | export default { 5 | title: 'components/Slider', 6 | component: Slider, 7 | }; 8 | 9 | const SliderTemplate = (args) => ( 10 | <> 11 | 12 | 13 | ); 14 | 15 | export const DefaultSlider = SliderTemplate.bind({}); 16 | 17 | DefaultSlider.args = { 18 | imageList: [ 19 | { 20 | id: 0, 21 | info: '곰돌이 푸', 22 | src: 'https://picsum.photos/id/10/1280/700', 23 | }, 24 | { 25 | id: 1, 26 | info: '피글렛', 27 | src: 'https://picsum.photos/id/20/1280/700', 28 | }, 29 | { 30 | id: 2, 31 | info: '티거', 32 | src: 'https://picsum.photos/id/30/1280/700', 33 | }, 34 | { 35 | id: 3, 36 | info: '이요르', 37 | src: 'https://picsum.photos/id/40/1280/700', 38 | }, 39 | { 40 | id: 4, 41 | info: '이요르', 42 | src: 'https://picsum.photos/id/50/1280/700', 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /front/src/components/SpeechBubble/SpeechBubbleWithAvatar.js: -------------------------------------------------------------------------------- 1 | import AvatarImage from '../Avatar/AvatarImage'; 2 | import { Caption2 } from '../../core/Typography'; 3 | import LinearLayout from '../../core/Layout/LinearLayout'; 4 | import PropTypes from 'prop-types'; 5 | import React from 'react'; 6 | import SpeechBubble from './SpeechBubble'; 7 | 8 | const SpeechBubbleWithAvatar = ({ size = 'small', time, user, children }) => { 9 | return ( 10 |
11 | 12 | 17 | 18 | {user.nickname} 19 | 20 | {children} 21 | 22 | 23 | 24 |
25 | ); 26 | }; 27 | 28 | SpeechBubbleWithAvatar.propTypes = { 29 | size: PropTypes.string, 30 | time: PropTypes.string, 31 | user: PropTypes.shape({ 32 | id: PropTypes.number, 33 | nickname: PropTypes.string, 34 | avatar: PropTypes.string, 35 | }), 36 | children: PropTypes.node, 37 | }; 38 | 39 | export default SpeechBubbleWithAvatar; 40 | -------------------------------------------------------------------------------- /front/src/components/Tag/Tag.js: -------------------------------------------------------------------------------- 1 | import './Tag.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Tag = ({ customClass = '', children, onClickTagName = () => {} }) => { 7 | return ( 8 | 9 | 10 | {children} 11 | 12 | 13 | ); 14 | }; 15 | 16 | Tag.propTypes = { 17 | children: PropTypes.node, 18 | customClass: PropTypes.string, 19 | onClickTagName: PropTypes.func, 20 | }; 21 | 22 | export default Tag; 23 | -------------------------------------------------------------------------------- /front/src/components/Tag/Tag.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/variables' as v; 2 | @use '../../../style/functions' as f; 3 | 4 | .tag-container { 5 | display: inline-flex; 6 | width: fit-content; 7 | padding: 0.5rem 1.2rem; 8 | align-items: center; 9 | justify-content: center; 10 | border: 1px solid f.primary(babble-pink); 11 | border-radius: f.border-radius(tag); 12 | user-select: none; 13 | white-space: pre; 14 | word-break: keep-all; 15 | } 16 | 17 | .erasable { 18 | padding: 0.4rem 0.8rem 0.4rem 1.2rem; 19 | 20 | span { 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | 26 | button { 27 | margin-left: 0.6rem; 28 | 29 | :hover { 30 | color: f.primary(babble-pink); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /front/src/components/Tag/Tag.stories.js: -------------------------------------------------------------------------------- 1 | import { Caption2, Subtitle3 } from '../../core/Typography'; 2 | 3 | import React from 'react'; 4 | import Tag from './Tag'; 5 | import TagErasable from './TagErasable'; 6 | 7 | export default { 8 | title: 'components/Tag', 9 | component: [Tag, TagErasable], 10 | }; 11 | 12 | const TagTemplate = (args) => ( 13 | <> 14 |
15 | Default Tag 16 |
17 | 18 | 실버 19 | 20 |
21 |
22 |
23 | Erasable Tag 24 |
25 | 26 | 실버 27 | 28 |
29 | 30 | ); 31 | 32 | export const General = TagTemplate.bind({}); 33 | -------------------------------------------------------------------------------- /front/src/components/Tag/TagErasable.js: -------------------------------------------------------------------------------- 1 | import './Tag.scss'; 2 | 3 | import BadgeClickable from '../Badge/BadgeClickable'; 4 | import { IoCloseOutline } from '@react-icons/all-files/io5/IoCloseOutline'; 5 | import PropTypes from 'prop-types'; 6 | import React from 'react'; 7 | import Tag from './Tag'; 8 | 9 | const TagErasable = ({ onDeleteTag, children, onClickTagName }) => { 10 | return ( 11 | 12 | {children} 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | TagErasable.propTypes = { 21 | children: PropTypes.node, 22 | onDeleteTag: PropTypes.func, 23 | onClickTagName: PropTypes.func, 24 | }; 25 | 26 | export default TagErasable; 27 | -------------------------------------------------------------------------------- /front/src/components/ThemeMode/ThemeMode.js: -------------------------------------------------------------------------------- 1 | import './ThemeMode.scss'; 2 | 3 | import React from 'react'; 4 | import useTheme from '../../hooks/useTheme'; 5 | 6 | const ThemeMode = () => { 7 | const { darkTheme, toggleDarkTheme } = useTheme(); 8 | 9 | return ( 10 |
11 |
12 | 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default ThemeMode; 25 | -------------------------------------------------------------------------------- /front/src/components/ThemeMode/ThemeMode.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | justify-content: space-between; 4 | 5 | .theme-container { 6 | display: flex; 7 | justify-content: flex-end; 8 | align-items: flex-end; 9 | 10 | button { 11 | background-repeat: no-repeat; 12 | background-size: 100%; 13 | position: absolute; 14 | right: 0rem; 15 | 16 | img { 17 | width: 2rem; 18 | height: 2rem; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /front/src/constants/api.js: -------------------------------------------------------------------------------- 1 | const BABBLE_URL = 'https://babble.gg'; 2 | const BASE_URL = 'https://api.babble.gg'; 3 | const TEST_URL = 'https://test-api.babble.gg'; 4 | 5 | const CONNECTION_URL = `${BASE_URL}/connection`; 6 | 7 | const SUBSCRIBE_URL = { 8 | USERS: (roomId) => `/topic/rooms/${roomId}/users`, 9 | CHAT: (roomId) => `/topic/rooms/${roomId}/chat`, 10 | }; 11 | 12 | const SEND_URL = { 13 | USERS: (roomId) => `/ws/rooms/${roomId}/users`, 14 | CHAT: (roomId) => `/ws/rooms/${roomId}/chat`, 15 | }; 16 | 17 | export { 18 | BABBLE_URL, 19 | BASE_URL, 20 | TEST_URL, 21 | CONNECTION_URL, 22 | SUBSCRIBE_URL, 23 | SEND_URL, 24 | }; 25 | -------------------------------------------------------------------------------- /front/src/constants/board.js: -------------------------------------------------------------------------------- 1 | export const LEAST_LIKE_TO_HOT_POST = 10; 2 | -------------------------------------------------------------------------------- /front/src/constants/chat.js: -------------------------------------------------------------------------------- 1 | export const SESSION_ID_LENGTH = 8; 2 | export const MODAL_TYPE_CHATTING = 'chatting'; 3 | export const MODAL_TYPE_DEFAULT = 'default'; 4 | export const SOCKET_URL_DIVIDER = '/'; 5 | export const NICKNAME_MAX_LENGTH = 24; 6 | export const NICKNAME_MIN_LENGTH = 1; 7 | -------------------------------------------------------------------------------- /front/src/constants/event.js: -------------------------------------------------------------------------------- 1 | export const SCROLL = { 2 | STEP: 20, 3 | NEUTRAL: 0, 4 | }; 5 | -------------------------------------------------------------------------------- /front/src/constants/i18n.js: -------------------------------------------------------------------------------- 1 | export const KOREAN = 'ko-KR'; 2 | -------------------------------------------------------------------------------- /front/src/constants/notification.js: -------------------------------------------------------------------------------- 1 | const PERMISSION = { 2 | GRANTED: 'granted', 3 | DENIED: 'denied', 4 | }; 5 | 6 | export { PERMISSION }; 7 | -------------------------------------------------------------------------------- /front/src/constants/path.js: -------------------------------------------------------------------------------- 1 | const PATH = { 2 | HOME: '/', 3 | MAKE_ROOM: '/make-room', 4 | ROOM_LIST: '/games', 5 | BOARD: '/board', 6 | VIEW_POST: '/post', 7 | WRITE_POST: '/write', 8 | ADMIN: '/admin', 9 | }; 10 | 11 | export default PATH; 12 | -------------------------------------------------------------------------------- /front/src/constants/regex.js: -------------------------------------------------------------------------------- 1 | const PATTERNS = { 2 | NOTIFICATION_COUNT: /^\(\d+\)/, 3 | KOREAN: /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]+/g, 4 | SPECIAL_CHARACTERS: /[^0-9|ㄱ-ㅎ|ㅏ-ㅣ|가-힣|a-z|A-Z]+/g, 5 | SPECIAL_CHARACTERS_WITHOUT_SPACE: /[^0-9|ㄱ-ㅎ|ㅏ-ㅣ|가-힣|a-z|A-Z|\s]+/g, 6 | SPACE: /\s/g, 7 | LINKS: /([\w+]+:\/\/)([\w\d-]+\.)*[\w-]+[.:]\w+([/?=&#.]?[\w-]+)*\/?/gm, 8 | }; 9 | 10 | export { PATTERNS }; 11 | -------------------------------------------------------------------------------- /front/src/contexts/ThemeChangeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState } from 'react'; 2 | 3 | import PropTypes from 'prop-types'; 4 | 5 | export const ThemeChangeContext = createContext(null); 6 | 7 | export const ThemeChangeContextProvider = ({ children }) => { 8 | const [darkTheme, setDarkTheme] = useState(false); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | ThemeChangeContextProvider.propTypes = { 18 | children: PropTypes.node, 19 | }; 20 | -------------------------------------------------------------------------------- /front/src/core/Layout/LinearLayout.js: -------------------------------------------------------------------------------- 1 | import './LinearLayout.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const LinearLayout = ({ direction = 'col', children }) => { 7 | return
{children}
; 8 | }; 9 | 10 | LinearLayout.propTypes = { 11 | direction: PropTypes.oneOf(['col', 'row']), 12 | children: PropTypes.node, 13 | }; 14 | 15 | export default LinearLayout; 16 | -------------------------------------------------------------------------------- /front/src/core/Layout/LinearLayout.scss: -------------------------------------------------------------------------------- 1 | section { 2 | &.linear-layout { 3 | display: flex; 4 | justify-content: flex-start; 5 | align-items: center; 6 | height: 100%; 7 | 8 | &.col { 9 | flex-direction: column; 10 | } 11 | 12 | &.row { 13 | flex-direction: row; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /front/src/core/Layout/PageLayout.js: -------------------------------------------------------------------------------- 1 | import './PageLayout.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const PageLayout = ({ type = 'default', direction = 'column', children }) => { 7 | return ( 8 |
{children}
9 | ); 10 | }; 11 | 12 | PageLayout.propTypes = { 13 | type: PropTypes.oneOf(['default', 'narrow']), 14 | direction: PropTypes.oneOf(['column', 'row']), 15 | children: PropTypes.node, 16 | }; 17 | 18 | export default PageLayout; 19 | -------------------------------------------------------------------------------- /front/src/core/Layout/PageLayout.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/mixins' as m; 2 | 3 | .page-layout { 4 | display: flex; 5 | justify-content: center; 6 | padding: 0 5rem; 7 | 8 | &.default { 9 | width: 100%; 10 | max-width: 102.66664rem; 11 | margin: 0 auto; 12 | } 13 | 14 | &.narrow { 15 | width: 100%; 16 | max-width: 59.33332rem; 17 | margin: 0 auto; 18 | } 19 | 20 | &.column { 21 | flex-direction: column; 22 | } 23 | } 24 | 25 | @include m.media(mobile-down, mobile) { 26 | .page-layout { 27 | display: flex; 28 | width: 100%; 29 | justify-content: center; 30 | padding: 0 1rem; 31 | 32 | &.column { 33 | flex-direction: column; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /front/src/core/Logo/Logo.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/animations'; 2 | 3 | .logo-container { 4 | svg { 5 | &:not(:root) { 6 | overflow: visible; 7 | } 8 | 9 | &:hover { 10 | .speech-bubble { 11 | transform-origin: center; 12 | animation: fill 0.7s cubic-bezier(0.72, 0, 0.32, 1.04), 13 | tilt 0.8s cubic-bezier(0.72, 0, 0.32, 1.04); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /front/src/core/Logo/Logo.stories.js: -------------------------------------------------------------------------------- 1 | import Logo from './Logo'; 2 | import React from 'react'; 3 | 4 | export default { 5 | title: 'components/Logo', 6 | component: Logo, 7 | }; 8 | 9 | const LogoTemplate = (args) => ; 10 | 11 | export const Default = LogoTemplate.bind({}); 12 | 13 | Default.args = { 14 | width: 150, 15 | }; 16 | -------------------------------------------------------------------------------- /front/src/core/Typography/Body1.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Body1 = ({ children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Body1.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Body1; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/Body2.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Body2 = ({ children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Body2.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Body2; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/Caption1.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Caption1 = ({ children }) => { 7 | return {children}; 8 | }; 9 | 10 | Caption1.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Caption1; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/Caption2.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Caption2 = ({ customClass = '', children, ...rest }) => { 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | }; 13 | 14 | Caption2.propTypes = { 15 | customClass: PropTypes.string, 16 | children: PropTypes.node, 17 | }; 18 | 19 | export default Caption2; 20 | -------------------------------------------------------------------------------- /front/src/core/Typography/Headline1.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Headline1 = ({ bold = false, children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Headline1.propTypes = { 11 | bold: PropTypes.string, 12 | children: PropTypes.node, 13 | }; 14 | 15 | export default Headline1; 16 | -------------------------------------------------------------------------------- /front/src/core/Typography/Headline2.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Headline2 = ({ bold = false, children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Headline2.propTypes = { 11 | bold: PropTypes.string, 12 | children: PropTypes.node, 13 | }; 14 | 15 | export default Headline2; 16 | -------------------------------------------------------------------------------- /front/src/core/Typography/Subtitle1.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Subtitle1 = ({ children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Subtitle1.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Subtitle1; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/Subtitle2.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Subtitle2 = ({ children }) => { 7 | return

{children}

; 8 | }; 9 | 10 | Subtitle2.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Subtitle2; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/Subtitle3.js: -------------------------------------------------------------------------------- 1 | import './Typography.scss'; 2 | 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | 6 | const Subtitle3 = ({ children }) => { 7 | return
{children}
; 8 | }; 9 | 10 | Subtitle3.propTypes = { 11 | children: PropTypes.node, 12 | }; 13 | 14 | export default Subtitle3; 15 | -------------------------------------------------------------------------------- /front/src/core/Typography/index.js: -------------------------------------------------------------------------------- 1 | export { default as Body1 } from './Body1'; 2 | export { default as Body2 } from './Body2'; 3 | export { default as Caption1 } from './Caption1'; 4 | export { default as Caption2 } from './Caption2'; 5 | export { default as Headline1 } from './Headline1'; 6 | export { default as Headline2 } from './Headline2'; 7 | export { default as Subtitle1 } from './Subtitle1'; 8 | export { default as Subtitle2 } from './Subtitle2'; 9 | export { default as Subtitle3 } from './Subtitle3'; 10 | -------------------------------------------------------------------------------- /front/src/hooks/useDebounce.js: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | const useDebounce = () => { 4 | const timerRef = useRef(null); 5 | 6 | const debounce = (callback, delay) => { 7 | if (timerRef.current) clearTimeout(timerRef.current); 8 | 9 | timerRef.current = setTimeout(() => { 10 | callback(); 11 | }, delay); 12 | }; 13 | 14 | return { debounce }; 15 | }; 16 | 17 | export default useDebounce; 18 | -------------------------------------------------------------------------------- /front/src/hooks/useInterval.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useInterval = (callback, delay) => { 4 | const savedCallback = useRef(null); 5 | 6 | useEffect(() => { 7 | savedCallback.current = callback; 8 | }, [callback]); 9 | 10 | useEffect(() => { 11 | const executeCallback = () => { 12 | savedCallback.current(); 13 | }; 14 | 15 | const timerId = setInterval(executeCallback, delay); 16 | 17 | return () => clearInterval(timerId); 18 | }, []); 19 | }; 20 | 21 | export default useInterval; 22 | -------------------------------------------------------------------------------- /front/src/hooks/useScript.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useScript = (url) => { 4 | useEffect(() => { 5 | const script = document.createElement('script'); 6 | 7 | script.src = url; 8 | script.async = true; 9 | 10 | document.body.appendChild(script); 11 | 12 | return () => { 13 | document.body.removeChild(script); 14 | }; 15 | }, [url]); 16 | }; 17 | 18 | export default useScript; 19 | -------------------------------------------------------------------------------- /front/src/hooks/useTheme.js: -------------------------------------------------------------------------------- 1 | import { getLocalStorage, setLocalStorage } from '../utils/storage'; 2 | 3 | import { ThemeChangeContext } from '../contexts/ThemeChangeProvider'; 4 | import { useContext } from 'react'; 5 | 6 | const useTheme = () => { 7 | const { darkTheme, setDarkTheme } = useContext(ThemeChangeContext); 8 | 9 | const toggleDarkTheme = () => { 10 | const isDarkMode = getLocalStorage('darkMode'); 11 | setLocalStorage('darkMode', !isDarkMode); 12 | document.body.classList.toggle('dark-mode'); 13 | setDarkTheme((wasDarkMode) => !wasDarkMode); 14 | }; 15 | 16 | return { darkTheme, toggleDarkTheme }; 17 | }; 18 | 19 | export default useTheme; 20 | -------------------------------------------------------------------------------- /front/src/hooks/useThrottle.js: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | const useThrottle = () => { 4 | const timerRef = useRef(null); 5 | 6 | const throttle = (callback, delay) => { 7 | if (!timerRef.current) { 8 | timerRef.current = setTimeout(() => { 9 | callback(); 10 | 11 | clearTimeout(timerRef.current); 12 | timerRef.current = null; 13 | }, delay); 14 | } 15 | }; 16 | 17 | return { throttle }; 18 | }; 19 | 20 | export default useThrottle; 21 | -------------------------------------------------------------------------------- /front/src/hooks/useUpdateEffect.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useUpdateEffect = (effect, dependencies) => { 4 | const isFirstMount = useRef(true); 5 | 6 | useEffect(() => { 7 | if (!isFirstMount.current) effect(); 8 | else isFirstMount.current = false; 9 | }, dependencies); 10 | }; 11 | 12 | export default useUpdateEffect; 13 | -------------------------------------------------------------------------------- /front/src/pages/BabbleManagement/BabbleManagement.js: -------------------------------------------------------------------------------- 1 | import './BabbleManagement.scss'; 2 | 3 | import { AdminManagement, GameManagement, TagManagement } from '../../chunks'; 4 | 5 | import React from 'react'; 6 | 7 | const BabbleManagement = () => { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default BabbleManagement; 18 | -------------------------------------------------------------------------------- /front/src/pages/ChangeNickname/ChangeNickname.scss: -------------------------------------------------------------------------------- 1 | @use '../../../style/functions' as f; 2 | @use '../../../style/variables' as v; 3 | @use '../../../style/mixins' as m; 4 | 5 | .nickname-container { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | 10 | .control-bar { 11 | position: absolute; 12 | cursor: pointer; 13 | top: 1.5rem; 14 | right: 1.5rem; 15 | } 16 | 17 | .control-inputs { 18 | text-align: center; 19 | margin-bottom: 2rem; 20 | 21 | .subtitle3 { 22 | margin-bottom: 1.5rem; 23 | } 24 | 25 | .form { 26 | margin: 1rem 0; 27 | display: inline-block; 28 | font-size: f.font-size(tiny); 29 | max-width: fit-content; 30 | 31 | &.error { 32 | color: f.alert(fail); 33 | } 34 | 35 | &.confirm { 36 | color: f.alert(confirm); 37 | } 38 | } 39 | } 40 | 41 | .control-buttons { 42 | display: flex; 43 | 44 | & > button:not(:last-child) { 45 | margin-right: 1rem; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /front/src/utils/id.js: -------------------------------------------------------------------------------- 1 | import { parse, v4 } from 'uuid'; 2 | 3 | export const getNumberId = () => { 4 | const idString = v4(); 5 | const bytes = parse(idString); 6 | const id = Number( 7 | [...bytes].map((v) => v.toString(10).padStart(2, '0')).join('') 8 | ); 9 | 10 | return id; 11 | }; 12 | 13 | export const getShortNumberId = () => { 14 | const idString = v4(); 15 | const bytes = parse(idString); 16 | const id = Number( 17 | [...bytes] 18 | .slice(0, 5) 19 | .map((v) => v.toString(10).padStart(2, '0')) 20 | .join('') 21 | ); 22 | 23 | return id; 24 | }; 25 | -------------------------------------------------------------------------------- /front/src/utils/storage.js: -------------------------------------------------------------------------------- 1 | export const setSessionStorage = (key, value) => { 2 | sessionStorage.setItem(key, value); 3 | }; 4 | 5 | export const getSessionStorage = (key) => { 6 | return sessionStorage.getItem(key); 7 | }; 8 | 9 | export const removeFromSessionStorage = (key) => { 10 | sessionStorage.removeItem(key); 11 | }; 12 | 13 | export const setLocalStorage = (key, value) => { 14 | localStorage.setItem(key, value); 15 | }; 16 | 17 | export const getLocalStorage = (key) => { 18 | return localStorage.getItem(key); 19 | }; 20 | 21 | export const removeFromLocalStorage = (key) => { 22 | localStorage.removeItem(key); 23 | }; 24 | -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoBold.ttf -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoBold.woff -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoBold.woff2 -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoMedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoMedium.ttf -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoMedium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoMedium.woff -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoMedium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoMedium.woff2 -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoRegular.ttf -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoRegular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoRegular.woff -------------------------------------------------------------------------------- /front/style/fonts/SpoqaHanSansNeoRegular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/front/style/fonts/SpoqaHanSansNeoRegular.woff2 -------------------------------------------------------------------------------- /images/Desktop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/Desktop.gif -------------------------------------------------------------------------------- /images/babble_be_cicd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/babble_be_cicd.png -------------------------------------------------------------------------------- /images/babble_fe_cicd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/babble_fe_cicd.png -------------------------------------------------------------------------------- /images/babble_production_infra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/babble_production_infra.png -------------------------------------------------------------------------------- /images/babble_tech_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/babble_tech_stack.png -------------------------------------------------------------------------------- /images/게시글검색.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게시글검색.gif -------------------------------------------------------------------------------- /images/게시글삭제.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게시글삭제.gif -------------------------------------------------------------------------------- /images/게시글수정.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게시글수정.gif -------------------------------------------------------------------------------- /images/게시글작성.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게시글작성.gif -------------------------------------------------------------------------------- /images/게시글조회.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게시글조회.gif -------------------------------------------------------------------------------- /images/게임검색.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/게임검색.gif -------------------------------------------------------------------------------- /images/닉네임변경.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/닉네임변경.gif -------------------------------------------------------------------------------- /images/댓글작성.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/댓글작성.gif -------------------------------------------------------------------------------- /images/방검색.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/방검색.gif -------------------------------------------------------------------------------- /images/방생성.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/방생성.gif -------------------------------------------------------------------------------- /images/채팅방.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/채팅방.gif -------------------------------------------------------------------------------- /images/태그.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2021-babble/97991ee89c5ab443a24910245db2e374c974db61/images/태그.gif -------------------------------------------------------------------------------- /infrastructure/sonarqube/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | sonarqube: 5 | container_name: sonarqube-server 6 | image: sonarqube:lts-community 7 | depends_on: 8 | - db 9 | environment: 10 | SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar 11 | SONAR_JDBC_USERNAME: sonar 12 | SONAR_JDBC_PASSWORD: sonar 13 | volumes: 14 | - ./sonarqube/data:/opt/sonarqube/data 15 | - ./sonarqube/extensions:/opt/sonarqube/extensions 16 | - ./sonarqube/logs:/opt/sonarqube/logs 17 | ports: 18 | - "9000:9000" 19 | 20 | db: 21 | container_name: sonarqube-db 22 | image: postgres:12 23 | environment: 24 | POSTGRES_USER: sonar 25 | POSTGRES_PASSWORD: sonar 26 | volumes: 27 | - ./postgresql:/var/lib/postgresql 28 | - ./postgresql/data:/var/lib/postgresql/data 29 | 30 | -------------------------------------------------------------------------------- /infrastructure/web-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | COPY api-fullchain.pem /etc/letsencrypt/live/api.babble.gg/fullchain.pem 5 | COPY api-privkey.pem /etc/letsencrypt/live/api.babble.gg/privkey.pem 6 | COPY sonarqube-fullchain.pem /etc/letsencrypt/live/sonarqube.babble.gg/fullchain.pem 7 | COPY sonarqube-privkey.pem /etc/letsencrypt/live/sonarqube.babble.gg/privkey.pem 8 | 9 | -------------------------------------------------------------------------------- /infrastructure/web-server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | #버전 정의 2 | # 버전에 따라서 지원하는 형식이 다릅니다. 3 | version: '3.7' 4 | 5 | #service 정의 6 | # docker-compose로 생성 할 container의 옵션을 정의 합니다. 7 | # service안의 container들은 하나의 project로서 docker-compose로 관리 됩니다. 8 | services: 9 | #생성 할 container 이름을 지정 합니다. 10 | nginx: 11 | container_name: web-server-nginx 12 | #container 생성시 사용 할 이미지 지정 13 | image: reverse-proxy:latest 14 | #build 옵션 15 | # docker-compose build 옵션에서 사용 됩니다. 16 | # dockerfile에 명시된 FROM의 image를 사용하여 위에 명시된image 이름과 tag로 새로운 image를 생성 합니다. 17 | build: 18 | #dockerfile의 위치를 지정 합니다. 19 | context: . 20 | #container port mapping 정보 21 | volumes: 22 | - ./docker-volume/log/nginx:/var/log/nginx 23 | ports: 24 | - "80:80" 25 | - "443:443" 26 | #환경 변수 리스트를 정의 27 | environment: 28 | - TZ=Asia/Seoul 29 | --------------------------------------------------------------------------------