묻고 답해요
130만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[Rookiss 켠김에 출시까지] MMO Lab 1기 <유니티 방치형 키우기 게임>
선생님께서 알려주신 코드
혼자서 안보고 작성할 정도 되야하나요제 뇌가 저사양이라 잘 안돌아가네요;
-
미해결스프링 시큐리티 완전 정복 [6.x 개정판]
AuthenticationManager에 초기화에 대해 질문있습니다
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); }위 처럼 Bean 으로 등록하게되면내부 메서드에서 Bean으로 등록된Builder를 통해 등록하는거 까지는 이해했는데요.근데 여기서 setShareObject와 같은게 없는데 return http.build();빌드하게되면 HttpSecurity 메서드의 @Override protected void beforeConfigure() throws Exception { if (this.authenticationManager != null) { setSharedObject(AuthenticationManager.class, this.authenticationManager); } else { ObservationRegistry registry = getObservationRegistry(); AuthenticationManager manager = getAuthenticationRegistry().build(); if (!registry.isNoop() && manager != null) { setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager)); } else { setSharedObject(AuthenticationManager.class, manager); } } } 가 호출되어 여기서else 조건을 타 getSharedObject 로 AuthenticationManagerBuilder 빌더 클래스를 다시 꺼내와서 빌드를 하는데 이렇게 되면 http.build 과정의 인증관리자와스프링컨테이너에 있는 인증관리자는서로 다른건가요? 같다면 우리가 등록한 Bean이 build 과정에서 나올것같은데못찾겠습니다 ㅠㅠ..
-
미해결[개정판 2023-11-27] Spring Boot 3.x 를 이용한 RESTful Web Services 개발
post userId 매칭
안녕하세요 강사님 좋은 강의 감사드립니다.post를 insert 할 때 user의 id가 post 의 user_id로 어떻게 매핑이 되어 db에 insert가 되는건지 궁금합니다.컬럼명을 따로 지정해준 것도 아닌데 어떻게 이렇게 매핑이 자동(?)으로 매핑이 되는 건가요?post 엔티티에서 setUser할 때 알아서 되는것일까요?확인해주시고 답변 주시면 감사드리겠습니다 !
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
Itemservice-db와 item service-db-start 두개다 존재하는데 어떤거 여나요?
두개의 폴더가 모두 존재하는데 start의 폴더를 이름변경하여 덮어쓰는것인지 아니면 이미 존재하는 itemservice-db를 오픈하면 되는것인지 혼란이 옵니다.두개의 폴더 모두 오픈해보았는데 db-start폴더는 item클래스가 영상과 같지만 generated폴더가 존재하지 않고 그냥 db 폴더는 item클래스가 영상과 다르지만 generated폴더가 존재합니다 어떤걸 이용하면 되나요?
-
미해결이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해
2강 11:20 전방 선언 불가인 이유가 궁금합니다.
안녕하세요!2강 11:20 부분에서 CreateDefaultSubobject<> 안에 UStaticMeshComponent를 전방 선언할 수가 없고 헤더에 포함시키는 이유가 궁금합니다!이유를 찾기가 힘들어서 질문드립니다
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
DTO 초기화 관련 질문드립니다.
@Data public class MemberSessionDto { private Long id; private String email; private String password; private String name; private Address address = new Address(); private List<Post> posts; } DTO의 필드 중 데이터가 들어있지 않은 필드가 존재할 수도 있을텐데요, 이럴 때는 new Address()와 같이 초기화를 해주는 것이 좋을까요, 아니면 타임리프에서 th:if="${findMember.address != null}"와 같이 null 처리를 해주는 것이 좋을까요? 도움이 되실까 해서 엔티티 코드도 추가로 첨부하겠습니다.package com.myproject.jpaboard.domain; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.ArrayList; import java.util.List; @Entity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PUBLIC) public class Member { @Id @GeneratedValue @Column(name = "member_id") private Long id; private String email; private String password; private String name; @Embedded private Address address; @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Post> posts = new ArrayList<>(); @Override public String toString() { return "Member{" + "address=" + address + ", name='" + name + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", id=" + id + '}'; } }
-
미해결스프링 시큐리티 완전 정복 [6.x 개정판]
SecurityContextHolderStrategy 설명해주신 부분에서 기존 방식 변경 방식에 대한 질문입니다.
안녕하세요, 24:00 에 말씀해주시는 SecurityContextHolderStrategy 사용하기부분에 기본방식과 변경방식 설명해주시는 부분에서 이해가 안되는 부분이 생겨 질문드립니다.기존방식과 변경방식의 구현된 소스코드를 보니 내부에서(SecurityContextHolder.crateEmptyContext() 내부) 도 결론적으로는 strategy.createEmptyContext() 를 delegate 하는 방식으로 호출 되는것을 보았습니다. 6.x, 5.x, 4.x 모두 동일한듯합니다. /** * Allows retrieval of the context strategy. See SEC-1188. * @return the configured strategy for storing the security context. */ public static SecurityContextHolderStrategy getContextHolderStrategy() { return strategy; } /** * Delegates the creation of a new, empty context to the configured strategy. */ public static SecurityContext createEmptyContext() { return strategy.createEmptyContext(); } AbstractAuthenticationProcessingFilter 쪽에서 successfulAuthentication 쪽에 로직dl 6.x, 5.7 이하 버전 대가 다른단것은 확인했는데 /** * Puts the <code>Authentication</code> instance returned by the authentication * manager into the secure context. */ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { this.logger.debug(LogMessage.format("Authentication success: %s", authResult)); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authResult); this.securityContextHolderStrategy.setContext(context); this.securityContextRepository.saveContext(context, request, response); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } if (this.authenticationSuccessHandler != null) { this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authResult); } }/** * Puts the <code>Authentication</code> instance returned by the authentication * manager into the secure context. */ protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } if (authenticationSuccessHandler != null) { authenticationSuccessHandler.onAuthenticationSuccess(request, response, authResult); } }추측건데 strategy 쪽을 세팅할수있는 부분이 추가됨에따라 달라진 부분이라 생각이 되어 이부분도 말씀하신 의도와는 다를듯하여 /** * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. * * @since 5.8 */ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); this.securityContextHolderStrategy = securityContextHolderStrategy; } 경쟁조건 때문이라고 설명하신 부분에서 말씀하시는 의도를 다시 한번 알수있을까요.
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
ssh xxxxx로 우분투에 들어가려니까 port 22: Connection timed out
nginx 부분을 따라하다가실수로 서버에서 나갔다가 다시 들어가려니까ssh: connect to host zzimzzim-front port 22: Connection timed out위와같은 에러가나면서 갑자기 들어가지지 않습니다.검색했는데도 해결되지 않아서 답답해다가이럴땐 어떤걸 참고하면 좋을지 조언 주시면 감사하겠습니다. ㅠ
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
sudo certbot --nginx 에러
ubuntu@ip-172-31-37-191:~/react-nodebird/prepare/front$ sudo certbot --nginx Saving debug log to /var/log/letsencrypt/letsencrypt.log Which names would you like to activate HTTPS for? We recommend selecting either all domains, or all domains in a VirtualHost/server block. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: zzimzzim.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): Requesting a certificate for zzimzzim.com Certbot failed to authenticate some domains (authenticator: nginx). The Certificate Authority reported these problems: Domain: zzimzzim.com Type: connection Detail: 3.37.101.58: Fetching http://zzimzzim.com/.well-known/acme-challenge/SNUl0WTK7OKWJ-oYyHTrIFuh67ww_P11CUJXw2zWRZk: Timeout during connect (likely firewall problem) Hint: The Certificate Authority failed to verify the temporary nginx configuration changes made by Certbot. Ensure the listed domains point to this nginx server and that it is accessible from the internet. Some challenges have failed. Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.위와같은 에러가 발생합니다.다른 질문들을 보고 제로초님https://www.zerocho.com/category/NodeJS/post/5ef450a5701d8a001f84baeb 따라해보았는데 해결되지 않아서 여쭤봅니다.
-
미해결ASP.NET Core MVC +ASP.NET Core +REST API +.NET 8.0
asp.net sercurity에 대한 추후 강의 계획 있으신가요?
해외 원서로 공부하고 있긴 한데, 막상 적용하려니 체감이 잘 되지 않아서요.. 혹시나 해서 질문 남깁니다.
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
em.find() 및 쓰기 지연
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]//given Member member = new Member(); member.setUsername("영한님 최고"); em.persist(member); em.flush(); em.clear(); //when & then System.out.println("================="); Member findMember = em.find(Member.class , member.getId()); System.out.println("================="); tx.commit();em.persist()로 저장 이후 em.flush & em.clear()로 영속성 컨텍스트의 1차 캐시를 깔끔하게 비우고 em.find()를 실행해보았습니다. 위와 같이 코드가 되어있을 때 ========== 사이에서 select 문이 db로 나감을 알 수 있었습니다.즉 이에 따라 em.find() 시점에 flush가 되는 것을 알 수 있었습니다. 해당 부분에서 궁금한 점이 두 가지 생겼습니다. [핵심 질문]flush 타이밍은 em.flush() , 트랜잭션 커밋 , JPQL 실행 일 경우에만 flush가 일어나서 실제 DB에 쿼리가 나간다고 알고 있습니다. 즉 이에 따라 em.find()는 내부적으로 JPQL을 통해서 조회를 한다고 이해하면 될까요? 사실 맨 처음 기대했던 것은 코드 맨 마지막 트랜잭션 커밋 시점(tx.commit())에 flush가 되며 저장 쿼리 및 조회 쿼리가 순차적으로 나가는 것을 기대하였습니다. 하지만 em.persist()로 저장하는 경우에는 트랜잭션 커밋 시점에 flush가 일어나고 em.find()로 조회하는 경우에는 그 즉시 flush가 되며 조회 쿼리가 나가는 것을 알 수 있었습니다 따라서 영속성 컨텍스트의 "쓰기 지연" 기능은 em.persist()를 통한 "엔티티 저장" 시에만 반영되는 특성인가요? 물론 조회 후 수정을 할 경우엔 트랜잭션 커밋 시점에 더티 체킹을 통해 update 쿼리를 날리는 것은 인지하고 있습니다 !
-
미해결[리뉴얼] 코딩자율학습 제로초의 자바스크립트 입문
야구게임 관련 질문입니다
야구게임에서 숫자말고 다른걸 입력했을 때 경고가 뜨게 하고 싶어서형식 확인하는 함수에서 마지막에 코드를 추가해봤는데 숫자를 입력했을 때도 경고가 뜨더라구요이래저래 해보는데 안돼서 방법이 잘못된건가 싶어서 여쭤봅니다! 혹시 애초에 input type이 text여서 그런걸까요..?
-
해결됨독하게 C를 배운 사람을 위한 선형 자료구조
RangeSearch002 의 singlelist.c 파일 중 질문 있습니다.
강사님 안녕하십니까. 양질의 수업을 제공해주셔서 잘 듣고 있습니다. 다름아니고 질문이 있어서 여쭙고자 게시글 남깁니다.다름아니고 RangeSearch002 프로젝트 중 singlelist.c 파일의 SearchByAgeRange 함수에서 아래와 같은 코드에서 이해가 되지 않는 점이 있습니다. """void** pNodePtrList = malloc(sizeof(void*) * cnt);""" 본 질문의 목적은 아래와 같습니다.ㅇ malloc을 할 때 왜 따로 타입캐스팅을 하지 않았는가? 검색해서 찾아봤을 때 malloc은 타입캐스팅을 해도 되고 안 해도 되지만 제가 한다면 (void *)malloc 으로 썼을 거 같으며, 디버깅했을 때 결과도 동일한 결과물을 제공하는 것으로 확인했습니다.혹시 잘못된 점이 있으면 따끔한 지적 부탁드리겠습니다. 감사합니다. 좋은하루 되십시오.
-
해결됨Next + React Query로 SNS 서비스 만들기
vercel 배포했는데 잘 되다가 안들어가집니다.
안녕하세요 제로초님 강의 잘 듣고있었는데, 강의 질문이 아니라 다른 질문이라 죄송합니다. 당장 다음주 월요일부터 심사가 시작되는 공모전에 나갈 프로젝트인데 잘 되던게 갑자기 안들어가져서요 ㅠㅠㅠㅠ 약 한달전에 배포 cicd 설정 다 해놓고 어제밤까지만해도 문제없이 잘 들어가졌는데 오늘 갑자기 안들어가지네요.. dns_probe_finished_nxdomain 이라고 뜬대요 근데 제 pc로는 캐시를 다 삭제하고 들어가도 잘 되거든요 근데 모바일에선 안되고 다른 사람은 안들어가진다고 하는데 원인이 뭘까요..? 제 pc에서도 크롬에서만 들어가지고 다른 브라우저는 안됩니다. 메일로 vercel에서 뭐 온것도 없습니다.. vercel 설정에서 domains에 문제도 없구요.메일로 vercel에서 뭐 온것도 없습니다.빌드 에러도 없구요무료플랜이었어서 유료플랜으로 업그레이드 했는데도 똑같습니다.. ㅠㅠ 도메인이름은 공개적으로 못 적지만 개인적으로 알려드리겠습니다. ci/cd 되어있어서 main에 계속 push해봐도 빌드 잘 되는데 들어가지지를 않아요...
-
미해결[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
Glide gradle implementation 관련 질문
안녕하세요.Glide를 사용하려다 gradle 문법이 달라서 질문 드립니다. 제가 쓰는 안드로이드 스튜디오는 Iguana 2023.2.1 입니다저의 소스는 implementation(libs....) 이런 식인데,가이드는 implementation 'com.github.bumptech.glide:glide:4.16.0'이렇습니다.어떻게 하면 될까요?관련 내용을 알려면 어떻게 어디를 찾아 보면 될까요?
-
미해결처음하는 딥러닝과 파이토치(Pytorch) 부트캠프 [데이터과학 Part3]
test data 의 loss 계산식 문의
10_ADVANCE-EXAMPLE-MILTI-LABEL-CLASSIFICATION 과 11_MILTI-LABEL-CLASSIFICATION-DROPOUT-BATCHNORMALIZATION 강의자료에서 맨 밑부분의 테스트셋 기반 Evaluation 에서 test_loss 를 계산할 때 전체 데이터인 10000으로 나누셨는데, 왜 그러신건지 궁금해서 질문드립니다.train 과 validation 의 loss 계산은 train_batches 와 val_batches 개수(for문 도는 횟수) 만큼만 나누셨는데 test loss 를 계산할 때는 minibatch 가 아닌 전체 데이터로 나누셔서 상대적으로 test data 의 loss 값이 작아보여서요.test_loss = 0correct = 0wrong_samples, wrong_preds, actual_preds = list(), list(), list()model.eval()with torch.no_grad(): for x_minibatch, y_minibatch in test_batches: y_test_pred = model(x_minibatch.view(x_minibatch.size(0), -1)) test_loss += loss_func(y_test_pred, y_minibatch) pred = torch.argmax(y_test_pred, dim=1) correct += pred.eq(y_minibatch).sum().item() wrong_idx = pred.ne(y_minibatch).nonzero()[:, 0].numpy().tolist() for index in wrong_idx: wrong_samples.append(x_minibatch[index]) wrong_preds.append(pred[index]) actual_preds.append(y_minibatch[index]) test_loss /= len(test_batches.dataset)
-
미해결AWS Certified Solutions Architect - Associate 자격증 준비하기
Aws 엔드포인트의 종류
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. https://docs.aws.amazon.com/ko_kr/general/latest/gr/rande.html#view-service-endpoints이 엔드포인트는 VPC 엔드포인트와 같은건가요? 다른건가요?다르다면 이렇게 이해해도 될까요? 서비스엔드포인트-Custom vpc가 없는 서비스를 생성할때 디폴트로 생성되는 인터넷 기반의 서비스 진입지점 vpc엔드포인트-서비스 엔드포인트를 인터넷 통신없이 접속하기 위해 추가로 생성하는 서비스 진입지점. private ip를 갖는 eni와 연결.한 vpc 안에 여러 개의 엔드포인트(ex s3 sts kms sagemaker lambda apig/w등)를 생성하는 경우 여러개의 eni 필요함 만약에 vpc endpoint를 공통계정에 통합하면 어떻게 되는건가요? 공통계정의 vpc endpoint는 dns역할을 하고 세부 서비스는 각각의 상세 서비스로 가나요??ex endpoint *.vpce.kms.aws.amazon.com common account
-
미해결[퇴근후딴짓] 빅데이터 분석기사 실기 (작업형1,2,3)
섹션4 평가지표 강의 13:50에서
pd.get_dummies(y_true[0])위 코드를 실행하면 강의에서는 0,1로 df이 보여지는데 제 실행결과에서는 True, False로 보여집니다어느 부분에서 차이가 난걸까요?? (이전 셀 모두 실행하기 적용해도 출력이 동일합니다)
-
미해결배달앱 클론코딩 [with React Native]
xcode 빌드 시 Command PhaseScriptExecution failed with a nonzero exit code 에러가 나옵니다
안녕하세요 제로초님 안드로이드는 시뮬레이터에서 돌아가는 것 보고 iOS도 해보고 있는 중입니다.그런데 계속 Command PhaseScriptExecution failed with a nonzero exit code 나오면서 빌드가 실패하네요 https://github.com/facebook/react-native/issues/36762https://stackoverflow.com/questions/75975043/xcode-error-phasescriptexecution-failed-with-a-nonzero-exit-code 위의 게시물들을 참고해서 수정도 해봤고 node_modules를 지우고 다시 설치도 해봤습니다. pod deintegrate와 캐시를 지운 후에 다시 pod install도 해줬습니다. 그래도 지속적으로 문제가 해결되지 않았습니다. 지금 애플 디벨로퍼에서 인증서들은 모두 발급 받고 Xcode에도 제 계정이 연결되어 있습니다. 하나 걸리는 건 react-native 버전이 0.74.0 인데 이 부분이 문제가 될 수도 있을까요?"react-native": "0.74.0" 혼자 해결해 보려고 했는데 도저히 답이 나오지 않아서 질문 드립니다. 늘 질문 받아주셔서 감사합니다
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
Minified React error 콘솔에러 (hydrate)
안녕하세요 선생님카카오톡 공유하기 까지 듣고 화면을 테스트해보고 있는데Minified React error 이 에러가 떠서 질문드립니다. 메인화면, 상세보기에서 게시글이 있을때 나옵니다.Uncaught Error: Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418 for the full message or use the non-minified dev environment for full errors and additional helpful warnings. at lg (framework-ecc4130bc7a58a64.js:9:46457) at i (framework-ecc4130bc7a58a64.js:9:121052) at oO (framework-ecc4130bc7a58a64.js:9:99019) at framework-ecc4130bc7a58a64.js:9:98886 at oF (framework-ecc4130bc7a58a64.js:9:98893) at oS (framework-ecc4130bc7a58a64.js:9:93932) at x (framework-ecc4130bc7a58a64.js:33:1364) at MessagePort.T (framework-ecc4130bc7a58a64.js:33:1894)검색해봤더니 hydrate 쪽 이슈더라구요.혼자서 풀어보다가 잘 풀리지 않아서 여쭤봅니다.혹시 제로초님은 저런 에러가 나지 않으시는지난다면 어느 로직을 확인해야할지 조언 부탁드립니다./pages/post/[id].jsimport axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { useSelector } from 'react-redux'; import AppLayout from '../../components/AppLayout'; import PostCard from '../../components/PostCard'; import { loadPost } from '../../reducers/post'; import { loadMyInfo } from '../../reducers/user'; import wrapper from '../../store/configurStore'; const Post = () => { const router = useRouter(); const { id } = router.query; const { singlePost } = useSelector((state) => state.post); return ( <AppLayout> {singlePost ? ( <> <Head> <title> {singlePost?.User.nickname} 님의 글 </title> <meta name="description" content={singlePost.content} /> <meta property="og:title" content={`${singlePost.User.nickname}님의 게시글`} /> <meta property="og:description" content={singlePost.content} /> <meta property="og:image" content={ singlePost.Images[0] ? singlePost.Images[0].src : 'https://nodebird.com/favicon.ico' } /> <meta property="og:url" content={`https://nodebird.com/post/${id}`} /> </Head> <PostCard post={singlePost}>{id}번 게시글</PostCard> </> ) : ( <div>존재하지 않는 게시물입니다.</div> )} </AppLayout> ); }; export const getServerSideProps = wrapper.getServerSideProps( (store) => async ({ req, params }) => { const cookie = req ? req.headers.cookie : ''; axios.defaults.headers.Cookie = ''; // 쿠키가 브라우저에 있는경우만 넣어서 실행 // (주의, 아래 조건이 없다면 다른 사람으로 로그인 될 수도 있음) if (req && cookie) { axios.defaults.headers.Cookie = cookie; } await store.dispatch(loadMyInfo()); await store.dispatch(loadPost(params.id)); }, ); export default Post; /components/PostCard.jsimport { RetweetOutlined, HeartOutlined, HeartTwoTone, MessageOutlined, EllipsisOutlined, } from '@ant-design/icons'; import { Card, Popover, Button, Avatar, List } from 'antd'; import dayjs from 'dayjs'; import Link from 'next/link'; import PropTypes from 'prop-types'; import { useState, useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import CommentForm from './CommentForm'; import Followbutton from './FollowButton'; import PostCardContent from './PostCardContent'; import PostImages from './PostImages'; import { removePostRequestAction, likePostRequestAction, unLikePostRequestAction, retweetRequestAction, } from '../reducers/post'; dayjs.locale('ko'); const PostCard = ({ post }) => { // const { me: {id} } = useSelector((state) => state.user); // const id = me && me.id; // const id = me?.id; // 옵셔널 체이닝 연산자 const { removePostLoading } = useSelector((state) => state.post); const dispatch = useDispatch(); const [commentFormOpend, setCommentFormOpend] = useState(false); const id = useSelector((state) => state.user.me?.id); const liked = post.Likers.find((d) => d.id === id); const onLike = useCallback(() => { if (!id) { alert('로그인이 필요합니다.'); } dispatch(likePostRequestAction(post.id)); }, [id]); const onUnLike = useCallback(() => { if (!id) { alert('로그인이 필요합니다.'); } dispatch(unLikePostRequestAction(post.id)); }, [id]); const onToggleComment = useCallback(() => { setCommentFormOpend((prev) => !prev); }, []); const onRemovePost = useCallback(() => { if (!id) { alert('로그인이 필요합니다.'); } dispatch(removePostRequestAction({ id: post.id })); }, [id]); const onRetweet = useCallback(() => { if (!id) { alert('로그인이 필요합니다.'); } dispatch(retweetRequestAction(post.id)); }, [id]); return ( <div style={{ marginTop: 10 }}> <Card cover={post.Images[0] && <PostImages images={post.Images} />} actions={[ // 배열안에 들어가는 것들은 다 key를 넣어줘야 한다. <RetweetOutlined key="retweet" onClick={onRetweet} />, liked ? ( <HeartTwoTone key="heart" twoToneColor="#eb2f96" onClick={onUnLike} /> ) : ( <HeartOutlined key="heart" onClick={onLike} /> ), <MessageOutlined key="comment" onClick={onToggleComment} />, <Popover key="more" content={ <Button.Group> {id && post.User?.id === id ? ( <> <Button type="primary" key="modify"> 수정 </Button> <Button type="danger" key="delete" onClick={onRemovePost} loading={removePostLoading} > 삭제 </Button> </> ) : ( <Button type="dashed" key="report"> 신고 </Button> )} </Button.Group> } > <EllipsisOutlined /> </Popover>, ]} extra={id && <Followbutton post={post} />} title={ post.RetweetId ? `${post.User.nickname}님이 리트윗하셨습니다.` : null } > {post.RetweetId && post.Retweet ? ( <Card cover={ post.Retweet.Images[0] && ( <PostImages images={post.Retweet.Images} /> ) } > <div style={{ float: 'right' }}> {dayjs(post.createdAt).format('YYYY.MM.DD')} </div> <Card.Meta avatar={ <Link href={`/user/${post.Retweet.User.id}`}> <Avatar>{post.Retweet.User?.nickname[0]}</Avatar> </Link> } title={post.Retweet.User?.nickname} description={<PostCardContent postData={post.Retweet.content} />} /> </Card> ) : ( <> <div style={{ float: 'right' }}> {dayjs(post.createdAt).format('YYYY.MM.DD')} </div> <Card.Meta avatar={ <Link href={`/user/${post.User.id}`}> <Avatar>{post.User?.nickname[0]}</Avatar> </Link> } title={post.User?.nickname} description={<PostCardContent postData={post.content} />} /> </> )} </Card> {commentFormOpend && ( <div> {/* 게시글의 아이디 위해서 post 넘겨줌 */} <CommentForm post={post} /> <List header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} renderItem={(item) => ( <List.Item key={item.id}> <List.Item.Meta title={item.User.nickname} avatar={ <Link href={`/user/${item.User.id}`}> <Avatar>{item.User.nickname[0]}</Avatar> </Link> } description={item.content} /> </List.Item> )} /> </div> )} </div> ); }; PostCard.propTypes = { post: PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, content: PropTypes.string, createdAt: PropTypes.string, Comments: PropTypes.arrayOf(PropTypes.object), Images: PropTypes.arrayOf(PropTypes.object), Likers: PropTypes.arrayOf(PropTypes.object), RetweetId: PropTypes.number, Retweet: PropTypes.objectOf(PropTypes.any), }).isRequired, }; export default PostCard; 여유되실때 한번 봐주시면 감사하겠습니다