<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>0_cyberlover_0</title>
        <link>https://velog.io/</link>
        <description>꿈꾸는 개발자</description>
        <lastBuildDate>Tue, 21 Nov 2023 09:20:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>0_cyberlover_0</title>
            <url>https://images.velog.io/images/0_cyberlover_0/profile/c4d87d40-fe6f-4662-96a3-bfa0a62546df/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 0_cyberlover_0. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/0_cyberlover_0" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[매일 글쓰기]]></title>
            <link>https://velog.io/@0_cyberlover_0/%EB%A7%A4%EC%9D%BC-%EA%B8%80%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@0_cyberlover_0/%EB%A7%A4%EC%9D%BC-%EA%B8%80%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Tue, 21 Nov 2023 09:20:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="어릴적-부터-글짓기를-하는걸-좋아했었다">어릴적 부터 글짓기를 하는걸 좋아했었다.</h1>
</blockquote>
<pre><code>어느 순간부터 안하게 되었다가... 요새 읽게 되는 책이 있다.

자청이라는 분의 &lt;역행자&gt;라는 책을 읽었다. 

역행자 한동안 엄청나게 히트를 친 베스트셀러인데 이제서야 봤다는건

내가 정말 자기계발에는 관심이 얼마나 없었는지 알게 되는 순간이기도 하다.</code></pre><blockquote>
<h1 id="일단-써보기로-하였다">일단 써보기로 하였다.</h1>
</blockquote>
<pre><code>작가님이 나랑 동년배이신 분인거 같은데 이미 많은걸 이루신것 같아 한편으론 부럽기도하고

너무 늦은 나이에 시작하는게 아닌가 하는 생각도 들었지만 

이런 엄청난 사실을 한 평생 모르고 살아간 사람들도 너무나도 많을 거라는 생각에 

대단한걸 얻은것 같아 감사하다는 생각도 든다.</code></pre><pre><code>일단 내 나이가 되면서 아무런 일도 없이 살았을수도 있지만 나는 일단 우여곡절도 많았고

작은 내 가게도 운영도 해보았고 10년 가까이 요식업도 해왔기에 자신감도 어느 정도 있었지만

그 당시 옆에 있던 사람의 부추김에 아무 준비 없이 시작한 작은 내 사업은 당연히 잘 될수가 

없었다.

가게가 안되고 그랬을때 남 핑계를 많이 했던것 같다. 아니 그랬다.

&lt;역행자&gt;를 읽고 나서 느낀 점은 정말이지 그 동안 내 무의식과 잠재되어 있던 생각들을

정리해주는 느낌이였다.

지금이라도 내 눈앞에 나타나준 이 책을 정말 감사 하며 조금씩 바꿔 나가는 내가 될수 있도록

노력해야 겠다.

이미 내가 어느 정도 실천해서 알게 된것들과 경험으로 얻은것들을 책으로 잘 설명을 해주셔서

책을 이해하는데 전혀 어려움도 없었을 뿐 만 아니라 작가님이 하시는 말씀이 전혀 헛소리가 

아니라는걸 내 경험들이 말해주고 있었다.
</code></pre><pre><code>내가 글 표현이 좋지를 못해서 정확하게 내가 생각 하고자 하는 것들을 잘 못쓰고 있다.

하지만 이것도 계속 써 나아가면 좀더 구체화적인 표현들을 할수 있을거라고 본다.

오늘 하루 어쩌면 평범하게 흘렀을 하루가 너무나도 오랜간만에 뜻깊은 마무리가 되어가는것 같아서 

가슴이 웅장해 진다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[User's Videos]]></title>
            <link>https://velog.io/@0_cyberlover_0/Users-Videos</link>
            <guid>https://velog.io/@0_cyberlover_0/Users-Videos</guid>
            <pubDate>Fri, 29 Jul 2022 07:45:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>전 파트에서 하던 것을 완성하도록 하겠다.</p>
</blockquote>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  if (!user) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;User not found.&quot; });
  }
  const videos = await Video.find({ owner: user._id });</code></pre>
<p>여기에서 하고 있는건 <code>owner</code>가 <code>user._id</code>인 모든 영상들을 찾는 거다.</p>
<p><code>url</code>에 있는 <code>user</code>의 <code>id</code>를 가지고 찾고 있다.</p>
<p>사실 이 코드만으로도 충분하다. </p>
<p>하지만 여기에 나온 방식을 사용해서 더 멋있게 코드를 만들어 보도록 한다.</p>
<p>어떤 유저가 업로드한 모든 영상들을 찾기 위해 <code>populate</code>를 쓸 수 있다.</p>
<pre><code class="language-js">  const videos = await Video.find({ owner: user._id });
</code></pre>
<p>현재 이 코드를 삭제를 한다. </p>
<pre><code class="language-js">return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
      videos,
  });</code></pre>
<p><code>videos</code>도 지워 준다. </p>
<pre><code class="language-js">return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
  });</code></pre>
<p>그러면 <code>profile.pug</code>의 <code>videos</code>가 작동하지 않는다.</p>
<pre><code class="language-js">block content 
    each video in videos 
        +video(video)
    else    
        li Sorry nothing found.</code></pre>
<p>이 부분이다.</p>
<p>그리고 <code>DB</code>를 초기화 했었기 때문에 로그인 상태가 아니다. 그러면 <code>user</code>없이 프로필 창에 들어 갈 수 없게 된다. </p>
<p>그래서 <code>videos</code>를 <code>profile</code>로 보내지 않았기 때문에 작동하지 않게 된다. </p>
<p>그래도 한 번 더 좋게 고쳐 보도록 한다. </p>
<p><code>models</code>폴더를 보면 <code>video</code>는 1개의 <code>owner</code>를 가지고 있다. </p>
<p>이전에도 살펴 봤듯이 <code>video</code>는 하나의 <code>owner</code>를 가지고 <code>owner</code>는 여러 <code>videos</code>를 가질 수 있다. </p>
<p><code>video</code>는 하나의 <code>user</code>를 가지지만 <code>user</code>는 여러 <code>videos</code>를 가질 수 있다. </p>
<p>이 논리를 기반으로 <code>user model</code>안에 <code>videos</code>라는 <code>array</code>를 만들어 준다. </p>
<pre><code class="language-js">const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  avatarUrl: String,
  socialOnly: { type: Boolean, default: false },
  username: { type: String, required: true, unique: true },
  password: { type: String },
  name: { type: String, required: true },
  location: String,
});</code></pre>
<p>여기에 추가 해 준다. </p>
<pre><code class="language-js">const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  avatarUrl: String,
  socialOnly: { type: Boolean, default: false },
  username: { type: String, required: true, unique: true },
  password: { type: String },
  name: { type: String, required: true },
  location: String,
  videos: [{ type: mongoose.Schema.Types.ObjectId, ref: &quot;Video&quot; }],
});</code></pre>
<p><code>object</code>가 아닌 <code>array</code>로 만들어 준다. 여러 개의 <code>videos</code>를 가지기 때문이다. </p>
<p>1개의 영상은 소유주가 1명이지만, 소유주는 여러 영상을 소유 할 수 있다.</p>
<p>그래서 <code>array</code>로 만들었다. 그리고 이 <code>array</code>에 무엇으로 채워 주냐면 <code>object</code> 이다.</p>
<p>그렇게 해서 <code>objectId</code>를 사용해 주었다. </p>
<pre><code class="language-js">  owner: { type: mongoose.Schema.Types.ObjectId, required: true, ref: &quot;User&quot; },</code></pre>
<p>이 부분이다. </p>
<p>해당 코드를 그대로 사용하게 된다. 수정 할 부분을 수정해 준다.</p>
<p>이제 <code>ObjectId</code>를 가지는 <code>array</code>가 되는 거다. </p>
<p><code>required</code>는 필요가 없다. <code>ref</code>는 <code>User</code>가 아니라 <code>Video</code>이다.</p>
<p><code>videos</code>는 <code>Video model</code>에 연결된 <code>ObjectId</code>로 구성된 <code>array</code>이다.</p>
<p>이건 <code>array</code>이니까 많은 <code>video</code>를 담을 수 있다. </p>
<blockquote>
<p>이제 <code>videoController</code>를 수정해 줄 차례이다.</p>
</blockquote>
<p>그 이유는 영상을 업로드 할때 추가적으로 해야 할 것이 있기 때문이다. </p>
<pre><code class="language-js"> const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      fileUrl,
      owner: _id,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<p>여기서 <code>Video owner</code>에 사용자 <code>_id</code>를 저장하고 있다. </p>
<p>하지만 이제는 업로드 될 영상의 <code>id</code>를 <code>user model</code>에도 저장해 줘야 한다. </p>
<p><code>User</code>에 <code>videos</code>라는 <code>array</code>가 있다는걸 기억하고 있어야 한다.</p>
<p>그래서 새로 업로드 하는 영상의 <code>id</code>를 <code>user model</code>에 저장해줄 거다.</p>
<p>현재 유저의 <code>id</code>를 <code>Video</code>의 <code>owner</code>에 추가 하고 있다. </p>
<p>이제 다른 방식으로 해보도록 한다. </p>
<p>다행히 <code>create()</code>가 새로 만드는 <code>video</code>를 <code>return</code> 해준다. </p>
<pre><code class="language-js"> await Video.create({
      title,
      description,
      fileUrl,
      owner: _id,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<pre><code class="language-js">   const newVideo = await Video.create({</code></pre>
<p>이렇게 해준다. 이말은 현재 가지고 있는 <code>id</code>로 사용자를 검색 할수 있고 </p>
<p><code>newVideo</code>의 <code>id</code>를 <code>User</code>의 <code>videos array</code>에 추가해 준다. </p>
<p>그리고 <code>user</code>를 찾아 보도록 한다. </p>
<pre><code class="language-js">const newVideo = await Video.create({
      title,
      description,
      fileUrl,
      owner: _id,
      hashtags: Video.formatHashtags(hashtags),
    });
    const user = await User.findById(_id);
    user.videos.push();</code></pre>
<p>그리고 <code>user.videos</code>를 쓸거다. <code>user</code>에 <code>videos array</code>가 있다는 걸 알기에 그렇다.</p>
<p><code>array</code>에 요소를 추가 할때는 <code>push</code>를 사용하면 된다. </p>
<p>해당 작업 페이지로 가서 <code>inspect &gt; console</code>에서 에시를 보도록 한다. </p>
<pre><code>const hello = []
undefined
hello.push(1)
1
hello
[1]</code></pre><p>위와 같은 원리로 <code>user.videos.push(비디오 id)</code>를 써보도록 한다. </p>
<pre><code class="language-js">    user.videos.push(newVideo._id);</code></pre>
<p>이렇게 <code>user.videos.push()</code>를 쓰고 <code>newVideo</code>의 <code>id</code>를 써준다. </p>
<p>그리고 <code>user.save()</code>를 써준다. </p>
<pre><code class="language-js">user.videos.push(newVideo._id);
user.save();</code></pre>
<p>그리고 다시 로그인을 해본다. 영상도 모두 삭제 되었으니 새로 올려 준다. </p>
<p><code>DB</code>에서 모든 사용자를 검색해 보도록 한다. </p>
<pre><code>db.users.find()
db.videos.find()</code></pre><p><code>user</code>에 <code>videos array</code>가 추가 되어 있다. 그리고 모든 영상들도 검색해 본다.</p>
<p>전 단계에서 본 것 처럼 <code>user</code>에는 <code>videos array</code>가 있고,  </p>
<p><code>video</code>에는 <code>owner id</code>가 있다. 이렇게 해서 둘이 연결이 되었다. </p>
<p>이제 업로드 된 영상 소유자의 프로필 창을 들어가 본다. </p>
<p><code>videos</code>가 정의되지 않아서 열리지 않고 있다고 한다. </p>
<blockquote>
<p><code>userController</code>를 수정해서 해결해 보도록 한다. </p>
</blockquote>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);</code></pre>
<p>먼저 <code>see controller</code>에 <code>console log(user)</code>를 해서 </p>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  console.log(user);</code></pre>
<p>그 결과 값을 확인 해보도록 한다. 에러가 나오지만 중요한건 <code>user</code>이다. </p>
<p>보다시피 이 안에는 <code>videos array</code>가 존재 한다. </p>
<p>이제 이 <code>videos array</code>로 <code>videos</code>정보를 다 가져올 수 있게 된다. </p>
<pre><code class="language-js">  const user = await User.findById(id);</code></pre>
<pre><code class="language-js">  const user = await User.findById(id).populate(&quot;videos&quot;);</code></pre>
<p><code>populate()</code>를 써준다. 그리고 <code>videos</code>를 <code>populate</code>해본다. </p>
<p>이제 <code>Mongoose</code>가 <code>id</code>를 가져다가 모든 영상들을 프로필 창에 보여줄거다.</p>
<p>새로고침을 해도 계속 에러가 나와있지만 <code>console</code>을 확인해 보면 <code>videos</code>정보가 로드 된걸 볼수 있다.</p>
<p>다시 한번 확인해 보면 <code>videos</code>라는 <code>array</code>를 만들었고 그 안에 <code>ObjectId</code>를 저장하고 있다. </p>
<p>그런데 <code>videos</code>를 가져왔다. 이전처럼 <code>id</code>만 가진 <code>array</code>가 아니고 </p>
<p><code>Mongoose</code>의 도움으로 <code>video object</code>로 구성된 <code>array</code>가 되었다.</p>
<blockquote>
<p>이제 <code>profile.pug</code>만 수정해주면 된다.</p>
</blockquote>
<pre><code class="language-js">block content 
    each video in videos 
        +video(video)
    else    
        li Sorry nothing found.</code></pre>
<p>이제 <code>each video in videos</code> 대신에 <code>each video in user.videos</code>를 써준다. </p>
<pre><code class="language-js">block content 
    each video in user.videos 
        +video(video)
    else    
        li Sorry nothing found.</code></pre>
<p><code>user</code>를 <code>variable</code>로 <code>users/profile</code>로 보내고 있기 때문이다. </p>
<p>그리고 <code>DB</code>에서 찾은 <code>user</code>는 <code>videos</code>를 가지고 있으니까 잘 작동하게 된다.</p>
<p>이 모든게 <code>populate</code> 한 줄로 정리가 되었다. </p>
<p>물론 <code>populate</code>를 하지 않으면 이전처럼 될거다.</p>
<p>이전에는 <code>video id</code>만 있었다. 그런데 <code>object</code>자체가 필요 하다.</p>
<p>그래서 그렇게 만들었다. 이렇게 <code>populate</code>를 쓰면 된다. </p>
<blockquote>
<p>지금까지 <code>relataionship</code>에 대해서 배웠다. </p>
</blockquote>
<p>이제 여러 개의 <code>video</code>와 어떻게 <code>relationship</code>을 구축하면 되는지 알게 되었다. </p>
<p><code>user</code>는 여러 개의 <code>video</code>를 업로드 할수 있다는걸 반드시 기억해야 한다.</p>
<p>그리고 <code>video</code>는 <code>owner</code>가 하나 뿐이다. 이렇게 <code>relationship</code>을 만들어 봤다. </p>
<p><code>video</code>를 하나 더 만들어 보도록 한다. </p>
<p>그렇게 하면 프로필 창에 들어가면 두 영상을 모두 볼 수 있다. </p>
<p>바로 현재 유저가 영상의 소유자 이기 때문이다. </p>
<p>다음 파트에서는 여기에 몇 가지 <code>condition</code>들을 추가 해 보겠다. </p>
<p>Edit Video →
Delete Video →</p>
<p>왜냐하면 해당 버튼들을 안보이게만 만들었기 때문이다. </p>
<p><code>owner</code>가 아닌 사람들이 수정할 수 있는 페이지를 볼 수 없게 만들어 본다.</p>
<p>현재는 누구나 수정 할수 있는 상태 이다. 이제 영상에는 <code>owner</code>가 있으니</p>
<p><code>owner</code>만 수정 사능하도록 만들어 볼거다. </p>
<p>그리고 <code>getEdit</code>도 수정해서 영상 소유주만 <code>form</code>을 보고 제출 할수 있게 만들거다. </p>
<p>이런 것들을 보완해야 하고 <code>video</code>를 삭제하는 것도 <code>owner</code>만 가능하도록 만들거다.</p>
<pre><code class="language-js">userSchema.pre(&quot;save&quot;, async function () {
  this.password = await bcrypt.hash(this.password, 5);
});</code></pre>
<p>현재 이 부분이 버그가 있는데 이 버그는 <code>user</code>를 <code>save</code>를 할 때 마다 </p>
<p>비밀번호를 반복적으로 <code>hashing</code>하고 있다. 좋지 않은 방법이다.</p>
<p>왜냐하면 <code>videoController</code>에서 영상을 업로드 할때 <code>user.save()</code>를 실행 하는데 </p>
<p>그렇게 되면 비밀번호가 다시 한번 <code>hash</code>가 된다. </p>
<p>사용자가 다시 로그인 할수 없기 때문에 이 부분을 수정 해줘야 한다. </p>
<p>특정 조건에서만 비밀번호가 <code>hash</code> 되도록 만들어 줄거다.</p>
<p>항상 되는 것이 아니라 현재는 <code>save()</code>가 실행 될 때마다 <code>hash</code>가 일어나고 있다. </p>
<p>그로 인해 이 사용자는 비밀번호를 사용 할수 없게 된다. 더 이상 로그인을 할수 없는 거다. </p>
<p>다음 파트에서는 버그를 수정하고 보안을 신경 써보도록 한다.</p>
<p>보안은 영상 <code>owner</code>가 아닌 사람들이 영상을 수정/삭제 하지 못하게 만드는 것이고</p>
<p>위에 언급했던 버그도 수정하도록 하겠다. </p>
<p>그리고 다시 <code>DB</code>를 초기화 한 다음에 버그가 존재 한다는 것을 살펴 보도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Video Owner #02]]></title>
            <link>https://velog.io/@0_cyberlover_0/Video-Owner-02</link>
            <guid>https://velog.io/@0_cyberlover_0/Video-Owner-02</guid>
            <pubDate>Wed, 27 Jul 2022 04:47:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>전 단계에서 영상에 영상 소유자의 <code>id</code>를 저장해봤다.</p>
</blockquote>
<p><code>DB</code>를 보면 이 <code>video</code>를 업로드한 유저의 <code>id</code>를 볼수 있다. </p>
<p>추가적으로 <code>watch</code>화면에서 영상 소유자의 이름을 보여주도록 만들었다.</p>
<p>여기까지의 코드를 보고 여기에 쓴 <code>reference</code>가 무슨 쓸모가 있는지 궁금해 진다.</p>
<p>왜냐하면 <code>video.owner</code>가 <code>User id</code> 라는걸 아는데 이걸 직접 해주고 있으니 그렇다.</p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);
  const owner = await User.findById(video.owner);</code></pre>
<pre><code class="language-js">  const owner = await User.findById(video.owner);</code></pre>
<p>그래서 이 코드를 지워 준다. </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);</code></pre>
<p>이렇게 하면 <code>owner</code>가 없으니까 에러가 난다. 그러니 지워 준다. </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);
  if (!video) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;Video not found.&quot; });
  }
  return res.render(&quot;watch&quot;, { pageTitle: video.title, video, owner });
};</code></pre>
<pre><code class="language-js">  return res.render(&quot;watch&quot;, { pageTitle: video.title, video});</code></pre>
<p>그래도 에러가 나오니 좀 이상해 질거다. </p>
<p>하지만 이 작업의 핵심은 같은 기능을 더 짧은 코드로 구현해 보는거다.</p>
<p>여기서 <code>video</code>를 <code>console.log</code>해주면 <code>DB</code>에서 찾은 <code>video</code>를 확인할수 있을거다.</p>
<p>이 상태에서 브라우저를 새로고침하면 오류가 생길거다. </p>
<p>걱정하지 않아도 된다. 핵심은 <code>node console</code>에 출력된 <code>video</code>이다.</p>
<p>이제 <code>mongoose</code>의 도움을 받아 볼거다. </p>
<pre><code> meta: { views: 0, rating: 0 },
  _id: new ObjectId(&quot;6278c6ea843aa2ab1951cb7a&quot;),
  title: &#39;code&#39;,
  fileUrl: &#39;uploads/videos/8354fc579bb23fa577b406bccaafe453&#39;,
  description: &#39;codecodecodecodecode&#39;,
  hashtags: [ &#39;#code&#39; ],
  owner: new ObjectId(&quot;6278ad69eeed5a64dd710d36&quot;),
  createdAt: 2022-05-09T07:46:50.207Z,
  __v: 0
}</code></pre><p><code>mongoose</code>는 이 <code>objectID</code>가 <code>User</code>에서 오는걸 아니까 도와 줄거다. </p>
<p>그래서 <code>mongoose</code>가 찾게 만들어 볼거다. </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id).populate();</code></pre>
<p><code>populate</code>가 뭘 하냐면 </p>
<pre><code> owner: new ObjectId(&quot;6278ad69eeed5a64dd710d36&quot;),</code></pre><p> 이 부분을 실제 <code>User</code> 데이터로 채워 준다. </p>
<p> <code>owner</code>를 <code>populate</code>해준다. </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id).populate(&quot;owner&quot;);</code></pre>
<p><code>owner</code>만 적어주면 끝이다. </p>
<p><code>populate</code>와 <code>relationship</code>만 적어주면 된다. </p>
<p>그리고 이제 <code>populate</code>한 <code>video</code>를 <code>console.log</code> 해본다. </p>
<p>오류는 걱정하지 말고 일단 새로고침을 해본다. 에러가 있지만 코드를 한번 보도록 한다. </p>
<p>차이가 보일 것이다. </p>
<p><code>video</code>안에 <code>owner object</code>전체 정보가 들어와 있다.</p>
<p><code>populate</code>하기전의 <code>owner</code>는 단순히 <code>string</code>이지만 </p>
<p><code>populate</code>를 하면 <code>mongoose</code>가 <code>video</code>를 찾고 그 안에서 <code>owner</code>도 찾아 줄거다.</p>
<p><code>mongoose</code>는 <code>owner</code>가 <code>object ID</code>인 것을 알고 이 <code>id</code>가 <code>User</code>에서 온 것임을 안다. </p>
<p>그래서 <code>mongoose</code>가 해당 <code>User</code>를 찾고 <code>video</code>를 로드했을때 <code>User</code> 정보도 얻을수 있다. </p>
<p><code>id</code>뿐만 아니라 모든 정보를 볼수 있다는 건 정말 대단한 일이다. </p>
<p>이제 <code>template</code>만 수정해서 작동하게 만들어 본다. </p>
<p>이전 코드에서는 이 두가지를 <code>string</code>으로 변환하고 비교 해야했다. </p>
<p>왜냐하면 이전에는 <code>owner</code>가 <code>id</code>에 불과 했다. 하지만 이제는 <code>object</code>이니깐 수정이 필요하다. </p>
<blockquote>
<p><code>watch.pug</code>에서 </p>
</blockquote>
<pre><code class="language-js">   if String(video.owner) === String(loggedInUser._id)   </code></pre>
<pre><code class="language-js">   if String(video.owner._id) === String(loggedInUser._id)   </code></pre>
<p>이 작업만 하면 <code>mongoose</code>가 나머지는 다 해결해주는 거다. </p>
<p>이제 이 부분의 오류도 해결할수 있다. </p>
<p>기존 코드 대신 <code>video.owner.name</code>으로 바꿔 볼거다. </p>
<pre><code class="language-js">      small Uploaded by #{owner.name}   </code></pre>
<pre><code class="language-js">      small Uploaded by #{video.owner.name}   </code></pre>
<p>이런 식으로 바꿔 주고 홈페이지 새로고침을 해주면 작동 되는걸 확인할수 있다.</p>
<p>보다시피 영상 소유자가 보이게 된다. 이 기능은 코드 한줄로 구현 된거다. </p>
<p>이게 바로 <code>mongoose relationship</code>의 힘이다. </p>
<p>현재 <code>id</code>만 저장하고 있다. <code>User</code> 전체가 아니라 <code>id</code>만 저장한다. </p>
<p><code>DB</code>에서 <code>populate</code>를 써도 작동이 되는지 해보니 작동하지 않는다.</p>
<blockquote>
<p><code>populate</code>는 <code>function</code>이 아니다. </p>
</blockquote>
<p>하지만 코드에서 <code>populate(&quot;owner&quot;)</code>를 사용하면 <code>owner object</code>전체가 불려지는걸 볼수 있다. </p>
<p><code>id</code>를 저장하고 <code>mongoose</code>에 이 <code>id</code>가 <code>User model</code>에서 왔다고 알려주기만 하면 된다. </p>
<p>그래서 이런식으로 이름을 통일하고 사전에 모든 <code>model</code>들을 <code>import</code>하는게 좋은거다.</p>
<p>그래야 <code>mongoose</code>가 도와 줄수 있다. </p>
<p>이제 여기에 링크를 걸어본다. 소유자 프로필에 갈수 있게 만들어 본다. </p>
<p>이건 다른 <code>populate, relation</code>을 할수 있게 도와 줄거다. </p>
<blockquote>
<p><code>video</code>에 <code>owner</code>를 추가하는건 끝났다. </p>
</blockquote>
<p>다음 단계는 특정 사용자가 업로드한 모든 영상을 볼 수 있게 만드는 거다. </p>
<p>그러기 위해선 <code>User.js</code> 코드를 수정해야 한다.</p>
<blockquote>
<p>이제 <code>watch template</code>을 수정해 본다. </p>
</blockquote>
<pre><code class="language-js">      small Uploaded by #{video.owner.name}   </code></pre>
<pre><code class="language-js">small Uploaded by 
      a(href=`/users/${video.owner._id}`)=video.owner.name</code></pre>
<p>새로고침하고 링크를 눌러주면 프로필 화면으로 넘어간다. </p>
<p>이제 사용자가 업로드한 영상들을 가져와야 한다. </p>
<p>여러가지 방법이 있는데 이해를 돕기 위해 좀더 긴 코드를 먼저 보도록 한다. </p>
<p>그 다음 짧은 코드를 보도록 한다. </p>
<blockquote>
<p><code>userController.js</code>를 띄워 준다. </p>
</blockquote>
<p><code>userController</code>에서 <code>user</code>를 찾고 있다. </p>
<p>그리고 <code>id</code>를 <code>owner</code>로 가진 <code>video</code>들을 찾을 수도 있다.</p>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  if (!user) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;User not found.&quot; });
  }</code></pre>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  if (!user) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;User not found.&quot; });
  }
  const videos = await Video.find({ owner: user._id });</code></pre>
<p><code>Video</code>을 쓰고 여러개의 <code>video</code>를 찾아 올거다. </p>
<p>그리고 <code>findOne</code> 이런거 말고 그냥 <code>find</code>를 써서 <code>video</code>의 <code>owner</code>가 <code>params</code>의 <code>id</code>와 같은 <code>video</code>들을 찾는거다. </p>
<p><code>video</code>의 <code>owner id</code>rk <code>URL</code>에 있는 <code>id</code>와 같은 <code>video</code>를 찾는거다. </p>
<p>추가해 준다. 그리고 <code>import</code>가 안되는 경우도 있으니 꼭 확인한다.</p>
<p>안되 있으면 </p>
<pre><code class="language-js">import Video from &quot;../models/Video&quot;;</code></pre>
<p>추가 해준다. </p>
<p>그리고 <code>video</code>를 <code>console.log</code>해주고 새로고침을 해보면 <code>array</code>가 나온다. </p>
<p>이 <code>URL</code>의 <code>id</code>와 <code>video owner id</code>가 일치한 경우만 나온거다. </p>
<p>현재 지금 <code>user</code>와 <code>owner</code>의 <code>id</code>가 같은 <code>video</code>들을 찾고 있다. </p>
<p>이것만 해도 충분하다. <code>template</code>으로 <code>videos</code>를 보낼수 있다.</p>
<pre><code class="language-js">const videos = await Video.find({ owner: user._id });
  return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
    videos,
  });</code></pre>
<blockquote>
<p><code>users/profile.pug</code>에서 </p>
</blockquote>
<pre><code class="language-js">block content 
    h1 hello</code></pre>
<p>이 말은 <code>users/profile</code>에서 <code>hello</code> 대신에 <code>videos</code>를 보여 줄수 있다는 거다. </p>
<p><code>videos</code>를 보여주는 방법은 <code>home</code>에서 하는 방법 이랑 똑같다. </p>
<pre><code class="language-js">
block content 
    each video in videos 
        +video(video)</code></pre>
<p>이렇게 바꿔 줄수 있다.</p>
<p><code>videos array</code>를 보내고 <code>each video in videos</code>를 썼을 뿐이다. </p>
<p><code>home</code>하는 방식과 똑같고 <code>mixin</code>도 사용했다. 코드를 복붙해도 코드가 작동하는데 문제가 없다. </p>
<p>물론 <code>minxin</code>을 <code>include</code>해줘야 한다. </p>
<pre><code class="language-js">include ../mixins/video
</code></pre>
<p>왜냐하면 지금 <code>/users/profile.pug</code>안에 있기 때문이다. </p>
<p>새로고침해서 확인해 보면 유저가 업로드한 영상이 나오고 있다. </p>
<p><code>home</code>에서도 다룬거라 새로운 내용은 아니다. </p>
<p>차이점은 <code>profile</code>의 <code>videos</code>가 특정 유저의 <code>id</code>와 <code>owner id</code>가 같은 <code>video</code>만 가져 온다는 거다. </p>
<p>그리고 <code>user.id</code>는 <code>URL</code>에 있는 <code>id</code>로 찾은 거다. </p>
<p>하지만 이 방법은 긴 코드로 작성 되었다. 짧게 만들 수도 있다. </p>
<p>하지만 이 지름길을 사용하려면 또 다시 <code>DB</code>를 초기화 시켜야 한다. </p>
<p>초기화 해야하는 이유는 <code>model</code>을 수정해야 하기 때문이다. </p>
<p>이제 아무것도 안되고 영상들도 다 사라졌다. </p>
<p><code>database</code>를 초기화 하고 다음 파트로 넘어가도록 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Video Owner #01]]></title>
            <link>https://velog.io/@0_cyberlover_0/Video-Owner-01</link>
            <guid>https://velog.io/@0_cyberlover_0/Video-Owner-01</guid>
            <pubDate>Mon, 09 May 2022 08:41:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Video를 user와 연결하는 작업을 해본다.</p>
</blockquote>
<p>현재는 <code>video</code>와 <code>user</code>가 서로 연결되어 있지 않다.</p>
<p><code>mongoose</code>나 <code>mongodb</code>를 활용해 연결하려면 <code>id</code>를 사용해야 한다.</p>
<p>왜냐하면 <code>id</code>는 하나밖에 없고 랜덤 숫자이기 때문이다. </p>
<p>그러면 현재 가지고 있는 <code>id</code>를 가지고 연결해 보도록 하겠다.</p>
<p><code>user</code>에는 해당 <code>user</code>가 업로드한 모든 영상의 <code>id</code>를 저장 해준다.</p>
<p>그리고 <code>video</code>에는 해당 영상을 올린 <code>user</code>의 <code>id</code>를 저장한다.</p>
<p>현재는 이해하지 못해도 코드를 보면 알게 될거다.</p>
<p>먼저 <code>videoSchema</code>에 <code>owner</code>를 추가해준다.</p>
<p><code>video.js</code>에서</p>
<pre><code class="language-js">const videoSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true, maxLength: 80 },
  fileUrl: { type: String, reqired: true },
  description: { type: String, required: true, trim: true, maxLength: 20 },
  createdAt: { type: Date, required: true, default: Date.now },
  hashtags: [{ type: String, trim: true }],
  meta: {
    views: { type: Number, default: 0, required: true },
    rating: { type: Number, default: 0, required: true },
  },
  ower: { type: mongoose.Schema.Types.ObjectId, reqired: true, ref: &quot;User&quot; },
});</code></pre>
<p><code>owner</code>의 <code>type</code>은 <code>number</code>,<code>string</code>,<code>date</code>가 아니고 <code>objectId</code>이다.</p>
<p>그러나 <code>objectId</code>문자 그대로 쓰면 코드가 노란색이 아니다.</p>
<p>위에 코드들을 보면 노랑색으로 활성화된 코드들은 <code>JavaScript</code>에서 제공하는 코드이다.</p>
<p>그런데 <code>object</code>는 <code>mongoose</code>코드에서만 사용 할수 있다.</p>
<p>그래서 이런식으로 작성할수가 있다. </p>
<p><code>ower: { type: mongoose.Schema.Types.ObjectId, reqired: true, ref: &quot;User&quot; },</code>
그리고 <code>required: true</code>를 추가해준다. <code>reference</code>도 추가해줄 필요가 있는데</p>
<p>그 이유는 <code>mongoose</code>에게 <code>owner</code>에 <code>id</code>를 저장하겠다고 알려줘야 하기 때문이다. </p>
<p>그런데 아직 어떤 <code>model</code>과 연결할지 알려주지도 않은 상태이다.</p>
<p>수많은 <code>model</code>이 있으면 어떤 것과 연결할 것인지 알려줘야 한다.</p>
<p>그래서 <code>mongoose</code>에게 이 <code>owner</code>가 어떤 <code>model</code>의 <code>object</code>라고 알려준다.</p>
<p>여기서는 <code>User model</code>이 된다. 그래서 <code>&quot;User&quot;</code>이라고 넣어 주었다.</p>
<blockquote>
<p>이제 video에 owner 항목이 추가 되었다. </p>
</blockquote>
<p><code>owner</code>은 <code>object ID</code>이다. <code>object ID</code>는 여기 보이는 긴 <code>string</code>이다.</p>
<p><code>ObjectId(&quot;626a5e7f2a8175076f7a1e45&quot;</code></p>
<p>이게 바로 <code>object ID</code>이다. </p>
<p>여기에서 중요한건 <code>Mongoose</code>에게 이 <code>object ID</code>가 <code>model user</code>에서 온다고 알려주는거다. </p>
<p>이렇게 해야 <code>Mongoose</code>가 도와 줄수 있다. </p>
<p>이제 <code>github</code>로그인을 통해 계정을 생성해 준다. </p>
<p>그리고 영상을 하나 업로드 해주기 전에 <code>controller</code>에 수정해줄 부분이 있다.</p>
<p>이제 영상을 업로드 할때 업로드 하는 사용자의 <code>id</code>를 전송해야 하기 때문이다.</p>
<p><code>videoController.js</code>에서</p>
<p><code>postUpload controller</code>에 <code>user</code>관련 코드를 추가 한다. </p>
<pre><code class="language-js">export const postUpload = async (req, res) =&gt; {
  const {
    user: { _id },
  } = req.session;
  const { path: fileUrl } = req.file;
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      fileUrl,
      owner: _id,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<p><code>user</code>는 <code>req.session</code>에서 가져오면 된다. 그리고 <code>user</code>에서 <code>_id</code>를 가져온다.</p>
<p>그리고 <code>Video</code>를 생성할때 <code>owner</code>도 보내도록 만들어 준다.</p>
<p><code>Video.create()</code>는 자주 다뤄 봐서 익숙하다. <code>mongoose</code>의 <code>create</code>과 <code>save</code> 같은거 말이다. </p>
<p><code>video model</code>에 <code>object ID type</code>을 가진 <code>owner property</code>를 추가 했다.</p>
<p>그래서 <code>owner</code>의 <code>_id</code>만 써주면 된다. </p>
<p><code>user object</code>전체를 전송할 필요는 없고 <code>id</code>만 전송해주면 되는 거다. </p>
<p>이제 다시 영상을 선택해주고 업로드까지 해준다. 영상 재생까지 잘 되는걸 확인 할수 있다.</p>
<p><code>database</code>도 확인해 본다. <code>db.videos.find({})</code>를 해보면 이제 <code>video</code>에 <code>owner</code>가 추가 된걸 알수 있다. </p>
<p>이 <code>owner</code>는 <code>ObjectId</code>이다. </p>
<p>성공적으로 <code>model</code>들을 <code>user</code>와 연결 시켰다. 현재는 그렇게 대단하게 보이지 않지만</p>
<p><code>populate</code>를 배우게 되는 순간부터는 달라 질거다. </p>
<blockquote>
<p><code>populate</code>가 왜 중요한지에 대해 알아 보겠다. </p>
</blockquote>
<p>그 이유는 <code>populate</code>가 있어야 영상 주인만 아래 버튼이 보이기 때문이다. </p>
<p>(<code>Edit Video</code>와 <code>Delete Video</code> 버튼을 말한다. )</p>
<p>그리고 여기에 누가 올렸는지 보여줄 수도 있다. </p>
<p>일단 버튼을 숨기는 기능을 구현해 보도록 한다. </p>
<p><code>video</code>에 <code>owner</code>의 <code>id</code>가 있다. 그리고 <code>localMiddleware</code>에 현재 로그인된 사용자가 누구인지 알려주는 부분이 있다.</p>
<p>이 말은 로그인된 사람의 <code>id</code>와 영상의 <code>owner</code>의 <code>id</code>가 일치 하면 로그인된 사용자가 현재 영상의 주인이라는 말이다. </p>
<p><code>template</code>을 수정해 본다. <code>watch.pug</code>를 열고 </p>
<pre><code class="language-js">extends base

block content
   video(src=&quot;/&quot; + video.fileUrl,controls)
   div
      p=video.description
      small=video.createdAt 
      small=video.owner
      br
      small=loggedInUser._id 
   a(href=`${video.id}/edit`) Edit Video &amp;rarr;
   br
   a(href=`${video.id}/delete`) Delete Video &amp;rarr;</code></pre>
<p><code>id</code>가 일치하는지 체크해준다. 붙어 있으면 일치하는지 헷갈릴수 있으니 띄워 준다.</p>
<p>이렇게 해주면 영상 주인과 현재 로그인된 사용자의 <code>id</code>가 일치하는걸 알수 있다. </p>
<p>이런 상황일때 아래 두 버튼이 보이도록 만들어 본다.</p>
<pre><code class="language-js">extends base

block content
   video(src=&quot;/&quot; + video.fileUrl,controls)
   div
      p=video.description
      small=video.createdAt 
   if String(video.owner) === String(loggedInUser._id)   
      a(href=`${video.id}/edit`) Edit Video &amp;rarr;
      br
      a(href=`${video.id}/delete`) Delete Video &amp;rarr;</code></pre>
<p><code>if video.owner === loggedInUser._id</code> 이렇게만 해주었을때는 작동을 하지 않는다.</p>
<p>왜냐하면 <code>video.owner</code>의 <code>id</code>는 <code>ObjectId</code>인데 현재 접속자의 <code>id</code>는 <code>string</code>형태의 데이터 이기 때문이다. </p>
<p>그래서 양쪽에 <code>String()</code>을 넣어주었다. 그래서 새로고침 해서 확인해 보면 잘 작동한다.</p>
<p>이제 <code>video</code>의 <code>owner</code>를 확인 할수 있게 되었다. </p>
<p>이렇게 <code>user</code>의 <code>id</code>를 <code>video model</code>에 저장하니까 유용하다.</p>
<p>누가 영상을 업로드 하였는지 확인 할수가 있게 되었다. </p>
<blockquote>
<p>video.owner는 string이고 id라는걸 기억하자.</p>
</blockquote>
<p>그러나 아직 해결해야 될게 있다. 영상 소유자의 이름을 가져와야 한다.</p>
<p>누가 비디오를 만들었는지 보여주고 그 사람의 프로칠을 볼수 있는 링크를 걸어 줄 수도 있다. </p>
<p>현재 페이지는 <code>watch</code>페이지이다. <code>watch controller</code>를 찾아 본다.</p>
<p><code>videoController.js</code>에서 </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);
  console.log(video);
  if (!video) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;Video not found.&quot; });
  }
  return res.render(&quot;watch&quot;, { pageTitle: video.title, video });
};</code></pre>
<p>이곳에서 <code>video</code>를 찾고 있다. <code>video</code>를 <code>console.log</code>해본다.</p>
<p>새로고침해서 <code>node</code>에서 찾아 보면 </p>
<pre><code>{
  meta: { views: 0, rating: 0 },
  _id: new ObjectId(&quot;6278c6ea843aa2ab1951cb7a&quot;),
  title: &#39;code&#39;,
  fileUrl: &#39;uploads/videos/8354fc579bb23fa577b406bccaafe453&#39;,
  description: &#39;codecodecodecodecode&#39;,
  hashtags: [ &#39;#code&#39; ],
  owner: new ObjectId(&quot;6278ad69eeed5a64dd710d36&quot;),
  createdAt: 2022-05-09T07:46:50.207Z,
  __v: 0
}</code></pre><p>이런 정보가 나온다. <code>owner</code>도 보인다. 이 말은 <code>owner</code>의 <code>id</code>를 알고 있다는거다.</p>
<p>이렇게 적용해 볼수 있다. </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);
  const owner = await User.findById(video.owner);
  if (!video) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;Video not found.&quot; });
  }
  return res.render(&quot;watch&quot;, { pageTitle: video.title, video, owner });
};</code></pre>
<p><code>const owner =</code> 그리고 <code>user</code>를 <code>models</code>에서 <code>import</code>한다.</p>
<p><code>models</code>에서 <code>User</code>를 <code>import</code>를 해준다. </p>
<p>그리고 <code>User.findById(video.owner)</code>를 해준다. </p>
<p><code>video</code>에는 <code>owner</code>가 있고 이건 <code>user</code>의 <code>id</code>이다. </p>
<p>그러면 <code>video</code>를 찾고 이걸 가지고 <code>owner</code>도 찾는 거다. </p>
<p><code>return res.render(&quot;watch&quot;, { pageTitle: video.title, video, owner }</code></p>
<p>그래서 <code>owner</code>도 추가해 주었다. </p>
<p>그리고 <code>watch.pug</code>에서 </p>
<pre><code class="language-js">extends base

block content
   video(src=&quot;/&quot; + video.fileUrl,controls)
   div
      p=video.description
      small=video.createdAt 
   div 
      small Uploaded by #{owner.name}   
   if String(video.owner) === String(loggedInUser._id)   
      a(href=`${video.id}/edit`) Edit Video &amp;rarr;
      br
      a(href=`${video.id}/delete`) Delete Video &amp;rarr;</code></pre>
<pre><code> div 
      small Uploaded by #{owner.name}   </code></pre><p>이렇게 추가해 주었다. </p>
<p>이게 가능한 이유는 <code>owner</code>를 <code>watch template</code>으로 보냈기 때문이다. </p>
<p>새로고침을 해주면 비디오를 업로드한 유저의 이름이 나오는걸 확인 할수 있다.</p>
<p><code>video</code>를 먼저 찾고 <code>owner</code>를 찾는거다. <code>DB</code>에는 2번이나 요청해서 조금 마음에는 안들지만 잘 작동하고 있다. </p>
<p>이게 바로 영상 소유자의 <code>id</code>를 <code>video</code>에 저장하면 좋은 이유이다. </p>
<p>이제 영상을 볼때 <code>owner</code>가 누군지도 알수 있다. </p>
<p>하지만 언제나 더 쉬운 방법이 있기 마련이다. 현재 코드도 직관적이고 좋다.</p>
<p>이보다 더 나은 방법이 있으니 그렇게 시도해 보도록 한다.</p>
<p><code>DB</code>에 2번이나 요청하는건 별로이기 때문이다.</p>
<p>현재는 영상 소유자의 <code>id</code>를 <code>video</code>에 저장하면 좋은 이유를 기억한다.</p>
<p>먼저 영상 소유주와 현재 접속자의 <code>id</code>를 비교 할수 있다. 현재 <code>id</code>만 비교하고 있다.</p>
<p>또한 영상 소유주의 이름을 보여줄수도 있다. </p>
<blockquote>
<p>중요한건 <code>postUpload</code>에서 <code>video</code>를 생성할때  <code>video.owner</code>에 현재 로그인된 사용자의 <code>id</code>를 넣어 주었다는 거다.</p>
</blockquote>
<p><code>req.session.user</code>가 현재 로그인된 사용자를 말한다. </p>
<p>이 말은 <code>video</code>의 <code>owner</code>로 현재 로그인 중인 유저의 <code>id</code>를 쓰겠다는 거다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[User Profile ]]></title>
            <link>https://velog.io/@0_cyberlover_0/User-Profile</link>
            <guid>https://velog.io/@0_cyberlover_0/User-Profile</guid>
            <pubDate>Thu, 28 Apr 2022 11:47:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번에는 사용자 프로필 창을 만들어 본다.</p>
</blockquote>
<p>왜냐하면 모든 사용자한테 프로필이 필요하기 때문이다. 그래서 모두가 부여 받는 사용자 프로필 창을 만들어 줄거다.</p>
<p>이 과정에서 많은 것들을 배우게 될거다. 예를 들어 <code>relationships</code>같은 것들이 있다. </p>
<p>사용자의 프로필 창에 들어가면 이름과 아바타 같은 해당 유저의 정보를 볼수 있게 만들거다.</p>
<p>그리고 그 유저가 업로드한 영상들을 볼수 있게 만들거다. </p>
<p>유튜브 채널처럼 만들면 좋을것 같다. </p>
<p>프로필에 들어가면 해당 유저가 올린 영상들을 볼수 있는 거다.</p>
<p>추가적으로 영상을 틀면 누가 그 영상을 올렸는지 확인하게 한다.</p>
<p>또한 해당 영상의 소유자가 아니면 이 두 버튼(<code>Edit Video</code>, <code>Delete Video</code>)을 보이지 않게 만들거다. </p>
<p>영상을 업로드한 사람만이 수정과 삭제 할수 있게 해야 하기 때문이다.</p>
<p>이 말은 <code>video</code>파일을 누가 업로드 했는지 알수 있어야 하고 사용자는 자신이 업로드한 영상들을 볼수 있어야 한다. </p>
<p>현재 <code>DB</code>를 보면 <code>video</code>에는 <code>owner</code> 항목이 없다.</p>
<p>그러니 <code>owner</code>를 <code>video</code>에 추가하고 <code>user</code>에는 <code>video list</code>를 추가해줄 필요가 있다. </p>
<pre><code>{ &quot;_id&quot; : ObjectId(&quot;6268bb3f69b51a45c9ad1ae3&quot;), &quot;email&quot; : &quot;pkpanda@naver.com&quot;, &quot;socialOnly&quot; : false, &quot;username&quot; : &quot;Cyber Lover&quot;, &quot;password&quot; : &quot;$2b$05$R5Yss2htjFd9vSa7TdxVGe4ukAj6wHpJAbip2FG06bcA6nKD.i9LS&quot;, &quot;name&quot; : &quot;Mercury1&quot;, &quot;location&quot; : &quot;NYC&quot;, &quot;__v&quot; : 0, &quot;avatarUrl&quot; : &quot;uploads/597ad456bcec6a0743a383a1653d5499&quot; }</code></pre><p>여기 보면 <code>user</code>가 있다. 그런데 이 <code>user</code>가 소유한 <code>video</code>는 하나도 없는것 같다.</p>
<p>그래서 이 둘을 연결해 줄거다. <code>video</code>가 하나의 <code>owner</code>만 가지도록 만들어야 하고
<code>user</code>는 여러 개의 <code>video</code>를 가질수 있도록 만들어야 한다.</p>
<blockquote>
<p><code>model</code>을 수정하기 전에 <code>my profile</code>의 링크를 만들어 준다.</p>
</blockquote>
<p><code>base.pug</code>에 들어가서 </p>
<pre><code class="language-js">body 
        header
            h1=pageTitle
            nav 
                ul 
                    li  
                        a(href=&quot;/&quot;) Home
                    if loggedIn
                        li 
                            a(href=&quot;/videos/upload&quot;) Upload Video
                        li 
                            a(href=`/users/${loggedInUser._id}`) My Profile</code></pre>
<p><code>localsMiddleware</code>가 제공하는 <code>loggedInUser locals</code>가 있다는걸 기억해야 한다.</p>
<p><code>middleware.js</code>에서 </p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = &quot;Wetube&quot;;
  res.locals.loggedInUser = req.session.user || {};
  next();
};</code></pre>
<p>이 부분이다. 현재 <code>/users/{로그인한 유저의 _id}</code>로 가는 <code>navigation</code>을 만들고 있다.</p>
<p>새로고침해서 <code>My Profile</code>로 들어가면 링크에 <code>/users/{사용자_id}</code>가 보인다.</p>
<p>나중에 이 <code>URL</code>을 모든 사용자들이 사용 할수 있게 만들어 준다.</p>
<p><code>URL</code>에 나와 있는 유저의 <code>id</code>를 사용해서 말이다. </p>
<p><code>My Profile</code>은 로그인된 사용자를 위한 것이니까 로그인한 사용자의 <code>id</code>를 사용했다.</p>
<p>그런데 이 페이지는 공개되어 있다. 아무나 들어갈수 있다.</p>
<p>왜냐면 프로필은 모두가 볼수 있기 때문이다. 첫번째 단계 였다.</p>
<blockquote>
<p>다음 단계는 이 오류를 해결할거다. 페이지가 에러가 나고 있다. </p>
</blockquote>
<pre><code>Cannot GET /users/$%7BloggedInUser._id%7D</code></pre><p><code>userRouter</code>에 들어가 준다.</p>
<pre><code class="language-js">userRouter.get(&quot;/:id&quot;, see);</code></pre>
<p><code>/</code>를 넣어 주었다. 새로고침 해서 확인해 보면 작동하고 있다. <code>See User</code>라고 나온다.</p>
<p>이제 <code>/users/사용자id</code>로 가면 <code>See User</code>를 볼수 있다.</p>
<p><code>userController.js</code>에서 </p>
<pre><code class="language-js">export const see = (req, res) =&gt; res.send(&quot;See User&quot;);</code></pre>
<p>아직 수정하지 않은 <code>controller</code>이다. 이제 <code>id</code>를 가져오는거다.</p>
<p><code>user session</code>에서 <code>id</code>를 가져오지 않을거다. 왜냐하면 이 페이지를 누구나 볼수 있어야 한다.</p>
<p>그래서 <code>URL</code>에 있는 <code>user id</code>를 가져 올거다. </p>
<p><code>req.session.user._id</code>로 <code>id</code>를 가져오지 않을거다.</p>
<p><code>public</code>으로 만들어야 하니까 <code>URL</code>에서 가져올거다. </p>
<pre><code class="language-js">export const see = (req, res) =&gt; {
  const { id } = req.params;
  return res.render(&quot;profile&quot;, { pageTitle: &quot;User Profile&quot; });
};</code></pre>
<p><code>req.params</code>에서 <code>id</code>를 꺼낸다. <code>pageTitle</code>의 구체적인건 나중에 바꾼다.</p>
<p>새로고침해보면 현재 <code>views</code> 디렉토리에 <code>profile</code>이 없다. </p>
<p><code>views</code>폴더 안 <code>users</code>폴더에 파일을 추가해주면 된다. </p>
<p><code>profild.pug</code>에서 </p>
<pre><code class="language-js">extends ../base

block content 
    h1 hello</code></pre>
<p>해주고 </p>
<p><code>userController.js</code>에서는</p>
<pre><code class="language-js">export const see = (req, res) =&gt; {
  const { id } = req.params;
  return res.render(&quot;users/profile&quot;, { pageTitle: &quot;User Profile&quot; });
};</code></pre>
<p><code>users/</code>를 추가 해준다. 그러면 새로고침하면 작동한다. </p>
<p>이제 <code>user profile tmeplate</code>을 <code>render</code>하고 있다. </p>
<p><code>pug template</code>를 만들고 <code>pug template</code>를 <code>base</code>에서 <code>extends</code> 해줬다.</p>
<p>그리고 <code>block content</code>를 썼고 <code>base</code>에도 <code>block content</code>가 있다.</p>
<p><code>base</code>에서는 <code>pageTitle</code>이란 변수를 사용한다. 그렇기에 <code>render</code>할때 <code>pageTitle</code>도 보내주는 거다.</p>
<p>지금 구현하고자 하는걸 설명하자면 <code>url</code>에 있는 <code>id</code>를 가지고 <code>user</code>를 찾는거다.</p>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  return res.render(&quot;users/profile&quot;, {
    pageTitle: `${user.name} Profile`,
    user,
  });
};</code></pre>
<p><code>req.params</code>에서 가져온 <code>id</code>를 추가한다. 이제 찾은 <code>user</code>를 <code>template</code>에 보내주기만 하면 된다. </p>
<p>그리고 <code>pageTitle</code>을 <code>user profile</code>로 하지 말고 유저 이름으로 만들어 준다.</p>
<p><code>${user.}</code>을 쓰고 <code>user</code>에 <code>name</code>이 있는거를 기억한다.</p>
<p>새로고침 하면 현재 로그인한 요저의 프로필이  나온다. </p>
<p>이렇게 수정도 가능하다. </p>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
  });
};</code></pre>
<p>현재 프로필 페이지는 로그아웃 상태여도 접속이 가능하다. </p>
<p>예를 들어 인스타에서 다른 사용자의 프로필을 볼수 있는 거랑 같다. </p>
<p>정보를 받아 <code>DB</code>를 검색하고 <code>render</code>하는 작업은 이제 거의 다 끝났다. </p>
<blockquote>
<p>이제 이 페이지에 어떻게 비디오를 추가할지 알아본다.</p>
</blockquote>
<p><code>video</code>를 어떻게 <code>user</code>와 연결 시킬수 있을까?</p>
<p>그전에 <code>DB</code>의 데이터를 전부 지워 준다. <code>DB</code>의 모든 <code>video</code>와 <code>user</code>를 없애준다.</p>
<p>이렇게 하지 않으면 연결을 할수 없다. 그래서 꼭 <code>DB</code>를 비워 줘야 한다. </p>
<p>현재 새로고침하면 에러가 난다. <code>user</code>가 없기 때문이다.</p>
<p>그래서 여기에서 체크를 해줄수 있다.</p>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  if (!user) {
    return res.status(404).render(&quot;404&quot;);
  }
  return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
  });
};</code></pre>
<p>만약 <code>user</code>가 없다면 이말은 <code>user</code>를 찾지 못했다는 거다. </p>
<p><code>res.render(&quot;404&quot;)</code>를 <code>return</code>할거다. 그리고 <code>status(404)</code>도 추가했다.</p>
<blockquote>
<p><code>404</code>를 적어주는걸 잊지 않는다.</p>
</blockquote>
<pre><code class="language-js">export const see = async (req, res) =&gt; {
  const { id } = req.params;
  const user = await User.findById(id);
  if (!user) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;User not found.&quot; });
  }
  return res.render(&quot;users/profile&quot;, {
    pageTitle: user.name,
    user,
  });
};</code></pre>
<p><code>pageTitle</code>도 추가해 준다. 이렇게 <code>mongoose</code>를 연습 해 봤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Video Upload]]></title>
            <link>https://velog.io/@0_cyberlover_0/Video-Upload</link>
            <guid>https://velog.io/@0_cyberlover_0/Video-Upload</guid>
            <pubDate>Thu, 28 Apr 2022 09:56:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>샘플로 쓸 영상을 하나 다운로드 해놨다.</p>
</blockquote>
<p><a href="https://sample-videos.com/">https://sample-videos.com/</a></p>
<p>여기서 쉽게 구할수 있다. 실험할 용량과 형식에 맞게 구할 수 있다.</p>
<p>이제 <code>video</code>를 업로드 할 거다. <code>video</code>에는 파일이 필요하다.</p>
<p>먼저 <code>views</code>폴더의 <code>upload.pug</code>에 들어가서 </p>
<pre><code class="language-js">block content 
    if errorMessage 
        span=errorMessage
    form(method=&quot;POST&quot;)
        label(for=&quot;video&quot;) Video File
        input(type=&quot;file&quot;, accept=&quot;video/*&quot;,requried,id=&quot;video&quot;,name=&quot;video&quot;)</code></pre>
<p><code>video</code>에 쓸 <code>label</code>을 만든다. <code>file</code>로 해도 상관없다.</p>
<p>그리고 <code>Video File</code>을 추가한다. 아래에는 <code>type</code>이 <code>file</code>인 <code>input</code>을 만든다.</p>
<p><code>accept</code>에는 모든 종류의 <code>video</code>형식을 쓴다. <code>required</code>를 추가해주는 걸 잊지 않는다.</p>
<p><code>label</code>은 <code>id</code>를 활용하니까 꼭 <code>id</code>를 추가해준다. 그리고 <code>multer middleware</code> 사용을 위해 <code>name</code>을 추가한다. <code>video</code>를 써줬다.</p>
<p>새로고침을 해보면 영상 파일을 선택하지 않으면 업로드 하지 못한다. </p>
<p>그리고 샘플 영상을 첨부해서 업로드를 해준다. </p>
<p>그러기 전에 <code>controller</code>에 어떤 일이 생길지 안다.</p>
<p><code>videoRouter</code>코드를 수정 해준다.</p>
<pre><code class="language-js">videoRouter
  .route(&quot;/upload&quot;)
  .all(protectorMiddleware)
  .get(getUpload)
  .post(uploadFiles.single(&quot;video&quot;), postUpload);</code></pre>
<p>여기에 만들어 놓은 <code>uploadFiles middleware</code>를 추가 해준다. </p>
<p>그리고 <code>form</code>에서 <code>file</code>의 <code>name</code>을 추가하는걸 잊어선 안된다.<code>file</code>의 <code>name</code>은 <code>video</code>이다.</p>
<p>이제 <code>multer documenatation</code>에서 볼게 있다.</p>
<p><a href="https://www.npmjs.com/package/multer">https://www.npmjs.com/package/multer</a></p>
<p>추가적으로 보낼수 있는 <code>option</code>들이 있다. 이 옵션들 중에 <code>fileSize</code>라는걸 사용할거다.</p>
<p>이걸 가지고 2개의 <code>middleware</code>에서 <code>avatar</code>파일의 업로드 용량을 <code>1MB</code>이하로 하고 
<code>video</code>파일의 업로드 용량을 <code>10MB</code>이하로 제한하면 좋을것 같다.</p>
<p>사람들이 용량이 큰 영상을 올리지 않게 말이다. </p>
<p>그래서 이 방법 대신에 2개의 <code>middleware</code>를 만든다.</p>
<pre><code class="language-js">export const avatarUpload = multer({
  dest: &quot;uploads/avatars/&quot;,
  limits: {
    fileSize: 3000000,
  },
});
export const videoUpload = multer({
  dest: &quot;uploads/videos/&quot;,
  limits: {
    fileSize: 10000000,
  },
});</code></pre>
<p>하나는 <code>avatar</code>업로드 하는걸로 만든다. <code>uploads/avatars</code>에 저장하도록 만든다.</p>
<p>다른 하나는 <code>videoUpload</code>로 만든다. 그리고 <code>uploads/videos</code>에 저장하도록 한다.</p>
<p>그리고 두곳 모두 <code>limits</code>를 추가한다. <code>avatar</code>,<code>video</code>의 <code>limits</code> 는 <code>fileSize</code>를 쓴다.</p>
<p>그리고 각각 용량을 설정해 주었다. 기본(단위)으로 바이트로 설정되어 있다. </p>
<p>이제 이보다 용량이 큰 파일은 업로드 되지 않는다.</p>
<p>테스트를 해본다. 일단 <code>avatarUpload</code>의 <code>fileSize</code>를 아주 작게 해본다.</p>
<p>3000바이트로 하면 어떻게 되는지 테스트 해보겠다.</p>
<p>새로고침을 하였더니 그전에 에러가 발생하였다. <code>middelware</code>이름을 변경해서 그런것 같다.</p>
<p><code>userRouter</code>에 들어가보면 더 이상 존재하지 않는 <code>uploadFiles</code>를 사용중이다.</p>
<pre><code class="language-js">import {
  protectorMiddleware,
  publicOnlyMiddleware,
  avatarUpload,
} from &quot;../middlewares&quot;;
userRouter
  .route(&quot;/edit&quot;)
  .all(protectorMiddleware)
  .get(getEdit)
  .post(avatarUpload.single(&quot;avatar&quot;), postEdit);</code></pre>
<p>변경한 이름으로 변경 해준다. </p>
<p>그리고 <code>videoRouter</code>의 <code>uploadFiles</code>도 존재하지 않으니까 </p>
<pre><code class="language-js">import { protectorMiddleware, videoUpload } from &quot;../middlewares&quot;;
videoRouter
  .route(&quot;/upload&quot;)
  .all(protectorMiddleware)
  .get(getUpload)
  .post(videoUpload.single(&quot;video&quot;), postUpload);</code></pre>
<p><code>videoUpload</code>로 변경해준다. 이제 콘솔에서 확인해 보면 에러가 나지 않는다.</p>
<p>새로고침하고 설정한 용량보다 큰 <code>avatar</code>를 업로드하면 어떻게 되는지 시도해 본다.</p>
<p>파일이 너무 크다고 에러가 났다. 파일이 너무 커서 <code>multer</code>가 허용하지 않고 있다. </p>
<p>이제 이 오류를 사용자에게 제대로 된 메세지로 보여줘야 한다. </p>
<p>그래서 응답을 보내줘야 하는데 나중에 해주기로 한다.</p>
<p>어찌되었든 <code>fileSize limits</code>는 잘 작동하고 있다. </p>
<p>이제 2개의 <code>middleware</code>가 생겼다. 하나는 용량 제한으로 <code>video</code>를 업로드하는거고
다른 하나는 <code>avatar</code>를 업로드 하는거다. </p>
<p><code>videoController.js</code>에서</p>
<pre><code class="language-js">export const postUpload = async (req, res) =&gt; {
  const file = req.file;
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      fileUrl: file.path,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<p>이제 <code>file</code>을 받아서 그리고 <code>file</code>자체가 아니라 <code>file</code>의 경로를 원하니까 <code>fileUrl</code>을 추가하고 <code>file.path</code>을 넣어준다.</p>
<blockquote>
<p><code>multer</code>는 <code>req.file</code>을 제공해주는데 <code>file</code>안에 <code>path</code>가 있다는걸 기억한다.</p>
</blockquote>
<p>하지만 아직 <code>video</code>안에 <code>fileUrl</code>을 만들지 않았다. </p>
<p><code>Video.js</code>에서 </p>
<pre><code class="language-js">const videoSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true, maxLength: 80 },
  fileUrl: { type: String, reqired: true },</code></pre>
<p><code>file</code>없이는 <code>video</code>를 만들지 못한다. 그리고 테스트 해보기 전에 잊으면 안될게 있다.</p>
<p><code>upload.pug</code>에서 </p>
<pre><code class="language-js">block content 
    if errorMessage 
        span=errorMessage
    form(method=&quot;POST&quot;, enctype=&quot;multipart/form-data&quot;</code></pre>
<p><code>multer</code>을 사용해서 파일을 업로드 하고 싶다면 <code>form</code>의 <code>encoding type</code>을 바꿔 줘야 한다.</p>
<p><code>edit-progfile</code>의 <code>form</code>에 <code>enctype</code>을 추가한 것처럼 똑같이 해주면 된다.</p>
<p>이 작업을 해주지 않으면 <code>multer</code>는 정상 작동하지 않는다. </p>
<p>잘못된 코드를 찾아 헤매도 찾지 못할거다. 그러니 <code>enctype</code>이 <code>multipart/form-data</code>인지 꼭 확인하도록 한다. </p>
<p>이제 테스트해 본다. 업로드에 성공했다. <code>DB</code>에서도 <code>video</code>를 찾을수 있다.</p>
<p><code>db.videos.find({})</code> 쳐보면 <code>fileUrl</code>이 <code>uploads/videos/파일이름</code>이 나온다.</p>
<p>그리고 <code>uploads</code>폴더에 들어가면 <code>avatars</code>와 <code>videos</code>가 있다.</p>
<p>이전 파일들이 지워지지 않는 문제가 있긴 하다. </p>
<p><code>multer</code>를 사용해서 <code>uploads</code> 폴더 안 서로 다른 디렉토리에 저장할수 있게 만들었다. </p>
<p><code>avatars</code>와 <code>videos</code>폴더가 있다. 현재 <code>avatars</code>에는 아무것도 없는데 새로운 <code>avatar</code>를 업로드 하면 생길거다.</p>
<blockquote>
<p>이제 업로드한 영상을 재생시켜 본다. </p>
</blockquote>
<p>업로드된 영상을 클릭해 보면 예전에 만든대로 <code>id</code>를 가지고 비디오를 찾고 있다. </p>
<p>그리고 <code>video</code>의 데이터가 모두 있다. 이제 여기에 <code>video element</code>를 추가하면 될것 같다.</p>
<p><code>watch.pug</code>로 가서 <code>video element</code>를 추가한다.</p>
<p><code>watch.pug</code>는 <code>videoController</code>가 <code>render</code>하고 있는 <code>template</code>이다.</p>
<p><code>videoControlloer.js</code>에서 </p>
<pre><code class="language-js">export const watch = async (req, res) =&gt; {
  const { id } = req.params;
  const video = await Video.findById(id);
  if (!video) {
    return res.status(404).render(&quot;404&quot;, { pageTitle: &quot;Video not found.&quot; });
  }
  return res.render(&quot;watch&quot;, { pageTitle: video.title, video });
};</code></pre>
<p><code>DB</code>에서 <code>video</code>를 찾은 다음 그 <code>video</code>를 변수로 보내고 있다.</p>
<p>이 말은 <code>object</code>가 통째로 <code>template</code>에 있다는 거다.</p>
<p>이제 다시 돌아가서 <code>video.fileUrl</code>을 쓴다.</p>
<p><code>watch.pug</code>에서 </p>
<pre><code class="language-js">block content
   video(src=&quot;/&quot; + video.fileUrl)</code></pre>
<p>그리고 <code>path</code> 때문에 작동하지는 않을거다. 그래서 <code>path</code>도 추가해 주었다.</p>
<p>새로고침해서 확인하면 영상이 올라온걸 확인 할수 있다. 여기서 이제 <code>controls</code>를 추가해준다.</p>
<pre><code class="language-js">block content
   video(src=&quot;/&quot; + video.fileUrl,controls</code></pre>
<p>그러면 이제 <code>controls</code>가 생겼고 재생이 된다. </p>
<blockquote>
<p>다시 한번 되짚어 보겠다.</p>
</blockquote>
<p><code>middleware</code>를 만들어 봤고 <code>limits</code>설정하는것도 했다. <code>avatar</code>와 <code>video</code>에 각각 원하는 용량으로 제한하면 된다. </p>
<p><code>watch.pug</code>에서 <code>video</code>도 추가해봤다. 그리고 영상이 실제로 존재하니까 <code>fileURL</code>도 추가해 주었다. </p>
<p><code>uploadVideo</code>로는 영상을 업로드하고 있다. </p>
<p>그리고 <code>path</code>를 바로 꺼내고 <code>path</code>을 쓸수 있게 변경 해주었다.</p>
<p><code>videoController.js</code>에서 </p>
<pre><code class="language-js">export const postUpload = async (req, res) =&gt; {
  const { path } = req.file;
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      fileUrl:path,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<p>그리고 다른 방법은 <code>request.file.path</code>가 있다는 것을 알고 있다. </p>
<p>이것 자체를 바꿀수는 없지만 <code>path</code>를 <code>req.file.path</code>에서 받은뒤 </p>
<p>이름을 <code>fileUrl</code>로 바꿀수 있다. </p>
<pre><code class="language-js">export const postUpload = async (req, res) =&gt; {
  const { path: fileUrl } = req.file;
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      fileUrl,
      hashtags: Video.formatHashtags(hashtags),
    });</code></pre>
<p>그러면 이렇게 바꿀수 있게 된다. 이게 바로 <code>ES6</code>의 힘이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Static Files and Recap]]></title>
            <link>https://velog.io/@0_cyberlover_0/Static-Files-and-Recap</link>
            <guid>https://velog.io/@0_cyberlover_0/Static-Files-and-Recap</guid>
            <pubDate>Thu, 28 Apr 2022 03:11:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 브라우저가 <code>uploads</code>폴더에 있는 내용을 볼수 있게 해야한다.</p>
</blockquote>
<p>브라우저가 서버에 있는 파일에 접근할수 없으니까 그렇다. 브라우저한테 어디로 가야 하는지 얘기해줘야 한다.</p>
<p>그리고 브라우저가 서버의 어떤 폴더로든 갈수 있는건 보안상 좋지 않다.</p>
<p>그래서 브라우저가 어떤 페이지와 폴더를 볼수 있는지 알려줘야 한다. </p>
<p>그러기 위해 <code>static files serving</code>이라는걸 활성화 해준다.</p>
<p>폴더 전체를 브라우저에게 노출시킨다는 의미이다. 폴더를 만들고 그 폴더를 브라우저에게 노출 시키는 거다.</p>
<p>먼저 <code>/uploads route</code>를 만들어야 한다. </p>
<p><code>server.js</code>에서 </p>
<pre><code class="language-js">app.use(&quot;/uploads&quot;, express.static(&quot;uploads&quot;));</code></pre>
<p><code>app.use</code>를 써서 <code>&quot;/uploads&quot;</code>로 간다면 그리고 폴더를 노출시킨다.</p>
<p>노출시키는 방법은 <code>express.static()</code>이다.</p>
<p><code>static()</code>에는 노출시키고 싶은 폴더의 이름을 쓰면 된다. </p>
<p><code>vsc</code>에서 정의를 보면 디렉토리 내부의 파일을 제공한다고 명시되어 있다. </p>
<p>그래서 <code>&quot;uploads&quot;</code>를 입력해 주면 된다. 새로고침을 하면 이미지가 안나오던데</p>
<p>서버는 잘 받고 있는데 서버에서 에러가 나던데 서버를 껐다 다시 재시작 해주니 잘 작동한다.</p>
<p>새탭에서 이미지를 열면 확장가 없어서 다운로드 할려고 한다.</p>
<p>확장자는 없지만 컴퓨터는 이해하고 있다. 브라우저도 이해하고 있다는 말이다.</p>
<blockquote>
<p>다시 한번 말하자면 처음 원했던건 파일을 업로드 하는거였다.</p>
</blockquote>
<p>서버로 파일을 보내고 싶었다. 먼저 <code>views</code>의 <code>edit-profile</code>에 파일을 받는 <code>input</code>을 만들었다.</p>
<pre><code class="language-js">    img(src=&quot;/&quot; + loggedInUser.avatarUrl, width=&quot;100&quot;,height=&quot;100&quot;)</code></pre>
<p>그리고 다른 파일은 필요 없으니  이미지만 받는다고 설정했다.</p>
<p>파일 <code>input</code>을 만들었고 원하는건 서버에 이미지를 저장하고 그 이미지에 대한 정보를 얻는거였다.</p>
<p>그래서 <code>multer</code>라는 <code>middelware</code>를 사용했다.</p>
<pre><code class="language-js">export const uploadFiles = multer({ dest: &quot;uploads/&quot; });</code></pre>
<p><code>form</code>으로 보낸 파일을 업로드 해주고, 그 파일에 관한 정보도 제공해주고 다른 기능도 있다.</p>
<p>예를 들면 파일명을 완전히 랜덤으로 생성해준다는 거다. </p>
<p>만약 두명의 유저가 <code>selfile</code>라는 같은 이름의 파일을 업로드 해도 <code>multer</code>가 이름을 바꿔 주기 때문에 아무 문제 없다.</p>
<p>그리고 그 파일을 지정한 폴더에 저장해준다. </p>
<p>그리고 다음 순서의 <code>controller</code>에 파일에 관한 정보를 제공한다.</p>
<p><code>routers</code>에서 <code>multer</code>를 먼저 사용해주고 </p>
<p><code>userRouter.js</code>에서 </p>
<pre><code class="language-js">userRouter
  .route(&quot;/edit&quot;)
  .all(protectorMiddleware)
  .get(getEdit)
  .post(uploadFiles.single(&quot;avatar&quot;), postEdit);</code></pre>
<p>그 다음 <code>postEdit controller</code>를 사용하면 <code>multer</code>가 시작된다.</p>
<p>파일이 업로드 되고 파일명이 바뀌고 파일이 <code>uploads</code>폴더에 저장되고 </p>
<p>그 파일에 관한 정보를 받아서 <code>postEdit</code>에 전달해 주는 거다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id, avatarUrl },
    },
    body: { name, email, username, location },
    file,
  } = req;
  const exists = await User.exists({
    _id: { $ne: { _id } },
    $or: [{ username }, { email }],
  });</code></pre>
<p>그리고 <code>postEdit</code>에서 <code>req.file</code>을 사용할수 있다.</p>
<p><code>postEdit</code>에서는 <code>avatarUrl</code>을 업데이트 할수 있는데 때로는 유저가 파일을 보내지 않을수도 있다.</p>
<p>그래서 꼭 확인해야 한다. <code>form</code>에 <code>file</code>이 있다면 <code>req</code>에 있는 <code>file object</code>를 사용 할수 있다는거고 그말은 <code>file.path</code>가 존재한다는 거다.</p>
<p>그런데 <code>form</code>에 <code>file</code>이 없다면 <code>user</code>의 <code>avatarUrl</code>은 기존의 것이랑 같다.</p>
<p>기존의 <code>avatarUrl</code>은 어디서 오는거냐면 <code>req.session.user.avatarUrl</code>에서 오는 거다.</p>
<p>기존 <code>avatarUrl</code>을 현재 로그인된 유저로 부터 가져오는 거다.</p>
<p>그리고 <code>avatarUrl</code>이 생기긴 했지만 그걸로는 충분하지 않다.</p>
<p>브라우저가 아직 이 파일이 존재하는지 모르기 때문이다.시도해봤지만 <code>404</code>에러가 뜨고 작동하지 않았다. </p>
<p>작동하지 않은 이유는 서버가 <code>/uploads/랜덤값</code>라는<code>url</code>을 이해하도록 설정되지 않아서 그랬다.</p>
<p>그래서 <code>server.js</code>에서</p>
<pre><code class="language-js">app.use(&quot;/uploads&quot;, express.static(&quot;uploads&quot;));</code></pre>
<p><code>Express</code>에게 만약 누군가 <code>/uploads</code>로 가려고 한다면 <code>uploads</code>폴더의 내용을 보여주라고 했다.</p>
<p><code>uploads</code>폴더는 <code>multer</code>가 파일을 저장하는 곳이다.</p>
<p>만약 다른 사진을 새로 아바타로 설정한다면 폴더에 또 새로운 이미지 파일이 생기는거다.</p>
<p>사이트가 활성화 되었을때 트래픽량이나 여러 모로 안 좋을것 같다.</p>
<p>이 방법의 문제점을 해결해 보도록 한다.</p>
<p>첫번째 문제점은 파일을 서버에 저장한다는거다. 좋은 생각이 아니다. 서버는 계속 종료되고 다시 시작하는걸 반복한다.</p>
<p>뭔가를 업데이트하면 새로운 서버를 만들어서 다시 시작하는거다.</p>
<p>그 전 서버에 저장 돼있던 파일들은 날아가는거다. </p>
<p>그렇다면 서버가 두개 필요하다면 어떨까? 하나의 백엔드를 만들더라도 서버 두개가 필요할때가 있다.</p>
<p>방문자가 너무 많으면 그럴수도 있다. 그럼 두개의 서버에서 <code>uploads</code>폴더를 공유해야 하나? 그건 너무 이상하다. </p>
<p>서버가 죽으면 어떻게 될까? </p>
<p>서버가 죽으면 서버를 시작할수 있는 코드를 가지고 다른 서버에서 다시 시작하면 된다.</p>
<p>그런데 서버가 죽었을때 코드와 업로드된 파일들이 있다면 파일은 날리는 거다.</p>
<p>그래서 나중에 이방법을 바꿀 건데 파일을 서버에 저장하는게 아니라 다른곳에 저장하는거다.</p>
<p>서버가 사라졌다 다시 돌아와도 파일은 그대로 있도록 말이다. 파일을 안전하게 저장하는거다.</p>
<p>이건 나중에 실제 서버에 배포할때 해본다.</p>
<blockquote>
<p>한가지 꼭 기억할게 있다. <code>DB</code>에 절대 파일을 저장하면 안된다.</p>
</blockquote>
<p><code>DB</code>에는 파일 자체가 아니라 파일의 위치를 저장하는거다.</p>
<p>그래서 파일 원본은 <code>Amazon</code>의 하드드라이브 같은데 저장하면된다.</p>
<p><code>DB</code>에는 <code>url</code>을 저장하면 된다.</p>
<p>다음 파트에서는 배운걸 연습하고 <code>video model</code>에 한가지를 추가할거다.</p>
<p><code>video model</code>에는 <code>video file</code>이 없다. </p>
<p>아바타에 했던걸 활용해서 <code>video file url</code>을 만들어 본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[File Uploads #02]]></title>
            <link>https://velog.io/@0_cyberlover_0/File-Uploads-02</link>
            <guid>https://velog.io/@0_cyberlover_0/File-Uploads-02</guid>
            <pubDate>Wed, 27 Apr 2022 12:40:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>controller</code>를 마무리 해본다.</p>
</blockquote>
<p>이제 <code>req.file</code>이 생겼고, 경로를 얻어야 한다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
    file: { path },
  } = req;</code></pre>
<p><code>req</code>에서 <code>file:{ path}</code>를 꺼내준다. 그리고 <code>path</code>를 <code>findByIdAndUpdate</code>에 보낸다.</p>
<pre><code class="language-js">const updatedUser = await User.findByIdAndUpdate(
    _id,
    {
      avatarUrl:path,
      name,
      email,
      username,
      location,
    },</code></pre>
<p><code>avatarUrl:path</code>를 입력해준다. 여기서 약간의 문제가 있다.</p>
<p>사용자가 아바타를 바꾸지 않았을때 발생한다. </p>
<p>그전에 콘솔로 확인해 본다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
    file: { path },
  } = req;
  console.log(path);</code></pre>
<p>사용자가 아바타를 바꾸지 않았을때 어떻게 되는지 확인하기 위해서다.</p>
<p>새로고침 한 다음 아바타는 변경하지 않고 이름만 변경해서 프로필을 업데이트 해보았다.</p>
<pre><code>TypeError: Cannot read properties of undefined (reading &#39;path&#39;)</code></pre><p>아바타를 변경하지 않았는데 에러가 났다. 콘솔을 확인해 보니 에러가 뜬다. </p>
<p><code>undefined</code>의 <code>path property</code>를 읽을 수 없다고 한다.</p>
<p>무슨 말이냐면 <code>file</code>이 <code>undefined</code>라서 <code>path</code>를 찾을 수 없다는 거다.</p>
<p>파일을 보내지 않으면 <code>req</code>안에 <code>file</code>은 <code>undefined</code>가 된다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
    file,
  } = req;
  console.log(file);</code></pre>
<p>이렇게 해주고 다시 테스트해 본다. 이름만 변경하고 업데이트 해본다. 저장 되었고 </p>
<p>콘솔을 확인해 보면 파일은 <code>undefined</code>이다. </p>
<p><code>file</code>이 존재하지 않으면 <code>avatarUrl:file.path</code>를 사용 할수 없다.</p>
<p><code>avatarUrl</code>을 빈 내용으로 만드는거랑 마찬가지이다. </p>
<p>사용자가 이미 아바타가 있다면 <code>undefined</code>인 아바타를 보내면 안된다.</p>
<p>대신에 <code>session</code>으로 <code>user</code>정보를 사용 할수 있었다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id,avatarUrl },
    },
    body: { name, email, username, location },
    file,
  } = req;
  console.log(file);
const updatedUser = await User.findByIdAndUpdate(
    _id,
    {
      avatarUrl: file ? file.path : avatarUrl,
      name,
      email,
      username,
      location,
    },</code></pre>
<p><code>user</code>에는 <code>avatarUrl</code>이 있다. 그래서 기존 <code>avatarUrl</code>을 찾을수 있다.</p>
<p>그리고 간단한 <code>if else</code>를 사용하면 된다. </p>
<p>파일이 존재한다면 그러니까 유저가 <code>form</code>으로 파일을 보낸다면 <code>file.path</code>을 쓰고</p>
<p>존재하지 않는다면 기존 <code>avatarUrl</code>을 유지할거다.</p>
<p>어떤 때는 파일이 <code>undefined</code>일 수 있다. <code>file</code>이 존재한다면 <code>file.path</code>가 있다는 말이다.</p>
<p><code>file</code>이 존재한다는건 <code>user</code>가 <code>input</code>으로 <code>file</code>을 보냈다는 거다, </p>
<p><code>file</code>이 존재하지 않는다면 사용자가 <code>input</code>을 건드리지 않았다는 거고,</p>
<p><code>file</code>이 존재하지 않으면 기존 <code>avatarUrl</code>로 저장 할거다. </p>
<p>새로운 <code>avatarUrl</code>을 <code>session</code>의 <code>user object</code>에 있는 기존 것으로 하겠다는 거다.</p>
<p><code>session</code>에는 <code>user object</code>가 있고 거기에 있는 정보를 사용할 수 있다.</p>
<p><code>_id</code>를 사용한 것처럼 기존의 <code>avatarUrl</code>을 사용 할수 있다.</p>
<p>테스트를 해본다. 먼저 아바타를 설정하고 업데이트 프로필 버튼을 클릭해준다.</p>
<p>업데이트가 잘 된것같다. <code>DB</code>에서도 확인해 본다.</p>
<p><code>db.users.find({})</code>해주면 여러 정보가 나오는데 <code>avatarUrl</code>에도 나오는데</p>
<p><code>uploads/</code>와 랜던된 값이 같이 나온다. </p>
<p>이제 <code>uploads</code>폴더를 보면 새로운 파일이 생겨 있다.</p>
<blockquote>
<p><code>commit</code>하기전에 <code>uploads</code>폴더를 <code>.gitignore</code>에 추가하는거를 잊지 말자</p>
</blockquote>
<pre><code class="language-js">/node_modules
.env
/uploads</code></pre>
<p><code>gitignore</code>에 추가하면 깃허브에 폴더가 업로드 되지 않는다. </p>
<p><code>form</code>에 파일을 보낼때도 있고 아닐때도 있다.</p>
<p>그래서 <code>form</code>으로 파일을 보내는걸 확인하면 그 파일을 새로운 <code>avatarUrl</code>로 저장해주는 거다.</p>
<p>파일을 보내지 않으면 <code>user</code>에 기존 <code>avatarUrl</code>이 그대로 새로운 <code>avatarUrl</code>로 된다.</p>
<p>그리고 이제 다시 프로필을 업데이트 해본다.  이번에는 아바타 파일을 보내지 않을거다.</p>
<p>다시 <code>DB</code>를 확인해 보면 <code>avatarUrl</code>에는 그대로 유지 되고 있다.</p>
<p>이제 파일을 보내지 않아도 에러가 생기지 않는다. </p>
<p>이렇게 파일을 업로드하고, 파일<code>URL</code>을 저장하게 되었다. </p>
<blockquote>
<p>절대로 <code>DB</code>에 파일을 저장하면 안된다.</p>
</blockquote>
<p>대신에 폴더에 파일을 저장하고 <code>DB</code>에는 그 파일의 위치만 저장하는거다.</p>
<p>이제 <code>user</code>에는 <code>avatarUrl</code>이 있으니 사용해 볼수 있다.</p>
<p>그래서 <code>edit-profile</code> 페이지에다가 이미지를 만들거다.</p>
<pre><code class="language-js">block content
    img(src=loggedInUser.avatarUrl, width=&quot;100&quot;,height=&quot;100</code></pre>
<p><code>img</code>의 <code>src</code>는 <code>loggedInUser.avatarUrl</code>이다.</p>
<blockquote>
<p><code>user</code>에는 <code>avatarUrl</code>이 있다는걸 명심한다.</p>
</blockquote>
<p><code>middleware.js</code>에서</p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = &quot;Wetube&quot;;
  res.locals.loggedInUser = req.session.user || {};
  next();
};</code></pre>
<p>다시 한번 설명하면 <code>loggedInUser</code>는 현재 로그인 된 사용자고 <code>localsMiddleware</code>를 통해 <code>pug</code>에 전달되고 있다. </p>
<p><code>locals</code>에 저장하는 모든 정보는 <code>views</code>에서 사용 가능하다. </p>
<p><code>img</code>가 생겼으니 <code>img</code>에 <code>100px</code>의 <code>width</code>,<code>height</code>를 설정해 준다.</p>
<p><code>CSS</code> 작업하기 전까지만 일단 이렇게 사용 하기로 한다.</p>
<p>새로고침을 하면 사진이 에러가 난다. 이미지가 보여야 되는데 검사를 해서 확인해 본다.</p>
<p><code>src</code>는 괜찮은것 같다. 콘솔에서 확인하면 이미지를 찾을수 없다고 한다.</p>
<p>새로운 탭으로 열어서 확인해 보면</p>
<pre><code>Cannot GET /users/uploads/597ad456bcec6a0743a383a1653d5499</code></pre><p>링크에 문제가 있다. 이미지가 <code>uploads/</code>로 저장되어 있는데  여기서는 상대 <code>URl</code>로 바뀌었다. </p>
<pre><code class="language-js">block content
    img(src=&quot;/&quot; + loggedInUser.avatarUrl, width=&quot;100&quot;,height=&quot;100&quot;)</code></pre>
<p>이렇게 바꿔준다. 새로고침을 하고 확인해보면 아직도 에러가 나고 있긴 하다.</p>
<p>이유는 해당 <code>URL</code>을 만들지 않았다. <code>Express</code>한테 <code>uploads</code>폴더가 새로 생겼다고 말해준적이 없다.</p>
<p>링크에 있는 모든 것들이 <code>router</code>에 있어야 되는데 그렇지 않다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ File Uploads #01]]></title>
            <link>https://velog.io/@0_cyberlover_0/File-Uploads-01</link>
            <guid>https://velog.io/@0_cyberlover_0/File-Uploads-01</guid>
            <pubDate>Wed, 27 Apr 2022 09:58:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>파일을 업로드 하는 방법을 알아 본다.</p>
</blockquote>
<p>업로드 하려면 먼저 로그인이 돼 있어야 한다. 깃허브로 로그인 한다. 그리고 <code>input</code>을 추가한다.</p>
<pre><code class="language-js">block content
    if errorMessage
        span=errorMessage
    form(method=&quot;POST&quot;)
        label(for=&quot;avatar&quot;) Avatar
        input(type=&quot;file&quot;, id=&quot;avatar&quot;, name=&quot;avatar&quot;,accept=&quot;image/*&quot;)</code></pre>
<p><code>change-password</code>가 아니라 <code>edit-profile</code>에서 추가해준다.  <code>type=&quot;file&quot;</code>인 <code>input</code>을 추가한다. </p>
<p><code>Avatar</code>라는 <code>label</code>을 먼저 만들고 <code>label(for=&quot;avatar&quot;)</code>를 입력하고 <code>input</code>에 <code>id=&quot;avatar&quot;</code>를 추가 해준다.</p>
<p>이 두개가 합쳐져서 클릭하면 아바타를 선택할수 있다. 그리고 <code>avatar</code>에 <code>name</code>을 줘야 한다.</p>
<p>여전히 <code>form</code>에 <code>name</code>을 줘야 하기 때문이다. <code>name=&quot;avatar&quot;</code>라고 해준다.</p>
<p>사용자가 파일을 선택할수 있는데 <code>pdf</code>같은 파일을 선택 할수도 있다.</p>
<p>모든 이미지 파일만 받아 들일수 있게 해준다. <code>accept=&quot;image/*&quot;</code> 를 사용 하면 된다.</p>
<p>어떤 이미지 파일이든 선택할수 있게 되었다. </p>
<p><code>input</code>을 만드는게 첫 단계이다. </p>
<blockquote>
<p>두번째는 이것을 도와줄 <code>middleware</code>를 사용한다.</p>
</blockquote>
<p>여기 <code>multer</code>라는 <code>middleware</code>가 있다. </p>
<p><a href="https://www.npmjs.com/package/multer">https://www.npmjs.com/package/multer</a></p>
<p><code>multer</code>는 파일을 업로드 할수 있게 해준다.<code>npm i multer</code>로 설치해준다. </p>
<p>그리고 <code>multer</code>를 사용법을 알아 본다.</p>
<pre><code>NOTE: Multer will not process any form which is not multipart (multipart/form-data).</code></pre><p>&quot;<code>multer</code>는 <code>multipart</code>가 아닌 <code>form</code>을 처리하지 않는다.&quot; 라고 되있다.</p>
<p><code>multer</code>의 도움을 받고 싶다면 <code>form</code>을 <code>multipart form</code>으로 만들어야 한다.</p>
<p><code>form</code>에다가 <code>enctype=&quot;multipart/form-data&quot;</code>를 추가해준다.</p>
<pre><code class="language-js">block content
    if errorMessage
        span=errorMessage
    form(method=&quot;POST&quot;, enctype=&quot;multipart/form-data&quot;)</code></pre>
<p>이렇게 해준건 <code>form</code>이 다르게 <code>encode</code>된다는 의미이다. </p>
<p>이게 파일을 업로드 하기 위한 유일한 조건이다. <code>form</code>을 <code>multipart/form-data</code>로 만들어 주면 된다.</p>
<p>이게 파일을 백엔드로 보내기 위해 필요한 <code>encoding type(enctype)</code>이다.</p>
<blockquote>
<p>그리고 다음은 <code>middleware</code>를 만들어 줘야 한다. </p>
</blockquote>
<pre><code>const upload = multer({ dest: &#39;uploads/&#39; })
app.post(&#39;/profile&#39;, upload.single(&#39;avatar&#39;), function (req, res, next) {
  // req.file is the `avatar` file
  // req.body will hold the text fields, if there were any
})</code></pre><p><code>middleware</code>를 설정 해주고 이 경우에는 설정과 함께 <code>multer</code> 함수를 사용해서 <code>middleware</code>를 만들고 <code>route</code>에서 사용하는 거다. </p>
<p>먼저 <code>middleware</code>를 만들어 본다. <code>middleware.js</code>에서</p>
<pre><code class="language-js">export const uploadFiles = multer({ dest: &quot;uploads/&quot; });</code></pre>
<p>여기에서는 <code>req</code>,<code>res</code>를 쓰지 않는다. 대신 <code>multer()</code>를 쓰고 설정한다.</p>
<p><code>multer</code>에는 <code>configuration object</code>가 있다.</p>
<p>살펴보면 보낼수 있는것 중 하나는 <code>destination</code>이다. 파일을 어디에 보낼지 정한다.</p>
<p>사용자가 파일을 보내면 사용자로부터 파일을 받으면 그 파일을 어딘가에 저장을 해야한다.</p>
<p>예를 들면 하드드라이브 같은 곳 말이다. 나중에는 이게 좋지 않은 이유를 알아 보고</p>
<p>다른 곳으로 저장 시킬거다.</p>
<blockquote>
<p>그리고 잊지 말고 <code>multer</code>를 <code>import</code>한다.</p>
</blockquote>
<p>현재는 파일들을 하드드라이브에 저장해야 된다고 가정하고 진행한다.</p>
<pre><code>const upload = multer({ dest: &#39;uploads/&#39; })</code></pre><p>이 경우에 <code>uploads</code>라는 폴더를 만들고 그리고 <code>multer</code>에게 사용자가 업로드 하는 모든 파일들을 서버의 <code>uploads</code>폴더에 저장하라고 하는거다.</p>
<p>그래서 <code>multer({ dest: &quot;uploads/&quot; })</code>라고 해주었다. </p>
<p>사용자가 보낸 파일을 <code>uploads</code>폴더에 저장하도록 설정된 <code>middleware</code>가 생겼다.</p>
<p>아직 그 폴더가 없어서 에러가 날거다. 폴더를 만들어 준다. (강의에서는 생기던데...)</p>
<blockquote>
<p>이제 <code>middleware</code>를 <code>route</code>에 사용한다. <code>controller</code>에는 사용하지 않는다.</p>
</blockquote>
<p><code>route</code>로 이동한다. 그리고 <code>edit-profile</code>에서 사용 해준다.</p>
<p><code>get</code>이 아니라 <code>post</code>에 사용해 준다. 사용방법을 살펴보면 </p>
<pre><code>app.post(&#39;/profile&#39;, upload.none(), function (req, res, next) {
  // req.body contains the text fields
})</code></pre><p><code>middleware</code>를 쓰고 <code>controller</code>함수를 사용하는거다. </p>
<p>다시 설명하면 <code>url</code>이 있고 <code>middleware</code>, 그리고 <code>controller</code>함수를 쓰는 거다.</p>
<p>이미 <code>url</code>이 있고 <code>post</code>에서 <code>middleware</code>를 사용한다. 그리고 <code>controller</code>함수를 쓰면 된다. </p>
<p>먼저 <code>uploadFiles middleware</code>를 사용한다.</p>
<pre><code class="language-js">userRouter.get(&quot;/logout&quot;, protectorMiddleware, logout);
userRouter
  .route(&quot;/edit&quot;)
  .all(protectorMiddleware)
  .get(getEdit)
  .post(uploadFiles.single(&quot;avatar&quot;), postEdit);</code></pre>
<p><code>uploadFiles.</code>을 써주고 <code>fields</code>,<code>none</code>,<code>single</code>,<code>array</code>등이 있다.</p>
<p>사용자가 다수의 파일을 보내야 될때도 있어서 그렇다. </p>
<p>현재는 하나의 파일만 필요하니 <code>single</code>로 사용해 준다. 그리고 <code>filedName</code>을 써줘야 한다.</p>
<p><code>formt</code>의 어디에서 파일이 오고 있냐면 <code>avatar</code>에서 오고 있다. 그래서 <code>avatar</code>를 넣어 주었다. 그리고 <code>string</code>이여야 한다. </p>
<p>조금 헷갈릴수 있는데 <code>middleware</code>를 실행한 다음 <code>postEdit</code>을 실행 하는거다. </p>
<p>왜 이렇게 하냐면 <code>multer</code>는 <code>input</code>으로 <code>avatar</code>파일을 받아서 그 파일을 <code>uploads</code>폴더에 저장한 다음 그 파일 정보를 <code>postEdit</code>에 전달해 주는거다. </p>
<blockquote>
<p><code>middleware</code>는 왼쪽에서 오른쪽 순서로 작동한다는 걸 기억한다.</p>
</blockquote>
<p><code>multer middleware</code>가 먼저 실행되고 그 다음 <code>postEdit</code>이 실행되는 거다.</p>
<p>다시 한번 정리하면 <code>uploadFiles.single</code>이 하는 역할은 <code>template</code>의 <code>input</code>에서 </p>
<p>오는 <code>avatar</code>파일을 가지고 파일을 업로드 하고 <code>uploads</code>폴더에 저장한다.</p>
<p>그리고 다음 <code>controller</code>인 <code>postEdit</code>에 그 파일의 정보를 전달하는 거다.</p>
<p>좋은점은 이렇게 하면 <code>request</code>에 <code>req.file</code>이 추가 된다는거다.</p>
<pre><code>function (req, res, next) {
  // req.file is the `avatar` file</code></pre><p>현재는 이미 <code>form data</code>인 <code>req.body</code>를 알고 있다. 이제 이걸 사용하기에 </p>
<pre><code class="language-js">userRouter.get(&quot;/logout&quot;, protectorMiddleware, logout);
userRouter
  .route(&quot;/edit&quot;)
  .all(protectorMiddleware)
  .get(getEdit)
  .post(uploadFiles.single(&quot;avatar&quot;), postEdit);</code></pre>
<p>이 코드가 <code>req.file</code>을 사용하게끔 해주는 거다. </p>
<p>사용자가 <code>url</code>로 <code>form</code>을 보내면 <code>middleware</code>를 걸쳐서 <code>middleware</code>는 사진을 시스템에 저장하고 , <code>req.file</code>을 추가 할거다. </p>
<p>그리고 나서 <code>controller</code>를 실행시키면 <code>req.file</code>과 <code>req.body</code>를 사용 할수 있다.</p>
<p>현재 <code>controller</code>는 이미 <code>req.body</code>를 사용하고 있다. </p>
<p>하지만 아직 <code>req.file</code>이 어떻게 생겼는지는 모른다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
    file,
  } = req;
  console.log(file);</code></pre>
<p>콘솔을 이용해서 확인해 본다. 에러가 없다. 아직 파일을 사용하고 있지 않지만</p>
<p>있어서 나쁠건 없다. 파일을 업로드 하면 어떤일도 발생하지 않지만 콘솔을 확인해 보면 </p>
<p><code>file</code>을 볼수 있다. 파일 경로도 나온다. 파일명은 랜덤은로 만들어 졌다.</p>
<p>원본 파일명도 나온다. <code>type</code>도 있고, <code>destination</code>도 있고, 파일명과 경로로 있다.</p>
<p>이제 <code>uploads</code>폴더를 확인해 본다. 살펴보면 현재 파일은 <code>jpeg</code> 같은 확장자가 없다.</p>
<p>그래도 브라우저는 이해하기 때문에 상관없다. 파일을 업로드 하는데 성공 했다.</p>
<p><code>req.file</code>을 사용할수 있는 이유는 <code>userRouter</code>를 보면 <code>postEdit</code>전에 <code>Multer</code>를 사용하고 있다. 반대였다면 <code>postEdit</code>에서 <code>req.file</code>을 사용할수 없다.</p>
<blockquote>
<p><code>middleware</code>의 순서는 아주 중요하다. </p>
</blockquote>
<pre><code class="language-js">userRouter.get(&quot;/github/start&quot;, publicOnlyMiddleware, startGithubLogin);
userRouter.get(&quot;/github/finish&quot;, publicOnlyMiddleware, finishGithubLogin);</code></pre>
<p>예를 들어 사용자가 <code>/github/start</code>에 가면 먼저 <code>middleware</code>가 확인하고 다음으로 가는 거랑 같은 거다. </p>
<p>이것도 마찬가지이다. <code>middleware</code>를 확인하고 <code>finishGithubLogin</code>으로 가는거다.</p>
<p>다시 한번 정리하면 <code>single</code>은 하나의 파일만 업로드 한다는거고 파일의 <code>input name</code>    을 <code>middleware</code>에 전달해야 한다. </p>
<p><code>name</code>은 <code>form</code>의 <code>name</code>이랑 같아야 한다.</p>
<pre><code>path: &#39;uploads/2ec71c2270a855b3e422a370e005d5b3&#39;</code></pre><p>이제 경로(<code>path</code>)로 사용할거다. <code>models</code>의 <code>User</code>에 <code>avatarUrl</code>이 있다.</p>
<p><code>avatarUrl</code>이 바로 이 경로인건다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Change Password #02]]></title>
            <link>https://velog.io/@0_cyberlover_0/Change-Password-02</link>
            <guid>https://velog.io/@0_cyberlover_0/Change-Password-02</guid>
            <pubDate>Wed, 27 Apr 2022 06:09:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>먼저 <code>mongo</code>로 가서 데이터를 삭제 해 주었다.</p>
</blockquote>
<p><code>use wetube</code>를 커맨드 입력해주고 <code>db.sessions</code>,<code>db.users</code>에서 <code>remove({})</code> 해주면 된다.</p>
<p>그리고 계정을 하나 만들어 본다. 다른건 다 작동하나 비밀번호 수정은 아직 안된다.</p>
<p>그전에 새로운 <code>middelware</code>를 만들어 본다.</p>
<p>깃허브로 로근인하지 않은 사람들을 위한 <code>middleware</code>이다.</p>
<p>깃허브로 로그인하면 무언가를 보여주지 않거나 보여주거나 하는 <code>middelware</code>이다.</p>
<p>비밀번호를 가지고 있는 유저로부터 <code>url</code>을 보호하는 <code>middleware</code>를 만들 수도 있다.</p>
<pre><code class="language-js"> if (req.session.user.socialOnly === true) {
    return res.redirect(&quot;/&quot;);
  }</code></pre>
<p>이 코드를 여러 번 반복한다면 <code>middleware</code>를 만들어도 된다. 현재는 아니니 그냥 둔다.</p>
<p>그리고 <code>input</code>에 <code>name</code>을 넣는걸 잊어서는 안된다. 그러면 백엔드에서는 읽을수 없다.</p>
<p><code>type</code>도 <code>password</code>로 해주는거 잊지 않는다. 이제 이 <code>name</code>들을 <code>controller</code>에서 사용 할거다.</p>
<pre><code class="language-js">extends ../base

block content 
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Old Password&quot;,name=&quot;oldPassword&quot;, type=&quot;password&quot;, requeired)
        input(placeholder=&quot;New Password&quot;,name=&quot;newPassword&quot;, type=&quot;password&quot;, requeired)
        input(placeholder=&quot;New Confirm Password&quot;,name=&quot;newPasswordConfirmation&quot;, type=&quot;password&quot;, requeired)
        input(type=&quot;submit&quot;, value=&quot;Change Password&quot;)
</code></pre>
<p>바로 여기(<code>pastChangePassword</code>)에서 사용 할거다.</p>
<pre><code class="language-js">export const postChangePassword = (req, res) =&gt; {</code></pre>
<p><code>name</code>들을 <code>req.body</code>안에서 가져온다.</p>
<pre><code class="language-js">const { oldPassword, newPassword, newPasswordConfirmation } = req.body;</code></pre>
<p>그리고 현재 로그인한 사용자가 누군지 알아야 한다. 지금은 누가 비밀번호를 변경하려는지 모른다.</p>
<p>이 경우에는 <code>postEdit</code>에서 사용 했던 방식을 쓰면 된다.</p>
<p>누가 로그인 했는지 알려면 <code>user</code>의 <code>id</code>를 확인해야 되기 때문이다.</p>
<p>그리고 비밀번호에 관련 된것들도 바꿔주면 된다. </p>
<pre><code class="language-js">export const postChangePassword = (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;</code></pre>
<p><code>session</code>에서 현재 로그인된 사용자를 확인하고 <code>form</code>에서 정보를 가져오는거다.</p>
<p><code>form</code>에서는 사용자에게 누구냐고 묻지 않는다. 다른 사용자의 정보를 변경 할수 없어야 한다.</p>
<p>이제 사용자가 존재하는지 확인하고 <code>async</code>를 하는거 잊지 말고 그리고 비밀번호를 변경해주면 된다.</p>
<p>새 비밀번호와 비밀번호 확인이 같은지 확인해야 한다. </p>
<pre><code class="language-js">export const postChangePassword = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;
  if (newPassword !== newPasswordConfirmation) {
    return res.render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The new password does not match the confirmation.&quot;,
    });
  }
  // send notification
  return res.redirect(&quot;/&quot;);</code></pre>
<p><code>getChangePassword</code>에서 썼던 코드를 가져와서 에러메세지를 추가 해주었다.</p>
<blockquote>
<p>나중에는 이렇게 에러 메세지를 보내는 대신에 어떤 <code>template</code>에도 보낼수 있는 알림을 사용 할거다.</p>
</blockquote>
<pre><code class="language-js">block content 
    if errorMessage 
        span=errorMessage</code></pre>
<p>그리고 <code>change-password</code>에도 에러메세지를 만들어 준다.</p>
<p>테스트 해보면 잘 된다. 그러나 브라우저는 맞은걸로 알고 비밀번호를 저장하겠냐고 물어본다.</p>
<p>전에도 해봤던 <code>status code</code>를 변경해야 한다. </p>
<pre><code class="language-js">    return res.status(400).render(&quot;users/change-password&quot;, {</code></pre>
<p><code>join</code>에서 에러가 생길때 했었다. 브라우저는 <code>status code</code>를 주시하고 있다.</p>
<blockquote>
<p>이번에 할건 기존 비밀번호가 정확한지 확인해야 한다. </p>
</blockquote>
<p><code>bcrypt</code>로 했었다. 적용해 본다.</p>
<pre><code class="language-js">export const postChangePassword = async (req, res) =&gt; {
  const {
    session: {
      user: { _id, password },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;
  const ok = await bcrypt.compare(oldPassword, password);
  if (!ok) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The current password is incorrect.&quot;,
    });
  }</code></pre>
<p><code>const ok = await bcrypt.compare()</code> 넣어준다. </p>
<p><code>oldPassword</code>를 <code>plain text</code>로 넣고 암호화된 <code>password</code>도 넣어준다.</p>
<p><code>session</code>에 현재 로그인된 <code>user</code> 정보가 있다. <code>user</code>는 <code>password</code>정보를 포함한 <code>object</code>이다.</p>
<p>그래서 사용자가 <code>form</code>으로 보낸 비밀번호와 <code>session</code>에 있는 비밀번호를 비교 한다.</p>
<p><code>if(!ok)</code> <code>ok</code>가 아니면 똑같은걸 <code>return</code>한다.</p>
<p>그리고 &quot;기존 비밀번호가 일치하지 않다&quot;로 에러 메세지를 바꿔 준다.</p>
<p>만약 모든게 일치한다면 비밀번호를 변경하도록 해준다. 그 전에 에러 메세지가 잘 나오는지 확인해 본다.</p>
<p>에러 메세지가 잘 나온다. </p>
<blockquote>
<p>그 다음 단계는 비밀 번호를 변경해준다.</p>
</blockquote>
<p>예전에 비밀번호를 저장하던게 있다. </p>
<p><code>User.js</code>에서</p>
<pre><code class="language-js">userSchema.pre(&quot;save&quot;, async function () {
  this.password = await bcrypt.hash(this.password, 5);
});</code></pre>
<p>비밀번호를 보내고 저장하면 저 함수가 비밀번호를 <code>hash</code>해준다.</p>
<p>함수를 작동시키려면 두가지를 해줘야 한다. 하나는 <code>pre save middleware</code>를 거치고</p>
<p><code>User.create</code>를 사용하는거다. </p>
<blockquote>
<p><code>pre save middleware</code>가 비밀번호를 <code>hash</code>해주고 있다는걸 기억한다.</p>
</blockquote>
<p>그리고 <code>user.save()</code>를 해도 <code>pre save middleware</code>를 작동시킬거다.</p>
<pre><code class="language-js">export const postChangePassword = async (req, res) =&gt; {
  const {
    session: {
      user: { _id, password },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;
  const ok = await bcrypt.compare(oldPassword, password);
  if (!ok) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The current password is incorrect.&quot;,
    });
  }
  if (newPassword !== newPasswordConfirmation) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The new password does not match the confirmation.&quot;,
    });
  }
  user.save();</code></pre>
<p>하지만 이 경우에는 현재 <code>user</code>가 없다. 그래서 먼저 <code>user</code>를 찾아야 한다.</p>
<p><code>save()</code>함수를 써야 하니까 <code>session</code>에서 로그인된 <code>user</code>를 찾아야 한다.</p>
<p>그 <code>user</code>를 찾으면 <code>save()</code>함수를 사용 할수 있다.</p>
<pre><code class="language-js">if (newPassword !== newPasswordConfirmation) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The new password does not match the confirmation.&quot;,
    });
  }
  const user = await User.findById(_id);
  console.log(&quot;Old pw&quot;, user.password);
  user.password = newPassword;
  console.log(&quot;New unhashed pw&quot;, user.password);
  await user.save();
  console.log(&quot;New pw&quot;, user.password);
  // send notification
  return res.redirect(&quot;/&quot;);
};</code></pre>
<p><code>const user = await User.findById</code>를 사용하여 <code>session</code>에서 <code>_id</code>를 찾을수 있다.</p>
<p><code>user</code>를 찾았으니 <code>user.password = newPassword;</code> 해주고 그 다음 <code>user.save()</code>를 사용하는 거다. </p>
<p><code>user.save()</code>를 하면 <code>pre save</code>가 작동한다. 이걸 작동시키려는 이유는 새로운 비밀번호를 <code>hash</code>하기 위해서이다.</p>
<p>확인할수 있게 <code>console.log(user.password)</code>를 해준다.</p>
<p>매 부분마다 확인할수 있게 <code>console.log(user.password)</code>를 넣어 주었다.</p>
<p>그리고 <code>save()</code>는 <code>promise</code>이다. 그래서 <code>await</code>를 붙여 주었다.</p>
<p><code>DB</code>에 무언가를 저장하는데는 시간이 걸려서 그렇다.</p>
<p><code>console.log</code>가 새 비밀번호와 <code>hash</code>된 새 비밀번호를 보여줄거다.</p>
<p>예전 비밀번호도 확인해 본다. 테스트 해서 확인해 보겠다. 잘 된것같다. 콘솔에서도 확인해 본다.</p>
<pre><code>Old pw $2b$05$k0gX8agEfRX0ufJ7H1lOWe99BpAEvMJ9qGPZZ389cRQHCJampVDl2
New unhashed pw 123
New pw $2b$05$.h1dcDGwaXZpF6iS9CC1Pehmb3kwZwOT9aeGozlsZAGAJm9yHgf/a</code></pre><p>기존 비밀번호, <code>hash</code>되지 않은 새 비밀번호 그리고 <code>hash</code>된 새 비밀번호가 나온다.</p>
<p>그리고 사용자가 비밀번호를 바꾸면 로그아웃 시키는 기능도 넣어본다.</p>
<p>다시 로그인 하도록 로그아웃 시키는 거다. </p>
<pre><code class="language-js">const user = await User.findById(_id);
  console.log(&quot;Old pw&quot;, user.password);
  user.password = newPassword;
  console.log(&quot;New unhashed pw&quot;, user.password);
  await user.save();
  console.log(&quot;New pw&quot;, user.password);
  // send notification
  return res.redirect(&quot;/users/logout&quot;);</code></pre>
<p>테스트 해보니 비밀번호를 바꾸면 로그아웃 시킨다. 그러나 해당 강의에서는 안된다.</p>
<p>그래서 따라 해보기로 한다. </p>
<p>안되는 이유는 비밀번호 변경을 잘목했거나 기존 비밀번호를 <code>session</code>에 있는 비밀번호와 비교해서 그럴수도 있다.</p>
<p><code>session</code>을 업데이트해주지 않아서 그렇다. <code>session</code>에 있는 <code>hash</code>된 비밀번호가 기존 비밀번호와 일치하는지 확인하고 있다. </p>
<p>그러니 <code>session</code>을 업데이트 해준다. </p>
<pre><code class="language-js"> const user = await User.findById(_id);
  user.password = newPassword;
  await user.save();
  req.session.user.password = user.password;
  // send notification
  return res.redirect(&quot;/users/logout&quot;);
};</code></pre>
<p><code>req.session.user.password = user.password;</code>이렇게 해준다.</p>
<p>왜냐하면 여기서는 비밀번호가 <code>hash</code> 돼 있기 때문이다. <code>DB</code>와 <code>session</code> 두개의 저장소를 사용하고 있다. </p>
<p><code>session</code>에서 정보를 받으면 업데이트도 해줘야 한다. </p>
<blockquote>
<p><code>form</code>에서 가져온 비밀번호랑 현재 로그인된 사용자의 비밀번호를 비교하고 있는걸 기억한다.</p>
</blockquote>
<p>다른 방법도 있다.</p>
<pre><code class="language-js">export const postChangePassword = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { oldPassword, newPassword, newPasswordConfirmation },
  } = req;
  const user = await User.findById(_id);
  const ok = await bcrypt.compare(oldPassword, user.password);
  if (!ok) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The current password is incorrect.&quot;,
    });
  }
  if (newPassword !== newPasswordConfirmation) {
    return res.status(400).render(&quot;users/change-password&quot;, {
      pageTitle: &quot;Change Password&quot;,
      errorMessage: &quot;The new password does not match the confirmation.&quot;,
    });
  }

  user.password = newPassword;
  await user.save();
  return res.redirect(&quot;/users/logout&quot;);
};</code></pre>
<p>여기에 <code>user</code>를 넣고 항상 <code>user.password</code>를 해주는거다.</p>
<p><code>user</code>를 찾아서 <code>DB</code>에 있는 가장 최근의 비밀번호를 사용하는거다. </p>
<p><code>session</code>에서 <code>password</code>를 가져오지 않아도 된다. 똑같이 작동한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Change Password #01]]></title>
            <link>https://velog.io/@0_cyberlover_0/Change-Password-01</link>
            <guid>https://velog.io/@0_cyberlover_0/Change-Password-01</guid>
            <pubDate>Wed, 27 Apr 2022 01:43:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>비밀번호 변경으로 넘어가 본다.</p>
</blockquote>
<p>그 페이지도 만들어 보고 <code>user profile</code>을 깔끔하게 만들어 본다.</p>
<p>이 다음에는 아바타를 변경하는것도 해본다. </p>
<p><code>edit-profile.pug</code>에서 </p>
<pre><code class="language-js">        hr
        a(href=&quot;change-password&quot;) Change Password &amp;rarr;</code></pre>
<p><code>change-password</code>로 가는 링크를 추가해주었다. </p>
<p>보다시피 <code>URL</code>이 아니라 상대 <code>URL</code>을 사용하고 있다. 이렇게 해주면 브라우저가 (<code>/users</code>)부터가 아니라 (<code>/edit</code>)만 바꿔준다. </p>
<p><code>userController.js</code>로 가서 </p>
<pre><code class="language-js">export const getChangePassword = (req, res) =&gt; {
  return res.render(&quot;change-password&quot;, { pageTitle: &quot;Change Password&quot; });
};
export const postChangePassword = (req, res) =&gt; {
  // send notification
  return res.redirect(&quot;/&quot;);
};</code></pre>
<p><code>getChangePassword</code>는 <code>&quot;change-password&quot;</code>를 <code>render</code>하도록 한다.</p>
<p><code>pageTitle</code>은 <code>Change Password</code>이다. </p>
<blockquote>
<p><code>pageTitle</code>을 입력한 이유는 나중에 <code>change-password</code>를 <code>base template</code>으로 부터 <code>extend</code>하는데 <code>pageTitle</code>이 필요하기 때문이다.</p>
</blockquote>
<p><code>postChangePassword</code>에서는 홈으로 <code>redirect</code>를 해준다.</p>
<p>나중에 비밀번호 바꿨다고 알려주는 메세지도 보낼거다.</p>
<p><code>getChangePassword</code>도 있고 <code>postChangePassword</code>도 있다.</p>
<p>이제 <code>userRouter</code>로 가서 <code>change-password url</code>을 만들어 준다.</p>
<pre><code class="language-js">userRouter.get(&quot;/logout&quot;, protectorMiddleware, logout);
userRouter.route(&quot;/edit&quot;).all(protectorMiddleware).get(getEdit).post(postEdit);
userRouter
  .route(&quot;/change-password&quot;)
  .all(protectorMiddleware)
  .get(getChangePassword)
  .post(postChangePassword);</code></pre>
<p>테스트해서 확인해 보면 에러가 뜬다. 콘솔에서 확인해 본다.</p>
<pre><code>Error: Failed to lookup view &quot;change-password&quot; in views directory</code></pre><p>&quot;<code>view</code>를 찾는데 실패 하였다&quot;고 한다. </p>
<p><code>view</code>로 가서 <code>change-password.pug</code>를 만들어 준다.</p>
<p>그리고 <code>views</code>폴더에 파일들이 많아졌다. <code>users</code>폴더를 하나 만들어줘서 넣어주었다.</p>
<p>앞으로 많은 <code>templates</code>가 만들어 질텐데 <code>user</code>폴더 <code>video</code>폴더를 만들어서 정리하면 도움이 될거다.</p>
<pre><code class="language-js">span Change Password</code></pre>
<p>이렇게 해주고 테스트해보면 아직 안되고 있다. </p>
<p><code>userController.js</code>에서 </p>
<pre><code class="language-js">export const getChangePassword = (req, res) =&gt; {
  return res.render(&quot;users/change-password&quot;, { pageTitle: &quot;Change Password&quot; });</code></pre>
<p><code>URL</code>을 변경해주면 작동한다. 이렇게 <code>view</code>안 폴더에 <code>template</code>을 넣으면 된다.</p>
<p>물론 이게 몇몇 변화를 일으킨다. 예를 들면 여태 해왔던 것처럼 <code>extends base</code>를 하고 작동시켜 본다.</p>
<pre><code class="language-js">extends base

block content 
    span Change Password</code></pre>
<p>이렇게 해주고 테스트를 해보면 작동하지 않는다.에러를 살펴보면 <code>users</code>안에 존재하지 않는 <code>base</code>를 찾고 있기 때문에 안되는거다.</p>
<p>이 디렉토리를 벗어나기 위해 <code>../</code>을 추가해 준다. 그러면 작동한다.</p>
<pre><code class="language-js">extends ../base

block content 
    span Change Password</code></pre>
<p><code>template</code>들을 폴더에 넣기로 했다면 파일 시스템을 따라야 한다는 걸 기억해야 한다.</p>
<p>이 경우에는 <code>change-password</code>가 <code>users</code>폴더 안에 있으니까 거기서 나와야 한다.</p>
<p><code>base.pug</code>를 사용 할수 있도록 했다.</p>
<p><code>change-password</code>에는 <code>form</code>이 필요하다. </p>
<pre><code class="language-js">extends ../base

block content 
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Old Password&quot;,name=&quot;password&quot;, type=&quot;password&quot;, requeired)
        input(placeholder=&quot;New Password&quot;,name=&quot;password&quot;, type=&quot;password&quot;, requeired)
        input(placeholder=&quot;New Confirm Password&quot;,name=&quot;password2&quot;, type=&quot;password&quot;, requeired)
        input(type=&quot;submit&quot;, value=&quot;Change Password&quot;)
</code></pre>
<p>한가지 문제점이 존재한다. 깃허브를 통해 계정을 만든 경우에는 비밀번호 변경을 볼수 없어야 한다.</p>
<p>깃허브 계정을 사용했다면 비밀번호가 필요 없기에 그렇다. 그러니 비밀번호를 바꿀 필요가 없다.</p>
<p>두가지 옵션이 있다.</p>
<p>하나는 <code>userController</code>의 <code>getChangePassword</code>에서 로그인된 사용자의 정보를 확인하는거다.</p>
<pre><code class="language-js">export const getChangePassword = (req, res) =&gt; {
  if (req.session.user.socialOnly === true) {
    return res.redirect(&quot;/&quot;);
  }
  return res.render(&quot;users/change-password&quot;, { pageTitle: &quot;Change Password&quot; });
};</code></pre>
<p>이게 첫번째 방법이다.</p>
<p>다른방법은 <code>form</code>을 볼수는 있지만 사용할수 없게 하는거다. 비밀번호가 있어야만 변경가능하다고 하는거다. </p>
<p>새로고침을 하면 잘 작동하고 있다. 깃허브로 로그인했을때 <code>change password</code>가 숨겨지면 더 좋을것 같다.</p>
<p><code>edit-profile view</code>로 가서</p>
<pre><code class="language-js">block content
    if errorMessage
        span=errorMessage
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Name&quot;,name=&quot;name&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.name)
        input(placeholder=&quot;Email&quot;,name=&quot;email&quot;, type=&quot;email&quot;, requeired, value=loggedInUser.email)
        input(placeholder=&quot;Username&quot;,name=&quot;username&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.username)
        input(placeholder=&quot;Location&quot;,name=&quot;location&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.location)
        input(type=&quot;submit&quot;, value=&quot;Update Profile&quot;)
        if !loggedInUser.socialOnly 
            hr
            a(href=&quot;change-password&quot;) Change Password &amp;rarr;</code></pre>
<p><code>loggedInUser</code>는 모든 <code>pug</code>에서 사용 가능한 <code>local</code>에서 가져오는 변수이다.</p>
<p>그래서 <code>loggedInUser</code>을 이용 할수 있는거다.</p>
<p><code>if !loggedInUser.socialOnly</code>이면 (<code>socialOnly</code>)가 아니라면 링크가 안보이게 해주었다.</p>
<p>새로고침을 하면 링크가 보이지 않게 되었다.  이게 첫 번째 방법이였다.</p>
<p>다른 방법은 링크와 <code>form</code>을 보여주지만 버튼을 없애 줄거다.</p>
<p><code>DB</code>에 있는 데이터들을 다 지워야 한다. 깃허브가 아니라 <code>form</code>으로 생성된 계정이 필요하기에 그렇다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Edit Profile POST #02]]></title>
            <link>https://velog.io/@0_cyberlover_0/Edit-Profile-POST-02</link>
            <guid>https://velog.io/@0_cyberlover_0/Edit-Profile-POST-02</guid>
            <pubDate>Tue, 26 Apr 2022 07:54:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>DB</code>에서의 업데이트가 프론트엔드에 반영되지 않았다.</p>
</blockquote>
<p>프론트엔드는 <code>session</code>으로부터 정보를 얻기 때문이다. <code>session</code>은 로그인 할때 한번만 작성되고 있다. </p>
<p>로그인 하고 나서는 <code>session</code>이 아무것도 안하기 때문이다. 로그인 했을때의 <code>user</code>가 그대로 남아 있는거다.</p>
<p>그래서 <code>DB</code>에 있는 <code>user</code>정보를 업데이트해도 <code>session</code>은 업데이트 되지 않는다.</p>
<p>이제 <code>session</code>을 업데이트 해주면 된다.</p>
<p>두가지 옵션이 있다. </p>
<p>하나는 직접 하는거 <code>req.session.user</code>를 해주고 여기에 정보를 넣어준다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">await User.findByIdAndUpdate(_id, {
    name,
    email,
    username,
    location,
  });
  req.session.user ={
    name,
    email,
    username,
    location,
  }
  return res.render(&quot;edit-profile&quot;);
};</code></pre>
<p>이 방법도 괜찮긴 하지만 별로 보기 좋지 않다. 그리고 나머지도 추가해 줘야한다.</p>
<p>이거 말고도 <code>avatarUrl</code>,<code>socialOnly</code>등 더 있다. 이 정보들도 <code>session</code>에 있다. </p>
<p>그래서 <code>req.session.user</code>가 <code>form</code>의 <code>value</code>들이랑 같고 나머지는 기존의 것과 같다고 할거다. </p>
<pre><code class="language-js">    ...req.session.user,</code></pre>
<p>무엇을 나타내냐면 <code>req.session.user</code>안에 있는 내용을 전해주는 거다.</p>
<p><code>req.session.user</code>안의 내용을 밖으로 꺼내주는 거다.</p>
<p>이걸 첫줄로 옮겨야 한다. <code>...req.session.user,</code>에 있는 <code>email</code>,<code>username</code>을 덮어 쓰기에 그렇다.</p>
<p>업데이트를 해주는 거다. 새로고침해서 테스트 해보면 업데이트가 되는걸 확인 할수 있다.</p>
<pre><code class="language-js">req.session.user = {
    ...req.session.user,
    name,
    email,
    username,
    location,
  };
  return res.redirect(&quot;/users/edit&quot;);</code></pre>
<p>이렇게 수정해 준다. <code>DB</code>에 있는 <code>session</code>도 업데이트 해준다.</p>
<blockquote>
<p><code>session</code>은 <code>DB</code>에 저장돼 있다는걸 기억한다.</p>
</blockquote>
<p><code>DB</code>랑 <code>req.session</code>을 똑같이 유지 할수 있는거다.</p>
<p>그리고 직접 하는 방법 말고 다른 방법도 있다.</p>
<blockquote>
<p><code>updatedUser</code>를 만드는 거다.</p>
</blockquote>
<pre><code class="language-js">const updatedUser = await User.findByIdAndUpdate(
    _id,
    {
      name,
      email,
      username,
      location,
    },
    { new: true }
  );
  req.session.user = updatedUser;
  return res.redirect(&quot;/users/edit&quot;);
};</code></pre>
<p><code>finByIdAndUpdate</code>가 <code>updatedUser</code>를 주기 때문에 <code>req.session.user = updatedUser</code>를 해주면 된다.</p>
<p>그리고 이것만으로는 새로운 <code>updatedUser</code>가 만들어 지지 않는다.</p>
<p><code>option</code>을 보면 보낼수 있는 <code>object</code>가 하나 있는데 </p>
<p><a href="https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdate">https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdate</a></p>
<p><code>Boolean</code>값을 가진 <code>new</code>이다. 기본적으로 <code>findByIdAndUpdate</code>는 <code>update</code>되기전의 데이터를 <code>return</code>해주고</p>
<p><code>new:true</code>를 설정해주면 <code>findByIdAndUpdate</code>가 업데이트 된 데이터를 <code>return</code>해준다.</p>
<p>무슨 뜻이냐면 <code>mongoose</code>한테 가장 최근 업데이트 된 <code>object</code>를 원한다고 말하는 거다.</p>
<p>그 이전 데이터는 필요 없고 말이다. 그래서 <code>{ new:true}</code>를 추가해 주었다.</p>
<p><code>findByIdAndUpdate</code>에 세개의 <code>argument</code>를 주고 있다.</p>
<p>첫번째는 업데이트 하려는 <code>user</code>의 <code>id</code> 이고 두번째는 업데이트하려는 정보(<code>object</code>)이고</p>
<p>세번째는 <code>option</code>이다. <code>{ new:true}</code> 바로 이거다.</p>
<p>일일이 업데이트 하는것보다 훨씬 나아 보인다. 다시 테스트 하면 여전히 업데이트는 잘 되고 있다. </p>
<blockquote>
<p>하지만 문제가 있다. 만약 <code>username</code>을 바꾸려는데 이미 있는 <code>username</code>이거나 
<code>email</code>을 바꾸려는데 이미 있는 <code>email</code>이면 어떻게 해주는게 좋을까</p>
</blockquote>
<p><code>user</code>을 생성 할때 이미 해봤었다. 그걸 활용해서 해본다.</p>
<pre><code class="language-js">  const exists = await User.exists({ $or: [{ username }, { email }] });</code></pre>
<p>이미 있는 <code>username</code>이나 <code>email</code>이면 업데이트 할수 없게 해줘야 한다.</p>
<p>사용자가 <code>username</code>이나 <code>email</code>을 업데이트하려는걸 어떻게 알수 있냐면</p>
<p><code>body</code>는 <code>form</code>으로부터 정보를 받아온다. <code>form</code>의 정보는 현재 사용자의 정보이고</p>
<p>그래서 <code>form</code>의 정보가 <code>session</code>의 <code>user</code>정보와 같은지 확인해야 될거다.</p>
<p>그러면 사용자가 <code>username</code>이나 <code>email</code>을 바꾸려 한다는 걸 알수 있다.</p>
<p><code>join</code>에서 했던 <code>exists</code>방식을 사용하면 항상 <code>true</code>가 될거다. </p>
<p>왜냐하면 <code>postEdit</code>에서 사용한 <code>username</code>이랑 <code>email</code>은 현재 <code>session</code>에 있는 사용자의 것이기 때문이다.</p>
<p>그래서 사용자가 이 내용을 변경했는지 알아낼수 있어야 한다.</p>
<p>한가지 방법은 <code>body</code>의 <code>username</code>,<code>email</code>이 <code>session.user</code>에 있는</p>
<p><code>username</code>,<code>email</code>이랑 다른지 확인해 보는거다.</p>
<p>다르다면 사용자가 <code>username</code>이나 <code>email</code>을 변경하고 싶다는거다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
  } = req;
  const exists = await User.exists({
    _id: { $ne: { _id } },
    $or: [{ username }, { email }],
  });

  if (exists) {
    return res.status(400).render(&quot;edit-profile&quot;, {
      pageTitle: &quot;Edit Profile&quot;,
      errorMessage: &quot;This username/email is already taken.&quot;,
    });
  }</code></pre>
<p>  <code>edit-profile.pug</code>에서</p>
<pre><code class="language-js">block content
    if errorMessage
        span=errorMessage</code></pre>
<p>이렇게 해주면 에러 메세지까지 <code>join</code>페이지 처럼 똑같지 구현하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Edit Profile POST #01]]></title>
            <link>https://velog.io/@0_cyberlover_0/Edit-Profile-POST-01</link>
            <guid>https://velog.io/@0_cyberlover_0/Edit-Profile-POST-01</guid>
            <pubDate>Tue, 26 Apr 2022 03:41:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>middleware를 videoController에 추가 해본다.</p>
</blockquote>
<p>그래서 <code>videoRouter</code>의 <code>/edit</code>,<code>/delete</code>, <code>/upload</code>를 보호 할거다.</p>
<p>로그인 돼 있어야만 접근할수 있게 만든다.</p>
<p>이렇게 하는 이유는 나중에 <code>video model</code>과 <code>user mogel</code>을 연결하기 때문이다.</p>
<p>지금은 <code>model</code>들이 나눠져 있다.  현재 <code>video</code>는 아직 <code>user</code>랑 아무 접점이 없다. </p>
<p>나중에 <code>video</code>가 어떤 <code>user</code>에 의해 업로드 되는지 알아 보겠다.</p>
<p><code>video</code>의 소유자를 설정하는 거다.</p>
<p>소유자를 설정하면 <code>videoController</code>에서 몇가지를 수정해야 한다. </p>
<p>예를 들어 소유자가 아니면 <code>video</code>를 수정할 수 없게 하거나 삭제 할수 없게 하는거다. </p>
<p><code>video</code>에 소유자를 설정하기 위해 웹사이트에 계정이 있고, 로그인돼 있는 사람만</p>
<p><code>video</code>를 업ㄹ로드 할수 있게 한다.</p>
<p>그래서 <code>route</code>( <code>/edit</code>,<code>/delete</code>, <code>/upload</code>)를 보호 할거다.</p>
<pre><code class="language-js">videoRouter.get(&quot;/:id([0-9a-f]{24})&quot;, watch);
videoRouter.route(&quot;/:id([0-9a-f]{24})/edit&quot;).get(getEdit).post(postEdit);
videoRouter.route(&quot;/:id([0-9a-f]{24})/delete&quot;).get(deleteVideo);
videoRouter.route(&quot;/upload&quot;).get(getUpload).post(postUpload);</code></pre>
<p>모두 <code>get</code>,<code>post</code>를 포함하고 있다. 그래서 <code>all()</code>을 써주면 된다.</p>
<pre><code class="language-js">videoRouter.get(&quot;/:id([0-9a-f]{24})&quot;, watch);
videoRouter
  .route(&quot;/:id([0-9a-f]{24})/edit&quot;)
  .all(protectorMiddleware)
  .get(getEdit)
  .post(postEdit);
videoRouter
  .route(&quot;/:id([0-9a-f]{24})/delete&quot;)
  .all(protectorMiddleware)
  .get(deleteVideo);
videoRouter
  .route(&quot;/upload&quot;)
  .all(protectorMiddleware)
  .get(getUpload)
  .post(postUpload);</code></pre>
<p>그리고 <code>prtectorMiddleware</code>를 <code>import</code>를 해줘야 한다.</p>
<blockquote>
<p>파일이 열려있고 열어 놓은 파일의 함수를 작성하면 <code>VSC</code>가 <code>import</code>를 도와 준다.</p>
</blockquote>
<p><code>videoRouter</code>의 <code>/edit</code>이 보호 되있고, <code>/delete</code>도 보호 돼있고, <code>/upload</code>도 보호 된다.</p>
<blockquote>
<p>이제 <code>Edit Profile</code>을 만들어 준다. </p>
</blockquote>
<p>그리고 <code>Upload Video</code>는 로그인 된 사람들만 볼수 있는거니까 바꿔줘야 한다.</p>
<p><code>base.pug</code>에서 </p>
<pre><code class="language-js">if loggedIn
   li 
      a(href=&quot;/videos/upload&quot;) Upload Video
</code></pre>
<p><code>Upload Video</code>를 <code>loggedIn</code>안으로 옮겨 준다. </p>
<p><code>edit-profile.pug</code>를 보면</p>
<pre><code class="language-js">extends base

block content
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Name&quot;,name=&quot;name&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.name)
        input(placeholder=&quot;Email&quot;,name=&quot;email&quot;, type=&quot;email&quot;, requeired, value=loggedInUser.email)
        input(placeholder=&quot;Username&quot;,name=&quot;username&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.username)
        input(placeholder=&quot;Location&quot;,name=&quot;location&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.location)
        input(type=&quot;submit&quot;, value=&quot;Update Profile&quot;)
</code></pre>
<p>모두 <code>name</code>을 가지고 있다. <code>user</code>가 <code>post request</code>를 보내면 <code>body</code>를 받을수 있다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">export const postEdit = (req, res) =&gt; {
  const {} = req.body;</code></pre>
<p><code>form</code>으로 부터 받는걸 알고 있다. 다음으로 <code>body</code>로부터 모든걸 받아서 넣어주는거다.</p>
<pre><code class="language-js">  const { name, email, username, location } = req.body;</code></pre>
<p>그 다음 <code>mongo</code>를 사용해 주면 된다. <code>user</code>를 찾아서 업데이트 해줘야 한다.</p>
<p><code>user model</code>이 있는걸 확인하고 <code>async</code>를 추가해 준다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { id },
    },
    body: { name, email, username, location },
  } = req;
  await User.findByIdAndUpdate(id, {
    name,
    email,
    username,
    location,
  });
  return res.render(&quot;edit-profile&quot;);
};</code></pre>
<p>그리고 <code>await User.</code>를 쓴다. <code>User.</code>으로 할수 있는게 많다. </p>
<p>예를 들면 <code>findById</code>도 있고 현재는 <code>findByIdAndUpdate</code>를 사용한다.</p>
<p>먼저 업데이트하려는 <code>id</code>를 보내줘야 한다. 현재 로그인된 <code>user</code>의 <code>id</code>를 얻기 위해서</p>
<p><code>request object</code>를 사용 해야 한다.</p>
<p><code>request object</code>의 <code>req.session.user</code>가 있다. </p>
<blockquote>
<p><code>user object</code>를 <code>session</code>에 있다는걸 기억하자.</p>
</blockquote>
<p>그래서 <code>req.session</code>에 있는 <code>user object</code>에서 <code>id</code>를 찾을수 있다.</p>
<p>좀더 보기 좋게 하기 위해서 <code>session</code>안에서 <code>user</code>를 찾고 <code>id</code>를 꺼내올수 있다.</p>
<p><code>const id = req.session.user.id</code>와 같은 거다.</p>
<p>이 방법이 좋은 이유는 혼합하기 좋기 때문이다. </p>
<p>예를 들어 이 방법을 사용하면 <code>session</code>안에 있는 <code>user</code>의 <code>id</code>를 찾을수 있다.</p>
<p><code>req.body</code>안에 있는 <code>name</code>,<code>email</code>,<code>username</code>,<code>location</code> 정보도 가져 올수 있다.</p>
<p>그래서 <code>const id = req.session.user.id</code>와 <code>const { name, email, username, location } = req.body;</code> 를 없애 줄수 있다.</p>
<p>훨씬 보기 좋다. 이게 바로 <code>ES6</code>이다. </p>
<p><code>req</code>안에 있는 <code>session</code>에서 <code>user</code>의 <code>id</code>를 얻었다.</p>
<p>또 <code>req</code>안에 <code>body</code>에 있는 <code>name</code>,<code>email</code>,<code>username</code>,<code>location</code>을 얻었다.</p>
<p>그리고 <code>User.findByIdAndUpdate()</code>에다가 <code>req.session.user</code>안에 있는 <code>id</code>를 넣어 준다.</p>
<p>업데이트 하기 위해서 <code>name</code>,<code>email</code>,<code>username</code>,<code>location</code>을 넣어준다.</p>
<blockquote>
<p>이 변수들은 <code>form</code>에서 온다는걸 기억한다.</p>
</blockquote>
<p><code>input</code>에 <code>name</code>을 입력해 주지 않으면 안된다. </p>
<p><code>form</code>으로부터 <code>name</code>,<code>email</code>,<code>username</code>,<code>location</code>을 받았다.</p>
<p>이 정보는 <code>req.body</code>에서 찾을 수 있다. 그리고 로그인된 <code>user</code>의 정보를 업데이트 하고 싶다.</p>
<p>보다시피 <code>user</code>가 누구냐고 묻지 않는다. <code>url</code>로 부터 <code>id</code>를 받고 있는게 아니다.</p>
<p><code>user id</code>를 가지고 있는 <code>session</code>에서 받는거다.</p>
<p>그래서 <code>findByIdAndUpdate</code>함수를 쓸때 첫번째 <code>argument</code>는 <code>id</code>이다.</p>
<p>두번째는 <code>UdateQuery</code>이고 <code>callback</code>을 사용 할수도 있지만 <code>await</code>를 사용 할수도 있다.</p>
<p>한번 테스트를 해보겠다. 새로고침하고 <code>email</code>을 바꿔본다. </p>
<p>업데이트가 되지 않았다. 왜 그런지 알아 본다. 아마 <code>user</code>가 <code>id</code>를 가지고 있지 않을수도 있다.</p>
<p><code>middleware</code>에서 로그인 된 유저를 <code>console.log</code>해서 확인해 본다. </p>
<pre><code class="language-js">res.locals.loggedInUser = req.session.user || {};
  console.log(req.session.user);</code></pre>
<p>새로고침하고 <code>email</code>을 수정 시도를 다시 해보고 콘솔을 확인해 보면</p>
<p><code>id</code>가 아닌 <code>_id</code>의 정보를 받고 있다. 그러면 이제 <code>_id</code>로 바꿔 주면 작동 할거다.</p>
<pre><code class="language-js">export const postEdit = async (req, res) =&gt; {
  const {
    session: {
      user: { _id },
    },
    body: { name, email, username, location },
  } = req;
  await User.findByIdAndUpdate(_id, {</code></pre>
<p>그래도 업데이트 되지 않고 있다. 콘솔에 아무 에러가 없는거 보니 코드가 작동하는건 확실하다.</p>
<p>이번에는 <code>DB</code>를 확인해 본다. <code>DB</code>를 확인해 보니 수정을 요청 한 게 저장이 되어있다.</p>
<p><code>DB</code>에서 <code>email</code>이 업데이트 되긴 했다. 문제는 페이지에서 새로고침을 해도 바뀌지 않는다는 거다.</p>
<p>홈페이지에서는 계속 이전 이메일이 나온다. <code>DB</code>에는 새로운 이메일인데 말이다.</p>
<p>생각해보면 <code>edit-profile</code>에서 <code>loggedInUser</code>의 값을 입력했다.</p>
<p>그러면 <code>loggedInUser</code>가 언제 생성 된건지 생각해본다.</p>
<p><code>loggedInUser</code>는 <code>localsMiddleware</code>에서 생성되었다.</p>
<p><code>middleware.js</code>에서</p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {</code></pre>
<p>여기서 <code>loggedInUser</code>를 <code>req.session.user</code>이라고 정의 하고 있다.</p>
<p>그러면 <code>req.session.user</code>는 어디서 생성 되는지 알아본다.</p>
<p>바로 로그인 할때이다. 로그인 하는곳으로 가 본다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">req.session.loggedIn = true;
    req.session.user = user;</code></pre>
<p>여기서 로그인할때 <code>user</code>를 <code>req.session.user</code>에 입력해 준다.</p>
<p>그래서 <code>user</code>는 업데이트 했는데 <code>session</code>이 업데이트 되지 않는다.</p>
<p><code>DB</code>에서는 <code>user</code>를 업데이트했는데 <code>session</code>은 <code>DB</code>와 연결 돼 있지 않다.</p>
<p><code>session</code>에 <code>user object</code>를 넣었는데 <code>user object</code>를 업데이트 했다면</p>
<p><code>session</code>도 업데이트 해줘야 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Protector and Public Middlewares ]]></title>
            <link>https://velog.io/@0_cyberlover_0/Protector-and-Public-Middlewares</link>
            <guid>https://velog.io/@0_cyberlover_0/Protector-and-Public-Middlewares</guid>
            <pubDate>Mon, 25 Apr 2022 10:18:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>현재 원하는건 로그인 하지 않은 사람들이 해당 페이지에 가는걸 막는 일이다.</p>
</blockquote>
<p> 그걸 위해서  <code>Protector Middlewares</code> 를 만드는 거다.</p>
<p> <code>middlewares.js</code>에서 </p>
<pre><code class="language-js">export const proectorMiddleware = (req, res, next) =&gt; {
  if (req.session.loggedIn) {
    return next();
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};

export const publicOnlyMiddleware = (req, res, next) =&gt; {
  if (!req.session.loggedIn) {
    return next();
  } else {
    return res.redirect(&quot;/&quot;);
  }
};</code></pre>
<p><code>middleware</code>뿐만 아니라 모든 것에 <code>req</code>,<code>res</code>,<code>next</code>가 포함되어 있다는 걸 명심한다.</p>
<p>그리고 사용자가 로그인 돼 있지 않은걸 확인하면 로그인 페이지로 <code>redirect</code>하게 할거다.</p>
<p>사용자가 로그인 돼 있다면 <code>request</code>를 계속 하도록 하는 거다.</p>
<p><code>controller</code>에서 <code>user</code>를 찾게 하는 방법은 <code>req.session.loggedIn</code>이다.</p>
<p>어느 <code>controller</code>나 <code>middleware</code>에서도 사용 할수 있다.</p>
<p><code>user</code>가 <code>loggedIn</code>이 되어 있으면 <code>next()</code>함수를 호출 하는거다.</p>
<p>그리고 유저가 <code>loggedIn</code>이 아니라면 로그인 페이지로 <code>redirect</code>하게 된다.</p>
<p>그 다음 로그인 돼 있지 않은 사람들만 접근 할수 있게 하는 <code>middleware</code>도 만든다.</p>
<p>예를 들어 로그인했는데 다시 로그인 페이지로 갈수 있으면 안되는 거다.</p>
<blockquote>
<p>방금 전이랑 반대로 만들어 준다. </p>
</blockquote>
<p>그리고 <code>loggedIn</code>이 돼 있다면 <code>/</code>로 <code>res.redirect</code>로 해준다.</p>
<p>현재 <code>middleware</code>에서는 <code>user</code>가 <code>loggedIn</code>돼 있다면 요청을 계속 하게 하고</p>
<p><code>loggedIn</code>돼 있지 않으면 로그인 페이지로 <code>redirect</code>해준다.</p>
<p>다른 <code>middleware</code>는 <code>user</code>가 <code>loggedIn</code>돼 있지 않으면 요청을 계속하게 하고</p>
<p><code>loggedIn</code>돼 있으면 <code>&quot;/&quot;</code>으로 <code>redirect</code>해준다.</p>
<p>그러면 이 <code>middleware</code>들을 사용해 보도록 한다.</p>
<p><code>userRouter.js</code>에서 </p>
<pre><code class="language-js">userRouter.get(&quot;/logout&quot;, protectorMiddleware, logout);
userRouter
  .route(&quot;/edit&quot;)
  .get(protectorMiddleware, getEdit)
  .post(protectorMiddleware, postEdit);</code></pre>
<p>로그인 돼 있는 사람들만 로그아웃 페이지로 갈수 있어야 한다.</p>
<p>그래서 <code>protectorMiddleware</code>를 추가 할거다.</p>
<p><code>/edit</code> 에도 <code>protectorMiddleware</code>를 적용해 준다.</p>
<p><code>middleware</code>를 <code>get</code>이랑 <code>post</code> 둘 다 사용 할수 있게 되었다.</p>
<p>하지만 <code>express</code>에서는 반복해서 사용 할 필요 없이 <code>all()</code>이라는 함수를 사용 하면 된다.</p>
<pre><code class="language-js">userRouter.route(&quot;/edit&quot;).all(protectorMiddleware).get(getEdit).post(postEdit);</code></pre>
<p>이렇게 해주면 <code>get</code>,<code>post</code> 등 어떤 <code>http method</code>를 사용 하든지 <code>middleware</code>를 사용 한다는 거다.</p>
<p>이제 <code>startGithubLogin</code>,<code>finishGithubLogin</code>을 싫행시키고 싶다면</p>
<blockquote>
<p><code>publicOnlyMiddleware</code>를 적용하면 된다.</p>
</blockquote>
<p><code>publicOnly</code>는 로그아웃 돼 있어야 실행시키는걸 허락해주니까 </p>
<p>로그인 돼 있으면 여기로 올수 없게 보호해주는 거다.</p>
<pre><code class="language-js">userRouter.get(&quot;/github/start&quot;, publicOnlyMiddleware, startGithubLogin);
userRouter.get(&quot;/github/finish&quot;, publicOnlyMiddleware, finishGithubLogin);</code></pre>
<p>그리고 이건 나중에 <code>user</code>를 볼수 있게 해줄거다.</p>
<pre><code class="language-js">userRouter.get(&quot;:id&quot;, see);</code></pre>
<p>이건 <code>public</code>도 될수 있고 <code>private</code>도 될수 있을것 같다. 로그인 여부가 상관없는 거다.</p>
<p>그리고 <code>import</code>도 해준다.</p>
<pre><code class="language-js">import { protectorMiddleware, publicOnlyMiddleware } from &quot;../middlewares&quot;;</code></pre>
<p>그리고 이제 새로고침을 해보면 잘 작동 한다.</p>
<p>로그인 된 상태에서 로그인 페이지로 갈수가 있다 이것도 보호 해주도록 한다.</p>
<p>그리고 <code>/github/start</code>로 가본다. 홈페이로 돌아가진다. 잘 작동한다.</p>
<p><code>rootRouer.js</code>에 가서 </p>
<pre><code class="language-js">rootRouter.route(&quot;/join&quot;).all(publicOnlyMiddleware).get(getJoin).post(postJoin);
rootRouter
  .route(&quot;/login&quot;)
  .all(publicOnlyMiddleware)
  .get(getLogin)
  .post(postLogin);</code></pre>
<p><code>/join</code>,<code>/login</code>에는 로그아웃 된 사람만 갈수 있다.</p>
<p>그래서 두곳에 <code>all()</code>함수를 적용 해주었다. 새로고침 해보니 로그인 상태에서 로그인 페이지로 갈수 없다.</p>
<p>당연히 <code>join</code>페이지도 못 간다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Edit Profile GET]]></title>
            <link>https://velog.io/@0_cyberlover_0/Edit-Profile-GET</link>
            <guid>https://velog.io/@0_cyberlover_0/Edit-Profile-GET</guid>
            <pubDate>Mon, 25 Apr 2022 07:54:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Edit Profile 페이지를 만들어 본다.</p>
</blockquote>
<p>이 페이지를 만들면서 많은 걸 배우게 될거다. 프로필을 바꾸고, <code>form</code>을 사용하는 연습은 물론이고</p>
<p>유저한테 있었으면 하는 아바타 같은 파일도 업로드 해볼거다.</p>
<p>지금은 깃허브로 로그인 해야지만 아바타가 있다.</p>
<p>그래서 웹사이트에서 생성한 계정에도 아바타를 업로드 할수 있게 할거다.</p>
<p>그리고 <code>Github</code>로 계정을 만들어도 아바타를 바꿀수 있도로고 해본다.</p>
<p>파일을 바꾸는 방법도 알아봐야 겠다. 모두 파일에 관련된 거다.</p>
<p>생각해보면 비디오에도 파일이 필요 하다. 현재는 <code>uploadVideo</code>에는 <code>title</code>, <code>dscription</code>, <code>hashtags</code> 밖에 없다.</p>
<p>그래서 <code>video file</code>을 추가 할거다. </p>
<p>지금부터 할것들은 파일 작업을 하기전에 <code>form</code>, <code>url</code>, <code>controller</code>를 연습하는거다.</p>
<p><code>router</code>, <code>controller</code>, <code>template</code>, <code>form</code>을 이해했으면 파일을 업로드 하는 부분도 이해하기 쉬울거다.</p>
<p>알다시피 <code>userRouter</code>에 가보면 <code>/edit</code>이 있다.</p>
<pre><code class="language-js">userRouter.get(&quot;/edit&quot;, edit);</code></pre>
<p><code>/edit</code>이 있고 <code>controller(edit)</code>도 있다. 그리고 <code>get</code>과 <code>post</code> 두개의 <code>method</code>가 필요하다.</p>
<p>그래서 이걸 <code>export const getEdit</code>으로 하고</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">export const getEdit = (req, res) =&gt; {
  return res.render(&quot;edit-profile&quot;, { pageTitle: &quot;Edit Profile&quot; });
};</code></pre>
<p><code>pageTitle</code>도 필요 해서 넣었다. 나중에 <code>form</code>으로 <code>data</code>를 보낼거다.</p>
<p>지금은 이 <code>controller</code>를 사용해 본다. 그리고 <code>getEdit</code>을 <code>import</code>한다.</p>
<p><code>userRouter.js</code>에서 </p>
<pre><code class="language-js">import express from &quot;express&quot;;
import {
  getEdit,
  logout,
  see,
  startGithubLogin,
  finishGithubLogin,
} from &quot;../controllers/userController&quot;;
userRouter.get(&quot;/edit&quot;, getEdit);</code></pre>
<p>그리고 <code>edit</code>을 <code>getEdit</code>으로 바꿔 준다. </p>
<p><code>post</code>도 필요하다. </p>
<pre><code class="language-js">export const postEdit = (req, res) =&gt; {
  return res.render(&quot;edit-profile&quot;);
};</code></pre>
<p><code>render</code>가 아니라 <code>redirect</code>하게 될지도 모른다. 일단 이렇게 하고 </p>
<p><code>edit</code>을 삭제 해 준다. 이제 <code>getEdit</code>,<code>postEdit</code>이 생겼다.</p>
<p>이제 <code>postEdit</code>을 <code>import</code>해준다. </p>
<pre><code class="language-js">import express from &quot;express&quot;;
import {
  getEdit,
  postEdit,
  logout,
  see,
  startGithubLogin,
  finishGithubLogin,
} from &quot;../controllers/userController&quot;;

userRouter.route(&quot;/edit&quot;).get(getEdit).post(postEdit);</code></pre>
<p>그리고 <code>route</code>를 이용하여 이렇게 코드를 해준다. </p>
<blockquote>
<p>다음 단계는 <code>/edit url</code>에 접근 할수 있어야 한다.</p>
</blockquote>
<p>그래서 <code>template</code>를 바꿔 볼거다. <code>template</code>의 <code>header</code>에서 링크를 사용하고 있다.</p>
<p><code>views</code>로 가서 <code>base.pug</code>에서 </p>
<pre><code class="language-js">body 
        header
            h1=pageTitle
            nav 
                ul 
                    li  
                        a(href=&quot;/&quot;) Home
                    if loggedIn
                        li
                            a(href=&quot;/users/edit&quot;) Edit Profile</code></pre>
<p>사용자가 로그인 돼 있는 경우에만 <code>editProfile</code> 링크를 볼수 있게 한다.</p>
<p><code>my profile</code>페이지는 마지막에 다시 만들어 보기로 한다. 지금은 지워 준다.</p>
<p>새로고침하면 로그인이 안되어 있기 때문에 <code>edit profile</code>, <code>logout</code>이 안보인다.</p>
<p><code>loggenIn</code>이 <code>localsMiddleware</code>에서 온 변수이기 때문이다. </p>
<p><code>middlewares.js</code>에서 </p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = &quot;Wetube&quot;;
  res.locals.loggedInUser = req.session.user;
  next();
};</code></pre>
<p>여기에 있는 <code>loggenIn</code>이 <code>session</code>에 <code>user</code>가 있는지 확인해 준다.</p>
<p>지금 현재 페이지에서는  <code>session</code>에는 <code>user</code>가 없다.</p>
<p>그래서 이런 이유로 <code>loggedIn</code>이 <code>base.pug</code>에서 쓰고 있는 거란걸 꼭 기억한다.</p>
<p>그리고 깃허브 계정으로 로그인 해본다. 해주면 <code>Edit Profile</code> 링크가 생겨 있는걸 알수 있다.</p>
<p>현재 <code>Edit Profile</code>을 클릭하면 <code>views</code>에 <code>edit-profile</code>이 없다고 에러가 생긴다.</p>
<p><code>views</code>에 가서 <code>edit-profile.pug</code>를 만들어 준다.</p>
<p>그리고 새로고침을 해보면 일단 에러가 뜨진 않는다. 물론 <code>edit-profile</code>이 텅 비어 있긴 하다.</p>
<p>그래서 모든 페이지에서 했던대로 적용해 준다. </p>
<pre><code class="language-js">extends base

block content 
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Name&quot;,name=&quot;name&quot;, type=&quot;text&quot;, requeired)
        input(placeholder=&quot;Email&quot;,name=&quot;email&quot;, type=&quot;email&quot;, requeired)
        input(placeholder=&quot;Username&quot;,name=&quot;username&quot;, type=&quot;text&quot;, requeired)
        input(placeholder=&quot;Location&quot;,name=&quot;location&quot;, type=&quot;text&quot;, requeired)
        input(type=&quot;submit&quot;, value=&quot;Update Profile</code></pre>
<p><code>base.pug</code>를 <code>extend</code>하고 <code>block content</code>를 추가한다.</p>
<p><code>base</code>만 입력해줘도 잘 작동한다. <code>input</code>으로 사용 할것을 <code>join.pug</code>에서 활용해서 쓴다.</p>
<p>비밀번호 변경 페이지는 따로 만들어 줄거다. 왜냐하면 비밀번호가 있는 사용자가 있고 </p>
<p>없는 사용자가 있어서 그렇다. 그리고 <code>submit input</code>의 <code>value=&quot;Update Profile&quot;</code></p>
<p>새로고침을 해주면 <code>Edit Profile</code>이 생겼다. 그런데 <code>Edit Profile</code>에 갔을때 </p>
<p><code>form</code>이 비어 있으면 안된다. 새로 생성하는게 아니라 수정하는 것이기 때문에 </p>
<p>현재 프로필을 볼수 있어야 한다. <code>Edit Video</code>에서 했던 것처럼 말이다.</p>
<p>그럼 이 <code>template</code>로 <code>user object</code>를 가져와 본다. </p>
<p><code>controller</code>에다가 할수도 있다. </p>
<p><code>userController.js</code>에서 </p>
<pre><code class="language-js">
export const getEdit = (req, res) =&gt; {
  return res.render(&quot;edit-profile&quot;, { pageTitle: &quot;Edit Profile&quot;, user: req.session.user });</code></pre>
<p>직접 <code>req.session.user</code>에 있는 <code>user object</code>를 줄수도 있다.</p>
<p>이런식으로 유저를 로그인 시켰다. </p>
<p>하지만 <code>middleware</code>가 있다. </p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = &quot;Wetube&quot;;
  res.locals.loggedInUser = req.session.user;
  next();
};</code></pre>
<p>이 <code>middleware</code>가 현재 로그인된 사용자를 알려준다. 그래서 <code>userController</code>에 이걸 보낼 필요 조차도 없다. </p>
<p>이미 <code>locals</code>를 통해서 <code>loggedInUser</code>를 사용 할수 있다.</p>
<p>이걸 이용해서 로그인 정보를 활용 할수 있는거다.  <code>locals</code>은 자동적으로 <code>views</code>로 <code>import</code>된다.</p>
<p><code>Edit Profile</code>에서 <code>value</code>를 추가 할수 있다.</p>
<p><code>edit-profile.pug</code>에서 </p>
<pre><code class="language-js">extends base

block content
    form(method=&quot;POST&quot;)
        input(placeholder=&quot;Name&quot;,name=&quot;name&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.name)
        input(placeholder=&quot;Email&quot;,name=&quot;email&quot;, type=&quot;email&quot;, requeired, value=loggedInUser.email)
        input(placeholder=&quot;Username&quot;,name=&quot;username&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.username)
        input(placeholder=&quot;Location&quot;,name=&quot;location&quot;, type=&quot;text&quot;, requeired, value=loggedInUser.location)
        input(type=&quot;submit&quot;, value=&quot;Update Profile&quot;)</code></pre>
<p>그래서 이런식으로 활용 할수 있다. 새로고침 해서 확인해 보면 해당 정보를 보여준다.</p>
<p><code>loggedInUser</code>도 <code>user</code>인걸 기억해야 한다. 그 말은 <code>user model</code>에 있는 모든걸 <code>template</code>에서 사용 가능하다는 거다.</p>
<pre><code class="language-js">
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  avatar_url: String,
  socialOnly: { type: Boolean, default: false },
  username: { type: String, required: true, unique: true },
  password: { type: String },
  name: { type: String, required: true },
  location: String,
});</code></pre>
<p>이부분을 얘기 하는 거다.  미리 만들어 놓은 <code>localsMiddelware</code>덕분이다. </p>
<p>이미 <code>form</code>, <code>get</code>,<code>post</code>는 다 해봤었다. </p>
<p>하지만 <code>loggedInUser</code>를 <code>form</code>에 이렇게 사용해 보는건 처음이였다.</p>
<blockquote>
<p>만약 이페이지에 로그인 되지 않은 상태로 온다면 어떻게 될까?</p>
</blockquote>
<p>당연히 에러가 난다. <code>undefined</code>의 <code>property</code>중 <code>name</code>을 읽을수 없다.</p>
<pre><code>Cannot read properties of undefined (reading &#39;name&#39;)</code></pre><p>왜냐하면 <code>name property</code>를 가져오려는데 <code>loggedInUser</code>가 <code>undefined</code>라서 그렇다.</p>
<p>두가지 문제점이 있다. </p>
<p>하나는 <code>loggedInUser</code>에 접근하려는데 로그인 되어 있지 않으면 생기는 에러</p>
<p>둘째는 로그인 돼 있지 않은 사람들은 해당 <code>URL</code>로 가지 못하게 해야 한다. </p>
<p><code>URL</code>이 작동하지 않고 <code>redirect</code> 시켜줘야 한다.</p>
<p>그러면 몇몇 <code>route</code>를 보호하는 <code>middleware</code>가 필요하겠다.</p>
<p>그전에 이 에러는 <code>middleware</code>에서 쉽게 고칠수 있다.</p>
<pre><code class="language-js">export const localsMiddleware = (req, res, next) =&gt; {
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = &quot;Wetube&quot;;
  res.locals.loggedInUser = req.session.user;
  next();
};</code></pre>
<p>보다시피 <code>loggedInUser</code>는 <code>req.session.user</code>인데 이게 <code>undefined</code>일수가 있다.</p>
<p>그래서 이뒤에 <code>or(||)</code>와 빈 <code>object({})</code>를 추가해주면 된다. </p>
<pre><code class="language-js">  res.locals.loggedInUser = req.session.user || {};</code></pre>
<p>이런식으로 해주면 된다.이렇게 하면 로그인을 안해도 접근 할수는 있다.</p>
<p><code>loggedInUser</code>를 사용하려 할때 생기는 문제는 고쳤다. 문제는 누구든지 접근 할수 있다는 거다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Recap]]></title>
            <link>https://velog.io/@0_cyberlover_0/Recap-tvy5s2wb</link>
            <guid>https://velog.io/@0_cyberlover_0/Recap-tvy5s2wb</guid>
            <pubDate>Sun, 24 Apr 2022 10:13:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전까지 한 모든 것들은 <code>Github</code>로 로그인을 하기 위함이었다.</p>
</blockquote>
<p>링크를 누르면 온갖 기능들이 실행된다. </p>
<p><a href="http://localhost:4000/users/github/start">http://localhost:4000/users/github/start</a></p>
<p>알다시피 이링크는 <code>users/github/start</code>로 가고 있다.</p>
<p>그건 현재 만든 <code>userRouter.js</code>에 생성한 <code>route</code>이다.</p>
<p>이 <code>route</code>는 <code>controller</code>를 가지고 있다.</p>
<p>그리고 이 <code>controller</code>의 유일한 역할은 몇몇 <code>configuration parameter</code>를 </p>
<p>가지고 <code>URL</code>을 만드는 거다. </p>
<p>그래서 <code>baseUrl</code>과 <code>parameter</code>들을 연결 시켰다.</p>
<pre><code class="language-js">export const startGithubLogin = (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/authorize&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    allow_signup: false,
    scope: &quot;read:user user:email&quot;,
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  return res.redirect(finalUrl);
};</code></pre>
<p>그렇게 해서 <code>finalUrl</code>을 만들고 여기로 <code>user</code>를 보내주었다.</p>
<p><code>user</code>를 <code>Github</code>을 보낸거다. </p>
<p><code>URL</code>을 설정하는 이유는 <code>URL</code>이 <code>Github</code>에게 뭔가를 알려줄수 있어서다.</p>
<p>예를 들면 <code>client_id</code>같은거다. 그래야 <code>Github</code>이 어떤 어플에 로그인하는지 알수 있다.</p>
<p>회원가입도 마찬가지이다. </p>
<p>예를 들어 어플에 어떤 종류의 <code>user</code>를 허용 시킬건지 설정 할수 있다.</p>
<p>그리고 <code>scope</code>에는 <code>user</code>로 뭘 할건지 설정하면 된다.</p>
<p>원하는 어떤 <code>scope</code>도 사용 할수 있다. 하지만 해당 <code>scope</code>으로 요청해야한다.</p>
<p>페이스북이나 구글 같은 웹사이트는 아주 큰 <code>scope</code>을 요구할때 어플 검증을 받아야 한다.</p>
<p>그게 며칠씩이나 걸릴수 있기에 귀찮다. </p>
<p>반면 <code>Github</code>로는 프로필을 읽고 <code>user email</code>에 접근 하는 것외엔 없다.</p>
<p>그래서 <code>Github</code>에 <code>user</code>를 보낼거다. </p>
<p><code>Github</code>는 비밀번호, 이메일, 보안 관련된 모든 데이터를 가지고 있는데</p>
<p>이 데이터들을 공유하는데 동의를 하면 웹사이트로 되돌아 올거다.</p>
<p><code>Github</code>가 <code>/github/finish</code>라는 <code>URL</code>로 돌려 보내준다.</p>
<pre><code class="language-js">userRouter.get(&quot;/github/finish&quot;, finishGithubLogin);</code></pre>
<p>좀 정확히 말하자면 <code>localhost:4000/users/github/finish</code>이다.</p>
<p>저 <code>URL</code>을 어디에도 만들지 않았다. <code>Github.com</code> 웹사이트에서 만들었다.</p>
<p><code>URL</code>은 <code>finishGithubLogin</code>이라는 <code>function</code>으로 호출 하는거다.</p>
<p>이 <code>function</code>은 굉장히 크다. 무슨 내용 들이 있는지 살펴 보면</p>
<p>먼저 <code>user</code>가 <code>Github</code>에서 돌아오면 이런 <code>URL</code>에 <code>?code=xxx</code>가 덧붙여진 내용을 받는다.</p>
<p><code>/gihut/finish?code=XXX</code>이런 식이다.</p>
<p>이 <code>code</code>는 <code>user</code>가 승인했다고 <code>Github</code>가 알려주는 거다.</p>
<p>그리고 나서 <code>baseUrl</code>과 몇몇 <code>parameter</code>들을 받고 </p>
<p><code>parameter</code>들을 <code>URL</code>의 <code>parameter string</code>으로 바꿔준다.</p>
<pre><code class="language-js">const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  const tokenRequest = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p>그리고 다른 <code>URL</code>을 만든다. </p>
<p><code>baseUrl</code>과 <code>config</code>를 더해서 다른 <code>URL</code>을 만드는 거다.</p>
<p>그리고 그 <code>URL</code>로 무엇을 하냐면 <code>POST request</code>를 보낸다.</p>
<p>이 <code>URL</code>에는 <code>Github</code>가 준 <code>code</code>가 담겨져 있다.</p>
<p>모든 것이 올바르다면 <code>Github</code>는 <code>access_token</code>을 준다.</p>
<pre><code class="language-js">if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const apiUrl = &quot;https://api.github.com&quot;;
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();</code></pre>
<p>이 <code>access_token</code>은 <code>Github API</code>와 상호작용 할때 쓸거다.</p>
<p><code>Github API</code>는 굉장히 크다 <code>Github API</code>에 어떤 <code>method</code>를 보내더라도 응답을 보내준다.</p>
<p><code>access_token</code>만 있다면 말이다. 그리고 <code>user</code>프로필을 받기 위해 요청할수 있다.</p>
<p>그 요청은 <code>api.github.com/user</code>로 갈것이고 </p>
<p><code>access_token</code>을 보내면 <code>user</code>데이터를 받을수 있을거다.</p>
<p>이걸 짧게 쓸수 있는데 <code>await fetch</code>와 <code>await json</code>이 그거다.</p>
<p>(이전 파트에서 <code>await</code>를 쓰지 않으면 어떻게 해야 하는지 해 보았다.)</p>
<p>가끔은 <code>user</code>들이 <code>email</code>을 보여주지 않을때도 있다.</p>
<p>그렇기 때문에 <code>email API</code>에게도 요청을 보내줘야 한다.</p>
<pre><code class="language-js">const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();</code></pre>
<p>그래서 여기에도 똑같은 <code>access_token</code>으로 요청을 보내주면 <code>email arrary</code>를 준다.</p>
<p>그리고 그 <code>email arrary</code>에서 <code>primary</code>이면서 <code>verified</code>된 <code>email</code>을 찾을거다.</p>
<pre><code class="language-js">const emailObj = emailData.find(
      (email) =&gt; email.primary === true &amp;&amp; email.verified === true
    );
    if (!emailObj) {
      return res.redirect(&quot;/login&quot;);
    }
    let user = await User.findOne({ email: emailObj.email });
    if (!user) {
      user = await User.create({
        avatarUrl: userData.avatar_url,
        name: userData.name ? userData.name : userData.login,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });
    }
    req.session.loggedIn = true;
    req.session.user = user;
    return res.redirect(&quot;/&quot;);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>만일 찾지 못한다면 <code>user</code>를 로그인 페이지로 돌아가게 만들고 나중에는 <code>notification</code>을 설정해준다.</p>
<p>(그건 나중에 해보도록 한다.)</p>
<p><code>notification</code>을 설정하는 이유는 유저한테 <code>Github</code>로 로그인 했다는걸 알려주기 위해서다.</p>
<p>그런데 <code>Github</code>에 <code>verification</code>된 <code>email</code>이 없으니 믿을수 없는거다.</p>
<p>만일 <code>primary</code>이면서 <code>verified</code>된 <code>email</code>을 찾게 된다면 </p>
<p>데이터베이스에서 해당 <code>email</code>을 찾게 된다면 유저를 로그인 시킬거다.</p>
<p>만일 데이터베이스에서 <code>email</code>을 찾지 못했을 경우 그 <code>email</code>로 <code>user</code>를 만들거다.</p>
<p>해당 <code>email</code>과 <code>Github</code>에 보낸 모든 데이터를 가지고 <code>user</code>를 만든다.</p>
<p><code>socialOnly</code>가 <code>true</code>이면 <code>Github</code>로 로그인을 통해 만들어진 계정이란 뜻이 된다.</p>
<p>그렇기에 해당 사용자는 <code>password</code>가 없으므로 <code>login form</code>을 사용할수 없다.</p>
<p>원한다면 무작위 <code>password</code>를 만들어 볼수도 있다.</p>
<p><code>Github</code>가 무작위 <code>password</code>를 줄거라고 생각한다.</p>
<p><code>node_id</code>가 바로 무작위 <code>password</code>이다.</p>
<pre><code> node_id: &#39;U_kgDOBXfSMQ&#39;,</code></pre><p> 원한다면 유저가 비밀번호를 아예 모르게끔 설정 할수 있다.  빈 <code>password</code>에 <code>socialOnly</code>는 <code>true</code>로 만들었다. </p>
<p>그 이유는 <code>postLogin</code>에서 </p>
<pre><code class="language-js">export const postLogin = async (req, res) =&gt; {
  const { username, password } = req.body;
  const pageTitle = &quot;Login&quot;;
  const user = await User.findOne({ username, socialOnly: false });
  if (!user) {
    return res.status(400).render(&quot;login&quot;, {
      pageTitle,
      errorMessage: &quot;An account with this username does not exists.&quot;,
    });
  }</code></pre>
<p><code>user</code>가 로그인 하는걸 체크할때 <code>socialOnly</code>가 <code>false</code>인걸 확인하려는 거다.</p>
<p>왜냐하면 <code>socialOnly</code>가 <code>false</code>이면 <code>username</code>과 <code>password</code>로만 로그인 할수 있는 유저이고</p>
<p><code>socialOnly</code>가 <code>true</code>라는건 <code>password</code>가 없다는 소리이다.</p>
<p>중요한 건 여기에서 <code>username</code> <code>email</code> 같은 걸 전부 쓰고 있기 때문에 뭔가 하나로 통합하고 싶을거다. </p>
<p>그러면 <code>username</code>필드를 제거해 본다. </p>
<p><code>username</code>과 <code>email</code>이 공존할 필요 없이 <code>email</code>만 써도 된다. </p>
<p>다음 포인트는 이것들이 전부 실행되면 쿠키가 생긴다는 거다.</p>
<pre><code class="language-js"> if (!user) {
      user = await User.create({
        avatarUrl: userData.avatar_url,
        name: userData.name ? userData.name : userData.login,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });
    }</code></pre>
<p><code>logout</code>기능도 구현했다. 정말 단순하다.</p>
<pre><code class="language-js">export const logout = (req, res) =&gt; {
  req.session.destroy();
  return res.redirect(&quot;/&quot;);
};</code></pre>
<p>그리고 <code>logoutController</code>로 <code>logout URL</code>을 만들었다.</p>
<pre><code class="language-js">userRouter.get(&quot;/logout&quot;, logout);</code></pre>
<p>이 <code>controller</code>는 <code>userController</code>로부터 온다. <code>session</code>을 <code>destroy</code>했고 그 다음 <code>redirect</code>를 했다.</p>
<p><code>user</code>가 <code>redirect</code> 되면 <code>session</code>이 없어진다. 그러면 유저가 로그아웃 된다.</p>
<p>이런 흐름은 어디에서든 적용이 된다. 물론 페이스북이나 구글 같은 곳은 좀 더 복잡할거다.</p>
<p>왜냐하면 거긴 더 크니 인증 절차 거치고, 기다리고 그러기 때문이다.</p>
<p>그런데 트위터, 카카오톡, 인스타그램은 매우 비슷하다. 물론 <code>client_id</code>같은 이름이 똑같지 않을 거다.</p>
<p><code>app_id</code> 일수도 있다. 하지만 방법은 똑같은 거다.</p>
<p><code>user</code>를 소셜 웹사이트로 보내면 몇몇 <code>code</code>와 함께 웹사이트로 돌려보내고</p>
<p>그 <code>code</code>로 <code>API</code>에 <code>request</code>를 보낼수 있다. 이제 <code>user</code>인증을 끝냈다. </p>
<p>다음 파트에선 <code>user</code>프로필 부분을 할거다. 그리고 파일에 대해서다 알아 보겠다.</p>
<p>왜냐하면 <code>Github</code>으로 로그인해서 계정을 만들때 <code>user</code>에게 <code>avatar URL</code>을 준다.</p>
<p>그 말인 즉슨 <code>user</code>한테 <code>avatarUrl</code>이 있다는 거다.</p>
<p>그런데 소셜 로그인이 아닌 다른 방법으로 계정을 만든 경우를 생각해보면 </p>
<p><code>email</code>과 <code>password</code>로 계정은 만든 유저들은 <code>avatarUrl</code>이 없다.</p>
<p>그래서 프로필 수정을 구현하고 백엔드에 어떻게 파일들을 보낼수 있는지 알아 보겠다.</p>
<p><code>Github user</code>뿐만아니라 모든 <code>user</code>가 <code>avatarUrl</code>을 가지게 할거다.</p>
<p>비디오를 만들때도 써 먹을수 있을거다. 비디오 파일을 보낼수 있어야 하기 때문이다.</p>
<p>그게 한 가지 기대되는 포인트고 또 다른 하나는 프로필 수정 페이지를 만들어 볼거다.</p>
<p>프로필 수정 페이지에서는 <code>avatar</code>를 설정할수 있고 원하는 모든 걸 수정 할수 있게 만들거다.</p>
<p><code>password</code>도 바꿀 수 있게 한다. </p>
<p>하지만 <code>socialOnly</code>가 <code>true</code>인 경우는 제외 할거다.</p>
<p><code>socialOnly</code>가 <code>true</code>이면 <code>password</code>를 가지고 있지 않으니깐 말이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Log Out]]></title>
            <link>https://velog.io/@0_cyberlover_0/Log-Out</link>
            <guid>https://velog.io/@0_cyberlover_0/Log-Out</guid>
            <pubDate>Sun, 24 Apr 2022 06:23:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>깃헙 프로필의 email이 데이터베이스에 있을때 유저가 로그인할 수 있게 해주었다.</p>
</blockquote>
<p>그 말이 즉슨 <code>Github</code>가 주는 <code>user</code>의 <code>email</code>을 쓰겠다는거다.</p>
<p><code>primary</code>이면서 <code>verified</code>된 <code>email</code>을 찾는다는 거다.</p>
<p>만일 찾았을 경우에 그 <code>email</code>을 데이터베이스에서 찾는거다.</p>
<p>만일 그 유저를 찾았다면 들어오게 할거다. 로그인 시킨다는 말이다.</p>
<p>그러면 누가 로그인되느냐 먼저 <code>Github</code>로 계정을 만든 사람이거나</p>
<p><code>username</code>과 <code>password</code>로 계정을 만든 사람이 있는데 두 가지 경우 모두 로그인이</p>
<p>가능하다. 왜냐하면 같은 <code>email</code>이기 때문이다. </p>
<p>그래서 <code>username</code>과 <code>password</code>로 계정을 만든 사람의 경우 <code>Github</code>을 통해서 로그인도 가능하다.</p>
<p>왜냐하면 데이터베이스에 있는 <code>email</code>과 <code>Github</code>에 있는 <code>email</code>이 같다.</p>
<p>이런식으로 카카오톡으로 계정 생성하는것도 구현할수 있다.</p>
<p>만일 카카오톡이 <code>email</code>을 <code>return</code>하면 그 <code>email</code>을 찾고 해당되는 유저를 찾아 </p>
<p>로그인 시켜주는거다.</p>
<p><code>Github</code>으로 계정을 만들었든 <code>password</code>로 만들었든지 간에 말이다.</p>
<p>이게 여기서 적용되는 규칮이다. <code>user</code>를 찾을 뿐이다. </p>
<p>어떤식으로 로그인 했는지는 관심없다. 어떤 식으로 계정을 만들었는지도 관심없고</p>
<p><code>Github</code>가 준것과 똑같은 <code>email</code>을 찾게 되면 해당 <code>user</code>를 로그인 시킬거다.</p>
<p><code>DB</code>에 <code>Github email</code>을 가진 <code>user</code>가 없다면 새로운 계정을 만들어서 </p>
<p>그 <code>user</code>를 로그인 시킬거다. </p>
<p>하지만 <code>schema</code>의 한 부분을 바꾼 적이 있다. 바로 <code>socialOnly</code>이다.</p>
<p><code>socialOnly</code>는 해당 계정을 <code>password</code>로 로그인 할수 없다는 것을 알려준다.</p>
<p>이건 오직 소셜 로그인으로만 로그인 할수 있다는 말이다. </p>
<p>그래서 무얼 해볼수가 있냐면 <code>postLogin</code>으로 와서 <code>username</code>을 가진 유저를 찾을때</p>
<p><code>socialOnly</code>가 <code>false</code>인 유저만 찾게 만들수 있다.</p>
<pre><code class="language-js">export const postLogin = async (req, res) =&gt; {
  const { username, password } = req.body;
  const pageTitle = &quot;Login&quot;;
  const user = await User.findOne({ username, socialOnly: false });</code></pre>
<p>여기 <code>postLogin</code>에서 <code>form</code>에 입력한 <code>username</code>을 가지고 </p>
<p><code>socialOnly</code>가 <code>false</code>인 <code>user</code>를 찾는 거다. </p>
<p>왜냐하면 몇몇 사람들은 <code>Github</code>로 로그인 했는지 <code>password</code>를 통해 로그인했는지 잊어 버리기 때문이다.</p>
<p>그래서 여기서 뭘 하고 있냐면 <code>Github</code>데이터를 가지고 <code>user</code>를 생성하고 있고</p>
<pre><code class="language-js"> const user = await User.create({
        name: userData.name ? userData.name : userData.login,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });</code></pre>
<p>그 <code>user</code>를 로그인시키고 있다. 근데 2번씩이나 반복하고 있다.</p>
<p><code>let existingUser</code>로 수정해준다.</p>
<pre><code class="language-js"> let existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = existingUser;
      return res.redirect(&quot;/&quot;);</code></pre>
<p>그리고 만일 <code>exstingUser</code>가 없다면 이부분을 다 실행 할거다.</p>
<pre><code class="language-js">    let user = await User.findOne({ email: emailObj.email });
    if (!user) {
      user = await User.create({
        name: userData.name ? userData.name : userData.login,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });
    }
    req.session.loggedIn = true;
    req.session.user = user;
    return res.redirect(&quot;/&quot;);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>그리고 마지막엔 이렇게 할거다. 그리고 <code>existingUser</code>대신 <code>user</code>라고 한다.</p>
<p>이렇게 <code>user</code>를 찾고 있고 이 유저를 찾게 되면 이 모든 것들을 건너뛰고 <code>user</code>를 로그인 할거다. </p>
<p>만일 <code>user</code>를 못 찾았다면 <code>user</code>를 새로 만든 <code>user</code>로 정의 할거다.</p>
<p>그리고 <code>user</code>를 로그인 할거다.</p>
<p>이미 저번 파트에서 <code>Github</code>    을 통해서 계정을 만들었다. </p>
<p>그래서 어떻게 작동하냐면 이 부분에서 로그인 될거다.</p>
<pre><code class="language-js">req.session.loggedIn = true;
    req.session.user = user;
    return res.redirect(&quot;/&quot;);</code></pre>
<p>테스트해보겠다. 쿠키를 삭제해주고 새로고침을 하면 로그아웃이 되고 </p>
<p><code>Continue with Github</code>을 눌러서 로그인이 된다. </p>
<p>그리고 데이터베이스에는 한 <code>user</code>만 있다. 이건 <code>socialOnly</code>가 <code>true</code>인 경우에만 일어난다.</p>
<p>이말은 해당 계정은 <code>Github</code>로 만들어졌고 <code>password</code>가 없다는 소리이다.</p>
<p>모든 <code>user</code>를 지우고 쿠키도 지워 본다. 다시 로그아웃 되었다.</p>
<p>그리고 새로운 이메일로 가입을 해 보겠다.</p>
<p><code>user</code>를 확인해 보면 <code>socialOnly</code>는 <code>false</code>로 나온다.</p>
<p>그 다음에 <code>Github</code>을 이용해서 로그인해서 작동해 본다.  그리고 다시 <code>user</code>를 찾아 보면</p>
<p><code>socialOnly</code> 가 <code>true</code>로 나온다. </p>
<p>그리고 이제 웹사이트에 계정이 존재한다면 <code>username</code>과 <code>password</code>로 로그인했든</p>
<p><code>Github</code>로 계정 생성을 했든 로그인 할거다.</p>
<p><code>Github</code>에서 가져오는 데이터는 굉장히 중요한 것이다. </p>
<blockquote>
<p>그러니 <code>avatar_url</code>을 저장해 볼수도 있다. </p>
</blockquote>
<p>왜냐하면 다음 섹션에서 <code>avatar</code>와 파일들에 대해 다뤄 볼거다. </p>
<pre><code>avatar_url: &#39;https://avatars.githubusercontent.com/u/91738673?v=4&#39;,</code></pre><p>바로 이 <code>url</code>을 이용해서 말이다. </p>
<p>그래서 이걸 <code>users schema</code>에 추가해 볼거다. </p>
<p><code>User.js</code>에서 </p>
<pre><code class="language-js">const userSchema = new mongoose.Schema({
  avatar_url: String,</code></pre>
<p><code>type</code>은 <code>string</code>으로 해준다. 그리고 <code>required</code>가 아니니깐 그냥 <code>string</code>만 적어준다.</p>
<p>이건 굉장히 유용하다. 왜냐하면 <code>user</code>들이 <code>avatar</code>를 가지고 있길 원한다.</p>
<p>그리고 <code>Github</code>은 필요한 링크를 준다. <code>avatar_url</code>을 클릭해보면 프로필 사진을 띄워 준다.</p>
<blockquote>
<p>만일 <code>user</code>가 <code>Github</code>으로부터 넘어왔다면 한가지를 더 추가해 본다.</p>
</blockquote>
<p><code>avatar_url</code>을 추가한다. </p>
<p><code>userController.js</code>에서 </p>
<pre><code class="language-js">user = await User.create({
        avatarUrl: userData.avatar_url,</code></pre>
<blockquote>
<p>그리고 <code>user</code>객체들은 전부 <code>userData</code>에서 온다는걸 기억하자.</p>
</blockquote>
<p> 그러니 <code>userData.avatar_url</code>을 쓰면 된다.</p>
<p> <code>avatarUrl</code>이 없는 <code>user</code>는 <code>email</code>과 <code>password</code>로만 계정을 만들었다는 소리이다.</p>
<p> 하지만 프로필 수정을 하면 바뀌게 된다. </p>
<p> 다시 한번 기억해 본다. <code>userData</code>는 <code>API</code>로 부터 오며 <code>emailData</code> 또한 <code>Github API</code>로부터 온다.</p>
<p> 여기 만든 두개의 <code>request</code>가 있다. </p>
<pre><code class="language-js"> const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();</code></pre>
<p>서로 다른 데이터에 접근 할수 있는 같은 토큰을 사용하고 있다. </p>
<blockquote>
<p>이제 로그아웃 페이지를 만들어 본다.</p>
</blockquote>
<p>로그아웃의 경우 보다시피 <code>url</code>에 <code>logout</code>이라 되어 있다. </p>
<p>이게 <code>Router</code>상에 있는지 확인해 본다. 이미 가지고 있다.</p>
<p><code>Cannot GET /logout</code> 으로 나오는 이유는 <code>userRouter</code>에 있어선 안된다.</p>
<p><code>/users/logout</code>으로 있어햐 한다.</p>
<p>이말은 <code>template</code>를 후정해야 한다는 소리이다.</p>
<p><code>base.pug</code>로 가서 <code>/users/logout</code>으로 바꿔준다.</p>
<pre><code class="language-js">if loggedIn
                        li 
                            a(href=&quot;/users/logout&quot;) Log Out</code></pre>
<p><code>logout</code>은 무얼 하고 있냐면 <code>logout</code>은 아무것도 안하고 있다.</p>
<p>그저 <code>Log out</code>을 보낸 뿐이다. </p>
<pre><code class="language-js">export const logout = (req, res) =&gt; res.send(&quot;Log out&quot;);</code></pre>
<p>새로고침해서 확인하면 이제 로그아웃 페이지로 넘어가지만 어떤 작업도 하진 않는다.</p>
<pre><code class="language-js">export const logout = (req, res) =&gt; {
  req.session.destroy();
  return res.redirect(&quot;/&quot;);
};</code></pre>
<p>이렇게 하면 세션을 없애준다. 그러면 이제 로그아웃을 페이지를 누르면 로그아웃 된다.</p>
<p>그리고 이제 <code>/remove</code>는 필요없어 졌으니 없애준다. </p>
<p>이제 <code>Github</code>인증과 로그아웃이 구현되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Login #06]]></title>
            <link>https://velog.io/@0_cyberlover_0/Github-Login-06</link>
            <guid>https://velog.io/@0_cyberlover_0/Github-Login-06</guid>
            <pubDate>Sun, 24 Apr 2022 04:38:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이제 스스로에게 물어본다(?)</p>
</blockquote>
<p>&quot;로그인 규칙을 어떻게 만들것인가?&quot; 예를 들어, 데이터베이스에</p>
<p><code>user</code>가 하나 있는데 이 <code>user</code>는 이런 <code>email</code>을 가지고 있다.</p>
<pre><code>{ &quot;_id&quot; : &quot;yuoIkA5t7NB_axs1qVST5xflGbrR45jh&quot;, &quot;expires&quot; : ISODate(&quot;2022-05-06T07:39:42.013Z&quot;), &quot;session&quot; : &quot;{\&quot;cookie\&quot;:{\&quot;originalMaxAge\&quot;:null,\&quot;expires\&quot;:null,\&quot;httpOnly\&quot;:true,\&quot;path\&quot;:\&quot;/\&quot;},\&quot;loggedIn\&quot;:true,\&quot;user\&quot;:{\&quot;_id\&quot;:\&quot;625942ace3564e09811a5f21\&quot;,\&quot;email\&quot;:\&quot;pkpanda@naver.com\&quot;,\&quot;username\&quot;:\&quot;Cyber Lover\&quot;,\&quot;password\&quot;:\&quot;$2b$05$WMO/VH/yctvvPJST0SyLq.QRQfSNeLJ5zAJPFfRMwLgg5ZFq1KtBm\&quot;,\&quot;name\&quot;:\&quot;Mercury\&quot;,\&quot;location\&quot;:\&quot;NYC\&quot;,\&quot;__v\&quot;:0}}&quot; }</code></pre><p>이미 해당 <code>email</code>로 계정도 있고 <code>password</code>도 있다. </p>
<p>즉 이 <code>user</code>는 <code>username</code>과 <code>passoword</code>로 <code>login</code>을 할수 있다.</p>
<p>그런데 만일 <code>Github</code>으로 로그인 버튼을 누르게 된다면 토큰 작업등 전부 거친 뒤 </p>
<p><code>Github</code>으로 로그인 한<code>user</code>는 데이터베이스상에 똑같은 <code>email</code>과 <code>password</code>를 가진 <code>user</code>를 받는다.</p>
<p>그러니까 웹사이트로 와서 <code>email</code>과 <code>password</code>로 계정을 생성하고 한달 후에 돌아와서</p>
<p><code>Github</code>으로 로그인 하려한다. </p>
<p>보다시피 <code>Github</code>은 <code>email</code>을 주고 있다. 그런데 이 <code>email</code>이 똑같다.</p>
<p>그러면 어떻게 하는게 좋을까? 두가지 옵션이 있다. 하나는 <code>user</code>에게 &quot;그건 안된다.</p>
<p>이미 <code>password</code>가 있으니 그걸로 로그인하라&quot; 라고 말 할수 있고</p>
<p>또는 &quot;똑같은 <code>email</code>이 있다는걸 증명했으니 <code>Github</code>로 로그인해도 된다&quot; 라고 할수 있다.</p>
<p>만일 <code>user</code>가 다른 무언가로 로그인을 했다면 예를 들어 카카오톡으로 로그인 했다하면</p>
<p>카카오톡이 이런식으로 <code>email</code>을 줄거다.  이렇게 <code>user</code>를 로그인 시킬수도 있다. </p>
<p>이런 방식을 채택한 여러 사이트들이 있다. 그치만 몇몇 사이트들은 <code>password</code>로 계정을 만들게 하는 곳도 있다. </p>
<p>&quot;<code>password</code>로 로그인 하세요&quot;라고 하면서 말이다. </p>
<p>혹은 <code>email</code>과 <code>password</code>로만 로그인하려 하는데 사실 <code>password</code>없이 <code>Github</code> 로그인으로</p>
<p>계정을 만들었다면 &quot;<code>user</code>는 있지만 <code>password</code>는 없다. 그러니까 <code>Github</code>로 로그인해라&quot; 라고 할거다.</p>
<p>보다시피 옵션은 정말 많다. 그래서 <code>Github</code>로그인 같은 소셜 로그인을 할때</p>
<p>만일 <code>email</code>에 접근 권한이 있다는게 증명이 된다면 즉, <code>password</code>가 있거나</p>
<p><code>Github</code>의 <code>email</code>이 <code>verified</code>된거라면 <code>email</code>의 주인이라는 뜻이니까</p>
<p>로그인 시켜 줄수 있다. 꼭 이렇게 할 필요는 없다. 더 많은 조건들을 만들어도 된다.</p>
<p>예를 들어 <code>user</code>가 <code>password</code>를 갖고 있다는걸 알게 된다면 로그인을 시켜선 안되는 거다.</p>
<p>이 같은 경우엔 여기에 <code>primary</code>랑 <code>verified</code>가 있기 때문에 그렇게 하지 않는다.</p>
<p>그래서 <code>primary</code>이면서 <code>verified</code>된 <code>email</code>을 찾는거다. 두개다 중요하기 때문이다.</p>
<p>그래서 <code>user</code>가 두개의 조건을 만족한다면 &quot;<code>email</code>이 있으니 로그인 시켜준다&quot;라고 할거다.</p>
<p>이 부분 또한 바뀐다. 예를 들어 <code>Github login</code>으로 계정을 만든 <code>user</code>있을때 </p>
<p>즉 <code>email</code>은 있지만 <code>password</code>가 없는 경우를 말하는 거다.</p>
<p>이럴때는 로그인 화면에서 <code>user</code>에게 이렇게 말해야 한다. </p>
<p>&quot;<code>email</code>은 있는데 <code>password</code>가 없다&quot;이건 그들이 <code>Github</code>으로 로그인해야 한다는 뜻이다.</p>
<p>이렇게 고려해야 할 사항들이 굉장히 많다. </p>
<p>이제 무엇을 하냐면 만약 <code>primary</code>인 <code>email</code>을 받고 데이터베이스에서 같은 <code>email</code>을 가진 <code>user</code>를 발견하면 </p>
<p><code>user</code>들을 로그인 시켜 준다. <code>email</code>은 사실 <code>string</code>이 아닌 객체 이다. </p>
<p>그래서 <code>find</code>가 객체를 주고 있는 거다. 그런데 <code>email</code>그 자체가 필요하다.</p>
<p>이 부분을 <code>emailObj</code>라 바꿔 본다. </p>
<pre><code class="language-js">    const emailObj = emailData.find(
      (email) =&gt; email.primary === true &amp;&amp; email.verified === true
    );
    if (!emailObj) {
      return res.redirect(&quot;/login&quot;);
    }
    const existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = user;
      return res.redirect(&quot;/&quot;);
    }
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>그리고 기존 <code>user</code>를 찾는거다. 그리고 <code>emailObj.email</code>을 가지고 <code>user</code>를 찾는다.</p>
<p>만일 해당 <code>email</code>을 가지는 <code>user</code>가 이미 있다면 그 유저가 전에 <code>Github</code>로 로그인했든</p>
<p><code>password</code>로 계정을 생성했든 신경 쓰지 않는다.</p>
<p>핵심은 해당 <code>email</code>을 가진 <code>user</code>가 이미 있는지 찾는 것이고  이런 유저를 로그인 시켜 줄거다.</p>
<p>로그인은 어떻게 시키냐면 위에서 있는 코드가 있으니 그것을 그대로 활용한다.</p>
<pre><code>req.session.loggedIn = true;
req.session.user = user;</code></pre><p><code>return res.redirect()</code>을 써서 <code>&quot;/&quot;/</code>으로 돌아가게 한다.</p>
<p>하지만 이번에는 로그인이 된 상태가 된다. </p>
<p>다시 한번 확인하면 깃헙이 주는 <code>list</code>에서 <code>primary</code>이면서 <code>verified</code>된 <code>email</code>객체를 찾아야한다.</p>
<p>그리고 같은 <code>email</code>을 가진 <code>user</code>가 이미 있다면 그 유저를 로그인시켜 줄거다.</p>
<p>나중에 이 부분에서는 계정을 생성하는걸 추가해줘야 한다.</p>
<pre><code class="language-js">const existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = user;
      return res.redirect(&quot;/&quot;);
    } else {
    }// create an account
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>무슨 말이냐면 해당 <code>email</code>로 <code>user</code>가 없으니까 계정을 생성해야 한다는 거다.</p>
<p>다행히도 이미 아주 만은 데이터를 가지고 있다. </p>
<p><code>email</code>도 있고 <code>location</code>도 있고 <code>name</code>도 있고 <code>Github ID</code>도 있고</p>
<p><code>username</code>도 있다. 필요한 모든 것이 갖춰져 있다.</p>
<p>여태까지의 변경 사항들을 저장한 다음 테스트 해본다. </p>
<p>실행시켜보면 이 코드가 실행될거다. </p>
<pre><code class="language-js">req.session.loggedIn = true;
      req.session.user = user;
      return res.redirect(&quot;/&quot;);</code></pre>
<p>왜냐하면 해당 <code>email</code>을 가지고 있는 <code>user</code>가 이미 데이터 베이스에 있고 </p>
<p><code>Github</code>의 <code>API</code>에서 해당 <code>email</code>을 주기 때문이다.</p>
<p>테스트 해본다. 새로고침하고 나면 에러가 뜬다.</p>
<pre><code>ReferenceError: user is not defined</code></pre><p><code>user</code>가 정의되지 않았다고 뜬다. </p>
<pre><code class="language-js">  const existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = existingUser;</code></pre>
<p><code>user</code>를  <code>existingUser</code>으로 변경해준다. 그러고 나서 다시 테스트를 해본다.</p>
<p>이제 로그인이 잘 된다. </p>
<p>이제 이 <code>user</code>를 지우고 &quot;만일 계정이 없다면 어떻게 할 것인가&quot;에 대해 해보도록 한다.</p>
<p>그래서 하나를 만들어야 한다. 그리고 일종의 <code>password</code> 같은걸 만들어야 한다.</p>
<p><code>mongodb</code>에서  <code>db.sessions.remove({})</code>를 해주고 나면</p>
<p>이 코드는 실행 되지 않을거다.</p>
<pre><code class="language-js">  const existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = existingUser;
      return res.redirect(&quot;/&quot;);
    } else {
      // create an account</code></pre>
<p><code>// create an account</code>  뭐가 실행되냐면 이게 실행될거다. </p>
<p>이제 <code>user</code>를 생성해야 한다. </p>
<pre><code class="language-js">await User.create({
      name,
      username,
      email,
      password,
      location,</code></pre>
<p>이부분을 이용해서 사용한다. 똑같이 해보도록 한다. <code>await User.create()</code>로 <code>user</code>를 만들어 주고 </p>
<pre><code class="language-js">const existingUser = await User.findOne({ email: emailObj.email });
    if (existingUser) {
      req.session.loggedIn = true;
      req.session.user = existingUser;
      return res.redirect(&quot;/&quot;);
    } else {
      const user = await User.create({
        name: userData.name,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        location: userData.location,
      });</code></pre>
<p>그리고 나서 이제 값을 채우면 된다. <code>name</code>은 <code>userData.name</code>이다.</p>
<p>이 데이터는 객체이다. 그래서 <code>userData.name</code>이라고 해준다.</p>
<p>다음은 <code>username</code>이건 <code>userData.login</code>이 될거다. </p>
<p><code>email</code>은 <code>userData.email</code>이 아닌 <code>emailObj.email</code>이다.</p>
<p><code>password</code>는 없으니 <code>&quot; &quot;</code>으로 해준다.</p>
<p><code>location</code>은 이미 있으니 <code>userData.location</code>으로 해준다.</p>
<p>그리고 <code>User.js</code>에서 하나 추가해준다.</p>
<pre><code class="language-js">const userSchema = new mongoose.Schema({
  githubId: { type: Number },</code></pre>
<p>바로 <code>githubId</code>이다. <code>type</code>은 <code>Number</code>이고 <code>required:flase</code>, <code>unique:true</code>로 해준다.</p>
<p>그냥 <code>number</code>만 써준다. 이렇게 하는 이유는 <code>user</code>가 <code>Github</code>로 로그인했는지 여부를 알기 위해서이다. </p>
<p>이건 로그인 페이지에서 유저가 <code>email</code>로 로그인하려는데 <code>password</code>가 없을때 유용할수 있다.</p>
<p><code>githubId</code>를 체크하면 된다. 사실 <code>githubId</code>가 아니라 <code>githubLoginOnly</code>라 해준다.</p>
<p>아니면 <code>noPasswordAccount</code> 아니면 <code>socialOnly</code> <code>type</code>은 <code>Boolean</code>으로 해준다.</p>
<p><code>default</code>는 <code>false</code>로 해준다. </p>
<pre><code class="language-js">  socialOnly: { type: Boolean, default: false },</code></pre>
<p>이렇게 계정을 만들어주면 이 계정은 <code>Github</code>을 이용해 계정을 만들었다면 <code>password</code>는 없게 된다.</p>
<p>그렇다면 <code>username</code>과 <code>password form</code>을 사용 할수 없다.</p>
<p>그래서 계정이 <code>socialOnly=true</code>라는걸 알려줘야 한다. </p>
<pre><code class="language-js">      const user = await User.create({
        name: userData.name,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });</code></pre>
<p>이렇게 <code>user</code>를 생성하도록 만들었다. 여기서 <code>User.create()</code>는 새로 만든 <code>user</code>를 <code>return</code>시켜준다.</p>
<p>이 <code>user</code>를 로그인 시켜줘야 한다. </p>
<pre><code class="language-js">const user = await User.create({
        name: userData.name,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });
      req.session.loggedIn = true;
      req.session.user = user;
      return res.redirect(&quot;/&quot;);</code></pre>
<p>그래서 밑에 이렇게 넣어준다. 이 부분을 다듬어 보는건 다음에 해보도록 한다.</p>
<p>그리고 이건 다른 옵션이다. 데이터베이스에 해당 <code>email</code>을 가진 <code>user</code>가 없을때</p>
<p>이렇게 <code>password</code>없이 <code>Github</code>의 데이터로 <code>user</code>를 생성하고 </p>
<p>그런 <code>user</code>에게는 <code>socialOnly:true</code>값을 주고 있다. </p>
<p>그리고 여기 <code>postLogin</code>때 유용 할거다. </p>
<pre><code class="language-js">export const postLogin = async (req, res) =&gt; {
</code></pre>
<p>왜냐하면 <code>user</code>를 찾을때 몇몇 <code>password</code>들을 비교해볼텐데 </p>
<p>그러기 위해 <code>if else</code>를 써야 될거 같다. </p>
<p><code>user</code>가 <code>Github</code>로 계정을 만들고 <code>password</code>로 로그인을 시도한다면 </p>
<p><code>user</code>에게 &quot;<code>password</code>가 없으니 <code>Github</code>로 로그인&quot;하라고 말해줘야 한다.</p>
<p>아니면 <code>pasword</code>를 새로 생성하는 페이지를 만들든지 해야 한다.</p>
<p>이제 테스트 해보도록 한다. 데이터베이스엔 <code>user</code>가 없고 </p>
<p>이 계정은 <code>Github</code>데이터로만 만들어졌을거다.</p>
<pre><code class="language-js"> const user = await User.create({
        name: userData.name,
        username: userData.login,
        email: emailObj.email,
        password: &quot;&quot;,
        socialOnly: true,
        location: userData.location,
      });</code></pre>
<p>그리고 <code>Github</code>데이터는 <code>avatarUrl</code>이라 하는 것이 있는데 그건 나중에 써보도록 한다.</p>
<p>테스트를 해본다. <code>login</code> -&gt; <code>Continue with Github</code>하면 계정이 생성되고</p>
<p>(해당 강의에서는 에러가 발생하는데 그냥 로그인이 잘된다.)</p>
<p>에러는 <code>User</code> 인증에 실패했습니다. <code>User validation failed</code>라고 뜬다.</p>
<p><code>password</code>가 필수 조건이라고 한다. </p>
<pre><code class="language-js">  password: { type: String, required: false },</code></pre>
<p><code>password required</code>값을 <code>false</code>로 바꿔준다. 유일한 옵션이다.</p>
<p>(그냥 없애도 잘 작동 한다.)</p>
<p>이건 몇몇 <code>user</code>들에게는 <code>password</code>가 없을테니 그런거다.</p>
<p>다시 실행해 보면 잘된다. (수정을 안 한 상태에서도 잘 작동하였지만 혹시 모를 에러때문에 이렇게 수정을 하고 진행하도록 한다. )</p>
<p><code>Github</code>으로 부터 온 프로필 이름이 나온다.</p>
<p><code>mongodb</code>로 가서 <code>user</code>를 보면 <code>socialOnly:true</code>인 계정이 생성 되었다.</p>
<p><code>password</code>는 없다. 왜냐하면 비어있는 해시값이기 때문이다.</p>
<p><code>username</code>은 <code>Github username</code>일 테고 <code>email</code>은 <code>Github email</code>이다.</p>
<p><code>location</code>이런 것들 전부 <code>Github</code>에서 오고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Login #05]]></title>
            <link>https://velog.io/@0_cyberlover_0/Github-Login-05</link>
            <guid>https://velog.io/@0_cyberlover_0/Github-Login-05</guid>
            <pubDate>Fri, 22 Apr 2022 07:15:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>저번 파트에서 뭘했는지 살펴 보기로 한다.</p>
</blockquote>
<p><code>Github</code>가 준 코드를 가지고 <code>access_token</code>으로 교환을 했다.</p>
<pre><code class="language-js">export const finishGithubLogin = async (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/access_token&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };</code></pre>
<pre><code class="language-js">export const finishGithubLogin = async (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/access_token&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  const tokenRequest = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();
  if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userRequest);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
</code></pre>
<p><code>access_token</code>은 <code>Github API URL</code>을 <code>fetch</code>하는데 사용 되었다.</p>
<p>그랬더니 얻은 건 <code>user</code>의 <code>public</code>정보였다. 이건 <code>scope</code>로 요청했기 때문에 가능했다.</p>
<p>만일 <code>read:user</code>를 요청하지 않았다면 받은 코드로는 <code>user</code>의 정보를 읽을 수 있는 </p>
<p><code>access_token</code>을 받을 수 없을거다.</p>
<p>어찌 되었든 <code>Github API</code>에 대해 얘기하고 있고 <code>Github API</code>로 가면 무얼 할수 있는지 전부 볼수 있다.</p>
<p><a href="https://docs.github.com/en/rest/users/users#get-the-authenticated-user">https://docs.github.com/en/rest/users/users#get-the-authenticated-user</a></p>
<p>예를 들면 현재 한것 처럼 <code>user</code>를 불러올수 있다.</p>
<p><a href="https://api.github.com/user">https://api.github.com/user</a></p>
<p>이 부분이 <code>user</code>를 가져오고 있는거다. 다른 많은 것들로 할수 있다.</p>
<p><code>user</code>를 차단 시킬수도 있고 <code>user</code>를 팔로우 할수도 있다.</p>
<p>이것들을 한번 해보는 것도 좋을 것 같다. <code>PUT request</code>를 <code>/user/following/{username}</code>으로 보낸다 하면</p>
<p>지금은 이걸 허용하는 <code>access_token</code>이 없으니 작동하진 않을거다.</p>
<p><code>access_token</code>이 모든걸 할수 있도록 허용하는건 아니기 때문이다.</p>
<p><code>scope</code>에 적은 내용에 대해서만 허용해줄 뿐이다. </p>
<p>다시 말해 <code>access_token</code>은 <code>user</code>가 모든걸 할수 있게 해주진 않는다.</p>
<p><code>scope</code>에 명시하면 <code>Github</code>가 코드를 줄거다. </p>
<p>그 코드에는 이미 하고자 하는 바가 명시 되어 있다. </p>
<p>그리고 그 코드를 <code>access_token</code>으로 바꾸게 되고 <code>access_token</code>은 한다고 한 것 하게 된다. </p>
<p>현재 경우에서 <code>access_token</code>을 가지고 하려는건 첫번째 <code>scope</code>이다.</p>
<pre><code class="language-js">export const startGithubLogin = (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/authorize&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    allow_signup: false,
    scope: &quot;read:user user:email&quot;,
  };</code></pre>
<p><code>user</code>의 정보를 읽어 들이고 있다. 하지만 아직 두번째 요구인 <code>email</code>을 읽어 들이고 있지 않다.</p>
<p>이건 <code>access_token</code>을 가지고 더 많은 걸 할수 있다는걸 보여주기 위해 일부러 이렇게 만들어 본거다.</p>
<p>여기서 <code>user</code>의 데이터를 가져오고 있으니까</p>
<pre><code class="language-js">  if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })</code></pre>
<p><code>userRequest</code>를 <code>userData</code>로 바꿔 준다. </p>
<pre><code class="language-js">const userData = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);</code></pre>
<p>어떤 <code>user</code>들은 <code>public</code>(공개된) <code>email</code>을 가지고 있을 수도 있다.</p>
<p>하지만 이 경우엔 고의로 이렇게 만들었다. <code>public email</code>이 아닌걸로 말이다.</p>
<p>그렇기 때문에 <code>user</code>의 <code>email</code>을 요청하기 위해 똑같은 <code>access_token</code>을 써야 한다.</p>
<p><code>Github API</code>를 잠깐 살펴보면 <code>email</code>만 다른 부분을 볼수 있다.</p>
<p><a href="https://docs.github.com/en/rest/users/emails">https://docs.github.com/en/rest/users/emails</a></p>
<p><code>primary email</code>설정하기는 현재 상태랑 관계없다. 모든 <code>email</code>주소를 부여주기가 현재 필요한 거다.</p>
<p>그러면 모든 <code>email</code>주소들을 가져오도록 한다.</p>
<pre><code class="language-js">if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const apiUrl = &quot;https://api.github.com&quot;;
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);
    const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(emailData);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p><code>fetch</code>에서 뒷부분은 편리하게 사용 할수 있게 만들어 준다. </p>
<p>이제 <code>user</code>데이터를 볼러 올수 있다는걸 안다. 하지만 이제 <code>email</code>데이터도 원한다.</p>
<p><code>await</code>부분도 반복되기에 그대로 복사해서 넣어주고 <code>url</code>일부분 수정해 준다.</p>
<p>그리고 <code>console.log(emailData)</code>를 해준다.</p>
<p>이제 두개의 <code>request</code>를 하고 있다. 그리고 당연히 <code>json</code>도 넣어 준다.</p>
<p>이렇게 하면 짧게 쓸 수 있다. <code>fetch</code>를 하고 <code>await json()</code>을 쓰는 거다.</p>
<p>이제 <code>email</code>데이터를 사용할 준비가 된거다. 그럼 콘솔로 돌아가서 다시 한번 시도한다.</p>
<p>이제 두개의 데이터를 받았다. 하나는 이미 알고 있는 공개된 프로필이고 </p>
<p>다른 하나는 <code>email</code>들이다. 이제 <code>email</code>이 <code>verified</code>이면서 <code>primary</code>인 것들을 찾아야한다.</p>
<p>그러니 <code>email</code>들 중 <code>verified</code>(확인)이면서 <code>primary</code>(일순위)인 걸 찾아 볼거다. </p>
<p>왜냐하면 가끔 <code>Github</code>으로 계정 생성을 해도 <code>primary</code>혹은 <code>verified</code>가 안 되었을수도 있다.</p>
<p><code>user</code>의 <code>email</code>을 얻고 있다. 다시 말하자면 <code>/user/emails</code>와 </p>
<p><code>/user</code>로 보내는 이 <code>request</code>들은 <code>access_token</code>이 볼수 있게끔 허락해줬기 때문에 작동하는거다. </p>
<p>모든건 <code>scope</code>부분에서 출발한다. 현재  이 경우가 제일 익숙하지 않다.</p>
<pre><code class="language-js">await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p><code>ES6</code>에 익숙지 않아서 그렇다. 그런데 이 두 부분은 지금 써보려는 방식과 비슷하다.</p>
<pre><code class="language-js">fetch(x).then(response =&gt; response.json()).then(json =&gt; )
await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p>하지만 이렇게 작성하는건 좋지 못하다. 왜냐하면 이 말인 즉슨 <code>.then</code>안으로 들어가야 하기 때문이다. </p>
<pre><code class="language-js">if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;</code></pre>
<p>예를 들어 이 부분에서 <code>access_token</code>을 얻으면 그렇다는건 여기에서 <code>access_token</code>을 얻는다는 거다.</p>
<pre><code class="language-js">fetch(x).then(response =&gt; response.json()).then(json =&gt; access_token)
await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p>그럼 이줄에서 <code>fetch</code>를 한번더 해야 한다는 소리이다. </p>
<pre><code class="language-js">fetch(x).then(response =&gt; response.json()).then(json =&gt; access_token fetch ())</code></pre>
<p>왜냐하면 이것도 가져와야 하기 때문이다. </p>
<pre><code class="language-js">await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })</code></pre>
<p>결국 <code>fetch</code>를 써야 하는데 만일 여기서 했던 것처럼 똑같이 하고 싶다면 </p>
<p>이렇게 해야 될거다. </p>
<pre><code class="language-js">const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;

  fetch(finalUrl, {
    method: &quot;POST&quot;,
    headers: {
      Accept: &quot;application/json&quot;,
    },
  }).then(response =&gt; response.json()).then(json =&gt; {
    if (&quot;access_token&quot; in json) {
      const { access_token } = tokenRequest;
      const apiUrl = &quot;https://api.github.com&quot;; 
        await fetch(`${apiUrl}/user`, {
          headers: {
            Authorization: `token ${access_token}`,
          },
        }).then(response =&gt; response.json()).then(json =&gt; {
          fetch(`${apiUrl}/user/emails`, {
            headers: {
              Authorization: `token ${access_token}`,
            },
          })
        });
      }
  });
const tokenRequest = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();
  if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const apiUrl = &quot;https://api.github.com&quot;;
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);
    const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(emailData);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>이것들을 전부 <code>fetch</code>하고 <code>then</code>을 쓰는 거다. 맨위 부터 시작해서 <code>then</code>안에 
<code>then</code>, 또 <code>then</code>안에 <code>then</code> 그리고 또 <code>fetch</code>를 한다.</p>
<p>이거 완전 이상한 짓이다. 이걸 <code>user email</code>이랑 <code>user</code> 프로필에도 해줘야 하는데</p>
<p>그러다간 머리가 터질지도 모른다. </p>
<p>그래서 <code>then</code>을 쓰는 대신에 그냥 원래대로 하는거다.너무나 당연히 이 방법이 훯씬 좋다.</p>
<p>어쨋든 <code>user</code>데이터를 불러오고 있고, <code>email</code>도 불러오고 있다. </p>
<p>이제 <code>user</code>데이터랑 <code>email</code>데이터를 가지고 있으니까 유저의 <code>Github ID</code>도 알고 있다는거고, </p>
<p><code>user</code>의 <code>email</code>들도 알고 있다는 거다. </p>
<p>이제 <code>primary</code>와 <code>verified</code>가 모두 <code>true</code>인 <code>email</code>을 찾아야 한다. </p>
<p>그래서 이 부분을 콘솔로 복붙해서 사용해 본다.</p>
<pre><code>[
  {
    email: &#39;pkpanda@naver.com&#39;,
    primary: true,
    verified: true,
    visibility: &#39;private&#39;
  },
  {
    email: &#39;91738673+JooMercury@users.noreply.github.com&#39;,
    primary: false,
    verified: true,
    visibility: null
  }
]</code></pre><pre><code>const emails = [
  {
    email: &#39;pkpanda@naver.com&#39;,
    primary: true,
    verified: true,
    visibility: &#39;private&#39;
  },
  {
    email: &#39;91738673+JooMercury@users.noreply.github.com&#39;,
    primary: false,
    verified: true,
    visibility: null
  }
]</code></pre><p>2개의 <code>email</code>을 가진 배열을 만들었다. 그런 다음에 이렇게 입력해 본다.</p>
<pre><code>emails.find(email =&gt; email.primary === true &amp;&amp; email.verified === true)
{email: &#39;pkpanda@naver.com&#39;, primary: true, verified: true, visibility: &#39;private&#39;}</code></pre><p><code>emails.find()</code>을 써서 <code>primary</code>와 <code>verified</code>가 <code>true</code>인 <code>email</code>을 찾는다.</p>
<p>그러면 이런 식으로 결과가 나온다. 이런 코드를 쓰고 싶었던 거다. </p>
<p>이걸 브라우저에서 하는게 좋을거다. 콘솔이 훨씬 인터렉티브 하니까 말이다.</p>
<p>이렇게 <code>primary</code>와 <code>verified</code>가 모두<code>true</code>인 <code>email</code>을 찾아 봤다.</p>
<p>현재 찾고 싶은걸 <code>array</code>에서 찾는 방법이였다.</p>
<pre><code class="language-js">const email = emailData.find(
      (email) =&gt; email.primary === true &amp;&amp; email.verified === true
    );
    if (!email) {
      return res.redirect(&quot;/login&quot;);
    }
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>아까 쓴 <code>find()</code>를 쓰면 된다. 그리고 만일 <code>email</code>이 없다면 <code>user</code>한테 <code>verified</code>된 <code>email</code>이 없을 수도 있기때문이다. </p>
<p>다시 <code>login</code>화면으로 <code>redirect</code> 한다.</p>
<p>나중에는 에러 <code>notificaton</code>을 보여주면서 <code>redirect</code> 시켜 본다.</p>
<p>만일 코드가  도착하게 되면 <code>primary</code>이면서 <code>verified</code>인 <code>email</code>이 있다는 뜻이다.</p>
<p><code>user</code>데이터 또한 받게 된다. 그래서 <code>user</code>를 로그인 시킬수도 있고 아니면 계정을 생성 시킬수도 있다.</p>
<p>왜냐하면 <code>email</code>이 없다는 뜻일 테니깐 말이다. </p>
<p>그리고 동일한 <code>user email</code>은 갖고 있지만 한명은 일반 <code>password</code>로 로그인 하고 </p>
<p>다른 하나는 <code>Github</code>로 로그인 하는 <code>user</code>를 어떻게 다룰지 알아 볼거다.</p>
<p><code>email</code>과 <code>password</code>로 계정을 생성한 <code>user</code>가 <code>Github</code>로 로그인하려고 하면 어떻게 할 것인지 생각해 보는거다.</p>
<p>그리고 똑같은 <code>email</code>이 있다면 어떻게 할지도 말이다. </p>
<p>두개의 계정을 만들게 할 것인가? 아니면 그 계정들을 하나로 통합 할것인가? </p>
<p><code>user</code>에게 이미 해당 <code>email</code>로 만든 계정이 있다고 에러를 보낼 것인가?</p>
<p>다음 파트에서 다뤄 보도록 한다.</p>
<p>현재는 만족스럽다. 왜냐하면 <code>email</code>이 있으니 어떤 <code>user</code>인지 알수 있다.</p>
<p>다음 파트에선 좀더 생각해봐야 할 필요가 있다.어떻게 중복된 <code>email</code>을 처리할건지 말이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Login #04]]></title>
            <link>https://velog.io/@0_cyberlover_0/Github-Login-04</link>
            <guid>https://velog.io/@0_cyberlover_0/Github-Login-04</guid>
            <pubDate>Fri, 22 Apr 2022 04:10:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>fetch가 필요한데 fetch는 서버엔 없고 브라우저에만 존재한다.</p>
</blockquote>
<p>사람들은 <code>node-fetch</code>라는 패키지를 만들었다. </p>
<p><a href="https://www.npmjs.com/package/node-fetch">https://www.npmjs.com/package/node-fetch</a></p>
<p><code>npm i node-fetch</code> 설치해 보도록 한다.</p>
<p>현재 에러로 통해 자바스크립트와 <code>NodeJS</code>가 다른 플랫폼이라는 걸 알수 있다.</p>
<p><code>userController.js</code>에서</p>
<pre><code class="language-js">import User from &quot;../models/User&quot;;
import bcrypt from &quot;bcrypt&quot;;
import fetch from &quot;node-fetch&quot;;</code></pre>
<p>그러면 이제 <code>fetch</code>가 정의 된다. <code>request</code>들을 해본다. <code>node</code>로 가 보면 에러는 안 보인다. </p>
<p>새로고침을 하면 코드는 만료가 된것 같다. <code>node</code>로 다시 가보면 에러가 뜬다.</p>
<p>어찌 되었든 작동은 한다.  <code>error: &#39;bad_verification_code&#39;,</code></p>
<p>이렇게 나온다.  <code>error_description: &#39;The code passed is incorrect or expired.&#39;,</code></p>
<p>확인해 보면 코드가 정확하지 않거나 만료되었을 수 있다. 라고 한다. </p>
<pre><code class="language-js">export const finishGithubLogin = async (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/access_token&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  const data = await fetch(finalUrl, {
    method: &quot;POST&quot;,
    headers: {
      Accept: &quot;application/json&quot;,
    },
  });
  const json = await data.json();
  console.log(json);
  res.send(JSON.stringify(json));
};</code></pre>
<p>그리고 맨 마지막에 이렇게 추가를 해줘야 한다. 그래야 프론트엔드에서 볼수 있다.</p>
<p>테스트를 해본다. 코드와 함께 되돌아오고 코드는 백엔드로 가서 결과를 보수 있을거다.</p>
<pre><code>// 20220422091738
// http://localhost:4000/users/github/finish?code=be7cb0a94beec5341141

{
  &quot;access_token&quot;: &quot;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&quot;,
  &quot;token_type&quot;: &quot;bearer&quot;,
  &quot;scope&quot;: &quot;read:user,user:email&quot;
}</code></pre><p>이런 결과가 나온다. <code>access_token</code>이 생겼다. <code>token_type</code>은 <code>bearer</code>이고 </p>
<p><code>scope</code>은 <code>read:user</code>,<code>user:email</code>이다.</p>
<p><code>url</code>에 나온 코드를 이용해서 <code>Github</code>백엔드에 <code>request</code>를 보내니 <code>access_token</code>이 생겼다. </p>
<p>이제 다음 단계로 <code>access_token</code>을 갖고 <code>API</code>에 접근한다. </p>
<p>이제 <code>access_token</code>을 가지고 <code>user</code>의 정보를 얻을수 있다.</p>
<p>다시 한번 되짚어 보면 <code>Github</code>에 <code>user</code>를 보내고 <code>user</code>가 <code>Github</code>에서 </p>
<p><code>&quot;yes&quot;</code>라고 하면 <code>Github</code>는 코드를 준다. 그 코드를 가지고 <code>access_token</code>으로 바꿔준다.</p>
<p>그럼 <code>access_token</code>으로 <code>Github API</code>를 사용해 <code>user</code>정보를 가져 온다.</p>
<p>이제 <code>JSON</code>에 있는 <code>access_token</code>을 가져오면 된다. </p>
<pre><code class="language-js">const json = await data.json();
  if(&quot;access_token&quot; in json){
    // access api 
  }else{
    return res.redirect(&quot;/login&quot;)
  }
};</code></pre>
<p><code>json</code>에 <code>access_token</code>이 있는 경우 <code>API</code>에 접근하도록 한다.</p>
<p>그게 아니라면 <code>res.render(&quot;login&quot;)</code>을 할거다. </p>
<p>그러나 <code>login</code>을 <code>render</code>하는게 아니다. <code>error</code>를 <code>render</code>하는거다.</p>
<p>그래서 우선 <code>redirect</code>를 써본다. <code>redirect(&quot;/login&quot;)</code>이라고 한다.</p>
<p>만약 <code>response</code>안에 <code>access_token</code>이 없다면 <code>login</code>으로 <code>redirect</code>된다.</p>
<p>그러나 이렇게 하면 별로 좋지 않다. <code>user</code>에게 <code>notification</code>을 보여주고 싶다.</p>
<p>그건 나중에 해본다. 아직 <code>user</code>에게 <code>notification</code>을 못 보낸다.</p>
<p><code>Pug template</code>을 <code>render</code>하는건 너무 불편하다.</p>
<p>그렇게 하면 <code>URL</code>에서 <code>user</code>가 <code>template</code>을 보게 된다.</p>
<p><code>user</code>가 <code>template</code>를 봐선 안된다.</p>
<p>이건 단지 어디론가 데려다주는 <code>URL</code>일뿐이다. </p>
<pre><code>http://localhost:4000/users/github/finish?code=be7cb0a94beec5341141</code></pre><p>그래서 <code>Controller</code>밖에선 어떤것도 <code>render</code>하고 싶지 않다.</p>
<pre><code class="language-js">export const finishGithubLogin = async (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/access_token&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };</code></pre>
<p>이 모든 건 <code>nitification</code>을 구현하면 가능하다.</p>
<p><code>notification</code>은 <code>user</code>가 <code>/login</code>으로 <code>redirect</code>하게 해줄테고</p>
<p><code>user</code>는 <code>/login</code>에서 에러 메세지를 보게 될거다. </p>
<p>현재 이곳에서 <code>render(&quot;/login&quot;)</code>을 하면 너무 불편하다.</p>
<pre><code class="language-js">const json = await data.json();
  if(&quot;access_token&quot; in json){
    // access api 
  }else{
    return res.render(&quot;login&quot;, {pageTitel})
  }</code></pre>
<p>그리고 그 뒤에 <code>pageTitle</code> 같은 이런 것들을 쓰게 되면 코드가 너무 지저분해진다.</p>
<p>그래서 <code>redirect(&quot;/login&quot;)</code>을 사용한다.</p>
<p>나중에 <code>notificaton</code>을 보내면서 어떻게 <code>redirect</code>를 하는지 알아본다.</p>
<p>일단은 먼저 테스트해본다. <code>JSON</code>안에 <code>access_token</code>이 없다면 어떻게 되냐면</p>
<p>웹사이트를 새로고침을 해보면 알수 있다. 왜냐하면 현재 코드는 이미 2번씩이나 사용했는데</p>
<p>이건 2번씩 사용하지 못한다. 그래서 새로고침을 하면 <code>login</code>으로 <code>redirect</code> 된다.</p>
<p>왜냐하면 <code>access_token</code>이 <code>JSON</code>안에 없었기 때문이다.</p>
<p><code>JSON</code>이 이렇게 생겼다는거 기억이 난다.</p>
<pre><code>
  access_token: &#39;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&#39;,
  token_type: &#39;bearer&#39;,
  scope: &#39;read:user,user:email&#39;
}</code></pre><p>그런데 코드에서 이미 토큰이 사용되었다면 에러가 발생한다.</p>
<pre><code>  error: &#39;bad_verification_code&#39;,
  error_description: &#39;The code passed is incorrect or expired.&#39;,</code></pre><p>그렇기 때문에 지금은 다시 <code>login URL</code>로 <code>redirect</code>되는거고 </p>
<p>나중에는 <code>redirect</code>되고나서 <code>user</code>에게 &quot;로그인 할수 없습니다&quot; 이런식으로 메시지를 보여주게 한다.</p>
<blockquote>
<p>3단계는 GET URl을 통해서 인증을 위한 access_token을 보내준다.</p>
</blockquote>
<pre><code>Authorization: token OAUTH-TOKEN
GET https://api.github.com/user</code></pre><pre><code class="language-js">  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  const tokenRequest = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();
  if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userRequest);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p><code>access_token</code>을 <code>JSON</code>으로부터 넣어줘서 </p>
<pre><code class="language-js">const {access_token} = json;
</code></pre>
<pre><code class="language-js">const userRequest = await fetch(&quot;https://api.github.com/user&quot;,{
   headers: {
          Authorization: `token ${access_token}`,
        },
  )</code></pre>
<p><code>&quot;https://api.github.com/user&quot;</code> <code>URL</code>을 넣어주고 그리고 <code>headers</code>에 </p>
<p><code>authorization</code>을 보내야 한다. 여기에 <code>token</code>이란 <code>string</code>을 집어 넣어준다.</p>
<p>그리고 <code>JSON</code>안에 있는 <code>access_token</code>을 쓰면 된다.</p>
<p><code>JSON</code>에는 토큰이 있다는걸 기억해야 한다.</p>
<pre><code>  access_token: &#39;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&#39;,
  token_type: &#39;bearer&#39;,
  scope: &#39;read:user,user:email&#39;
}</code></pre><p>그러니 <code>access_token</code>을 <code>fetch</code>안의 <code>headers</code>로 보내준다.</p>
<p>그리고 <code>awit</code>을 2번씩이나 쓸 필요가 없다. 모든 <code>await</code>들을 괄호 안에 넣고 </p>
<p><code>awati</code>를 한 다음에 <code>.json()</code>을 해준다. </p>
<pre><code class="language-js">  const data = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p>그래서 이건 필요가 없어 졌다.</p>
<pre><code class="language-js">const json = await data.json();</code></pre>
<p><code>await</code>를 또 다른 <code>await</code> 안에 넣어 주었다. 그리고 <code>data</code>대신에 <code>token</code>을 받게 된다.</p>
<pre><code class="language-js">  const tokenRequest = await (
    await fetch(finalUrl, {
      method: &quot;POST&quot;,
      headers: {
        Accept: &quot;application/json&quot;,
      },
    })
  ).json();</code></pre>
<p><code>tokenRequest</code>이라고 해준다. </p>
<p>만일 <code>tokenRequest</code>안에 <code>access_token</code>이 있다면 여기에도 똑같은걸 할거다.</p>
<pre><code class="language-js">if (&quot;access_token&quot; in json) {
 const { access_token } = json;</code></pre>
<pre><code class="language-js">  if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;</code></pre>
<pre><code class="language-js">if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>보다시피 <code>fetch</code>를 요청하고 있다. <code>fetch</code>가 돌아오면 해당 <code>fetch</code>의 <code>JSON</code>을 받게 된다.</p>
<p>그런후에 <code>console.log(userRequest);</code>를 해준다. </p>
<pre><code class="language-js">if (&quot;access_token&quot; in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch(&quot;https://api.github.com/user&quot;, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userRequest);
  } else {
    return res.redirect(&quot;/login&quot;);
  }
};</code></pre>
<p>조금 많이 복잡하긴 하다. </p>
<p>새로고침을 하고 <code>Continue with Github</code>를 해주면 누르면 다시 돌아오고 ,</p>
<p><code>code</code>를 <code>access_token</code>으로 바꾼 다음에 <code>access_token</code>을 이용해  <code>Github API</code>로 간다.</p>
<p>그러면 <code>user</code>데이터를 볼수 있다. </p>
<p><code>NodeJS</code>으로 가보면 잘 작동한다. 정보를 받아오고 있다.</p>
<p>보다시피 홈페이지는 계속 로딩중이다. 왜냐하면 아무것도 <code>return</code>하지 않아서 그렇다.</p>
<p>어쨌든 작동은 하고 있다. <code>Github</code> 프로필 정보를 가져온다.</p>
<p>이제 이걸 사용하면 된다. 하지만 문제가 있다. <code>email</code>이 <code>null</code>값이다. </p>
<p>무슨 말이냐면 <code>email</code>이 없거나 <code>private</code>이라는 뜻이다. </p>
<p>그래서 <code>email</code>이 <code>null</code>값일때를 대비해서 또 다른 <code>request</code>를 만들어야 한다.</p>
<p>일단 작동은 하고 있다. <code>Github user</code>의 정보를 가져오고 있다.</p>
<p>물론 아무것도 <code>return</code>을 해주지 않아서 계속 로딩 중이다.</p>
<p>이제 <code>request</code>를 끝낸다. </p>
<p>그리고 다시 새로고침으로 시작을 하면 <code>URL</code>이 바뀌는 것조차 보질 못했다.</p>
<p>모든게 너무 빨리 돌아가고 있다. </p>
<p>다음 파트에서 <code>user</code>의 <code>email</code>을 가져와 본다.</p>
<p>왜냐하면 여기서 <code>email</code>을 요청했는데 받지 못했다.</p>
<pre><code class="language-js">export const startGithubLogin = (req, res) =&gt; {
  const baseUrl = &quot;https://github.com/login/oauth/authorize&quot;;
  const config = {
    client_id: process.env.GH_CLIENT,
    allow_signup: false,
    scope: &quot;read:user user:email&quot;,
  };</code></pre>
<p>일단 받은 <code>code</code>를 <code>access_token</code>으로 바꾸었다.</p>
<p>그 <code>access_token</code>을 가지고 <code>Github API</code>를 이용해서 <code>user</code>의 정보를 가져올수 있다.</p>
<p>일단 웹사이트에는 이 정도만 해도 충분하긴 하다.  깃헙 아이디만 필요한거라면 말이다.</p>
<p>하지만 현재 상태에서는 <code>email</code>이 필요하다.</p>
]]></description>
        </item>
    </channel>
</rss>