안녕하세요

프로젝트 설계 연습 본문

유튜브컨텐츠탐색-StelLife

프로젝트 설계 연습

sakuraop 2023. 2. 4. 16:16

REACT로 서버 구현부터 MongoDB 연결까지

npm i sass
npm i bootstrap-icons

 

express 설치하기
$ npm i express

 


cors 설치하기
$ npm install cors

 



nodemon 설치하기 (저장할 때마다 자동으로 서버를 재실행 해서 편리해짐)
$ npm i -g nodemon
그리고나서 nodemon concurrently를 설치해줍니다. (요하다면)
npm install nodemon concurrently --save-dev
 ,   "server": "nodemon server",
    "client": "npm start",
    "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client"



const express = require("express");
const path = require("path");
const app = express();
const PORT = 8080;

 

// Form에서 전달 받은 값 이용하기 값을 가져오려면 상단에 추가
app.use(express.urlencoded({ extended: true }));
// 유저가 보낸 array/object 데이터를 출력하기 위함
app.use(express.json());
// cors는 다른 도메인 주소끼리 ajax 요청을 주고받기 위해 필요한 브라우저 정책
var cors = require("cors");
app.use(cors());


app.listen(PORT, () => {
  console.log(`${PORT} 포트 서버 실행됨.`);
});

// 특정 폴더(프로젝트의 build)의 파일을 static 파일로 전송하기
app.use(express.static(path.join(__dirname, "../build"))); // 프로젝트위치/build



// 서버가 GET요청을 하면 DB에서 데이터를 받아와 보내주는  API 만들기
app.get("/", (요청, 응답) => {
  응답.sendFile(path.join(__dirname, "build/index.html")); // 메인페이지('/')로 접속하면 html파일을 보내주세요.
});

// '/product'로 ajax 이용해서 GET 요청을 보내면 됩니다.
app.get("/product", (요청, 응답) => {
  응답.json({ name: "uriri" });
});

// "*" 어떤 url을 접속하든 react-router
app.get("*", (요청, 응답) => {
  응답.sendFile(path.join(__dirname, "../build/index.html")); // index.html 을 띄워주세요
});





react-router 설치하기
$ npm install react-router-dom@6

 

 

 

라우터 테스트 O
deploy 테스트 O
데이터 테스트 💬
koyeb으로 배포 테스트 O
https://08genie.github.io/posts/koyeb-deploy/

 

 


axios 설치하기
$ npm i axios 

 

 

redux toolkit 설치하기
$ npm install @reduxjs/toolkit react-redux
https://sakuraop.tistory.com/395

 

 

 

googleapis (api 라이브러리) 설치하기 
$ npm install googleapis --save



Form에서 전달 받은 값 이용하기
값을 가져오려면 상단에 추가

app.use(express.urlencoded({extended: true})) 

https://sakuraop.tistory.com/406

 

 

 

Atlas DB만들기
https://sakuraop.tistory.com/407

터미널에서 MongoDB 설치하기
$ npm i mongodb@3.6.4

server.js (서버 파일)에서 다음 코드를 입력하기

const mongoUri = process.env.REACT_APP_DB_ACCESS;

const mongoUri = "mongodb+srv://디비계정아이디:디비계정패스워드@cluster0-qaxa3.mongodb.net/데이터베이스이름?retryWrites=true&w=majority";

=> 주소를 변수에 담고

const { MongoClient } = require("mongodb");
=> MongoClient 모듈을 불러오고

 

const client = new MongoClient(mongoUri, { useUnifiedTopology: true });
=> MongoClient 객체 생성. 두번째 인자는 크게 신경 쓸 필요 없는 warning을 없애준다고 함

 

let db;
 
client.connect((에러) => {
  if (에러) return console.log(에러);

  db = client.db("test");

  app.listen(PORT, () => {
    console.log(`${PORT} 포트 서버 실행. Database에 연결 되었음`);
  });
});
=> db에 연결을 하고, express server 실행




리액트 .env 환경변수파일 이용하기
npm install --save dotenv
require("dotenv").config();
console.log(process.env.REACT_APP_API_KEY);



server로 POST하여 데이터 보내기
<div onClick={() => axios.post("http://localhost:8080/add", { todo: "todo" })
.then((res) => console.log(res))}>POST</div>

app.post("/add", (req, res) => {
  res.send("sent");
  console.log(req.body);
});



server에서 GET하여 DB 데이터를 가져오기
const [testWord, setTestWord] = useState("");

<div
    onClick={() =>
    axios
        .get("http://localhost:8080/list")
        .then((res) => setTestWord(res.data.test))
        .then((res) => console.log(testWord))
    }
>
    Get testWord
</div>
{testWord.length ? <div>{testWord.map((el) => el.name)}</div> : "list"}

app.get("/list", (요청, 응답) => {
  // 모든 데이터 가져오기
  db.collection("post")
    .find()
    .toArray((에러, 결과) => {
      console.log(결과);
      응답.send({ test: 결과 });
    });
});

 

 

 

KOYEB에서 .env 환경변수 이용하는 방법 

=> 환경변수파일을 저장한 .env파일은 .gitignore로 github에 해당 파일을 업로드하지 않는다.

따라서 Koyeb 서버에서는 이 환경변수들을 읽어들이기 위해 설정이 필요하다.

  

=> 프로젝트를 만들고 나서 Settings 항목을 클릭

 

=> 환경 변수를 추가하는 기능이 있다.

 

=> Create secret 버튼을 눌러 비밀 환경 변수를 만들 수 있는데, 작성 이후에는 자신도 다시 볼 수 없다.

버그인지 모르겠지만 Value 항목에 저장된 값들은 삭제를 할 수 없으니 한번 입력할 때 잘 작성해야 보기에 깔끔하고 좋다.

 

=> db 주소와 apiKey를 브라우저 console 창에 출력한 결과,

PORT는 undefined인데, 환경변수 테스트를 위해 뒤늦게 추가하여 commit한 것으로

server에서 아직 변경된 사항을 반영해주지 않아서이다.

 

=> 얼마를 주기로 commit된 서버를 배포하는지 모르겠지만 수 분의 짧은 시간 안에는 재배포되지는 않는 듯 하다.

 

=> Redeploy 버튼을 눌러본다.

 

=> 기본의 deploy는 실패하고 새로운 작업이 Pending에 있다.

 

=> dashboard에서 Degraded 라는 표시를 확인하여 찾아보았다.

 

=> 일시적인 현상으로 저절로 해결이 되기도 하나, 수정을 할 필요가 있을 수도 있다는 뜻이다.

 

=> 너무 오래 걸리는 것은 문제가 있어서 안될 때 생기는 문제라고 보면 될 것 같다.

이제 무엇 때문에 안 되는 것인지 원인을 찾을 수 있으면 좋겠는데...

 

PORT 관련 문제로 보임

=> 위의 PORT 환경변수를 설정하지 않으면 배포가 안된다.

const PORT = 8080;
  app.listen(PORT, () => {
    console.log(`${PORT} 포트 서버 실행. Database에 연결 되었음`);
  });

이 PORT 변수가 8080으로 강제 할당이 되는 듯 하다.

 

=> 정상작동은 하지만 이제 WebSocket 에러가 남아있다. 이건 별도의 문제같다.

 

https://github.com/facebook/create-react-app/issues/11779

https://stackoverflow.com/questions/70585472/websocketclient-js16-websocket-connection-to-ws-localhost3000-ws-failed-r

 

이거 보고 해결했다.

=> 환경변수에 WDS_SOCKET_PORT=0 

 

을 추가하면 된다. 누구는 443, 누구는 0 둘 다 상관없어 보인다.

https://github.com/facebook/create-react-app/issues/11897

=> 해결이 됐다 하는데 이유는 안알려주고 가셨다. 이래서 개발자 되려면 네트워크 공부를 해야하나보다,

 

=> Koyeb app 설정으로 가서 환경변수에 추가해주라는 대로 추가해준다.

deploy가 끝나고 확인해보면~

 

=> 이제 배포도 잘 되고, websocket 관련 에러도 안 뜬다.


DB

channel: 모든 채널

video: search로 탐색한 모든 중복되지 않은 비디오 업로드 일자별로 구분

playlist: 재생목록


"/search" (100P * videoDailyUploadCount / 50 )

"최신순" query 검색

const getLatestSearch = async (q, amount) => {
  const params = {
    key: apiKey,
    part: "snippet",
    order: "date",
    q: q,
    regionCode: "kr",
    type: "video",
    maxResults: amount,
  };

  try {
    const response = await axios.get("/search", { params });
    console.log(response.data.items);
    return response.data.items;
  } catch (error) {
    console.log("error", error);
  }
};

1) 중복되지 않은 채널을 channel db에 저장

저장할 속성

"snippet.channelId"[string]: 채널 고유 id

"snippet.channelTitle"[string]: 채널명

"except"[boolean]: default: true / 예외등록할 채널은 false

=> 추후에 새롭게 등록된 channel 페이지에 활용

saveTime: db에 채널을 저장한 일자를 같이 저장

 

2) 중복되지 않은 영상을 video db에 저장

저장할 속성

"snippet.channelId"[string]: 채널 고유 id

"snippet.channelTitle"[string]: 채널명

"videoId"[string]: video 고유 id

"snippet.publishedAt"[string]: video 발행 일시

"snippet.title"[string]: video 제목

"snippet.description"[string]: video 내용

"snippet.thumbnails.[default, medium, high].url"[string]: video 썸네일 주소 

=> 추후에 최근 업로드 된 video 페이지에 활용. video를 나열, 격자형식으로 보여주기


"/channels" (1P * channel db.length)

"/channels.snippet"  [최초 1회 / 긴 주기로 업데이트ud] 

채널 정보 channel db에 업데이트

channelId 로 검색을 하여 part: snippet, contentDetails, statistics 에 대하여 정보를 업데이트한다.

업데이트할 속성

"snippet.description"[string]: 채널 설명 

"snippet.thumbnails.[default, medium, high].url"[string]: 채널 썸네일 주소

"/channels.contentDetails" [최초 1회 / 긴 주기로 업데이트ud] 

      "contentDetails": {
        "relatedPlaylists": {
          "likes": "",
          "uploads": "UUzmd0IzkyCG1zpHbneTrtYQ"
        }
      },

=> uploads 속성 이용하면 업로드한 모든 영상을 담은 재생목록 반환

playlist db에 저장

저장할 속성

"contentDetails.relatedPlaylists.uploads"[string]: 재생목록 고유 id

"/channels.statistics" [짧은 주기로 업데이트ud]

채널 통계 channel db에 업데이트

"statistics.viewCount"[integer]: 채널 조회수

"statistics.subscriberCount"[integer]: 구독자 수

"statistics.videoCount"[integer]: 업로드된 동영상 수


"/playlistItems" (1P * channel db.length) 

"/playlistItems.snippet" 

playlist db 에 업데이트

"snippet.publishedAt"[string]: video 발행 일시

"snippet.title"[string]: video 제목

"snippet.description"[string]: video 설명

"snippet.thumbnails.[default, medium, high].url"[string]: video 썸네일 주소 

"snippet.channelTitle"[string]: 채널명 ([0] 인덱스에만 실행)

"snippet.resourceId.videoId"[string]: video 고유 id 

"snippet.videoOwnerChannelId"[string]: channel 고유 id

"/playlistItems.status"

(만일 비공개 영상을 제외하고 싶다면 status part를 포함하여 검색해야할 수도 있음.

비공개 영상은 어떻게 나타나는지에 대해서 확인해보지 않아서 아직 모름)

playlist db 에 업데이트

"status.privacyStatus"[string]: ex) "public"

 


기능 설계

[ ] 최신순으로 50개 검색 (searchNewVideo)

(일일 업데이트 되는 동영상 수에 따라 "nextPageToken" 이용하여 다음 페이지 탐색)

ex) "nextPageToken"[string]: "CDIQAA"

[ ] "/search" 중복되지 않은 channelId를 channel db에 저장 (addNewChannelBySearchApi)

새롭게 등록된 channel은 등록된 시간 속성(saveTime)을 추가해 New Channel 표시 3일간 유지

1) channel db에서 channel id 배열 가져오기

2) channel id 배열과 비교하여 중복되지 않은 channel id를 channel db에 추가

snippet

  • "snippet.channelId"[string]: 채널 고유 id
  • "snippet.channelTitle"[string]: 채널명

else

  • "except"[boolean]: default: true / 예외등록할 채널은 false
  • "saveTime"[string]: db에 채널을 저장한 일자를 같이 저장

[ ] "/channels" channel id를 조회한 채널 정보 업데이트 (updateChannelDataByChannelsApi)

channel db에 등록되어 있는 channel id의 정보를 모두 업데이트

1) channel id로 채널 정보 조회

2) 채널 정보 업데이트

snippet

  • "snippet.description"[string]: 채널 설명
  • "snippet.thumbnails.[default, medium, high].url"[string]: 채널 썸네일 주소

contentDetails

  • "contentDetails.relatedPlaylists.uploads"[string]: 재생목록 고유 id

status

  • "statistics.viewCount"[integer]: 채널 조회수
  • "statistics.subscriberCount"[integer]: 구독자 수
  • "statistics.videoCount"[integer]: 업로드된 동영상 수

[ ] "/search" 중복되지 않은 videoId를 video db에 저장 (saveDailyVideoBySearchApi)

동영상 발행 일자를 id로(20230206), 일자별로 업로드된 video를 [배열]에 저장하여 매일마다 검색되는 동영상을 기록

1) 동영상을 발행 일자로 구분하여 video db에서 video id 배열 가져오기

2) video id 배열과 비교하여 search 결과와 중복되지 않은 video id를 video db에 추가

base

  • "videoId"[string]: video 고유 id

snippet

  • "snippet.channelId"[string]: 채널 고유 id
  • "snippet.channelTitle"[string]: 채널명
  • "snippet.publishedAt"[string]: video 발행 일시
  • "snippet.title"[string]: video 제목
  • "snippet.description"[string]: video 내용
  • "snippet.thumbnails.[default, medium, high].url"[string]: video 썸네일 주소 

[ ] "/playlistItems" 재생 목록으로 조회한 video 정보를 playlist db에 업데이트 하기 (updatePlaylistVideoByPlaylistItemsApi)

playlist의 데이터를 업데이트하여 channel db에 등록되어 있는 채널을 격자 형태로 나열해 보거나 검색할 수 있도록 함

1) playlist db에 저장된 playlist id를 [배열]에 저장

2) channel id를 이용해 playlist db에 등록된 video 데이터를 찾아 해당 채널의 video를 모아보기 

snippet

  • "snippet.publishedAt"[string]: video 발행 일시
  • "snippet.title"[string]: video 제목
  • "snippet.description"[string]: video 설명
  • "snippet.thumbnails.[default, medium, high].url"[string]: video 썸네일 주소 
  • "snippet.channelTitle"[string]: 채널명 ([0] 인덱스에만 실행)
  • "snippet.resourceId.videoId"[string]: video 고유 id 
  • "snippet.videoOwnerChannelId"[string]: channel 고유 id