묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Do It! 장고+부트스트랩: 파이썬 웹개발의 정석
다음에는 어떻게 공부해야 하나요?
책과 강의를 둘다 보면서 웹사이트를 만들고 있습니다.파이썬은 데이터 분석 위주로 공부해서 장고는 사실 이번이 처음 배우는 중입니다.html, css, js는 아주 기초정도 알고 있습니다. 책을 따라가면, 책 대로 만들어지긴 하는데, 책 내용을 100% 이해한다고 보기는 힘들 것 같습니다. 책 내용을 100% 이해 못하면서 따라가도 되나요? 이걸 끝까지 완료 한 다음에는 어떻게 장고를 공부하는 것을 추천하시나요?
-
미해결자바(Java) 알고리즘 문제풀이 입문: 코딩테스트 대비
어떻게 a 배열, b 배열에 알아서 잘 담겨지는지 모르겠습니다.
값이 들어왔을때5 2 3 3 1 3 1 1 2 2 3이게 어떻게 a 배열, b 배열에 알아서 잘 담겨지는지 모르겠습니다. int n = sc.nextInt(); int [] a = new int[n]; int [] b = new int [n]; for(int i =0; i<n; i++){ a[i] = sc.nextInt(); } for(int i =0; i<n; i++){ b[i] = sc.nextInt(); }이런건 그냥 외우는 건가요?
-
미해결Slack 클론 코딩[실시간 채팅 with React]
index.html 실행
현재 npm run build까지 성공한 상태인데요, index파일을 실행시킨다는 뜻이 터미널에 cd alecture에 npm run start를 하라는 말씀이 맞나요?
-
미해결풀스택을 위한 도커와 최신 서버 기술(리눅스, nginx, AWS, HTTPS, 배포까지) [풀스택 Part3]
vim 재설치
맥에서 vim 설정을 잘못하는 바람에 mac shell(zsh)에서 vi 실행할 때 마다Error detected while processing /Users/yoohajun/.vimrc: line 38: E518: Unknown option: CursorLine line 70: E518: Unknown option: CursorLine위와 같은 에러가 발생하는데요 ㅠㅠ 혹시 vim 재설치를 하는 방법이 있을까요?vimrc를 어떻게 건들여야 할지 모르겠습니다
-
미해결Slack 클론 코딩[실시간 채팅 with React]
checker.emitFiles is not a function
npm run build를 했더니 밑과 같은 에러가 뜹니다.. 어떻게 해야하나요 ㅜ [webpack-cli] Failed to load 'C:\Users\JW\slack_front\alecture\webpack.config.ts' config[webpack-cli] TypeError: checker.emitFiles is not a function at compile (C:\Users\JW\slack_front\alecture\node_modules\typescript-register\index.js:132:26) at Object.req (C:\Users\JW\slack_front\alecture\node_modules\typescript-register\index.js:29:9) at Module.load (node:internal/modules/cjs/loader:1004:32) at Function.Module._load (node:internal/modules/cjs/loader:839:12) at Module.require (node:internal/modules/cjs/loader:1028:19) at require (node:internal/modules/cjs/helpers:102:18) at WebpackCLI.tryRequireThenImport (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:204:22) at loadConfigByPath (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1404:38) at WebpackCLI.loadConfig (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1510:44) at WebpackCLI.createCompiler (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1785:33)
-
미해결[리뉴얼] 코딩자율학습 제로초의 자바스크립트 입문
\n 또는 \ 사용 관련 (5강)
강의 중간에 따옴표 사용할 때 줄바꿈 하는 법과 \를 문자열로 취급하는 법을 따라해보는데저는 '안녕하세요?\n줄바꿈했어요'; 라고 입력을 하면결과값도 '안녕하세요?\n줄바꿈했어요' 라고 나옵니다.제로초님의 영상에서는'안녕하세요?줄바꿈했어요' 라고 출력이 되는데,,, 말입니다...?*참고로 백틱을 이용하여 문자열을 쓸 때`안녕하세요줄바꿈하고있습니다`; 라고 입력을 하면결과값에 '안녕하세요\n줄바꿈하고있습니다' 라고 출력됩니다.... 결과값에서 \n이 엔터가 되어 나오게 하는 법을 아시는지... 그리고 \를 문자열 중간에 사용할때'how\re you?';라고 치면 'howe you?'라고 나오기 때문에'how\\re you?';라고 쳐야 'how\re you?'라고 나온다고 하셨는데저는 'how\re you?'; 라고 쳐도 결과값이 그대로 'how\re you?';로 나옵니다... 이게 왜 이렇게 되는지..?구글로 이모저모 검색을 해봤는데 뭐라고 검색해야 딱 집어 나올지 검색해보니 2프로 부족해서 그만..질문합니다
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
dataSource 빨간 줄 에러
jdbcMemberRepository.java에서 dataSource부분에 계속 빨간 줄이 떠요 오류 내용은 위와 같습니다..!
-
미해결스프링 프레임워크는 내 손에 [스프1탄]
파일업로드 프로필 질문드려요
파일업로드 프로필 부분에서 선생님것을 보고하였는데 약간의 오류가 있어서 말씀드립니다.파일 업로드할시 같은 이름 다른 사진으로 처리하였을 경우 upload폴더에는 다른이름으로 저장 되는 것을 확인 할수 있었으나 db테이블에서는 같은 이름으로 처리가 되어서 서로 매칭이 되고 있지않습니다.upload파일에서는 test와 test1 두개의 이름으로 저장이 되어있고db테이블에서는 test.jpg두개의 이름으로 저장이 되어있습니다.그래서 사진 프로필은 upload파일에서 test.jpg만 가지고 오고있습니다. db는 어떻게 처리해야 되는걸까요...
-
미해결모든 개발자의 실무를 위한 올인원 기본기 클래스
remote add 및 push 실행 후 password 에러
password를 잊어버려 github로 push를 못하고 있는데, 이 경우 password 찾기나 재설정을 어떻게 해야 하나요~
-
미해결[백문이불여일타] 데이터 분석을 위한 기초 SQL
문의 드립니다.
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요! :)데이터 분석가를 목표로 준비 중인 학생입니다! 1) 강의 출처를 기재한다면, 기초 > 중급 > 고급 강의 개인 복습 내용을 깃허브에 코드로 정리해도 될지요? 최대한 변형하며 스스로 작성해보겠으나, 전체적인 코드 흐름이 강의 내용을 따라갈 예정이라 사전에 문의 드립니다.2) 'SQL 데이터 분석 캠프 | VOD 기초 또는 실전반' 참여도 고려 중인데 캠프 학습 내용도 상기 질문 내용과 같은 조건으로 업로드 가능한지요?너무 기대되는 강의입니다, 열공해보겠습니다!
-
미해결생산성을 향상시키는 스프링부트 기반의 API 템플릿 프로젝트 구현
SocialLoginApiServiceFactory 주입 시점
안녕하세요 구파고님!SocialLoginApiServiceFactory 클래스에 socialLoginApiServices에서 key와 value값이 언제 주입되는지 궁금합니다!생성자 주입을 통해서 key값으로는 bean의 이름, value에는 각 소셜로그인 api 서비스 구현체가 들어간다고 하셨는데 어떤 시점에 주입되는지 모르겠어서 질문드립니다!
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
오류 )java.lang.IllegalStateException: Failed to load ApplicationContext
오류가 떠요..import를 잘못해서 그런걸까요?java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [jpabook.jpashop.JpashopApplication]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'memberRepository' for bean class [jpabook.jpashop.repository.MemberRepository] conflicts with existing, non-compatible bean definition of same name and class [jpabook.jpashop.MemberRepository] at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:189) at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:336) at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:252) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:285) at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:99) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:751) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:569) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:122) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ... 27 moreCaused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'memberRepository' for bean class [jpabook.jpashop.repository.MemberRepository] conflicts with existing, non-compatible bean definition of same name and class [jpabook.jpashop.MemberRepository] at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:349) at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:287) at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:296) at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250) at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:207) at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:175) ... 40 more
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
코드가 잘못된 것도 없는거 같은데 포스트맨 에서 에러가 뜹니다
포스트맨 에서 로그인 하면 자꾸 404에러가 나옵니다 코드는 잘못된게 없는거 같은데 몇시간을 봐도 어디가 잘 못 된거지 잘 모르겠네여
-
미해결[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!
중급 다음 과정은 무얼 배워야 할까요?
안녕하세요, 코팩님의 초급, 중급강의 수강하고 유데미에서 플러터 공부를 이어가고 있습니다. 코팩님의 중급 강의 다음으로는 어떤 걸 배워야 할까요?? 혼자서 앱 출시까지 해보고 싶은데요, 꼭 숙지해야하지만 중급강의엔 없는 것이 있나요??
-
미해결[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
트와이스 에뮬레이터에서 앱 실행 안됨
앱을 실행할려고 해도 에뮬레이터에서 앱 실행이 안됩니다. BTS앱을 만들 때는 괜찮았는데 트와이스 앱에서부터 이렇게 됐습니다. 시간이 걸린다고 생각해 계속 기다려봤지만 실행되지 않고 에뮬레이터에 별도의 클릭도 가능한 상태가 아닙니다.
-
해결됨Spring Boot JWT Tutorial
그대로 따라했는데 오류가 납니다
Description:Parameter 0 of constructor in com.example.demo.service.CustomUserDetailsService required a bean of type 'com.example.demo.repository.UserRepository' that could not be found.Action:Consider defining a bean of type 'com.example.demo.repository.UserRepository' in your configuration. 구글링을 해봐도 전혀 해결되지 않아요 ㅠㅠ
-
미해결자바스크립트로 알아보는 함수형 프로그래밍 (ES5)
_curryR 을 적용한 _map 혹은 _filter함수 질문입니다!!
curryR을 활용해 등록한 map 함수나 _filter함수 동작에 const mapR = _curryR(_map); console.log( mapR(v => v.name)(arr) ));은 작동하는데 반해 console.log( mapR(v => v.name, arr));은 작동하지 않습니다...저는 조금 이해가 안가는게 arguments.length ===2 로 파라미터가 2개 들어올때의 경우의 수 까지 처리 한것으로 알고 있는데 왜 밑에 부분은 작동하지 않는지...맞게 동작하는 것인지 궁금합니다....만약 제가 놓치고 있는 부분이 있다면 알려주시면 감사드리겠습니다!
-
미해결게임 프로그래머 취업 전략 가이드
혹시 서버강의는 무기한 중단인가요?
정말 루키스님 강의보고 취업했다고 하는말이 과언이 아닐정도로 많이 배웠고 또 배우고 싶습니다.개인적으로 루키스님 강의의 장점은 아주 중요하지만 아무도 안다루는 게임개발의 코어파트(서버, 그래픽스)를 다뤄주신다는 점과 그 복잡하고 어려운 내용을 명쾌하게 설명해주시는 강의력이라 생각합니다 c++ 서버강의 part4 이후 엔진 활용 강의로 맥이 바뀐거 같은데 혹시 part 5 이후의 강의는 무기한 잠정 중단인가요? ㅠㅠ
-
미해결자바스크립트로 알아보는 함수형 프로그래밍 (ES5)
_reduce 질문 있습니다!
_reduce 함수에서 파라미터로 받은 list 를 list = _rest(list)를 통해 변경 하는데요... 이 부분에서 순수 함수가 맞는지 맞다면 이유가 무엇인지 궁금한데 알려 주실 수 있을까요??
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
다이나믹 라우터 애러 도와주시면 감사하겠습니다.
1 of 1 unhandled errorServer ErrorTypeError: Cannot read properties of null (reading 'Likers')This error happened while generating the page. Any console logs will be displayed in the terminal window.Sourcecomponents\PostCard.js (18:23) @ PostCard 16 | const dispatch = useDispatch(); 17 | const id = useSelector((state) => state.user.me?.id) > 18 | const liked = post.Likers.find((item) => item.id === id) | ^ 19 | 20 | const onToggleComment = useCallback(() => { 21 | setCommentFormOpened((prev) => !prev)Call StackFunction.getInitialPropspages\_document.tsx (91:33)Show collapsed frames선생님도 같은 오류 나왔었던거 있어서 그대로 오류 해결하는데도 이 오류가 안없어지네요...//서버 메세지 GET /posts?lastId=0 200 12.677 ms - 2197 GET /post/60 200 15.146 ms - 216서버에서는 요청 잘 받는것 같은데 Likers가 계속 비어있는거 같습니다... 메인 화면에서는 작동이제 다 잘되는데 다이나믹 라우트 post/ 로 넘어가기만 하면 오류 발생하네요... 하루종일 매달리고 있는데... 선생님 깃허브 코드랑 강의 코드가 달라서 조금 헷갈려서요... 깃허브 주소 남기겠습니다 한번 봐주시면 감사하겠습니다. ㅠㅠhttps://github.com/wihyanghoon/react-nodebird// [id].js import React from 'react'; import { useSelector } from 'react-redux'; import { useRouter } from 'next/router'; import { END } from 'redux-saga'; import axios from 'axios'; import { LOAD_POST_REQUEST } from '../../reducers/post'; import wrapper from '../../store/configureStore'; import PostCard from '../../components/PostCard'; import AppLayout from '../../components/AppLayout'; import { LOAD_MYINFO_REQUEST } from '../../reducers/user'; const Post = () => { const { singlePost } = useSelector((state) => state.post); const router = useRouter(); const { id } = router.query; // if (router.isFallback) { // return <div>Loading...</div> // } return ( <AppLayout> <PostCard post={ singlePost } /> </AppLayout> ); }; export const getServerSideProps = wrapper.getServerSideProps(async (context) => { const cookie = context.req ? context.req.headers.cookie : ''; console.log(context); axios.defaults.headers.Cookie = ''; if (context.req && cookie) { axios.defaults.headers.Cookie = cookie; } context.store.dispatch({ type: LOAD_MYINFO_REQUEST, }); context.store.dispatch({ type: LOAD_POST_REQUEST, data: context.params.id, }); context.store.dispatch(END); await context.store.sagaTask.toPromise(); }); export default Post;import { delay, all, fork, takeLatest, put, throttle, call } from "redux-saga/effects"; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, LIKE_POST_REQUEST, LIKE_POST_SUCCESS, LIKE_POST_FAILURE, UNLIKE_POST_REQUEST, UNLIKE_POST_SUCCESS, UNLIKE_POST_FAILURE, UPLOAD_IMAGES_REQUEST, UPLOAD_IMAGES_SUCCESS, UPLOAD_IMAGES_FAILURE, RETWEET_REQUEST,RETWEET_SUCCESS,RETWEET_FAILURE, LOAD_POST_REQUEST, LOAD_POST_SUCCESS, LOAD_POST_FAILURE } from '../reducers/post' import { ADD_POST_TO_ME, REMOVE_POST_TO_ME } from "../reducers/user"; import axios from "axios"; import shortId from 'shortid'; function addPostAPI(data) { return axios.post('/post', data); } function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield console.log(result) yield put({ type: ADD_POST_SUCCESS, data: result.data, }); yield put({ type: ADD_POST_TO_ME, data: result.data.id, }); } catch (err) { console.error(err); yield put({ type: ADD_POST_FAILURE, error: err.response.data, }); } } function removePostAPI(data) { return axios.delete(`/post/${data}`) } function* removePost(action) { console.log(action.data) try { const result = yield call(removePostAPI, action.data) yield console.log(typeof result.data.PostId) yield put({ type: REMOVE_POST_SUCCESS, data: result.data }) yield put({ type: REMOVE_POST_TO_ME, data: result.data.PostId }) } catch (err) { yield put({ type: REMOVE_POST_FAILURE, error: err.response.data }); } } function addCommentAPI(data) { return axios.post(`/post/${data.postId}/comment`, data) } function* addComment(action) { try { const result = yield call(addCommentAPI, action.data) yield put({ type: ADD_COMMENT_SUCCESS, data: result.data }); } catch (err) { yield put({ type: ADD_COMMENT_FAILURE, error: err.response.data }); } } function loadPostsAPI(lastId) { return axios.get(`/posts?lastId=${lastId || 0}`); } function* loadPosts(action) { try { const result = yield call(loadPostsAPI, action.lastId); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: LOAD_POSTS_FAILURE, data: err.response.data, }); } } function loadPostAPI(data) { return axios.get(`/post/${data}`); } function* loadPost(action) { try { const result = yield call(loadPostAPI, action.data); yield put({ type: LOAD_POST_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: LOAD_POST_FAILURE, error: err.response.data, }); } } function likePostAPI(data) { return axios.patch(`/post/${data}/like `, data); } function* likePost(action) { try { const result = yield call(likePostAPI, action.data); yield put({ type: LIKE_POST_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: LIKE_POST_FAILURE, data: err.response.data, }); } } function UnLikePostAPI(data) { return axios.delete(`/post/${data}/like`); } function* UnLikePost(action) { try { const result = yield call(UnLikePostAPI, action.data); yield put({ type: UNLIKE_POST_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: UNLIKE_POST_FAILURE, data: err.response.data, }); } } function upLoadImagesAPI(data) { return axios.post('/post/images', data); } function* upLoadImages(action) { try { const result = yield call(upLoadImagesAPI, action.data); yield put({ type: UPLOAD_IMAGES_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: UPLOAD_IMAGES_FAILURE, data: err.response.data, }); } } function retweetApi(data){ return axios.post(`/post/${data}/retweet`); } function* retweet(action) { try { const result = yield call(retweetApi, action.data); yield put({ type: RETWEET_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: RETWEET_FAILURE, err: err.response.data, }); } } function* watchLoadPosts() { yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts); } function* watchLoadPost() { yield takeLatest(LOAD_POST_REQUEST, loadPost); } function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost) } function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost) } function* watchCommentPost() { yield takeLatest(ADD_COMMENT_REQUEST, addComment) } function* watchLikePost() { yield takeLatest(LIKE_POST_REQUEST, likePost) } function* watchUnLiketPost() { yield takeLatest(UNLIKE_POST_REQUEST, UnLikePost) } function* watchUpLoadImages() { yield takeLatest(UPLOAD_IMAGES_REQUEST, upLoadImages) } function* watchRetweet() { yield takeLatest(RETWEET_REQUEST, retweet) } export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchCommentPost), fork(watchRemovePost), fork(watchLoadPosts), fork(watchLoadPost), fork(watchLikePost), fork(watchUnLiketPost), fork(watchUpLoadImages), fork(watchRetweet), ]); }import shortId from 'shortid'; import produce from 'immer'; import faker from 'faker'; import { LIKE_FAILURE, LIKE_REQUEST, LIKE_SUCCESS } from './user'; export const initialState = { mainPosts: [], imagePath: [], hasMorePosts: true, loadPostsLoading: false, loadPostsDone: false, loadPostsError: null, loadPostLoading: false, loadPostDone: false, loadPostError: null, likeLoading: false, likeDone: false, likeError: null, unLikeLoading: false, unLikeDone: false, unLikeError: null, addPostLoadding: false, addPostDone: false, addPostErr: null, removePostLoadding: false, removePostDone: false, removePostErr: null, addCommentLoadding: false, addCommentDone: false, addCommentErr: null, upLoadImagesLoadding: false, upLoadImagesDone: false, upLoadImagesErr: null, retweetLoadding: false, retweetDone: false, retweetErr: null, singlePost: null, } // export const getDemmuyPost = (number) => Array(number).fill().map(() => ({ // id: shortId.generate(), // User: { // id: shortId.generate(), // nickname: faker.name.findName(), // }, // content: faker.lorem.paragraph(), // Images: [{ // src: faker.image.image(), // }], // Comments: [{ // User: { // id: shortId.generate(), // nickname: faker.name.findName(), // }, // content: faker.lorem.sentence(), // }], // })) export const REMOVE_IMAGES_SUCSESS = 'REMOVE_IMAGES_SUCSESS'; export const LOAD_POST_REQUEST = 'LOAD_POSTS_REQUEST'; export const LOAD_POST_SUCCESS = 'LOAD_POSTS_SUCCESS'; export const LOAD_POST_FAILURE = 'LOAD_POSTS_FAILURE'; export const LOAD_POSTS_REQUEST = 'LOAD_POSTS_REQUEST'; export const LOAD_POSTS_SUCCESS = 'LOAD_POSTS_SUCCESS'; export const LOAD_POSTS_FAILURE = 'LOAD_POSTS_FAILURE'; export const ADD_POST_REQUEST = 'ADD_POST_REQUEST' export const ADD_POST_SUCCESS = 'ADD_POST_SUCCESS' export const ADD_POST_FAILURE = 'ADD_POST_FAILURE' export const REMOVE_POST_REQUEST = 'REMOVE_POST_REQUEST' export const REMOVE_POST_SUCCESS = 'REMOVE_POST_SUCCESS' export const REMOVE_POST_FAILURE = 'REMOVE_POST_FAILURE' export const ADD_COMMENT_REQUEST = 'ADD_COMMENT_REQUEST' export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS' export const ADD_COMMENT_FAILURE = 'ADD_COMMENT_FAILURE' export const LIKE_POST_REQUEST = 'LIKE_POST_REQUEST'; export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS'; export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE'; export const UPLOAD_IMAGES_REQUEST = 'UPLOAD_IMAGES_REQUEST'; export const UPLOAD_IMAGES_SUCCESS = 'UPLOAD_IMAGES_SUCCESS'; export const UPLOAD_IMAGES_FAILURE = 'UPLOAD_IMAGES_FAILURE'; export const UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST'; export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS'; export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE'; export const RETWEET_REQUEST = 'RETWEET_REQUEST' export const RETWEET_SUCCESS = 'RETWEET_SUCCESS' export const RETWEET_FAILURE = 'RETWEET_FAILURE' export const addPostAction = (data) => { return { type: ADD_POST_REQUEST, data } } export const addCommentAction = (data) => { return { type: ADD_COMMENT_REQUEST, data } } const reducer = (state = initialState, action) => { return produce(state, (draft) => { switch (action.type) { case RETWEET_REQUEST: draft.retweetLoadding = true draft.retweetDone = false draft.retweetErr = null break; case RETWEET_SUCCESS: draft.retweetLoadding = false draft.retweetDone = true draft.mainPosts.unshift(action.data) break; case RETWEET_FAILURE: draft.retweetLoadding = false draft.retweetErr = action.err break; case REMOVE_IMAGES_SUCSESS: console.log(action.data) draft.imagePath = draft.imagePath.filter((item, index) => index !== action.data) break; case LOAD_POSTS_REQUEST: draft.loadPostsLoading = true; draft.loadPostsDone = false; draft.loadPostsError = null; break; case LOAD_POSTS_SUCCESS: draft.loadPostsLoading = false; draft.loadPostsDone = true; draft.mainPosts = draft.mainPosts.concat(action.data); draft.hasMorePosts = draft.mainPosts.length === 10; break; case LOAD_POSTS_FAILURE: draft.loadPostsLoading = false; draft.loadPostsError = action.error; break; case LOAD_POST_REQUEST: draft.loadPostLoading = true; draft.loadPostDone = false; draft.loadPostError = null; break; case LOAD_POST_SUCCESS: draft.loadPostLoading = false; draft.loadPostDone = true; draft.singlePost = action.data; break; case LOAD_POST_FAILURE: draft.loadPostLoading = false; draft.loadPostError = action.error; break; case ADD_POST_REQUEST: draft.addPostLoadding = true draft.addPostDone = false draft.addPostErr = null break; case ADD_POST_SUCCESS: draft.addPostLoadding = false draft.addPostDone = true draft.mainPosts.unshift(action.data) draft.imagePath = [] break; case ADD_POST_FAILURE: draft.addPostLoadding = false draft.addPostErr = action.err break; case REMOVE_POST_REQUEST: draft.removePostLoadding = true draft.removePostDone = false draft.removePostErr = null break; case REMOVE_POST_SUCCESS: draft.removePostLoadding = false draft.removePostDone = true draft.mainPosts = state.mainPosts.filter((item) => item.id !== action.data.PostId) break; case REMOVE_POST_FAILURE: draft.removePostLoadding = false draft.removePostErr = action.err break; case ADD_COMMENT_REQUEST: draft.addCommentLoadding = true draft.addCommentDone = false draft.addCommentErr = null break; case ADD_COMMENT_SUCCESS: const post = draft.mainPosts.find((item) => { return item.id === action.data.PostId }) post.Comments.unshift(action.data) draft.addCommentLoadding = false draft.addCommentDone = true break; case ADD_COMMENT_FAILURE: draft.addCommentLoadding = false draft.addCommentErr = action.error break; case LIKE_POST_REQUEST: draft.likeLoading = true draft.likeDone = false draft.likeError = null break; case LIKE_POST_SUCCESS: { draft.likeLoading = false draft.likeDone = true const post = draft.mainPosts.find((item) => item.id === action.data.PostId) post.Likers.push({ id: action.data.UserId }) break; } case LIKE_POST_FAILURE: draft.unLikeLoading = false draft.unLikeError = true break; case UNLIKE_POST_REQUEST: draft.unLikeLoading = true draft.unLikeDone = false draft.unLikeError = null break; case UNLIKE_POST_SUCCESS: { draft.unLikeLoading = false draft.unLikeDone = true const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId); break; } case UNLIKE_POST_FAILURE: draft.unLikeLoading = false draft.unLikeDone = true break; case UPLOAD_IMAGES_REQUEST: draft.upLoadImagesLoadding = true draft.upLoadImagesDone = false draft.upLoadImagesErr = null break; case UPLOAD_IMAGES_SUCCESS: draft.upLoadImagesLoadding = true draft.upLoadImagesDone = false draft.imagePath = action.data break; case UPLOAD_IMAGES_FAILURE: draft.upLoadImagesLoadding = false draft.upLoadImagesErr = action.error break; default: return state } }) } export default reducerconst express = require('express') const multer = require('multer') const path = require('path') const fs = require('fs') const { Post, Comment, Image, User, Hashtag } = require('../models') const { isLoggedIn } = require('./middlewares') const user = require('../models/user') const router = express.Router(); try { fs.accessSync('uploads') } catch (error) { console.log('폴더가 없으므로 생성합니다.') fs.mkdirSync('uploads') } const upload = multer({ storage: multer.diskStorage({ destination(req, file, done) { done(null, 'uploads'); }, filename(req, file, done) { // 제로초.png const ext = path.extname(file.originalname); // 확장자 추출(.png) const basename = path.basename(file.originalname, ext); // 제로초 done(null, basename + '_' + new Date().getTime() + ext); // 제로초15184712891.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, // 20MB }); router.post('/', isLoggedIn, upload.none(), async (req, res, next) => { // POST /post try { const hashtags = req.body.content.match(/#[^\s#]+/g); const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (hashtags) { const result = await Promise.all(hashtags.map((tag) => Hashtag.findOrCreate({ where: { name: tag.slice(1).toLowerCase() }, }))); // [[노드, true], [리액트, true]] await post.addHashtags(result.map((v) => v[0])); } if (req.body.image) { if (Array.isArray(req.body.image)) { // 이미지를 여러 개 올리면 image: [제로초.png, 부기초.png] const images = await Promise.all(req.body.image.map((image) => Image.create({ src: image }))); await post.addImages(images); } else { // 이미지를 하나만 올리면 image: 제로초.png const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } const fullPost = await Post.findOne({ where: { id: post.id }, include: [{ model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'] }] }, { model: User, attributes: ['id', 'nickname'] }, { model: User, as: 'Likers', attributes: ['id'], }] }) res.status(201).json(fullPost) } catch (error) { console.error(error) next(error) } }) router.delete('/:PostId', isLoggedIn, async (req, res, next) => { try { await Post.destroy({ where: { id: req.params.PostId, UserId: req.user.id }, }) res.status(201).json({ PostId: parseInt(req.params.PostId, 10) }) } catch (error) { console.error(error) next(error) } }) router.post('/:postId/comment', isLoggedIn, async (req, res) => { try { const post = await Post.findOne({ where: { id: req.params.postId } }) if (!post) { return res.status(403).send('존재하지 않는 게시글 입니다.') } const commnet = await Comment.create({ content: req.body.content, PostId: Number(req.params.postId), UserId: req.user.id, }) const fullComment = await Comment.findOne({ where: { id: commnet.id }, include: [{ model: User, attributes: ['id', 'nickname'] }] }) res.status(201).json(fullComment) } catch (error) { console.error(error) next(error) } }) router.get('/:postId', async (req, res, next) => { try { const post = await Post.findOne({ where: { id: req.params.postId }, }) if (!post) { return res.status(404).send('존재하지않는 게시글입니다..') } const fullPost = await Post.findOne({ where: { id: post.id }, include: [{ model: Post, as: 'Retweet', include: [{ model: User, attributes: ['id', 'nickname'] }, { model: Image, }] }, { model: User, attributes: ['id', 'nickname'] }, { model: User, as: 'Likers', attributes: ['id', 'nickname'] }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'] }] }] }) res.status(200).json(fullPost) } catch (err) { console.error(err) next(err) } }); router.patch('/:postId/like', isLoggedIn, async (req, res, next) => { // PATCH /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send('게시글이 존재하지 않습니다.'); } await post.addLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete('/:postId/like', isLoggedIn, async (req, res, next) => { // DELETE /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send('게시글이 존재하지 않습니다.'); } await post.removeLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete('/:postId/like', (req, res, next) => { }) router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => { res.json(req.files.map((item) => item.filename)) }) router.post('/:postId/retweet', isLoggedIn, async (req, res, next) => { // POST /post/1/retweet try { const post = await Post.findOne({ where: { id: req.params.postId }, include: [{ model: Post, as: 'Retweet', }], }); if (!post) { return res.status(403).send('존재하지 않는 게시글입니다.'); } if (req.user.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user.id)) { return res.status(403).send('자신의 글은 리트윗할 수 없습니다.'); } const retweetTargetId = post.RetweetId || post.id; const exPost = await Post.findOne({ where: { UserId: req.user.id, RetweetId: retweetTargetId, }, }); if (exPost) { return res.status(403).send('이미 리트윗했습니다.'); } const retweet = await Post.create({ UserId: req.user.id, RetweetId: retweetTargetId, content: 'retweet', }); const retweetWithPrevPost = await Post.findOne({ where: { id: retweet.id }, include: [{ model: Post, as: 'Retweet', include: [{ model: User, attributes: ['id', 'nickname'], }, { model: Image, }] }, { model: User, attributes: ['id', 'nickname'], }, { model: User, // 좋아요 누른 사람 as: 'Likers', attributes: ['id'], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'], }], }], }) res.status(201).json(retweetWithPrevPost); } catch (error) { console.error(error); next(error); } }); module.exports = router;