├── 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 |
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 |
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 extends Payload>[] 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 |
20 |
21 |
22 |
23 |
24 |
25 | >
26 | );
27 | };
28 |
29 | export default Recruit;
30 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/auth_user_service/PointService.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.auth_user_service;
2 |
3 | import com.morakmorak.morak_back_end.dto.UserDto;
4 | import com.morakmorak.morak_back_end.entity.User;
5 | import com.morakmorak.morak_back_end.mapper.UserMapper;
6 | import lombok.RequiredArgsConstructor;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.transaction.annotation.Transactional;
9 |
10 | @Service
11 | @Transactional
12 | @RequiredArgsConstructor
13 | public class PointService {
14 | private final UserService userService;
15 | private final UserMapper userMapper;
16 |
17 | public UserDto.ResponsePoint getRemainingPoint(Long userId) {
18 | User user = userService.findVerifiedUserById(userId);
19 | return userMapper.toResponsePoint(user);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/components/review/ProgressBar.tsx:
--------------------------------------------------------------------------------
1 | type ProgressProps = {
2 | pageNumber: number;
3 | };
4 |
5 | export const ProgressBar = ({ pageNumber }: ProgressProps) => {
6 | return (
7 |
8 | {[
9 | '채택 태그 선택',
10 | '후기 남기기',
11 | '모락 보내기 (선택)',
12 | '후기 전송!',
13 | ].map((text, i) =>
14 | i === pageNumber ? (
15 |
19 | ) : (
20 |
21 | {text}
22 |
23 | ),
24 | )}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/TagService.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service;
2 |
3 | import com.morakmorak.morak_back_end.entity.Tag;
4 | import com.morakmorak.morak_back_end.entity.enums.TagName;
5 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
6 | import com.morakmorak.morak_back_end.exception.ErrorCode;
7 | import com.morakmorak.morak_back_end.repository.TagRepository;
8 | import lombok.RequiredArgsConstructor;
9 | import org.springframework.stereotype.Service;
10 |
11 | @Service
12 | @RequiredArgsConstructor
13 | public class TagService {
14 |
15 | private final TagRepository tagRepository;
16 |
17 | public Tag findVerifiedTagByTagName(TagName tagName) {
18 | return tagRepository.findTagByName(tagName)
19 | .orElseThrow(() -> new BusinessLogicException(ErrorCode.TAG_NOT_FOUND));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/config/ApiDocumentUtils.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.config;
2 |
3 | import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
4 | import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
5 |
6 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
7 |
8 | public interface ApiDocumentUtils {
9 | static OperationRequestPreprocessor getDocumentRequest() {
10 | return preprocessRequest(
11 | modifyUris()
12 | .scheme("https")
13 | .host("docs.api.com")
14 | .removePort(),prettyPrint()
15 | );
16 | }
17 |
18 | static OperationResponsePreprocessor getDocumentResponse() {
19 | return preprocessResponse(prettyPrint());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/ResponsePagesWithLinks.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import lombok.*;
4 | import org.springframework.hateoas.RepresentationModel;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | @Getter
10 | @Builder
11 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
12 | @AllArgsConstructor(access = AccessLevel.PROTECTED)
13 | public class ResponsePagesWithLinks extends RepresentationModel> {
14 | private List data = new ArrayList<>();
15 | private PageInfo pageInfo;
16 |
17 | public static ResponsePagesWithLinks of(ResponseMultiplePaging responseMultiplePaging) {
18 | return ResponsePagesWithLinks.builder()
19 | .data(responseMultiplePaging.getData())
20 | .pageInfo(responseMultiplePaging.getPageInfo())
21 | .build();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/PageRequest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import org.springframework.data.domain.Sort;
6 |
7 | @Builder
8 | @AllArgsConstructor
9 | public final class PageRequest {
10 | private int page;
11 | private int size;
12 | private Sort sort;
13 |
14 | private final int MAX_SIZE = 50;
15 |
16 | public void setPage(int page) {
17 | this.page = Math.min(page-1, 0);
18 | }
19 |
20 | public void setSize(int size) {
21 | this.size = Math.min(size, MAX_SIZE);
22 | }
23 |
24 | public void addSort(String sort) {
25 | this.sort = Sort.by(sort);
26 | }
27 |
28 | public org.springframework.data.domain.PageRequest toRealPageRequest() {
29 | return org.springframework.data.domain.PageRequest.of(page, size, sort);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/UserRole.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonBackReference;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 |
8 | @Entity
9 | @Getter
10 | @Builder
11 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
12 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
13 | public class UserRole extends BaseTime{
14 |
15 | @Id
16 | @GeneratedValue(strategy = GenerationType.IDENTITY)
17 | @Column(name = "user_role_id")
18 | private Long id;
19 |
20 | @JsonBackReference(value = "user")
21 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
22 | @JoinColumn(name = "user_id")
23 | private User user;
24 |
25 | @JsonBackReference(value = "role")
26 | @ManyToOne(fetch = FetchType.LAZY)
27 | @JoinColumn(name = "role_id")
28 | private Role role;
29 | }
30 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/domain/TokenGenerator.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.domain;
2 |
3 | import com.morakmorak.morak_back_end.entity.User;
4 | import com.morakmorak.morak_back_end.security.util.JwtTokenUtil;
5 | import lombok.RequiredArgsConstructor;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | @RequiredArgsConstructor
10 | public class TokenGenerator {
11 | private final JwtTokenUtil jwtTokenUtil;
12 |
13 | public String generateAccessToken(User user) {
14 | return user.injectUserInformationForAccessToken(jwtTokenUtil);
15 | }
16 |
17 | public String generateRefreshToken(User user) {
18 | return user.injectUserInformationForRefreshToken(jwtTokenUtil);
19 | }
20 |
21 | public boolean tokenValidation(String refreshToken) {
22 | jwtTokenUtil.parseRefreshToken(refreshToken);
23 | return true;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/client/libs/inspectNicknameDuplication.ts:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-toastify';
2 | import { client } from './client';
3 |
4 | interface IError {
5 | response: {
6 | data: {
7 | status: number;
8 | };
9 | };
10 | }
11 | export const inspectNicknameDuplication = async (
12 | nickname: string,
13 | editNickname: string,
14 | ) => {
15 | if (nickname !== editNickname) {
16 | try {
17 | await client.post('/api/auth/nickname', {
18 | nickname: editNickname,
19 | });
20 | } catch (error) {
21 | const customError = error as IError;
22 | switch (customError.response.data.status) {
23 | case 400:
24 | toast.error('닉네임은 최소 1글자, 최대 7글자, 자음, 모음 불가입니다');
25 | break;
26 | case 409:
27 | toast.error('죄송합니다 중복된 닉네임이네요 😭');
28 | break;
29 | default:
30 | toast.error('알 수 없는 오류가 다시 시도해 주세요 😭');
31 | }
32 | }
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/controller/utility/PageRequestGenerator.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.controller.utility;
2 |
3 | import org.springframework.data.domain.PageRequest;
4 | import org.springframework.data.domain.Sort;
5 | import org.springframework.util.StringUtils;
6 |
7 | public class PageRequestGenerator {
8 | private static final Integer MAX_SIZE = 50;
9 |
10 | public static PageRequest of(String sort, Integer page, Integer size) {
11 | Sort s = (!StringUtils.hasText(sort)) ? Sort.by("point") : Sort.by(sort);
12 | page = Math.max(page-1, 0);
13 | size = Math.min(size, MAX_SIZE);
14 |
15 | return PageRequest.of(page, size, s);
16 | }
17 |
18 | public static PageRequest of(Integer page, Integer size) {
19 | page = Math.max(page-1, 0);
20 | size = Math.min(size, MAX_SIZE);
21 |
22 | return PageRequest.of(page, size);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Category.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.morakmorak.morak_back_end.entity.enums.CategoryName;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | @Entity
11 | @Getter
12 | @Builder
13 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
14 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
15 | public class Category extends BaseTime {
16 |
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | @Column(name = "category_id")
20 | private Long id;
21 |
22 | @Enumerated(EnumType.STRING)
23 | private CategoryName name;
24 |
25 | @OneToMany(mappedBy = "category")
26 | @Builder.Default
27 | private List articleList = new ArrayList<>();
28 |
29 | public Category(CategoryName name) {
30 | this.name = name;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/components/common/BtnTopDown.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2 | import { faArrowUpLong } from '@fortawesome/free-solid-svg-icons';
3 | import { faArrowDownLong } from '@fortawesome/free-solid-svg-icons';
4 |
5 | const classNames =
6 | 'p-3 flex justify-center items-center bg-main-yellow opacity-70 hover:opacity-100 transition-all';
7 |
8 | export const BtnTopDown = () => {
9 | return (
10 |
11 |
17 |
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Tag.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.morakmorak.morak_back_end.entity.enums.TagName;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | @Entity
11 | @Getter
12 | @Builder
13 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
14 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
15 | public class Tag extends BaseTime{
16 |
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | @Column(name = "tag_id")
20 | private Long id;
21 |
22 | @Enumerated(EnumType.STRING)
23 | private TagName name;
24 |
25 | @Builder.Default
26 | @OneToMany(mappedBy = "tag")
27 | private List articleTags = new ArrayList<>();
28 |
29 | public Tag(TagName name) {
30 | this.name = name;
31 | this.articleTags = new ArrayList<>();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/CategoryService.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service;
2 |
3 | import com.morakmorak.morak_back_end.entity.Category;
4 | import com.morakmorak.morak_back_end.entity.enums.CategoryName;
5 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
6 | import com.morakmorak.morak_back_end.exception.ErrorCode;
7 | import com.morakmorak.morak_back_end.repository.CategoryRepository;
8 | import lombok.RequiredArgsConstructor;
9 | import org.springframework.stereotype.Service;
10 |
11 | @Service
12 | @RequiredArgsConstructor
13 | public class CategoryService {
14 |
15 | private final CategoryRepository categoryRepository;
16 |
17 | public Category findVerifiedCategoryByName(CategoryName categoryName) {
18 | return categoryRepository.findCategoryByName(categoryName)
19 | .orElseThrow(() -> new BusinessLogicException(ErrorCode.CATEGORY_NOT_FOUND));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/NotificationDto.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import com.querydsl.core.annotations.QueryProjection;
4 | import lombok.*;
5 |
6 | import java.time.LocalDateTime;
7 | import java.util.List;
8 |
9 | public class NotificationDto {
10 |
11 | @Builder
12 | @Getter
13 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
14 | public static class SimpleResponse {
15 | private Long notificationId;
16 | private String message;
17 | private Boolean isChecked;
18 | private LocalDateTime createdAt;
19 |
20 | @QueryProjection
21 | public SimpleResponse(Long notificationId, String message, Boolean isChecked, LocalDateTime createdAt) {
22 | this.notificationId = notificationId;
23 | this.message = message;
24 | this.isChecked = isChecked;
25 | this.createdAt = createdAt;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/crawler/JobKoreaConstant.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.crawler;
2 |
3 | public class JobKoreaConstant {
4 | final static String BASE_URL = "https://www.jobkorea.co.kr/starter/calendar";
5 | final static String MONTHLY_DATE = ".lyDate";
6 | final static String JOB_FILTER_DEVELOPER_CHECKBOX = "label[for=lb_Jobtype_10016]";
7 | final static String JOB_FILTER_ACCEPT_BTN = ".btnFilterSc";
8 | final static String JOB_CALENDAR_DAY_DIV_MORE_BTN = ".moreNum";
9 | final static String JOB_STATE = ".coLink.devBoothItem strong";
10 | final static String JOB_NAME = ".coLink.devBoothItem span";
11 | final static String JOB_CAREER_REQUIREMENT = ".sDesc strong";
12 | final static String JOB_RECRUITMENT_PERIOD = ".side .day";
13 | final static String JOB_HREF = ".coLink.devBoothItem";
14 | final static String NEXT_DAY_BTN = ".dateNext";
15 | final static String PREV_MONTH_BTN = ".calMonthBtn .prev";
16 | }
17 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Badge.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 |
4 | import com.morakmorak.morak_back_end.entity.enums.BadgeName;
5 | import lombok.*;
6 |
7 | import javax.persistence.*;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | @Entity
12 | @Getter
13 | @Builder
14 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
15 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
16 | public class Badge extends BaseTime{
17 |
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.IDENTITY)
20 | @Column(name = "badge_id")
21 | private Long id;
22 |
23 | @Enumerated(EnumType.STRING)
24 | private BadgeName name;
25 |
26 | @Builder.Default
27 | @OneToMany(mappedBy = "badge")
28 | private List reviewBadges = new ArrayList<>();
29 |
30 | public Badge(BadgeName name) {
31 | this.name = name;
32 | this.reviewBadges = new ArrayList<>();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/ReviewBadge.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 ReviewBadge extends BaseTime{
13 |
14 | @Id
15 | @GeneratedValue(strategy = GenerationType.IDENTITY)
16 | @Column(name = "review_badge_id")
17 | private Long id;
18 |
19 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
20 | @JoinColumn(name = "review_id")
21 | private Review review;
22 |
23 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
24 | @JoinColumn(name = "badge_id")
25 | private Badge badge;
26 |
27 | public void mapBadgeAndReview() {
28 | this.review.getReviewBadges().add(this);
29 | this.badge.getReviewBadges().add(this);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/JobInfoDto.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import com.querydsl.core.annotations.QueryProjection;
4 | import lombok.*;
5 |
6 | import java.util.Date;
7 |
8 | @Getter
9 | @Builder
10 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
11 | public class JobInfoDto {
12 | private Long jobId;
13 | private String name;
14 | private String state;
15 | private String careerRequirement;
16 | private String url;
17 | private Date startDate;
18 | private Date endDate;
19 |
20 | @QueryProjection
21 | public JobInfoDto(Long jobId, String name, String state, String careerRequirement, String url, Date startDate, Date endDate) {
22 | this.jobId = jobId;
23 | this.name = name;
24 | this.state = state;
25 | this.careerRequirement = careerRequirement;
26 | this.url = url;
27 | this.startDate = startDate;
28 | this.endDate = endDate;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Bookmark.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 Bookmark extends BaseTime{
13 |
14 | @Id
15 | @GeneratedValue(strategy = GenerationType.IDENTITY)
16 | @Column(name = "book_mark_id")
17 | private Long id;
18 |
19 | private String memo;
20 |
21 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
22 | @JoinColumn(name = "user_id")
23 | private User user;
24 |
25 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
26 | @JoinColumn(name = "article_id")
27 | private Article article;
28 |
29 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
30 | @JoinColumn(name = "answer_id")
31 | private Answer answer;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/config/SecurityTestConfig.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.config;
2 |
3 | import org.springframework.boot.test.context.TestConfiguration;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
7 | import org.springframework.security.web.SecurityFilterChain;
8 |
9 | @TestConfiguration
10 | @EnableWebSecurity
11 | public class SecurityTestConfig {
12 |
13 | @Bean
14 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
15 | return http
16 | .httpBasic().disable()
17 | .formLogin().disable()
18 | .csrf().disable()
19 | .cors().disable()
20 | .authorizeRequests()
21 | .anyRequest().permitAll()
22 | .and().build();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/components/common/Footer/DeveloperLink.tsx:
--------------------------------------------------------------------------------
1 | type DeveloperInfoProps = {
2 | github: string;
3 | blog: string;
4 | children: string;
5 | };
6 |
7 | import Link from 'next/link';
8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
9 | import { faGithub } from '@fortawesome/free-brands-svg-icons';
10 | import { faBloggerB } from '@fortawesome/free-brands-svg-icons';
11 |
12 | export const DeveloperLink = ({
13 | github,
14 | blog,
15 | children,
16 | }: DeveloperInfoProps) => {
17 | return (
18 |
19 |
{children}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Role.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.morakmorak.morak_back_end.entity.enums.RoleName;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import static javax.persistence.EnumType.*;
11 |
12 | @Entity
13 | @Getter
14 | @Builder
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
17 | public class Role extends BaseTime{
18 |
19 | @Id
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | @Column(name = "role_id")
22 | private Long id;
23 |
24 | @Enumerated(EnumType.STRING)
25 | private RoleName roleName;
26 |
27 | @Builder.Default
28 | @OneToMany(mappedBy = "role", cascade = CascadeType.PERSIST)
29 | private List userRoles = new ArrayList<>();
30 |
31 | public Role(RoleName rolename) {
32 | this.roleName = rolename;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/client/components/questions/question-detail/QuestionContent/useHandleClickBookmark.ts:
--------------------------------------------------------------------------------
1 | import { isLoginAtom } from '@atoms/loginAtom';
2 | import { client } from '@libs/client';
3 | import { toast } from 'react-toastify';
4 | import { useRecoilValue } from 'recoil';
5 | import { useCheckClickIsLogin } from '@libs/useCheckIsLogin';
6 | import React, { SetStateAction } from 'react';
7 |
8 | export const useHandleClickBookmark = (articleId: string) => {
9 | const isLogin = useRecoilValue(isLoginAtom);
10 | const checkIsLogin = useCheckClickIsLogin();
11 |
12 | const handleClickBookmark = async (
13 | setIsMarked: React.Dispatch>,
14 | ) => {
15 | if (isLogin) {
16 | try {
17 | const res = await client.post(`/api/articles/${articleId}/bookmark`);
18 | setIsMarked(res.data.scrappedByThisUser);
19 | } catch (err) {
20 | toast.error('북마크에 실패하였습니다. 다시 시도해주세요!');
21 | }
22 | } else {
23 | checkIsLogin();
24 | }
25 | };
26 |
27 | return { handleClickBookmark };
28 | };
29 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/ResponseMultiplePaging.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 |
4 | import lombok.*;
5 | import org.springframework.data.domain.Page;
6 |
7 | import java.io.Serializable;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | @Getter
12 | @Builder
13 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
14 | @AllArgsConstructor(access = AccessLevel.PROTECTED)
15 | public class ResponseMultiplePaging implements Serializable {
16 | private List data = new ArrayList<>();
17 |
18 | private PageInfo pageInfo;
19 |
20 | public ResponseMultiplePaging(List data, Page page) {
21 | this.data = data;
22 | this.pageInfo = PageInfo.builder()
23 | .page(page.getNumber() + 1)
24 | .size(page.getSize())
25 | .totalElements(page.getTotalElements())
26 | .totalPages(page.getTotalPages())
27 | .sort(page.getSort())
28 | .build();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/client/components/common/QuillEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from 'next/dynamic';
2 | import { Loader } from '../Loader';
3 |
4 | const formats = [
5 | 'header',
6 | 'font',
7 | 'size',
8 | 'bold',
9 | 'italic',
10 | 'underline',
11 | 'strike',
12 | 'blockquote',
13 | 'list',
14 | 'bullet',
15 | 'indent',
16 | 'link',
17 | 'image',
18 | 'video',
19 | 'code-block',
20 | ];
21 |
22 | export const QuillEditor = dynamic(
23 | async () => {
24 | const { default: RQ } = await import('react-quill');
25 | const { default: ImageResize } = await import('quill-image-resize');
26 | RQ.Quill.register('modules/ImageResize', ImageResize);
27 | return function comp({ forwardRef, ...props }: any) {
28 | return (
29 | <>
30 |
31 | >
32 | );
33 | };
34 | },
35 | {
36 | ssr: false,
37 | loading: () => (
38 |
39 |
40 |
41 | ),
42 | },
43 | );
44 |
--------------------------------------------------------------------------------
/client/pages/login/index.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps, 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 { LoginForm } from '@components/login/LoginForm';
7 |
8 | const Login: NextPage = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | 로그인
16 | 다시 오셨군요! 환영합니다.
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export const getServerSideProps: GetServerSideProps = async (context) => {
26 | const content = context.req.url?.split('/')[1];
27 | return {
28 | props: {
29 | content,
30 | },
31 | };
32 | };
33 |
34 | export default Login;
35 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/util/ActivityQueryDtoTestImpl.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.util;
2 |
3 | import com.morakmorak.morak_back_end.dto.ActivityQueryDto;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Builder;
6 |
7 | @Builder
8 | @AllArgsConstructor
9 | public class ActivityQueryDtoTestImpl implements ActivityQueryDto {
10 | private Integer articleCount;
11 | private Integer answerCount;
12 | private Integer commentCount;
13 | private Integer total;
14 | private String date;
15 |
16 | @Override
17 | public Integer getArticleCount() {
18 | return articleCount;
19 | }
20 |
21 | @Override
22 | public Integer getAnswerCount() {
23 | return answerCount;
24 | }
25 |
26 | @Override
27 | public Integer getCommentCount() {
28 | return commentCount;
29 | }
30 |
31 | @Override
32 | public Integer getTotal() {
33 | return total;
34 | }
35 |
36 | @Override
37 | public String getDate() {
38 | return date;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/components/common/Input.tsx:
--------------------------------------------------------------------------------
1 | import { ValidationMsg } from '../common/ValildationMsg';
2 | import type { UseFormRegisterReturn } from 'react-hook-form';
3 |
4 | type InputProps = {
5 | label: string;
6 | sublabel?: string;
7 | register: UseFormRegisterReturn;
8 | errors?: string | undefined;
9 | type: string;
10 | placeholder: string;
11 | };
12 |
13 | const inputClassName = 'rounded-full w-96 h-10 pl-4 border';
14 | const inputContainerClassName = 'flex flex-col items-start space-y-2';
15 |
16 | export const Input = ({
17 | label,
18 | sublabel,
19 | register,
20 | errors,
21 | type,
22 | placeholder,
23 | }: InputProps) => {
24 | return (
25 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/client/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/components/common/Header/Nav.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useRouter } from 'next/router';
3 | import { useEffect, useState } from 'react';
4 |
5 | export const Nav = () => {
6 | const [pathname, setPathname] = useState('/');
7 | const router = useRouter();
8 | useEffect(() => {
9 | setPathname(router.pathname);
10 | }, []);
11 |
12 | const CATEGORIES = ['질문 / 답변', '정보글', '채용 일정'];
13 |
14 | return (
15 |
16 | {['/questions', '/informations', '/recruit'].map((path, idx) => (
17 | -
18 |
19 |
28 |
29 |
30 | ))}
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/docs/asciidoc/calendar.adoc:
--------------------------------------------------------------------------------
1 | ifndef::snippets[]
2 | :snippets: ./build/generated-snippets
3 | endif::[]
4 |
5 | = 채용 정보 조회 API
6 | :sectnums:
7 | :toc: left
8 | :toclevels: 4
9 | :toc-title: Table of Contents
10 | :source-highlighter: prettify
11 | :app-name: morakmorak
12 | :author: YANGEUNCHAN
13 |
14 | 본 REST API 문서에서 사용하는 HTTP 상태 코드는 가능한한 표준 HTTP와 REST 규약을 따릅니다.
15 | |===
16 | | `200 OK`
17 | | 상태를 성공적으로 처리함.
18 | | `201 CREATED`
19 | | 새 리소스를 성공적으로 생성함.
20 | | `204 NO CONTENT`
21 | | 기존 리소스를 성공적으로 제거하여 반환할 자원이 존재하지 않음.
22 | | `400 BAD REQUEST`
23 | | 잘못된 요청이므로 서버에서 처리할 수 없음.
24 | | `401 UNAUTHORIZED`
25 | | 인증에 실패함.
26 | | `403 FORBIDDEN`
27 | | 권한이 부족하여 요청을 수행할 수 없음.
28 | | `404 NOT FOUND`
29 | | 요청한 자원이 존재하지 않음.
30 | | `409 CONFLICT`
31 | | 서버의 규칙에 의해 해당 요청을 수행할 수 없음.
32 | |===
33 |
34 | YangEunChan
35 |
36 | 2022.11.11
37 |
38 | ***
39 | == 캘린더 조회
40 | === 성공 200
41 |
42 | .http-request
43 | include::{snippets}/채용정보_조회성공_200/http-request.adoc[]
44 |
45 | .http-response
46 | include::{snippets}/채용정보_조회성공_200//http-response.adoc[]
47 |
48 | .response-fields
49 | include::{snippets}/채용정보_조회성공_200//response-fields.adoc[]
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/filter/CrawlerFilter.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.security.filter;
2 |
3 | import org.springframework.util.StringUtils;
4 | import org.springframework.web.filter.OncePerRequestFilter;
5 |
6 | import javax.servlet.FilterChain;
7 | import javax.servlet.ServletException;
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import java.io.IOException;
11 |
12 | public class CrawlerFilter extends OncePerRequestFilter {
13 | @Override
14 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
15 | if (isMethodEqualsGet(request)) {
16 | String userAgent = request.getHeader("user-agent");
17 | if (!StringUtils.hasText(userAgent)) throw new SecurityException();
18 | }
19 |
20 | filterChain.doFilter(request, response);
21 | }
22 |
23 | private boolean isMethodEqualsGet(HttpServletRequest request) {
24 | return request.getMethod().equals("GET");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/components/review/BtnBackArticle.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2 | import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
3 | import Link from 'next/link';
4 | import { useRecoilValue } from 'recoil';
5 |
6 | import { reviewRequestAtom } from '@atoms/reviewAtom';
7 |
8 | type BtnBackArticleProp = {
9 | articleId: string;
10 | };
11 |
12 | export const BtnBackArticle = ({ articleId }: BtnBackArticleProp) => {
13 | const reviewRequest = useRecoilValue(reviewRequestAtom);
14 | if (reviewRequest.dashboardUrl)
15 | return (
16 |
17 |
21 |
22 | );
23 | else
24 | return (
25 |
26 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/ArticleLike.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonBackReference;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 |
8 | @Entity
9 | @Getter
10 | @Builder
11 | @Table(name = "article_like")
12 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
13 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
14 | public class ArticleLike extends BaseTime{
15 | @Id
16 | @GeneratedValue(strategy = GenerationType.IDENTITY)
17 | @Column(name = "article_like_id")
18 | private Long id;
19 |
20 | @JsonBackReference(value = "user")
21 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
22 | @JoinColumn(name = "user_id")
23 | private User user;
24 |
25 | @JsonBackReference(value = "article")
26 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
27 | @JoinColumn(name = "article_id")
28 | private Article article;
29 |
30 | public void mapUserAndArticleWithLike() {
31 | user.getArticleLikes().add(this);
32 | article.getArticleLikes().add(this);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/mapper/CommentMapper.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.mapper;
2 |
3 | import com.morakmorak.morak_back_end.dto.CommentDto;
4 | import com.morakmorak.morak_back_end.entity.Comment;
5 | import org.mapstruct.Mapper;
6 | import org.mapstruct.Mapping;
7 | import org.mapstruct.ReportingPolicy;
8 |
9 | @Mapper(componentModel = "spring", typeConversionPolicy = ReportingPolicy.IGNORE)
10 | public interface CommentMapper {
11 |
12 | @Mapping(source = "comment.id", target = "commentId")
13 | @Mapping(source = "comment.article.id", target = "articleId")
14 | @Mapping(source = "comment.user.id", target = "userInfo.userId")
15 | @Mapping(source = "comment.user.nickname", target = "userInfo.nickname")
16 | @Mapping(source = "comment.user.grade", target = "userInfo.grade")
17 | @Mapping(source = "comment.user.avatar.id", target = "avatar.avatarId")
18 | @Mapping(source = "comment.user.avatar.originalFilename", target = "avatar.filename")
19 | @Mapping(source = "comment.user.avatar.remotePath", target = "avatar.remotePath")
20 | CommentDto.Response commentToCommentDto(Comment comment);
21 | }
22 |
--------------------------------------------------------------------------------
/client/pages/signup/index.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps, 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 { SignUpForm } from '@components/signup/SignupForm';
7 |
8 | const SignUp: NextPage = () => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | 회원가입
17 |
18 | 따뜻한 개발 문화에 동참하세요!
19 |
20 |
21 |
22 |
23 |
24 |
25 | >
26 | );
27 | };
28 |
29 | export const getServerSideProps: GetServerSideProps = async (context) => {
30 | const content = context.req.url?.split('/')[1];
31 | return {
32 | props: {
33 | content,
34 | },
35 | };
36 | };
37 |
38 | export default SignUp;
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/controller/TestController.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.controller;
2 |
3 | import com.morakmorak.morak_back_end.dto.UserDto;
4 | import com.morakmorak.morak_back_end.security.resolver.RequestUser;
5 | import org.springframework.web.bind.annotation.GetMapping;
6 | import org.springframework.web.bind.annotation.PostMapping;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | @RestController
11 | @RequestMapping("/test")
12 | public class TestController {
13 |
14 | @GetMapping("/all")
15 | public String test1() {
16 | return "all";
17 | }
18 |
19 | @GetMapping("/user")
20 | public String test2() {
21 | return "all";
22 | }
23 |
24 | @GetMapping("/manager")
25 | public String test3() {
26 | return "all";
27 | }
28 |
29 | @GetMapping("/admin")
30 | public String admin() {
31 | return "all";
32 | }
33 |
34 | @PostMapping("/resolver")
35 | public String resolver(@RequestUser UserDto.UserInfo userInfo) {
36 | return userInfo.getEmail();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/domain/UserPasswordManager.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.domain;
2 |
3 | import com.morakmorak.morak_back_end.entity.User;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.security.crypto.password.PasswordEncoder;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | @RequiredArgsConstructor
10 | public class UserPasswordManager {
11 | private final PasswordEncoder passwordEncoder;
12 |
13 | public String encryptUserPassword(User user) {
14 | return user.encryptPassword(passwordEncoder);
15 | }
16 |
17 | public Boolean comparePasswordWithUser(User entityUser, User requestUser) {
18 | return entityUser.comparePassword(passwordEncoder, requestUser.getPassword());
19 | }
20 |
21 | public Boolean comparePasswordWithPlainPassword(User entityUser, String plainPassword) {
22 | return entityUser.comparePassword(passwordEncoder, plainPassword);
23 | }
24 |
25 | public String changeUserPassword(User user, String newPassword) {
26 | user.changePassword(newPassword);
27 | return encryptUserPassword(user);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/EmailDto.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import lombok.*;
4 |
5 | import javax.validation.constraints.NotBlank;
6 | import javax.validation.constraints.Pattern;
7 |
8 | import static com.morakmorak.morak_back_end.dto.DtoValidConstants.INVALID_EMAIL;
9 |
10 | public class EmailDto {
11 |
12 | @Getter
13 | @Builder
14 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | public static class RequestSendMail {
17 | @NotBlank
18 | @Pattern(regexp = "^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", message = INVALID_EMAIL)
19 | private String email;
20 | }
21 |
22 | @Getter
23 | @Builder
24 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
25 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
26 | public static class RequestVerifyAuthKey {
27 | @NotBlank
28 | @Pattern(regexp = "^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", message = INVALID_EMAIL)
29 | private String email;
30 | @NotBlank
31 | private String authKey;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/domain/AuthMailSender.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.domain;
2 |
3 | import com.morakmorak.morak_back_end.repository.redis.RedisRepository;
4 | import com.morakmorak.morak_back_end.service.mail_service.MailSender;
5 | import lombok.RequiredArgsConstructor;
6 |
7 | import static com.morakmorak.morak_back_end.service.mail_service.MailSenderImpl.BASIC_AUTH_SUBJECT;
8 | import static com.morakmorak.morak_back_end.service.mail_service.MailSenderImpl.BASIC_PASSWORD_SUBJECT;
9 |
10 | // TODO : 추후 코드 리팩터링을 위해 만들어졌으며 아직 사용되지 않는 클래스입니다.
11 | @RequiredArgsConstructor
12 | public class AuthMailSender {
13 | private final MailSender mailSender;
14 | private final RedisRepository mailAuthKeyStore;
15 | private final RandomKeyGenerator randomKeyGenerator;
16 |
17 | private Boolean sendAuthenticationMail(String emailAddress, String randomKey) {
18 | return mailSender.sendMail(emailAddress, randomKey, BASIC_AUTH_SUBJECT);
19 | }
20 |
21 | private Boolean sendTemporaryPassword(String emailAddress, String randomKey) {
22 | return mailSender.sendMail(emailAddress, randomKey, BASIC_PASSWORD_SUBJECT);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/atoms/reviewAtom.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 |
3 | export const reviewTagsEnumAtom = atom({
4 | key: 'reviewTagsEnum',
5 | default: [
6 | ['친절한', 'KINDLY'],
7 | ['현명한', 'WISE'],
8 | ['똑똑한', 'SMART'],
9 | ['응가', 'POO'],
10 | ['꼼꼼한', 'DETAILED'],
11 | ['박식한', 'INTELLEGENT'],
12 | ['너그러운', 'GENEROUS'],
13 | ['믿음직한', 'COHERENT'],
14 | ['재치있는', 'WITTY'],
15 | ['도움이 되는', 'HELPFUL'],
16 | ['따듯한', 'WARM'],
17 | ['사려 깊은', 'CONSIDERATE'],
18 | ['문제를 해결하는', 'PROBLEMSOLVER'],
19 | ['배려심 있는', 'CARING'],
20 | ['창의적인', 'CREATIVE'],
21 | ['논리적인', 'LOGICAL'],
22 | ['이해하기 쉬운', 'UNDERSTANDABLE'],
23 | ],
24 | });
25 |
26 | export const reviewTagsAtom = atom({
27 | key: 'reviewTags',
28 | default: [{ badgeId: 0, name: '' }],
29 | });
30 |
31 | export const reviewContentAtom = atom({
32 | key: 'reviewContent',
33 | default: '',
34 | });
35 |
36 | export const reviewPointAtom = atom({
37 | key: 'reviewPoint',
38 | default: 0,
39 | });
40 |
41 | export const reviewRequestAtom = atom({
42 | key: 'reviewRequest',
43 | default: {
44 | targetId: -1,
45 | articleId: '',
46 | targetUserName: '',
47 | dashboardUrl: '',
48 | },
49 | });
50 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/config/RedisContainerTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.config;
2 |
3 | import org.springframework.test.context.DynamicPropertyRegistry;
4 | import org.springframework.test.context.DynamicPropertySource;
5 | import org.testcontainers.containers.GenericContainer;
6 | import org.testcontainers.junit.jupiter.Testcontainers;
7 |
8 | @Testcontainers
9 | public abstract class RedisContainerTest {
10 | private final static int REDIS_PORT = 6379;
11 |
12 | static final String REDIS_IMAGE = "redis:6-alpine";
13 | static final GenericContainer> REDIS_CONTAINER;
14 |
15 | static {
16 | REDIS_CONTAINER = new GenericContainer<>(REDIS_IMAGE)
17 | .withExposedPorts(REDIS_PORT)
18 | .withReuse(true);
19 | REDIS_CONTAINER.start();
20 | }
21 |
22 | @DynamicPropertySource
23 | public static void overrideProps(DynamicPropertyRegistry registry) {
24 | registry.add("spring.redis.host", REDIS_CONTAINER::getHost);
25 | registry.add("spring.redis.port", () -> "" + REDIS_CONTAINER.getMappedPort(REDIS_PORT));
26 | registry.add("spring.redis.password", () -> "password");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/ArticleTag.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 ArticleTag extends BaseTime{
13 | @Id
14 | @GeneratedValue(strategy = GenerationType.IDENTITY)
15 | @Column(name = "article_tag_id")
16 | private Long id;
17 |
18 | @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST )
19 | @JoinColumn(name = "article_id")
20 | private Article article;
21 |
22 | @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
23 | @JoinColumn(name = "tag_id")
24 | private Tag tag;
25 |
26 | public void injectTo(Article article) {
27 | this.article = article;
28 | }
29 |
30 | public void mapArticleAndTagWithArticleTag() {
31 | this.article.getArticleTags().add(this);
32 | this.tag.getArticleTags().add(this);
33 | }
34 |
35 | public void removeTo(Article article, Tag tag) {
36 | this.article = null;
37 | this.tag = null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/client/atoms/userAtom.ts:
--------------------------------------------------------------------------------
1 | import { IDataHeader } from '@type/user';
2 | import { UserDashboard } from '@type/dashboard';
3 | import { atom } from 'recoil';
4 |
5 | export const userEmailAtom = atom({
6 | key: 'email',
7 | default: '',
8 | });
9 |
10 | export const userAuthKeyAtom = atom({
11 | key: 'auth',
12 | default: '',
13 | });
14 |
15 | export const userPasswordAtom = atom({
16 | key: 'password',
17 | default: '',
18 | });
19 |
20 | export const userNickNameAtom = atom({
21 | key: 'nickName',
22 | default: '',
23 | });
24 |
25 | export const userDashboardAtom = atom({
26 | key: 'userDashboard',
27 | default: {
28 | userId: 0,
29 | email: '',
30 | nickname: '',
31 | jobType: '',
32 | grade: '',
33 | point: 0,
34 | github: '',
35 | blog: '',
36 | infoMessage: '',
37 | rank: 0,
38 | avatar: {
39 | avatarId: 0,
40 | filename: '',
41 | remotePath: '',
42 | },
43 | tags: [],
44 | reviewBadges: [],
45 | articles: [], // 수정 필요
46 | activities: [], // 수정 필요
47 | reviews: [],
48 | },
49 | });
50 |
51 | // 현재 로그인된 유저의 포인트를 포함한 모든 정보
52 | export const dataHeaderAtom = atom({
53 | key: 'dataHeader',
54 | default: null,
55 | });
56 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/exception/CustomAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.security.exception;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.http.MediaType;
5 | import org.springframework.security.core.AuthenticationException;
6 | import org.springframework.security.web.AuthenticationEntryPoint;
7 | import org.springframework.stereotype.Component;
8 |
9 | import javax.servlet.ServletException;
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 | import java.io.IOException;
13 | import java.nio.charset.StandardCharsets;
14 |
15 | @Slf4j
16 | @Component
17 | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
18 | @Override
19 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
20 | response.setStatus(response.SC_UNAUTHORIZED);
21 | response.setContentType(MediaType.APPLICATION_JSON_VALUE);
22 | response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
23 | response.getWriter().write("{\"error\": \"unauthorized?\"}");
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/client/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
4 |
5 | const withPlugins = require('next-compose-plugins');
6 | const nextConfig = {
7 | reactStrictMode: true,
8 | swcMinify: true,
9 | images: {
10 | domains: [
11 | 'images.unsplash.com',
12 | 'morakmorak.s3.ap-northeast-2.amazonaws.com',
13 | ],
14 | },
15 | eslint: {
16 | ignoreDuringBuilds: true,
17 | },
18 | async rewrites() {
19 | return [
20 | {
21 | source: `/api/auth`,
22 | destination: BASE_URL + '/auth/',
23 | },
24 | {
25 | source: `/api/auth/mail`,
26 | destination: BASE_URL + '/auth/mail',
27 | },
28 | {
29 | source: `/api/auth/token`,
30 | destination: BASE_URL + '/auth/token',
31 | },
32 | {
33 | source: '/api/:path*',
34 | destination: BASE_URL + '/:path*',
35 | },
36 | ];
37 | },
38 | };
39 |
40 | const withTM = require('next-transpile-modules')([
41 | '@fullcalendar/common',
42 | '@babel/preset-react',
43 | '@fullcalendar/common',
44 | '@babel/preset-react',
45 | '@fullcalendar/daygrid',
46 | '@fullcalendar/react',
47 | ]);
48 |
49 | module.exports = withPlugins([withTM], nextConfig);
50 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/domain/PointCalculator.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.domain;
2 |
3 | import com.morakmorak.morak_back_end.entity.*;
4 | import com.morakmorak.morak_back_end.entity.enums.CategoryName;
5 | import org.springframework.stereotype.Component;
6 |
7 | @Component
8 | public class PointCalculator {
9 | final Integer ARTICLE_POINT = 50;
10 | final Integer QUESTION_POINT = 20;
11 | final Integer ANSWER_POINT = 50;
12 | final Integer COMMENT_POINT = 10;
13 | final Integer LIKE_POINT = 1;
14 |
15 | public Integer calculatePaymentPoint(Object obj) {
16 | if (obj.getClass().equals(Answer.class)) return ANSWER_POINT;
17 | if (obj.getClass().equals(Comment.class)) return COMMENT_POINT;
18 | if (obj.getClass().equals(AnswerLike.class)) return LIKE_POINT;
19 | if (obj.getClass().equals(ArticleLike.class)) return LIKE_POINT;
20 | if (obj.getClass().equals(Article.class)) {
21 | if (((Article) obj).getCategory().getName().equals(CategoryName.INFO)) return ARTICLE_POINT;
22 | if (((Article) obj).getCategory().getName().equals(CategoryName.QNA)) return QUESTION_POINT;
23 | }
24 |
25 | throw new IllegalArgumentException("유효하지 않은 객체 타입");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Report.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 |
4 | import com.morakmorak.morak_back_end.entity.enums.ReportReason;
5 | import lombok.*;
6 |
7 | import javax.persistence.*;
8 |
9 | @Entity
10 | @Getter
11 | @Builder
12 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
13 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
14 | public class Report extends BaseTime {
15 |
16 | @Id
17 | @GeneratedValue(strategy = GenerationType.IDENTITY)
18 | @Column(name = "report_id")
19 | private Long id;
20 |
21 | private ReportReason reason;
22 |
23 | private String content;
24 |
25 | @ManyToOne(fetch = FetchType.LAZY)
26 | @JoinColumn(name = "user_id")
27 | private User user;
28 |
29 | @ManyToOne(fetch = FetchType.LAZY)
30 | @JoinColumn(name = "comment_id")
31 | private Comment comment;
32 |
33 | @ManyToOne(fetch = FetchType.LAZY)
34 | @JoinColumn(name = "article_id")
35 | private Article article;
36 |
37 | @ManyToOne(fetch = FetchType.LAZY)
38 | @JoinColumn(name = "answer_id")
39 | private Answer answer;
40 |
41 | public void injectTo(User user, Article article) {
42 | this.user = user;
43 | this.article = article;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/token/JwtAuthenticationToken.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.security.token;
2 |
3 | import org.springframework.security.authentication.AbstractAuthenticationToken;
4 | import org.springframework.security.core.GrantedAuthority;
5 |
6 | import java.util.Collection;
7 |
8 | public class JwtAuthenticationToken extends AbstractAuthenticationToken {
9 | private String jwtToken;
10 | private Object principal;
11 | private Object credentials;
12 |
13 | public JwtAuthenticationToken(Collection extends GrantedAuthority> authorities, Object principal, Object credentials) {
14 | super(authorities);
15 | this.principal = principal;
16 | this.credentials = credentials;
17 | super.setAuthenticated(true);
18 | }
19 |
20 | public JwtAuthenticationToken(String jwtToken) {
21 | super(null);
22 | this.jwtToken = jwtToken;
23 | this.setAuthenticated(false);
24 | }
25 |
26 |
27 | @Override
28 | public Object getCredentials() {
29 | return this.credentials;
30 | }
31 |
32 | @Override
33 | public Object getPrincipal() {
34 | return this.principal;
35 | }
36 |
37 | public String getJwtToken() { return this.jwtToken; }
38 | }
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/File.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 File extends BaseTime{
13 |
14 | @Id
15 | @GeneratedValue(strategy = GenerationType.IDENTITY)
16 | @Column(name = "file_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 | @ManyToOne(fetch = FetchType.LAZY)
28 | @JoinColumn(name = "answer_id")
29 | private Answer answer;
30 |
31 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
32 | @JoinColumn(name = "article_id")
33 | private Article article;
34 |
35 | public File(String remotePath) {
36 | this.remotePath = remotePath;
37 | }
38 |
39 | public void injectTo(Article article) {
40 | this.article = article;
41 | article.getFiles().add(this);
42 | }
43 |
44 | public void injectTo(Answer answer) {
45 | this.answer = answer;
46 | answer.getFiles().add(this);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/components/common/Header/SearchBar.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2 | import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
3 | import { useSetRecoilState } from 'recoil';
4 | import { useRouter } from 'next/router';
5 | import { SubmitHandler, useForm } from 'react-hook-form';
6 |
7 | import { keywordAtom } from '@atoms/keywordAtom';
8 |
9 | interface IFormValue {
10 | keyword: string;
11 | }
12 |
13 | export const SearchBar = () => {
14 | const setKeyword = useSetRecoilState(keywordAtom);
15 | const router = useRouter();
16 | const { register, handleSubmit } = useForm();
17 | const onValid: SubmitHandler = (data) => {
18 | if (!data.keyword.length) {
19 | setKeyword('');
20 | } else {
21 | setKeyword(data.keyword);
22 | router.push('/questions');
23 | }
24 | };
25 | return (
26 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/config/AwsConfig.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.config;
2 |
3 | import com.amazonaws.auth.AWSCredentials;
4 | import com.amazonaws.auth.AWSStaticCredentialsProvider;
5 | import com.amazonaws.auth.BasicAWSCredentials;
6 | import com.amazonaws.regions.Regions;
7 | import com.amazonaws.services.s3.AmazonS3;
8 | import com.amazonaws.services.s3.AmazonS3Builder;
9 | import com.amazonaws.services.s3.AmazonS3ClientBuilder;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 |
14 | @Configuration
15 | public class AwsConfig {
16 | @Value("${cloud.aws.credentials.accessKey}")
17 | private String accessKey;
18 |
19 | @Value("${cloud.aws.credentials.secretKey}")
20 | private String secretKey;
21 |
22 | @Bean
23 | public AWSCredentials awsCredentials() {
24 | return new BasicAWSCredentials(accessKey, secretKey);
25 | }
26 |
27 | @Bean
28 | public AmazonS3 amazonS3() {
29 | return AmazonS3ClientBuilder.standard()
30 | .withCredentials(new AWSStaticCredentialsProvider(awsCredentials()))
31 | .withRegion(Regions.AP_NORTHEAST_2)
32 | .build();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/mail_service/MailSenderImpl.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.mail_service;
2 |
3 | import com.morakmorak.morak_back_end.service.mail_service.MailSender;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.boot.autoconfigure.mail.MailProperties;
6 | import org.springframework.mail.SimpleMailMessage;
7 | import org.springframework.mail.javamail.JavaMailSender;
8 | import org.springframework.stereotype.Service;
9 |
10 | @Service
11 | @RequiredArgsConstructor
12 | public class MailSenderImpl implements MailSender {
13 | private final JavaMailSender mailSender;
14 | private final MailProperties mailProperties;
15 |
16 | public final static String BASIC_AUTH_SUBJECT = "모락모락의 이메일 인증 번호입니다.";
17 | public final static String BASIC_PASSWORD_SUBJECT = "모락모락의 임시 비밀번호입니다.";
18 | @Override
19 | public boolean sendMail(String to, String content, String title) {
20 | SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
21 | simpleMailMessage.setFrom(mailProperties.getUsername());
22 | simpleMailMessage.setTo(to);
23 | simpleMailMessage.setSubject(title);
24 | simpleMailMessage.setText(content);
25 | mailSender.send(simpleMailMessage);
26 |
27 | return true;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/controller/BookmarkController.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.controller;
2 |
3 | import com.morakmorak.morak_back_end.dto.BookmarkDto;
4 | import com.morakmorak.morak_back_end.dto.UserDto;
5 | import com.morakmorak.morak_back_end.security.resolver.RequestUser;
6 | import com.morakmorak.morak_back_end.service.BookmarkService;
7 | import lombok.RequiredArgsConstructor;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.web.bind.annotation.*;
11 |
12 |
13 | @RestController
14 | @RequestMapping("/articles")
15 | @RequiredArgsConstructor
16 | @Slf4j
17 | public class BookmarkController {
18 | private final BookmarkService bookmarkService;
19 |
20 | @PostMapping("/{article-id}/bookmark")
21 | @ResponseStatus(HttpStatus.OK)
22 | public BookmarkDto.ResponsePostBookmark postOrDeleteBookmark(
23 | @RequestUser UserDto.UserInfo user,
24 | @PathVariable("article-id") Long articleId) {
25 | log.info("POST_OR_DELETE_BOOKMARK_INPUT", user);
26 | log.info("POST_OR_DELETE_BOOKMARK_INPUT", articleId);
27 | BookmarkDto.ResponsePostBookmark response =
28 | bookmarkService.pressBookmark(user.getId(),articleId);
29 | return response;
30 |
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/service/auth_service/LogoutTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.auth_service;
2 |
3 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
4 | import org.junit.jupiter.api.DisplayName;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static com.morakmorak.morak_back_end.util.SecurityTestConstants.BEARER_REDIS_TOKEN_EMPTY;
8 | import static com.morakmorak.morak_back_end.util.SecurityTestConstants.BEARER_REDIS_TOKEN_NOT_EMPTY;
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
11 |
12 | public class LogoutTest extends AuthServiceTest {
13 | @Test
14 | @DisplayName("유저 로그아웃 / 요청값으로 들어온 리프레시 토큰이 기존에 존재하지 않는다면 BusinessLogicEcxeption이 발생한다.")
15 | public void test6() {
16 | //given when then
17 | assertThatThrownBy(() -> authService.logoutUser(BEARER_REDIS_TOKEN_EMPTY))
18 | .isInstanceOf(BusinessLogicException.class);
19 | }
20 |
21 | @Test
22 | @DisplayName("유저 로그아웃 / 요청값으로 들어온 리프레시 토큰 데이터가 존재한다면 해당 데이터를 삭제하고 true를 반환한다.")
23 | public void test7() {
24 | //when
25 | boolean result = authService.logoutUser(BEARER_REDIS_TOKEN_NOT_EMPTY);
26 |
27 | //then
28 | assertThat(result).isTrue();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/client/components/edit-privacy/AsideEditProfile.tsx:
--------------------------------------------------------------------------------
1 | import { faUser } from '@fortawesome/free-regular-svg-icons';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import Link from 'next/link';
4 | import { useRouter } from 'next/router';
5 | import { useEffect, useState } from 'react';
6 |
7 | const links = [
8 | { href: '/edit-profile', text: '프로필 수정' },
9 | { href: '/edit-password', text: '비밀번호 수정' },
10 | { href: '/membership-withdrawal', text: '회원 탈퇴' },
11 | ];
12 |
13 | export const AsideEditProfile = () => {
14 | const [pathname, setPathname] = useState('');
15 | const router = useRouter();
16 | const getPathname = () => {
17 | setPathname(router.pathname);
18 | };
19 | useEffect(() => {
20 | getPathname();
21 | }, []);
22 | return (
23 | <>
24 | {links.map((link) => (
25 |
26 |
33 |
34 | {link.text}
35 |
36 |
37 | ))}
38 | >
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/BaseTime.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import lombok.Getter;
5 | import org.springframework.data.annotation.CreatedDate;
6 | import org.springframework.data.annotation.LastModifiedDate;
7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
8 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
9 | import org.springframework.format.annotation.DateTimeFormat;
10 |
11 | import javax.persistence.Column;
12 | import javax.persistence.EntityListeners;
13 | import javax.persistence.MappedSuperclass;
14 | import java.time.LocalDateTime;
15 |
16 | @EntityListeners(AuditingEntityListener.class)
17 | @MappedSuperclass
18 | @Getter
19 | public class BaseTime {
20 | @CreatedDate
21 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
22 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
23 | @Column(updatable = false,name = "created_at")
24 | private LocalDateTime createdAt;
25 |
26 | @LastModifiedDate
27 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
28 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
29 | @Column(name = "last_modified_at")
30 | private LocalDateTime lastModifiedAt;
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/client/components/questions/question-detail/QuestionContent/BtnBookmark.tsx:
--------------------------------------------------------------------------------
1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2 | import { faBookmark as SolidBookmark } from '@fortawesome/free-solid-svg-icons';
3 | import { faBookmark as VoidBookmark } from '@fortawesome/free-regular-svg-icons';
4 | import { useState } from 'react';
5 | import { useRouter } from 'next/router';
6 | import { useFetch } from '@libs/useFetchSWR';
7 | import { useHandleClickBookmark } from './useHandleClickBookmark';
8 |
9 | export const BtnBookmark = () => {
10 | const router = useRouter();
11 | const { articleId } = router.query;
12 |
13 | const { data } = useFetch(`/api/articles/${articleId}`);
14 | const [isMarked, setIsMarked] = useState(data.isBookmarked);
15 |
16 | const { handleClickBookmark } = useHandleClickBookmark(articleId as string);
17 |
18 | return (
19 | <>
20 |
35 | >
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Job.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import lombok.*;
4 | import org.springframework.data.annotation.CreatedDate;
5 |
6 | import javax.persistence.*;
7 | import java.sql.Date;
8 | import java.sql.Timestamp;
9 | import java.time.Instant;
10 | import java.time.LocalDateTime;
11 |
12 | @Getter
13 | @Entity
14 | @Builder
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
17 | public class Job {
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.IDENTITY)
20 | @Column(name = "job_id")
21 | private Long id;
22 |
23 | @Column(name="name", unique = true)
24 | private String name;
25 |
26 | private String state;
27 |
28 | private String careerRequirement;
29 |
30 | private Date startDate;
31 |
32 | private Date endDate;
33 |
34 | private String url;
35 |
36 | private Instant createdAt;
37 |
38 | private Instant lastModifiedAt;
39 |
40 | public Job(String name, String state, String careerRequirement, Date startDate, Date endDate, String url) {
41 | this.name = name;
42 | this.state = state;
43 | this.careerRequirement = careerRequirement;
44 | this.startDate = startDate;
45 | this.endDate = endDate;
46 | this.url = url;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/service/CalendarServiceTest.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 org.assertj.core.api.Assertions;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.BDDMockito;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.jupiter.MockitoExtension;
12 |
13 | import java.sql.Date;
14 | import java.util.List;
15 |
16 | @ExtendWith(MockitoExtension.class)
17 | public class CalendarServiceTest {
18 | @InjectMocks
19 | CalendarService calendarService;
20 |
21 | @Mock
22 | JobQueryRepository jobQueryRepository;
23 |
24 | @Test
25 | void findCalendarData() {
26 | //given
27 | JobInfoDto dto = JobInfoDto.builder().url("url").build();
28 |
29 | BDDMockito.given(jobQueryRepository.getJobDateOn(Date.valueOf("9999-12-31"))).willReturn(List.of(dto));
30 |
31 | //when
32 | List response = calendarService.findCalendarData(Date.valueOf("9999-12-31"));
33 |
34 | //then
35 | Assertions.assertThat(response.size()).isEqualTo(1);
36 | Assertions.assertThat(response.get(0).getUrl()).isEqualTo("url");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/repository/JobQueryRepository.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.repository;
2 |
3 | import com.morakmorak.morak_back_end.dto.JobInfoDto;
4 | import com.morakmorak.morak_back_end.dto.QJobInfoDto;
5 | import com.querydsl.jpa.impl.JPAQueryFactory;
6 | import lombok.RequiredArgsConstructor;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.sql.Date;
10 | import java.time.LocalDate;
11 | import java.util.List;
12 |
13 | import static com.morakmorak.morak_back_end.entity.QJob.*;
14 |
15 | @Repository
16 | @RequiredArgsConstructor
17 | public class JobQueryRepository {
18 | private final JPAQueryFactory jpaQueryFactory;
19 |
20 | public List getJobDateOn(Date date) {
21 | LocalDate startDayOfMonth = date.toLocalDate().withDayOfMonth(1);
22 | LocalDate lastDayOfMonth = date.toLocalDate().plusMonths(1).withDayOfMonth(1).minusDays(1);
23 |
24 | return jpaQueryFactory.select(new QJobInfoDto(
25 | job.id, job.name, job.state, job.careerRequirement, job.url, job.startDate, job.endDate
26 | ))
27 | .from(job)
28 | .where(job.startDate.between(Date.valueOf(startDayOfMonth), Date.valueOf(lastDayOfMonth)))
29 | .groupBy(job.id)
30 | .orderBy(job.startDate.asc())
31 | .fetch();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/config/LoggingConfig.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.config;
2 |
3 | import com.morakmorak.morak_back_end.interceptor.PerformanceLoggingInterceptor;
4 | import org.aspectj.lang.annotation.Aspect;
5 | import org.aspectj.lang.annotation.Pointcut;
6 | import org.springframework.aop.Advisor;
7 | import org.springframework.aop.aspectj.AspectJExpressionPointcut;
8 | import org.springframework.aop.support.DefaultPointcutAdvisor;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 | import org.springframework.context.annotation.EnableAspectJAutoProxy;
12 |
13 | @Aspect
14 | @Configuration
15 | @EnableAspectJAutoProxy
16 | public class LoggingConfig {
17 | @Pointcut("execution(* com.morakmorak.morak_back_end.service..*.*(..))")
18 | public void monitor() { }
19 |
20 | @Bean
21 | public PerformanceLoggingInterceptor performanceLoggingInterceptor() {
22 | return new PerformanceLoggingInterceptor();
23 | }
24 |
25 | @Bean
26 | public Advisor performanceLoggingAdvisor() {
27 | AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
28 | pointcut.setExpression("com.morakmorak.morak_back_end.config.LoggingConfig.monitor()");
29 | return new DefaultPointcutAdvisor(pointcut, performanceLoggingInterceptor());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/security/util/SecurityConstants.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.security.util;
2 |
3 | public class SecurityConstants {
4 | public static final String JWT_HEADER = "Authorization";
5 | public static final String REFRESH_HEADER = "RefreshToken";
6 | public static final String JWT_PREFIX = "Bearer ";
7 |
8 | public final static Long ACCESS_TOKEN_EXPIRE_COUNT = 60000L*10*3; // 30 minutes
9 | public final static Long REFRESH_TOKEN_EXPIRE_COUNT = 7 * 24 * 60 * 60 * 1000L; // 7 days
10 |
11 | public final static String ROLES = "roles";
12 | public final static String ID = "id";
13 | public final static String EMAIL = "email";
14 | public static final String NICKNAME = "nickname";
15 |
16 | public final static String REDIRECT_URL_OAUTH2 = "https://morakmorak.vercel.app/oauth/login";
17 | public final static String ACCESS_TOKEN = "AccessToken";
18 |
19 | public final static Long AUTH_KEY_EXPIRATION_PERIOD = 300000L;
20 | public final static Long VALIDITY_PERIOD_OF_THE_AUTHENTICATION_KEY = 3_600_000L; // 1시간
21 |
22 | public final static Integer TEMPORARY_PASSWORD_LENGTH = 15;
23 | public final static Integer EMAIL_AUTH_KEY_LENGTH = 8;
24 | public final static Integer TEMPORARY_NICKNAME_CODE_LENGTH = 5;
25 |
26 | public final static String DO_NOT_CRAWL = "I am a Teapot, do not crawl";
27 | }
28 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/file_service/FileService.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.file_service;
2 |
3 | import com.morakmorak.morak_back_end.dto.FileDto;
4 | import com.morakmorak.morak_back_end.entity.File;
5 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
6 | import com.morakmorak.morak_back_end.exception.ErrorCode;
7 | import com.morakmorak.morak_back_end.repository.FileRepository;
8 | import lombok.RequiredArgsConstructor;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.util.ObjectUtils;
11 |
12 | import java.util.Collections;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | @Service
17 | @RequiredArgsConstructor
18 | public class FileService {
19 | private final FileRepository fileRepository;
20 | public List createFileListFrom(List fileIdList) {
21 | if (ObjectUtils.isEmpty(fileIdList)) {return Collections.emptyList();}
22 | return fileIdList.stream().map(request ->
23 | findVerifiedFileById(request.getFileId()))
24 | .collect(Collectors.toList());
25 | }
26 |
27 | public File findVerifiedFileById(Long fileId) {
28 | return fileRepository.findById(fileId)
29 | .orElseThrow(() -> new BusinessLogicException(ErrorCode.FILE_NOT_FOUND));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Company.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 |
4 | import com.morakmorak.morak_back_end.entity.enums.JobPayType;
5 | import com.morakmorak.morak_back_end.entity.enums.JobType;
6 |
7 | import javax.persistence.*;
8 | import java.time.LocalDateTime;
9 |
10 | public class Company extends BaseTime{
11 | @Id
12 | @GeneratedValue(strategy = GenerationType.IDENTITY)
13 | @Column(name = "inicis_log_id")
14 | private Long id;
15 |
16 | private String companyName;
17 |
18 | private String description;
19 |
20 | private String companyEmail;
21 |
22 | private String employeeNumber;
23 |
24 | @Enumerated(EnumType.STRING)
25 | private JobType jobType;
26 |
27 | @Enumerated(EnumType.STRING)
28 | private JobPayType jobPayType;
29 |
30 | private Integer maxCarer;
31 |
32 | private Integer minCarer;
33 |
34 | private LocalDateTime startDate;
35 |
36 | private LocalDateTime endDate;
37 |
38 | private LocalDateTime workingStartMonth;
39 |
40 | private String tel;
41 |
42 | private LocalDateTime closed;
43 |
44 | private String city;
45 |
46 | private String techStack;
47 |
48 | private String logo;
49 |
50 | private String image1;
51 |
52 | private String image2;
53 |
54 | private String image3;
55 |
56 | private String image4;
57 |
58 | private String image5;
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/repository/TagRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.repository;
2 |
3 | import com.morakmorak.morak_back_end.entity.Tag;
4 | import com.morakmorak.morak_back_end.entity.enums.TagName;
5 |
6 | import org.assertj.core.api.Assertions;
7 | import org.junit.jupiter.api.DisplayName;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
12 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
13 |
14 | import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;
15 |
16 |
17 | @DataJpaTest
18 | @AutoConfigureTestDatabase(replace = NONE)
19 |
20 | class TagRepositoryTest {
21 |
22 | @Autowired
23 | TagRepository tagRepository;
24 |
25 |
26 | @Test
27 | @DisplayName("태그아이디 검색시 정확하게 값을 가져올때")
28 | public void Tag_suc1() throws Exception{
29 | //given
30 | Tag saved = Tag.builder().name(TagName.JAVA).build();
31 | tagRepository.save(saved);
32 | //when
33 | Tag tag = tagRepository.findById(saved.getId()).orElseThrow(() -> new RuntimeException("dsf"));
34 | //then
35 | Assertions.assertThat(tag.getName()).isEqualTo(saved.getName());
36 | }
37 | }
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/controller/AmazonS3Controller.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.controller;
2 |
3 |
4 | import com.morakmorak.morak_back_end.dto.AvatarDto;
5 | import com.morakmorak.morak_back_end.dto.FileDto;
6 | import com.morakmorak.morak_back_end.dto.UserDto;
7 | import com.morakmorak.morak_back_end.security.resolver.RequestUser;
8 | import com.morakmorak.morak_back_end.service.AmazonS3StorageService;
9 | import lombok.RequiredArgsConstructor;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | @RestController
14 | @RequestMapping
15 | @RequiredArgsConstructor
16 | public class AmazonS3Controller {
17 | private final AmazonS3StorageService s3StorageService;
18 |
19 | @ResponseStatus(HttpStatus.OK)
20 | @GetMapping("/users/profiles/avatars")
21 | public AvatarDto.ResponseS3Url getS3AvatarUrl(@RequestUser UserDto.UserInfo token) {
22 | return s3StorageService.saveAvatar(token.getId());
23 | }
24 |
25 | @ResponseStatus(HttpStatus.OK)
26 | @GetMapping("/files")
27 | public FileDto.ResponseFileDto getS3FileUrl() {
28 | return s3StorageService.saveFile();
29 | }
30 |
31 | @ResponseStatus(HttpStatus.NO_CONTENT)
32 | @DeleteMapping("/users/profiles/avatars")
33 | public void deleteAvatar(@RequestUser UserDto.UserInfo token) {
34 | s3StorageService.deleteAvatar(token.getId());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/client/components/dashboard/AsideBot.tsx:
--------------------------------------------------------------------------------
1 | import { userDashboardAtom } from '@atoms/userAtom';
2 | import { useRecoilValue } from 'recoil';
3 |
4 | const arrKor = [
5 | '친절한',
6 | '현명한',
7 | '똑똑한',
8 | '응가',
9 | '꼼꼼한',
10 | '박식한',
11 | '너그러운',
12 | '믿음직한',
13 | '재치있는',
14 | '도움이 되는',
15 | '따듯한',
16 | '사려 깊은',
17 | '문제를 해결하는',
18 | '배려심 있는',
19 | '창의적인',
20 | '논리적인',
21 | '이해하기 쉬운',
22 | ];
23 |
24 | const arrEng = [
25 | 'KINDLY',
26 | 'WISE',
27 | 'SMART',
28 | 'POO',
29 | 'DETAILED',
30 | 'INTELLEGENT',
31 | 'GENEROUS',
32 | 'COHERENT',
33 | 'WITTY',
34 | 'HELPFUL',
35 | 'WARM',
36 | 'CONSIDERATE',
37 | 'PROBLEMSOLVER',
38 | 'CARING',
39 | 'CREATIVE',
40 | 'LOGICAL',
41 | 'UNDERSTANDABLE',
42 | ];
43 |
44 | export const AsideBot = () => {
45 | const userDashboard = useRecoilValue(userDashboardAtom);
46 | return (
47 |
48 |
49 | 후기 태그
50 |
51 |
52 | {userDashboard.reviewBadges.map((badge) => (
53 |
54 |
60 |
61 | ))}
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/exception/webHook/ErrorNotificationGenerator.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.exception.webHook;
2 |
3 | import lombok.*;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.http.HttpEntity;
6 | import org.springframework.http.HttpHeaders;
7 | import org.springframework.http.HttpMethod;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.web.client.RestTemplate;
10 |
11 |
12 | @Getter
13 | @Component
14 | @NoArgsConstructor
15 | public class ErrorNotificationGenerator {
16 | @Value("${discord.url}")
17 | String discordUrl;
18 |
19 | public void send(String errorMessage) {
20 |
21 | HttpHeaders headers = new HttpHeaders();
22 |
23 | headers.add("Content-Type", "application/json");
24 | HttpEntity entity = new HttpEntity<>(DiscordWebhookMessageForm.builder()
25 | .avatarUrl("https://previews.123rf.com/images/arcady31/arcady311303/arcady31130300032/18519959-vector-oops-symbol.jpg")
26 | .content(errorMessage)
27 | .username("에러 전달 봇")
28 | .build(), headers);
29 |
30 | RestTemplate restTemplate = new RestTemplate();
31 |
32 | restTemplate.exchange(
33 | discordUrl,
34 | HttpMethod.POST,
35 | entity,
36 | String.class);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/entity/Notification.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.entity;
2 |
3 | import com.morakmorak.morak_back_end.entity.enums.DomainType;
4 | import lombok.*;
5 |
6 | import javax.persistence.*;
7 |
8 | @Entity
9 | @Getter
10 | @Builder
11 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
12 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
13 | public class Notification extends BaseTime {
14 |
15 | @Id
16 | @GeneratedValue(strategy = GenerationType.IDENTITY)
17 | @Column(name = "notification_id")
18 | private Long id;
19 |
20 | private Long senderId;
21 |
22 | private String message;
23 |
24 | @Builder.Default
25 | private Boolean isChecked = Boolean.FALSE;
26 |
27 | private Long domainId;
28 |
29 | private String uri;
30 |
31 | @Enumerated(EnumType.STRING)
32 | private DomainType domainType;
33 |
34 | @ManyToOne(fetch = FetchType.LAZY)
35 | @JoinColumn(name = "user_id")
36 | private User user;
37 |
38 | public static Notification of(String message, String uri, User receiver) {
39 | return Notification.builder()
40 | .message(message)
41 | .uri(uri)
42 | .user(receiver)
43 | .build();
44 | }
45 |
46 | public void mapUserAndNotification() {
47 | this.user.getNotifications().add(this);
48 | }
49 |
50 | public void changeCheckStatus() { this.isChecked = true; }
51 | }
52 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/mapper/FileMapper.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.dto.FileDto;
5 | import org.mapstruct.Mapper;
6 | import org.mapstruct.ReportingPolicy;
7 |
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | @Mapper(componentModel = "spring", typeConversionPolicy = ReportingPolicy.IGNORE)
12 | public interface FileMapper {
13 |
14 | default List RequestFileWithIdToFile(ArticleDto.RequestUploadArticle requestUploadArticle){
15 | List filesWithOnlyId = requestUploadArticle.getFileId().stream()
16 | .map(file -> FileDto.RequestFileWithId.builder()
17 | .fileId(file.getFileId())
18 | .build())
19 | .collect(Collectors.toList());
20 | return filesWithOnlyId;
21 | }
22 |
23 | default List RequestFileWithIdToFile(ArticleDto.RequestUpdateArticle requestUpdateArticle) {
24 | List filesWithOnlyId = requestUpdateArticle.getFileId().stream()
25 | .map(file -> FileDto.RequestFileWithId.builder()
26 | .fileId(file.getFileId())
27 | .build())
28 | .collect(Collectors.toList());
29 | return filesWithOnlyId;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/client/components/main/Calendar.tsx:
--------------------------------------------------------------------------------
1 | import FullCalendar from '@fullcalendar/react';
2 | import daygridPlugin from '@fullcalendar/daygrid';
3 |
4 | export const Calendar = () => {
5 | return (
6 |
7 |
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/client/components/review/ReviewTag.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useRecoilState } from 'recoil';
3 |
4 | import { reviewTagsAtom } from '@atoms/reviewAtom';
5 |
6 | type TagProps = {
7 | children: string;
8 | isSelectable: boolean;
9 | enumTag: string;
10 | };
11 |
12 | export const ReviewTag = ({ children, isSelectable, enumTag }: TagProps) => {
13 | const [isSelected, setIsSelected] = useState(false);
14 | const [reviewTags, setReviewTags] = useRecoilState(reviewTagsAtom);
15 |
16 | // 클릭시 selected state 와 selectedTags state를 업데이트
17 | const onClick = () => {
18 | setIsSelected(!isSelected);
19 | if (!isSelected) {
20 | const newState = reviewTags.slice();
21 | newState.push({ badgeId: 0, name: enumTag });
22 | setReviewTags(newState);
23 | } else {
24 | setReviewTags(reviewTags.filter((tag) => tag.name !== enumTag));
25 | }
26 | };
27 |
28 | return (
29 |
30 | {!isSelected && !isSelectable ? (
31 | {children}
32 | ) : (
33 |
44 | )}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/mapper/AnswerMapperTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.mapper;
2 |
3 | import com.morakmorak.morak_back_end.dto.AnswerDto;
4 | import com.morakmorak.morak_back_end.dto.UserDto;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 |
11 | import javax.transaction.Transactional;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | @SpringBootTest
16 | @Transactional
17 |
18 | class AnswerMapperTest {
19 |
20 | @Autowired
21 | AnswerMapper answerMapper;
22 |
23 |
24 | @Test
25 | @DisplayName("makingResponseAnswerLikeDto 메퍼 작동 테스트")
26 | void makingResponseAnswerLikeDto() {
27 | //given
28 | Long answerId = 1L;
29 | UserDto.UserInfo userInfo = UserDto.UserInfo.builder().id(1L).build();
30 | //when
31 | AnswerDto.ResponseAnswerLike responseAnswerLike =
32 | answerMapper.makingResponseAnswerLikeDto(answerId, userInfo.getId(), true, 1);
33 | //then
34 | assertThat(responseAnswerLike.getAnswerId()).isEqualTo(1L);
35 | assertThat(responseAnswerLike.getUserId()).isEqualTo(1L);
36 | assertThat(responseAnswerLike.getIsLiked()).isTrue();
37 | assertThat(responseAnswerLike.getLikeCount()).isEqualTo(1);
38 | }
39 |
40 |
41 | }
--------------------------------------------------------------------------------
/client/pages/membership-withdrawal/index.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps, NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import { useEffect } from 'react';
4 |
5 | import { Footer } from '@components/common/Footer';
6 | import { Header } from '@components/common/Header';
7 | import { Seo } from '@components/common/Seo';
8 | import { AsideEditProfile } from '@components/edit-privacy/AsideEditProfile';
9 | import { EditProfileComponent } from '@components/edit-privacy/EditProfile';
10 |
11 | const MembershipWithdrawal: NextPage = () => {
12 | const router = useRouter();
13 | useEffect(() => {
14 | if (typeof window !== 'undefined') {
15 | if (!localStorage.getItem('refreshToken')) {
16 | alert('로그인이 필요한 페이지입니다');
17 | router.push('/');
18 | }
19 | }
20 | }, []);
21 | return (
22 | <>
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | >
36 | );
37 | };
38 |
39 | export const getServerSideProps: GetServerSideProps = async (context) => {
40 | const content = context.req.url?.split('/')[1];
41 | return {
42 | props: {
43 | content,
44 | },
45 | };
46 | };
47 |
48 | export default MembershipWithdrawal;
49 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/dto/AvatarDto.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.dto;
2 |
3 | import com.querydsl.core.annotations.QueryProjection;
4 | import com.morakmorak.morak_back_end.entity.Avatar;
5 | import lombok.*;
6 |
7 | import java.io.Serializable;
8 |
9 | public class AvatarDto {
10 | @Getter
11 | @Builder
12 | @ToString
13 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
14 | @AllArgsConstructor(access = AccessLevel.PROTECTED)
15 | public static class SimpleResponse implements Serializable {
16 | private Long avatarId;
17 | private String filename;
18 | private String remotePath;
19 |
20 | public static SimpleResponse of(Avatar avatar) {
21 | return avatar == null ? SimpleResponse.builder()
22 | .avatarId(null)
23 | .filename(null)
24 | .remotePath(null)
25 | .build()
26 | :
27 | SimpleResponse.builder()
28 | .avatarId(avatar.getId())
29 | .filename(avatar.getOriginalFilename())
30 | .remotePath(avatar.getRemotePath())
31 | .build();
32 | }
33 | }
34 |
35 | @Getter
36 | @Builder
37 | @NoArgsConstructor(access = AccessLevel.PROTECTED)
38 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
39 | public static class ResponseS3Url {
40 | private Long avatarId;
41 | private String preSignedUrl;
42 | }
43 | }
--------------------------------------------------------------------------------
/client/pages/edit-password/index.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps, NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import { useEffect } from 'react';
4 |
5 | import { Seo } from '@components/common/Seo';
6 | import { AsideEditProfile } from '@components/edit-privacy/AsideEditProfile';
7 | import { EditProfileComponent } from '@components/edit-privacy/EditProfile';
8 | import { Footer } from '@components/common/Footer';
9 | import { Header } from '@components/common/Header';
10 | import { toast } from 'react-toastify';
11 |
12 | const EditPassword: NextPage = () => {
13 | const router = useRouter();
14 | useEffect(() => {
15 | if (typeof window !== 'undefined') {
16 | if (!localStorage.getItem('refreshToken')) {
17 | toast.error('로그인이 필요한 페이지입니다');
18 | router.push('/');
19 | }
20 | }
21 | }, []);
22 | return (
23 | <>
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | >
37 | );
38 | };
39 |
40 | export const getServerSideProps: GetServerSideProps = async (context) => {
41 | const content = context.req.url?.split('/')[1];
42 | return {
43 | props: {
44 | content,
45 | },
46 | };
47 | };
48 | export default EditPassword;
49 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/controller/CalendarController.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.controller;
2 |
3 | import com.morakmorak.morak_back_end.dto.JobInfoDto;
4 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
5 | import com.morakmorak.morak_back_end.exception.ErrorCode;
6 | import com.morakmorak.morak_back_end.service.CalendarService;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.PathVariable;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | import java.sql.Date;
14 | import java.util.List;
15 |
16 | import static com.morakmorak.morak_back_end.exception.ErrorCode.*;
17 |
18 | @RestController
19 | @RequiredArgsConstructor
20 | @RequestMapping("/calendars")
21 | public class CalendarController {
22 | private final CalendarService calendarService;
23 |
24 | @GetMapping("/{date}")
25 | public List getCalendarData(@PathVariable(name = "date") String requestDate) {
26 | Date date = getDate(requestDate);
27 | return calendarService.findCalendarData(date);
28 | }
29 |
30 | private Date getDate(String requestDate) {
31 | Date date;
32 | try {
33 | date = Date.valueOf(requestDate);
34 | } catch (IllegalArgumentException e) {
35 | throw new BusinessLogicException(INVALID_DATE_FORMAT);
36 | }
37 | return date;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/client/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@typescript-eslint", "prettier"],
3 | "extends": [
4 | "plugin:@typescript-eslint/eslint-recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:prettier/recommended"
7 | ],
8 | "parser": "@typescript-eslint/parser",
9 | "parserOptions": {
10 | "ecmaVersion": 2018,
11 | "sourceType": "module",
12 | "ecmaFeatures": {
13 | "jsx": true,
14 | "tsx": true
15 | }
16 | },
17 | "settings": {
18 | "react": {
19 | "version": "detect"
20 | }
21 | },
22 | "ignorePatterns": ["node_modules/", ".next/*"],
23 | "rules": {
24 | "react/react-in-jsx-scope": 0,
25 | "react/prefer-stateless-function": 0,
26 | "react/jsx-filename-extension": 0,
27 | "react/jsx-one-expression-per-line": 0,
28 | "no-nested-ternary": 0,
29 | "no-unused-vars": "off",
30 | "no-undef": "off",
31 | "/no-useless-escape": "off",
32 | "prettier/prettier": [
33 | "error",
34 | {
35 | "endOfLine": "auto",
36 | "useTabs": false
37 | }
38 | ],
39 | "indent": "off",
40 | "@typescript-eslint/no-empty-interface": "off",
41 | "@typescript-eslint/no-var-requires": "off",
42 | "no-empty-interface": "off"
43 | },
44 | "globals": {
45 | "React": "writable"
46 | },
47 | "overrides": [
48 | {
49 | "files": ["*.js", "*.jsx"],
50 | "rules": {
51 | "@typescript-eslint/explicit-function-return-type": "off",
52 | "@typescript-eslint/no-unused-vars": "off",
53 | "@typescript-eslint/no-empty-interface": "off"
54 | }
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/service/auth_service/CheckDuplicateNicknameTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.auth_service;
2 |
3 | import com.morakmorak.morak_back_end.entity.User;
4 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import java.util.Optional;
9 |
10 | import static com.morakmorak.morak_back_end.util.TestConstants.NICKNAME1;
11 | import static org.assertj.core.api.Assertions.assertThat;
12 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
13 | import static org.mockito.BDDMockito.given;
14 |
15 | public class CheckDuplicateNicknameTest extends AuthServiceTest {
16 | @Test
17 | @DisplayName("닉네임 중복 검사 시 해당 닉네임이 존재할 경우 BusinessLogicException 발생")
18 | public void checkDuplicateNickname_failed() {
19 | //given
20 | given(userRepository.findUserByNickname(NICKNAME1)).willReturn(Optional.of(User.builder().build()));
21 |
22 | //when //then
23 | assertThatThrownBy(() -> authService.checkDuplicateNickname(NICKNAME1)).isInstanceOf(BusinessLogicException.class);
24 | }
25 |
26 | @Test
27 | @DisplayName("닉네임 중복 검사 시 해당 닉네임이 존재하지 않을 경우 true 반환")
28 | public void checkDuplicateNickname_success() {
29 | //given
30 | given(userRepository.findUserByNickname(NICKNAME1)).willReturn(Optional.empty());
31 |
32 | //when
33 | Boolean result = authService.checkDuplicateNickname(NICKNAME1);
34 |
35 | //then
36 | assertThat(result).isTrue();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/file_service/DeleteFileService.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.service.file_service;
2 |
3 | import com.amazonaws.AmazonServiceException;
4 | import com.amazonaws.services.s3.AmazonS3;
5 | import com.morakmorak.morak_back_end.entity.File;
6 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
7 | import com.morakmorak.morak_back_end.repository.FileRepository;
8 | import lombok.RequiredArgsConstructor;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.stereotype.Service;
12 |
13 | import javax.transaction.Transactional;
14 |
15 | import static com.morakmorak.morak_back_end.exception.ErrorCode.FILE_NOT_FOUND;
16 |
17 | @Service
18 | @Slf4j
19 | @Transactional
20 | @RequiredArgsConstructor
21 | public class DeleteFileService {
22 | @Value("${cloud.aws.s3.bucket}")
23 | private String bucketName;
24 | private final FileRepository fileRepository;
25 |
26 | private final AmazonS3 amazonS3;
27 | void deleteFile(Long fileId) {
28 | try{
29 | File file = fileRepository.findById(fileId).orElseThrow(() -> new BusinessLogicException(FILE_NOT_FOUND));
30 | amazonS3.deleteObject(bucketName, file.getOriginalFilename());
31 | } catch (NullPointerException e) {
32 | throw new BusinessLogicException(FILE_NOT_FOUND);
33 | } catch (AmazonServiceException e) {
34 | log.error("", e);
35 | fileRepository.deleteById(fileId);
36 | return;
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/client/db/articles.json:
--------------------------------------------------------------------------------
1 | {
2 | "articles": {
3 | "articleId": 1,
4 | "category": "INFO",
5 | "title": "테스트 타이틀입니다. 잘부탁드립니다. 제발 돼라!!!~~~~~~~~",
6 | "content": "콘탠트입니다. 제발 됬으면 좋겠습니다.",
7 | "clicks": 10,
8 | "likes": 1,
9 | "isClosed": false,
10 | "isLiked": true,
11 | "isBookmarked": true,
12 | "tags": [
13 | {
14 | "tagId": 1,
15 | "name": "JAVA"
16 | }
17 | ],
18 | "createdAt": "2022-11-21T09:48:10.621091",
19 | "lastModifiedAt": "2022-11-21T09:48:10.621094",
20 | "expiredDate": null,
21 | "userInfo": {
22 | "userId": 1,
23 | "nickname": "김코딩",
24 | "grade": "BRONZE"
25 | },
26 | "avatar": {
27 | "avatarId": 1,
28 | "filename": "fileName",
29 | "remotePath": "https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=843&q=80"
30 | },
31 | "comments": [
32 | {
33 | "commentId": 1,
34 | "articleId": 1,
35 | "content": "comment 입니다.",
36 | "userInfo": {
37 | "userId": 1,
38 | "nickname": "nickname",
39 | "grade": "BRONZE"
40 | },
41 | "avatar": {
42 | "avatarId": 1,
43 | "filename": "fileName",
44 | "remotePath": "https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=843&q=80"
45 | },
46 | "createdAt": "2022-11-21T09:48:10.621061",
47 | "lastModifiedAt": "2022-11-21T09:48:10.621077"
48 | }
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/test/java/com/morakmorak/morak_back_end/repository/AnswerRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.repository;
2 |
3 | import com.morakmorak.morak_back_end.entity.Answer;
4 | import com.morakmorak.morak_back_end.repository.answer.AnswerRepository;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
11 |
12 | import javax.persistence.EntityManager;
13 |
14 | import static org.assertj.core.api.Assertions.*;
15 | import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;
16 |
17 | @DataJpaTest
18 | @AutoConfigureTestDatabase(replace = NONE)
19 |
20 | class AnswerRepositoryTest {
21 |
22 | @Autowired
23 | EntityManager em;
24 |
25 | @Autowired
26 | AnswerRepository answerRepository;
27 |
28 | @Test
29 | @DisplayName("findAnswerByContent 메서드 정상 작동 테스트")
30 | public void findAnswerByContent_suc(){
31 | //given
32 | Answer answer = Answer.builder().content("콘테트입니다. 잘부탁드립니다. 하하하하하").build();
33 | em.persist(answer);
34 |
35 | //when
36 | Answer dbAnswer = answerRepository.findAnswerByContent("콘테트입니다. 잘부탁드립니다. 하하하하하")
37 | .orElseThrow(() -> new RuntimeException("답변 없음"));
38 | //then
39 | assertThat(dbAnswer.getContent()).isEqualTo(answer.getContent());
40 | }
41 | }
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/service/answer_service/VerifyAnswerService.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 | import com.morakmorak.morak_back_end.exception.BusinessLogicException;
7 | import com.morakmorak.morak_back_end.exception.ErrorCode;
8 | import com.morakmorak.morak_back_end.repository.answer.AnswerRepository;
9 | import lombok.AllArgsConstructor;
10 | import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
11 | @AllArgsConstructor
12 | public class VerifyAnswerService implements AnswerDeletePolicy{
13 | private final AnswerDeletePolicy answerDeletePolicy;
14 | private AnswerRepository answerRepository;
15 |
16 | public Answer findVerifiedAnswer(Long answerId) {
17 | return answerRepository.findById(answerId).orElseThrow(() -> new BusinessLogicException(ErrorCode.ANSWER_NOT_FOUND));
18 | }
19 | public void checkUserPermission(Answer answer, User requestUser) {
20 | if (!answer.hasPermissionWith(requestUser)) {throw new BusinessLogicException(ErrorCode.NO_ACCESS_TO_THAT_OBJECT);}}
21 | public void checkArticleStatusPosting(Article verifiedArticle) {
22 | if (!verifiedArticle.statusIsPosting()) { throw new BusinessLogicException(ErrorCode.NO_ACCESS_TO_THAT_OBJECT);}
23 | }
24 |
25 | public void checkAnswerIsPicked(Answer verifiedAnswer) {
26 | if (verifiedAnswer.isPickedAnswer()) {throw new BusinessLogicException(ErrorCode.UNABLE_TO_CHANGE_ANSWER);}}
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/interceptor/PerformanceLoggingInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.interceptor;
2 |
3 | import org.aopalliance.intercept.MethodInvocation;
4 | import org.apache.commons.logging.Log;
5 | import org.springframework.aop.interceptor.AbstractMonitoringInterceptor;
6 |
7 | import java.util.Date;
8 |
9 | public class PerformanceLoggingInterceptor extends AbstractMonitoringInterceptor {
10 | @Override
11 | protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable {
12 | String name = createInvocationTraceName(invocation);
13 | long start = System.currentTimeMillis();
14 | logger.info("Method" + name + " execution started at:" + new Date());
15 |
16 | try {
17 | return invocation.proceed();
18 | } finally {
19 | long end = System.currentTimeMillis();
20 | long time = end - start;
21 | logger.info("Method " + name + " execution lasted:" + time + " ms");
22 | logger.info("Method " + name + " execution ended at:" + new Date());
23 |
24 | if (time > 4000) {
25 | logger.warn("☠️☠️☠️☠️☠️ 해당 메서드의 실행 시간이 3500ms를 초과했습니다. ☠️☠️☠️☠️☠️");
26 | }else if (time > 3000) {
27 | logger.warn("🚨🚨🚨🚨🚨 해당 메서드의 실행 시간이 2500ms를 초과했습니다. 🚨🚨🚨🚨🚨");
28 | }else if (time > 2000) {
29 | logger.warn("⚠️⚠️⚠️⚠️⚠️ 해당 메서드의 실행 시간이 1500ms를 초과했습니다. ⚠️⚠️⚠️⚠️⚠️");
30 | }else if (time > 1000) {
31 | logger.warn("🤫🤫🤫🤫🤫 해당 메서드의 실행 시간이 500ms를 초과했습니다. 🤫🤫🤫🤫🤫");
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/client/pages/edit-profile/index.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps, NextPage } from 'next';
2 | import { useRouter } from 'next/router';
3 | import { useEffect } from 'react';
4 |
5 | import { Footer } from '@components/common/Footer';
6 | import { Header } from '@components/common/Header';
7 | import { Seo } from '@components/common/Seo';
8 | import { AsideEditProfile } from '@components/edit-privacy/AsideEditProfile';
9 | import { EditAvatar } from '@components/edit-privacy/EditAvatar';
10 | import { EditProfileComponent } from '@components/edit-privacy/EditProfile';
11 | import { toast } from 'react-toastify';
12 |
13 | const EditProfile: NextPage = () => {
14 | const router = useRouter();
15 | useEffect(() => {
16 | if (typeof window !== 'undefined') {
17 | if (!localStorage.getItem('refreshToken')) {
18 | toast.error('로그인이 필요한 페이지입니다');
19 | router.push('/');
20 | }
21 | }
22 | }, []);
23 | return (
24 | <>
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | );
41 | };
42 |
43 | export const getServerSideProps: GetServerSideProps = async (context) => {
44 | const content = context.req.url?.split('/')[1];
45 | return {
46 | props: {
47 | content,
48 | },
49 | };
50 | };
51 |
52 | export default EditProfile;
53 |
--------------------------------------------------------------------------------
/server/morak_back_end/src/main/java/com/morakmorak/morak_back_end/domain/RandomKeyGenerator.java:
--------------------------------------------------------------------------------
1 | package com.morakmorak.morak_back_end.domain;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | import java.security.SecureRandom;
6 | import java.util.Date;
7 |
8 | import static com.morakmorak.morak_back_end.security.util.SecurityConstants.*;
9 |
10 | @Component
11 | public class RandomKeyGenerator {
12 | SecureRandom secureRandom = new SecureRandom();
13 |
14 | public String generateMailAuthKey() {
15 | return generateRandomKey(EMAIL_AUTH_KEY_LENGTH);
16 | }
17 |
18 | public String generateTemporaryPassword() {
19 | return generateRandomKey(TEMPORARY_PASSWORD_LENGTH);
20 | }
21 |
22 | public String generateTemporaryNicknameCode() { return generateRandomKey(TEMPORARY_NICKNAME_CODE_LENGTH); }
23 |
24 | private String generateRandomKey(int size) {
25 | StringBuilder stringBuilder = new StringBuilder();
26 |
27 | char[] charSet = new char[] {
28 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
29 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
30 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
31 | '!', '@', '#', '$', '%', '^', '&' };
32 |
33 | secureRandom.setSeed(new Date().getTime());
34 |
35 | int len = charSet.length;
36 | for (int i=0; i authService.sendAuthenticationMailForFindPwd(EMAIL1))
24 | .isInstanceOf(BusinessLogicException.class);
25 | }
26 |
27 | @Test
28 | @DisplayName("패스워드 찾기 요청 시 DB에 해당 이메일이 존재한다면 이메일 전송")
29 | public void success() {
30 | //given
31 | User dbUser = User.builder().email(REDIS_EMAIL_EMPTY).id(1L).build();
32 |
33 | given(userRepository.findUserByEmail(REDIS_EMAIL_EMPTY)).willReturn(Optional.of(dbUser));
34 |
35 | //when
36 | Boolean result = authService.sendAuthenticationMailForFindPwd(REDIS_EMAIL_EMPTY);
37 |
38 | //then
39 | Assertions.assertThat(result).isTrue();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------