├── client ├── .gitignore ├── @types │ ├── dompurify │ │ └── index.d.ts │ └── quill-image-resize │ │ └── index.d.ts ├── react-app-env.d.ts ├── globals.css ├── styles │ └── globals.css ├── public │ ├── kakao.png │ ├── favicon.ico │ ├── morak-main-1.png │ ├── morak-main-2.png │ ├── morak-main-3.png │ └── vercel.svg ├── postcss.config.js ├── atoms │ ├── loginAtom.ts │ ├── keywordAtom.ts │ ├── commentAtom.ts │ ├── renderingAtom.ts │ ├── answerAtom.ts │ ├── articleAtom.ts │ ├── reviewAtom.ts │ └── userAtom.ts ├── types │ ├── comment.ts │ ├── pageInfo.ts │ ├── login.ts │ ├── calendar.ts │ ├── answer.ts │ ├── base.ts │ ├── user.ts │ └── article.ts ├── libs │ ├── changeTagPrettier.ts │ ├── getIsFromDashboard.ts │ ├── jobOptions.ts │ ├── changeGradeEmoji.ts │ ├── useFetchSWR.ts │ ├── defaultUserDashboard.ts │ ├── uploadS3.ts │ ├── elapsedTime.ts │ ├── getConfirmAlert.ts │ ├── tokens.ts │ ├── tagOptions.ts │ ├── useCheckIsLogin.ts │ └── inspectNicknameDuplication.ts ├── api │ ├── makePostRequest.ts │ ├── loginApi.ts │ ├── aritlceApi.ts │ ├── authCheckAndSetCodeApi.ts │ └── signUpApi.ts ├── components │ ├── common │ │ ├── ValildationMsg.tsx │ │ ├── Seo.tsx │ │ ├── BtnTag.tsx │ │ ├── Loader.tsx │ │ ├── CreatedDate.tsx │ │ ├── QuillEditor │ │ │ ├── EditorLabel.tsx │ │ │ ├── TitleInputBox.tsx │ │ │ ├── FormButtonGroup.tsx │ │ │ └── index.tsx │ │ ├── TagList.tsx │ │ ├── UserNickname.tsx │ │ ├── Button.tsx │ │ ├── Header │ │ │ ├── BtnLogin.tsx │ │ │ ├── Nav.tsx │ │ │ └── SearchBar.tsx │ │ ├── SocialLoginBtn.tsx │ │ ├── BtnTopDown.tsx │ │ ├── Footer │ │ │ └── DeveloperLink.tsx │ │ └── Input.tsx │ ├── main │ │ ├── CheerUp.tsx │ │ └── Calendar.tsx │ ├── edit-privacy │ │ ├── SaveButton.tsx │ │ ├── ChangeButton.tsx │ │ ├── WithDrawalButton.tsx │ │ ├── ProfileEditTitle.tsx │ │ ├── CancelButton.tsx │ │ ├── SelectJobOption.tsx │ │ ├── NewPasswordInput.tsx │ │ ├── OriginalPasswordInput.tsx │ │ ├── NewPaswordCheckInput.tsx │ │ └── AsideEditProfile.tsx │ ├── signup │ │ └── SignupForm │ │ │ ├── RegistrationButton.tsx │ │ │ ├── Divider.tsx │ │ │ └── AccountLink.tsx │ ├── dashboard │ │ ├── AsideComponent.tsx │ │ ├── AsideMid.tsx │ │ └── AsideBot.tsx │ ├── questions │ │ └── question-detail │ │ │ ├── QuestionContent │ │ │ ├── QuestionMainText.tsx │ │ │ ├── useHandleClickBookmark.ts │ │ │ └── BtnBookmark.tsx │ │ │ └── AnswerContent │ │ │ ├── AnswerMainText.tsx │ │ │ └── ProfileImage.tsx │ ├── signup-email │ │ └── AuthenticationTimer.tsx │ └── review │ │ ├── ProgressBar.tsx │ │ ├── BtnBackArticle.tsx │ │ └── ReviewTag.tsx ├── hooks │ ├── useGetArticleId.ts │ └── useCheckAuth.ts ├── next-env.d.ts ├── .prettierrc ├── README.md ├── pages │ ├── _document.tsx │ ├── post │ │ └── index.tsx │ ├── recruit │ │ └── index.tsx │ ├── login │ │ └── index.tsx │ ├── signup │ │ └── index.tsx │ ├── membership-withdrawal │ │ └── index.tsx │ ├── edit-password │ │ └── index.tsx │ └── edit-profile │ │ └── index.tsx ├── tailwind.config.js ├── tsconfig.json ├── next.config.js ├── .eslintrc.json └── db │ └── articles.json ├── server └── morak_back_end │ ├── settings.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ └── src │ ├── Dockerfile │ ├── main │ ├── java │ │ └── com │ │ │ └── morakmorak │ │ │ └── morak_back_end │ │ │ ├── repository │ │ │ ├── redis │ │ │ │ ├── RedisRankStore.java │ │ │ │ ├── RedisRankStoreImpl.java │ │ │ │ └── RedisRepository.java │ │ │ ├── ReviewRepository.java │ │ │ ├── CalendarRepository.java │ │ │ ├── ReviewBadgeRepository.java │ │ │ ├── user │ │ │ │ ├── UserRoleRepository.java │ │ │ │ ├── AvatarRepository.java │ │ │ │ └── RoleRepository.java │ │ │ ├── notification │ │ │ │ └── NotificationRepository.java │ │ │ ├── article │ │ │ │ ├── ArticleTagRepository.java │ │ │ │ ├── ArticleQueryRepository.java │ │ │ │ ├── ArticleLikeRepository.java │ │ │ │ └── ArticleRepository.java │ │ │ ├── ReportRepository.java │ │ │ ├── RefreshTokenRepository.java │ │ │ ├── TagRepository.java │ │ │ ├── BadgeRepository.java │ │ │ ├── FileRepository.java │ │ │ ├── answer │ │ │ │ ├── AnswerRepository.java │ │ │ │ └── AnswerLikeRepository.java │ │ │ ├── CommentRepository.java │ │ │ ├── CategoryRepository.java │ │ │ ├── BookmarkRepository.java │ │ │ └── JobQueryRepository.java │ │ │ ├── service │ │ │ ├── auth_user_service │ │ │ │ ├── UserPoint.java │ │ │ │ ├── TransactionContextCreator.java │ │ │ │ ├── TransactionContextFacade.java │ │ │ │ ├── UserTransactionExecutor.java │ │ │ │ ├── UserTransactionContext.java │ │ │ │ └── PointService.java │ │ │ ├── mail_service │ │ │ │ ├── MailSender.java │ │ │ │ └── MailSenderImpl.java │ │ │ ├── EnumValidValidator.java │ │ │ ├── EnumValid.java │ │ │ ├── answer_service │ │ │ │ ├── AnswerDeletePolicy.java │ │ │ │ └── VerifyAnswerService.java │ │ │ ├── CalendarService.java │ │ │ ├── TagService.java │ │ │ ├── CategoryService.java │ │ │ └── file_service │ │ │ │ ├── FileService.java │ │ │ │ └── DeleteFileService.java │ │ │ ├── entity │ │ │ ├── enums │ │ │ │ ├── ActivityType.java │ │ │ │ ├── PStatus.java │ │ │ │ ├── PayType.java │ │ │ │ ├── PilType.java │ │ │ │ ├── JobPayType.java │ │ │ │ ├── DepositStatus.java │ │ │ │ ├── ReportReason.java │ │ │ │ ├── DomainType.java │ │ │ │ ├── UserStatus.java │ │ │ │ ├── ArticleStatus.java │ │ │ │ ├── JobType.java │ │ │ │ ├── Grade.java │ │ │ │ ├── RoleName.java │ │ │ │ ├── CategoryName.java │ │ │ │ ├── TagName.java │ │ │ │ └── BadgeName.java │ │ │ ├── RefreshToken.java │ │ │ ├── Calendar.java │ │ │ ├── GuestBoard.java │ │ │ ├── AnswerLike.java │ │ │ ├── Avatar.java │ │ │ ├── UserRole.java │ │ │ ├── Category.java │ │ │ ├── Tag.java │ │ │ ├── Badge.java │ │ │ ├── ReviewBadge.java │ │ │ ├── Bookmark.java │ │ │ ├── Role.java │ │ │ ├── ArticleLike.java │ │ │ ├── ArticleTag.java │ │ │ ├── Report.java │ │ │ ├── File.java │ │ │ ├── BaseTime.java │ │ │ ├── Job.java │ │ │ ├── Company.java │ │ │ └── Notification.java │ │ │ ├── dto │ │ │ ├── BadgeQueryDto.java │ │ │ ├── TagQueryDto.java │ │ │ ├── ActivityQueryDto.java │ │ │ ├── DtoValidConstants.java │ │ │ ├── PageInfo.java │ │ │ ├── BookmarkDto.java │ │ │ ├── FileDto.java │ │ │ ├── ReviewBadgeDto.java │ │ │ ├── TagDto.java │ │ │ ├── ResponsePagesWithLinks.java │ │ │ ├── PageRequest.java │ │ │ ├── NotificationDto.java │ │ │ ├── JobInfoDto.java │ │ │ ├── ResponseMultiplePaging.java │ │ │ ├── EmailDto.java │ │ │ └── AvatarDto.java │ │ │ ├── config │ │ │ ├── CacheCosntant.java │ │ │ ├── JpaQuerydslConfiguration.java │ │ │ ├── AwsConfig.java │ │ │ └── LoggingConfig.java │ │ │ ├── security │ │ │ ├── resolver │ │ │ │ └── RequestUser.java │ │ │ ├── config │ │ │ │ └── PasswordEncoderConfig.java │ │ │ ├── exception │ │ │ │ ├── InvalidJwtTokenException.java │ │ │ │ └── CustomAuthenticationEntryPoint.java │ │ │ ├── filter │ │ │ │ └── CrawlerFilter.java │ │ │ ├── token │ │ │ │ └── JwtAuthenticationToken.java │ │ │ └── util │ │ │ │ └── SecurityConstants.java │ │ │ ├── exception │ │ │ ├── EmbeddedRedisServerException.java │ │ │ ├── BusinessLogicException.java │ │ │ └── webHook │ │ │ │ ├── DiscordWebhookMessageForm.java │ │ │ │ └── ErrorNotificationGenerator.java │ │ │ ├── MorakBackEndApplication.java │ │ │ ├── schedule │ │ │ └── CrawlingScheduler.java │ │ │ ├── mapper │ │ │ ├── CategoryMapper.java │ │ │ ├── CommentMapper.java │ │ │ └── FileMapper.java │ │ │ ├── domain │ │ │ ├── TokenGenerator.java │ │ │ ├── UserPasswordManager.java │ │ │ ├── AuthMailSender.java │ │ │ ├── PointCalculator.java │ │ │ └── RandomKeyGenerator.java │ │ │ ├── controller │ │ │ ├── utility │ │ │ │ └── PageRequestGenerator.java │ │ │ ├── TestController.java │ │ │ ├── BookmarkController.java │ │ │ ├── AmazonS3Controller.java │ │ │ └── CalendarController.java │ │ │ ├── crawler │ │ │ └── JobKoreaConstant.java │ │ │ └── interceptor │ │ │ └── PerformanceLoggingInterceptor.java │ └── resources │ │ └── binary │ │ └── redis │ │ └── redis-server-6.0.10-mac-arm64 │ ├── test │ └── java │ │ └── com │ │ └── morakmorak │ │ └── morak_back_end │ │ ├── util │ │ ├── CommentTestConstants.java │ │ ├── BadgeQueryDtoTestImpl.java │ │ ├── TagQueryDtoTestImpl.java │ │ └── ActivityQueryDtoTestImpl.java │ │ ├── MorakBackEndApplicationTests.java │ │ ├── config │ │ ├── JpaQueryFactoryConfig.java │ │ ├── ApiDocumentUtils.java │ │ ├── SecurityTestConfig.java │ │ └── RedisContainerTest.java │ │ ├── service │ │ ├── auth_service │ │ │ ├── LogoutTest.java │ │ │ ├── CheckDuplicateNicknameTest.java │ │ │ └── SendAuthenticationEmailForFindPwdTest.java │ │ └── CalendarServiceTest.java │ │ ├── repository │ │ ├── TagRepositoryTest.java │ │ └── AnswerRepositoryTest.java │ │ └── mapper │ │ └── AnswerMapperTest.java │ ├── docker-compose.yml │ └── docs │ └── asciidoc │ └── calendar.adoc ├── .github └── ISSUE_TEMPLATE │ ├── pull_request_template.md │ ├── 버그-리포트.md │ ├── 프론트-작업-할당-템플릿.md │ ├── be │ ├── 데일리-회고-템플릿.md │ ├── -데일리-회고-템플릿.md │ └── 백엔드-작업-할당-템플릿.md ├── appspec.yml ├── .idea └── modules │ └── morak_back_end.test.iml ├── .gitmessage.txt └── deploy.sh /client/.gitignore: -------------------------------------------------------------------------------- 1 | /.next 2 | /node_modules 3 | .env -------------------------------------------------------------------------------- /client/@types/dompurify/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'dompurify'; 2 | -------------------------------------------------------------------------------- /client/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | Kakao: any; 3 | } 4 | -------------------------------------------------------------------------------- /server/morak_back_end/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'morak_back_end' 2 | -------------------------------------------------------------------------------- /client/@types/quill-image-resize/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'quill-image-resize'; 2 | -------------------------------------------------------------------------------- /client/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /client/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /client/public/kakao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/client/public/kakao.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### 👀리뷰에서 이 부분을 더 꼼꼼히 봐주세요! 2 | - 3 | - 4 | ### ✔️보완할 점 5 | - 6 | - 7 | 8 | -------------------------------------------------------------------------------- /client/public/morak-main-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/client/public/morak-main-1.png -------------------------------------------------------------------------------- /client/public/morak-main-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/client/public/morak-main-2.png -------------------------------------------------------------------------------- /client/public/morak-main-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/client/public/morak-main-3.png -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/atoms/loginAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | export const isLoginAtom = atom({ 4 | key: 'isLogin', 5 | default: false, 6 | }); 7 | -------------------------------------------------------------------------------- /client/atoms/keywordAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | // 네비바 검색 관련 전역상태 4 | export const keywordAtom = atom({ 5 | key: 'keyword', 6 | default: '', 7 | }); 8 | -------------------------------------------------------------------------------- /client/types/comment.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | 3 | export interface CommentResp extends Base { 4 | answerId?: number | null; 5 | commentId: number; 6 | } 7 | -------------------------------------------------------------------------------- /server/morak_back_end/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/server/morak_back_end/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /server/morak_back_end/src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11 2 | VOLUME /tmp 3 | ARG JAR_FILE=./build/libs/*.jar 4 | COPY ${JAR_FILE} app.jar 5 | ENTRYPOINT ["java", "-jar", "app.jar"] -------------------------------------------------------------------------------- /client/atoms/commentAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | // 댓글 더보기 4 | export const isCommentOpenAtom = atom({ 5 | key: 'isCommentOpen', 6 | default: false, 7 | }); 8 | -------------------------------------------------------------------------------- /client/atoms/renderingAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | // 렌더링 여부 확인 4 | export const renderingAtom = atom({ 5 | key: 'rendering', 6 | default: true, 7 | }); 8 | -------------------------------------------------------------------------------- /client/libs/changeTagPrettier.ts: -------------------------------------------------------------------------------- 1 | export const changeTagPrettier = (tag: string) => 2 | tag 3 | .split('') 4 | .map((char, i) => (i === 0 ? char : char.toLowerCase())) 5 | .join(''); 6 | -------------------------------------------------------------------------------- /client/types/pageInfo.ts: -------------------------------------------------------------------------------- 1 | export interface PageInfo { 2 | page: number; 3 | size: number; 4 | totalElements: number; 5 | sort: { empty: boolean; unsorted: boolean; sorted: boolean }; 6 | } 7 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/redis/RedisRankStore.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.redis;public interface RedisRankStore { 2 | } 3 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/redis/RedisRankStoreImpl.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.redis;public class RedisRankStoreImpl { 2 | } 3 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/UserPoint.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service.auth_user_service;public @interface UserPoint { 2 | } 3 | -------------------------------------------------------------------------------- /client/api/makePostRequest.ts: -------------------------------------------------------------------------------- 1 | import { client } from './../libs/client'; 2 | 3 | export const makePostRequest = async (endpoint: string, data: object) => { 4 | return await client.post(endpoint, data); 5 | }; 6 | -------------------------------------------------------------------------------- /client/types/login.ts: -------------------------------------------------------------------------------- 1 | export interface AuthResp { 2 | email: string; 3 | authKey?: string; 4 | } 5 | 6 | export type DecodedResp = { 7 | sub: string; 8 | id: number; 9 | nickname: string; 10 | }; 11 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/TransactionContextCreator.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service;public interface TransactionContextCreator { 2 | } 3 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/TransactionContextFacade.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service;public interface TransactionContextFacade { 2 | } 3 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/UserTransactionExecutor.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service;public interface UserTransactionExecutor { 2 | } 3 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/ActivityType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | public enum ActivityType { 4 | ARTICLE, ANSWER, COMMENT 5 | } 6 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/PStatus.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PStatus { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/PayType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PayType { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/PilType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum PilType { 7 | } 8 | -------------------------------------------------------------------------------- /client/components/common/ValildationMsg.tsx: -------------------------------------------------------------------------------- 1 | type MsgProps = { 2 | msg: string | undefined; 3 | }; 4 | 5 | export const ValidationMsg = ({ msg }: MsgProps) => { 6 | return

{msg}

; 7 | }; 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/JobPayType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum JobPayType { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/resources/binary/redis/redis-server-6.0.10-mac-arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestates-seb/seb40_main_004/HEAD/server/morak_back_end/src/main/resources/binary/redis/redis-server-6.0.10-mac-arm64 -------------------------------------------------------------------------------- /client/components/common/Seo.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | export const Seo = ({ title }: { title: string }) => { 4 | return ( 5 | 6 | {title} 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /client/types/calendar.ts: -------------------------------------------------------------------------------- 1 | export interface ICalendar { 2 | jobId: number; 3 | name: string; 4 | state: string; 5 | careerRequirement: string; 6 | url: string; 7 | startDate: string; 8 | endDate: string; 9 | } 10 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/BadgeQueryDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | public interface BadgeQueryDto { 4 | Long getBadge_Id(); 5 | String getName(); 6 | } 7 | -------------------------------------------------------------------------------- /client/hooks/useGetArticleId.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | export const useGetArticleId = () => { 4 | const router = useRouter(); 5 | const { articleId } = router.query; 6 | 7 | return { articleId }; 8 | }; 9 | -------------------------------------------------------------------------------- /client/libs/getIsFromDashboard.ts: -------------------------------------------------------------------------------- 1 | export const getIsFromDashboard = (url: string) => { 2 | const prevPageAndId = url.split('/').slice(-2); 3 | if (prevPageAndId[0] !== 'dashboard') return false; 4 | else return prevPageAndId; 5 | }; 6 | -------------------------------------------------------------------------------- /client/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/DepositStatus.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum DepositStatus { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/ReportReason.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | public enum ReportReason { 4 | BAD_LANGUAGE, 5 | ILLEGAL_ADVERTISING, 6 | } 7 | -------------------------------------------------------------------------------- /client/components/main/CheerUp.tsx: -------------------------------------------------------------------------------- 1 | export const CheerUp = () => { 2 | return ( 3 |
4 | 모락모락 5 | 이 당신의 성장을 응원합니다 ! 😊 6 |
7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /client/components/edit-privacy/SaveButton.tsx: -------------------------------------------------------------------------------- 1 | const SaveButton = () => { 2 | return ( 3 | 6 | ); 7 | }; 8 | 9 | export default SaveButton; 10 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/TagQueryDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | public interface TagQueryDto { 4 | Long getRanking(); 5 | Long getTag_Id(); 6 | String getName(); 7 | } 8 | -------------------------------------------------------------------------------- /client/api/loginApi.ts: -------------------------------------------------------------------------------- 1 | import { makePostRequest } from './makePostRequest'; 2 | 3 | export const authentiCate = async (email: string, password: string) => { 4 | const res = makePostRequest(`/api/auth/token`, { email, password }); 5 | return res; 6 | }; 7 | -------------------------------------------------------------------------------- /client/components/edit-privacy/ChangeButton.tsx: -------------------------------------------------------------------------------- 1 | const ChangeButton = () => { 2 | return ( 3 | 6 | ); 7 | }; 8 | 9 | export default ChangeButton; 10 | -------------------------------------------------------------------------------- /server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/util/CommentTestConstants.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.util; 2 | 3 | public class CommentTestConstants { 4 | public final static String VALID_COMMENT = "유효한 길이의 댓글 콘텐트입니다."; 5 | } 6 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "endOfLine": "auto", 9 | "useTabs": false, 10 | "arrowParens": "always" 11 | } 12 | -------------------------------------------------------------------------------- /client/components/edit-privacy/WithDrawalButton.tsx: -------------------------------------------------------------------------------- 1 | const WithDrawalButton = () => { 2 | return ( 3 | 6 | ); 7 | }; 8 | 9 | export default WithDrawalButton; 10 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/mail_service/MailSender.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service.mail_service; 2 | 3 | public interface MailSender { 4 | boolean sendMail(String to, String content, String title); 5 | } 6 | -------------------------------------------------------------------------------- /client/components/edit-privacy/ProfileEditTitle.tsx: -------------------------------------------------------------------------------- 1 | const ProfileEditTitle = () => { 2 | return ( 3 |
4 | 프로필 수정 5 |
6 | ); 7 | }; 8 | 9 | export default ProfileEditTitle; 10 | -------------------------------------------------------------------------------- /server/morak_back_end/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/DomainType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum DomainType { 7 | ARTICLE, 8 | ANSWER, 9 | COMMENT 10 | } 11 | -------------------------------------------------------------------------------- /client/libs/jobOptions.ts: -------------------------------------------------------------------------------- 1 | export const jobOptions = [ 2 | { value: 'JOB_SEEKER', title: '개발자 취준생' }, 3 | { value: 'DEVELOPER', title: '현업 개발자' }, 4 | { value: 'DESIGNER', title: '디자이너' }, 5 | { value: 'PM', title: '프로덕트 매니저' }, 6 | { value: 'NON_NORMAL', title: '비개발 직군' }, 7 | ]; 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/UserStatus.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum UserStatus { 7 | RUNNING, 8 | BLOCKED, 9 | DELETED 10 | } 11 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/ArticleStatus.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ArticleStatus { 7 | POSTING, 8 | REMOVED, 9 | BLOCKED; 10 | } 11 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | ## Tech Stack 4 | 5 | - React 6 | - TypeScript 7 | - Next.JS 8 | - TailwindCSS 9 | - Recoil 10 | - SWR 11 | - Axios 12 | - React-Hook-Form 13 | - Framer Motion 14 | - React-Quill 15 | - Http-Proxy-MiddleWare 16 | 17 | ## Script 18 | 19 | - `$ npm run dev` 20 | -------------------------------------------------------------------------------- /client/components/common/BtnTag.tsx: -------------------------------------------------------------------------------- 1 | type ButtonText = { children: string }; 2 | 3 | export const BtnTag = ({ children }: ButtonText) => { 4 | return ( 5 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /client/components/common/Loader.tsx: -------------------------------------------------------------------------------- 1 | import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | 4 | export const Loader = () => { 5 | return ( 6 | 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/JobType.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum JobType { 7 | DEVELOPER, 8 | JOB_SEEKER, 9 | NON_NORMAL, 10 | PM, 11 | DESIGNER, 12 | DEFAULT 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/버그-리포트.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 버그 리포트 3 | about: 버그 관련 이슈 템플릿입니다. 4 | title: "[BUG] 버그 이슈" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | * 스크린샷을 첨부해주세요. 13 | * 어떤 상황에서 발생하는지 자세하게 작성해주세요. 14 | 15 | ## Todo 16 | 17 | - [ ] 해결 프로세스 1 18 | - [ ] 해결 프로세스 2 19 | 20 | ## Log 21 | - 해결 과정을 메모하세요. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/프론트-작업-할당-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 프론트 작업 할당 템플릿 3 | about: 작업하실 내용을 작성해주세요. 4 | title: "[FE] 작업 내용 한 줄 정리" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | 작업할 내용을 작성해주세요. 13 | 14 | ## Todo 15 | 16 | - [x] 할 일 17 | - [x] 할 일 18 | - [x] 할 일 19 | 20 | 21 | ## ETC 22 | - 주의 사항 혹은 기타 내용 23 | -------------------------------------------------------------------------------- /client/types/answer.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | import { CommentResp } from './comment'; 3 | export interface Answer extends Base { 4 | answerId: number; 5 | authorId: string; 6 | isLiked: boolean; 7 | isPicked: boolean; 8 | answerLikeCount: number; 9 | commentCount: number; 10 | commentPreview: CommentResp; 11 | } 12 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/ActivityQueryDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | public interface ActivityQueryDto { 4 | Integer getArticleCount(); 5 | Integer getAnswerCount(); 6 | Integer getCommentCount(); 7 | Integer getTotal(); 8 | String getDate(); 9 | } 10 | -------------------------------------------------------------------------------- /client/components/common/CreatedDate.tsx: -------------------------------------------------------------------------------- 1 | import { elapsedTime } from '@libs/elapsedTime'; 2 | 3 | type CreatedDateProps = { 4 | createdAt: string; 5 | }; 6 | 7 | export const CreatedDate = ({ createdAt }: CreatedDateProps) => { 8 | return ( 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /client/libs/changeGradeEmoji.ts: -------------------------------------------------------------------------------- 1 | export const changeGradeEmoji = (grade: string) => { 2 | switch (grade) { 3 | case 'CANDLE': 4 | return '🕯'; 5 | case 'MATCH': 6 | return '🔥'; 7 | case 'BONFIRE': 8 | return '🎇'; 9 | case 'MORAKMORAK': 10 | return '♨️'; 11 | default: 12 | return '❓'; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/Grade.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum Grade { 7 | BRONZE, 8 | GOLD, 9 | SILVER, 10 | VIP, 11 | CANDLE, 12 | MATCH, 13 | BONFIRE, 14 | MORAKMORAK; 15 | } 16 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/ReviewRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Review; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface ReviewRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /appspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | os: linux 3 | files: 4 | - source: / 5 | destination: /home/ec2-user/app/ 6 | overwrite: yes 7 | 8 | permissions: 9 | - object: / 10 | pattern: "**" 11 | owner: ec2-user 12 | group: ec2-user 13 | 14 | hooks: 15 | ApplicationStart: 16 | - location: deploy.sh 17 | timeout: 600 18 | runas: ec2-user -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/CalendarRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Calendar; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface CalendarRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /client/components/signup/SignupForm/RegistrationButton.tsx: -------------------------------------------------------------------------------- 1 | const RegistrationButton = () => { 2 | return ( 3 | 9 | ); 10 | }; 11 | 12 | export default RegistrationButton; 13 | -------------------------------------------------------------------------------- /client/api/aritlceApi.ts: -------------------------------------------------------------------------------- 1 | import { client } from '@libs/client'; 2 | import { ReportProps } from '@type/article'; 3 | 4 | export const ArticleApi = { 5 | delete: (articleId: string) => client.delete(`/api/articles/${articleId}`), 6 | 7 | report: (articleId: string, payload: ReportProps) => 8 | client.post(`/api/articles/${articleId}/reports`, payload), 9 | }; 10 | -------------------------------------------------------------------------------- /client/components/common/QuillEditor/EditorLabel.tsx: -------------------------------------------------------------------------------- 1 | type EditorLabelProps = { 2 | htmlFor: string; 3 | title: string; 4 | }; 5 | 6 | const EditorLabel = ({ htmlFor, title }: EditorLabelProps) => { 7 | return ( 8 | 11 | ); 12 | }; 13 | 14 | export default EditorLabel; 15 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/config/CacheCosntant.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.config; 2 | 3 | public class CacheCosntant { 4 | public static final String USER_RANK = "userRankList"; 5 | public static final String MAIN_ARTICLE_LIST = "mainArticleList"; 6 | public static final String JOB_CALENDAR = "jobCalendar"; 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/ReviewBadgeRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.ReviewBadge; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface ReviewBadgeRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/user/UserRoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.user; 2 | 3 | import com.morakmorak.morak_back_end.entity.UserRole; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserRoleRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/be: -------------------------------------------------------------------------------- 1 | ### 필수 2 | 3 | - [ ] 전체 로직 작성 및 검토 4 | - [ ] JSON 요청/반환 값 작성 및 검토 5 | - [ ] 응용 계층 테스트 6 | - [ ] 응용 계층 로직 구현 7 | - [ ] 비즈니스 계층 테스트 8 | - [ ] 비즈니스 로직 구현 9 | - [ ] 통합 테스트 10 | - [ ] 기능 테스트 11 | 12 | ### 해당사항 있는 경우 13 | 14 | - [ ] 데이터 액세스 계층 테스트 15 | - [ ] 데이터 액세스 계층 구현 16 | - [ ] DTO 테스트 17 | - [ ] DTO 구현 18 | - [ ] Mapper 테스트 19 | - [ ] Mapper 구현 20 | -------------------------------------------------------------------------------- /server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/MorakBackEndApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MorakBackEndApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /client/components/edit-privacy/CancelButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | const CancelButton = () => { 4 | return ( 5 | 6 |
7 | 취소 8 |
9 | 10 | ); 11 | }; 12 | 13 | export default CancelButton; 14 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/UserTransactionContext.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service; 2 | 3 | public interface UserTransactionContext { 4 | Response processWithAddPoint(Request request); 5 | Response processWithMinusPoint(Request request); 6 | Response processWithoutPoint(Request request); 7 | } 8 | -------------------------------------------------------------------------------- /client/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | ); 14 | } 15 | } 16 | 17 | export default MyDocument; 18 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/notification/NotificationRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.notification; 2 | 3 | import com.morakmorak.morak_back_end.entity.Notification; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface NotificationRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/article/ArticleTagRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.article; 2 | 3 | import com.morakmorak.morak_back_end.entity.ArticleTag; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | 7 | public interface ArticleTagRepository extends JpaRepository { 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /client/api/authCheckAndSetCodeApi.ts: -------------------------------------------------------------------------------- 1 | import { makePostRequest } from './makePostRequest'; 2 | 3 | export const authCheckCode = async ( 4 | email: string, 5 | authKey: string | undefined, 6 | ) => { 7 | makePostRequest(`/api/auth/password/recovery`, { email, authKey }); 8 | }; 9 | 10 | export const authGetCode = async (email: string) => { 11 | makePostRequest(`/api/auth/password/support`, { email }); 12 | }; 13 | -------------------------------------------------------------------------------- /client/components/signup/SignupForm/Divider.tsx: -------------------------------------------------------------------------------- 1 | export const Divider = () => { 2 | return ( 3 |
4 |
5 | 소셜 로그인 6 |
7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /client/atoms/answerAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | // 답변 작성 후 스크롤 상단 이동시에 활용 4 | export const isAnswerPostedAtom = atom({ 5 | key: 'isAnswerPosted', 6 | default: false, 7 | }); 8 | 9 | // 답변 작성 후 수정 상단 이동시에 활용 10 | export const isAnswerEditAtom = atom({ 11 | key: 'isAnswerEdit', 12 | default: { 13 | isEdit: false, 14 | answerId: 0, 15 | answerPage: 0, 16 | payload: '', 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /client/libs/useFetchSWR.ts: -------------------------------------------------------------------------------- 1 | import { client } from './client'; 2 | import useSWR from 'swr'; 3 | 4 | const fetcher = async (url: string) => 5 | await client.get(url).then((res) => res.data); 6 | 7 | export const useFetch = (url: string) => { 8 | const { data, error, mutate } = useSWR(url, fetcher); 9 | 10 | return { 11 | data, 12 | isLoading: !error && !data, 13 | isError: error, 14 | mutate, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /client/types/base.ts: -------------------------------------------------------------------------------- 1 | import { Tags } from './article'; 2 | import { Avatar, UserInfo } from './user'; 3 | 4 | export interface Base { 5 | articleId: number; 6 | category?: string; 7 | title: string; 8 | content: string; 9 | clicks?: number; 10 | likes?: number; 11 | isClosed?: boolean; 12 | createdAt: string; 13 | lastModifiedAt: string; 14 | tags?: Tags[]; 15 | avatar: Avatar; 16 | userInfo: UserInfo; 17 | } 18 | -------------------------------------------------------------------------------- /client/components/common/TagList.tsx: -------------------------------------------------------------------------------- 1 | import { Tags } from '@type/article'; 2 | import { BtnTag } from './BtnTag'; 3 | 4 | type TagListProps = { 5 | tags?: Tags[]; 6 | }; 7 | 8 | export const TagList = ({ tags }: TagListProps) => { 9 | return ( 10 |
11 | {tags?.map((tag) => ( 12 | {tag.name} 13 | ))} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /client/components/dashboard/AsideComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AsideBot } from './AsideBot'; 3 | import { AsideMid } from './AsideMid'; 4 | import { AsideTop } from './AsideTop'; 5 | 6 | const AsideComponent = () => { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default AsideComponent; 17 | -------------------------------------------------------------------------------- /client/components/questions/question-detail/QuestionContent/QuestionMainText.tsx: -------------------------------------------------------------------------------- 1 | type MainTextProps = { 2 | children: string; 3 | }; 4 | 5 | // 본문 텍스트 6 | export const QuestionMainText = ({ children }: MainTextProps) => { 7 | return ( 8 |
9 |
13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/RoleName.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum RoleName { 7 | ROLE_USER("ROLE_USER"), 8 | ROLE_MANAGER("ROLE_MANAGER"), 9 | ROLE_ADMIN("ROLE_ADMIN"); 10 | 11 | private final String name; 12 | 13 | RoleName(String name) { 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/ReportRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Report; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface ReportRepository extends JpaRepository { 9 | Optional findReportByContent(String content); 10 | } 11 | -------------------------------------------------------------------------------- /client/atoms/articleAtom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | 3 | // 게시글 작성자 id를 저장한 후 다른 컴포넌트에서 작성자 일치 여부 확인시에 활용 4 | export const articleAuthorIdAtom = atom({ 5 | key: 'articleAuthorId', 6 | default: '', 7 | }); 8 | 9 | // 게시글 수정시 게시글 수정 페이지로 이동할 때 사용 10 | export const isArticleEditAtom = atom({ 11 | key: 'isArticleEdit', 12 | default: { 13 | isArticleEdit: false, 14 | title: '', 15 | content: '', 16 | articleId: '', 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/user/AvatarRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.user; 2 | 3 | import com.morakmorak.morak_back_end.entity.Avatar; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface AvatarRepository extends JpaRepository { 9 | 10 | Optional findByUserId(Long userId); 11 | } 12 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/DtoValidConstants.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | public final class DtoValidConstants { 4 | public final static String CAN_NOT_NULL = "input can not be empty"; 5 | public final static String INVALID_EMAIL = "invalid email"; 6 | public final static String INVALID_PASSWORD = "invalid password"; 7 | public final static String INVALID_NICKNAME = "invalid nickname"; 8 | } 9 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/resolver/RequestUser.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.security.resolver; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.PARAMETER; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Target(PARAMETER) 10 | @Retention(RUNTIME) 11 | public @interface RequestUser { 12 | } 13 | -------------------------------------------------------------------------------- /.idea/modules/morak_back_end.test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/user/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.user; 2 | 3 | import com.morakmorak.morak_back_end.entity.Role; 4 | import com.morakmorak.morak_back_end.entity.enums.RoleName; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface RoleRepository extends JpaRepository { 8 | Role findRoleByRoleName(RoleName roleName); 9 | } 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/데일리-회고-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 데일리 회고 템플릿 3 | about: Describe this issue template's purpose here. 4 | title: [dev-log] 이름 YY-MM-DD 5 | labels: "\U0001F4CB Dev Log" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 😸 오늘 어떻게 프로젝트에 기여했고, 무엇을 배웠나요? 11 | - 오늘 한 일을 적어주세요! 12 | - 회의, 개인 작업 등도 좋습니다. 13 | 14 | ### 😸 오늘의 프로젝트에서 힘든 점은 무엇인가요? 15 | - 컨디션은 어떠셨나요? 16 | - 힘들었던 부분의 원인이 있다면 무엇일까요? 17 | 18 | ### 😸 내일을 위해 기여해야 하는 일은? 19 | - 내일은 무엇을 하실 건가요? 20 | - 내일을 위해 오늘 해야 할 일이 있으신가요? 21 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/RefreshTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.RefreshToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface RefreshTokenRepository extends JpaRepository { 9 | Optional findRefreshTokenByValue(String value); 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-데일리-회고-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: " 데일리 회고 템플릿" 3 | about: Describe this issue template's purpose here. 4 | title: "[dev-log] 이름 YY-MM-DD" 5 | labels: "\U0001F4CB Dev Log" 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 😸 오늘 어떻게 프로젝트에 기여했고, 무엇을 배웠나요? 11 | - 오늘 한 일을 적어주세요! 12 | - 회의, 개인 작업 등도 좋습니다. 13 | 14 | ### 😸 오늘의 프로젝트에서 힘든 점은 무엇인가요? 15 | - 컨디션은 어떠셨나요? 16 | - 힘들었던 부분의 원인이 있다면 무엇일까요? 17 | 18 | ### 😸 내일을 위해 기여해야 하는 일은? 19 | - 내일은 무엇을 하실 건가요? 20 | - 내일을 위해 오늘 해야 할 일이 있으신가요? 21 | -------------------------------------------------------------------------------- /client/components/common/UserNickname.tsx: -------------------------------------------------------------------------------- 1 | import { changeGradeEmoji } from '@libs/changeGradeEmoji'; 2 | import { UserInfo } from '@type/user'; 3 | import Link from 'next/link'; 4 | 5 | export const UserNickname = ({ nickname, userId, grade }: UserInfo) => { 6 | return ( 7 | 8 | 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /client/components/common/Button.tsx: -------------------------------------------------------------------------------- 1 | import { MouseEventHandler } from 'react'; 2 | 3 | type ButtonText = { 4 | children: string; 5 | funcProp?: MouseEventHandler; 6 | }; 7 | 8 | export const Button = ({ children, funcProp }: ButtonText) => { 9 | return ( 10 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /client/components/edit-privacy/SelectJobOption.tsx: -------------------------------------------------------------------------------- 1 | type OptionType = { 2 | value: string; 3 | title: string; 4 | }; 5 | 6 | type Props = { 7 | options: OptionType[]; 8 | }; 9 | 10 | const SelectJobOption = ({ options }: Props) => { 11 | return ( 12 | <> 13 | {options.map((option) => ( 14 | 17 | ))} 18 | 19 | ); 20 | }; 21 | 22 | export default SelectJobOption; 23 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/redis/RedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.redis; 2 | 3 | import java.util.Optional; 4 | 5 | public interface RedisRepository { 6 | 7 | boolean saveData(String key, T value, Long expire); 8 | 9 | Optional getData(String key, Class classType); 10 | 11 | boolean deleteData(String key); 12 | 13 | Optional getDataAndDelete(String key, Class classType); 14 | } 15 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/exception/EmbeddedRedisServerException.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.exception; 2 | 3 | import io.jsonwebtoken.io.IOException; 4 | 5 | public class EmbeddedRedisServerException extends IOException { 6 | public EmbeddedRedisServerException(String msg) { 7 | super(msg); 8 | } 9 | 10 | public EmbeddedRedisServerException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/TagRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | 4 | import com.morakmorak.morak_back_end.entity.Tag; 5 | import com.morakmorak.morak_back_end.entity.enums.TagName; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.Optional; 9 | 10 | public interface TagRepository extends JpaRepository { 11 | 12 | Optional findTagByName(TagName name); 13 | } 14 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/BadgeRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Badge; 4 | import com.morakmorak.morak_back_end.entity.enums.BadgeName; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.Optional; 8 | 9 | public interface BadgeRepository extends JpaRepository { 10 | Optional findBadgeByName(BadgeName name); 11 | } 12 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/FileRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | 4 | import com.morakmorak.morak_back_end.entity.File; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.Optional; 8 | 9 | public interface FileRepository extends JpaRepository { 10 | Optional findFileByLocalPath(String localPath); 11 | 12 | Optional findById(Long fileId); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /client/components/questions/question-detail/AnswerContent/AnswerMainText.tsx: -------------------------------------------------------------------------------- 1 | import Dompurify from 'dompurify'; 2 | 3 | type AnswerTextProps = { 4 | children: string; 5 | }; 6 | 7 | // children은 html 형태의 string 으로 오기 때문에, 이를 변환하는 작업이 필요하다. 8 | export const AnswerMainText = ({ children }: AnswerTextProps) => { 9 | return ( 10 |
11 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /client/libs/ defaultUserDashboard.ts: -------------------------------------------------------------------------------- 1 | export const defaultAvatar = { 2 | avatarId: 0, 3 | filename: '', 4 | remotePath: '/favicon.ico', 5 | }; 6 | 7 | export const defaultUserDashboard = { 8 | userId: 0, 9 | email: '', 10 | nickname: '탈퇴한 유저', 11 | jobType: '', 12 | grade: '', 13 | point: 0, 14 | github: '', 15 | blog: '', 16 | infoMessage: '', 17 | rank: 0, 18 | avatar: defaultAvatar, 19 | tags: [], 20 | reviewBadges: [], 21 | articles: [], 22 | activities: [], 23 | reviews: [], 24 | }; 25 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/answer/AnswerRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.answer; 2 | 3 | import com.morakmorak.morak_back_end.entity.Answer; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface AnswerRepository extends JpaRepository { 9 | Optional findAnswerByContent(String content); 10 | 11 | Optional findTopByUserId(Long id); 12 | } 13 | -------------------------------------------------------------------------------- /.gitmessage.txt: -------------------------------------------------------------------------------- 1 | # 제목은 대문자로 시작합니다. 2 | # 본문과 푸터는 선택 사항 입니다. 3 | #######제목####### 4 | 5 | # 바로 아래 공백은 지우지 마세요 (제목과 본문의 분리를 위함) 6 | 7 | #######본문####### 8 | 9 | #######푸터####### 10 | 11 | ################## 12 | # Feat : 새로운 기능 추가 13 | # Fix : 버그 수정 14 | # Add : 추가 15 | # Remove : 삭제 16 | # Rename : 이름변경 17 | # Docs : 문서 수정 18 | # Test : 테스트 코드 추가 19 | # Refactor : 코드 리팩토링 20 | # Style : 코드 의미에 영향을 주지 않는 변경사항 21 | # Update : 보완 22 | # Implement : 구현 23 | # Prevent : 방지 24 | # Move : 이동 25 | # Chore : 빌드 부분 혹은 패키지 매니저 수정사항 26 | ################## -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/백엔드-작업-할당-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 백엔드 작업 할당 템플릿 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 필수 11 | 12 | - [ ] 전체 로직 작성 및 검토 13 | - [ ] JSON 요청/반환 값 작성 및 검토 14 | - [ ] 응용 계층 테스트 15 | - [ ] 응용 계층 로직 구현 16 | - [ ] 비즈니스 계층 테스트 17 | - [ ] 비즈니스 로직 구현 18 | - [ ] 통합 테스트 19 | - [ ] 기능 테스트 20 | 21 | ### 해당사항 있는 경우 22 | 23 | - [ ] 데이터 액세스 계층 테스트 24 | - [ ] 데이터 액세스 계층 구현 25 | - [ ] DTO 테스트 26 | - [ ] DTO 구현 27 | - [ ] Mapper 테스트 28 | - [ ] Mapper 구현 29 | -------------------------------------------------------------------------------- /client/components/common/Header/BtnLogin.tsx: -------------------------------------------------------------------------------- 1 | import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import Link from 'next/link'; 4 | 5 | export const BtnLogin = () => { 6 | return ( 7 | 8 | 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /client/libs/uploadS3.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { client } from './client'; 3 | 4 | export const getFileUrl = async () => { 5 | return await ( 6 | await client.get('/api/files') 7 | ).data; 8 | }; 9 | 10 | export const uploadImg = async (url: string, file: any) => { 11 | axios 12 | .put(url, file, { 13 | headers: { 14 | 'Content-Type': 'multipart/form-data', 15 | }, 16 | }) 17 | .then((res) => { 18 | // console.log(res); 19 | }) 20 | .catch((err) => console.error(err)); 21 | }; 22 | -------------------------------------------------------------------------------- /client/types/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfo { 2 | userId: number; 3 | nickname: string; 4 | grade: string; 5 | } 6 | 7 | export interface Avatar { 8 | avatarId: number; 9 | fileName: string; 10 | remotePath: string; 11 | } 12 | 13 | export interface IDataHeader { 14 | point: number; 15 | userInfo: { 16 | userId: number; 17 | nickname: string; 18 | grade: string; 19 | }; 20 | avatar: { 21 | avatarId: number | null; 22 | filename: string | null; 23 | remotePath: string | null; 24 | } | null; 25 | } 26 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/exception/BusinessLogicException.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class BusinessLogicException extends RuntimeException { 7 | private ErrorCode errorCode; 8 | 9 | public BusinessLogicException(ErrorCode errorCode) { 10 | this.errorCode = errorCode; 11 | } 12 | 13 | @Override 14 | public synchronized Throwable fillInStackTrace() { 15 | return this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/article/ArticleQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.article; 2 | 3 | import com.morakmorak.morak_back_end.entity.Article; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | import java.util.List; 8 | 9 | public interface ArticleQueryRepository { 10 | 11 | public Page
search(String category, String keyword, String target, String sort, Pageable pageable); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /client/components/signup/SignupForm/AccountLink.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | type LoginTitleProps = { 4 | loginTitle: string; 5 | }; 6 | 7 | const AccountLink = ({ loginTitle }: LoginTitleProps) => { 8 | return ( 9 | 10 | 이미 계정이 있으신가요?{' '} 11 | 12 | 13 | {loginTitle} 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default AccountLink; 21 | -------------------------------------------------------------------------------- /client/types/article.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './base'; 2 | // import { UserInfo } from './user'; 3 | // import { Avatar } from './user'; 4 | 5 | export interface Tags { 6 | tagId: number; 7 | name: string; 8 | } 9 | 10 | export interface ArticleDetail extends Base { 11 | isLiked: boolean; 12 | isBookmarked: boolean; 13 | expiredDate: null; 14 | comments: Comment[]; 15 | } 16 | 17 | export interface ArticleListProps extends Base { 18 | answerCount: number; 19 | commentCount: number; 20 | } 21 | 22 | export interface ReportProps { 23 | reason: string; 24 | content: string; 25 | } 26 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/PageInfo.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | import lombok.*; 4 | import org.springframework.data.domain.Sort; 5 | 6 | import java.io.Serializable; 7 | 8 | @Getter 9 | @Builder 10 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 11 | @AllArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class PageInfo implements Serializable { 13 | private Integer page; 14 | private Integer size; 15 | private Long totalElements; 16 | private Integer totalPages; 17 | private Sort sort; 18 | } 19 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/EnumValidValidator.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | 6 | public class EnumValidValidator implements ConstraintValidator> { 7 | @Override 8 | public void initialize(EnumValid constraint) { 9 | 10 | } 11 | 12 | @Override 13 | public boolean isValid(Enum value, ConstraintValidatorContext constraintValidatorContext) { 14 | return value != null; 15 | } 16 | } -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/EnumValid.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service; 2 | 3 | 4 | 5 | 6 | import javax.validation.Constraint; 7 | import javax.validation.Payload; 8 | import java.lang.annotation.*; 9 | 10 | 11 | @Target(ElementType.FIELD) 12 | @Retention( RetentionPolicy.RUNTIME) 13 | @Documented 14 | @Constraint(validatedBy=EnumValidValidator.class) 15 | public @interface EnumValid { 16 | String message() default "Invalid Enum"; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | } -------------------------------------------------------------------------------- /client/components/questions/question-detail/AnswerContent/ProfileImage.tsx: -------------------------------------------------------------------------------- 1 | type ProfileImageProps = { 2 | src: string; 3 | }; 4 | 5 | import Image from 'next/image'; 6 | 7 | export const ProfileImage = ({ src }: ProfileImageProps) => { 8 | // 응답 데이터가 null인 경우 보여질 임시 이미지...! 9 | if (!src) 10 | src = 11 | 'https://images.unsplash.com/photo-1665168667719-aad400bd8c4a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=764&q=80'; 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/config/PasswordEncoderConfig.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.security.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class PasswordEncoderConfig { 10 | 11 | @Bean 12 | public PasswordEncoder passwordEncoder() { 13 | return new BCryptPasswordEncoder(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/morak_back_end/src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | 5 | redis: 6 | image: redis 7 | container_name: rediscache 8 | ports: 9 | - 6380:6379 10 | networks: 11 | - spring-net 12 | 13 | spring-testcontainers-app: 14 | image: spring-testcontainers-app 15 | container_name: spring-testcontainers-app 16 | build: . 17 | restart: always 18 | environment: 19 | REDIS_HOST: rediscache 20 | REDOS_PORT: 6379 21 | ports: 22 | - 8080:8080 23 | depends_on: 24 | - redis 25 | networks: 26 | - spring-net 27 | 28 | networks: 29 | spring-net: -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/answer_service/AnswerDeletePolicy.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service.answer_service; 2 | 3 | import com.morakmorak.morak_back_end.entity.Answer; 4 | import com.morakmorak.morak_back_end.entity.Article; 5 | import com.morakmorak.morak_back_end.entity.User; 6 | 7 | public interface AnswerDeletePolicy { 8 | Answer findVerifiedAnswer(Long answerId); 9 | void checkUserPermission(Answer answer, User requestUser); 10 | void checkArticleStatusPosting(Article verifiedArticle); 11 | void checkAnswerIsPicked(Answer verifiedAnswer); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPOSITORY=/home/ec2-user/app/server 4 | PROJECT_NAME=morak_back_end 5 | 6 | # git clone 받은 위치로 이동 7 | cd $REPOSITORY/$PROJECT_NAME/ 8 | 9 | APP_NAME=morak_back_end 10 | CURRENT_PID=$(pgrep -f $APP_NAME) 11 | 12 | if [ -z $CURRENT_PID ] 13 | then 14 | echo "> 종료할것 없음." 15 | else 16 | echo "> kill -9 $CURRENT_PID" 17 | sudo kill -15 $CURRENT_PID 18 | sleep 5 19 | fi 20 | 21 | # jar 파일 위치로 이동 22 | cd $REPOSITORY/$PROJECT_NAME/build/libs 23 | 24 | echo "> JAR BUILD" 25 | sudo nohup java -jar -Dspring.profiles.active=prod morak_back_end-0.0.1-SNAPSHOT.jar >> /home/ec2-user/deploy.log 2>/home/ec2-user/deploy_err.log & 26 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/exception/webHook/DiscordWebhookMessageForm.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.exception.webHook; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.*; 5 | 6 | 7 | @Getter 8 | @Builder 9 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 10 | @AllArgsConstructor(access = AccessLevel.PROTECTED) 11 | public class DiscordWebhookMessageForm { 12 | @JsonProperty("username") 13 | private String username; 14 | @JsonProperty("avatar_url") 15 | private String avatarUrl; 16 | @JsonProperty("content") 17 | private String content; 18 | } 19 | -------------------------------------------------------------------------------- /client/components/edit-privacy/NewPasswordInput.tsx: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const NewPasswordInput = () => { 4 | const { register } = useForm(); 5 | return ( 6 | 17 | ); 18 | }; 19 | 20 | export default NewPasswordInput; 21 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Comment; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface CommentRepository extends JpaRepository { 11 | List findAllCommentsByArticleId(Long articleId); 12 | 13 | Optional findByUserId(Long userId); 14 | 15 | Collection findAllCommentsByAnswerId(Long answerId); 16 | } 17 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/answer/AnswerLikeRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.answer; 2 | 3 | import com.morakmorak.morak_back_end.entity.AnswerLike; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Optional; 8 | 9 | public interface AnswerLikeRepository extends JpaRepository { 10 | 11 | @Query("select a from AnswerLike a where a.user.id = :userId and a.answer.id = :answerId") 12 | Optional checkUserLiked(Long userId, Long answerId); 13 | } 14 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class RefreshToken { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | @Column(name = "refresh_token_id") 17 | private Long id; 18 | 19 | private String value; 20 | 21 | public RefreshToken(String value) { 22 | this.value = value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Calendar.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import java.util.Date; 10 | 11 | @Entity 12 | @Getter 13 | @Builder 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class Calendar { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | private Date date; 22 | } 23 | -------------------------------------------------------------------------------- /server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/util/BadgeQueryDtoTestImpl.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.util; 2 | 3 | import com.morakmorak.morak_back_end.dto.BadgeQueryDto; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | 7 | @Builder 8 | @AllArgsConstructor 9 | public class BadgeQueryDtoTestImpl implements BadgeQueryDto { 10 | private Long ranking; 11 | private Long badgeId; 12 | private String name; 13 | 14 | @Override 15 | public Long getBadge_Id() { 16 | return badgeId; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return name; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/MorakBackEndApplication.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | 9 | @EnableScheduling 10 | @EnableJpaAuditing 11 | @SpringBootApplication 12 | public class MorakBackEndApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(MorakBackEndApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/CategoryName.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Optional; 6 | 7 | @Getter 8 | public enum CategoryName { 9 | INFO("정보"), 10 | QNA("질문글"), 11 | ; 12 | 13 | private String name; 14 | 15 | CategoryName(String name) { 16 | this.name = name; 17 | } 18 | public static Optional check(String name) { 19 | try { return Optional.of(CategoryName.valueOf(name)); } 20 | catch (Exception e) {/* do nothing */} 21 | return Optional.empty(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/article/ArticleLikeRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.article; 2 | 3 | import com.morakmorak.morak_back_end.entity.ArticleLike; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Optional; 8 | 9 | public interface ArticleLikeRepository extends JpaRepository { 10 | 11 | @Query("select a from ArticleLike a where a.user.id = :userId and a.article.id = :articleId") 12 | Optional checkUserLiked(Long userId, Long articleId); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/schedule/CrawlingScheduler.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.schedule; 2 | 3 | import com.morakmorak.morak_back_end.crawler.Crawler; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Slf4j 10 | @Component 11 | @RequiredArgsConstructor 12 | public class CrawlingScheduler { 13 | private final Crawler crawler; 14 | 15 | @Scheduled(cron = "0 0 4 * * MON-SAT") 16 | public void runCrawler() { 17 | crawler.run(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/util/TagQueryDtoTestImpl.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.util; 2 | 3 | import com.morakmorak.morak_back_end.dto.TagQueryDto; 4 | import lombok.*; 5 | 6 | @Getter 7 | @Builder 8 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 9 | @AllArgsConstructor 10 | public class TagQueryDtoTestImpl implements TagQueryDto { 11 | private Long tagId; 12 | private String name; 13 | private Long ranking; 14 | 15 | @Override 16 | public Long getTag_Id() { 17 | return tagId; 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/libs/elapsedTime.ts: -------------------------------------------------------------------------------- 1 | export const elapsedTime = (pastDate: string) => { 2 | const now = new Date(); 3 | const past = new Date(pastDate); 4 | const diff = now.getTime() - past.getTime(); 5 | const sec = Math.floor(diff / 1000) + 1; 6 | const min = Math.floor(sec / 60); 7 | const hour = Math.floor(min / 60); 8 | const day = Math.floor(hour / 24); 9 | const month = Math.floor(day / 30); 10 | const year = Math.floor(month / 12); 11 | 12 | if (year) return `${year}년 전`; 13 | if (month) return `${month}달 전`; 14 | if (day) return `${day}일 전`; 15 | if (hour) return `${hour}시간 전`; 16 | if (min) return `${min}분 전`; 17 | if (sec) return `${sec}초 전`; 18 | }; 19 | -------------------------------------------------------------------------------- /client/libs/getConfirmAlert.ts: -------------------------------------------------------------------------------- 1 | import { confirmAlert } from 'react-confirm-alert'; 2 | 3 | type getConfirmAlertFactors = { 4 | title: string; 5 | message: string; 6 | submitCallback: () => void; 7 | }; 8 | 9 | export const getConfirmAlert = ({ 10 | title, 11 | message, 12 | submitCallback, 13 | }: getConfirmAlertFactors) => { 14 | const confrim = confirmAlert({ 15 | title: '', 16 | message: '', 17 | buttons: [ 18 | { 19 | label: '확인', 20 | onClick: () => alert('Click Yes'), 21 | }, 22 | { 23 | label: '취소', 24 | onClick: () => alert('Click No'), 25 | }, 26 | ], 27 | }); 28 | return { confirm }; 29 | }; 30 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/mapper/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.mapper; 2 | 3 | import com.morakmorak.morak_back_end.dto.ArticleDto; 4 | import com.morakmorak.morak_back_end.entity.Category; 5 | 6 | import com.morakmorak.morak_back_end.entity.enums.CategoryName; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.Mapping; 9 | 10 | @Mapper(componentModel = "spring") 11 | public interface CategoryMapper { 12 | 13 | 14 | default Category RequestUploadArticleToCategory(ArticleDto.RequestUploadArticle uploadArticle) { 15 | return Category.builder().name(uploadArticle.getCategory()).build(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/exception/InvalidJwtTokenException.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.security.exception; 2 | 3 | import com.morakmorak.morak_back_end.exception.ErrorCode; 4 | import io.jsonwebtoken.JwtException; 5 | import lombok.Getter; 6 | 7 | public class InvalidJwtTokenException extends JwtException { 8 | @Getter 9 | private final ErrorCode errorCode; 10 | 11 | public InvalidJwtTokenException(ErrorCode errorCode) { 12 | super(null); 13 | this.errorCode = errorCode; 14 | } 15 | 16 | @Override 17 | public synchronized Throwable fillInStackTrace() { 18 | return this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/config/JpaQueryFactoryConfig.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.config; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | 10 | @TestConfiguration 11 | public class JpaQueryFactoryConfig { 12 | @PersistenceContext 13 | private EntityManager entityManager; 14 | 15 | @Bean 16 | public JPAQueryFactory jpaQueryFactory() { 17 | return new JPAQueryFactory(entityManager); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/config/JpaQuerydslConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.config; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | 10 | @Configuration 11 | public class JpaQuerydslConfiguration { 12 | 13 | @PersistenceContext 14 | private EntityManager entityManager; 15 | 16 | @Bean 17 | public JPAQueryFactory jpaQueryFactory() { 18 | return new JPAQueryFactory(entityManager); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/components/edit-privacy/OriginalPasswordInput.tsx: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | 4 | const OriginalPasswordInput = () => { 5 | const { register } = useForm(); 6 | return ( 7 | 18 | ); 19 | }; 20 | 21 | export default OriginalPasswordInput; 22 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/BookmarkDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | import lombok.*; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | public class BookmarkDto { 8 | @Builder 9 | @Getter 10 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 11 | @AllArgsConstructor(access = AccessLevel.PROTECTED) 12 | @ToString 13 | public static class ResponsePostBookmark { 14 | 15 | private Long articleId; 16 | 17 | private Long userId; 18 | 19 | private boolean scrappedByThisUser; 20 | 21 | private LocalDateTime createdAt; 22 | 23 | private LocalDateTime lastModifiedAt; 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | // 어디서 사용할지 4 | // pages, components 디렉토리(**)의 모든파일(*) 해당확장자 5 | content: [ 6 | './pages/**/*.{js,jsx,ts,tsx}', 7 | './components/**/*.{js,jsx,ts,tsx}', 8 | ], 9 | 10 | theme: { 11 | extend: { 12 | screens: { 13 | mobile: { 14 | max: '500px', 15 | }, 16 | }, 17 | colors: { 18 | 'main-orange': '#FF9F10', 19 | 'main-yellow': '#FFDF6B', 20 | 'font-gray': '#3A3A3A', 21 | 'main-gray': '#A0A0A0', 22 | 'background-gray': '#F2F2F2', 23 | }, 24 | }, 25 | }, 26 | plugins: [require('@tailwindcss/typography')], 27 | }; 28 | -------------------------------------------------------------------------------- /client/components/common/SocialLoginBtn.tsx: -------------------------------------------------------------------------------- 1 | import { faComment } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | 4 | import Link from 'next/link'; 5 | 6 | export const SocialLoginBtn = () => { 7 | return ( 8 |
9 | 10 | 16 | 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /client/libs/tokens.ts: -------------------------------------------------------------------------------- 1 | export const getAccessToken = () => { 2 | let accessToken: any = ''; 3 | if (typeof window !== 'undefined') { 4 | accessToken = localStorage.getItem('accessToken'); 5 | } 6 | 7 | return accessToken; 8 | }; 9 | 10 | export const getRefreshToken = () => { 11 | let accessToken: any = ''; 12 | if (typeof window !== 'undefined') { 13 | accessToken = localStorage.getItem('refreshToken'); 14 | } 15 | 16 | return accessToken; 17 | }; 18 | 19 | export const setTokens = (newAccessToken: string, newRefreshToken?: string) => { 20 | localStorage.setItem('accessToken', newAccessToken); 21 | 22 | if (newRefreshToken) { 23 | localStorage.setItem('refreshToken', newRefreshToken); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/FileDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | import lombok.*; 4 | 5 | public class FileDto { 6 | 7 | 8 | @Getter 9 | @Builder 10 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 11 | @AllArgsConstructor (access = AccessLevel.PROTECTED) 12 | public static class RequestFileWithId { 13 | private Long fileId; 14 | } 15 | 16 | @Getter 17 | @Builder 18 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 19 | @AllArgsConstructor (access = AccessLevel.PROTECTED) 20 | public static class ResponseFileDto { 21 | private Long fileId; 22 | private String preSignedUrl; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/hooks/useCheckAuth.ts: -------------------------------------------------------------------------------- 1 | import { userEmailAtom } from '@atoms/userAtom'; 2 | import { AuthResp } from '@type/login'; 3 | 4 | import { useRouter } from 'next/router'; 5 | import { useState } from 'react'; 6 | import { useForm } from 'react-hook-form'; 7 | import { useSetRecoilState } from 'recoil'; 8 | 9 | export const useCheckAuth = () => { 10 | const { register, handleSubmit } = useForm({ mode: 'onChange' }); 11 | const [isSubmitting, setIsSubmitting] = useState(false); 12 | const setEmail = useSetRecoilState(userEmailAtom); 13 | const router = useRouter(); 14 | return { 15 | register, 16 | handleSubmit, 17 | isSubmitting, 18 | setIsSubmitting, 19 | setEmail, 20 | router, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Category; 4 | import com.morakmorak.morak_back_end.entity.enums.CategoryName; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.Optional; 9 | 10 | public interface CategoryRepository extends JpaRepository { 11 | 12 | Optional findCategoryByName(CategoryName category); 13 | 14 | @Query("select c from Category c where c.name = :category ") 15 | Optional findCategoryByCategoryName(String category); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /client/libs/tagOptions.ts: -------------------------------------------------------------------------------- 1 | export const options = [ 2 | { tagId: 0, name: 'JAVA' }, 3 | { tagId: 1, name: 'C' }, 4 | { tagId: 2, name: 'NODE' }, 5 | { tagId: 3, name: 'SPRING' }, 6 | { tagId: 4, name: 'REACT' }, 7 | { tagId: 5, name: 'JAVASCRIPT' }, 8 | { tagId: 6, name: 'CPLUSPLUS' }, 9 | { tagId: 7, name: 'CSHOP' }, 10 | { tagId: 8, name: 'NEXT' }, 11 | { tagId: 9, name: 'NEST' }, 12 | { tagId: 10, name: 'PYTHON' }, 13 | { tagId: 11, name: 'SWIFT' }, 14 | { tagId: 12, name: 'KOTLIN' }, 15 | { tagId: 13, name: 'CSS' }, 16 | { tagId: 14, name: 'HTML' }, 17 | { tagId: 15, name: 'AWS' }, 18 | { tagId: 16, name: 'REDUX' }, 19 | { tagId: 17, name: 'SCALA' }, 20 | { tagId: 18, name: 'GO' }, 21 | { tagId: 19, name: 'TYPESCRIPT' }, 22 | ]; 23 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/ReviewBadgeDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | import com.querydsl.core.annotations.QueryProjection; 4 | import lombok.*; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | 8 | public class ReviewBadgeDto { 9 | @Getter 10 | @Builder 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public static class SimpleReviewBadgeDto { 13 | @NotBlank 14 | private Long reviewBadgeId; 15 | private String name; 16 | 17 | @QueryProjection 18 | public SimpleReviewBadgeDto(Long reviewBadgeId, String name) { 19 | this.reviewBadgeId = reviewBadgeId; 20 | this.name = name; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/BookmarkRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository; 2 | 3 | import com.morakmorak.morak_back_end.entity.Bookmark; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Optional; 8 | 9 | public interface BookmarkRepository extends JpaRepository { 10 | Optional findByUserIdAndArticleId(Long userId, Long articleId); 11 | Optional findByArticleId(Long id); 12 | 13 | @Query("select b from Bookmark b where b.user.id = :userId and b.article.id = :articleId") 14 | Optional checkUserBookmarked(Long userId, Long articleId); 15 | } 16 | -------------------------------------------------------------------------------- /client/api/signUpApi.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { makePostRequest } from './makePostRequest'; 3 | 4 | type UserCredentials = { 5 | email: string; 6 | password: string; 7 | confirmPassword?: string; 8 | authKey?: string; 9 | nickname: string; 10 | }; 11 | 12 | export const signUpWithEmail = async (cridentials: UserCredentials) => { 13 | makePostRequest(`/api/auth/mail`, { cridentials }); 14 | }; 15 | 16 | export const signUpWithEmailAndKey = async (cridentials: UserCredentials) => { 17 | makePostRequest(`/api/auth`, { cridentials }); 18 | }; 19 | 20 | export const authSetKey = async ( 21 | email: string, 22 | authKey: string | undefined, 23 | ) => { 24 | const res = await axios.put(`/api/auth/mail`, { email, authKey }); 25 | return res; 26 | }; 27 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/GuestBoard.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class GuestBoard extends BaseTime { 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | @Column(name = "guest_board_id") 16 | private Long id; 17 | 18 | private String content; 19 | 20 | private Long receiverId; 21 | 22 | private Integer likeCount; 23 | 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | @JoinColumn(name = "user_id") 26 | private User user; 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/article/ArticleRepository.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.repository.article; 2 | 3 | 4 | import com.morakmorak.morak_back_end.entity.Article; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.Optional; 9 | 10 | public interface ArticleRepository extends JpaRepository , ArticleQueryRepository { 11 | Optional
findByUserId(Long articleId); 12 | 13 | Optional
findArticleByContent(String content); 14 | 15 | @Query("select a from Article a left join fetch a.user where a.id = :articleId") 16 | Optional
findArticleRelationWithUser(Long articleId); 17 | } 18 | -------------------------------------------------------------------------------- /client/components/edit-privacy/NewPaswordCheckInput.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | 4 | const NewPaswordCheckInput = () => { 5 | const { register, watch } = useForm(); 6 | const passwordCheck = useRef({}); 7 | passwordCheck.current = watch('newPassword', ''); 8 | return ( 9 | 17 | value === passwordCheck.current || '비밀번호가 일치하지 않습니다.', 18 | })} 19 | /> 20 | ); 21 | }; 22 | 23 | export default NewPaswordCheckInput; 24 | -------------------------------------------------------------------------------- /client/components/signup-email/AuthenticationTimer.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export const AuthenticationTimer = () => { 4 | const [minutes, setMinutes] = useState(5); 5 | const [seconds, setSeconds] = useState(0); 6 | 7 | useEffect(() => { 8 | const countDown = setTimeout(() => { 9 | if (seconds > 0) setSeconds(seconds - 1); 10 | if (seconds === 0) { 11 | if (minutes === 0) { 12 | clearTimeout(countDown); 13 | } else { 14 | setMinutes(minutes - 1); 15 | setSeconds(59); 16 | } 17 | } 18 | }, 1000); 19 | return () => clearTimeout(countDown); 20 | }, [minutes, seconds]); 21 | 22 | return ( 23 |

24 | {minutes}:{seconds < 10 ? `0${seconds}` : seconds} 25 |

26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /client/pages/post/index.tsx: -------------------------------------------------------------------------------- 1 | import { GetServerSideProps, NextPage } from 'next'; 2 | 3 | import { Editor } from '@components/common/QuillEditor/Editor'; 4 | import { Seo } from '@components/common/Seo'; 5 | import { useEffect } from 'react'; 6 | import { useCheckClickIsLogin } from '@libs/useCheckIsLogin'; 7 | 8 | const Ask: NextPage = () => { 9 | const checkIsLogin = useCheckClickIsLogin(); 10 | useEffect(() => { 11 | checkIsLogin(); 12 | }, []); 13 | return ( 14 | <> 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export const getServerSideProps: GetServerSideProps = async (context) => { 22 | const content = context.req.url?.split('/')[1]; 23 | return { 24 | props: { 25 | content, 26 | }, 27 | }; 28 | }; 29 | 30 | export default Ask; 31 | -------------------------------------------------------------------------------- /client/components/common/QuillEditor/TitleInputBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | 4 | type OnChangeProps = { 5 | onChange: (event: React.ChangeEvent) => void; 6 | }; 7 | 8 | const TitleInputBox = ({ onChange }: OnChangeProps) => { 9 | const { register } = useForm(); 10 | return ( 11 | 24 | ); 25 | }; 26 | 27 | export default TitleInputBox; 28 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/TagName.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum TagName { 7 | JAVA("Java"), 8 | C("C"), 9 | NODE("Node.JS"), 10 | SPRING("Spring"), 11 | REACT("react.Js"), 12 | JAVASCRIPT("javascript"), 13 | CPLUSPLUS("C++"), 14 | CSHOP("C#"), 15 | NEXT("next.js"), 16 | NEST("nest.js"), 17 | PYTHON("python"), 18 | SWIFT("swift"), 19 | KOTLIN("kotiln"), 20 | CSS("css"), 21 | HTML("html"), 22 | AWS("aws"), 23 | REDUX("redux"), 24 | SCALA("scala"), 25 | GO("go"), 26 | TYPESCRIPT("typescript"); 27 | 28 | private String name; 29 | 30 | TagName(String name) { 31 | this.name = name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/components/common/QuillEditor/FormButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | type OnCancelClickProps = { 2 | onCancelClick: () => void; 3 | }; 4 | 5 | const FormButtonGroup = ({ onCancelClick }: OnCancelClickProps) => { 6 | return ( 7 |
8 | 14 | 19 |
20 | ); 21 | }; 22 | 23 | export default FormButtonGroup; 24 | -------------------------------------------------------------------------------- /client/libs/useCheckIsLogin.ts: -------------------------------------------------------------------------------- 1 | import { confirmAlert } from 'react-confirm-alert'; 2 | import { useRouter } from 'next/router'; 3 | import { useRecoilValue } from 'recoil'; 4 | 5 | import { isLoginAtom } from '@atoms/loginAtom'; 6 | 7 | export const useCheckClickIsLogin = () => { 8 | const isLogin = useRecoilValue(isLoginAtom); 9 | const router = useRouter(); 10 | const checkIsLogin = () => { 11 | if (!isLogin) { 12 | confirmAlert({ 13 | message: '로그인이 필요한 서비스 입니다. 바로 로그인 하시겠어요?', 14 | buttons: [ 15 | { 16 | label: 'YES', 17 | onClick: () => router.push('/login'), 18 | }, 19 | { 20 | label: 'NO', 21 | onClick: () => router.back(), 22 | }, 23 | ], 24 | }); 25 | } 26 | }; 27 | 28 | return checkIsLogin; 29 | }; 30 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/enums/BadgeName.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum BadgeName { 7 | KINDLY("친절한"), 8 | WISE("현명한"), 9 | SMART("똑똑한"), 10 | POO("응가"), 11 | DETAILED("꼼꼼한"), 12 | INTELLEGENT("박식한"), 13 | GENEROUS("너그러운"), 14 | COHERENT("조리있는"), 15 | RELIABLE("믿음직한"), 16 | WITTY("재치있는"), 17 | PASSIONATE("열정적인"), 18 | HELPFUL("도움이 되는"), 19 | WARM("따듯한"), 20 | CONSIDERATE("사려 깊은"), 21 | PROBLEMSOLVER("문제를 해결하는"), 22 | CARING("배려심 있는"), 23 | CREATIVE("창의적인"), 24 | LOGICAL("논리적인"), 25 | UNDERSTANDABLE("이해하기 쉬운"); 26 | 27 | 28 | private String name; 29 | 30 | BadgeName(String name) { 31 | this.name = name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/CalendarService.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.service; 2 | 3 | import com.morakmorak.morak_back_end.dto.JobInfoDto; 4 | import com.morakmorak.morak_back_end.repository.JobQueryRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.sql.Date; 10 | import java.time.LocalDate; 11 | import java.util.List; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class CalendarService { 16 | private final JobQueryRepository jobQueryRepository; 17 | 18 | @Transactional(readOnly = true) 19 | public List findCalendarData(Date date) { 20 | return jobQueryRepository.getJobDateOn(date); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@components/*": ["components/*"], 20 | "@atoms/*": ["atoms/*"], 21 | "@libs/*": ["libs/*"], 22 | "@type/*": ["types/*"], 23 | "@common/*": ["./components/common/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/TagDto.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.dto; 2 | 3 | import com.morakmorak.morak_back_end.entity.enums.TagName; 4 | import com.morakmorak.morak_back_end.service.EnumValid; 5 | import com.querydsl.core.annotations.QueryProjection; 6 | import lombok.*; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | 10 | public class TagDto { 11 | 12 | @Getter 13 | @Builder 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | public static class SimpleTag { 16 | 17 | private Long tagId; 18 | // @EnumValid 19 | @NotBlank 20 | private TagName name; 21 | 22 | @QueryProjection 23 | public SimpleTag(Long tagId, TagName name) { 24 | this.tagId = tagId; 25 | this.name = name; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/AnswerLike.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity 8 | @Getter 9 | @Builder 10 | @Table(name = "answer_like") 11 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 12 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 13 | public class AnswerLike extends BaseTime{ 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | @Column(name = "answer_like_id") 17 | private Long id; 18 | 19 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 20 | @JoinColumn(name = "user_id") 21 | private User user; 22 | 23 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 24 | @JoinColumn(name = "answer_id") 25 | private Answer answer; 26 | } 27 | -------------------------------------------------------------------------------- /server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Avatar.java: -------------------------------------------------------------------------------- 1 | package com.morakmorak.morak_back_end.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class Avatar extends BaseTime{ 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | @Column(name = "avatar_id") 17 | private Long id; 18 | 19 | private String originalFilename; 20 | 21 | private Integer fileSize; 22 | 23 | private String localPath; 24 | 25 | private String remotePath; 26 | 27 | @OneToOne(mappedBy = "avatar", cascade = CascadeType.PERSIST) 28 | private User user; 29 | 30 | public void injectTo(User user) { 31 | this.user = user; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/components/dashboard/AsideMid.tsx: -------------------------------------------------------------------------------- 1 | import { useRecoilValue } from 'recoil'; 2 | 3 | import { userDashboardAtom } from '@atoms/userAtom'; 4 | import { changeTagPrettier } from '@libs/changeTagPrettier'; 5 | 6 | export const AsideMid = () => { 7 | const userDashboard = useRecoilValue(userDashboardAtom); 8 | return ( 9 |
10 |
11 | 기술 태그 12 |
13 |
14 | {userDashboard.tags.map((tag) => ( 15 |
16 | 22 |
23 | ))} 24 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /client/pages/recruit/index.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | 3 | import { Footer } from '@components/common/Footer'; 4 | import { Header } from '@components/common/Header'; 5 | import { Seo } from '@components/common/Seo'; 6 | import { ListRecruit } from '@components/recruit/ListRecruit'; 7 | 8 | const Recruit: NextPage = () => { 9 | return ( 10 | <> 11 | 12 |
13 |
17 |
18 | 👨‍💻 채용 일정 19 |
20 |
21 | 22 |
23 |
24 |