묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨Windows 소켓 프로그래밍 입문에서 고성능 서버까지!
파일 송신 서버 제작 (예제 포함)
안녕하세요 선생님 질문이 있습니다. 파일 송신을 할때 server가 100을 보내도 client가 빠르게 처리 못하는 경우 50정도 만간다는 예시에서나머지 50은 tcp buffer(OS쪽) 에 남겨져 있다고 봐야하는건가요? 어디서 머무르고 있는것인가요?
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
람다 접근 에러
너무 자주 잘문드려 죄송합니다...2023-11-23T13:10:50.588Z 50b59392-754b-4b9c-90a1-ed48e95f40e1 ERROR AccessDenied: Access Denied at throwDefaultError (/var/task/node_modules/@smithy/smithy-client/dist-cjs/default-error-handler.js:8:22) at /var/task/node_modules/@smithy/smithy-client/dist-cjs/default-error-handler.js:18:39 at de_GetObjectCommandError (/var/task/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:4330:20) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async /var/task/node_modules/@smithy/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24 at async /var/task/node_modules/@aws-sdk/middleware-signing/dist-cjs/awsAuthMiddleware.js:14:20 at async /var/task/node_modules/@smithy/middleware-retry/dist-cjs/retryMiddleware.js:27:46 at async /var/task/node_modules/@aws-sdk/middleware-flexible-checksums/dist-cjs/flexibleChecksumsMiddleware.js:63:20 at async /var/task/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/region-redirect-endpoint-middleware.js:14:24 at async /var/task/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/region-redirect-middleware.js:9:20 { '$fault': 'client', '$metadata': { httpStatusCode: 403, requestId: 'S3JVT25F4WT5TH9H', extendedRequestId: 'i2FSNxeCIH5smb0tHWggtUQ7WWZIvDurOoQ4UGIZ1eVgwIPsJwrNC85V8Oh2XHVpCaFyITlXaaM=', cfId: undefined, attempts: 1, totalRetryDelay: 0 }, Code: 'AccessDenied', RequestId: 'S3JVT25F4WT5TH9H', HostId: 'i2FSNxeCIH5smb0tHWggtUQ7WWZIvDurOoQ4UGIZ1eVgwIPsJwrNC85V8Oh2XHVpCaFyITlXaaM='}이러한 에러가 발생했습니다. 찾아보니깐 s3 버컷 정책과 관련이 있는 것 같습니다.{ "Version": "2012-10-17", "Statement": [ { "Sid": "AddPerm", "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::whatsup1/*" } ]}이게 저의 s3 버캣 정책입니다.그리고 아래는 aws-upload의 index.js입니다 //이미지 리사이징 라이브러리 const sharp = require("sharp"); const { S3Client, GetObjectCommand, PutObjectCommand, } = require("@aws-sdk/client-s3"); //함수가 aws 람다에서 돌아가기 때문에 스크릿키랑 아이디를 자동으로 넣어준다 = > 아무것도 넣어줄 필요 x const s3 = new S3Client(); //람다는 3개의 매개변수를 제공하고 이 함수를 호출해준다. exports.handler = async (event, context, callback) => { const Bucket = event.Records[0].s3.bucket.name; const Key = decodeURIComponent(event.Records[0].s3.object.key); //original/리버풀.png const filename = Key.split("/").at(-1); const ext = Key.split(".").at(-1).toLowerCase(); const requiredFormat = ext === "jpg" ? "jpeg" : ext; console.log("name", filename, "ext", ext); try { const getObject = await s3.send(new GetObjectCommand({ Bucket, Key })); const buffers = []; for await (const data of getObject.Body) { buffers.push(data); } const imageBuffer = Buffer.concat(buffers); console.log("original", getObject); const resizedImage = await sharp(imageBuffer) .resize(200, 200, { fit: "inside" }) .toFormat(requiredFormat) .toBuffer(); await s3.send( new PutObjectCommand({ Bucket, Key: `thumb/${filename}`, Body: resizedImage, }) ); console.log("put", resizedImage.length); return callback(null, `thumb/${filename}`); } catch (error) { console.error(error); return callback(error); } }; 구글링 해보니깐 s3정책들이 비슷하면서 약간씩 다르던데 뭐가 맞는건지 잘 모르겠습니다..
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
람다 에러
stack": [ "Error: ", "Something went wrong installing the \"sharp\" module", "", "Cannot find module '../build/Release/sharp-linux-arm64v8.node'"이렇게 에러가 발생했는데 람다에서 함수 생성시 선택사항에 보면아키텍쳐 선택할때 제로초님은 기본적으로 선택되어있는 x86을 선택하셨고 저는 맥북에어 m2를 사용해서 arm64를 선택했는데 이거 때문에 에러가 난걸까요? 그리고 제로초님이 강의 만드실때는 node 18버전이 최신버전이였는데 지금은 20까지 나와서 20으로 했는데 이것도 문제가 될까요?
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
Error: Data too long for column
req.file을 보면 아래와 같이 나옵니다.{ fieldname: 'img', originalname: 'á\x84\x89á\x85³á\x84\x8Fá\x85³á\x84\x85á\x85µá\x86«á\x84\x89á\x85£á\x86º 2023-11-22 á\x84\x8Bá\x85©á\x84\x92á\x85® 8.36.00.png', encoding: '7bit', mimetype: 'image/png', size: 156946, bucket: 'whatsup1', key: 'original/1700700649389_á\x84\x89á\x85³á\x84\x8Fá\x85³á\x84\x85á\x85µá\x86«á\x84\x89á\x85£á\x86º 2023-11-22 á\x84\x8Bá\x85©á\x84\x92á\x85® 8.36.00.png', acl: 'private', contentType: 'application/octet-stream', contentDisposition: null, contentEncoding: null, storageClass: 'STANDARD', serverSideEncryption: null, metadata: undefined, location: 'https://whatsup1.s3.ap-northeast-2.amazonaws.com/original/1700700649389_%C3%A1%C2%84%C2%89%C3%A1%C2%85%C2%B3%C3%A1%C2%84%C2%8F%C3%A1%C2%85%C2%B3%C3%A1%C2%84%C2%85%C3%A1%C2%85%C2%B5%C3%A1%C2%86%C2%AB%C3%A1%C2%84%C2%89%C3%A1%C2%85%C2%A3%C3%A1%C2%86%C2%BA%202023-11-22%20%C3%A1%C2%84%C2%8B%C3%A1%C2%85%C2%A9%C3%A1%C2%84%C2%92%C3%A1%C2%85%C2%AE%208.36.00.png', etag: '"9afb9409e1bcd41269629b6bb1100245"', versionId: undefined}제로초님은 사진 파일 확장자가 jpg로 뜨는데 저는 png로 뜹니다..s3에 저장되는 쪽이 아니라 사진을 파일로 만드는 부분에서 문제가 있는 것 같은데 어느부분에서 손을 봐야할지 잘 모르겠습니다..const { S3Client } = require("@aws-sdk/client-s3"); const multerS3 = require("multer-s3"); const s3 = new S3Client({ credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, }, region: "ap-northeast-2", }); const upload = multer({ storage: multerS3({ s3, bucket: "whatsup1", key(req, file, cb) { cb(null, `original/${Date.now()}_${file.originalname}`); }, }), limits: { fileSize: 5 * 1024 * 1024 }, });그리고 추가적인 질문이 있는데 localhost로 서버를 작동시킬때 db는 잘 보이는데 lightsail로 작동시킨 db가 보이지 않습니다..mysql connection 추가 버튼 눌러서 hostname을 aws에서 제공해준 ip로 바꾸면 되는거 아닌가요?그런데 그렇게 하고 연결을 하니깐 버퍼링이 걸리면서 연결이 되지 않습니다..
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
카카오 로그인 질문드립니다!
안녕하세요!!제가 지금 Next.js와 Node.js를 활용해서 중고거래 사이트를 프론트와 백 둘다 구현을 하려고 하는데프론트(NextAuth)에서 API 를 받아와 카카오 로그인을 구현하면, 백에서는 카카오 로그인에대한 구현을 할 필요가 없는건가요?또, 프론트 나 백 둘중 어느곳에서 카카오 로그인을 구현해야 효율적인지 궁금합니다!
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
11.3 통합테스트 중 TypeError: model.initiate is not a function
질문할까 고민하다가 용기내어 질문드려봅니다..!11장 통합테스트 중 나타난 에러입니다.npm test 를 입력하면 나오는 에러입니다.> nodebird@0.0.1 test > jest PASS models/user.test.js PASS services/user.test.js PASS middlewares/index.test.js FAIL routes/auth.test.js ● Test suite failed to run TypeError: model.initiate is not a function 22 | console.log(file, model.name); 23 | db[model.name] = model; > 24 | model.initiate(sequelize); | ^ 25 | }); 26 | 27 | Object.keys(db).forEach(modelName => { // associate 호출 at initiate (models/index.js:24:11) at Array.forEach (<anonymous>) at Object.forEach (models/index.js:20:4) at Object.require (routes/auth.test.js:2:23) Test Suites: 1 failed, 3 passed, 4 total Tests: 9 passed, 9 total Snapshots: 0 total Time: 0.725 s, estimated 1 s현재 에러는 model.initiate가 함수화가 되지 않았다고 나타는거 같습니다.model.initiate가 존재하는 index.js입니다.const Sequelize = require('sequelize'); const fs = require('fs'); const path = require('path'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize( config.database, config.username, config.password, config, ); db.sequelize = sequelize; const basename = path.basename(__filename); fs .readdirSync(__dirname) .filter(file => { return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); }) .forEach(file => { const model = require(path.join(__dirname, file)); console.log(file, model.name); db[model.name] = model; model.initiate(sequelize); // 오류 발생시점 }); Object.keys(db).forEach(modelName => { if (db[modelName].associate) { db[modelName].associate(db); } }); module.exports = db;11.3 강의를 보는 중이며 auth.test.js 코드를 작성중입니다작성중인 auth.test.js는 아래와 같습니다.const app = require('../app'); const request = require('supertest'); const { sequelize } = require('../models'); beforeAll(async () => { await sequelize.sync() }) beforEach(() => { }); describe('POST /join', () => { test('로그인 안 했으면 가입', (done) => { request(app).post('/auth/join') .send({ email: 'choibo@naver.com', nick: 'bobobo', password: 'choibo11' }) .expect('Location', '/') .expect(302, done) }) }) describe('POST /login', () =>{ test('로그인 수행', (done) => { request(app).post('/auth/login') .send({ email: 'choibo@naver.com', password: 'choibo11' }) .expect('Location', '/') .expect(302, done) }) }); afterEach(() => {}); aftereAll(() => { }); 이부분에서 에러가 발생한다고 나타나있습니다.const { sequelize } = require('../models');
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
S3 버킷 만들 때 암호화 유형
버킷 만들때 강의 와 달리 처음부터 저렇게 선택되어있던데 그냥 원래 선택되었던거 유지하면서 만들면 되나요?
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
pm2 error
const express = require("express"); const cookieParser = require("cookie-parser"); const morgan = require("morgan"); const session = require("express-session"); const router = express.Router(); const path = require("path"); const dotenv = require("dotenv"); const passport = require("passport"); const app = express(); const passportConfig = require("./passport"); const redis = require("redis"); const RedisStore = require("connect-redis").default; dotenv.config(); const redisClient = redis.createClient({ url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, password: process.env.REDIS_PASSWORD, legacyMode: false, }); redisClient.connect().catch(console.error); const authRouter = require("./routes/auth"); const pageRouter = require("./routes/page"); const postRouter = require("./routes/post"); const userRouter = require("./routes/user"); const commentRouter = require("./routes/comment"); const updateRouter = require("./routes/update"); const deleteRouter = require("./routes/delete"); const helmet = require("helmet"); const hpp = require("hpp"); const { sequelize } = require("./models"); const logger = require("./logger"); passportConfig(); app.set("port", process.env.PORT || 8005); sequelize .sync({ force: false }) .then(() => { console.log("데이터베이스 연결 성공"); }) .catch((err) => { console.error(err); }); if (process.env.NODE_ENV === "production") { app.use( helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, crossOriginOpenerPolicy: false, originAgentCluster: false, }) ); app.use(hpp()); app.use(morgan("combined")); } else { app.use(morgan("dev")); } app.use(express.json({ limit: "10mb" })); var cors = require("cors"); const { deepStrictEqual } = require("assert"); app.use(cors()); app.use("/img", express.static(path.join(__dirname, "uploads"))); app.use("/profileImg", express.static(path.join(__dirname, "profileImg"))); app.use(express.json()); app.use(express.urlencoded({ limit: "10mb", extended: false })); app.use(cookieParser(process.env.COOKIE_SECRET)); const sessionOption = { resave: false, saveUninitialized: false, secret: process.env.COOKIE_SECRET, cookie: { httpOnly: true, secure: false, }, store: new RedisStore({ client: redisClient }), }; if (process.env.NODE_ENV === "production") { sessionOption.proxy = true; } app.use(session(sessionOption)); app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(path.join(__dirname, "prototype-client/build"))); app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "prototype-client/build/index.html")); }); app.use("/page", pageRouter); app.use("/auth", authRouter); app.use("/post", postRouter); app.use("/user", userRouter); app.use("/comment", commentRouter); app.use("/update", updateRouter); app.use("/delete", deleteRouter); //react에서 react-router-dom으로 다룰 수 있게 app.get("*", function (req, res) { res.sendFile(path.join(__dirname, "/prototype-client/build/index.html")); }); //에러 처리 담당 app.use((req, res, next) => { const error = new Error(`${(req, method)} ${req.url} 라우터가 없습니다.`); error.status = 404; logger.info("hello"); logger.error(error.message); next(error); }); app.use((err, req, res, next) => { console.error(err); res.locals.message = err.message; res.locals.erorr = process.env.NODE_ENV !== "production" ? err : {}; res.status(err.status || 500); res.render("error"); }); module.exports = app;이렇게 코드를 실행하면 sudo pm2 monit의 server log에 2가지 에러가 나옵니다. server > [Error: ENOENT: no such file or directory, stat │ ││ server > errno: -2, │ ││ server > code: 'ENOENT', │ ││ server > syscall: 'stat', │ ││ server > path: '/home/bitnami/Whats-up/prototype-client/bu ││ ││ server > expose: false, │ ││ server > statusCode: 404, │ ││ server > status: 404 │ ││ server > } 에러 메시지를 봤을때 해당 경로에 대한 파일을 찾을 수 없다고 하는데 ls를 입력했을때 잘 있는걸 확인 할 수 있는데 왜 이런 에러가 발생하는지 모르겠습니다..2번쨰는 server > Error: No default engine was specified and no ││ server > at new View (/home/bitnami/Whats-up/node_module │ ││ server > at Function.render (/home/bitnami/Whats-up/node │││ server > at ServerResponse.render (/home/bitnami/Whats-u │ ││ server > at /home/bitnami/Whats-up/app.js:127:7 │ ││ server > at Layer.handle_error (/home/bitnami/Whats-up/n │ ││ server > at trim_prefix (/home/bitnami/Whats-up/node_mod │ ││ server > at /home/bitnami/Whats-up/node_modules/express/ ││ server > at Function.process_params (/home/bitnami/Whats 이런 에러가 발생합니다. 에러 메세지를 봤을떄는 view engine관련된 에러같은데 react랑 express연동할때는 따로 view engine 설정을 주지 않고 express.static으로 리액트 코드를 전달하면 되는거 아닌가요?
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
AWS 설정 후 실행했을때 사이트에 연결할 수 없음이 뜹니다
저는 지금 단순 html페이지 대신 react와 함께 nodebird 프로젝트를 하고 있는데요. 지금 aws설정도 다 마치고 pm2를 실행하고 sudo pm2 list를 해봐도 오류 없이 잘 실행됩니다.그래서 크롬 주소창에 ip를 입력하면 사이트에 연결할 수 없다고 합니다.. 직접적으로 에러가 뜨는 것도 아니여서 어디가 문제인지 잘 모르겠습니다..github:https://github.com/AUDWO/Whats-up/blob/main/app.js(prototype-client가 리액트로 만든 페이지 입니다)
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
unread Count 같은 경우 실무에선는 DB에 마지막 일자를 기록하나요?
강의에서 localStorage 를 이용하여 접속시간 or 마지막 불러온 일자를 저장하고그 local 값을 unreads 에 after 파람으로 전송하여 읽지않은 메시지를 카운트 햇는데해당 방식은 다른 아이디로 로그인 시 같은 local 데이터를 활용하기도 하고 다른 기기에 접속시 해당값은 날라가게 될텐데 이는 에러사항으로 이어질 것으로 예상됩니다.마지막 일자를 활용하게 된다면 이 값은 db에 저장되야 할걸로 생각이 되는데 맞을까요?
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
내부디비를 세부적으로 쓰고싶은데...
sleact 서비스에 내부스토리지 (web스토리지,indexedDB) 를 좀더 체계적으로 써야될것같은데zustand 같은 상태관리 라이브러리에 내부스토리지를 연결하는게 좋을까요? 아니면 swr 같은데다 연결하는게 좋을까요? 아니면 내부스토리 접근하는 코드 따로 빼놓고 데이터 가져와서 state 에 관리하는게 나을까요?
-
미해결Slack 클론 코딩[실시간 채팅 with React]
onScroll 스크롤 위치 유지가 안됩니다 ㅠ
import Chat from '@components/Chat'; import { ChatZone, Section, StickyHeader } from '@components/ChatList/style'; import { IChat, IDM } from '@typings/db'; import React, { FC, RefObject, VFC, forwardRef, useCallback, useRef } from 'react'; import { Scrollbars } from 'react-custom-scrollbars'; interface Props { scrollRef: RefObject<Scrollbars>; chatSections: { [key: string]: IDM[] }; setSize: (f: (index: number) => number) => Promise<IDM[][] | undefined>; isEmpty: boolean; isReachingEnd: boolean; } const ChatList: VFC<Props> = (({ chatSections, setSize, isEmpty, scrollRef, isReachingEnd}) => { const onScroll = useCallback((values) => { // 끝에 도달하면 불러오지 않기 if(values.scrollTop === 0 && !isReachingEnd){ console.log('가장 위'); setSize((prevSize) => prevSize + 1).then(()=>{ // 스크롤 위치 유지 if(scrollRef?.current){ scrollRef.current?.scrollTop(scrollRef.current?.getScrollHeight() - values.scrollHeight); } }); } }, []); return ( <ChatZone> <Scrollbars autoHide ref={scrollRef} onScrollFrame={onScroll}> {Object.entries(chatSections).map(([date, chats]) => { return ( <Section className={`section-${date}`} key={date}> <StickyHeader> <button>{date}</button> </StickyHeader> {chats.map((chat) => ( <Chat key={chat.id} data={chat} /> ))} </Section> ); })} </Scrollbars> </ChatZone> ); }); export default ChatList; 이쪽 코드는 문제가 없는것같은데 희한하게 위치가 유지가 되지않고 원래처럼 쭉 올라가버립니다.. ref쪽이 문제인가요..? 혹시몰라 DirectMessage 컴포넌트도 아래에 첨부하겠습니다.import React, { useCallback, useEffect, useRef } from 'react'; import gravator from 'gravatar'; import useSWR, { mutate } from 'swr'; // swr 인피니티스크롤링 전용 메서드 import useSWRInfinite from 'swr/infinite'; import { IDM, IUser } from '@typings/db'; import fetcher from '@utils/fetcher'; import { useParams } from 'react-router'; import ChatBox from '@components/ChatBox'; import { Container, Header } from '@pages/DirectMessage/style'; import ChatList from '@components/ChatList'; import useInput from '@hooks/useInput'; import axios from 'axios'; import makeSection from '@utils/makeSection'; import Scrollbars from 'react-custom-scrollbars'; const DirectMessage = () => { const { workspace, id } = useParams<{ workspace: string; id: string }>(); const { data: userData } = useSWR(`http://localhost:3095/api/workspaces/${workspace}/users/${id}`, fetcher); // 내정보 const { data: myData } = useSWR(`http://localhost:3095/api/users`, fetcher); const [chat, onChangeChat, setChat] = useInput(''); // 과거 채팅리스트에서 채팅을 치면 최신목록으로 바로 스크롤을 내려줄려면 ref를 // 이 컴포넌트에서 props로 내려줘야하기 때문에 forwardRef를 사용해서 props로 넘겨준다 // 💡 HTML 엘리먼트가 아닌 React 컴포넌트에서 ref prop을 사용하려면 React에서 제공하는 forwardRef()라는 함수를 사용해야 합니다 const scrollbarRef = useRef<Scrollbars>(null); // 채팅 받아오는곳 (setSize : 페이지수를 바꿔줌) // useSWRInfinite를 쓰면 [{id:1},{id:2},{id:3},{id:4}] 1차원배열이 [[{id:1},{id:2}],[{id:3},{id:4}]] 2차원배열이 된다. const { data: chatData, mutate: mutateChat, setSize, } = useSWRInfinite<IDM[]>( (index) => `http://localhost:3095/api/workspaces/${workspace}/dms/${id}/chats?perPage=20&page=${index + 1}`, fetcher, ); // 데이터 40 개중에 20개씩 사져오면 첫번째페이지부터 20 + 20 + 0 세번째 페이지 0 이되면 isEmpty, isReachingEnd는 true가 됨 // 반대의 상황에서 데이터가 45개면 20 + 20 + 5 isEmpty는 0이 아니라서 false isReachingEnd는 여전히 데이터 가져옴 const isEmpty = chatData?.[0]?.length === 0; const isReachingEnd = isEmpty || (chatData && chatData[chatData.length - 1]?.length < 20) || false; const onSubmitForm = useCallback( (e) => { e.preventDefault(); if (chat?.trim() && chatData) { const savedChat = chat; // 💡 옵티미스틱 UI // 서버쪽에 다녀오지 않아도 성공해서 데이터가 있는거처럼 보이게 미리 만듦 mutateChat((prevChatData) => { // infinite 스크롤링은 2차원 배열이다. prevChatData?.[0].unshift({ // unshift : 앞쪽에 추가 id: (chatData[0][0]?.id || 0) + 1, content: savedChat, SenderId: myData.id, Sender: myData, ReceiverId: userData.id, Receiver: userData, createdAt : new Date(), }); return prevChatData; },false) // 옵티미스틱 UI 할땐 이부분이 항상 false .then(()=>{ setChat(''); // 버튼클릭 시 기존 채팅지우기 scrollbarRef.current?.scrollToBottom(); // 채팅 첬을때 맨 아래로 }) axios .post( `http://localhost:3095/api/workspaces/${workspace}/dms/${id}/chats`, { content: chat, }, { withCredentials: true, }, ) .then(() => { mutateChat(); // SWR에서 데이터를 다시 불러와서 캐시를 갱신하는 역할을 합니다. }) .catch(() => { console.error; }); } }, [chat, chatData, myData, userData, workspace, id], ); // (채팅이 최신것을 아래에 두기 위함) = 기존것 데이터를두고 새 데이터를 뒤집어서 출력 / flat() 배열을 1차원 배열로 만들어줌 const chatSections = makeSection(chatData ? [...chatData].flat().reverse() : []); // 로딩 시 스크롤바 제일 아래로 useEffect(()=>{ if(chatData?.length === 1){ // 채팅 데이터가 있어서 불러온 경우 scrollbarRef.current?.scrollToBottom(); // 가장 아래쪽으로 내려줌 } },[chatData]) // 로딩 if (!userData || !myData) { return null; } return ( <Container> <Header> <img src={gravator.url(userData.email, { s: '24px', d: 'retro' })} alt={userData.nickname}></img> <span>{userData.nickname}</span> </Header> {/* 컴포넌트 위치를 미리 지정해도 좋다. */} {/* 전역 상태관리 라이브러리를 사용해도 컴포넌트상황에따라 props 로 넘겨줌*/} <ChatList scrollRef={scrollbarRef} chatSections={chatSections} setSize={setSize} isEmpty={isEmpty} isReachingEnd={isReachingEnd} /> <ChatBox chat={chat} onChangeChat={onChangeChat} onSubmitForm={onSubmitForm} /> </Container> ); }; export default DirectMessage;
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
서버 실패시, 패스포트 로그인 실패시 통합테스트 방법
exports.login = (req, res, next) => { passport.authenticate('local', (authError, user, info) => { // 1. 서버 실패 부분 if (authError) { console.error(authError); return next(authError); } if (!user) { return res.redirect(`/?error=${info.message}`); } return req.login(user, (loginError) => { //2. 패스포트 로그인 실패 if (loginError) { console.error(loginError); return next(loginError); } return res.redirect('/'); }); })(req, res, next); }; --------------------- 1번과 2번은 통합테스트 코드로 어떻게 테스트 코드를 작성해야 하는지 여쭤봐도 될까요? 1번은 try - catch 로 어떻게 해보려해도 생각이 안나용 2번은 아애 생각이 안납니다 ㅜㅜ controllers를 자체를 단위테스트를 하는 것이 아니라 분기마다 test를 작성하라고 하신 말씀에 이 부분은 통합테스트로 코드를 짤 수 있나 의문이 들어서요! 감사합니다
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
7.5 시퀄라이즈 사용하기 에러
7.5 시퀄라이즈 사용하기 강좌 부분입니다.피피티와 동일하게 작성했는데 sync에러가 자꾸 뜹니다. 어떻게 고쳐야할지 모르겠습니다. app.jsconst express = require('express'); const path = require('path'); const morgan = require('morgan'); const nunjucks = require('nunjucks'); const { sequelize } = require('./models'); const app = express(); app.set('port', process.env.PORT || 3001); app.set('view engine', 'html'); nunjucks.configure('views', { express: app, watch: true, }); sequelize.sync({ force: false }) .then(() => { console.log('데이터베이스 연결 성공'); }) .catch((err) => { console.error(err); }); app.use(morgan('dev')); app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use((req, res, next) => { const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`); error.status = 404; next(error); }); app.use((err, req, res, next) => { res.locals.message = err.message; res.locals.error = process.env.NODE_ENV !== 'production' ? err : {}; res.status(err.status || 500); res.render('error'); }); app.listen(app.get('port'), () => { console.log(app.get('port'), '번 포트에서 대기 중'); }); index.jsconst Sequelize = require('sequelize'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize(config.database, config.username, config.password, config); db.seqelize = sequelize; module.exports = db;error ->[nodemon] restarting due to changes...[nodemon] starting node app.jsC:\Users\jyoun\udr_node\lecture\app.js:15sequelize.sync({ force: false }) ^TypeError: Cannot read properties of undefined (reading 'sync') at Object.<anonymous> (C:\Users\jyoun\udr_node\lecture\app.js:15:11) at Module._compile (node:internal/modules/cjs/loader:1241:14) at Module._extensions..js (node:internal/modules/cjs/loader:1295:10) at Module.load (node:internal/modules/cjs/loader:1091:32) at Module._load (node:internal/modules/cjs/loader:938:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12) at node:internal/main/run_main_module:23:47Node.js v20.7.0[nodemon] app crashed - waiting for file changes before starting... [제로초 강좌 질문 필독 사항입니다]질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.도움이 되는 질문을 하는 방법을 알려드립니다.https://www.youtube.com/watch?v=PUKOWrOuC0c0. 숫자 0부터 시작한 이유는 1보다 더 중요한 것이기 때문입니다. 에러가 났을 때 해결을 하는 게 중요한 게 아닙니다. 왜 여러분은 해결을 못 하고 저는 해결을 하는지, 어디서 힌트를 얻은 것이고 어떻게 해결한 건지 그걸 알아가셔야 합니다. 그렇지 못한 질문은 무의미한 질문입니다.1. 에러 메시지를 올리기 전에 반드시 스스로 번역을 해야 합니다. 번역기 요즘 잘 되어 있습니다. 에러 메시지가 에러 해결 단서의 90%를 차지합니다. 한글로 번역만 해도 대부분 풀립니다. 그냥 에러메시지를 올리고(심지어 안 올리는 분도 있습니다. 저는 독심술사가 아닙니다) 해결해달라고 하시면 아무런 도움이 안 됩니다.2. 에러 메시지를 잘라서 올리지 않아야 합니다. 입문자일수록 에러메시지에서 어떤 부분이 가장 중요한 부분인지 모르실 겁니다. 그러니 통째로 올리셔야 합니다.3. 코드도 같이 올려주세요. 다만 코드 전체를 다 올리거나, 깃헙 주소만 띡 던지지는 마세요. 여러분이 "가장" 의심스럽다고 생각하는 코드를 올려주세요.4. 이 강좌를 바탕으로 여러분이 응용을 해보다가 막히는 부분, 여러 개의 선택지 중에서 조언이 필요한 부분, 제 경험이 궁금한 부분에 대한 질문은 대환영입니다. 다만 여러분의 회사 일은 질문하지 마세요.5. 강좌 하나 끝날 때마다 남의 질문들을 읽어보세요. 여러분이 곧 만나게 될 에러들입니다.6. 위에 적은 내용을 명심하지 않으시면 백날 강좌를 봐도(제 강좌가 아니더라도) 실력이 늘지 않고 그냥 코딩쇼 관람 및 한컴타자연습을 한 셈이 될 겁니다.
-
미해결[웹 개발 풀스택 코스] Node.js 프로젝트 투입 일주일 전 - 기초에서 실무까지
그럼 그걸 다 들어야 하나요?
다른 강의에 테이블이 있다고 해서 가보니..파일로 있는 게 아니고 화면에 있던데..그러면 저 처럼 그런 강의가 필요 없는 사람은..그걸 다 보고 만들라는 건가요? 그러기엔 시간이 아까운데요?하루죙일..개발 하다가 왔는데..그걸 보고 일일이 쳐서 만들라는 건 좀 아니지 않나요?
-
미해결Slack 클론 코딩[실시간 채팅 with React]
useSwr 여러컴포넌트에 사용가능한가요
workSpace 에도 useSwr 로 유저데이터랑 채널데이터 요청하고모달안에서도 유저데이터랑 채널데이터 요청하는데 이러면 불필요한 요청이 모달하나 띄운다고 실행되는거 아닌가요? workSpace에서 revalidate 함수만 넘겨서 하면 어떤가요?
-
미해결Slack 클론 코딩[실시간 채팅 with React]
코드에 대한 질문이 잇습니다.
강의를 전부 진행한지 시간이 좀 된 상태에서 프로젝트 리팩토링중 이해가 안되는 부분이 있어 질문드립니다.아래 의문점에 대해 확인과 의견을 부탁드립니다.작성된 코드는 제로초님의 front / nest-typeorm 에서 가져온 코드입니다. 의문점 : 채팅 데이터 전송에 웹소켓이 역할을 하지 않는것 같다. 그렇게 생각한 근거 : 1-1 : useSocket을 이용해서 소켓에 연결하는데 ws-${workspace} 의 message에 onMessage 함수를 연결하고 있다const Channel = () => { const [socket] = useSocket(workspace); useEffect(() => { socket?.on('message', onMessage); return () => { socket?.off('message', onMessage); }; }, [socket, onMessage]); }const useSocket = (workspace?: string): [Socket | undefined, () => void] => { const disconnect = useCallback(() => { if (workspace && sockets[workspace]) { console.log('소켓 연결 끊음'); sockets[workspace].disconnect(); delete sockets[workspace]; } }, [workspace]); if (!workspace) { return [undefined, disconnect]; } if (!sockets[workspace]) { sockets[workspace] = io(`${backUrl}/ws-${workspace}`, { transports: ['websocket'], }); console.info('create socket', workspace, sockets[workspace]); sockets[workspace].on('connect_error', (err) => { console.error(err); console.log(`connect_error due to ${err.message}`); }); } return [sockets[workspace], disconnect]; };1-2 : 백엔드에서 채팅을 수신받는 createWorkspaceChannelChats는 ws-${url}-${chatWithUser.ChannelId} 의 message에 받아온 채팅을 보내고 있다. async createWorkspaceChannelChats( url: string, name: string, content: string, myId: number, ) { const channel = await this.channelsRepository .createQueryBuilder('channel') .innerJoin('channel.Workspace', 'workspace', 'workspace.url = :url', { url, }) .where('channel.name = :name', { name }) .getOne(); const chats = new ChannelChats(); chats.content = content; chats.UserId = myId; chats.ChannelId = channel.id; const savedChat = await this.channelChatsRepository.save(chats); const chatWithUser = await this.channelChatsRepository.findOne({ where: { id: savedChat.id }, relations: ['User', 'Channel'], }); this.eventsGateway.server // .of(`/ws-${url}`) .to(`/ws-${url}-${chatWithUser.ChannelId}`) .emit('message', chatWithUser); } 2 : 네트워크 탭의 웹소켓 메시지에 채팅내역 수신내역이 남지 않는다이미지가 보일지는 모르겟지만 빨간 박스가 새로 전송한 채팅이고 정상적으로 수신받으면 네트워크 탭에 messag에 내역이 남아야 하는걸로 알고 있는데 남지 않는걸로 확인됩니다. 3 : 웹페이지에서 포커스를 유지한 상태로 모바일에서 입력시 채팅이 전송되지 않음 왜 채팅이 정상적으로 전송된거 처럼 보일까 생각해보니 swr이 브라우저를 포커스 아웃후 재 포커스하면 채팅데이터를 다시 가져오는걸로 추측하여 웹페이지 포커스 유지중 모바일로 테스트해보니 채팅이 전송되지 않습니다.
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
NODE로 프로그램을 WINDOW 설치 프로그램으로 만들고싶습니다.
제가 만든 NODEJS 프로그램을 한글처럼 WINDOW 환경에서 통합설치 프로그램 하나만 설치하면 DB, NODE 프로그램이 설치되도록 하고싶은데 어떤 방법이 있고 구글에 어떻게 검색해야 하는지 궁금합니다.
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
MentionsTextarea 와 autoSize 사이 버그가 하나 있습니다.
MentionsTextarea 에 ctrl+c ctrl+v 를 이용하여 값을 입력시 사이즈는 조절되나 글자 줄 수가 잘려서 보이는 버그가 있습니다.MentionsTextarea 을 사용시 실제보이는 textarea와 보이지 않는 span 영역이 생성되는데 이 중 autosize에 span ref만 넘어가서 span영역만 커지고 textarea의 영역은 유지되서 생기는 버그로 보입니다.이후 글자를 제거 하거나 추가로 입력시 정상적으로 보이게 됩니다.
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
sequelize initiate() 쓰는 방식 최신인지, 공식문서 방법인지 궁금합니다
영상에서는 sequelize 공식문서에서 바뀌어서 따라갔다고 하는데 공식문서에서 initiate() 쓰는 것 검색하였을 때 안보이며 static이랑 같이 쓴 것도 아직은 못찾았습니다 공식문서 따라가보면 주로 define(), init()을 쓰는데 강의영상의 방식과는 차이점이 있습니다.define()const { Sequelize, DataTypes } = require('sequelize'); const sequelize = new Sequelize('sqlite::memory:'); const User = sequelize.define('User', { // Model attributes are defined here firstName: { type: DataTypes.STRING, allowNull: false }, lastName: { type: DataTypes.STRING // allowNull defaults to true } }, { // Other model options go here }); // `sequelize.define` also returns the model console.log(User === sequelize.models.User); // trueinit()// Invalid class User extends Model { id; // this field will shadow sequelize's getter & setter. It should be removed. otherPublicField; // this field does not shadow anything. It is fine. } User.init({ id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true } }, { sequelize }); const user = new User({ id: 1 }); user.id; // undefined 강의에서 알려주는 방식으로 DB연결도 되고 조작도 잘되지만이렇게 질문을 남긴 이유는강의에서는 공식문서를 따라갔다고 하는데 공식문서에서는 찾기 어렵다는 점과강의에 나오는 방식이 시기가 지난 방법이 아닐까 하는 생각에 질문 남기게 되었습니다 제가 찾아본게 잘못된걸수도 있기에 그런점 있다면 알려주세요글읽어주셔서 고맙습니다:)