├── .editorconfig ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── back.yml │ ├── front-dev.yml │ ├── front-prod.yml │ ├── sonar-build.yml │ └── terraform.yml ├── .gitignore ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── Dockerfile ├── PrologJavaStyle.xml ├── README.md ├── appspec.yml ├── build.gradle ├── docker │ └── docker-compose.yml ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts │ ├── after-install.sh │ ├── before-install.sh │ ├── start-app.sh │ └── stop-app.sh ├── settings.gradle └── src │ ├── acceptanceTest │ ├── java │ │ └── wooteco │ │ │ └── prolog │ │ │ ├── AcceptanceContext.java │ │ │ ├── AcceptanceHooks.java │ │ │ ├── AcceptanceSteps.java │ │ │ ├── AcceptanceTestRunnerIT.java │ │ │ ├── fixtures │ │ │ ├── ArticleFixture.java │ │ │ ├── CommentAcceptanceFixture.java │ │ │ ├── CurriculumFixture.java │ │ │ ├── GithubResponses.java │ │ │ ├── GithubTestController.java │ │ │ ├── KeywordAcceptanceFixture.java │ │ │ ├── LevellogFixture.java │ │ │ ├── MissionAcceptanceFixture.java │ │ │ ├── PostAcceptanceFixture.java │ │ │ ├── SessionAcceptanceFixture.java │ │ │ ├── StudylogAcceptanceFixture.java │ │ │ └── TagAcceptanceFixture.java │ │ │ └── steps │ │ │ ├── ArticleStepDefinitions.java │ │ │ ├── BadgesStepDefinitions.java │ │ │ ├── CommentStepDefinitions.java │ │ │ ├── CurriculumStepDefinitions.java │ │ │ ├── EssayAnswerStepDefinitions.java │ │ │ ├── FilterStepDefinitions.java │ │ │ ├── GroupMemberStepDefinitions.java │ │ │ ├── KeywordRecommendedPostStepDefinitions.java │ │ │ ├── KeywordStepDefinitions.java │ │ │ ├── LevellogsStepDefinitions.java │ │ │ ├── LoginStepDefinitions.java │ │ │ ├── MemberStepDefinitions.java │ │ │ ├── MissionStepDefinitions.java │ │ │ ├── NewSessionStepDefinitions.java │ │ │ ├── ProfileStepDefinitions.java │ │ │ ├── QuestionStepDefinitions.java │ │ │ ├── QuizStepDefinitions.java │ │ │ ├── SessionMemberStepDefinitions.java │ │ │ ├── SessionStepDefinitions.java │ │ │ ├── StudylogOverviewStepDefinitions.java │ │ │ ├── StudylogStepDefinitions.java │ │ │ └── TagStepDefinitions.java │ └── resources │ │ ├── application.yml │ │ ├── junit-platform.properties │ │ ├── logback-spring.xml │ │ ├── logback │ │ └── console-logger.xml │ │ └── wooteco │ │ └── prolog │ │ ├── article.feature │ │ ├── badges.feature │ │ ├── comment.feature │ │ ├── curriculum.feature │ │ ├── essayanswer.feature │ │ ├── filter.feature │ │ ├── keyword-recommended-post.feature │ │ ├── keyword.feature │ │ ├── levellogs.feature │ │ ├── login.feature │ │ ├── member.feature │ │ ├── mission.feature │ │ ├── new-session.feature │ │ ├── profile.feature │ │ ├── question.feature │ │ ├── quiz.feature │ │ ├── session.feature │ │ ├── sessionmember.feature │ │ ├── studylog-create.feature │ │ ├── studylog-popular.feature │ │ ├── studylog.feature │ │ ├── studylogOverview.feature │ │ └── tag.feature │ ├── main │ ├── java │ │ └── wooteco │ │ │ ├── prolog │ │ │ ├── AsyncConfig.java │ │ │ ├── DataLoaderApplicationListener.java │ │ │ ├── JpaAuditingConfig.java │ │ │ ├── PrologApplication.java │ │ │ ├── RetryConfig.java │ │ │ ├── article │ │ │ │ ├── application │ │ │ │ │ ├── ArticleService.java │ │ │ │ │ ├── MetaOgService.java │ │ │ │ │ ├── OgTagParser.java │ │ │ │ │ ├── RssClient.java │ │ │ │ │ ├── RssFeedException.java │ │ │ │ │ ├── SSLUtil.java │ │ │ │ │ ├── SlackService.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── ArticleBookmarkRequest.java │ │ │ │ │ │ ├── ArticleLikesRequest.java │ │ │ │ │ │ ├── ArticleRequest.java │ │ │ │ │ │ ├── ArticleResponse.java │ │ │ │ │ │ ├── ArticleUrlRequest.java │ │ │ │ │ │ └── ArticleUrlResponse.java │ │ │ │ ├── domain │ │ │ │ │ ├── Article.java │ │ │ │ │ ├── ArticleBookmark.java │ │ │ │ │ ├── ArticleBookmarks.java │ │ │ │ │ ├── ArticleFilterType.java │ │ │ │ │ ├── ArticleLike.java │ │ │ │ │ ├── ArticleLikes.java │ │ │ │ │ ├── Description.java │ │ │ │ │ ├── ImageUrl.java │ │ │ │ │ ├── RssFeed.java │ │ │ │ │ ├── RssFeeds.java │ │ │ │ │ ├── Title.java │ │ │ │ │ ├── Url.java │ │ │ │ │ ├── ViewCount.java │ │ │ │ │ └── repository │ │ │ │ │ │ └── ArticleRepository.java │ │ │ │ └── ui │ │ │ │ │ ├── ArticleController.java │ │ │ │ │ └── MetaOgController.java │ │ │ ├── badge │ │ │ │ ├── application │ │ │ │ │ ├── BadgeCreator.java │ │ │ │ │ ├── BadgeService.java │ │ │ │ │ ├── ComplimentKingBadgeCreator.java │ │ │ │ │ ├── PassionKingBadgeCreator.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── BadgeResponse.java │ │ │ │ │ │ └── BadgesResponse.java │ │ │ │ ├── config │ │ │ │ │ └── BadgeCreatorConfig.java │ │ │ │ ├── domain │ │ │ │ │ ├── Badge.java │ │ │ │ │ ├── BadgeType.java │ │ │ │ │ └── FourthCrewSessions.java │ │ │ │ └── ui │ │ │ │ │ └── BadgeController.java │ │ │ ├── common │ │ │ │ ├── PageableResponse.java │ │ │ │ ├── ResourceSizeAdvice.java │ │ │ │ ├── SSLConfig.java │ │ │ │ ├── WebConfig.java │ │ │ │ ├── WebConverterConfig.java │ │ │ │ ├── entity │ │ │ │ │ ├── AuditingEntity.java │ │ │ │ │ └── CreateAuditingEntity.java │ │ │ │ ├── exception │ │ │ │ │ ├── AiResponseProcessingException.java │ │ │ │ │ ├── BadRequestCode.java │ │ │ │ │ ├── BadRequestException.java │ │ │ │ │ ├── ExceptionController.java │ │ │ │ │ └── ExceptionDto.java │ │ │ │ ├── filter │ │ │ │ │ └── ServletWrappingFilter.java │ │ │ │ ├── slacklogger │ │ │ │ │ ├── ExceptionAppender.java │ │ │ │ │ ├── PrologSlack.java │ │ │ │ │ ├── RequestStorage.java │ │ │ │ │ ├── SlackAlarm.java │ │ │ │ │ ├── SlackAlarmErrorLevel.java │ │ │ │ │ └── SlackMessageGenerator.java │ │ │ │ └── ui │ │ │ │ │ └── HealthCheckController.java │ │ │ ├── image │ │ │ │ ├── application │ │ │ │ │ ├── ImageService.java │ │ │ │ │ └── dto │ │ │ │ │ │ └── ImageUrlResponse.java │ │ │ │ ├── config │ │ │ │ │ └── S3Configuration.java │ │ │ │ ├── domain │ │ │ │ │ └── FileExtension.java │ │ │ │ ├── infrastructure │ │ │ │ │ ├── FileNameGenerator.java │ │ │ │ │ └── S3Uploader.java │ │ │ │ └── ui │ │ │ │ │ └── ImageController.java │ │ │ ├── interview │ │ │ │ ├── application │ │ │ │ │ ├── InterviewAnswerRequest.java │ │ │ │ │ ├── InterviewMessageResponse.java │ │ │ │ │ ├── InterviewService.java │ │ │ │ │ ├── InterviewSessionMapper.java │ │ │ │ │ ├── InterviewSessionRequest.java │ │ │ │ │ └── InterviewSessionResponse.java │ │ │ │ ├── domain │ │ │ │ │ ├── FollowUpQuestion.java │ │ │ │ │ ├── InterviewMessage.java │ │ │ │ │ ├── InterviewMessages.java │ │ │ │ │ ├── InterviewSession.java │ │ │ │ │ ├── Interviewer.java │ │ │ │ │ └── repository │ │ │ │ │ │ └── InterviewSessionRepository.java │ │ │ │ ├── infrastructure │ │ │ │ │ ├── AzureOpenAiInterviewer.java │ │ │ │ │ └── FakeInterviewer.java │ │ │ │ └── ui │ │ │ │ │ └── InterviewController.java │ │ │ ├── levellogs │ │ │ │ ├── application │ │ │ │ │ ├── LevelLogService.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── LevelLogRequest.java │ │ │ │ │ │ ├── LevelLogResponse.java │ │ │ │ │ │ ├── LevelLogSummariesResponse.java │ │ │ │ │ │ ├── LevelLogSummaryResponse.java │ │ │ │ │ │ ├── SelfDiscussionRequest.java │ │ │ │ │ │ └── SelfDiscussionResponse.java │ │ │ │ ├── domain │ │ │ │ │ ├── Content.java │ │ │ │ │ ├── LevelLog.java │ │ │ │ │ ├── SelfDiscussion.java │ │ │ │ │ ├── Title.java │ │ │ │ │ └── repository │ │ │ │ │ │ ├── LevelLogRepository.java │ │ │ │ │ │ └── SelfDiscussionRepository.java │ │ │ │ └── ui │ │ │ │ │ └── LevelLogsController.java │ │ │ ├── login │ │ │ │ ├── aop │ │ │ │ │ ├── LoginMemberVerifier.java │ │ │ │ │ ├── MemberAuthorityCache.java │ │ │ │ │ └── MemberOnly.java │ │ │ │ ├── application │ │ │ │ │ ├── AuthorizationExtractor.java │ │ │ │ │ ├── GithubClient.java │ │ │ │ │ ├── GithubLoginService.java │ │ │ │ │ ├── JwtTokenProvider.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── ErrorMessage.java │ │ │ │ │ │ ├── GithubAccessTokenRequest.java │ │ │ │ │ │ ├── GithubAccessTokenResponse.java │ │ │ │ │ │ ├── GithubProfileResponse.java │ │ │ │ │ │ ├── TokenRequest.java │ │ │ │ │ │ └── TokenResponse.java │ │ │ │ ├── domain │ │ │ │ │ └── AuthMemberPrincipal.java │ │ │ │ └── ui │ │ │ │ │ ├── AuthMemberPrincipalArgumentResolver.java │ │ │ │ │ ├── GithubLoginController.java │ │ │ │ │ ├── LoginConfig.java │ │ │ │ │ ├── LoginInterceptor.java │ │ │ │ │ └── LoginMember.java │ │ │ ├── member │ │ │ │ ├── application │ │ │ │ │ ├── DepartmentMemberService.java │ │ │ │ │ ├── MemberService.java │ │ │ │ │ ├── MemberTagService.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── MemberResponse.java │ │ │ │ │ │ ├── MemberScrapRequest.java │ │ │ │ │ │ ├── MemberScrapResponse.java │ │ │ │ │ │ ├── MemberUpdateRequest.java │ │ │ │ │ │ ├── MembersResponse.java │ │ │ │ │ │ ├── ProfileIntroRequest.java │ │ │ │ │ │ ├── ProfileIntroResponse.java │ │ │ │ │ │ ├── ProfileResponse.java │ │ │ │ │ │ └── RoleUpdateRequest.java │ │ │ │ ├── domain │ │ │ │ │ ├── Department.java │ │ │ │ │ ├── DepartmentMember.java │ │ │ │ │ ├── Departments.java │ │ │ │ │ ├── Member.java │ │ │ │ │ ├── MemberCreatedEvent.java │ │ │ │ │ ├── MemberTag.java │ │ │ │ │ ├── MemberTags.java │ │ │ │ │ ├── MemberUpdatedEvent.java │ │ │ │ │ ├── Part.java │ │ │ │ │ ├── Role.java │ │ │ │ │ ├── Term.java │ │ │ │ │ └── repository │ │ │ │ │ │ ├── DepartmentMemberRepository.java │ │ │ │ │ │ ├── DepartmentRepository.java │ │ │ │ │ │ ├── JdbcMemberTagRepository.java │ │ │ │ │ │ ├── MemberRepository.java │ │ │ │ │ │ └── MemberTagRepository.java │ │ │ │ └── ui │ │ │ │ │ ├── MemberController.java │ │ │ │ │ └── MemberReactionController.java │ │ │ ├── organization │ │ │ │ ├── application │ │ │ │ │ ├── OrganizationGroupMemberRequest.java │ │ │ │ │ ├── OrganizationGroupSessionRequest.java │ │ │ │ │ ├── OrganizationGroupSessionResponse.java │ │ │ │ │ └── OrganizationService.java │ │ │ │ └── domain │ │ │ │ │ ├── Organization.java │ │ │ │ │ ├── OrganizationGroup.java │ │ │ │ │ ├── OrganizationGroupMember.java │ │ │ │ │ ├── OrganizationGroupSession.java │ │ │ │ │ └── repository │ │ │ │ │ ├── OrganizationGroupMemberRepository.java │ │ │ │ │ ├── OrganizationGroupRepository.java │ │ │ │ │ ├── OrganizationGroupSessionRepository.java │ │ │ │ │ └── OrganizationRepository.java │ │ │ ├── roadmap │ │ │ │ ├── application │ │ │ │ │ ├── CurriculumService.java │ │ │ │ │ ├── EssayAnswerService.java │ │ │ │ │ ├── KeywordService.java │ │ │ │ │ ├── NewSessionService.java │ │ │ │ │ ├── QuizService.java │ │ │ │ │ ├── RecommendedPostService.java │ │ │ │ │ ├── RoadMapService.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── CurriculumQuizResponse.java │ │ │ │ │ │ ├── CurriculumRequest.java │ │ │ │ │ │ ├── CurriculumResponse.java │ │ │ │ │ │ ├── CurriculumResponses.java │ │ │ │ │ │ ├── EssayAnswerQuizResponse.java │ │ │ │ │ │ ├── EssayAnswerRequest.java │ │ │ │ │ │ ├── EssayAnswerResponse.java │ │ │ │ │ │ ├── EssayAnswerSearchRequest.java │ │ │ │ │ │ ├── EssayAnswerUpdateRequest.java │ │ │ │ │ │ ├── KeywordCreateRequest.java │ │ │ │ │ │ ├── KeywordResponse.java │ │ │ │ │ │ ├── KeywordUpdateRequest.java │ │ │ │ │ │ ├── KeywordsResponse.java │ │ │ │ │ │ ├── QuizRequest.java │ │ │ │ │ │ ├── QuizResponse.java │ │ │ │ │ │ ├── QuizzesResponse.java │ │ │ │ │ │ ├── RecommendedPostResponse.java │ │ │ │ │ │ ├── RecommendedRequest.java │ │ │ │ │ │ ├── RecommendedUpdateRequest.java │ │ │ │ │ │ ├── SessionRequest.java │ │ │ │ │ │ ├── SessionResponse.java │ │ │ │ │ │ └── SessionsResponse.java │ │ │ │ ├── domain │ │ │ │ │ ├── Curriculum.java │ │ │ │ │ ├── EssayAnswer.java │ │ │ │ │ ├── Keyword.java │ │ │ │ │ ├── Quiz.java │ │ │ │ │ ├── RecommendedPost.java │ │ │ │ │ └── repository │ │ │ │ │ │ ├── CurriculumRepository.java │ │ │ │ │ │ ├── EssayAnswerRepository.java │ │ │ │ │ │ ├── EssayAnswerSpecification.java │ │ │ │ │ │ ├── KeywordRepository.java │ │ │ │ │ │ ├── QuizRepository.java │ │ │ │ │ │ ├── RecommendedPostRepository.java │ │ │ │ │ │ └── dto │ │ │ │ │ │ ├── KeywordIdAndAnsweredQuizCount.java │ │ │ │ │ │ └── KeywordIdAndTotalQuizCount.java │ │ │ │ └── ui │ │ │ │ │ ├── CurriculumController.java │ │ │ │ │ ├── EssayAnswerController.java │ │ │ │ │ ├── KeywordController.java │ │ │ │ │ ├── NewSessionController.java │ │ │ │ │ ├── QuizController.java │ │ │ │ │ ├── RecommendedController.java │ │ │ │ │ └── RoadmapController.java │ │ │ ├── session │ │ │ │ ├── application │ │ │ │ │ ├── AnswerFeedbackService.java │ │ │ │ │ ├── AnswerService.java │ │ │ │ │ ├── MissionService.java │ │ │ │ │ ├── QuestionService.java │ │ │ │ │ ├── SessionMemberService.java │ │ │ │ │ ├── SessionService.java │ │ │ │ │ └── dto │ │ │ │ │ │ ├── MissionQuestionResponse.java │ │ │ │ │ │ ├── MissionRequest.java │ │ │ │ │ │ ├── MissionResponse.java │ │ │ │ │ │ ├── QuestionRequest.java │ │ │ │ │ │ ├── QuestionResponse.java │ │ │ │ │ │ ├── SessionGroupMemberRequest.java │ │ │ │ │ │ ├── SessionMemberRequest.java │ │ │ │ │ │ ├── SessionRequest.java │ │ │ │ │ │ └── SessionResponse.java │ │ │ │ ├── domain │ │ │ │ │ ├── Answer.java │ │ │ │ │ ├── AnswerFeedback.java │ │ │ │ │ ├── AnswerTemp.java │ │ │ │ │ ├── AnswerUpdatedEvent.java │ │ │ │ │ ├── Mission.java │ │ │ │ │ ├── QnaFeedbackContents.java │ │ │ │ │ ├── QnaFeedbackProvider.java │ │ │ │ │ ├── QnaFeedbackRequest.java │ │ │ │ │ ├── Question.java │ │ │ │ │ ├── Session.java │ │ │ │ │ ├── SessionMember.java │ │ │ │ │ └── repository │ │ │ │ │ │ ├── AnswerFeedbackRepository.java │ │ │ │ │ │ ├── AnswerRepository.java │ │ │ │ │ │ ├── AnswerTempRepository.java │ │ │ │ │ │ ├── MissionRepository.java │ │ │ │ │ │ ├── QuestionRepository.java │ │ │ │ │ │ ├── SessionMemberRepository.java │ │ │ │ │ │ └── SessionRepository.java │ │ │ │ ├── infrastructure │ │ │ │ │ ├── AzureOpenAiFeedbackProvider.java │ │ │ │ │ └── FakeFeedbackProvider.java │ │ │ │ └── ui │ │ │ │ │ ├── MissionController.java │ │ │ │ │ ├── QuestionController.java │ │ │ │ │ ├── SessionController.java │ │ │ │ │ └── SessionMemberController.java │ │ │ └── studylog │ │ │ │ ├── application │ │ │ │ ├── AbstractStudylogDocumentService.java │ │ │ │ ├── CommentService.java │ │ │ │ ├── DocumentService.java │ │ │ │ ├── FakeStudylogDocumentService.java │ │ │ │ ├── FilterService.java │ │ │ │ ├── PopularStudylogService.java │ │ │ │ ├── StudylogDocumentService.java │ │ │ │ ├── StudylogLikeService.java │ │ │ │ ├── StudylogScrapService.java │ │ │ │ ├── StudylogService.java │ │ │ │ ├── StudylogSessionService.java │ │ │ │ ├── StudylogTagService.java │ │ │ │ ├── TagService.java │ │ │ │ ├── ViewedStudyLogCookieGenerator.java │ │ │ │ └── dto │ │ │ │ │ ├── AnswerRequest.java │ │ │ │ │ ├── AnswerResponse.java │ │ │ │ │ ├── CalendarStudylogResponse.java │ │ │ │ │ ├── CommentChangeRequest.java │ │ │ │ │ ├── CommentCreateRequest.java │ │ │ │ │ ├── CommentMemberResponse.java │ │ │ │ │ ├── CommentResponse.java │ │ │ │ │ ├── CommentSaveRequest.java │ │ │ │ │ ├── CommentUpdateRequest.java │ │ │ │ │ ├── CommentsResponse.java │ │ │ │ │ ├── EssayAnswersResponse.java │ │ │ │ │ ├── FilterResponse.java │ │ │ │ │ ├── MemberTagResponse.java │ │ │ │ │ ├── PopularStudylogsResponse.java │ │ │ │ │ ├── StudylogDocumentResponse.java │ │ │ │ │ ├── StudylogLikeResponse.java │ │ │ │ │ ├── StudylogMissionRequest.java │ │ │ │ │ ├── StudylogRequest.java │ │ │ │ │ ├── StudylogResponse.java │ │ │ │ │ ├── StudylogRssFeedResponse.java │ │ │ │ │ ├── StudylogSearchRequest.java │ │ │ │ │ ├── StudylogSessionRequest.java │ │ │ │ │ ├── StudylogTempResponse.java │ │ │ │ │ ├── StudylogWithScrapedCountResponse.java │ │ │ │ │ ├── StudylogsResponse.java │ │ │ │ │ ├── StudylogsWithScrapCountResponse.java │ │ │ │ │ ├── TagRequest.java │ │ │ │ │ ├── TagResponse.java │ │ │ │ │ └── search │ │ │ │ │ ├── SearchArgumentResolver.java │ │ │ │ │ ├── SearchParams.java │ │ │ │ │ └── StudylogsSearchRequest.java │ │ │ │ ├── domain │ │ │ │ ├── Comment.java │ │ │ │ ├── Content.java │ │ │ │ ├── Curriculum.java │ │ │ │ ├── DocumentQueryParser.java │ │ │ │ ├── Like.java │ │ │ │ ├── Likes.java │ │ │ │ ├── PopularStudylog.java │ │ │ │ ├── Studylog.java │ │ │ │ ├── StudylogDocument.java │ │ │ │ ├── StudylogRead.java │ │ │ │ ├── StudylogScrap.java │ │ │ │ ├── StudylogTag.java │ │ │ │ ├── StudylogTags.java │ │ │ │ ├── StudylogTemp.java │ │ │ │ ├── StudylogTempTag.java │ │ │ │ ├── StudylogTempTags.java │ │ │ │ ├── Tag.java │ │ │ │ ├── TagName.java │ │ │ │ ├── Tags.java │ │ │ │ ├── Title.java │ │ │ │ ├── ViewCount.java │ │ │ │ └── repository │ │ │ │ │ ├── BadgeRepository.java │ │ │ │ │ ├── CommentRepository.java │ │ │ │ │ ├── PopularStudylogRepository.java │ │ │ │ │ ├── StudylogReadRepository.java │ │ │ │ │ ├── StudylogRepository.java │ │ │ │ │ ├── StudylogScrapRepository.java │ │ │ │ │ ├── StudylogSpecification.java │ │ │ │ │ ├── StudylogTagRepository.java │ │ │ │ │ ├── StudylogTempRepository.java │ │ │ │ │ ├── TagRepository.java │ │ │ │ │ └── dto │ │ │ │ │ └── CommentCount.java │ │ │ │ ├── event │ │ │ │ └── StudylogDeleteEvent.java │ │ │ │ └── ui │ │ │ │ ├── CommentController.java │ │ │ │ ├── FilterController.java │ │ │ │ ├── PopularStudylogController.java │ │ │ │ ├── PostController.java │ │ │ │ ├── ProfileStudylogController.java │ │ │ │ ├── StudylogController.java │ │ │ │ ├── StudylogDocumentController.java │ │ │ │ ├── StudylogLikeController.java │ │ │ │ ├── StudylogOverviewController.java │ │ │ │ ├── StudylogRssFeedController.java │ │ │ │ ├── StudylogRssFeedView.java │ │ │ │ ├── StudylogSessionController.java │ │ │ │ └── TagController.java │ │ │ └── support │ │ │ ├── autoceptor │ │ │ ├── AuthenticationDetector.java │ │ │ ├── AutoInterceptorPatternMaker.java │ │ │ ├── MethodPattern.java │ │ │ └── scanner │ │ │ │ ├── ClassScanner.java │ │ │ │ ├── ControllerScanner.java │ │ │ │ ├── MappingAnnotation.java │ │ │ │ ├── MethodScanner.java │ │ │ │ └── URIScanner.java │ │ │ └── performance │ │ │ ├── PerformanceLogger.java │ │ │ ├── PerformanceLoggingForm.java │ │ │ ├── ProxyConnectionHandler.java │ │ │ ├── ProxyPreparedStatementHandler.java │ │ │ ├── RequestApi.java │ │ │ ├── RequestApiExtractor.java │ │ │ └── annotationDataExtractor │ │ │ ├── AnnotationDataExtractor.java │ │ │ ├── DeleteMappingDataExtractor.java │ │ │ ├── GetMappingDataExtractor.java │ │ │ ├── PostMappingDataExtractor.java │ │ │ └── PutMappingDataExtractor.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-local.yml │ │ ├── application-prod.yml │ │ ├── application-test.yml │ │ ├── application.yml │ │ ├── bootstrap-dev.yml │ │ ├── bootstrap-prod.yml │ │ ├── bootstrap.yml │ │ ├── db │ │ └── migration │ │ │ └── prod │ │ │ ├── V1.1__create_initial_scheme_squashed.sql │ │ │ ├── V10__create_column_rssfeed.sql │ │ │ ├── V11__create_table_organization.sql │ │ │ ├── V12__create_table_question.sql │ │ │ ├── V13__create_table_answer_feedback.sql │ │ │ ├── V14__alter_table_answer_feedback.sql │ │ │ ├── V15__create_table_interview.sql │ │ │ ├── V2__create_table_keyword_reference.sql │ │ │ ├── V3__create_table_article.sql │ │ │ ├── V4__alter_table_keyword_reference.sql │ │ │ ├── V5__alter_table_article.sql │ │ │ ├── V6__create_table_article_bookmark.sql │ │ │ ├── V7__create_table_article_like.sql │ │ │ ├── V8__alter_table_article.sql │ │ │ └── V9__alter_table_group_and_member.sql │ │ ├── logback-access.xml │ │ ├── logback-spring.xml │ │ ├── logback │ │ ├── console-access-logger.xml │ │ ├── console-logger.xml │ │ ├── file-access-logger.xml │ │ ├── file-debug-logger.xml │ │ ├── file-error-logger.xml │ │ ├── file-info-logger.xml │ │ ├── file-warn-logger.xml │ │ └── performance-logger.xml │ │ └── prompts │ │ ├── qna-feedback.st │ │ ├── qna-interview-closing-summary.st │ │ ├── qna-interview-initial-question.st │ │ ├── qna-interview-interactive-follow-up.st │ │ └── qna-interview-system-guide.st │ └── test │ ├── java │ └── wooteco │ │ ├── prolog │ │ ├── RetryConfigTest.java │ │ ├── article │ │ │ ├── application │ │ │ │ ├── ArticleServiceTest.java │ │ │ │ ├── MetaOgServiceTest.java │ │ │ │ └── RssClientTest.java │ │ │ └── domain │ │ │ │ ├── ArticleBookmarkTest.java │ │ │ │ ├── ArticleBookmarksTest.java │ │ │ │ ├── ArticleLikeTest.java │ │ │ │ ├── ArticleLikesTest.java │ │ │ │ ├── ArticleTest.java │ │ │ │ ├── ImageUrlTest.java │ │ │ │ ├── TitleTest.java │ │ │ │ └── UrlTest.java │ │ ├── badge │ │ │ ├── application │ │ │ │ ├── ComplimentKingBadgeCreatorTest.java │ │ │ │ └── PassionKingBadgeCreatorTest.java │ │ │ └── domain │ │ │ │ └── FourthCrewSessionsTest.java │ │ ├── common │ │ │ ├── DataInitializer.java │ │ │ └── exception │ │ │ │ └── BadRequestCodeTest.java │ │ ├── interview │ │ │ ├── application │ │ │ │ └── InterviewServiceTest.java │ │ │ └── domain │ │ │ │ └── InterviewMessagesTest.java │ │ ├── levellogs │ │ │ └── domain │ │ │ │ └── LevelLogTest.java │ │ ├── login │ │ │ ├── application │ │ │ │ └── JwtTokenProviderTest.java │ │ │ └── domain │ │ │ │ └── MemberTest.java │ │ ├── member │ │ │ ├── application │ │ │ │ ├── DepartmentMemberServiceTest.java │ │ │ │ ├── MemberServiceTest.java │ │ │ │ └── MemberTagServiceTest.java │ │ │ └── domain │ │ │ │ ├── DepartmentTest.java │ │ │ │ ├── MemberTagTest.java │ │ │ │ └── repository │ │ │ │ ├── DepartmentMemberRepositoryTest.java │ │ │ │ └── MemberRepositoryTest.java │ │ ├── organization │ │ │ ├── application │ │ │ │ └── OrganizationServiceTest.java │ │ │ └── domain │ │ │ │ └── OrganizationGroupMemberRepositoryTest.java │ │ ├── roadmap │ │ │ ├── application │ │ │ │ ├── CurriculumServiceTest.java │ │ │ │ ├── EssayAnswerSearchTest.java │ │ │ │ ├── EssayAnswerServiceTest.java │ │ │ │ ├── KeywordServiceTest.java │ │ │ │ ├── QuizServiceTest.java │ │ │ │ ├── RecommendedPostServiceTest.java │ │ │ │ └── RoadMapServiceTest.java │ │ │ ├── domain │ │ │ │ ├── CurriculumTest.java │ │ │ │ ├── EssayAnswerTest.java │ │ │ │ ├── KeywordTest.java │ │ │ │ └── RecommendedPostTest.java │ │ │ └── repository │ │ │ │ ├── EssayAnswerRepositoryTest.java │ │ │ │ ├── KeywordRepositoryTest.java │ │ │ │ └── QuizRepositoryTest.java │ │ ├── session │ │ │ ├── application │ │ │ │ ├── MissionServiceTest.java │ │ │ │ ├── SessionMemberServiceTest.java │ │ │ │ └── SessionServiceTest.java │ │ │ ├── domain │ │ │ │ ├── MissionTest.java │ │ │ │ ├── SessionTest.java │ │ │ │ └── repository │ │ │ │ │ ├── AnswerFeedbackRepositoryTest.java │ │ │ │ │ ├── SessionMemberRepositoryTest.java │ │ │ │ │ └── SessionRepositoryTest.java │ │ │ └── infrastructure │ │ │ │ └── AzureOpenAiFeedbackProviderTest.java │ │ └── studylog │ │ │ ├── application │ │ │ ├── CommentServiceTest.java │ │ │ ├── FilterServiceTest.java │ │ │ ├── IntegratedStudylogServiceTest.java │ │ │ ├── PopularStudylogServiceTest.java │ │ │ ├── StudylogLikeServiceTest.java │ │ │ ├── StudylogScrapServiceTest.java │ │ │ ├── StudylogServiceTest.java │ │ │ ├── StudylogSessionServiceTest.java │ │ │ ├── StudylogTagServiceTest.java │ │ │ ├── TagServiceTest.java │ │ │ └── ViewedStudyLogCookieGeneratorTest.java │ │ │ ├── domain │ │ │ ├── CommentTest.java │ │ │ ├── DocumentQueryParserTest.java │ │ │ ├── StudylogTagsTest.java │ │ │ ├── StudylogTest.java │ │ │ └── repository │ │ │ │ ├── CommentRepositoryTest.java │ │ │ │ └── StudylogTagRepositoryTest.java │ │ │ ├── mission │ │ │ └── domain │ │ │ │ └── repository │ │ │ │ └── MissionRepositoryTest.java │ │ │ ├── studylog │ │ │ ├── domain │ │ │ │ ├── ContentTest.java │ │ │ │ ├── TitleTest.java │ │ │ │ └── repository │ │ │ │ │ ├── StudylogReadRepositoryTest.java │ │ │ │ │ ├── StudylogRepositoryTest.java │ │ │ │ │ └── StudylogScrapRepositoryTest.java │ │ │ └── util │ │ │ │ └── StudylogFixture.java │ │ │ ├── studylogtag │ │ │ └── domain │ │ │ │ ├── StudylogTagsTest.java │ │ │ │ └── repository │ │ │ │ └── StudylogTagRepositoryTest.java │ │ │ └── tag │ │ │ └── domain │ │ │ ├── TagNameTest.java │ │ │ ├── TagsTest.java │ │ │ └── repository │ │ │ └── TagRepositoryTest.java │ │ └── support │ │ ├── autoceptor │ │ ├── AutoInterceptorPatternMakerTest.java │ │ ├── ClassScannerTest.java │ │ ├── ControllerScannerTest.java │ │ ├── MappingAnnotationTest.java │ │ ├── MethodPatternTest.java │ │ ├── MethodScannerTest.java │ │ ├── URIScannerTest.java │ │ └── test_classes │ │ │ ├── ControllerClass.java │ │ │ ├── NormalClass.java │ │ │ └── RestControllerClass.java │ │ └── utils │ │ └── RepositoryTest.java │ └── resources │ └── application.yml ├── frontend ├── .eslintcache ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .storybook │ ├── main.js │ └── preview.js ├── README.md ├── env │ ├── .env.development │ ├── .env.development.local │ ├── .env.local │ ├── .env.production │ └── .env.production.local ├── package-lock.json ├── package.json ├── public │ ├── assets │ │ └── images │ │ │ ├── ability-be.png │ │ │ ├── ability-fe.png │ │ │ └── select-default-ability-bg.png │ ├── favicon.png │ ├── index.html │ ├── manifest.json │ ├── mockServiceWorker.js │ └── robots.txt ├── src │ ├── App.tsx │ ├── GlobalStyles.tsx │ ├── PageRouter.tsx │ ├── apis │ │ ├── ability.ts │ │ ├── articles.ts │ │ ├── comment.ts │ │ ├── curriculum.ts │ │ ├── essayanswers.ts │ │ ├── filter.ts │ │ ├── index.ts │ │ ├── keywords.ts │ │ ├── levellogs.ts │ │ ├── questions.ts │ │ └── studylogs.ts │ ├── assets │ │ ├── images │ │ │ ├── ability-be.png │ │ │ ├── ability-fe.png │ │ │ ├── article-ex.png │ │ │ ├── background-image.png │ │ │ ├── badge-1-level2.svg │ │ │ ├── badge-2-level2.svg │ │ │ ├── check.png │ │ │ ├── comment.svg │ │ │ ├── heart-filled.svg │ │ │ ├── heart.svg │ │ │ ├── logo.svg │ │ │ ├── minus_icon.svg │ │ │ ├── no-profile-image.png │ │ │ ├── not-found.png │ │ │ ├── overview.png │ │ │ ├── overview.svg │ │ │ ├── pen.svg │ │ │ ├── pencil_icon.svg │ │ │ ├── person.svg │ │ │ ├── person_icon.svg │ │ │ ├── plus_icon.svg │ │ │ ├── post.png │ │ │ ├── post.svg │ │ │ ├── prolog-banner-image.png │ │ │ ├── prolog-icon-white.svg │ │ │ ├── report.png │ │ │ ├── reportIcon.svg │ │ │ ├── right-arrow-angle.svg │ │ │ ├── rss.png │ │ │ ├── scrap.svg │ │ │ ├── scrap_filled.svg │ │ │ ├── search.svg │ │ │ ├── search_icon.svg │ │ │ ├── select-default-ability-bg.png │ │ │ ├── view.svg │ │ │ ├── wait.png │ │ │ └── woteco-logo.png │ │ └── lotties │ │ │ ├── index.ts │ │ │ └── not-found.json │ ├── components │ │ ├── @shared │ │ │ ├── FlexBox │ │ │ │ └── FlexBox.ts │ │ │ ├── Icons │ │ │ │ └── CancelIcon.tsx │ │ │ ├── SideSheet │ │ │ │ ├── SideSheet.stories.tsx │ │ │ │ ├── SideSheet.style.ts │ │ │ │ └── SideSheet.tsx │ │ │ └── SnackBar │ │ │ │ ├── SnackBar.styles.ts │ │ │ │ └── SnackBar.tsx │ │ ├── Article │ │ │ ├── Article.stories.js │ │ │ ├── Article.style.tsx │ │ │ ├── Article.tsx │ │ │ ├── ArticleBookmarkFIlter.tsx │ │ │ ├── ArticleList.stories.js │ │ │ ├── ArticleList.style.tsx │ │ │ └── ArticleList.tsx │ │ ├── Badge │ │ │ ├── BadgeList.styles.ts │ │ │ └── BadgeList.tsx │ │ ├── Banner │ │ │ ├── Banner.styles.ts │ │ │ ├── Banner.tsx │ │ │ ├── BannerList.styles.ts │ │ │ └── BannerList.tsx │ │ ├── Button │ │ │ ├── Button.stories.js │ │ │ ├── Button.styles.ts │ │ │ ├── Button.tsx │ │ │ ├── PageButton.tsx │ │ │ ├── ResponsiveButton.stories.js │ │ │ ├── ResponsiveButton.styles.ts │ │ │ └── ResponsiveButton.tsx │ │ ├── Calendar │ │ │ ├── Calendar.stories.js │ │ │ ├── Calendar.styles.ts │ │ │ └── Calendar.tsx │ │ ├── Card │ │ │ ├── Card.stories.tsx │ │ │ ├── Card.styles.ts │ │ │ └── Card.tsx │ │ ├── Charts │ │ │ ├── Chart.styles.ts │ │ │ ├── DonutChart.tsx │ │ │ ├── DonutChartForm.styles.ts │ │ │ └── DonutChartForm.tsx │ │ ├── Chip │ │ │ ├── Chip.styles.ts │ │ │ └── Chip.tsx │ │ ├── Comment │ │ │ ├── Comment.style.tsx │ │ │ ├── Comment.tsx │ │ │ ├── CommentList.style.tsx │ │ │ └── CommentList.tsx │ │ ├── Controls │ │ │ └── SelectBox.tsx │ │ ├── Count │ │ │ ├── CommentCount.tsx │ │ │ ├── LikeCount.tsx │ │ │ └── ViewCount.tsx │ │ ├── CreatableSelectBox │ │ │ └── CreatableSelectBox.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.stories.tsx │ │ │ ├── DropdownMenu.styles.ts │ │ │ └── DropdownMenu.tsx │ │ ├── Editor │ │ │ ├── Editor.styles.ts │ │ │ └── Editor.tsx │ │ ├── FilterList │ │ │ ├── FilterList.jsx │ │ │ ├── FilterList.styles.ts │ │ │ ├── FilterList.styles.tsx │ │ │ └── SelectedFilterList.tsx │ │ ├── GithubLogin │ │ │ └── GithubLogin.tsx │ │ ├── Introduction │ │ │ ├── EditIntroduction.tsx │ │ │ ├── Introduction.styles.ts │ │ │ └── Introduction.tsx │ │ ├── Items │ │ │ ├── EssayAnswerItem.styles.ts │ │ │ ├── EssayAnswerItem.tsx │ │ │ ├── LevellogItem.styles.ts │ │ │ ├── LevellogItem.tsx │ │ │ ├── NewLevellogQnAInputItem.styles.ts │ │ │ ├── NewLevellogQnAInputItem.tsx │ │ │ ├── PopularStudylogItem.styles.ts │ │ │ ├── PopularStudylogItem.tsx │ │ │ ├── StudylogItem.styles.ts │ │ │ └── StudylogItem.tsx │ │ ├── KeywordDetailSideSheet │ │ │ ├── KeywordDetailSideSheet.styles.tsx │ │ │ └── KeywordDetailSideSheet.tsx │ │ ├── LabelledImage │ │ │ ├── LabelledImage.stories.js │ │ │ └── LabelledImage.tsx │ │ ├── Lists │ │ │ ├── LevellogList.styles.ts │ │ │ ├── LevellogList.tsx │ │ │ ├── NewLevellogQnAInputList.styles.ts │ │ │ ├── NewLevellogQnAInputList.tsx │ │ │ ├── QuizAnswerList.tsx │ │ │ └── StudylogList.tsx │ │ ├── Modal │ │ │ ├── Modal.styles.ts │ │ │ └── Modal.tsx │ │ ├── NavBar │ │ │ ├── NavBar.styles.ts │ │ │ ├── NavBar.tsx │ │ │ └── Navbar.stories.js │ │ ├── NotFound │ │ │ ├── NotFound.styles.ts │ │ │ └── NotFound.tsx │ │ ├── Pagination │ │ │ ├── Pagination.styles.ts │ │ │ └── Pagination.tsx │ │ ├── ProfileChip │ │ │ ├── ProfilChip.styles.ts │ │ │ └── ProfileChip.tsx │ │ ├── ProfileChipMax │ │ │ ├── ProfilChipMax.styles.ts │ │ │ └── ProfileChipMax.tsx │ │ ├── ProfilePageSideBar │ │ │ ├── ProfilePageSideBar.styles.ts │ │ │ ├── ProfilePageSideBar.tsx │ │ │ └── getMenuList.ts │ │ ├── Reaction │ │ │ ├── Like.styles.ts │ │ │ ├── Like.tsx │ │ │ ├── Scrap.styles.ts │ │ │ └── Scrap.tsx │ │ ├── SearchBar │ │ │ ├── SearchBar.styles.ts │ │ │ ├── SearchBar.styles.tsx │ │ │ └── SearchBar.tsx │ │ ├── SelectBox │ │ │ ├── SelectBox.stories.js │ │ │ ├── SelectBox.styles.ts │ │ │ └── SelectBox.tsx │ │ ├── StudylogEditor │ │ │ ├── QuestionAnswerStyles.ts │ │ │ ├── QuestionAnswers.tsx │ │ │ ├── Sidebar.tsx │ │ │ ├── StudylogEditor.tsx │ │ │ └── styles.ts │ │ ├── Tag │ │ │ ├── Tag.styles.ts │ │ │ └── Tag.tsx │ │ └── index.ts │ ├── configs │ │ ├── bannerList.tsx │ │ └── environment.ts │ ├── constants │ │ ├── color.ts │ │ ├── errorCode.ts │ │ ├── index.ts │ │ ├── input.ts │ │ ├── localStorage.ts │ │ ├── mediaQuery.ts │ │ ├── message.ts │ │ ├── path.ts │ │ ├── profilePageMenu.ts │ │ ├── reactQueryKey.ts │ │ ├── requestType.ts │ │ └── screenBreakpoints.ts │ ├── contexts │ │ └── UserProvider.js │ ├── enumerations │ │ ├── color.ts │ │ └── path.ts │ ├── hooks │ │ ├── Articles │ │ │ └── useArticles.ts │ │ ├── Comment │ │ │ └── useStudylogComment.ts │ │ ├── EssayAnswer │ │ │ ├── useEssayAnswer.ts │ │ │ └── useNewEssayAnswer.ts │ │ ├── Levellog │ │ │ ├── useEditLevellog.ts │ │ │ ├── useLevellog.ts │ │ │ ├── useLevellogList.ts │ │ │ ├── useNewLevellog.ts │ │ │ └── useQnAInputList.ts │ │ ├── Studylog │ │ │ └── useTempSavedStudylog.ts │ │ ├── queries │ │ │ ├── article.ts │ │ │ ├── auth.ts │ │ │ ├── comment.ts │ │ │ ├── curriculum.ts │ │ │ ├── essayanswer.ts │ │ │ ├── filters.ts │ │ │ ├── keywords.ts │ │ │ ├── levellog.ts │ │ │ ├── profile.ts │ │ │ ├── report.ts │ │ │ └── studylog.ts │ │ ├── useBeforeunload.ts │ │ ├── useCustomSelectBox.ts │ │ ├── useFetch.js │ │ ├── useFilterWithParams.ts │ │ ├── useImage.ts │ │ ├── useMutation.js │ │ ├── useNotFound.ts │ │ ├── useRequest.js │ │ ├── useScreenMediaQuery.ts │ │ ├── useScrollToSelected.js │ │ ├── useSnackBar.tsx │ │ ├── useStudylog.js │ │ ├── useStudylogsPagination.js │ │ ├── useValidParams.ts │ │ └── useValidQueryString.ts │ ├── index.tsx │ ├── mocks │ │ ├── browser.ts │ │ ├── db │ │ │ ├── articles-android.json │ │ │ ├── articles-backend.json │ │ │ ├── articles-frontend.json │ │ │ ├── articles.json │ │ │ ├── comments.json │ │ │ ├── levellogs.js │ │ │ ├── metaog.json │ │ │ └── popularStudyLog.json │ │ ├── fixtures │ │ │ ├── curriculums.ts │ │ │ ├── essayAnswers.ts │ │ │ ├── quizzes.ts │ │ │ └── roadmap.ts │ │ └── handlers │ │ │ ├── articles.ts │ │ │ ├── comment.ts │ │ │ ├── essayAnswers.ts │ │ │ ├── index.ts │ │ │ ├── levellog.ts │ │ │ ├── popularStudyLog.ts │ │ │ └── roadmap.ts │ ├── models │ │ ├── Ability.ts │ │ ├── Article.ts │ │ ├── Comment.ts │ │ ├── EssayAnswers.ts │ │ ├── Keywords.ts │ │ ├── Levellogs.ts │ │ ├── Studylogs.ts │ │ └── filter.ts │ ├── pages │ │ ├── ArticleListPage │ │ │ └── index.tsx │ │ ├── EditEssayAnswerPage │ │ │ └── index.tsx │ │ ├── EditLevellogPage │ │ │ └── index.tsx │ │ ├── EditStudylogPage │ │ │ └── index.tsx │ │ ├── EssayAnswerListPage │ │ │ ├── components │ │ │ │ └── RoadmapFilter │ │ │ │ │ ├── RoadmapFilter.styles.ts │ │ │ │ │ └── RoadmapFilter.tsx │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── EssayAnswerPage │ │ │ ├── Content.tsx │ │ │ ├── index.tsx │ │ │ └── styles.tsx │ │ ├── InterviewPage │ │ │ ├── InterviewPage.tsx │ │ │ ├── InterviewSession.tsx │ │ │ ├── InterviewSetup.tsx │ │ │ └── index.tsx │ │ ├── LevellogListPage │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── LevellogPage │ │ │ ├── Content.tsx │ │ │ ├── QnAList.styles.tsx │ │ │ ├── QnAList.tsx │ │ │ ├── index.tsx │ │ │ └── styles.tsx │ │ ├── LoginCallbackPage │ │ │ └── index.tsx │ │ ├── MainPage │ │ │ ├── PopularStudyLogList.tsx │ │ │ ├── RecentStudylogList.tsx │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── NewArticlePage │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── NewEssayAnswerPage │ │ │ └── index.tsx │ │ ├── NewLevellogPage │ │ │ └── index.tsx │ │ ├── NewStudylogPage │ │ │ └── index.tsx │ │ ├── ProfilePage │ │ │ ├── StudylogOverviews.js │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── ProfilePageAccount │ │ │ └── index.tsx │ │ ├── ProfilePageScraps │ │ │ ├── index.js │ │ │ └── styles.ts │ │ ├── ProfilePageStudylogs │ │ │ ├── index.js │ │ │ └── styles.ts │ │ ├── QuizAnswerListPage │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── RoadmapPage │ │ │ ├── RoadmapStyles.tsx │ │ │ ├── colors.ts │ │ │ ├── components │ │ │ │ ├── ImportanceLegend │ │ │ │ │ ├── ImportanceLegend.styles.ts │ │ │ │ │ └── ImportanceLegend.tsx │ │ │ │ ├── QuizProgress │ │ │ │ │ ├── QuizProgress.styles.ts │ │ │ │ │ └── QuizProgress.tsx │ │ │ │ └── Roadmap │ │ │ │ │ ├── MainKeyword.tsx │ │ │ │ │ ├── Roadmap.tsx │ │ │ │ │ └── SubKeyword.tsx │ │ │ ├── index.tsx │ │ │ └── styles.tsx │ │ ├── StudylogListPage │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ ├── StudylogPage │ │ │ ├── Content.tsx │ │ │ ├── index.tsx │ │ │ └── styles.ts │ │ └── index.ts │ ├── react-app-env.d.ts │ ├── redux │ │ ├── actions │ │ │ └── snackBarAction.ts │ │ ├── reducers │ │ │ ├── index.ts │ │ │ └── snackBarReducer.ts │ │ └── store.ts │ ├── routes.js │ ├── service │ │ └── requests.ts │ ├── styles │ │ ├── flex.styles.ts │ │ ├── layout.styles.ts │ │ ├── markdown.styles.ts │ │ └── reset.styles.ts │ ├── types │ │ └── utils.ts │ └── utils │ │ ├── arrayMutation.ts │ │ ├── axiosInstance.ts │ │ ├── canvas.ts │ │ ├── colors.ts │ │ ├── debounce.ts │ │ ├── filteringList.ts │ │ ├── object.ts │ │ ├── response.js │ │ ├── styles.ts │ │ ├── textColorPicker.js │ │ ├── toggleCheckbox.js │ │ └── validator.ts ├── tsconfig.json └── yarn.lock ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── terraform ├── environments ├── dev │ ├── backend.tf │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ └── variables.tf └── prod │ ├── backend.tf │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ └── variables.tf └── modules ├── application ├── main.tf ├── outputs.tf ├── templates │ └── user_data.tpl └── variables.tf ├── bastion ├── main.tf ├── outputs.tf └── variables.tf ├── compute ├── main.tf ├── outputs.tf └── variables.tf ├── database ├── main.tf ├── outputs.tf └── variables.tf ├── iam ├── main.tf ├── outputs.tf └── variables.tf ├── network ├── main.tf ├── outputs.tf └── variables.tf ├── secret ├── main.tf ├── outputs.tf └── variables.tf ├── storage ├── main.tf ├── outputs.tf └── variables.tf └── tags ├── main.tf ├── outputs.tf └── variables.tf /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | 14 | [*.{ts, tsx, js}] 15 | indent_size = 2 16 | 17 | [*.{bat, cmd}] 18 | end_of_line = crlf 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## #️⃣연관된 이슈 2 | 3 | > 연관된 이슈 번호를 모두 작성해주세요 4 | > 5 | > ex) #이슈번호, #이슈번호 6 | 7 | ## 📝작업 내용 8 | 9 | > 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능) 10 | 11 | ## 💬리뷰 요구사항(선택) 12 | 13 | > 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 14 | > 15 | > ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pro-log 2 | -------------------------------------------------------------------------------- /backend/.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 | 40 | ### direnv ### 41 | .envrc 42 | 43 | ### log ### 44 | logs 45 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11 as build 2 | 3 | WORKDIR /work 4 | 5 | # 빌드 스테이지에 소스 코드 파일들 추가 6 | COPY src ./src 7 | COPY build.gradle . 8 | COPY settings.gradle . 9 | COPY PrologJavaStyle.xml . 10 | COPY gradle ./gradle 11 | COPY ./gradlew . 12 | 13 | # 테스트를 제외하고 빌드 진행 14 | RUN ./gradlew clean build -x test -x acceptanceTest 15 | RUN mv /work/build/libs/*[!-plain].jar ./app.jar 16 | 17 | FROM eclipse-temurin:17-jre 18 | 19 | COPY --from=build /work/app.jar . 20 | 21 | ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh ./wait-for-it.sh 22 | RUN chmod +x ./wait-for-it.sh 23 | 24 | CMD ./wait-for-it.sh -t 0 db:3306 -- java -Dspring.profiles.active=local -jar ./app.jar 25 | 26 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # 학습로그 프로젝트 백엔드 2 | 3 | ## 퀵스타트 4 | 5 | ``` 6 | cd backend 7 | ./gradlew bootRun 8 | ``` 9 | 10 | ### 로컬 퀵스타트 11 | 12 | ``` 13 | cd backend 14 | ./gradlew bootRun --args='--spring.profiles.active=local' 15 | ``` 16 | -------------------------------------------------------------------------------- /backend/appspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | os: linux 3 | 4 | files: 5 | - source: /app.jar 6 | destination: /home/ec2-user/ 7 | 8 | hooks: 9 | ApplicationStop: 10 | - location: scripts/stop-app.sh 11 | timeout: 300 12 | runas: ec2-user 13 | 14 | BeforeInstall: 15 | - location: scripts/before-install.sh 16 | timeout: 300 17 | runas: ec2-user 18 | 19 | AfterInstall: 20 | - location: scripts/after-install.sh 21 | timeout: 300 22 | runas: ec2-user 23 | 24 | ApplicationStart: 25 | - location: scripts/start-app.sh 26 | timeout: 300 27 | runas: ec2-user 28 | -------------------------------------------------------------------------------- /backend/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | server: 4 | build: 5 | context: .. 6 | dockerfile: Dockerfile 7 | ports: 8 | - 9900:8080 9 | environment: 10 | SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/prolog?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC 11 | SPRING_FLYWAY_ENABLED: true 12 | db: 13 | platform: linux/x86_64 14 | image: library/mysql:8.0.28 15 | container_name: prolog-local-db 16 | restart: always 17 | ports: 18 | - 13306:3306 19 | environment: 20 | MYSQL_ROOT_PASSWORD: root 21 | MYSQL_DATABASE: prolog 22 | MYSQL_USER: user 23 | MYSQL_PASSWORD: password 24 | TZ: Asia/Seoul 25 | command: [ "mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_general_ci" ] 26 | -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/backend/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /backend/scripts/after-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Setting permissions..." 3 | 4 | sudo chmod +x /home/ec2-user/app.jar 5 | -------------------------------------------------------------------------------- /backend/scripts/before-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Preparing for application installation..." 3 | rm -rf /home/ec2-user/app.jar 4 | -------------------------------------------------------------------------------- /backend/scripts/start-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Starting application..." 3 | 4 | source /etc/environment 5 | 6 | sudo nohup java -jar /home/ec2-user/app.jar > /home/ec2-user/app.log 2>&1 & 7 | -------------------------------------------------------------------------------- /backend/scripts/stop-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Stopping application..." 3 | 4 | sudo pkill -f app.jar || true 5 | -------------------------------------------------------------------------------- /backend/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'prolog' 2 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/AcceptanceSteps.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | public class AcceptanceSteps { 6 | 7 | @Autowired 8 | public AcceptanceContext context; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/AcceptanceTestRunnerIT.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import io.cucumber.junit.platform.engine.Cucumber; 4 | 5 | @Cucumber 6 | public class AcceptanceTestRunnerIT { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/fixtures/ArticleFixture.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.fixtures; 2 | 3 | import wooteco.prolog.article.application.dto.ArticleRequest; 4 | import wooteco.prolog.article.application.dto.ArticleUrlRequest; 5 | 6 | public class ArticleFixture { 7 | 8 | public static final ArticleRequest ARTICLE_REQUEST1 = new ArticleRequest("첫 아티클 제목", "첫 아티클 주소", 9 | "이미지 URL"); 10 | public static final ArticleRequest ARTICLE_REQUEST2 = new ArticleRequest("두번째 아티클 제목", 11 | "두번째 아티클 주소", "이미지 URL"); 12 | public static final ArticleUrlRequest ARTICLE_URL_REQUEST = new ArticleUrlRequest( 13 | "https://www.woowahan.com/"); 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/fixtures/CommentAcceptanceFixture.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.fixtures; 2 | 3 | import wooteco.prolog.studylog.application.dto.CommentChangeRequest; 4 | import wooteco.prolog.studylog.application.dto.CommentCreateRequest; 5 | 6 | public enum CommentAcceptanceFixture { 7 | 8 | COMMENT("스터디로그의 댓글 내용입니다."), 9 | UPDATED_COMMENT("수정된 스터디로그의 댓글 내용입니다."); 10 | 11 | private final String content; 12 | 13 | CommentAcceptanceFixture(String content) { 14 | this.content = content; 15 | } 16 | 17 | public CommentCreateRequest getCreateRequest() { 18 | return new CommentCreateRequest(COMMENT.content); 19 | } 20 | 21 | public CommentChangeRequest getUpdateRequest() { 22 | return new CommentChangeRequest(UPDATED_COMMENT.content); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/fixtures/CurriculumFixture.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.fixtures; 2 | 3 | import wooteco.prolog.roadmap.application.dto.CurriculumRequest; 4 | 5 | public class CurriculumFixture { 6 | 7 | public static CurriculumRequest 커리큘럼1_생성_요청_DTO() { 8 | return new CurriculumRequest("커리큘럼1"); 9 | } 10 | 11 | public static CurriculumRequest 커리큘럼_수정_요청_DTO() { 12 | return new CurriculumRequest("수정된 커리큘럼"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/java/wooteco/prolog/fixtures/MissionAcceptanceFixture.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.fixtures; 2 | 3 | import wooteco.prolog.session.application.dto.MissionRequest; 4 | 5 | public class MissionAcceptanceFixture { 6 | 7 | public static MissionRequest mission1 = new MissionRequest("backend 지하철 3차 미션", 1L); 8 | public static MissionRequest mission2 = new MissionRequest("FRONTEND 지하철 3차 미션", 2L); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: test 4 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | cucumber.plugin=pretty, html:build/cucumber/cucumber.html -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/logback/console-logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %cyan(%d{yyyy-MM-dd HH:mm:ss}:%-4relative) %highlight(%-5level) 8 | %yellow([%C.%M]:%boldWhite(%L)]) %n > %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | utf8 16 | 17 | %green( > %msg%n) 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/badges.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 배지 관련 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | And 미션 여러개를 생성하고 7 | 8 | Scenario: 존재하지 않는 멤버로 배지 조회하기 9 | When 존재하지 않는 멤버의 배지를 조회하면 10 | Then 존재하지 않는 멤버 관련 예외가 발생한다 11 | 12 | Scenario: 배지목록 조회하기 13 | Given "브라운"이 크루역할로 로그인을 하고 14 | And 여러개의 스터디로그를 작성하고 15 | When 배지를 조회하면 16 | Then 열정왕 배지를 부여한다. 17 | 18 | 19 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/comment.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 댓글 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "브라운"이 크루역할로 로그인을 하고 6 | And 세션 여러개를 생성하고 7 | And 미션 여러개를 생성하고 8 | And 스터디로그를 작성하면 9 | 10 | Scenario: 댓글 작성하기 11 | When 1번 스터디로그에 대한 댓글을 작성하면 12 | Then 댓글이 작성된다 13 | 14 | Scenario: 스터디로그의 댓글 목록 조회하기 15 | Given 1번 스터디로그에 대한 댓글을 작성하고 16 | And "웨지"가 크루역할로 로그인을 하고 17 | And 1번 스터디로그에 대한 댓글을 작성하고 18 | When 1번 스터디로그의 댓글을 조회하면 19 | Then 해당 스터디로그의 댓글 목록을 조회한다 20 | 21 | Scenario: 댓글 수정하기 22 | Given 1번 스터디로그에 대한 댓글을 작성하고 23 | When 1번 스터디로그에 대한 1번 댓글을 수정하면 24 | Then 댓글이 수정된다 25 | 26 | Scenario: 댓글 삭제하기 27 | Given 1번 스터디로그에 대한 댓글을 작성하고 28 | When 1번 스터디로그에 대한 1번 댓글을 삭제하면 29 | Then 댓글이 삭제된다 30 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/curriculum.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 커리큘럼 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "수달"이 크루역할로 로그인을 하고 6 | 7 | Scenario: 커리큘럼 생성하기 8 | When 커리큘럼을 생성하면 9 | Then 커리큘럼이 생성된다 10 | 11 | Scenario: 커리큘럼 목록 조회하기 12 | When 커리큘럼을 생성하고 13 | And 커리쿨럼을 조회하면 14 | Then 커리큘럼이 조회된다 15 | 16 | Scenario: 커리큘럼 수정하기 17 | When 커리큘럼을 생성하고 18 | And 1번 커리쿨럼을 수정하면 19 | Then 커리큘럼이 수정된다 20 | 21 | Scenario: 커리큘럼 삭제하기 22 | When 커리큘럼을 생성하고 23 | And 1번 커리쿨럼을 삭제하면 24 | Then 커리큘럼이 삭제된다 25 | 26 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/filter.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 필터 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "현구막"이 크루역할로 로그인을 하고 6 | And "브라운"이 크루역할로 로그인을 하고 7 | And "서니"가 크루역할로 로그인을 하고 8 | 9 | # Scenario: 필터 목록 조회하기 10 | # When 필터요청이 들어오면 11 | # Then nickname을 기준으로 멤버데이터들을 오름차순 정렬하여 반환한다 12 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/keyword-recommended-post.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 로드맵 키워드 추천 포스트 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "2022 백엔드 레벨1" 세션을 생성하고 - 1번 세션 6 | And 1번 세션에 "자바"라는 키워드를 순서 1, 중요도 2로 작성하고 7 | 8 | Scenario: 키워드 추천 포스트 생성하기 9 | When 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하면 10 | Then 추천 포스트가 생성된다 11 | 12 | Scenario: 키워드 추천 포스트 수정하기 13 | Given 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하고 14 | When 1번 키워드에 대한 1번 추천 포스트를 "https://java2java2"로 수정하면 15 | Then 추천 포스트가 수정된다 16 | 17 | Scenario: 키워드 추천 포스트 삭제하기 18 | Given 1번 키워드에 대해 추천 포스트 "https://javajavajava"를 작성하고 19 | When 1번 키워드에 대한 1번 추천 포스트를 삭제하면 20 | Then 추천 포스트가 삭제된다 21 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/levellogs.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 레벨 로그 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "브라운"이 크루역할로 로그인을 하고 6 | 7 | Scenario: 레벨 로그 작성하기 8 | When 레벨로그를 작성하면 9 | Then 레벨로그가 조회된다 10 | 11 | Scenario: 레벨 로그 삭제하기 12 | When 레벨로그를 작성하고 13 | And 레벨로그를 삭제하면 14 | Then 레벨로그가 삭제된다 15 | 16 | Scenario: 레벨 로그 수정하기 17 | When 레벨로그를 작성하고 18 | And 레벨로그를 수정하면 19 | Then 레벨로그가 수정된다 20 | 21 | 22 | Scenario: 레벨 로그 목록 조회하기 23 | When 레벨로그를 여러개 작성하면 24 | Then 레벨로그가 여러개 조회된다 25 | 26 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/login.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 로그인 기능 3 | 4 | Scenario: 로그인하기 5 | When "브라운"이 로그인을 하면 6 | Then 액세스 토큰을 받는다 7 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/member.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 멤버 관련 기능 3 | 4 | Scenario: 자신의 멤버정보 조회하기 5 | Given "브라운"이 크루역할로 로그인을 하고 6 | When 자신의 멤버 정보를 조회하면 7 | Then 멤버 정보가 조회된다 8 | 9 | Scenario: 자신의 정보를 수정하기 10 | Given "브라운"이 크루역할로 로그인을 하고 11 | When 자신의 닉네임을 "brown"으로 수정하면 12 | Then "브라운"의 닉네임이 "brown"으로 수정 13 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/mission.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 로그인 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | 7 | Scenario: 미션 등록하기 8 | Given "브라운"이 크루역할로 로그인을 하고 9 | When "이런저런" 미션 등록을 하면 10 | Then 미션이 등록된다 11 | 12 | Scenario: 미션 조회하기 13 | Given "브라운"이 크루역할로 로그인을 하고 14 | And "이런저런" 미션 등록을 하고 15 | When 미션 목록을 조회하면 16 | Then 미션 목록을 받는다 17 | 18 | Scenario: 중복된 이름으로 미션 등록하기 19 | Given "브라운"이 크루역할로 로그인을 하고 20 | And "이런저런" 미션 등록을 하고 21 | When "이런저런" 미션 등록을 하면 22 | Then 미션을 실패한다 23 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/new-session.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 로드맵 키워드 관련 기능 3 | 4 | Background: 사전 작업 5 | # 커리큘럼 병합 후 작성 6 | 7 | Scenario: 세션 생성하기 8 | When 1번 커리큘럼에 "백엔드 레벨1" 세션을 작성하면 9 | Then 세션이 생성된다 10 | 11 | Scenario: 세션 목록 조회하기 12 | Given 1번 커리큘럼에 "백엔드 레벨1" 세션을 작성하고 13 | When 1번 커리큘럼의 세션 목록을 조회하면 14 | Then 세션 목록이 조회된다 15 | 16 | Scenario: 세션 목록 조회하기 17 | Given 1번 커리큘럼에 "백엔드 레벨1" 세션을 작성하고 18 | When 1번 커리큘럼의 세션 목록을 조회하면 19 | Then 세션 목록이 조회된다 20 | 21 | Scenario: 세션 수정하기 22 | Given 1번 커리큘럼에 "백엔드 레벨1" 세션을 작성하고 23 | When 1번 커리큘럼의 1번 세션을 "백엔드 레벨2" 세션으로 수정하면 24 | Then 세션이 수정된다 25 | 26 | Scenario: 세션 삭제하기 27 | Given 1번 커리큘럼에 "백엔드 레벨1" 세션을 작성하고 28 | When 1번 커리큘럼의 1번 세션을 삭제하면 29 | Then 세션이 삭제된다 30 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/question.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 질문 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "2025 백엔드 레벨1" 강의를 생성하고 6 | And "로또" 미션 등록을 하고 7 | 8 | Scenario: 질문 생성하기 9 | When "JUnit5와 AssertJ의 주요 차이점은 무엇인가?" 질문을 생성하면 10 | Then 질문이 생성된다 11 | 12 | Scenario: 질문 조회하기 13 | Given "JUnit5와 AssertJ의 주요 차이점은 무엇인가?" 질문을 생성하고 14 | When 질문을 조회하면 15 | Then 질문이 조회된다 16 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/quiz.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 로드맵 키워드 관련 기능 3 | 4 | Background: 사전 작업 5 | Given "2022 백엔드 레벨1" 세션을 생성하고 - 1번 세션 6 | When 1번 세션에 "자바"라는 키워드를 순서 1, 중요도 1로 작성하면 7 | Then 키워드가 생성된다 8 | 9 | Scenario: 퀴즈 생성하기 10 | When 1번 세션, 1번 키워드에 퀴즈를 작성하면 11 | Then 퀴즈가 생성된다 12 | 13 | Scenario: 퀴즈 단일 조회하기 14 | When 1번 세션, 1번 키워드에 퀴즈를 작성하면 15 | And 1번 세션과 1번 키워드와 1번 퀴즈를 조회하면 16 | Then 퀴즈가 조회된다 17 | 18 | Scenario: 퀴즈 수정하기 19 | When 1번 세션, 1번 키워드에 퀴즈를 작성하고 20 | And 1번 세션과 1번 키워드와 1번 퀴즈 내용을 "수달이수정한퀴즈"으로 수정하면 21 | Then 퀴즈가 수정된다 22 | 23 | Scenario: 퀴즈 삭제하기 24 | When 1번 세션, 1번 키워드에 퀴즈를 작성하고 25 | And 1번 세션과 1번 키워드와 1번 퀴즈를 삭제하면 26 | Then 퀴즈가 삭제된다 27 | 28 | 29 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/session.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 강의 관련 기능 3 | 4 | Scenario: 강의 생성기능 5 | When "새로운이름" 강의를 추가하면 6 | Then "새로운이름" 강의가 추가된다 7 | 8 | Scenario: 강의 목록 조회하기 9 | Given 강의 여러개를 작성하고 10 | When 강의 목록을 조회하면 11 | Then 강의 목록을 받는다 12 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/sessionmember.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 세션 멤버 기능 3 | 4 | Scenario: 강의에 자신을 등록하는 기능 5 | Given "브라운" 이 로그인을 하공 6 | Given "강의1" 추가하면 7 | When 1 강의에 자신을 등록하면 8 | Then 강의에 내가 추가된다. 9 | 10 | Scenario: 강의에서 자신을 제거하는 기능 11 | Given "브라운" 이 로그인을 하공 12 | Given "강의1" 추가하면 13 | Given 1 강의에 자신을 등록하고 14 | When 1 강의에서 자신을 제거하면 15 | Then 강의에서 내가 제거된다. 16 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/studylog-create.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 스터디로그 관련 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | And 미션 여러개를 생성하고 7 | And "브라운"이 크루역할로 로그인을 하고 8 | 9 | Scenario: 스터디로그 작성하기 10 | When 스터디로그를 작성하면 11 | Then 스터디로그가 작성된다 12 | 13 | Scenario: 세션, 미션 없이 스터디로그 작성하기 14 | When 세션과 미션 없이 스터디로그를 작성하면 15 | Then 스터디로그가 작성된다 16 | 17 | Scenario: 스터디로그 세션 수정하기 18 | Given 스터디로그를 작성하고 19 | When 스터디로그 세션을 2로 수정하면 20 | Then 스터디로그 세션이 2로 수정된다 21 | 22 | Scenario: 스터디로그 미션 수정하기 23 | Given 스터디로그를 작성하고 24 | When 스터디로그 미션을 2로 수정하면 25 | Then 스터디로그 미션이 2로 수정된다 26 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/studylog-popular.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 인기있는 학습로그 관련 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | And 미션 여러개를 생성하고 7 | And "브라운"이 크루역할로 로그인을 하고 8 | And "브라운"을 멤버그룹과 그룹멤버에 등록하고 9 | 10 | Scenario: 인기 있는 순서로 스터디로그 목록 조회하기 11 | Given 스터디로그 여러개를 작성하고 12 | When 로그인된 사용자가 2번째 스터디로그를 좋아요 하고 13 | When 인기 있는 스터디로그 목록을 "2"개만큼 갱신하고 14 | Then 인기있는 스터디로그 목록 요청시 id "2, 1" 순서로 조회된다 15 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/studylogOverview.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 스터디로그 오버뷰 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | And 미션 여러개를 생성하고 7 | And "브라운"이 크루역할로 로그인을 하고 8 | 9 | Scenario: 해당 유저의 태그 목록 조회하기 10 | Given 1번 태그의 스터디로그를 1개 작성하고 11 | Given 2번 태그의 스터디로그를 1개 작성하고 12 | Given 3번 태그의 스터디로그를 1개 작성하고 13 | Given 4번 태그의 스터디로그를 1개 작성하고 14 | When "브라운"의 태그 목록을 조회하면 15 | Then 해당 유저의 태그 목록이 조회된다 16 | 17 | Scenario: 해당 유저의 스터디로그 목록 월별로 조회하기 18 | Given 1번 태그의 스터디로그를 1개 작성하고 19 | Given 3번 태그의 스터디로그를 1개 작성하고 20 | When "브라운"의 이번 달 스터디로그 목록을 조회하면 21 | Then 해당 유저의 스터디로그 목록이 조회된다 22 | -------------------------------------------------------------------------------- /backend/src/acceptanceTest/resources/wooteco/prolog/tag.feature: -------------------------------------------------------------------------------- 1 | @api 2 | Feature: 태그 관련 기능 3 | 4 | Background: 사전 작업 5 | Given 세션 여러개를 생성하고 6 | And 미션 여러개를 생성하고 7 | And "웨지"가 크루역할로 로그인을 하고 8 | 9 | Scenario: 태그 작성하기 10 | When 스터디로그를 작성하면 11 | Then 태그도 작성된다 12 | 13 | Scenario: 태그 목록 조회하기 14 | Given 스터디로그 여러개를 작성하고 15 | When 태그 목록을 조회하면 16 | Then 태그 목록을 받는다 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.scheduling.annotation.EnableAsync; 5 | 6 | @Configuration 7 | @EnableAsync 8 | public class AsyncConfig { 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/JpaAuditingConfig.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 5 | 6 | @Configuration 7 | @EnableJpaAuditing 8 | public class JpaAuditingConfig { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/PrologApplication.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import java.util.TimeZone; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | 9 | @EnableScheduling 10 | @SpringBootApplication 11 | public class PrologApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(PrologApplication.class, args); 15 | } 16 | 17 | @PostConstruct 18 | private void setTimeZone() { 19 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/RetryConfig.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.retry.annotation.EnableRetry; 5 | 6 | @Configuration 7 | @EnableRetry 8 | public class RetryConfig { 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/MetaOgService.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application; 2 | 3 | import java.util.Map; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | import wooteco.prolog.article.application.OgTagParser.OgType; 7 | import wooteco.prolog.article.application.dto.ArticleUrlRequest; 8 | import wooteco.prolog.article.application.dto.ArticleUrlResponse; 9 | 10 | @RequiredArgsConstructor 11 | @Service 12 | public class MetaOgService { 13 | 14 | private final OgTagParser ogTagParser; 15 | 16 | public ArticleUrlResponse parse(final ArticleUrlRequest articleUrlRequest) { 17 | final Map parse = ogTagParser.parse(articleUrlRequest.getUrl()); 18 | 19 | return ArticleUrlResponse.from(parse); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/RssFeedException.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application; 2 | 3 | public class RssFeedException extends RuntimeException { 4 | 5 | public RssFeedException(String message, Exception e) { 6 | super(message, e); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/dto/ArticleBookmarkRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class ArticleBookmarkRequest { 9 | 10 | private final Boolean bookmark; 11 | 12 | public ArticleBookmarkRequest() { 13 | this(null); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/dto/ArticleLikesRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class ArticleLikesRequest { 9 | 10 | private final Boolean like; 11 | 12 | public ArticleLikesRequest() { 13 | this(null); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/dto/ArticleUrlRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class ArticleUrlRequest { 9 | 10 | private final String url; 11 | 12 | private ArticleUrlRequest() { 13 | this(null); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/application/dto/ArticleUrlResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.application.dto; 2 | 3 | import java.util.Map; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import wooteco.prolog.article.application.OgTagParser.OgType; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public class ArticleUrlResponse { 11 | 12 | private final String title; 13 | private final String description; 14 | private final String imageUrl; 15 | 16 | public static ArticleUrlResponse from(final Map parsedTags) { 17 | final String title = parsedTags.get(OgType.TITLE); 18 | final String description = parsedTags.get(OgType.DESCRIPTION); 19 | final String image = parsedTags.get(OgType.IMAGE); 20 | return new ArticleUrlResponse(title, description, image); 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.domain; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ArticleFilterType { 7 | ALL(""), 8 | ANDROID("안드로이드"), 9 | BACKEND("백엔드"), 10 | FRONTEND("프론트엔드"); 11 | 12 | private final String partName; 13 | 14 | ArticleFilterType(String partName) { 15 | this.partName = partName; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/article/domain/ViewCount.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.article.domain; 2 | 3 | import jakarta.persistence.Embeddable; 4 | import lombok.AccessLevel; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | @EqualsAndHashCode 13 | @ToString 14 | @Embeddable 15 | public class ViewCount { 16 | 17 | private int views; 18 | 19 | public void increase() { 20 | this.views++; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/badge/application/BadgeCreator.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.badge.application; 2 | 3 | import java.util.Optional; 4 | import wooteco.prolog.badge.domain.BadgeType; 5 | 6 | public interface BadgeCreator { 7 | 8 | Optional create(String username); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/badge/application/dto/BadgeResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.badge.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class BadgeResponse { 11 | 12 | private String name; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/badge/application/dto/BadgesResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.badge.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Getter 12 | public class BadgesResponse { 13 | 14 | private List badges; 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/badge/domain/Badge.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.badge.domain; 2 | 3 | public class Badge { 4 | 5 | private final String name; 6 | 7 | public Badge(String name) { 8 | this.name = name; 9 | } 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "Badge{" + 18 | "name='" + name + '\'' + 19 | '}'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/badge/domain/BadgeType.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.badge.domain; 2 | 3 | public enum BadgeType { 4 | 5 | PASSION_KING, 6 | COMPLIMENT_KING 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/PageableResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import org.springframework.data.domain.Page; 7 | 8 | @AllArgsConstructor 9 | @Getter 10 | public class PageableResponse { 11 | 12 | private static final int ONE_INDEXED_PARAMETER = 1; 13 | 14 | private List data; 15 | private Long totalSize; 16 | private int totalPage; 17 | private int currentPage; 18 | 19 | public static PageableResponse of(List data, Page page) { 20 | return new PageableResponse<>(data, page.getTotalElements(), page.getTotalPages(), 21 | page.getNumber() + ONE_INDEXED_PARAMETER); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/SSLConfig.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common; 2 | 3 | import org.springframework.boot.ApplicationRunner; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import wooteco.prolog.article.application.SSLUtil; 8 | 9 | @Configuration 10 | public class SSLConfig { 11 | 12 | @Bean 13 | @Profile({"local", "test"}) 14 | public ApplicationRunner applicationRunner() { 15 | return args -> SSLUtil.disableSSLVerification(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/entity/CreateAuditingEntity.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.EntityListeners; 5 | import jakarta.persistence.MappedSuperclass; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | @MappedSuperclass 12 | @EntityListeners(AuditingEntityListener.class) 13 | public abstract class CreateAuditingEntity { 14 | 15 | @Column(nullable = false, updatable = false) 16 | @CreatedDate 17 | private LocalDateTime createdAt; 18 | 19 | public LocalDateTime getCreatedAt() { 20 | return createdAt; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/exception/AiResponseProcessingException.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.exception; 2 | 3 | public final class AiResponseProcessingException extends RuntimeException { 4 | public AiResponseProcessingException(final Throwable cause) { 5 | super("Invalid response format from AI model", cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class BadRequestException extends RuntimeException { 7 | 8 | private final String message; 9 | private final int code; 10 | 11 | public BadRequestException(BadRequestCode badRequestCode) { 12 | this.message = badRequestCode.getMessage(); 13 | this.code = badRequestCode.getCode(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/exception/ExceptionDto.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class ExceptionDto { 9 | 10 | private int code; 11 | private String message; 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/slacklogger/RequestStorage.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.slacklogger; 2 | 3 | import org.springframework.web.util.ContentCachingRequestWrapper; 4 | 5 | public class RequestStorage { 6 | 7 | private ContentCachingRequestWrapper request; 8 | 9 | public void set(ContentCachingRequestWrapper request) { 10 | this.request = request; 11 | } 12 | 13 | public ContentCachingRequestWrapper get() { 14 | return request; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/slacklogger/SlackAlarm.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.slacklogger; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SlackAlarm { 11 | 12 | SlackAlarmErrorLevel level() default SlackAlarmErrorLevel.WARN; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/slacklogger/SlackAlarmErrorLevel.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.slacklogger; 2 | 3 | public enum SlackAlarmErrorLevel { 4 | TRACE, 5 | DEBUG, 6 | INFO, 7 | WARN, 8 | ERROR 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/common/ui/HealthCheckController.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.common.ui; 2 | 3 | import static com.google.common.collect.ImmutableMap.of; 4 | 5 | import com.google.common.collect.ImmutableMap; 6 | import java.util.Map; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class HealthCheckController { 12 | 13 | private static final ImmutableMap HEALTH_CHECK_RESULT = of("status", "UP"); 14 | 15 | @GetMapping("/elb-health") 16 | public Map healthCheck() { 17 | return HEALTH_CHECK_RESULT; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/image/application/dto/ImageUrlResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.image.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 8 | @Getter 9 | public class ImageUrlResponse { 10 | 11 | private String imageUrl; 12 | 13 | public ImageUrlResponse(final String imageUrl) { 14 | this.imageUrl = imageUrl; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/image/config/S3Configuration.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.image.config; 2 | 3 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 4 | import com.amazonaws.regions.Regions; 5 | import com.amazonaws.services.s3.AmazonS3; 6 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class S3Configuration { 12 | 13 | @Bean 14 | public AmazonS3 amazonS3() { 15 | return AmazonS3ClientBuilder 16 | .standard() 17 | .withCredentials(InstanceProfileCredentialsProvider.getInstance()) 18 | .withRegion(Regions.AP_NORTHEAST_2) 19 | .build(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/image/domain/FileExtension.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.image.domain; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public enum FileExtension { 6 | 7 | JPG("jpg"), 8 | JPEG("jpeg"), 9 | PNG("png"), 10 | BMP("bmp"); 11 | 12 | private final String value; 13 | 14 | FileExtension(final String value) { 15 | this.value = value; 16 | } 17 | 18 | public static boolean isSupport(final String extension) { 19 | return Stream.of(values()) 20 | .anyMatch(it -> it.value.equalsIgnoreCase(extension)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/application/InterviewAnswerRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.application; 2 | 3 | public record InterviewAnswerRequest( 4 | String answer 5 | ) { 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/application/InterviewMessageResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.application; 2 | 3 | import wooteco.prolog.interview.domain.InterviewMessage; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | public record InterviewMessageResponse( 8 | InterviewMessage.Sender sender, 9 | String content, 10 | String hint, 11 | LocalDateTime createdAt 12 | ) { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/application/InterviewSessionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.application; 2 | 3 | public record InterviewSessionRequest(Long questionId) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/application/InterviewSessionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.application; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record InterviewSessionResponse( 8 | Long id, 9 | Long memberId, 10 | boolean finished, 11 | List messages, 12 | int currentRound 13 | ) { 14 | private static final int START_ROUND = 1; 15 | private static final int MAX_ROUND = 10; 16 | 17 | @JsonProperty("remainRound") 18 | public int remainRound() { 19 | return MAX_ROUND + START_ROUND - currentRound; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/domain/FollowUpQuestion.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.domain; 2 | 3 | public record FollowUpQuestion( 4 | String followUpQuestion, 5 | String hint 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/domain/Interviewer.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.domain; 2 | 3 | public interface Interviewer { 4 | 5 | InterviewMessages start(String goal, String question); 6 | 7 | InterviewMessages followUp(InterviewMessages interviewMessages, String answer); 8 | 9 | InterviewMessages finish(InterviewMessages interviewMessages); 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/interview/domain/repository/InterviewSessionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.interview.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.interview.domain.InterviewSession; 5 | 6 | public interface InterviewSessionRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/levellogs/application/dto/LevelLogSummariesResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.levellogs.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | @Getter 13 | @ToString 14 | public class LevelLogSummariesResponse { 15 | 16 | private List data; 17 | private long totalSize; 18 | private int totalPage; 19 | private int currPage; 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/levellogs/application/dto/SelfDiscussionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.levellogs.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.ToString; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | @Getter 11 | @EqualsAndHashCode 12 | @ToString 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | public class SelfDiscussionRequest { 15 | 16 | @NotNull 17 | private String question; 18 | @NotNull 19 | private String answer; 20 | 21 | public SelfDiscussionRequest(String question, String answer) { 22 | this.question = question; 23 | this.answer = answer; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/levellogs/application/dto/SelfDiscussionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.levellogs.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.ToString; 7 | import wooteco.prolog.levellogs.domain.SelfDiscussion; 8 | 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @Getter 11 | @ToString 12 | public class SelfDiscussionResponse { 13 | 14 | private Long id; 15 | private String question; 16 | private String answer; 17 | 18 | public SelfDiscussionResponse(SelfDiscussion selfDiscussion) { 19 | this.id = selfDiscussion.getId(); 20 | this.question = selfDiscussion.getQuestion(); 21 | this.answer = selfDiscussion.getAnswer(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/levellogs/domain/repository/LevelLogRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.levellogs.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.levellogs.domain.LevelLog; 5 | 6 | public interface LevelLogRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/levellogs/domain/repository/SelfDiscussionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.levellogs.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.levellogs.domain.LevelLog; 6 | import wooteco.prolog.levellogs.domain.SelfDiscussion; 7 | 8 | public interface SelfDiscussionRepository extends JpaRepository { 9 | 10 | List findByLevelLog(LevelLog levelLog); 11 | 12 | List findAllByLevelLogIn(List levelLogs); 13 | 14 | void deleteByLevelLog(LevelLog levelLog); 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/aop/MemberAuthorityCache.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.aop; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.context.annotation.RequestScope; 5 | import wooteco.prolog.login.ui.LoginMember.Authority; 6 | 7 | @Component 8 | @RequestScope 9 | public class MemberAuthorityCache { 10 | 11 | private Authority authority; 12 | 13 | public Authority getAuthority() { 14 | return authority; 15 | } 16 | 17 | public void setAuthority(Authority authority) { 18 | this.authority = authority; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/aop/MemberOnly.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.aop; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface MemberOnly { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/application/dto/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.application.dto; 2 | 3 | public class ErrorMessage { 4 | 5 | private String message; 6 | 7 | public ErrorMessage() { 8 | 9 | } 10 | 11 | public ErrorMessage(String message) { 12 | this.message = message; 13 | } 14 | 15 | public String getMessage() { 16 | return message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/application/dto/GithubAccessTokenRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.application.dto; 2 | 3 | public class GithubAccessTokenRequest { 4 | 5 | private final String code; 6 | private final String client_id; 7 | private final String client_secret; 8 | 9 | public GithubAccessTokenRequest(String code, String client_id, String client_secret) { 10 | this.code = code; 11 | this.client_id = client_id; 12 | this.client_secret = client_secret; 13 | } 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getClient_id() { 20 | return client_id; 21 | } 22 | 23 | public String getClient_secret() { 24 | return client_secret; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/application/dto/TokenRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class TokenRequest { 11 | 12 | private String code; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/application/dto/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class TokenResponse { 11 | 12 | private String accessToken; 13 | 14 | public static TokenResponse of(String accessToken) { 15 | return new TokenResponse(accessToken); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/login/domain/AuthMemberPrincipal.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.login.domain; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.PARAMETER) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface AuthMemberPrincipal { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/DepartmentMemberService.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import wooteco.prolog.member.domain.DepartmentMember; 8 | import wooteco.prolog.member.domain.repository.DepartmentMemberRepository; 9 | 10 | @Service 11 | @AllArgsConstructor 12 | @Transactional(readOnly = true) 13 | public class DepartmentMemberService { 14 | 15 | private DepartmentMemberRepository departmentMemberRepository; 16 | 17 | public List findDepartmentMemberByDepartmentId(Long departmentId) { 18 | return departmentMemberRepository.findByDepartmentId(departmentId); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/MemberScrapRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @NoArgsConstructor 9 | @Getter 10 | public class MemberScrapRequest { 11 | 12 | private Long studylogId; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/MemberScrapResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.studylog.application.dto.StudylogResponse; 7 | import wooteco.prolog.studylog.domain.StudylogScrap; 8 | 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Getter 12 | public class MemberScrapResponse { 13 | 14 | private MemberResponse memberResponse; 15 | private StudylogResponse studylogResponse; 16 | 17 | public static MemberScrapResponse of(StudylogScrap studylogScrap) { 18 | return new MemberScrapResponse( 19 | MemberResponse.of(studylogScrap.getMember()), 20 | StudylogResponse.of(studylogScrap.getStudylog()) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/MemberUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class MemberUpdateRequest { 11 | 12 | private String nickname; 13 | private String imageUrl; 14 | private String rssFeedUrl; 15 | 16 | public MemberUpdateRequest(String nickname, String imageUrl) { 17 | this.nickname = nickname; 18 | this.imageUrl = imageUrl; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/ProfileIntroRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class ProfileIntroRequest { 11 | 12 | private String text; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/ProfileIntroResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.member.domain.Member; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | public class ProfileIntroResponse { 12 | 13 | private String text; 14 | 15 | public static ProfileIntroResponse of(Member member) { 16 | return new ProfileIntroResponse(member.getProfileIntro()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/application/dto/RoleUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @NoArgsConstructor 9 | @Getter 10 | public class RoleUpdateRequest { 11 | 12 | private String role; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/Departments.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | public class Departments { 10 | 11 | private List values; 12 | 13 | public boolean isContainsDepartments(Department department) { 14 | return values.contains(department); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/MemberCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class MemberCreatedEvent extends ApplicationEvent { 6 | 7 | public MemberCreatedEvent(Member member) { 8 | super(member); 9 | } 10 | 11 | public Member getMember() { 12 | return (Member) getSource(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/MemberUpdatedEvent.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class MemberUpdatedEvent extends ApplicationEvent { 6 | 7 | public MemberUpdatedEvent(Member member) { 8 | super(member); 9 | } 10 | 11 | public Member getMember() { 12 | return (Member) getSource(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/Part.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | import java.util.Arrays; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | public enum Part { 10 | 11 | BACKEND("백엔드"), 12 | FRONTEND("프론트엔드"), 13 | ANDROID("안드로이드"); 14 | 15 | private final String name; 16 | 17 | public static Part getPartByName(String name) { 18 | return Arrays.stream(values()) 19 | .filter(part -> part.name.equals(name)) 20 | .findFirst() 21 | .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 part가 존재하지 않습니다.")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/Role.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | public enum Role { 4 | 5 | GUEST(0), // 미인증 사용자 6 | CREW(1), // 인증 사용자 7 | COACH(2), // 운영진 8 | ADMIN(3); // 관리자 9 | 10 | private final int importance; 11 | 12 | Role(final int importance) { 13 | this.importance = importance; 14 | } 15 | 16 | public boolean hasLowerImportanceThan(final Role role) { 17 | return this.importance < role.importance; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/Term.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain; 2 | 3 | import java.util.Arrays; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | public enum Term { 10 | 11 | FIRST("1기"), 12 | SECOND("2기"), 13 | THIRD("3기"), 14 | FOURTH("4기"), 15 | FIFTH("5기"), 16 | SIXTH("6기"); 17 | 18 | private final String name; 19 | 20 | public static Term getTermByName(String name) { 21 | return Arrays.stream(values()) 22 | .filter(term -> term.name.equals(name)) 23 | .findFirst() 24 | .orElseThrow(() -> new IllegalArgumentException("name과 일치하는 term이 존재하지 않습니다.")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentMemberRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.member.domain.Department; 6 | import wooteco.prolog.member.domain.DepartmentMember; 7 | import wooteco.prolog.member.domain.Member; 8 | 9 | public interface DepartmentMemberRepository extends JpaRepository { 10 | 11 | List findByDepartmentId(Long departmentId); 12 | 13 | boolean existsDepartmentMemberByMemberAndDepartment(Member member, Department department); 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/repository/DepartmentRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.member.domain.Department; 5 | 6 | public interface DepartmentRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/repository/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import wooteco.prolog.member.domain.Member; 7 | 8 | public interface MemberRepository extends JpaRepository { 9 | 10 | Optional findByGithubId(Long githubId); 11 | 12 | Optional findByUsername(String username); 13 | 14 | List findByIdIn(List memberIds); 15 | 16 | List findByRssFeedUrlIsNotNull(); 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/member/domain/repository/MemberTagRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.member.domain.repository; 2 | 3 | import wooteco.prolog.member.domain.MemberTags; 4 | 5 | public interface MemberTagRepository { 6 | 7 | void register(MemberTags memberTags); 8 | 9 | void update(MemberTags originalMemberTags, MemberTags newMemberTags); 10 | 11 | void unregister(MemberTags memberTags); 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/application/OrganizationGroupMemberRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.application; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | public class OrganizationGroupMemberRequest { 10 | 11 | private String username; 12 | private String nickname; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/application/OrganizationGroupSessionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.application; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class OrganizationGroupSessionRequest { 9 | 10 | private Long organizationGroupId; 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/application/OrganizationGroupSessionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.application; 2 | 3 | public class OrganizationGroupSessionResponse { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/Organization.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Entity 11 | @NoArgsConstructor 12 | @Getter 13 | public class Organization { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | private String name; 19 | 20 | public Organization(String name) { 21 | this.name = name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/OrganizationGroup.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Entity 11 | @NoArgsConstructor 12 | @Getter 13 | public class OrganizationGroup { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | private Long organizationId; 19 | private String name; 20 | 21 | public OrganizationGroup(Long organizationId, String name) { 22 | this.organizationId = organizationId; 23 | this.name = name; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/repository/OrganizationGroupMemberRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.organization.domain.OrganizationGroupMember; 6 | 7 | public interface OrganizationGroupMemberRepository extends JpaRepository { 8 | 9 | List findByUsername(String username); 10 | 11 | List findByMemberId(Long memberId); 12 | 13 | boolean existsByUsername(String username); 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/repository/OrganizationGroupRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.organization.domain.OrganizationGroup; 6 | 7 | public interface OrganizationGroupRepository extends JpaRepository { 8 | 9 | List findByIdInOrderByIdDesc(List organizationGroupIds); 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/repository/OrganizationGroupSessionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.organization.domain.OrganizationGroupSession; 6 | 7 | public interface OrganizationGroupSessionRepository extends JpaRepository { 8 | 9 | List findByOrganizationGroupIdIn(List organizationGroupIds); 10 | 11 | OrganizationGroupSession findByOrganizationGroupId(Long organizationGroupId); 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/organization/domain/repository/OrganizationRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.organization.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.organization.domain.Organization; 5 | 6 | public interface OrganizationRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumQuizResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | public interface CurriculumQuizResponse { 4 | 5 | Long getId(); 6 | 7 | String getQuestion(); 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.roadmap.domain.Curriculum; 7 | 8 | @Getter 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | public class CurriculumRequest { 11 | 12 | private String name; 13 | 14 | public CurriculumRequest(String name) { 15 | this.name = name; 16 | } 17 | 18 | public Curriculum toEntity() { 19 | return new Curriculum(this.name); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/CurriculumResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.roadmap.domain.Curriculum; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class CurriculumResponse { 11 | 12 | private Long id; 13 | private String name; 14 | 15 | public CurriculumResponse(Long id, String name) { 16 | this.id = id; 17 | this.name = name; 18 | } 19 | 20 | public static CurriculumResponse of(Curriculum curriculum) { 21 | if (curriculum == null) { 22 | return null; 23 | } 24 | return new CurriculumResponse(curriculum.getId(), curriculum.getName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/EssayAnswerQuizResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.roadmap.domain.Quiz; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class EssayAnswerQuizResponse { 11 | 12 | private Long quizId; 13 | private String question; 14 | 15 | public EssayAnswerQuizResponse(Long quizId, String question) { 16 | this.quizId = quizId; 17 | this.question = question; 18 | } 19 | 20 | public static EssayAnswerQuizResponse from(Quiz quiz) { 21 | return new EssayAnswerQuizResponse(quiz.getId(), quiz.getQuestion()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/EssayAnswerRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @NoArgsConstructor 9 | @Getter 10 | public class EssayAnswerRequest { 11 | 12 | private Long quizId; 13 | private String answer; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/EssayAnswerSearchRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | @Setter 11 | @Getter 12 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 13 | @AllArgsConstructor 14 | public class EssayAnswerSearchRequest { 15 | 16 | private Long curriculumId; 17 | private Long keywordId; 18 | private List quizIds; 19 | private List memberIds; 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/EssayAnswerUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public final class EssayAnswerUpdateRequest { 11 | 12 | private String answer; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/QuizRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @NoArgsConstructor 9 | @Getter 10 | public class QuizRequest { 11 | 12 | private String question; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/QuizResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import wooteco.prolog.roadmap.domain.Quiz; 8 | 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @Getter 11 | public class QuizResponse { 12 | 13 | private Long quizId; 14 | private String question; 15 | private Boolean isLearning; 16 | 17 | public QuizResponse(Long quizId, String question, boolean isLearning) { 18 | this.quizId = quizId; 19 | this.question = question; 20 | this.isLearning = isLearning; 21 | } 22 | 23 | public static QuizResponse of(Quiz quiz, boolean isLearning) { 24 | return new QuizResponse(quiz.getId(), quiz.getQuestion(), isLearning); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/QuizzesResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class QuizzesResponse { 11 | 12 | private Long keywordId; 13 | 14 | private List data; 15 | 16 | public QuizzesResponse(Long keywordId, 17 | List data) { 18 | this.keywordId = keywordId; 19 | this.data = data; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/RecommendedPostResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import wooteco.prolog.roadmap.domain.RecommendedPost; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | public class RecommendedPostResponse { 10 | 11 | private final Long id; 12 | private final String url; 13 | 14 | public static RecommendedPostResponse from(final RecommendedPost recommendedPost) { 15 | return new RecommendedPostResponse(recommendedPost.getId(), recommendedPost.getUrl()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/RecommendedRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @AllArgsConstructor 11 | public class RecommendedRequest { 12 | 13 | private String url; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/RecommendedUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @AllArgsConstructor 11 | public class RecommendedUpdateRequest { 12 | 13 | private String url; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/SessionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 8 | @Getter 9 | public class SessionRequest { 10 | 11 | private String name; 12 | 13 | public SessionRequest(final String name) { 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/SessionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import wooteco.prolog.session.domain.Session; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class SessionResponse { 11 | 12 | private Long sessionId; 13 | private String name; 14 | 15 | public SessionResponse(final Long sessionId, final String name) { 16 | this.sessionId = sessionId; 17 | this.name = name; 18 | } 19 | 20 | public static SessionResponse createResponse(final Session session) { 21 | return new SessionResponse(session.getId(), session.getName()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/application/dto/SessionsResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class SessionsResponse { 11 | 12 | private List sessions; 13 | 14 | public SessionsResponse(final List sessions) { 15 | this.sessions = sessions; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/domain/repository/CurriculumRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.roadmap.domain.Curriculum; 5 | 6 | public interface CurriculumRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/domain/repository/RecommendedPostRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.roadmap.domain.RecommendedPost; 5 | 6 | public interface RecommendedPostRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndAnsweredQuizCount.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.domain.repository.dto; 2 | 3 | public interface KeywordIdAndAnsweredQuizCount { 4 | 5 | long getKeywordId(); 6 | 7 | int getAnsweredQuizCount(); 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/roadmap/domain/repository/dto/KeywordIdAndTotalQuizCount.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.roadmap.domain.repository.dto; 2 | 3 | public interface KeywordIdAndTotalQuizCount { 4 | 5 | long getKeywordId(); 6 | 7 | int getTotalQuizCount(); 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/MissionQuestionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | public class MissionQuestionResponse { 10 | 11 | private Long missionId; 12 | private List questions; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/MissionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @NoArgsConstructor 9 | @Getter 10 | public class MissionRequest { 11 | 12 | private String name; 13 | private Long sessionId; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/QuestionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | public record QuestionRequest( 4 | long missionId, 5 | String content 6 | ) { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/QuestionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class QuestionResponse { 9 | 10 | private Long id; 11 | private String content; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/SessionGroupMemberRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class SessionGroupMemberRequest { 11 | 12 | private Long groupId; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/SessionMemberRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | public class SessionMemberRequest { 12 | 13 | private List memberIds; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/SessionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import wooteco.prolog.session.domain.Session; 4 | 5 | public class SessionRequest { 6 | 7 | private String name; 8 | 9 | public SessionRequest() { 10 | } 11 | 12 | public SessionRequest(final String name) { 13 | this.name = name; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public Session toEntity() { 21 | return new Session(this.name); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/application/dto/SessionResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import wooteco.prolog.session.domain.Session; 8 | 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Getter 12 | @EqualsAndHashCode 13 | public class SessionResponse { 14 | 15 | private Long id; 16 | private String name; 17 | 18 | public static SessionResponse of(Session session) { 19 | if (session == null) { 20 | return null; 21 | } 22 | return new SessionResponse(session.getId(), session.getName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/AnswerUpdatedEvent.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class AnswerUpdatedEvent extends ApplicationEvent { 6 | 7 | public AnswerUpdatedEvent(final Answer answer) { 8 | super(answer); 9 | } 10 | 11 | public Answer getAnswer() { 12 | return (Answer) getSource(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/QnaFeedbackContents.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Embeddable; 5 | 6 | @Embeddable 7 | public record QnaFeedbackContents( 8 | @Column(nullable = false, length = 1024) 9 | String strengths, 10 | 11 | @Column(nullable = false, length = 1024) 12 | String improvementPoints, 13 | 14 | @Column(nullable = false, length = 1024) 15 | String additionalLearning, 16 | 17 | @Column(nullable = false) 18 | int score 19 | ) { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/QnaFeedbackProvider.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain; 2 | 3 | public interface QnaFeedbackProvider { 4 | 5 | QnaFeedbackContents evaluate(QnaFeedbackRequest request); 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/QnaFeedbackRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Embeddable; 5 | 6 | @Embeddable 7 | public record QnaFeedbackRequest( 8 | @Column(nullable = false, length = 1024) 9 | String goal, 10 | 11 | @Column(nullable = false, length = 1024) 12 | String question, 13 | 14 | @Column(nullable = false, length = 1024) 15 | String answer 16 | ) { 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/Question.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.ManyToOne; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @NoArgsConstructor 12 | @Entity 13 | @Getter 14 | public class Question { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | private String content; 20 | 21 | @ManyToOne 22 | private Mission mission; 23 | 24 | public Question(final String content, final Mission mission) { 25 | this.content = content; 26 | this.mission = mission; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/AnswerRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.session.domain.Answer; 5 | 6 | import java.util.List; 7 | 8 | public interface AnswerRepository extends JpaRepository { 9 | 10 | List findByStudylogId(Long studylogId); 11 | 12 | List findByStudylogIdIn(List studylogIds); 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/AnswerTempRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.session.domain.AnswerTemp; 6 | 7 | public interface AnswerTempRepository extends JpaRepository { 8 | 9 | boolean existsByMemberId(Long memberId); 10 | 11 | void deleteByMemberId(Long memberId); 12 | 13 | List findByMemberId(Long memberId); 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/MissionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import wooteco.prolog.session.domain.Mission; 7 | 8 | public interface MissionRepository extends JpaRepository { 9 | 10 | Optional findByName(String name); 11 | 12 | List findBySessionIdIn(List sessionIds); 13 | 14 | List findBySessionId(long sessionId); 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/QuestionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.session.domain.Question; 6 | 7 | public interface QuestionRepository extends JpaRepository { 8 | 9 | List findByMissionId(Long missionId); 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/SessionMemberRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import wooteco.prolog.member.domain.Member; 7 | import wooteco.prolog.session.domain.SessionMember; 8 | 9 | public interface SessionMemberRepository extends JpaRepository { 10 | 11 | List findAllBySessionId(Long sessionId); 12 | 13 | List findByMember(Member member); 14 | 15 | Optional findBySessionIdAndMember(Long sessionId, Member member); 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/domain/repository/SessionRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.domain.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import wooteco.prolog.session.domain.Session; 7 | 8 | public interface SessionRepository extends JpaRepository { 9 | 10 | Optional findByName(String name); 11 | 12 | List findAllByCurriculumId(Long curriculumId); 13 | 14 | List findAllByOrderByIdDesc(); 15 | 16 | List findAllByIdInOrderByIdDesc(List ids); 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/session/infrastructure/FakeFeedbackProvider.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.session.infrastructure; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.stereotype.Component; 5 | import wooteco.prolog.session.domain.QnaFeedbackContents; 6 | import wooteco.prolog.session.domain.QnaFeedbackProvider; 7 | import wooteco.prolog.session.domain.QnaFeedbackRequest; 8 | 9 | @Profile({"local", "test"}) 10 | @Component 11 | public final class FakeFeedbackProvider implements QnaFeedbackProvider { 12 | 13 | @Override 14 | public QnaFeedbackContents evaluate(final QnaFeedbackRequest request) { 15 | return new QnaFeedbackContents( 16 | "강점입니다.", 17 | "개선점입니다.", 18 | "추가학습 방법입니다.", 19 | 1 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CalendarStudylogResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import java.time.LocalDateTime; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import wooteco.prolog.studylog.domain.Studylog; 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class CalendarStudylogResponse { 13 | 14 | private Long id; 15 | private String title; 16 | private LocalDateTime createdAt; 17 | private LocalDateTime updatedAt; 18 | 19 | public static CalendarStudylogResponse of(Studylog studylog) { 20 | return new CalendarStudylogResponse(studylog.getId(), studylog.getTitle(), 21 | studylog.getCreatedAt(), studylog 22 | .getUpdatedAt()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CommentChangeRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 8 | @Getter 9 | public class CommentChangeRequest { 10 | 11 | private String content; 12 | 13 | public CommentChangeRequest(String content) { 14 | this.content = content; 15 | } 16 | 17 | public CommentUpdateRequest toUpdateRequest(Long id, Long studylogId, Long commentId) { 18 | return new CommentUpdateRequest(id, studylogId, commentId, content); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CommentCreateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 8 | @Getter 9 | public class CommentCreateRequest { 10 | 11 | private String content; 12 | 13 | public CommentCreateRequest(String content) { 14 | this.content = content; 15 | } 16 | 17 | public CommentSaveRequest toSaveRequest(Long memberId, Long studylogId) { 18 | return new CommentSaveRequest(memberId, studylogId, content); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CommentMemberResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 8 | @Getter 9 | public class CommentMemberResponse { 10 | 11 | private Long id; 12 | private String username; 13 | private String nickname; 14 | private String imageUrl; 15 | private String role; 16 | 17 | public CommentMemberResponse(Long id, String username, String nickname, String imageUrl, 18 | String role) { 19 | this.id = id; 20 | this.username = username; 21 | this.nickname = nickname; 22 | this.imageUrl = imageUrl; 23 | this.role = role; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CommentUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 8 | @Getter 9 | public class CommentUpdateRequest { 10 | 11 | private Long memberId; 12 | private Long studylogId; 13 | private Long commentId; 14 | private String content; 15 | 16 | public CommentUpdateRequest(Long memberId, Long studylogId, Long commentId, String content) { 17 | this.memberId = memberId; 18 | this.studylogId = studylogId; 19 | this.commentId = commentId; 20 | this.content = content; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/CommentsResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @Getter 10 | public class CommentsResponse { 11 | 12 | private List data; 13 | 14 | public CommentsResponse(List data) { 15 | this.data = data; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/FilterResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import wooteco.prolog.session.application.dto.MissionResponse; 8 | import wooteco.prolog.session.application.dto.SessionResponse; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class FilterResponse { 14 | 15 | private List sessions; 16 | private List mySessions; 17 | private List missions; 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogDocumentResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | public class StudylogDocumentResponse { 12 | 13 | private List studylogIds; 14 | private long totalSize; 15 | private int totalPage; 16 | private int currPage; 17 | 18 | public static StudylogDocumentResponse of(List studylogIds, long totalSize, int totalPage, 19 | int currPage) { 20 | return new StudylogDocumentResponse(studylogIds, totalSize, totalPage, currPage); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogLikeResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @AllArgsConstructor 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @Getter 11 | public class StudylogLikeResponse { 12 | 13 | private boolean liked; 14 | private Integer likesCount; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogMissionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class StudylogMissionRequest { 11 | 12 | private Long missionId; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogSearchRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import java.time.LocalDate; 4 | import lombok.Data; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | 7 | @Data 8 | public class StudylogSearchRequest { 9 | 10 | private Long tagId; 11 | @DateTimeFormat(pattern = "yyyy-MM-dd") 12 | private LocalDate date; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogSessionRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class StudylogSessionRequest { 11 | 12 | private Long sessionId; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/StudylogWithScrapedCountResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class StudylogWithScrapedCountResponse { 11 | 12 | private StudylogResponse studylogResponse; 13 | private int scrapedCount; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/TagRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import wooteco.prolog.studylog.domain.Tag; 4 | 5 | public class TagRequest { 6 | 7 | private String name; 8 | 9 | public TagRequest() { 10 | } 11 | 12 | public TagRequest(String name) { 13 | this.name = name; 14 | } 15 | 16 | public static Tag toEntity(TagRequest tagRequest) { 17 | return new Tag(tagRequest.getName()); 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/TagResponse.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto; 2 | 3 | import wooteco.prolog.studylog.domain.Tag; 4 | 5 | public class TagResponse { 6 | 7 | private Long id; 8 | private String name; 9 | 10 | public TagResponse() { 11 | } 12 | 13 | public TagResponse(Long id, String name) { 14 | this.id = id; 15 | this.name = name; 16 | } 17 | 18 | public static TagResponse of(Tag tag) { 19 | return new TagResponse(tag.getId(), tag.getName()); 20 | } 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/search/SearchParams.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto.search; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.PARAMETER) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SearchParams { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/application/dto/search/StudylogsSearchRequest.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.application.dto.search; 2 | 3 | import java.time.LocalDate; 4 | import java.util.List; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | @AllArgsConstructor 10 | @Getter 11 | public class StudylogsSearchRequest { 12 | 13 | private final String keyword; 14 | private final List sessions; 15 | private final List missions; 16 | private final List tags; 17 | private final List usernames; 18 | private final List members; 19 | private final LocalDate startDate; 20 | private final LocalDate endDate; 21 | private final List ids; 22 | private final Pageable pageable; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/StudylogDocument.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @EqualsAndHashCode 13 | @NoArgsConstructor 14 | @Builder 15 | @AllArgsConstructor 16 | public class StudylogDocument { 17 | 18 | private Long id; 19 | 20 | private String title; 21 | 22 | private String content; 23 | 24 | private List tagIds; 25 | 26 | private Long missionId; 27 | 28 | private Long levelId; 29 | 30 | private String username; 31 | 32 | private LocalDateTime dateTime; 33 | } 34 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/StudylogTempTags.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain; 2 | 3 | import jakarta.persistence.CascadeType; 4 | import jakarta.persistence.Embeddable; 5 | import jakarta.persistence.OneToMany; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import lombok.Getter; 9 | import org.hibernate.annotations.BatchSize; 10 | 11 | @Getter 12 | @Embeddable 13 | public class StudylogTempTags { 14 | 15 | @OneToMany(mappedBy = "studylogTemp", cascade = CascadeType.PERSIST, orphanRemoval = true) 16 | @BatchSize(size = 1000) 17 | private final List values; 18 | 19 | public StudylogTempTags() { 20 | this(new ArrayList<>()); 21 | } 22 | 23 | public StudylogTempTags(List values) { 24 | this.values = values; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/ViewCount.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain; 2 | 3 | import jakarta.persistence.Embeddable; 4 | import lombok.AccessLevel; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | @EqualsAndHashCode 13 | @ToString 14 | @Embeddable 15 | public class ViewCount { 16 | 17 | private int views; 18 | 19 | public void increase() { 20 | this.views++; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/PopularStudylogRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import wooteco.prolog.studylog.domain.PopularStudylog; 6 | 7 | public interface PopularStudylogRepository extends JpaRepository { 8 | 9 | List findAllByDeletedFalse(); 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/StudylogReadRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import wooteco.prolog.studylog.domain.StudylogRead; 7 | 8 | public interface StudylogReadRepository extends JpaRepository { 9 | 10 | boolean existsByMemberIdAndStudylogId(Long memberId, Long studylogId); 11 | 12 | Optional findByMemberIdAndStudylogId(Long memberId, Long studylogId); 13 | 14 | List findByMemberId(Long memberId); 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/StudylogTagRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import wooteco.prolog.studylog.domain.StudylogTag; 7 | import wooteco.prolog.studylog.domain.Tag; 8 | 9 | public interface StudylogTagRepository extends JpaRepository { 10 | 11 | List findByTagIn(List tags); 12 | 13 | @Query("select pt.tag from StudylogTag pt inner join pt.tag group by pt.tag") 14 | List findTagsIncludedInStudylogs(); 15 | 16 | @Query("select pt from StudylogTag pt join fetch pt.tag where pt.studylog.member.id = :memberId") 17 | List findByMember(Long memberId); 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/StudylogTempRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import wooteco.prolog.studylog.domain.StudylogTemp; 5 | 6 | public interface StudylogTempRepository extends JpaRepository { 7 | 8 | boolean existsByMemberId(Long memberId); 9 | 10 | StudylogTemp findByMemberId(Long memberId); 11 | 12 | void deleteByMemberId(Long memberId); 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/TagRepository.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository; 2 | 3 | import java.util.List; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import wooteco.prolog.member.domain.Member; 7 | import wooteco.prolog.studylog.domain.Studylog; 8 | import wooteco.prolog.studylog.domain.Tag; 9 | 10 | public interface TagRepository extends JpaRepository { 11 | 12 | List findByNameValueIn(List name); 13 | 14 | @Query("select t from Tag t where t.id in (select pt.tag.id from StudylogTag pt where pt.studylog = :studylog and pt.studylog.member = :member)") 15 | List findByStudylogAndMember(Studylog studylog, Member member); 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/domain/repository/dto/CommentCount.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.domain.repository.dto; 2 | 3 | import wooteco.prolog.studylog.domain.Studylog; 4 | 5 | public class CommentCount { 6 | 7 | private final Studylog studylog; 8 | 9 | private final long count; 10 | 11 | public CommentCount(final Studylog studylog, final long count) { 12 | this.studylog = studylog; 13 | this.count = count; 14 | } 15 | 16 | public Long getStudylogId() { 17 | return studylog.getId(); 18 | } 19 | 20 | public long getCount() { 21 | return count; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/event/StudylogDeleteEvent.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.event; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class StudylogDeleteEvent { 9 | 10 | private Long studylogId; 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/prolog/studylog/ui/StudylogDocumentController.java: -------------------------------------------------------------------------------- 1 | package wooteco.prolog.studylog.ui; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import wooteco.prolog.studylog.application.DocumentService; 8 | 9 | @RestController 10 | @AllArgsConstructor 11 | public class StudylogDocumentController { 12 | 13 | private DocumentService studylogDocumentService; 14 | 15 | @GetMapping("/sync") 16 | public ResponseEntity sync() { 17 | studylogDocumentService.sync(); 18 | return ResponseEntity.ok().build(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/support/autoceptor/scanner/ClassScanner.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.autoceptor.scanner; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import org.reflections.Reflections; 6 | import org.reflections.scanners.SubTypesScanner; 7 | 8 | public class ClassScanner { 9 | 10 | private final String basePackage; 11 | 12 | public ClassScanner(String basePackage) { 13 | this.basePackage = basePackage; 14 | } 15 | 16 | public Set> getAllClasses() { 17 | Reflections reflections = new Reflections( 18 | basePackage, 19 | new SubTypesScanner(false) 20 | ); 21 | 22 | return new HashSet<>(reflections.getSubTypesOf(Object.class)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/support/performance/PerformanceLoggingForm.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.performance; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Setter 7 | @Getter 8 | public class PerformanceLoggingForm { 9 | 10 | private String targetApi; 11 | private String targetMethod; 12 | private Long transactionTime; 13 | private Long queryCounts = 0L; 14 | private Long queryTime = 0L; 15 | 16 | public void queryCountUp() { 17 | queryCounts++; 18 | } 19 | 20 | public void addQueryTime(Long queryTime) { 21 | this.queryTime += queryTime; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/support/performance/RequestApi.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.performance; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class RequestApi { 11 | 12 | private String urlForm; 13 | private String method; 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/wooteco/support/performance/annotationDataExtractor/AnnotationDataExtractor.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.performance.annotationDataExtractor; 2 | 3 | import java.lang.reflect.Method; 4 | import wooteco.support.performance.RequestApi; 5 | 6 | public interface AnnotationDataExtractor { 7 | 8 | boolean isAssignable(Method method); 9 | 10 | RequestApi extractRequestApi(Method method, String classUrl); 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | application: 2 | url: https://dev.prolog.techcourse.co.kr 3 | 4 | spring: 5 | flyway: 6 | enabled: true 7 | 8 | manager: 9 | role: GUEST 10 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | application: 2 | url: https://prolog.techcourse.co.kr 3 | 4 | spring: 5 | jpa: 6 | properties: 7 | hibernate: 8 | show_sql: false 9 | format_sql: true 10 | use_sql_comment: false 11 | flyway: 12 | out-of-order: false 13 | -------------------------------------------------------------------------------- /backend/src/main/resources/bootstrap-dev.yml: -------------------------------------------------------------------------------- 1 | aws: 2 | secretsmanager: 3 | enabled: true 4 | -------------------------------------------------------------------------------- /backend/src/main/resources/bootstrap-prod.yml: -------------------------------------------------------------------------------- 1 | aws: 2 | secretsmanager: 3 | enabled: true 4 | -------------------------------------------------------------------------------- /backend/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | aws: 2 | secretsmanager: 3 | enabled: false 4 | name: prolog 5 | prefix: secrets 6 | default-context: prolog 7 | region: ap-northeast-2 8 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V10__create_column_rssfeed.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE member 2 | ADD COLUMN rss_feed_url VARCHAR(256); 3 | 4 | ALTER TABLE article 5 | ADD COLUMN description VARCHAR(256) AFTER title; 6 | 7 | ALTER TABLE article 8 | ADD COLUMN published_at datetime(6) AFTER image_url; 9 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V14__alter_table_answer_feedback.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE prolog.answer_feedback 2 | ADD COLUMN visible BOOLEAN NOT NULL DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V2__create_table_keyword_reference.sql: -------------------------------------------------------------------------------- 1 | create table if not exists prolog.keyword_reference 2 | ( 3 | keyword_id 4 | bigint 5 | not 6 | null, 7 | url 8 | varchar 9 | ( 10 | 255 11 | ) null, 12 | constraint FK_KEYWORD_ID 13 | foreign key 14 | ( 15 | keyword_id 16 | ) references prolog.keyword 17 | ( 18 | id 19 | ) 20 | ); 21 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V3__create_table_article.sql: -------------------------------------------------------------------------------- 1 | create table if not exists prolog.article 2 | ( 3 | id 4 | bigint 5 | auto_increment 6 | primary 7 | key, 8 | member_id 9 | bigint 10 | not 11 | null, 12 | title 13 | varchar 14 | ( 15 | 50 16 | ) not null, 17 | url varchar 18 | ( 19 | 1024 20 | ) not null, 21 | created_at datetime 22 | ( 23 | 6 24 | ) not null, 25 | foreign key 26 | ( 27 | member_id 28 | ) references prolog.member 29 | ( 30 | id 31 | ) 32 | ) ENGINE = InnoDB 33 | DEFAULT CHARSET = utf8mb4 34 | COLLATE = utf8mb4_0900_ai_ci; 35 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V4__alter_table_keyword_reference.sql: -------------------------------------------------------------------------------- 1 | drop table prolog.keyword_reference; 2 | 3 | create table if not exists prolog.recommended_post 4 | ( 5 | id 6 | bigint 7 | auto_increment 8 | primary 9 | key, 10 | url 11 | varchar 12 | ( 13 | 512 14 | ) not null, 15 | keyword_id bigint not null, 16 | constraint FK_RECOMMENDED_POST_PARENT_KEYWORD_ID 17 | foreign key 18 | ( 19 | keyword_id 20 | ) references prolog.keyword 21 | ( 22 | id 23 | ) 24 | ); 25 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V5__alter_table_article.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE prolog.article 2 | ADD COLUMN image_url VARCHAR(1024); 3 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V6__create_table_article_bookmark.sql: -------------------------------------------------------------------------------- 1 | create table if not exists prolog.article_bookmark 2 | ( 3 | id 4 | bigint 5 | auto_increment 6 | primary 7 | key, 8 | article_id 9 | bigint 10 | not 11 | null, 12 | member_id 13 | bigint 14 | not 15 | null, 16 | foreign 17 | key 18 | ( 19 | member_id 20 | ) references prolog.member 21 | ( 22 | id 23 | ), 24 | foreign key 25 | ( 26 | article_id 27 | ) references prolog.article 28 | ( 29 | id 30 | ) 31 | ) ENGINE = InnoDB 32 | DEFAULT CHARSET = utf8mb4 33 | COLLATE = utf8mb4_0900_ai_ci; 34 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V7__create_table_article_like.sql: -------------------------------------------------------------------------------- 1 | create table if not exists prolog.article_like 2 | ( 3 | id 4 | bigint 5 | auto_increment 6 | primary 7 | key, 8 | article_id 9 | bigint 10 | not 11 | null, 12 | member_id 13 | bigint 14 | not 15 | null, 16 | foreign 17 | key 18 | ( 19 | member_id 20 | ) references prolog.member 21 | ( 22 | id 23 | ), 24 | foreign key 25 | ( 26 | article_id 27 | ) references prolog.article 28 | ( 29 | id 30 | ) 31 | ) ENGINE = InnoDB 32 | DEFAULT CHARSET = utf8mb4 33 | COLLATE = utf8mb4_0900_ai_ci; 34 | -------------------------------------------------------------------------------- /backend/src/main/resources/db/migration/prod/V8__alter_table_article.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE prolog.article 2 | ADD COLUMN views int default 0; 3 | -------------------------------------------------------------------------------- /backend/src/main/resources/logback-access.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/src/main/resources/logback/console-access-logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %n###### HTTP Request ###### %n%fullRequest###### HTTP Response ###### 8 | %n%fullResponse 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /backend/src/main/resources/logback/console-logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | utf8 6 | 7 | %cyan(%d{yyyy-MM-dd HH:mm:ss}:%-4relative) %highlight(%-5level) 8 | %yellow([%C.%M]:%boldWhite(%L)]) %n > %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | utf8 16 | 17 | %green( > %msg%n) 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /backend/src/main/resources/logback/performance-logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${home}performance.log 6 | 7 | ${home}performance-%d{yyyyMMdd}-%i.log 8 | 15MB 9 | 1 10 | 11 | 12 | utf8 13 | 14 | %msg%n 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /backend/src/main/resources/prompts/qna-interview-initial-question.st: -------------------------------------------------------------------------------- 1 | 안녕하세요! 저는 여러분의 학습을 돕는 AI 인터뷰어예요. 😊 2 | 이제부터 여러분과 함께 대화를 나누며 개념을 점검해볼 거예요! 3 | 4 | 🔹 진행 방식 5 | 1️⃣ 제가 질문을 드리면, 여러분이 답변해주세요. 6 | 2️⃣ 답변을 분석하고 피드백을 제공해드릴게요. 7 | 3️⃣ 질문은 총 {totalQuestionCount}개! 인터뷰를 통해 개념을 정리해볼 수 있어요. 8 | 9 | 그럼, 바로 시작해볼까요? ✨ 10 | 첫 번째 질문입니다! 🎤 11 | 12 | --- 13 | ## ✅ **질문**: 14 | **{question}** 15 | -------------------------------------------------------------------------------- /backend/src/test/java/wooteco/support/autoceptor/test_classes/NormalClass.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.autoceptor.test_classes; 2 | 3 | public class NormalClass { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/test/java/wooteco/support/autoceptor/test_classes/RestControllerClass.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.autoceptor.test_classes; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RequestMapping("/api") 8 | @RestController 9 | public class RestControllerClass { 10 | 11 | @GetMapping("/test") 12 | public void annotationNotExists() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/test/java/wooteco/support/utils/RepositoryTest.java: -------------------------------------------------------------------------------- 1 | package wooteco.support.utils; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | @SpringBootTest 13 | @Transactional 14 | public @interface RepositoryTest { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: test 4 | -------------------------------------------------------------------------------- /frontend/.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/Users/sunny/Woowa/studylog/frontend/src/App.js":"1","/Users/sunny/Woowa/studylog/frontend/src/index.js":"2"},{"size":66,"mtime":1620538125672,"results":"3","hashOfConfig":"4"},{"size":197,"mtime":1620537289467,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"coxdw4",{"filePath":"8","messages":"9","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/sunny/Woowa/studylog/frontend/src/App.js",[],"/Users/sunny/Woowa/studylog/frontend/src/index.js",[]] 2 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | 18 | # log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # direnv 24 | .envrc 25 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 16.20.2 2 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "endOfLine": "auto", 5 | "tabWidth": 2, 6 | "useTabs": false 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.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-create-react-app', 7 | ], 8 | typescript: { 9 | reactDocgen: 'none', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/react'; 2 | import { BrowserRouter as Router } from 'react-router-dom'; 3 | import * as React from 'react'; 4 | import GlobalStyles from '../src/GlobalStyles'; 5 | 6 | export const parameters = { 7 | actions: { argTypesRegex: '^on[A-Z].*' }, 8 | controls: { 9 | matchers: { 10 | color: /(background|color)$/i, 11 | date: /Date$/, 12 | }, 13 | }, 14 | layout: 'centered', 15 | }; 16 | addDecorator((style) => ( 17 | <> 18 | 19 | {style()} 20 | 21 | )); 22 | 23 | configure(require.context('../src', true, /\.stories\.js?$/), module); 24 | -------------------------------------------------------------------------------- /frontend/env/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_URL=https://dev.prolog.techcourse.co.kr/login/callback 2 | REACT_APP_API_URL=https://dev-api.prolog.techcourse.co.kr 3 | REACT_APP_GITHUB_CLIENT_ID=ef151ceecd2e44f1511e 4 | REACT_APP_MODE=DEV 5 | REACT_APP_GTM_ID=GTM-TRVX6TK 6 | -------------------------------------------------------------------------------- /frontend/env/.env.development.local: -------------------------------------------------------------------------------- 1 | REACT_APP_URL=http://localhost:3000/login/callback 2 | REACT_APP_API_URL=https://dev-api.prolog.techcourse.co.kr 3 | REACT_APP_GITHUB_CLIENT_ID=f91b56445e08d44adb76 4 | -------------------------------------------------------------------------------- /frontend/env/.env.local: -------------------------------------------------------------------------------- 1 | REACT_APP_URL=http://localhost:3000/login/callback 2 | REACT_APP_API_URL=http://localhost:8080 3 | REACT_APP_GITHUB_CLIENT_ID=f91b56445e08d44adb76 4 | REACT_APP_MODE=LOCAL 5 | -------------------------------------------------------------------------------- /frontend/env/.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_URL=https://prolog.techcourse.co.kr/login/callback 2 | REACT_APP_API_URL=https://api.prolog.techcourse.co.kr 3 | REACT_APP_GITHUB_CLIENT_ID=329dc0c13bce36959f47 4 | REACT_APP_MODE=PROD 5 | REACT_APP_GTM_ID=GTM-NSHC7H5 6 | -------------------------------------------------------------------------------- /frontend/env/.env.production.local: -------------------------------------------------------------------------------- 1 | REACT_APP_URL=http://localhost:3000/login/callback 2 | REACT_APP_API_URL=https://api.prolog.techcourse.co.kr 3 | REACT_APP_GITHUB_CLIENT_ID=f91b56445e08d44adb76 4 | -------------------------------------------------------------------------------- /frontend/public/assets/images/ability-be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/public/assets/images/ability-be.png -------------------------------------------------------------------------------- /frontend/public/assets/images/ability-fe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/public/assets/images/ability-fe.png -------------------------------------------------------------------------------- /frontend/public/assets/images/select-default-ability-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/public/assets/images/select-default-ability-bg.png -------------------------------------------------------------------------------- /frontend/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/public/favicon.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Study log", 3 | "name": "Study log", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "48x48" 8 | } 9 | ], 10 | "start_url": ".", 11 | "display": "standalone", 12 | "theme_color": "#000000", 13 | "background_color": "#ffffff" 14 | } 15 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import TagManager from 'react-gtm-module'; 2 | 3 | import useSnackBar from './hooks/useSnackBar'; 4 | import PageRouter from './PageRouter'; 5 | 6 | const tagManagerArgs = { 7 | gtmId: process.env.REACT_APP_GTM_ID, 8 | }; 9 | 10 | TagManager.initialize(tagManagerArgs); 11 | 12 | const App = () => { 13 | const { isSnackBarOpen, SnackBar } = useSnackBar(); 14 | 15 | return ( 16 | <> 17 | 18 | {isSnackBarOpen && } 19 | 20 | ); 21 | }; 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /frontend/src/apis/curriculum.ts: -------------------------------------------------------------------------------- 1 | import { createAxiosInstance } from '../utils/axiosInstance'; 2 | import { CurriculumListResponse } from '../models/Keywords'; 3 | 4 | const instanceWithoutToken = createAxiosInstance(); 5 | 6 | export const getCurriculums = async () => { 7 | const response = await instanceWithoutToken.get(`/curriculums`); 8 | 9 | return response.data; 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/apis/filter.ts: -------------------------------------------------------------------------------- 1 | import { client } from '.'; 2 | import { FilterResponse } from '../models/filter'; 3 | import { Author } from '../models/Studylogs'; 4 | 5 | export const getMembersForFilter = async (): Promise => { 6 | const { 7 | data: { members }, 8 | } = await client.get(`/filters`); 9 | 10 | return members; 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/apis/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { BASE_URL } from '../configs/environment'; 3 | import LOCAL_STORAGE_KEY from '../constants/localStorage'; 4 | 5 | const getAccessToken = () => { 6 | const accessToken = localStorage.getItem(LOCAL_STORAGE_KEY.ACCESS_TOKEN); 7 | if (!accessToken || accessToken === 'null') return null; 8 | 9 | return accessToken; 10 | }; 11 | 12 | const accessToken = getAccessToken(); 13 | 14 | export const client = axios.create({ 15 | baseURL: BASE_URL, 16 | headers: { 17 | ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /frontend/src/apis/questions.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import { createAxiosInstance } from '../utils/axiosInstance'; 3 | 4 | const instanceWithoutToken = createAxiosInstance(); 5 | 6 | /** 질문 조회 **/ 7 | export const fetchQuestionsByMissionId = (missionId: number): Promise> => 8 | instanceWithoutToken.get(`/questions?missionId=${missionId}`); 9 | -------------------------------------------------------------------------------- /frontend/src/assets/images/ability-be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/ability-be.png -------------------------------------------------------------------------------- /frontend/src/assets/images/ability-fe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/ability-fe.png -------------------------------------------------------------------------------- /frontend/src/assets/images/article-ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/article-ex.png -------------------------------------------------------------------------------- /frontend/src/assets/images/background-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/background-image.png -------------------------------------------------------------------------------- /frontend/src/assets/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/check.png -------------------------------------------------------------------------------- /frontend/src/assets/images/heart-filled.svg: -------------------------------------------------------------------------------- 1 | Heart -------------------------------------------------------------------------------- /frontend/src/assets/images/heart.svg: -------------------------------------------------------------------------------- 1 | Heart 2 | -------------------------------------------------------------------------------- /frontend/src/assets/images/no-profile-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/no-profile-image.png -------------------------------------------------------------------------------- /frontend/src/assets/images/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/not-found.png -------------------------------------------------------------------------------- /frontend/src/assets/images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/overview.png -------------------------------------------------------------------------------- /frontend/src/assets/images/pen.svg: -------------------------------------------------------------------------------- 1 | asdasdasd 2 | -------------------------------------------------------------------------------- /frontend/src/assets/images/pencil_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/person.svg: -------------------------------------------------------------------------------- 1 | asadasdas 2 | -------------------------------------------------------------------------------- /frontend/src/assets/images/person_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/plus_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/post.png -------------------------------------------------------------------------------- /frontend/src/assets/images/prolog-banner-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/prolog-banner-image.png -------------------------------------------------------------------------------- /frontend/src/assets/images/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/report.png -------------------------------------------------------------------------------- /frontend/src/assets/images/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/rss.png -------------------------------------------------------------------------------- /frontend/src/assets/images/search.svg: -------------------------------------------------------------------------------- 1 | search_final 2 | -------------------------------------------------------------------------------- /frontend/src/assets/images/search_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/select-default-ability-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/select-default-ability-bg.png -------------------------------------------------------------------------------- /frontend/src/assets/images/view.svg: -------------------------------------------------------------------------------- 1 | Eye 2 | -------------------------------------------------------------------------------- /frontend/src/assets/images/wait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/wait.png -------------------------------------------------------------------------------- /frontend/src/assets/images/woteco-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/frontend/src/assets/images/woteco-logo.png -------------------------------------------------------------------------------- /frontend/src/assets/lotties/index.ts: -------------------------------------------------------------------------------- 1 | export { default as NotFoundAnimation } from './not-found.json'; 2 | -------------------------------------------------------------------------------- /frontend/src/components/@shared/FlexBox/FlexBox.ts: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles } from '@emotion/react'; 2 | import styled from '@emotion/styled'; 3 | import { CSSProperties } from 'react'; 4 | 5 | const FlexBox = styled.div< 6 | Pick & { 7 | css?: SerializedStyles; 8 | } 9 | >` 10 | display: flex; 11 | ${({ flexDirection, justifyContent, alignItems }) => css` 12 | flex-direction: ${flexDirection}; 13 | justify-content: ${justifyContent}; 14 | align-items: ${alignItems}; 15 | `} 16 | 17 | ${({ css }) => css} 18 | `; 19 | 20 | export default FlexBox; 21 | -------------------------------------------------------------------------------- /frontend/src/components/@shared/Icons/CancelIcon.tsx: -------------------------------------------------------------------------------- 1 | interface CancelIconProps { 2 | width: string; 3 | height: string; 4 | stroke: string; 5 | strokeWidth?: string; 6 | } 7 | 8 | const CancelIcon = ({ width, height, stroke, strokeWidth }: CancelIconProps) => { 9 | return ( 10 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default CancelIcon; 23 | -------------------------------------------------------------------------------- /frontend/src/components/@shared/SideSheet/SideSheet.stories.tsx: -------------------------------------------------------------------------------- 1 | import { SideSheet, SideSheetProps } from './SideSheet'; 2 | 3 | const backdropRoot = document.createElement('div'); 4 | backdropRoot.setAttribute('id', 'backdrop-root'); 5 | document.body.append(backdropRoot); 6 | 7 | const modalRoot = document.createElement('div'); 8 | modalRoot.setAttribute('id', 'overlay-root'); 9 | document.body.append(modalRoot); 10 | 11 | export default { 12 | title: 'Component/SideSheet', 13 | component: SideSheet, 14 | }; 15 | 16 | export const DefaultSideSheet = (args: SideSheetProps) => ; 17 | 18 | DefaultSideSheet.args = { 19 | children: '테스트', 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/components/@shared/SideSheet/SideSheet.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Backdrop, SideSheetContent } from './SideSheet.style'; 4 | 5 | export type SideSheetProps = { 6 | width?: string; 7 | onClickBackdrop(): void; 8 | }; 9 | 10 | export const SideSheet = ({ 11 | children, 12 | width, 13 | onClickBackdrop, 14 | }: PropsWithChildren) => { 15 | return ( 16 | <> 17 | {ReactDOM.createPortal( 18 | , 19 | document.getElementById('backdrop-root') as HTMLElement 20 | )} 21 | {ReactDOM.createPortal( 22 | {children}, 23 | document.getElementById('overlay-root') as HTMLElement 24 | )} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /frontend/src/components/@shared/SnackBar/SnackBar.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from './SnackBar.styles'; 2 | 3 | const SnackBar = ({ children }: { children: string }) => {children}; 4 | 5 | export default SnackBar; 6 | -------------------------------------------------------------------------------- /frontend/src/components/Article/Article.stories.js: -------------------------------------------------------------------------------- 1 | import Article from './Article'; 2 | 3 | export default { 4 | title: 'Component/Article', 5 | component: Article, 6 | }; 7 | 8 | const Template = (args) =>
; 9 | 10 | export const Basic = Template.bind({}); 11 | 12 | const mockArticle = { 13 | id: 1, 14 | userName: 'string', 15 | title: 'JS Prototype', 16 | url: 'https://poiemaweb.com/js-prototype', 17 | createdAt: '2023-07-10', 18 | }; 19 | 20 | Basic.args = { ...mockArticle }; 21 | -------------------------------------------------------------------------------- /frontend/src/components/Article/ArticleBookmarkFIlter.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | interface ArticleBookmarkFilterProps { 4 | checked: boolean; 5 | handleCheckBookmark: React.ChangeEventHandler; 6 | } 7 | 8 | const ArticleBookmarkFilter = ({ checked, handleCheckBookmark }: ArticleBookmarkFilterProps) => { 9 | return ( 10 | 11 | 15 | 16 | ); 17 | }; 18 | 19 | export default ArticleBookmarkFilter; 20 | 21 | const ArticleBookmarkFilterContainer = styled.div` 22 | display: flex; 23 | align-items: center; 24 | margin-left: 10px; 25 | width: 150px; 26 | height: 100%; 27 | font-size: 1.5rem; 28 | `; 29 | -------------------------------------------------------------------------------- /frontend/src/components/Article/ArticleList.stories.js: -------------------------------------------------------------------------------- 1 | import ArticleList from './ArticleList'; 2 | 3 | export default { 4 | title: 'Component/ArticleList', 5 | component: ArticleList, 6 | }; 7 | 8 | const Template = (args) => ; 9 | 10 | export const Basic = Template.bind({}); 11 | 12 | Basic.args = {}; 13 | -------------------------------------------------------------------------------- /frontend/src/components/Article/ArticleList.style.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import MEDIA_QUERY from '../../constants/mediaQuery'; 3 | 4 | export const Container = styled.ul` 5 | display: grid; 6 | grid-template-columns: repeat(4, 1fr); 7 | grid-auto-rows: 1fr; 8 | gap: 10px; 9 | 10 | ${MEDIA_QUERY.lg} { 11 | grid-template-columns: repeat(3, 1fr); 12 | } 13 | 14 | ${MEDIA_QUERY.md} { 15 | grid-template-columns: repeat(2, 1fr); 16 | } 17 | 18 | ${MEDIA_QUERY.sm} { 19 | grid-template-columns: repeat(1, 1fr); 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /frontend/src/components/Article/ArticleList.tsx: -------------------------------------------------------------------------------- 1 | import * as Styled from './ArticleList.style'; 2 | import Article from './Article'; 3 | import { ArticleType } from '../../models/Article'; 4 | 5 | interface ArticleListProps { 6 | articles: ArticleType[]; 7 | } 8 | 9 | const ArticleList = ({ articles }: ArticleListProps) => { 10 | return ( 11 | 12 | {articles?.map((article) => ( 13 |
14 | ))} 15 | 16 | ); 17 | }; 18 | 19 | export default ArticleList; 20 | -------------------------------------------------------------------------------- /frontend/src/components/Button/ResponsiveButton.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import styled from '@emotion/styled'; 3 | import { COLOR } from '../../enumerations/color'; 4 | import { ResponsiveButtonProps } from './ResponsiveButton'; 5 | 6 | export const Root = styled.button< 7 | Pick 8 | >` 9 | width: 100%; 10 | border-radius: 12px; 11 | text-align: center; 12 | padding: 0 10px; 13 | color: #fff; 14 | background-color: ${COLOR.LIGHT_BLUE_900}; 15 | height: 50px; 16 | 17 | ${({ fontSize, color, backgroundColor, height }) => css` 18 | font-size: ${fontSize}; 19 | color: ${color}; 20 | background-color: ${backgroundColor}; 21 | height: ${height}; 22 | `}; 23 | `; 24 | -------------------------------------------------------------------------------- /frontend/src/components/Button/ResponsiveButton.tsx: -------------------------------------------------------------------------------- 1 | import * as Styled from './ResponsiveButton.styles'; 2 | import { ButtonHTMLAttributes } from 'react'; 3 | 4 | export interface ResponsiveButtonProps extends ButtonHTMLAttributes { 5 | text?: string; 6 | fontSize?: string; 7 | color?: string; 8 | backgroundColor?: string; 9 | height?: string; 10 | } 11 | 12 | const ResponsiveButton = ({ 13 | text, 14 | fontSize, 15 | color, 16 | backgroundColor, 17 | height, 18 | ...props 19 | }: ResponsiveButtonProps) => { 20 | return ( 21 | 28 | {text} 29 | 30 | ); 31 | }; 32 | 33 | export default ResponsiveButton; 34 | -------------------------------------------------------------------------------- /frontend/src/components/Card/Card.stories.tsx: -------------------------------------------------------------------------------- 1 | import Card from './Card'; 2 | import { CARD_SIZE } from './Card.styles'; 3 | 4 | interface Props { 5 | children: JSX.Element; 6 | size: string; 7 | } 8 | 9 | export default { 10 | title: 'Component/Card', 11 | component: Card, 12 | args: { 13 | size: 'SMALL', 14 | }, 15 | argTypes: { 16 | size: { options: Object.values(CARD_SIZE), control: { type: 'select' } }, 17 | }, 18 | }; 19 | 20 | export const Default = (args: Props) => { 21 | return ( 22 | 23 |
hello
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /frontend/src/components/Card/Card.tsx: -------------------------------------------------------------------------------- 1 | import { SerializedStyles } from '@emotion/react'; 2 | import { Container, Title } from './Card.styles'; 3 | 4 | interface Props { 5 | children: JSX.Element; 6 | title?: string; 7 | size: string; 8 | cssProps?: SerializedStyles; 9 | css?: SerializedStyles; 10 | onClick?: () => void; 11 | } 12 | 13 | const Card = ({ title, children, size, cssProps, css, onClick }: Props) => { 14 | return ( 15 |
16 | {title && {title} } 17 | 18 | {children} 19 | 20 |
21 | ); 22 | }; 23 | 24 | export default Card; 25 | -------------------------------------------------------------------------------- /frontend/src/components/Comment/CommentList.style.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { COLOR } from '../../enumerations/color'; 3 | 4 | export const CommentsContainer = styled.div` 5 | padding: 28px 12px 0; 6 | 7 | & > div + div { 8 | padding-top: 18px; 9 | border-top: 1px solid ${COLOR.LIGHT_GRAY_200}; 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /frontend/src/components/DropdownMenu/DropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource @emotion/react */ 2 | 3 | // import PropTypes from 'prop-types'; 4 | import { Container } from './DropdownMenu.styles'; 5 | import { css } from '@emotion/react'; 6 | 7 | export interface DropdownMenuProps { 8 | css?: ReturnType; 9 | } 10 | 11 | const DropdownMenu = ({ children, css }: React.PropsWithChildren) => { 12 | return {children}; 13 | }; 14 | 15 | export default DropdownMenu; 16 | -------------------------------------------------------------------------------- /frontend/src/components/Editor/Editor.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { COLOR } from '../../constants'; 3 | 4 | export const EditorWrapperStyle = css` 5 | .toastui-editor-mode-switch { 6 | height: 4.8rem; 7 | border-bottom-right-radius: 2rem; 8 | border-bottom-left-radius: 2rem; 9 | } 10 | `; 11 | 12 | export const EditorTitleStyle = css` 13 | padding-bottom: 2rem; 14 | margin-bottom: 2rem; 15 | border-bottom: 1px solid ${COLOR.LIGHT_GRAY_100}; 16 | 17 | > input { 18 | width: 100%; 19 | font-size: 2.4rem; 20 | border: none; 21 | //padding: 0.4rem 1rem; 22 | outline: none; 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /frontend/src/components/GithubLogin/GithubLogin.tsx: -------------------------------------------------------------------------------- 1 | const params = { 2 | client_id: `${process.env.REACT_APP_GITHUB_CLIENT_ID}`, 3 | redirect_uri: `${process.env.REACT_APP_URL}`, 4 | }; 5 | 6 | const githubOauthURL = 'https://github.com/login/oauth/authorize'; 7 | const githubOauthQueries = new URLSearchParams(params).toString(); 8 | 9 | const GithubLogin = ({ children }) => { 10 | return {children}; 11 | }; 12 | 13 | export default GithubLogin; 14 | -------------------------------------------------------------------------------- /frontend/src/components/KeywordDetailSideSheet/KeywordDetailSideSheet.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Root = styled.main` 4 | display: flex; 5 | flex-direction: column; 6 | gap: 24px; 7 | 8 | width: 100%; 9 | padding: 0 30px; 10 | `; 11 | 12 | export const DescriptionSection = styled.section` 13 | display: flex; 14 | flex-direction: column; 15 | gap: 16px; 16 | `; 17 | 18 | export const QuizSection = styled.section` 19 | display: flex; 20 | flex-direction: column; 21 | gap: 16px; 22 | 23 | ol { 24 | display: flex; 25 | flex-direction: column; 26 | gap: 6px; 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /frontend/src/components/LabelledImage/LabelledImage.stories.js: -------------------------------------------------------------------------------- 1 | import LabelledImage from './LabelledImage'; 2 | import SomeImage from '../../assets/images/background-image.png'; 3 | 4 | export default { 5 | title: 'Component/LabelledImage', 6 | component: LabelledImage, 7 | argTypes: {}, 8 | }; 9 | 10 | const Template = (args) => ; 11 | 12 | export const Selected = Template.bind({}); 13 | 14 | Selected.args = { 15 | src: SomeImage, 16 | alt: '이미지', 17 | text: 'JavaScript', 18 | isSelected: true, 19 | }; 20 | 21 | export const UnSelected = Template.bind({}); 22 | 23 | UnSelected.args = { 24 | src: SomeImage, 25 | alt: '이미지', 26 | text: 'JavaScript', 27 | isSelected: false, 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/components/Lists/LevellogList.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | export const NoDefaultHoverLink = styled(Link)` 5 | :hover { 6 | font-weight: unset; 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /frontend/src/components/Lists/LevellogList.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource @emotion/react */ 2 | import { css } from '@emotion/react'; 3 | import { PATH } from '../../constants'; 4 | 5 | import LevellogItem from '../Items/LevellogItem'; 6 | import { NoDefaultHoverLink } from './LevellogList.styles'; 7 | 8 | const LevellogList = ({ levellogs }) => { 9 | return ( 10 |
    li:not(:last-child) { 13 | margin-bottom: 1.6rem; 14 | } 15 | `} 16 | > 17 | {levellogs.map((levellog) => ( 18 |
  • 19 | 20 | 21 | 22 |
  • 23 | ))} 24 |
25 | ); 26 | }; 27 | 28 | export default LevellogList; 29 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/Modal.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { COLOR } from '../../constants'; 3 | import { ModalProps } from './Modal'; 4 | 5 | const ModalSection = styled.section` 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | 10 | width: 100%; 11 | height: 100vh; 12 | 13 | background-color: ${COLOR.BLACK_OPACITY_100}; 14 | `; 15 | 16 | const ModalInner = styled.article` 17 | position: absolute; 18 | width: ${({ width }) => width ?? '50%'}; 19 | height: ${({ height }) => height ?? '50%'}; 20 | left: 0; 21 | top: 0; 22 | 23 | border: 1px solid ${COLOR.LIGHT_GRAY_400}; 24 | background-color: ${COLOR.WHITE}; 25 | border-radius: 0.5rem; 26 | box-shadow: 0px 2px 4px 0px ${COLOR.BLACK_OPACITY_300}; 27 | `; 28 | 29 | export { ModalSection, ModalInner }; 30 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes } from 'react'; 2 | import { ModalSection, ModalInner } from './Modal.styles'; 3 | 4 | export interface ModalProps extends HTMLAttributes { 5 | width: string; 6 | height: string; 7 | } 8 | 9 | const Modal = ({ children, width, height }: ModalProps) => { 10 | return ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default Modal; 20 | -------------------------------------------------------------------------------- /frontend/src/components/NavBar/Navbar.stories.js: -------------------------------------------------------------------------------- 1 | import NavBar from './NavBar'; 2 | 3 | export default { 4 | title: 'Component/NavBar', 5 | component: NavBar, 6 | argTypes: { children: { control: 'text' } }, 7 | }; 8 | 9 | const Template = (args) => ; 10 | 11 | export const Basic = Template.bind({}); 12 | 13 | Basic.args = { 14 | children: ( 15 | <> 16 | 17 | 18 | 19 | 20 | ), 21 | onLogoClick: () => {}, 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/components/NotFound/NotFound.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | const Container = styled.div` 4 | position: fixed; 5 | top: 6.4rem; 6 | left: 0; 7 | width: 100%; 8 | display: flex; 9 | justify-content: flex-end; 10 | align-items: center; 11 | `; 12 | 13 | const Image = styled.img` 14 | width: 100%; 15 | `; 16 | 17 | export { Container, Image }; 18 | -------------------------------------------------------------------------------- /frontend/src/components/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import Lottie from 'react-lottie'; 2 | import { NotFoundAnimation } from '../../assets/lotties'; 3 | import { Container } from './NotFound.styles'; 4 | 5 | const NotFound = () => { 6 | const defaultOptions = { 7 | height: '100%', 8 | loop: true, 9 | autoplay: true, 10 | animationData: NotFoundAnimation, 11 | rendererSettings: { 12 | preserveAspectRatio: 'xMidYMid slice', 13 | }, 14 | }; 15 | return ( 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default NotFound; 23 | -------------------------------------------------------------------------------- /frontend/src/components/Reaction/Like.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { COLOR } from '../../constants'; 3 | 4 | export const LikeIconStyle = css` 5 | padding: 0; 6 | width: fit-content; 7 | font-size: 1.4rem; 8 | margin-left: 1rem; 9 | margin-right: 1rem; 10 | height: inherit; 11 | 12 | background-color: transparent; 13 | color: ${COLOR.DARK_GRAY_400}; 14 | 15 | & > img { 16 | margin-right: 0; 17 | width: 2.4rem; 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /frontend/src/components/Reaction/Scrap.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { COLOR } from '../../enumerations/color'; 3 | 4 | export const DefaultScrapButtonStyle = css` 5 | flex-direction: column; 6 | padding: 0; 7 | width: fit-content; 8 | font-size: 1.4rem; 9 | height: inherit; 10 | 11 | background-color: transparent; 12 | color: ${COLOR.DARK_GRAY_400}; 13 | 14 | & > img { 15 | margin-right: 0; 16 | width: 2.4rem; 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /frontend/src/components/SelectBox/SelectBox.stories.js: -------------------------------------------------------------------------------- 1 | import SelectBox from './SelectBox'; 2 | 3 | export default { 4 | title: 'Component/SelectBox', 5 | component: SelectBox, 6 | }; 7 | 8 | const Template = (args) => ; 9 | 10 | export const Basic = Template.bind({}); 11 | 12 | Basic.args = { 13 | options: ['디토', '티케', '엘라', '서니', '브라운', '포코', '포모', '피케이', '소롱', '웨지'], 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/components/StudylogEditor/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import COLOR from '../../constants/color'; 3 | 4 | const Card = styled.div` 5 | padding: 2.5rem; 6 | background-color: ${COLOR.WHITE}; 7 | border-radius: 8px; 8 | border: 1px solid ${COLOR.LIGHT_GRAY_50}; 9 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 10 | `; 11 | 12 | const SectionName = styled.h3` 13 | margin-top: 1rem; 14 | margin-bottom: 2rem; 15 | font-weight: 600; 16 | font-size: 1.5rem; 17 | color: ${COLOR.DARK_GRAY_600}; 18 | `; 19 | 20 | export { Card, SectionName }; 21 | -------------------------------------------------------------------------------- /frontend/src/components/Tag/Tag.tsx: -------------------------------------------------------------------------------- 1 | import { Container, PostCount } from './Tag.styles'; 2 | 3 | interface Props { 4 | id: number; 5 | name: string; 6 | postCount: number; 7 | selectedTagId: number; 8 | onClick: () => void; 9 | } 10 | 11 | const Tag = ({ id, name, postCount, selectedTagId, onClick }: Props) => ( 12 | 13 | #{name} {postCount} 14 | 15 | ); 16 | 17 | export default Tag; -------------------------------------------------------------------------------- /frontend/src/configs/environment.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL = process.env.REACT_APP_API_URL; 2 | 3 | export const APP_MODE = process.env.REACT_APP_MODE; 4 | 5 | export const isLocal = process.env.REACT_APP_MODE === 'LOCAL'; 6 | 7 | export const isDev = process.env.REACT_APP_MODE === 'DEV'; 8 | 9 | export const isProd = process.env.REACT_APP_MODE === 'PROD'; 10 | -------------------------------------------------------------------------------- /frontend/src/constants/errorCode.ts: -------------------------------------------------------------------------------- 1 | const ERROR_CODE = { 2 | EXPIRED_ACCESS_TOKEN: 1002, 3 | NON_EXISTENT_MEMBER: 1004, 4 | NO_CONTENT: 2001, 5 | NO_TITLE: 2002, 6 | 7 | NOT_EXIST_LEVELLOG: 7002, 8 | 9 | SERVER_ERROR: -9999, 10 | }; 11 | 12 | export default ERROR_CODE; 13 | -------------------------------------------------------------------------------- /frontend/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PATH } from './path'; 2 | export { default as PROFILE_PAGE_MENU } from './profilePageMenu'; 3 | export { 4 | CONFIRM_MESSAGE, 5 | ALERT_MESSAGE, 6 | PLACEHOLDER, 7 | SNACKBAR_MESSAGE, 8 | ERROR_MESSAGE, 9 | } from './message'; 10 | export { POST_TITLE, PROFILE, CHART, REPORT_DESCRIPTION } from './input'; 11 | export { default as COLOR } from './color'; 12 | export { REQUEST_REPORT_TYPE } from './requestType'; 13 | -------------------------------------------------------------------------------- /frontend/src/constants/input.ts: -------------------------------------------------------------------------------- 1 | const POST_TITLE = { 2 | MIN_LENGTH: '1', 3 | MAX_LENGTH: '50', 4 | }; 5 | 6 | const PROFILE = { 7 | NICKNAME: { 8 | MIN_LENGTH: 1, 9 | MAX_LENGTH: 4, 10 | }, 11 | }; 12 | 13 | const CHART = { 14 | ABILITY_WEIGHT: { 15 | MIN: 1, 16 | MAX: 10, 17 | }, 18 | }; 19 | 20 | const REPORT_DESCRIPTION = { 21 | MIN_LENGTH: '1', 22 | MAX_LENGTH: '150', 23 | }; 24 | 25 | export { POST_TITLE, PROFILE, CHART, REPORT_DESCRIPTION }; 26 | -------------------------------------------------------------------------------- /frontend/src/constants/localStorage.ts: -------------------------------------------------------------------------------- 1 | const LOCAL_STORAGE_KEY = { 2 | ACCESS_TOKEN: 'accessToken', 3 | }; 4 | 5 | export default LOCAL_STORAGE_KEY; 6 | -------------------------------------------------------------------------------- /frontend/src/constants/path.ts: -------------------------------------------------------------------------------- 1 | const PATH = { 2 | ROOT: '/', 3 | PROFILE: '/:username', 4 | PROFILE_STUDYLOGS: '/:username/studylogs', 5 | PROFILE_SCRAPS: '/:username/scraps', 6 | PROFILE_ACCOUNT: '/:username/account', 7 | LOGIN_CALLBACK: '/login/callback', 8 | STUDYLOG: '/studylogs', 9 | NEW_STUDYLOG: '/studylog/write', 10 | LEVELLOG: '/levellogs', 11 | NEW_LEVELLOG: '/levellog/write', 12 | ABILITY: '/:username/ability', 13 | ROADMAP: '/roadmap', 14 | ARTICLE: '/article', 15 | NEW_ESSAY_ANSWER: '/quizzes/:quizId/essay-answers/form', 16 | ESSAY_ANSWER: '/essay-answers/:essayAnswerId', 17 | ESSAY_ANSWER_LIST: '/quizzes/:quizId/essay-answers', 18 | NEW_ARTICLE: '/article/write', 19 | INTERVIEW: '/interview' 20 | }; 21 | 22 | export default PATH; 23 | -------------------------------------------------------------------------------- /frontend/src/constants/profilePageMenu.ts: -------------------------------------------------------------------------------- 1 | const PROFILE_PAGE_MENU = { 2 | OVERVIEW: 'oveview', 3 | SCRAPS: 'scraps', 4 | STUDYLOGS: 'studylogs', 5 | }; 6 | 7 | export default PROFILE_PAGE_MENU; 8 | -------------------------------------------------------------------------------- /frontend/src/constants/reactQueryKey.ts: -------------------------------------------------------------------------------- 1 | const REACT_QUERY_KEY = { 2 | STUDYLOG: 'STUDYLOG', 3 | QUIZ: 'QUIZ', 4 | QUIZZES: 'QUIZZES', 5 | ESSAY_ANSWER: 'ESSAY_ANSWER', 6 | QUIZ_ANSWERS: 'QUIZ_ANSWERS', 7 | ESSAY_ANSWER_FILTER_LIST: 'ESSAY_ANSWER_FILTER_LIST', 8 | }; 9 | 10 | export default REACT_QUERY_KEY; 11 | -------------------------------------------------------------------------------- /frontend/src/constants/requestType.ts: -------------------------------------------------------------------------------- 1 | export const REQUEST_REPORT_TYPE = { 2 | ALL: 'all', 3 | SIMPLE: 'simple', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/constants/screenBreakpoints.ts: -------------------------------------------------------------------------------- 1 | export type ScreenBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; 2 | 3 | const SCREEN_BREAKPOINT = { 4 | xs: 420, 5 | sm: 640, 6 | md: 768, 7 | lg: 1024, 8 | xl: 1280, 9 | } as const; 10 | 11 | export default SCREEN_BREAKPOINT; 12 | -------------------------------------------------------------------------------- /frontend/src/enumerations/path.ts: -------------------------------------------------------------------------------- 1 | export enum PATH { 2 | ROOT = '/', 3 | PROFILE = '/=username', 4 | PROFILE_STUDYLOGS = '/=username/studylogs', 5 | PROFILE_SCRAPS = '/=username/scraps', 6 | PROFILE_ACCOUNT = '/=username/account', 7 | PROFILE_REPORTS = '/=username/reports', 8 | PROFILE_REPORT = '/=username/reports/=reportId', 9 | PROFILE_NEW_REPORT = '/=username/reports/write', 10 | LOGIN_CALLBACK = '/login/callback', 11 | NEW_STUDYLOG = '/studylog/write', 12 | ABILITY = '/=username/ability', 13 | STUDYLOGS = '/studylogs', 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/hooks/Studylog/useTempSavedStudylog.ts: -------------------------------------------------------------------------------- 1 | import { TempSavedStudyLogForm } from '../../models/Studylogs'; 2 | import { useCreateTempSavedStudylog, useFetchTempSavedStudylog } from './../queries/studylog'; 3 | 4 | export default function useTempSavedStudylog() { 5 | const { 6 | data: tempSavedStudylog, 7 | remove: removeCachedTempSavedStudylog, 8 | } = useFetchTempSavedStudylog(); 9 | 10 | const createTempSavedStudylogMutation = useCreateTempSavedStudylog(); 11 | 12 | const createTempSavedStudylog = (body: TempSavedStudyLogForm) => { 13 | createTempSavedStudylogMutation.mutate(body); 14 | }; 15 | 16 | return { 17 | tempSavedStudylog, 18 | createTempSavedStudylog, 19 | removeCachedTempSavedStudylog, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/hooks/queries/auth.ts: -------------------------------------------------------------------------------- 1 | import { useMutation } from 'react-query'; 2 | import { loginRequest } from '../../service/requests'; 3 | 4 | export const useLogin = ({ onSuccess }) => 5 | useMutation( 6 | async ({ code }) => { 7 | const response = await loginRequest({ code }); 8 | 9 | return response.data; 10 | }, 11 | { 12 | onSuccess: ({ accessToken }) => { 13 | onSuccess(accessToken); 14 | }, 15 | onError: (error) => { 16 | if (error instanceof Error) { 17 | alert(error.message); 18 | } 19 | }, 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /frontend/src/hooks/queries/curriculum.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query'; 2 | import { getCurriculums } from '../../apis/curriculum'; 3 | 4 | const QUERY_KEY = { 5 | curriculum: 'curriculum', 6 | }; 7 | 8 | export const useGetCurriculums = () => { 9 | const { data, isLoading } = useQuery([QUERY_KEY.curriculum], () => getCurriculums()); 10 | 11 | return { 12 | curriculums: data?.data, 13 | isLoading, 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/hooks/queries/report.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from 'react-query'; 2 | import { requestGetMatchedStudylogs } from '../../service/requests'; 3 | 4 | const QUERY_KEY = { 5 | matchedStudylogs: 'matchedStudylogs', 6 | }; 7 | 8 | export const useGetMatchedStudylogs = ({ accessToken, startDate, endDate }) => { 9 | const fetchMatchedStudylogs = async () => { 10 | const res = await requestGetMatchedStudylogs({ accessToken, startDate, endDate }); 11 | return res.data; 12 | }; 13 | 14 | return useQuery([QUERY_KEY.matchedStudylogs], fetchMatchedStudylogs, { enabled: false }); 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/hooks/useBeforeunload.ts: -------------------------------------------------------------------------------- 1 | import { Editor as ToastEditor } from '@toast-ui/react-editor'; 2 | import { useEffect, MutableRefObject } from 'react'; 3 | 4 | const useBeforeunload = (editorContentRef: MutableRefObject) => { 5 | useEffect(() => { 6 | window.addEventListener('beforeunload', (event) => { 7 | const content = editorContentRef.current?.getInstance().getMarkdown() || ''; 8 | 9 | if (content) { 10 | event.preventDefault(); 11 | event.returnValue = ''; 12 | } 13 | }); 14 | }, []); 15 | }; 16 | 17 | export default useBeforeunload; 18 | -------------------------------------------------------------------------------- /frontend/src/hooks/useFetch.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | const useFetch = (defaultValue, callback) => { 4 | const [response, setResponse] = useState(defaultValue); 5 | const [error, setError] = useState(''); 6 | 7 | const fetchData = async () => { 8 | try { 9 | const response = await callback(); 10 | const json = response.data; 11 | 12 | setResponse(json); 13 | } catch (error) { 14 | const errorResponse = error.response; 15 | 16 | console.error(errorResponse); 17 | setError(errorResponse); 18 | } 19 | }; 20 | 21 | useEffect(() => { 22 | fetchData(); 23 | }, []); 24 | 25 | return [response, error]; 26 | }; 27 | 28 | export default useFetch; 29 | -------------------------------------------------------------------------------- /frontend/src/hooks/useImage.ts: -------------------------------------------------------------------------------- 1 | import { requestImageUpload } from '../service/requests'; 2 | import useSnackBar from './useSnackBar'; 3 | 4 | const useImage = () => { 5 | const { openSnackBar } = useSnackBar(); 6 | 7 | const uploadImage = async (blob, callback) => { 8 | if (blob.size > 1024 * 1024 * 10) return openSnackBar('10MB 이하의 이미지를 업로드해주세요.'); 9 | 10 | try { 11 | const imageData = new FormData(); 12 | imageData.append('file', blob); 13 | 14 | const response = await requestImageUpload(imageData); 15 | 16 | const { imageUrl } = await response.data; 17 | 18 | callback(imageUrl, blob.name); 19 | } catch (error) { 20 | console.error(error); 21 | openSnackBar('해당 이미지를 업로드 할 수 없습니다.'); 22 | } 23 | }; 24 | 25 | return { uploadImage }; 26 | }; 27 | 28 | export default useImage; 29 | -------------------------------------------------------------------------------- /frontend/src/hooks/useNotFound.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { NotFound } from '../components'; 3 | 4 | const useNotFound = () => { 5 | const [isNotFound, setNotFound] = useState(false); 6 | 7 | return { isNotFound, setNotFound, NotFound }; 8 | }; 9 | 10 | export default useNotFound; 11 | -------------------------------------------------------------------------------- /frontend/src/hooks/useScrollToSelected.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useScrollToSelected = ({ container, dependency, options, selectedOption }) => { 4 | useEffect(() => { 5 | const target = container.current; 6 | if (!target) return; 7 | 8 | const scrollY = 9 | (target.scrollHeight / options.length) * 10 | options.indexOf(options.find((option) => option.name === selectedOption)); 11 | target.scroll({ top: scrollY, behavior: 'smooth' }); 12 | 13 | // eslint-disable-next-line react-hooks/exhaustive-deps 14 | }, [dependency]); 15 | }; 16 | 17 | export default useScrollToSelected; 18 | -------------------------------------------------------------------------------- /frontend/src/hooks/useValidParams.ts: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | 3 | type Params = { 4 | [key in Key]: string; 5 | }; 6 | 7 | export const useValidParams = (paramKeys: T[]): Readonly> => { 8 | const params = useParams>>(); 9 | const validParams = paramKeys.reduce((acc, key) => { 10 | if (!(key in params)) throw new Error(`Param '${key}' not found`); 11 | if (!params[key]) throw new Error('Invalid Params'); 12 | 13 | return { ...acc, [key]: params[key] }; 14 | }, {} as Readonly>); 15 | 16 | return validParams; 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/mocks/browser.ts: -------------------------------------------------------------------------------- 1 | import { setupWorker } from 'msw'; 2 | 3 | import { handlers } from './handlers'; 4 | 5 | export const worker = setupWorker(...handlers); 6 | -------------------------------------------------------------------------------- /frontend/src/mocks/db/comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "author": { 6 | "id": 1, 7 | "username": "euijinkk", 8 | "nickname": "잉", 9 | "imageUrl": "https://avatars.githubusercontent.com/u/24906022?v=4", 10 | "role": "crew" 11 | }, 12 | "content": "내용1", 13 | "createAt": "2022-07-24" 14 | }, 15 | { 16 | "id": 2, 17 | "author": { 18 | "id": 2, 19 | "username": "euijinkk", 20 | "nickname": "루키", 21 | "imageUrl": "https://avatars.githubusercontent.com/u/24906022?v=4", 22 | "role": "crew" 23 | }, 24 | "content": "내용2", 25 | "createAt": "2022-07-24" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/mocks/db/metaog.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "우아한테크코스(우테코) 프론트엔드 LEVEL 3-1~2주차/팀프로젝트 기획/팀워크/근로 프롤로그", 3 | "imageUrl": "https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmheC0%2Fbtsn254nadm%2FpxVtTxYhKVfTKoSJAeZN1K%2Fimg.png" 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/mocks/fixtures/curriculums.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | data: [ 3 | { 4 | id: 1, 5 | name: '2023 우아한테크코스 백엔드', 6 | }, 7 | { 8 | id: 2, 9 | name: '2023 우아한테크코스 프론트엔드', 10 | }, 11 | { 12 | id: 3, 13 | name: '2023 우아한테크코스 안드로이드', 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/mocks/handlers/index.ts: -------------------------------------------------------------------------------- 1 | import { articlesHandler } from './articles'; 2 | import { commentsHandler } from './comment'; 3 | import { essayAnswerHandler } from './essayAnswers'; 4 | import { roadmapHandler } from './roadmap'; 5 | import { levellogHandler } from './levellog'; 6 | import { popularStudyLogHandler } from './popularStudyLog'; 7 | 8 | export const handlers = [ 9 | ...commentsHandler, 10 | ...essayAnswerHandler, 11 | ...roadmapHandler, 12 | ...articlesHandler, 13 | ...levellogHandler, 14 | ...popularStudyLogHandler, 15 | ]; 16 | -------------------------------------------------------------------------------- /frontend/src/mocks/handlers/popularStudyLog.ts: -------------------------------------------------------------------------------- 1 | import { rest } from 'msw'; 2 | import { BASE_URL } from '../../configs/environment'; 3 | import popularStudyLog from '../db/popularStudyLog.json'; 4 | 5 | export const popularStudyLogHandler = [ 6 | rest.get(`${BASE_URL}/studylogs/popular`, (req, res, ctx) => { 7 | return res(ctx.status(200), ctx.json(popularStudyLog)); 8 | }), 9 | ]; 10 | -------------------------------------------------------------------------------- /frontend/src/models/Ability.ts: -------------------------------------------------------------------------------- 1 | export interface Ability { 2 | id: number; 3 | name: string; 4 | description: string; 5 | color: string; 6 | isParent: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/models/Comment.ts: -------------------------------------------------------------------------------- 1 | import { Author } from './Studylogs'; 2 | 3 | export interface CommentType { 4 | id: number; 5 | author: Author; 6 | content: string; 7 | createAt: string; 8 | } 9 | 10 | export interface CommentListResponse { 11 | data: CommentType[]; 12 | } 13 | 14 | export interface CommentRequest { 15 | content: string; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/models/Levellogs.ts: -------------------------------------------------------------------------------- 1 | import { Author } from './Studylogs'; 2 | 3 | export interface LevellogRequest { 4 | title: string; 5 | content: string; 6 | levelLogs: QnAType[]; 7 | } 8 | 9 | export interface LevellogResponse { 10 | id: number; 11 | title: string; 12 | content: string; 13 | levelLogs: QnAType[]; 14 | author: Author; 15 | createdAt: string; 16 | updatedAt: string; 17 | } 18 | 19 | export interface QnAType { 20 | id?: number; 21 | question: string; 22 | answer: string; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/models/filter.ts: -------------------------------------------------------------------------------- 1 | import type { Author } from './Studylogs'; 2 | 3 | interface FilterData { 4 | id: number; 5 | name: string; 6 | } 7 | 8 | export interface FilterResponse { 9 | sessions: FilterData[]; 10 | missions: unknown[]; 11 | tags: FilterData[]; 12 | members: Author[]; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/pages/EditStudylogPage/index.tsx: -------------------------------------------------------------------------------- 1 | import StudylogEditor from '../../components/StudylogEditor/StudylogEditor'; 2 | 3 | const EditStudylogPage = () => { 4 | return ; 5 | }; 6 | 7 | export default EditStudylogPage; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/EssayAnswerListPage/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import MEDIA_QUERY from '../../constants/mediaQuery'; 3 | 4 | const HeaderContainer = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | margin-bottom: 1.5rem; 8 | 9 | ${MEDIA_QUERY.xs} { 10 | margin-bottom: 0.8rem; 11 | } 12 | `; 13 | 14 | const PostListContainer = styled.div` 15 | display: grid; 16 | grid-row-gap: 2rem; 17 | word-break: break-all; 18 | `; 19 | 20 | export { HeaderContainer, PostListContainer }; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/InterviewPage/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './InterviewPage'; -------------------------------------------------------------------------------- /frontend/src/pages/LevellogListPage/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import MEDIA_QUERY from '../../constants/mediaQuery'; 3 | 4 | const HeaderContainer = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | margin-bottom: 1.5rem; 8 | 9 | ${MEDIA_QUERY.xs} { 10 | margin-bottom: 0.8rem; 11 | } 12 | `; 13 | 14 | const PostListContainer = styled.div` 15 | display: grid; 16 | grid-row-gap: 2rem; 17 | word-break: break-all; 18 | `; 19 | 20 | export { HeaderContainer, PostListContainer }; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/LevellogPage/QnAList.tsx: -------------------------------------------------------------------------------- 1 | import { S } from './QnAList.styles'; 2 | 3 | const QnAList = ({ QnAList }) => { 4 | return ( 5 | 6 | Q & A 7 | 8 | {QnAList.map((QnA) => ( 9 | 10 | {QnA.question} 11 | {QnA.answer} 12 | 13 | ))} 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default QnAList; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/NewStudylogPage/index.tsx: -------------------------------------------------------------------------------- 1 | import StudylogEditor from '../../components/StudylogEditor/StudylogEditor'; 2 | 3 | const NewStudylogPage = () => { 4 | return ; 5 | }; 6 | 7 | export default NewStudylogPage; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/ProfilePageAccount/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ProfilePageAccount = () => { 4 | return
account
; 5 | }; 6 | 7 | export default ProfilePageAccount; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/QuizAnswerListPage/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import MEDIA_QUERY from '../../constants/mediaQuery'; 3 | 4 | const HeaderContainer = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | margin-bottom: 1.5rem; 8 | 9 | ${MEDIA_QUERY.xs} { 10 | margin-bottom: 0.8rem; 11 | } 12 | `; 13 | 14 | const PostListContainer = styled.div` 15 | display: grid; 16 | grid-row-gap: 2rem; 17 | word-break: break-all; 18 | `; 19 | 20 | export { HeaderContainer, PostListContainer }; 21 | -------------------------------------------------------------------------------- /frontend/src/pages/RoadmapPage/RoadmapStyles.tsx: -------------------------------------------------------------------------------- 1 | import { css, Global } from '@emotion/react'; 2 | 3 | const RoadmapStyles = () => { 4 | return ( 5 | 16 | ); 17 | }; 18 | 19 | export default RoadmapStyles; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/RoadmapPage/components/ImportanceLegend/ImportanceLegend.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { HSL, hsl } from '../../colors'; 3 | 4 | export const Container = styled.div` 5 | display: flex; 6 | align-items: center; 7 | font-size: 10px; 8 | column-gap: 15px; 9 | margin-bottom: 20px; 10 | `; 11 | 12 | export const LegendItemList = styled.ul` 13 | display: flex; 14 | align-items: center; 15 | list-style: none; 16 | height: 16px; 17 | border-radius: 4px; 18 | overflow: hidden; 19 | border: 1px solid #bbbbbb; 20 | `; 21 | 22 | export const LegendItem = styled.li<{ $color: HSL }>` 23 | width: 40px; 24 | height: 100%; 25 | background: ${({ $color }) => hsl($color)}; 26 | `; 27 | -------------------------------------------------------------------------------- /frontend/src/pages/RoadmapPage/components/ImportanceLegend/ImportanceLegend.tsx: -------------------------------------------------------------------------------- 1 | import { ImportanceColors } from '../../colors'; 2 | import { Container, LegendItem, LegendItemList } from './ImportanceLegend.styles'; 3 | 4 | const ImportanceLegend = () => { 5 | return ( 6 | 7 |

중요도 범례

8 |

낮음

9 | 10 | {Object.entries(ImportanceColors).map(([importance, color]) => ( 11 | 12 | ))} 13 | 14 |

높음

15 |
16 | ); 17 | }; 18 | 19 | export default ImportanceLegend; 20 | -------------------------------------------------------------------------------- /frontend/src/pages/RoadmapPage/components/QuizProgress/QuizProgress.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const CircularProgress = styled.div<{ value: number }>` 4 | width: 16px; 5 | height: 16px; 6 | border-radius: 50%; 7 | background: conic-gradient( 8 | hsl(42, 100%, 45%) ${({ value }) => Math.round(value * 100)}%, 9 | #eaeaea 0 10 | ); 11 | box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.05); 12 | `; 13 | -------------------------------------------------------------------------------- /frontend/src/pages/RoadmapPage/styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Root = styled.div` 4 | display: flex; 5 | justify-content: center; 6 | margin: 25px 0px; 7 | `; 8 | 9 | export const Main = styled.main` 10 | display: flex; 11 | flex-direction: column; 12 | gap: 30px; 13 | width: 100%; 14 | max-width: 112rem; 15 | overflow-x: hidden; 16 | padding: 0 4rem; 17 | `; 18 | 19 | export const Title = styled.h2` 20 | margin-bottom: 10px; 21 | `; 22 | 23 | export const RoadmapContainer = styled.div` 24 | overflow-x: auto; 25 | margin: 0 -4rem; 26 | `; 27 | 28 | export const RoadmapHeader = styled.div` 29 | display: flex; 30 | justify-content: space-between; 31 | ` 32 | 33 | export const CurriculumButtonList = styled.div` 34 | display: flex; 35 | flex-wrap: wrap; 36 | gap: 8px; 37 | `; 38 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/redux/actions/snackBarAction.ts: -------------------------------------------------------------------------------- 1 | export const OPEN_SNACKBAR = 'snackBar/OPEN'; 2 | export const CLOSE_SNACKBAR = 'snackBar/CLOSE'; 3 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import snackBarReducer from './snackBarReducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | snackBar: snackBarReducer, 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/snackBarReducer.ts: -------------------------------------------------------------------------------- 1 | import { CLOSE_SNACKBAR, OPEN_SNACKBAR } from '../actions/snackBarAction'; 2 | export interface SnackBar { 3 | isSnackBarOpen: boolean; 4 | message: string; 5 | } 6 | 7 | const initialState: SnackBar = { 8 | isSnackBarOpen: false, 9 | message: '', 10 | }; 11 | 12 | const snackBarReducer = (state = initialState, action) => { 13 | switch (action.type) { 14 | case OPEN_SNACKBAR: 15 | return { 16 | ...state, 17 | isSnackBarOpen: true, 18 | message: action.payload, 19 | }; 20 | case CLOSE_SNACKBAR: 21 | return { 22 | ...state, 23 | isSnackBarOpen: false, 24 | message: 'action.payload', 25 | }; 26 | 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default snackBarReducer; 33 | -------------------------------------------------------------------------------- /frontend/src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore } from 'redux'; 2 | import rootReducer from './reducers'; 3 | import thunk from 'redux-thunk'; 4 | import { composeWithDevTools } from 'redux-devtools-extension'; 5 | 6 | const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk))); 7 | 8 | export default store; 9 | -------------------------------------------------------------------------------- /frontend/src/styles/layout.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | import { getSize } from '../utils/styles'; 3 | 4 | /** 5 | * 상하 간격 6 | * @param gap 간격 크기, number로 오는 경우 rem 단위로 변환 7 | */ 8 | export const getRowGapStyle = (gap: number | string) => css` 9 | /* > *:not(:last-child) { 10 | margin-bottom: ${getSize(gap)}; 11 | } */ 12 | row-gap: ${getSize(gap)}; 13 | `; 14 | 15 | /** 16 | * 좌우 간격 17 | * @param gap 간격 크기, number로 오는 경우 rem 단위로 변환 18 | */ 19 | export const getColumnGapStyle = (gap: number | string) => css` 20 | > *:not(:last-child) { 21 | margin-right: ${getSize(gap)}; 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /frontend/src/styles/reset.styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/react'; 2 | 3 | export const ResetScrollBar = css` 4 | ::-webkit-scrollbar { 5 | width: auto; 6 | } 7 | 8 | ::-webkit-scrollbar-track { 9 | -webkit-box-shadow: auto; 10 | -webkit-border-radius: auto; 11 | border-radius: auto; 12 | background: auto; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb { 16 | -webkit-border-radius: auto; 17 | border-radius: auto; 18 | background: auto; 19 | -webkit-box-shadow: auto; 20 | } 21 | 22 | ::-webkit-scrollbar-thumb:window-inactive { 23 | background: auto; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /frontend/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | type ValueOf = T[keyof T]; 2 | type Nullable = { [P in keyof T]: T[P] | null }; 3 | 4 | export type { ValueOf, Nullable }; 5 | -------------------------------------------------------------------------------- /frontend/src/utils/arrayMutation.ts: -------------------------------------------------------------------------------- 1 | export const arrayMutation = >(arr: T, method: keyof T) => { 2 | switch (method) { 3 | case 'includes': 4 | return (arg: unknown): boolean => arr.includes(arg); 5 | default: 6 | throw new Error('Invalid method'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/utils/canvas.ts: -------------------------------------------------------------------------------- 1 | import { COLOR } from '../constants'; 2 | 3 | const canvasInit = (canvasElement: HTMLCanvasElement, backgroundColor = COLOR.WHITE) => { 4 | const context = canvasElement.getContext('2d'); 5 | 6 | if (!context) { 7 | return; 8 | } 9 | 10 | const width = canvasElement.width; 11 | const height = canvasElement.height; 12 | 13 | context.fillStyle = backgroundColor; 14 | context.fillRect(0, 0, width, height); 15 | }; 16 | 17 | export { canvasInit }; 18 | -------------------------------------------------------------------------------- /frontend/src/utils/colors.ts: -------------------------------------------------------------------------------- 1 | const getGrayscaleColor = (hexColor: string) => { 2 | const [red, green, blue] = Array.from({ length: 3 }).map((_, index) => 3 | Number.parseInt(hexColor.slice(2 * index + 1, 2 * index + 3), 16) 4 | ); 5 | const grayscaleCode = Math.floor((red + green + blue) / 3).toString(16); 6 | 7 | return '#' + grayscaleCode.repeat(3); 8 | }; 9 | 10 | export { getGrayscaleColor }; 11 | -------------------------------------------------------------------------------- /frontend/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | const debounce = (() => { 2 | let id = 0; 3 | 4 | return (callback: (...args: unknown[]) => void, ms?: number) => { 5 | if (id) { 6 | window.clearTimeout(id); 7 | } 8 | 9 | id = window.setTimeout(callback, ms); 10 | }; 11 | })(); 12 | 13 | export default debounce; 14 | -------------------------------------------------------------------------------- /frontend/src/utils/filteringList.ts: -------------------------------------------------------------------------------- 1 | const filterOnlyNewList = (list, conditionList) => { 2 | return list.filter((item) => !conditionList.map((filterItem) => filterItem.id).includes(item.id)); 3 | }; 4 | 5 | const filterIds = (list) => { 6 | return list.map((item) => item.id); 7 | }; 8 | 9 | export { filterOnlyNewList, filterIds }; 10 | -------------------------------------------------------------------------------- /frontend/src/utils/object.ts: -------------------------------------------------------------------------------- 1 | export const isEmptyObject = (value: object): boolean => 2 | Object.keys(value).length === 0 && value.constructor === Object; 3 | 4 | export function getKeyByValue(object: object, value: unknown) { 5 | return Object.keys(object).find((key) => object[key] === value); 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/utils/response.js: -------------------------------------------------------------------------------- 1 | const getResponseData = (response) => { 2 | const contentType = response.headers['content-type']; 3 | 4 | if (contentType && contentType.includes('application/json')) { 5 | return response.data; 6 | } 7 | 8 | return response.data.toString(); 9 | }; 10 | 11 | export { getResponseData }; 12 | -------------------------------------------------------------------------------- /frontend/src/utils/textColorPicker.js: -------------------------------------------------------------------------------- 1 | import { COLOR } from '../constants'; 2 | 3 | function getTextColor(hexColor) { 4 | if (!hexColor) { 5 | return `${COLOR.BLACK_900}`; 6 | } 7 | 8 | const rgb = parseInt(hexColor.substring(1), 16); 9 | 10 | const r = (rgb >> 16) & 0xff; 11 | const g = (rgb >> 8) & 0xff; 12 | const b = (rgb >> 0) & 0xff; 13 | 14 | const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; 15 | 16 | return luma < 127.5 ? `${COLOR.WHITE}` : `${COLOR.BLACK_900}`; 17 | } 18 | 19 | export { getTextColor }; 20 | -------------------------------------------------------------------------------- /frontend/src/utils/toggleCheckbox.js: -------------------------------------------------------------------------------- 1 | const onToggleCheckbox = (checkboxList, item) => { 2 | const checkboxIds = checkboxList.map((checkItem) => checkItem.id); 3 | 4 | if (checkboxIds.includes(item.id)) { 5 | const index = checkboxIds.indexOf(item.id); 6 | 7 | return [...checkboxList.slice(0, index), ...checkboxList.slice(index + 1)]; 8 | } else { 9 | return [item, ...checkboxList]; 10 | } 11 | }; 12 | 13 | export { onToggleCheckbox }; 14 | -------------------------------------------------------------------------------- /frontend/src/utils/validator.ts: -------------------------------------------------------------------------------- 1 | export const limitLetterLength = (words: string, limitedLength: number) => { 2 | if (words.length > limitedLength) return false; 3 | 4 | return true; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "noImplicitAny": false 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse/prolog/39e2e52d40f4793c2db557b710ac9ff5560b25a3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "prolog" 2 | 3 | include 'backend' 4 | -------------------------------------------------------------------------------- /terraform/environments/dev/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | cloud { 3 | organization = "cholog" 4 | 5 | workspaces { 6 | name = "cholog-dev" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /terraform/environments/dev/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "5.54.1" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = var.region 12 | 13 | /** Note: 14 | AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY는 환경변수를 통해 설정해야 합니다. 15 | 아래와 같이 환경 변수를 설정하세요: 16 | 17 | export AWS_ACCESS_KEY_ID="your-access-key" 18 | export AWS_SECRET_ACCESS_KEY="your-secret-key" 19 | 20 | GitHub Actions에서는 환경 변수를 Secrets에 저장하여 사용해야 합니다. 21 | */ 22 | } 23 | -------------------------------------------------------------------------------- /terraform/environments/dev/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | default = "ap-northeast-2" 3 | } 4 | variable "project_name" { 5 | default = "prolog-dev" 6 | } 7 | variable "environment" { 8 | default = "dev" 9 | } 10 | variable "bucket_name" { 11 | default = "prolog-dev-bucket" 12 | } 13 | variable "key_pair_name" { 14 | default = "prolog-dev" 15 | } 16 | variable "db_name" { 17 | default = "prolog" 18 | } 19 | variable "db_secret_name" { 20 | default = "secrets/prolog_dev" 21 | } 22 | -------------------------------------------------------------------------------- /terraform/environments/prod/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | cloud { 3 | organization = "cholog" 4 | 5 | workspaces { 6 | name = "cholog-prod" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /terraform/environments/prod/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "5.54.1" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = var.region 12 | 13 | /** Note: 14 | AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY는 환경변수를 통해 설정해야 합니다. 15 | 아래와 같이 환경 변수를 설정하세요: 16 | 17 | export AWS_ACCESS_KEY_ID="your-access-key" 18 | export AWS_SECRET_ACCESS_KEY="your-secret-key" 19 | 20 | GitHub Actions에서는 환경 변수를 Secrets에 저장하여 사용해야 합니다. 21 | */ 22 | } 23 | -------------------------------------------------------------------------------- /terraform/environments/prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | default = "ap-northeast-2" 3 | } 4 | variable "project_name" { 5 | default = "prolog-prod" 6 | } 7 | variable "environment" { 8 | default = "prod" 9 | } 10 | variable "bucket_name" { 11 | default = "prolog-prod-bucket" 12 | } 13 | variable "key_pair_name" { 14 | default = "prolog-prod" 15 | } 16 | variable "db_name" { 17 | default = "prolog" 18 | } 19 | variable "db_secret_name" { 20 | default = "secrets/prolog_prod" 21 | } 22 | -------------------------------------------------------------------------------- /terraform/modules/application/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" {} 2 | variable "project_name" {} 3 | variable "environment" {} 4 | variable "ec2_role_name" {} 5 | variable "bucket_name" {} 6 | variable "region" {} 7 | variable "code_deploy_role_arn" {} 8 | variable "ami_id" {} 9 | variable "key_pair_name" {} 10 | variable "bastion_sg_id" {} 11 | variable "private_subnet_ids" { 12 | type = list(string) 13 | } 14 | variable "public_subnet_ids" { 15 | type = list(string) 16 | } 17 | variable "service_worker_tags" { 18 | type = map(string) 19 | } 20 | variable "server_tags" { 21 | type = map(string) 22 | } 23 | -------------------------------------------------------------------------------- /terraform/modules/bastion/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" {} 2 | variable "project_name" {} 3 | variable "ami_id" {} 4 | variable "key_pair_name" {} 5 | variable "public_subnet_ids" { 6 | type = list(string) 7 | } 8 | variable "server_tags" { 9 | type = map(string) 10 | } 11 | -------------------------------------------------------------------------------- /terraform/modules/compute/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_ami" "ec2_ami" { 2 | most_recent = true 3 | owners = ["amazon"] 4 | filter { 5 | name = "name" 6 | values = ["amzn2-ami-hvm-*-arm64-gp2"] 7 | } 8 | } 9 | 10 | resource "tls_private_key" "private_key" { 11 | algorithm = "RSA" 12 | rsa_bits = 2048 13 | } 14 | 15 | resource "aws_key_pair" "key_pair" { 16 | key_name = "${var.project_name}_key_pair" 17 | public_key = tls_private_key.private_key.public_key_openssh 18 | 19 | // Note: Tag not required 20 | } 21 | 22 | resource "local_file" "private_key" { 23 | filename = "${path.root}/keys/private_key.pem" 24 | content = tls_private_key.private_key.private_key_pem 25 | file_permission = "0600" 26 | } 27 | -------------------------------------------------------------------------------- /terraform/modules/compute/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_name" {} 2 | -------------------------------------------------------------------------------- /terraform/modules/database/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" {} 2 | variable "project_name" {} 3 | variable "db_name" {} 4 | variable "secret_name" {} 5 | variable "ingress_security_group_ids" { 6 | type = list(string) 7 | } 8 | variable "private_subnet_ids" { 9 | type = list(string) 10 | } 11 | variable "server_tags" { 12 | type = map(string) 13 | } 14 | variable "database_tags" { 15 | type = map(string) 16 | } 17 | -------------------------------------------------------------------------------- /terraform/modules/iam/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_name" {} 2 | 3 | variable "bucket_arns" { 4 | type = list(string) 5 | } 6 | -------------------------------------------------------------------------------- /terraform/modules/network/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" {} 2 | variable "project_name" {} 3 | variable "server_tags" { 4 | type = map(string) 5 | } 6 | variable "gateway_tags" { 7 | type = map(string) 8 | } 9 | -------------------------------------------------------------------------------- /terraform/modules/secret/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_secretsmanager_secret" "secret" { 2 | name = var.secret_name 3 | } 4 | 5 | data "aws_secretsmanager_secret_version" "secret_version" { 6 | secret_id = data.aws_secretsmanager_secret.secret.id 7 | } 8 | -------------------------------------------------------------------------------- /terraform/modules/secret/outputs.tf: -------------------------------------------------------------------------------- 1 | output "value" { 2 | value = jsondecode(data.aws_secretsmanager_secret_version.secret_version.secret_string) 3 | } 4 | -------------------------------------------------------------------------------- /terraform/modules/secret/variables.tf: -------------------------------------------------------------------------------- 1 | variable "secret_name" {} 2 | -------------------------------------------------------------------------------- /terraform/modules/storage/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "bucket" { 2 | bucket = var.bucket_name 3 | force_destroy = true 4 | 5 | tags = merge(var.storage_tags, { 6 | Name = "${var.project_name}_bucket" 7 | }) 8 | } 9 | 10 | resource "aws_s3_bucket_public_access_block" "public_access" { 11 | bucket = aws_s3_bucket.bucket.id 12 | 13 | block_public_acls = true 14 | block_public_policy = true 15 | ignore_public_acls = true 16 | restrict_public_buckets = true 17 | } 18 | -------------------------------------------------------------------------------- /terraform/modules/storage/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_name" { 2 | description = "The name of the S3 bucket" 3 | value = aws_s3_bucket.bucket.id 4 | } 5 | 6 | output "bucket_arn" { 7 | description = "The ARN of the S3 bucket" 8 | value = aws_s3_bucket.bucket.arn 9 | } 10 | 11 | output "public_access_block_id" { 12 | description = "The ID of the S3 bucket public access block configuration" 13 | value = aws_s3_bucket_public_access_block.public_access.id 14 | } 15 | -------------------------------------------------------------------------------- /terraform/modules/storage/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bucket_name" {} 2 | variable "project_name" {} 3 | variable "storage_tags" { 4 | type = map(string) 5 | } 6 | -------------------------------------------------------------------------------- /terraform/modules/tags/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | common_tags = { 3 | Service = var.project_name, 4 | Environment = var.environment 5 | } 6 | server_tags = merge(local.common_tags, { 7 | Role = "${var.project_name}-server" 8 | }) 9 | gateway_tags = merge(local.common_tags, { 10 | Role = "${var.project_name}-gateway" 11 | }) 12 | storage_tags = merge(local.common_tags, { 13 | Role = "${var.project_name}-storage" 14 | }) 15 | database_tags = merge(local.common_tags, { 16 | Role = "${var.project_name}-db" 17 | }) 18 | service_worker_tags = merge(local.common_tags, { 19 | Role = "service-worker" 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /terraform/modules/tags/outputs.tf: -------------------------------------------------------------------------------- 1 | output "common_tags" { 2 | description = "Common tags for all resources" 3 | value = local.common_tags 4 | } 5 | 6 | output "server_tags" { 7 | description = "Tags for server resources" 8 | value = local.server_tags 9 | } 10 | 11 | output "gateway_tags" { 12 | description = "Tags for gateway resources" 13 | value = local.gateway_tags 14 | } 15 | 16 | output "storage_tags" { 17 | description = "Tags for storage resources" 18 | value = local.storage_tags 19 | } 20 | 21 | output "database_tags" { 22 | description = "Tags for database resources" 23 | value = local.database_tags 24 | } 25 | 26 | output "service_worker_tags" { 27 | description = "Tags for service worker resources" 28 | value = local.service_worker_tags 29 | } 30 | -------------------------------------------------------------------------------- /terraform/modules/tags/variables.tf: -------------------------------------------------------------------------------- 1 | variable "project_name" {} 2 | variable "environment" {} 3 | --------------------------------------------------------------------------------