<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>charlie-lyc.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 18 Feb 2021 10:52:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>charlie-lyc.log</title>
            <url>https://images.velog.io/images/charlie-lyc/profile/b8e89204-cd8a-4d65-be5d-8dbb492b470b/my_scuba0.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. charlie-lyc.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/charlie-lyc" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Project Reflection : Final Project 회고]]></title>
            <link>https://velog.io/@charlie-lyc/Project-Reflection-Final-Project</link>
            <guid>https://velog.io/@charlie-lyc/Project-Reflection-Final-Project</guid>
            <pubDate>Thu, 18 Feb 2021 10:52:24 GMT</pubDate>
            <description><![CDATA[<h3 id="꼭-배포에-성공하고-싶습니다"><em>꼭 배포에 성공하고 싶습니다!</em></h3>
<p><a href="https://velog.io/@charlie-lyc/Project-Reflection-First-Project">첫 번째 프로젝트</a>를 마치고 바로 다음 날부터 부트캠프 코스 중 두 번째 프로젝트이자 마지막 프로젝트가 시작되었다. 팀원 간 소개가 끝나고 팀명을 정하는 등 다들 앞선 경험이 있어서 그런지 원할하게 진행되었다. SR미팅 시간에도 팀원들은 제각기 자신들이 제안하는 아이디어에 관하여 설명하고 의견을 교환하였다. 나는 모든 간절함을 담아 진심으로 팀원들에게 호소하였다.</p>
<p><em>&quot;누구의 아이디어가 선택되든 최선을 다해서 꼭 배포에 성공하고 싶습니다!&quot;</em></p>
<p>팀원 중 누군가 피식 웃었고 이후에 긴장감을 풀고 나름 준비한 아이디어를 설명하고 아울러 프로젝트 일정 중에 반드시 주기적으로 배포와 디버깅을 했으면 좋겠다고 덧붙였다. 팀원들이 동의를 해주어 회의를 거쳐 매주 금요일 오후를 서버와 클라이언트를 통합하여 디버깅과 설치 및 배포 일정으로 잡고 첫번째 주에 HTTP, 두번째 주에는 HTTPS로 배포하기로 결정하였다.</p>
<p>돌이켜 보면 AWS를 이용한 HTTPS 배포에 대한 부담이 있었지만 팀원 모두의 집단지성을 통해 결국 해결하였고, 이를 통해 배포에 대한 부담을 덜게 되면서 나머지 작업에 좀 더 집중하여 끝까지 마무리할 수 있었던 것 같다.</p>
<p>4주(25일)동안의 프로젝트 중 회고내용을 4F에 근거하여 아래와 같이 정리해 보았다.</p>
<blockquote>
<h4 id="--fact-사실">- Fact (사실)</h4>
</blockquote>
<ul>
<li>백엔드 포지션에 나를 포함하여 3명이 희망하였고 결국 내가 프론트엔드를 선택했다.</li>
<li>이제껏 시도해보지 않은 기술스택을 최소한 한가지 정도 프로젝트에서 사용해보고자 저녁 시간을 이용해 Redux를 공부하였다.</li>
<li>점차 코드가 복잡해지고 내가 작업한 내용을 스스로 파악하기 힘들어지면서 디버깅 과정에 난항을 겪게 되었으나 회의 시간을 통해 팀원들과 문제를 공유하면서 해결하는 경험을 하게 되었다.<h4 id="--feeling-느낌">- Feeling (느낌)</h4>
</li>
<li>백엔드를 지망하고 있고, 무엇보다 첫 프로젝트의 실패로 사실상 마지막인 기회를 놓치고 싶지 않았으나 한편으로는 성공 경험을 가진 팀원들을 통해 프로젝트의 완성이 우선이라는 생각이 들어 결정하기 힘들었다.</li>
<li>기본적인 기능 구현에 많은 신경을 쓰다보니 공부한 시간에 비해 학습 능률이 오르지 않았고, 결국 투입한 시간만큼 프로젝트에 기여하지 못한 것 같아 아쉬웠다.</li>
<li>자칫 형식적일 수 있는 회의 시간이 프로젝트 문제 해결에 있어 중요한 시간이라는 생각이 들었다.<h4 id="--finding-교훈">- Finding (교훈)</h4>
</li>
<li>오히려 취약한 부분이였던 React를 보강할 수 있는 기회가 되었던 것 같다.  </li>
<li>새로운 기술 스택에 대한 스터디와 활용 이전에 프로젝트에 꼭 필요한 것인지 그리고 정해진 시간내에 학습하여 사용할 수 있을 지 명확하게 판단하는 것이 필요한 것 같다.</li>
<li>문제해결에 매달리는 사람은 문제가 발생된 원인을 파악하기 힘들지만, 일련의 과정을 지켜본 팀원들의 눈을 통해서 그 원인이 파악될 수 있다는 점에서 결국 매일 아침, 저녁 30분에서 1시간 가량의 미팅과 코드리뷰가 헛된 것이 아니며 오히려 팀웍 형성에 긍정적인 영향을 가져온다는 것을 깨닫게 되었다.<h4 id="--future-action앞으로의-행동">- Future action(앞으로의 행동)</h4>
</li>
<li>역할을 스스로 제한하지 않고 협업에 도움이 되는 방향으로 과감하게 선택하는 것이 오히려 성장을 위해 필요할 것 같다. </li>
<li>본격적인 시간 자원을 투입하기 전에 탐색 기간을 가지고 실제 구현을 위한 사전 코딩을 실행해보는 과정을 거쳐야 겠다.</li>
<li>협업에서 어려운 문제에 부딪힐 수록 팀원들과 좀더 적극적으로 공유하기 위해 노력해야 겠다.</li>
</ul>
<h3 id="실력보다는-인성"><em>실력보다는 인성!</em></h3>
<p>데모 데이부터 잡서칭, 그리고 수료일까지 어떻게 일정이 지나갔는지 기억이 잘 나지 않을 정도로 주말동안 원없이 숙면을 취했다. 마치 부트캠프 과정이 오래 전 일처럼 느껴졌다. 하지만 처음부터 끝까지 하나의 프로젝트를 누군가와 함께 완성해 내었다는 첫 경험은 절대 잊지 못할 것이다.
지난 첫 프로젝트가 망가진 가장 큰 이유가 &quot;<strong>소통</strong>&quot;이었다면, 이번 프로젝트에서 원활한 소통을 통한 협업이 가능했던 이유는 팀원 모두의 &quot;<strong>인성</strong>&quot;이었던 것 같다. 회의 때마다 배려하고 존중하는 모습을 통해 신뢰감이 생겼고, 많은 코드리뷰와 디버깅 시간을 통해 문제해결의 어려움에 부딪히게 되었을 때 의지할 수 있었다. 
특히, 프로젝트 막바지 힘들고 지쳤을 때 아마 혼자였다면 그냥 포기하지 않았을까 싶은 순간에도 팀원들의 긍정적인 에너지를 통해 힘을 얻을 수도 있었다. 이 글을 빌려 다시 한번 팀원들에게 감사의 말을 전한다.</p>
<p>우여곡절이 많았지만 처음으로 배포한 프로젝트에 대한 기록으로 이 회고를 마무리하고자 한다.</p>
<blockquote>
<ol>
<li>프로젝트명 : Missing Animal</li>
<li>프로젝트 소개 : 잃어버린 반려동물을 쉽게 찾을 수 있도록 도와주는 지도기반 서비스</li>
<li>역할 : 팀원, 프론트엔드</li>
<li>스킬셋 : React, Hooks, Axios, React-Modal</li>
<li>작업 내용<ul>
<li>React 컴포넌트 작성</li>
<li>React 모달 작성</li>
<li>Axios CRUD AJAX 구현</li>
<li>페이지 CSS 작성</li>
<li>Amazon Web Service(S3, CloudFront, CM, Route53 설치 및 배포)</li>
</ul>
</li>
<li>관련 링크:<ul>
<li><a href="https://missinganimal.ml">배포 링크</a></li>
<li><a href="https://github.com/codestates/Missing_Animal_Client">깃헙 레포</a></li>
</ul>
</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Project Reflection : First Project 회고]]></title>
            <link>https://velog.io/@charlie-lyc/Project-Reflection-First-Project</link>
            <guid>https://velog.io/@charlie-lyc/Project-Reflection-First-Project</guid>
            <pubDate>Wed, 17 Feb 2021 14:05:13 GMT</pubDate>
            <description><![CDATA[<h3 id="첫-프로젝트가-폭망이라니"><em>첫 프로젝트가 폭망이라니...</em></h3>
<p>첫 번째 프로젝트의 회고를 뒤늦게 작성하게된 것은 메인인 두 번째 프로젝트가 연달아 이어져 시간적 여유가 없었다고 변명을 하고 싶지만, 소위 말해 &quot;<strong>폭망</strong>&quot;으로 이어진 매운 맛의 첫 경험을 최대한 감정적인 부분을 덜어내고 객관적인 관점에서 기록하고 싶었기 때문이었다. 
공식 기간은 2주일이지만 사실상 10일 정도의 첫 프로젝트는 마치 롤러코스터를 방불케 할 정도로 스펙태클하였다. 어쩌면 팀원간 협업을 하면서 발생할 수 있는 최악을 경험했을 지도 모를 일이다. 솔직히 기록으로 남기고 싶지 않은 생각도 있었으나 실패한 프로젝트라 하더라도 나름대로 기록으로 남겨 둠으로써 교훈으로 삼을 수 있을 것으로 판단되어 프로젝트 진행과정을 3부분 정도로 나뉘어 <strong>4F</strong>에 근거하여 회고를 작성하고자 한다.</p>
<blockquote>
<h3 id="전반전">&lt;전반전&gt;</h3>
</blockquote>
<h4 id="--fact-사실">- Fact (사실)</h4>
<ul>
<li>SR 미팅 도중 팀원과 커뮤니케이션 상의 오해가 발생하였다.</li>
<li>첫 번째 프로젝트에 대한 접근 방식이나 이해도가 서로 달랐다.</li>
<li>API 문서 작성과정에서 심각한 의견 충돌이 발생하였고 팀원 한명이 이탈하였다.  <h4 id="--feeling-느낌">- Feeling (느낌)</h4>
</li>
<li>본격적으로 프로젝트를 진행하기에 앞서 팀원들끼리 의견을 충분히 나누는 것이 중요하다고 생각하였고 어색한 분위기도 깰겸 얘기를 주도한 것이 마치 나의 제안을 주장하는 것으로 오해를 산 것 같았고, 이후에는 소통이 다소 위축되었다.</li>
<li>팀원의 잦은 미팅 결석과 지각, 그리고 다소 일방적인 프로젝트 진행에 대하여 불신감이 생성되었다.</li>
<li>각자의 포지션은 정해졌지만 팀원 모두에게 처음인 프로젝트에서 요구사항을 100% 다 구현해 낼 수 없다고 판단하였고, 같은 팀원에 대한 일방적인 요구는 도저히 납득하기 어려웠다. 포지션을 떠나 팀원들의 집단 지성을 이용한 문제해결과 협업이 더 중요하다고 생각하였다.  <h4 id="--finding-교훈">- Finding (교훈)</h4>
</li>
<li>이성적인 생각 또는 판단과는 별개로 진심으로 소통하기 위해 노력하는 것이 얼마나 어려운 일인지 새삼 깨닫게 되었다.</li>
<li>상호 불신감이 생성된 상태에서 제대로된 소통이 될 수 없었고 감정에 휘둘릴 수 밖에 없었던 것 같다.<h4 id="--future-action앞으로의-행동">- Future action(앞으로의 행동)</h4>
</li>
<li>팀장을 통한 의견 교환 등 커뮤니케이션에 필요한 최소한의 룰 등을 명확하게 정하는 것이 필요한 것 같다. </li>
</ul>
<blockquote>
<h3 id="후반전">&lt;후반전&gt;</h3>
</blockquote>
<h4 id="--fact-사실-1">- Fact (사실)</h4>
<ul>
<li>부트캠프 크루원이 중재하여 이탈했던 팀원과 함께 다시 프로젝트를 진행하였다.</li>
<li>다른 팀에 비해 다소 늦은 출발이였고, 프로젝트의 기본요구사항을 구현하기에도 빠듯한 남은 일정이었다.<h4 id="--feeling-느낌-1">- Feeling (느낌)</h4>
</li>
<li>간신히 프로젝트를 다시 시작하여 팀원을 얻었지만 팀원간 소통을 잃어버린 것 같았다. </li>
<li>다들 아까운 시간을 버린만큼 최선을 다해 시간과 노력을 투자하였지만, 결국 누군가는 나서서 남은 일정에 대한 조율을 말해야 했었는데 충돌을 피하기위해 어느 누구도 얘기를 꺼내지 못했다. <h4 id="--finding-교훈-1">- Finding (교훈)</h4>
</li>
<li>한 번 망가진 팀 분위기는 다시 회복되기 힘들었다.  <h4 id="--future-action앞으로의-행동-1">- Future action(앞으로의 행동)</h4>
</li>
<li>어떤 상황에서든 프로젝트와 관련된 의견을 거리낌 없이 나누고 소통하면서 협업하는 분위기를 유지하기 위한 노력이 필요한 것 같다. </li>
</ul>
<blockquote>
<h3 id="연장전">&lt;연장전&gt;</h3>
</blockquote>
<h4 id="--fact-사실-2">- Fact (사실)</h4>
<ul>
<li>발표 당일 불과 한 시간 전까지도 구현하지 못한 기능에 매달리느라 배포를 포기하였다.</li>
<li>배포를 하지못하였으므로 로컬환경에서 메인 랜딩페이지라도 로딩하고자 하였으나 디버깅에도 실패하였다.<h4 id="--feeling-느낌-2">- Feeling (느낌)</h4>
</li>
<li>너무 절망적이고 실망스러웠다.  <h4 id="--finding-교훈-2">- Finding (교훈)</h4>
</li>
<li>다른 팀의 발표를 보면서 첫 번째 프로젝트는 사실상 마지막 스프린트 과제의 연장선상에서 심플한 아이디어와 기능구현을 바탕으로 설치와 배포에 중점을 두어야 했다.<h4 id="--future-action앞으로의-행동-2">- Future action(앞으로의 행동)</h4>
</li>
<li>다음 협업 프로젝트에서 같은 실수를 반복하지 않아야 겠다.</li>
</ul>
<h3 id="갈등-상황보다-더-최악은-소통의-부재"><em>갈등 상황보다 더 최악은 소통의 부재!</em></h3>
<p>누군가와 배우고 익힌 것을 함께 나누고 생산적인 무언가를 만든다는 것은 상상만으로도 짜릿하고 재미있는 일이다. 다만 한 가지 큰 전제가 있다. 바로 협업과정에서 끝임없이 원활한 &quot;<strong>소통</strong>&quot;이 이루어져야 한다는 것이다. 이것은 첫 프로젝트를 통해 배운 가장 큰 교훈이다.</p>
<p>비록 폭망했지만 첫 프로젝트에 대한 기록으로 이 회고를 마무리하고자 한다.</p>
<blockquote>
<ol>
<li>프로젝트명 : Record</li>
<li>프로젝트 소개 : 개발자를 위한 간단하고 편리한 블로그 서비스</li>
<li>역할 : 팀원, 백엔드</li>
<li>스킬셋 : Node.js, Express.js / MySQL / Sequelize, JWT / AWS</li>
<li>작업내용<ul>
<li>데이터베이스 스키마 작성</li>
<li>Sequelize 모델 작성</li>
<li>Express.js server / router / controller, middleware 작성</li>
<li>JWT 회원가입, 로그인, 로그아웃, 회원정보 수정 구현</li>
<li>글 쓰기, 수정 및 삭제, 쓴 근 검색 구현</li>
<li>댓글 쓰기, 수정 및 삭제 구현</li>
<li>Amazon Web Service(RDS, EC2 설치)</li>
</ul>
</li>
<li>관련 링크 : <ul>
<li><a href="https://github.com/codestates/record-client/wiki/Software-Requirement">프로젝트 기획서</a></li>
<li><a href="https://github.com/codestates/record-server">깃헙 레포</a></li>
</ul>
</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL39: Deployment on Cloud with AWS]]></title>
            <link>https://velog.io/@charlie-lyc/TIL39-Deployment-on-Cloud-with-AWS</link>
            <guid>https://velog.io/@charlie-lyc/TIL39-Deployment-on-Cloud-with-AWS</guid>
            <pubDate>Sun, 17 Jan 2021 13:14:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Deployment with <strong>Amazon Web Service(AWS)</strong><ul>
<li><strong>Simple Storage Service(S3)</strong>: 빌드된 SPA(Single Page Application)을 배포</li>
<li><strong>Elastic Compute Cloud(EC2)</strong>: 작성된 Node API Server Applicaton을 배포</li>
<li><strong>Relational Database Service(RDS)</strong>: 관계형 데이터베이스를 구축하고 외부 접속 가능</li>
<li>AWS CLI: 터미널환경에서 S3에 배포 가능</li>
<li><strong>Route53</strong>: 도메인을 관리</li>
<li><strong>Certificat Manager(CM)</strong>: SSL을 발급</li>
<li><strong>CloudFront</strong>: S3 버킷에 SSL을 적용</li>
<li><strong>Elastic Load Balancer(ELB)</strong>: EC2에 SSL을 적용</li>
<li>Code Build, Code Deploy: CI(Continuous Integration, 지속적인 통합)/CD(Continuous Delivery or Deployment, 지속적인 전달 혹은 배포) 시스템 구축
<img src="https://images.velog.io/images/charlie-lyc/post/b22a2e8b-9bf5-409b-bc56-17816ef14086/Screen%20Shot%202021-01-17%20at%209.31.30%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Etc<ul>
<li>Jenkins, CircleCI : AWS 이외의 CI/CD 개발 도구 </li>
<li>Docker: 독립적인 배포환경 구축, 개발 및 테스트 그리고 운영이 동일한 환경에서 실행(&#39;실행 환경이 달라서...&#39;라는 말을 더이상 할 수 없음) </li>
</ul>
</li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL38: Authentication / OAuth 2.0]]></title>
            <link>https://velog.io/@charlie-lyc/TIL38-Authentication-OAuth-2.0</link>
            <guid>https://velog.io/@charlie-lyc/TIL38-Authentication-OAuth-2.0</guid>
            <pubDate>Sun, 17 Jan 2021 12:15:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Open Authentication(OAuth)</strong>: 보안화된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 개방형 표준 프로토콜(open standard protocol)<ul>
<li>이미 사용자의 정보를 가지고 있는 웹 서비스(Google, Facebook 등)로부터 <strong>authorization code</strong>를 받아 서버에 전달</li>
<li>서버에서는 전달받은 authorization code를 이용하여 인증하고 <strong>access token</strong>을 받아 접근권한을 관리(authorization)</li>
<li>모든 웹 서비스들의 아이디와 비밀번호 설정을 따로 기억하고 관리할 필요가 없음</li>
<li>검증되지 않는 서비스에 로그인시 민감한 정보의 노출 확률이 낮고 안전</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Social Login Logic Flow</strong>
<img src="https://images.velog.io/images/charlie-lyc/post/9aabc28d-714e-44e3-9904-477f7f880b44/Screen%20Shot%202021-01-17%20at%209.17.06%20PM.png" alt=""></li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL37: Authentication / Token]]></title>
            <link>https://velog.io/@charlie-lyc/TIL37-Authentication-Token</link>
            <guid>https://velog.io/@charlie-lyc/TIL37-Authentication-Token</guid>
            <pubDate>Sun, 17 Jan 2021 10:56:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Token</strong><ul>
<li>서버 또는 데이터베이스에 사용자 정보를 담는 경우 인증이 필요한 요청 때마다 서버와 데이터베이스를 살펴보아야 하는 부담을 가지게 되는데, 이 부담을 클라이언트에 넘기기 위해 고안</li>
<li>기존의 쿠키나 세션과 달리 토큰은 유저정보를 암호화한 상태로 담기 때문에 안전하게 클라이언트에 넘길 수 있음</li>
<li>아무리 암호화된다 하더라고 비밀번호 같은 민감한 정보는 담지 않아야 함</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Json Web Token(JWT)</strong>: 대표적인 토큰기반 인증<ul>
<li><strong>Header</strong>: 토큰 종류, 알리고리 종류</li>
<li><strong>Payload</strong>: 사용자 정보, 부여된 권한</li>
<li><strong>Signature</strong>: header 및 payload를 base64로 encoding한 값과 salt값의 조합으로 암호화된 값
<img src="https://images.velog.io/images/charlie-lyc/post/d8a373cb-ab88-467d-ba17-f6da040c77ee/Screen%20Shot%202021-01-17%20at%207.51.15%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL36: Authentication / Session]]></title>
            <link>https://velog.io/@charlie-lyc/TIL36-Authentication-Session</link>
            <guid>https://velog.io/@charlie-lyc/TIL36-Authentication-Session</guid>
            <pubDate>Sun, 17 Jan 2021 10:20:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Session</strong>: 서버가 클라이언트에게 유일하고 암호화된 ID를 부여하고 중요 데이터를 서버에 저장하여 관리<ul>
<li>사용자의 인증 정보를 session object에 저장하고, 쿠키에는 인증정보 대신 session ID를 저장</li>
<li>로그아웃 요청시 세션 아이디가 담긴 쿠키는 브라우저 탭을 닫으면 삭제되지만 유효기간이 따로 정해져 있지 않으므로 반드시 서버에서 세션을 파괴하는 과정을 거쳐야 함 
<img src="https://images.velog.io/images/charlie-lyc/post/dd8b3f10-6244-4433-9631-b7876526f2a9/Screen%20Shot%202021-01-17%20at%207.05.47%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>쿠키와 세션 비교</strong>
<img src="https://images.velog.io/images/charlie-lyc/post/90c71881-1d48-4e41-a975-6b20d0f44731/Screen%20Shot%202021-01-17%20at%207.10.05%20PM.png" alt=""></li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ TIL35: Authentication / Cookie]]></title>
            <link>https://velog.io/@charlie-lyc/TIL35-Authentication-Cookie</link>
            <guid>https://velog.io/@charlie-lyc/TIL35-Authentication-Cookie</guid>
            <pubDate>Sun, 17 Jan 2021 09:32:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Cookie</strong>: stateless(무상태성)의 HTTP에서 정보를 유지할 수 있게 서버가 일방적으로 클라이언트에 전달하여 브라우저에 저장하는 작은 데이터<ul>
<li>해당 도메인에 대해 쿠키가 존재하면 웹브라우저는 도메인에게 HTTP요청 시 쿠키를 함께 전달</li>
<li>사용자의 인증정보, 장바구니정보 등 장시간 보존해야하는 정보 저장에 적합</li>
<li>장시간 정보 저장의 장점은 오히려 자바스크립트에 의한 쿠키 접근 가능성을 높여 인증정보 유출의 가능성이 높음</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Cookie Options</strong><ul>
<li><strong>Domain</strong>: 도메인이 일치하는 경우 쿠키 전송</li>
<li><strong>Path</strong>: 경로가 일치하는 경우 쿠키 전송</li>
<li><strong>MaxAge or Expires</strong>: 쿠키의 유효기간 설정</li>
<li><strong>HttpOnly</strong>: 자바스크립트의 쿠키에 대한 접근 가능여부 결정(<code>false</code>인 경우 XSS공격에 취약)</li>
<li><strong>Secure</strong>: HTTPS에서만 쿠키 전송</li>
<li><strong>SameSite</strong>: CORS요청의 경우 옵션 및 메소드에 따라 쿠키 전송<ul>
<li><strong>Lax</strong>: Cross-Origin 요청일 경우 &#39;GET&#39;메소드에 대해서만 쿠키를 전송</li>
<li><strong>Strict</strong>:    Cross-Origin 요청일 경우 쿠키를 전송하지 않고, 오로지 Same-Site 요청(Origin과 서버도메인이 같은) 경우에만 쿠키를 전송</li>
<li><strong>None</strong>: 요청에 관계없이 항상 쿠키를 전송하되 <code>Secure: true</code>이어야 함</li>
</ul>
</li>
</ul>
</li>
</ul>
</blockquote>
<p><img src="https://images.velog.io/images/charlie-lyc/post/f0f3d1f8-14fe-43da-a3b2-f32c54b46ea5/Screen%20Shot%202021-01-17%20at%206.12.26%20PM.png" alt=""></p>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL34: Authentication, HTTPS, Hashing & Salt]]></title>
            <link>https://velog.io/@charlie-lyc/TIL34-Authentication-HTTPS-Hashing-Salt</link>
            <guid>https://velog.io/@charlie-lyc/TIL34-Authentication-HTTPS-Hashing-Salt</guid>
            <pubDate>Sun, 17 Jan 2021 07:02:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>HTTPS(HyperText Transfer Protocol over Secure)</strong><ul>
<li>HTTP 요청을 SSL(Secure Socket Layer) 또는 TLS(Transport Layer Security)를 이용해 통신 과정에서 내용을 암호화하여 데이터를 전송</li>
<li>로컬환경에서 HTTPS 서버를 만들기 위해 &#39;mkcert&#39;를 이용하여 인증서 발급 및 적용
<img src="https://images.velog.io/images/charlie-lyc/post/83bc01ea-9f26-4546-bd31-d9789bc3eb19/Screen%20Shot%202021-01-17%20at%203.24.42%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Hashing</strong>: 어떠한 문자열에 임의의 연산을 적용하여 다른 문자열로 변환하는 것<ul>
<li>해시 값을 계산하는데 오래걸리지 않아야 함</li>
<li>모든 값은 고유한 해시 값을 가지며, 이 때 해시 값들은 최대한 서로 피해야 함</li>
<li>아주 작은 단위의 변경이라도 완전히 다른 해시 값을 가져야 함
<img src="https://images.velog.io/images/charlie-lyc/post/f6076003-47f9-4b22-a10b-31c507f740e5/Screen%20Shot%202021-01-17%20at%203.41.38%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Salt</strong>: 별도의 값을 추가하여 해시를 진행함으로써 암호화만 되었을 경우 알고리즘이 노출되어 decoding되는 경우를 방지<ul>
<li>Hash 값<ul>
<li>기존: 암호화하려는 값 =&gt; hash 값</li>
<li>솔트사용: 암호화하려는 값 + salt 값 =&gt; hash 값</li>
</ul>
</li>
<li>유저와 패스워드 별로 각각 유일한 값을 가져야 함</li>
<li>계정 생성과 비밀번호를 변경할 때마다 새로운 salt를 사용해야 함</li>
<li>절대 재사용하면 안됨</li>
<li>DB의 유저 테이블에 같이  저장되어야 함
<img src="https://images.velog.io/images/charlie-lyc/post/3154c3db-be9d-4892-be5f-f530b5e4057d/Screen%20Shot%202021-01-17%20at%203.59.22%20PM.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL33: ORM / MVC]]></title>
            <link>https://velog.io/@charlie-lyc/TIL33-ORM-MVC</link>
            <guid>https://velog.io/@charlie-lyc/TIL33-ORM-MVC</guid>
            <pubDate>Thu, 14 Jan 2021 16:33:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Object Relational Mapping(ORM)</strong><ul>
<li><strong>관계형 데이터베이스</strong>와 <strong>객체지향 프로그래밍 언어</strong>간의 간극을 좁히고 <strong>호환성</strong>을 높이기 위해 고안된 프로그래밍 기법</li>
<li><strong>Sequelize</strong>: Promise 기반의 대표적인 Node.js ORM
<img src="https://images.velog.io/images/charlie-lyc/post/ed576ae7-f420-488c-9d9e-ef314f1f3551/image.png" alt="">
<img src="https://images.velog.io/images/charlie-lyc/post/903bacc1-7cd9-4df7-ad43-e3f0d554af62/image.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Model-View-Controller(MVC)</strong><ul>
<li>웹 애플리케이션 설계에 널리 사용되고 있는 <strong>Software Architecture Design Pattern</strong>  </li>
<li><strong>Model</strong> : database와 상호작용하여 data를 처리</li>
<li><strong>View</strong> : 사용자에게 보이는 화면의 시각적인 표현</li>
<li><strong>Controller</strong> : 사용자로부터 입력받은 요청을 처리하여 Model로 부터 데이터를 받아서 View로 전달
<img src="https://images.velog.io/images/charlie-lyc/post/4ef7d605-6f56-49d6-9b6d-bf5ca25528b7/image.png" alt=""></li>
</ul>
</li>
</ul>
</blockquote>
<pre><code class="language-js">// MVC pattern pseudo code example

// 1. routes : users/profile/:id =&gt; Users.getProfile(id)

// 2. controllers
class Users {
  function getProfile(id) {
      profile = this.UserModel.getProfile(id);
      renderView(&#39;users/profile&#39;, profile);
  }
}

// 3. models
class UserModel {
  function getProfile(id) {
    data = this.db.get(&#39;SELECT * FROM users WHERE id = id&#39;);
    return data;
  }
}</code></pre>
<pre><code class="language-html">// 4. views : users/profile
&lt;h1&gt;{{profile.name}}&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Email: {{profile.email}}&lt;/li&gt;
  &lt;li&gt;Phone: {{profile.phone}}&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL32: Database / SQL / RDBMS]]></title>
            <link>https://velog.io/@charlie-lyc/TIL32-Database-SQL-RDBMS-ORM</link>
            <guid>https://velog.io/@charlie-lyc/TIL32-Database-SQL-RDBMS-ORM</guid>
            <pubDate>Thu, 14 Jan 2021 15:43:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>In-memory : 전원이 꺼지면 데이터가 날라가버림</li>
</ul>
</blockquote>
<ul>
<li>File I/O : 원하는 데이터만 가져올 수 없고 모든 데이터를 가져와 필터링 과정을 거쳐야 함</li>
<li><strong>Database</strong> : 필터링 뿐만 아니라 File I/O로 구현이 힘든 여러 기능을 가지고 있는 데이터에 특화된 서버</li>
</ul>
<blockquote>
<ul>
<li><strong>Structured Query Language(SQL)</strong><ul>
<li>데이터베이스에서 사용하는 구조화된 질문 언어</li>
<li>데이터베이스에 Query를 보내어 원하는 데이터만 뽑아 냄</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Schema</strong><ul>
<li>데이터베이스에서 데이터가 구성되는 방식과 서로 다른 Entity간의 관계</li>
<li>데이터베이스의 &quot;청사진(Blue Print)&quot;이라고 할 수 있음</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Relational DataBase Management System(RDBMS)</strong><ul>
<li>계형 데이터 모델에 기초를 둔 관계형 데이터 베이스(Relational DataBase, RDB)를 생성, 수정 및 관리할 수 있는 소프트웨어</li>
<li><strong>MySQL</strong>, <strong>PostgreSQL</strong>, <strong>SQLite</strong>, <strong>MariaDB</strong> 등</li>
</ul>
</li>
</ul>
</blockquote>
<p>자료 출처: <a href="https://codestates.com/">코드스테이츠</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL31: ReactJS]]></title>
            <link>https://velog.io/@charlie-lyc/TIL31-ReactJS</link>
            <guid>https://velog.io/@charlie-lyc/TIL31-ReactJS</guid>
            <pubDate>Thu, 14 Jan 2021 14:21:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li><strong>Props</strong> and <strong>State</strong>
<img src="https://images.velog.io/images/charlie-lyc/post/1d53401c-2d2f-456a-aac9-b26f01e1e032/image.png" alt=""></li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Props Drilling</strong> and <strong>Lifting State Up</strong>
<img src="https://images.velog.io/images/charlie-lyc/post/dffe1185-9eb3-440b-b812-c989e1835dd0/image.png" alt="">
자료 출처: <a href="https://morioh.com/p/38dedcfb2d6b">props drilling diagram</a>
<img src="https://images.velog.io/images/charlie-lyc/post/f4084a10-1247-4fb0-80ed-2e525d901a7d/image.png" alt="">
자료 출처: <a href="https://jawblia.medium.com/lifting-state-up-and-passing-it-to-another-child-component-in-react-15d7036f1b40">lifting state up diagram</a></li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>Life Cycle Method</strong>
<img src="https://images.velog.io/images/charlie-lyc/post/fb718e28-d5aa-46ed-b492-cb5abc658236/image.png" alt=""></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP8: controllers/comments]]></title>
            <link>https://velog.io/@charlie-lyc/FP8-controllerscomments</link>
            <guid>https://velog.io/@charlie-lyc/FP8-controllerscomments</guid>
            <pubDate>Mon, 11 Jan 2021 00:01:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Json Web Token</li>
</ul>
</blockquote>
<ul>
<li>Sequelize</li>
</ul>
<pre><code class="language-js">// &#39;index.js&#39; from &#39;controllers/comments&#39; Directory
module.exports = {
  create: require(&#39;./create&#39;),
  read: require(&#39;./read&#39;),
  update: require(&#39;./update&#39;),
  remove: require(&#39;./remove&#39;),
};</code></pre>
<pre><code class="language-js">// &#39;create.js&#39; from &#39;controllers/comments&#39; Directory
const { Comment } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

    post: async (req, res) =&gt; {
        if (!req.body.contents || !req.body.postId) {
            res.status(400).json({
                data: null,
                message: &quot;insufficient parameters supplied&quot;
            });
        }
        if (!req.headers[&#39;authorization&#39;]) {
            res.status(403).json({ data: null, message: &quot;invalid access token&quot; });
        }
        const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
        const payload = await jwt.verify(accessToken, process.env.ACCESS_SECRET);
        const newComment = await Comment.create({
            userId: payload.id,
            contents: req.body.contents,
            postId: req.body.postId,
            username: req.body.username,
            profileUrl: req.body.profileUrl
        });
        const { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl } = newComment;
        res.status(201).json({
          commentData: { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl },
          message: &quot;created ok&quot;
        });
    }

};</code></pre>
<pre><code class="language-js">// &#39;read.js&#39; from &#39;controllers/comments&#39; Directory
const { Comment } = require(&#39;../../models&#39;);

module.exports = {

    getComments: async (req, res) =&gt; {
        if (!req.params.postId) {
            res.status(400).json({
                data: null,
                message: &quot;insufficient parameters supplied&quot;
            });
        }
        const foundComments = await Comment.findAll({
            where: {
                postId: req.params.postId
            }
        });
        const results = foundComments.map(comment =&gt; comment.dataValues);
        res.status(200).json({
          commentsData: results,
          message: &quot;ok&quot;
        });
    },

    getComment: async (req, res) =&gt; {
        if (!req.params.id) {
            res.status(400).json({
                data: null,
                message: &quot;insufficient parameters supplied&quot;
            });
        }
        const foundComment = await Comment.findOne({
            where: {
                id: req.params.id
            }
        });
        const { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl } = foundComment;
        res.status(200).json({
            commentData: { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl },
            message: &quot;ok&quot;
        });
    }

};</code></pre>
<pre><code class="language-js">// &#39;update.js&#39; from &#39;controllers/comments&#39; Directory
const { Comment } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

    put: async (req, res) =&gt; {
        if (!req.params.id || !req.body.contents || !req.body.postId) {
            res.status(400).json({
                data: null,
                message: &quot;insufficient parameters supplied&quot;
            });
        }
        if (!req.headers[&#39;authorization&#39;]) {
            res.status(403).json({ 
              data: null, 
              message: &quot;invalid access token&quot; 
            });
        }
        const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
        const payload = await jwt.verify(accessToken, process.env.ACCESS_SECRET);
        const foundComment = await Comment.findOne({
            where: {
                id: req.params.id
            }
        });
        if (!foundComment) {
            res.status(404).json({
                data: null,
                message: &quot;not found comment&quot;
            });
        } else {
            await Comment.update(
              {
                  contents: req.body.contents,
                  userId: payload.id,
                  postId: req.body.postId,
                  username: req.body.username,
                  profileUrl: req.body.profileUrl
              },
              {
                  where: {
                      id: req.params.id
                  }
              }
          );
          const updatedComment = await Comment.findOne({
            where: {id: req.params.id}
          })
          const { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl } = updatedComment;
          res.status(200).json({
            commentData: { id, contents, userId, postId, createdAt, updatedAt, username, profileUrl },
            message: &quot;updated ok&quot;
          });
        }
    }

};</code></pre>
<pre><code class="language-js">// &#39;remove.js&#39; from &#39;controllers/comments&#39; Directory
const { Comment } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

    delete: async (req, res) =&gt; {
        if (!req.params.id) {
            res.status(400).json({
              data: null,
              message: &quot;insufficient parameters supplied&quot;
            });
        }
        if (!req.headers[&#39;authorization&#39;]) {
          res.status(403).json({ 
            data: null, 
            message: &quot;invalid access token&quot; 
          });
        }
        const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
        const payload = await jwt.verify(accessToken, process.env.ACCESS_SECRET);
        const foundComment = await Comment.findOne({
            where: {
                id: req.params.id,
                userId: payload.id
            }
        });
        if (!foundComment) {
            res.status(404).json({
                data: null,
                message: &quot;not found comment&quot;
            });
        } else {
            await Comment.destroy({
                where: {
                    id: req.params.id
                }
            });
            res.status(200).json({
                data: null,
                message: &quot;removed ok&quot;
            });
        }
    }

};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP7: controllers/posts]]></title>
            <link>https://velog.io/@charlie-lyc/FP7-controllersposts</link>
            <guid>https://velog.io/@charlie-lyc/FP7-controllersposts</guid>
            <pubDate>Sun, 10 Jan 2021 23:45:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Json Web Token</li>
</ul>
</blockquote>
<ul>
<li>Sequelize</li>
</ul>
<pre><code class="language-js">// &#39;index.js&#39; from &#39;controllers/posts&#39; Directory
module.exports = {
  create : require(&#39;./create&#39;),
  read : require(&#39;./read&#39;),
  search : require(&#39;./search&#39;),
  tags : require(&#39;./tags&#39;),
  update : require(&#39;./update&#39;),
  remove : require(&#39;./remove&#39;)
};</code></pre>
<pre><code class="language-js">// &#39;create.js&#39; from &#39;controllers/posts&#39; Directory
const { Post, Tag, Post_Tag } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  post: async (req, res) =&gt; {
    if (!req.body.title || !req.body.contents) {
      res.status(400).json({ 
        data: null, 
        message: &quot;insufficient parameters supplied&quot; 
      });
    }
    if (!req.headers[&#39;authorization&#39;]) {
      res.status(403).json({ 
        data: null, 
        message: &quot;invalid access token&quot; 
      });
    }
    const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
    jwt.verify(accessToken, process.env.ACCESS_SECRET, async (err, decoded) =&gt; {
      if (err) {
        res.status(401).json({ 
          data: null, 
          message: &quot;not authorized&quot; 
        });
      }
      const newPost = await Post.create({
          userId: decoded.id,
          imageUrl: req.body.imageUrl,
          title: req.body.title,
          contents: req.body.contents
      });
      const {tagNames} = req.body;
      tagNames.map(async (tagName) =&gt; {
        await Tag.findOrCreate({
          where: {tagName: tagName}
        });
      })
      const foundTagIds = tagNames.map(async (tagName) =&gt; {
        const foundTag = await Tag.findOne({
          where: {tagName: tagName}
        });
        return foundTag.id;
      });
      foundTagIds.map(async (tagId) =&gt; {
        await Post_Tag.create({
          postId: newPost.id,
          tagId: tagId
        });
      })
      const {id, userId, title, contents, imageUrl, createdAt, updatedAt} = newPost.dataValues;
      res.status(201).json({
        postData: {id, userId, title, contents, imageUrl, createdAt, updatedAt}, 
        message: &quot;created ok&quot; 
      });
    });
  }

};</code></pre>
<pre><code class="language-js">// &#39;read.js&#39; from &#39;controllers/posts&#39; Directory
const { Post, User } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  getPosts: async (req, res) =&gt; {
    const allPosts = await Post.findAll();
    const resultPosts = allPosts.map(el =&gt; el.dataValues);
    const allUsers = await User.findAll();
    const resultUsers = allUsers.map(el =&gt; {
      return el.dataValues;
    });
    let results = [];
    resultPosts.forEach(post =&gt; {
      resultUsers.forEach(user =&gt; {
        if(post.userId === user.id) {
          results.push({...post, user})
        }
      })
    })
    res.status(200).json({
      results: results, 
      postsData: resultPosts, 
      usersData: resultUsers, 
      message: &quot;ok&quot;
    });
  },

  getUserPosts: async (req, res) =&gt; {
    if (!req.headers[&#39;authorization&#39;]) {
      res.status(400).json({
        data: null, 
        message: &quot;insufficient parameters supplied&quot;
      });
    }
    const ACCESS_TOKEN = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
    const payload = await jwt.verify(ACCESS_TOKEN, process.env.ACCESS_SECRET);
    const foundPosts = await Post.findAll({
      where: {
        userId: payload.id
      }
    })
    if (!foundPosts) {
      res.status(404).json({
        data: null, 
        message: &quot;not found posts&quot;
      });
    } else {
      const results = foundPosts.map(el =&gt; el.dataValues);
      res.status(200).json({
        postData: results, 
        message: &quot;ok&quot;
      });
    }
  },

  getPost: async (req, res) =&gt; {
    if (!req.params.id) {
      res.status(400).json({
        data: null, 
        message:&quot;insufficient parameters supplied&quot;
      });
    }
    const foundPost = await Post.findOne({
      where: {
        id: req.params.id
      }
    });
    const foundUser = await User.findOne({
      where: {
        id: foundPost.userId
      }
    })
    delete foundUser.dataValues.password;
    if (!foundPost) {
      res.status(404).json({
        data: null, 
        message: &quot;not found post&quot;
      });
    } else {
      const {id, title, contents, imageUrl, userId, like, createdAt, updatedAt} = foundPost;
      res.status(200).json({
        postData: { id, title, contents, imageUrl, userId, like, createdAt, updatedAt },
        userInfo: foundUser.dataValues,
        message: &quot;ok&quot;
      });
    }
  }

};</code></pre>
<pre><code class="language-js">// &#39;search.js&#39; from &#39;controllers/posts&#39; Directory
const { Post, Sequelize } = require(&#39;../../models&#39;);
const { Op } = require(&quot;sequelize&quot;);

module.exports = {

  get: async (req, res) =&gt; {
    const {keywords} = req.body;
    if (!keywords) {
      res.status(400).json({
        data: null, 
        message: &quot;insufficient parameters supplied&quot;
      });
    }
    const allPosts = await Post.findAll({
        where: {
          [Op.or]: [
              {title: {
                [Op.like]: `${keywords}%`,
                [Op.like]: `%${keywords}%`,
                // [Op.like]: `%${keywords}`
              }},
              {contents: {
                [Op.like]: `${keywords}%`,
                [Op.like]: `%${keywords}%`,
                // [Op.like]: `%${keywords}` 
              }}
          ]
        }
    });
    const results = allPosts.map(el =&gt; el.dataValues);
    res.status(200).json({
      postsData: results, 
      message: &quot;searched ok&quot;
    });
  }

};</code></pre>
<pre><code class="language-js">// &#39;tags.js&#39; from &#39;controllers/posts&#39; Directory
const { Post, Tag, Post_Tag } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  get: async (req, res) =&gt; {
    if (!req.params.id) {                                 
      res.status(400).json({ 
        data: null, 
        message: &quot;insufficient parameters supplied&quot; 
      });
    }
    const targetPost = await Post.findOne({               
      where: {
        id: req.params.id
      }
    });
    if (!targetPost) {
      res.status(404).json({
        data: null, 
        message: &quot;not found post&quot;
      });
    }
    const foundPostTags = await Post_Tag.findAll({
        where: {
          postId: req.params.id
        }
      });
    const foundTagIds = foundPostTags.map(async (el) =&gt; { 
      return el.dataValues.tagId;
    });
    const searchedTags = foundTagIds.map(async (tagId) =&gt; {
      const foundTag = await Tag.findOne({
        where: {id: tagId}
      });
      return foundTag;
    });
    const results = searchedTags.map(async (tag) =&gt; {
      return tag.dataValues.tagName;
    });
    res.status(200).json({
      tagsData: results, 
      message: &quot;ok&quot; 
    });
  }

};</code></pre>
<pre><code class="language-js">// &#39;update.js&#39; from &#39;controllers/posts&#39; Directory
const { Post, Post_Tag, Tag } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
const { Op } = require(&#39;sequelize&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  put: async (req, res) =&gt; {
    if (!req.body.title || !req.body.contents || !req.params.id) {
      res.status(400).json({ 
        data: null, 
        message: &quot;insufficient parameters supplied&quot; 
      });
    }
    if (!req.headers[&#39;authorization&#39;]) {
      res.status(403).json({ 
        data: null, 
        message: &quot;invalid access token&quot; 
      });
    }
    const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
    jwt.verify(accessToken, process.env.ACCESS_SECRET, async(err, decoded) =&gt; {
      if (err) {
        res.status(401).json({ 
          data: null, 
          message: &quot;not authorized&quot; 
        });
      }
      const targetPost = await Post.findOne({
        where: {
          id: req.params.id
        }
      });
      if (!targetPost) {
        res.status(404).json({
          data: null, 
          message: &quot;not found post&quot;
        });
      }
      await Post.update(
        {
          imageUrl: req.body.imageUrl,
          title: req.body.title,
          contents: req.body.contents
        },
        {
          where: {
            id: req.params.id
          }
        }
      );
      const updatedPost = await Post.findOne({
        where: {id: req.params.id}
      });
      const {tagNames} = req.body;
      tagNames.map(async(tagName) =&gt; {
        await Tag.findOrCreate({
          where: {tagName: tagName}
        });
      });
      const foundTagIds = tagNames.map(async (tagName) =&gt; {
        const foundTag = await Tag.findOne({
          where: {tagName: tagName}
        });
        return foundTag.id;
      });
      foundTagIds.map(async (tagId) =&gt; {
        await Post_Tag.findOrCreate({
          where: {
            tagId: tagId 
          }
        });
      });
      const {id, userId, title, contents, imageUrl, createdAt, updatedAt} = updatedPost.dataValues;
      res.status(200).json({
        postData: { id, userId, title, contents, imageUrl, createdAt, updatedAt },
        message: &quot;updated ok&quot; 
      });
    });
  }

};</code></pre>
<pre><code class="language-js">// &#39;remove.js&#39; from &#39;controllers/posts&#39; Directory
const { Post } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  delete: async(req, res) =&gt; {
    if (!req.params.id) {                                 
      res.status(400).json({ 
        data: null, 
        message: &quot;insufficient parameters supplied&quot; 
      });
    }
    if (!req.headers[&#39;authorization&#39;]) {
      res.status(403).json({ 
        data: null, 
        message: &quot;invalid access token&quot; 
      });
    }
    const accessToken = req.headers[&#39;authorization&#39;].split(&#39; &#39;)[1];
    jwt.verify(accessToken, process.env.ACCESS_SECRET, async(err, decoded) =&gt; {
      if (err) {
        res.status(401).json({ 
          data: null, 
          message: &quot;not authorized&quot; 
        });
      }
      const targetPost = await Post.findOne({
        where: {
          userId: decoded.id,
          id: req.params.id
        }
      });
      if (!targetPost) {
        res.status(404).json({
          data: null, 
          message: &quot;not found post&quot;
        });
      }
      await Post.destroy({
        where: {
          userId: decoded.id,
          id: req.params.id
        }
      });
      res.status(200).json({ 
        data: null, 
        message: &quot;removed ok&quot; 
      });
    });
  }

};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP6: controllers/users]]></title>
            <link>https://velog.io/@charlie-lyc/FP6-controllersusers</link>
            <guid>https://velog.io/@charlie-lyc/FP6-controllersusers</guid>
            <pubDate>Sat, 02 Jan 2021 18:57:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Json Web Token</li>
</ul>
</blockquote>
<ul>
<li>Sequelize</li>
</ul>
<pre><code class="language-js">// &#39;index.js&#39; from &#39;controllers&#39; Directory
module.exports = {
  usersController: require(&#39;./users&#39;),
  postsController: require(&#39;./posts&#39;),
  commentsController: require(&#39;./comments&#39;)
};</code></pre>
<pre><code class="language-js">// &#39;index.js&#39; from &#39;controllers/users&#39; Directory
module.exports = {
  register : require(&#39;./register&#39;),
  login : require(&#39;./login&#39;),
  logout : require(&#39;./logout&#39;),
  mypage : require(&#39;./mypage&#39;),
  accessTokenRequest: require(&#39;./accessTokenRequest&#39;),
  refreshTokenRequest: require(&#39;./refreshTokenRequest&#39;),
};</code></pre>
<pre><code class="language-js">// &#39;register.js&#39; from &#39;controllers/users&#39; Directory
const { User } = require(&#39;../../models&#39;);

module.exports = {

  post: async(req, res) =&gt; {
    const {email, username, password, passwordCheck} = req.body;
    if (!email || !password || !username || !passwordCheck) {
      res.status(422).json({
        data: null, 
        message: &quot;insufficient parameters supplied&quot;
      });
    }
    if (password !== passwordCheck) {
      res.status(400).json({
        data: null, 
        message: &quot;passwordCheck does not correspond with password&quot;
      });
    }
    const [newUser, created] = await User.findOrCreate({
      where: {email: email},
      defaults: {
        username: username,
        password: password,
      }
    })
    if (!created) {
      res.status(409).json({
        data: null, 
        message: &quot;email already exists&quot;
      });
    } else {
      const { id, username, email } = newUser;
      res.status(201).json({
        userInfo: { id, username, email }, 
        message: &quot;successfully registered!&quot;
      });
    }
  }

};</code></pre>
<pre><code class="language-js">// &#39;login.js&#39; from &#39;controllers/users&#39; Directory
const { User } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  post: async(req, res) =&gt; {
    const { email, password } = req.body;
    if (!email || !password) {
      res.status(422).json({
        data: null, 
        message: &quot;insufficient parameters supplied&quot;});
    }
    const userInfo = await User.findOne({
      where: {
        email:req.body.email, 
        password:req.body.password
      }
    });
    if (!userInfo) {
      res.status(401).json({
        data: null,
        message: &quot;not authorized&quot;
      });
    } else {
      const { id, username, email, profileUrl, nickname } = userInfo;
      const ACCESS_TOKEN = await jwt.sign(
        { id, username, email, profileUrl, nickname },
        process.env.ACCESS_SECRET, 
        {expiresIn: &#39;12h&#39;}
      );
      const REFRESH_TOKEN = await jwt.sign(
        { id, username, email, profileUrl, nickname },
        process.env.REFRESH_SECRET
      );
      res.cookie(&#39;refreshToken&#39;, REFRESH_TOKEN);
      res.status(200).json({
        accessToken: ACCESS_TOKEN, 
        message: &quot;successfully token issued!&quot;
      });
    }
  }

};</code></pre>
<pre><code class="language-js">// &#39;logout.js&#39; from &#39;controllers/users&#39; Directory
module.exports = {

  post: (req, res) =&gt; {
    if (!req.headers.cookie) {
      res.status(400).json({
        data: null, 
        message: &quot;invalid refresh token&quot;
      });
    }
    const refreshToken = req.headers.cookie.split(&#39;=&#39;)[1];
    if (!refreshToken) {
      res.status(401).json({
        data: null, 
        message: &quot;not authorized&quot;
      });
    } else {
      delete req.headers.authorization;
      res.clearCookie(&#39;refreshToken&#39;);
      res.status(200).json({
        data: null, 
        message: &quot;successfully log out!&quot;
      });
    }
  }

};</code></pre>
<pre><code class="language-js">// &#39;mypage.js&#39; from &#39;controllers/users&#39; Directory
const { User } = require(&#39;../../models&#39;)
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  get: async (req, res) =&gt; {
    if(!req.headers[&#39;authorization&#39;]) {
      res.status(400).json({data: null, message: &quot;insufficient parameters supplied&quot;});
    }
       const ACCESS_TOKEN = token.split(&#39; &#39;)[1];
    payload payload = await jwt.verify(ACCESS_TOKEN, process.env.ACCESS_SECRET);

    const foundUser = await User.findOne({
      where: {
        id: payload.id
      }
    })
    if(!foundUser) {
      res.status(404).json({
        data: null, 
        message: &quot;not found user&quot;
      });
    } else {
      const { id, username, email, profileUrl, githubUrl, introduce, nickname, created_at, updated_at } = foundUser;
      res.status(200).json({
        data: {
          userInfo: {
            id, username, email, profileUrl, githubUrl, introduce, nickname, created_at, updated_at
          }
        },
        message: &quot;ok&quot;
      });
    }

  },

  put: async (req, res) =&gt; {
    if(!req.headers[&#39;authorization&#39;]) {
      res.status(400).json({
        data: null, 
        message: &quot;insufficient parameters supplied&quot;
      });
    }
    const ACCESS_TOKEN = token.split(&#39; &#39;)[1];
    const payload = await jwt.verify(ACCESS_TOKEN, process.env.ACCESS_SECRET);
    const { username, profileUrl, githubUrl, introduce, nickname } = req.body;
    await User.update(
      { username, profileUrl, githubUrl, introduce, nickname },
      {
        where: {
            id: payload.id
        }
      }
    )
    const updatedUser = await User.findOne({
        where: {
            id: payload.id
        }
    });
    if(!updatedUser) {
      res.status(401).json({data: null, message: &quot;not authorized&quot;});
    } else {
      const { id, username, email, profileUrl, githubUrl, introduce, nickname, created_at, updated_at } = updatedUser;
      res.status(200).json({
        data: {
          userInfo: {
            id, username, email, profileUrl, githubUrl, introduce, nickname, created_at, updated_at
          }
        },
        message: &quot;ok&quot;
      });
    }
  }

};</code></pre>
<pre><code class="language-js">// &#39;accessTokenRequest.js&#39; from &#39;controllers/users&#39; Directory
const { User } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  get: async (req, res) =&gt; {
    if (!req.headers[&#39;authorization&#39;]) {
      res.status(400).json({
        data: null,
        message: &quot;invalid access token&quot;
      });
    }
    const ACCESS_TOKEN = token.split(&#39; &#39;)[1];
    const payload = await jwt.verify(ACCESS_TOKEN, process.env.ACCESS_SECRET);
    const userInfo = await User.findOne({
      where: {
        id: payload.id
      }
    })
    if (!userInfo) {
      res.status(401).json({
        data:null,
        message:&quot;access token has been tempered&quot;
      });
    } else {
      const { id, username, email, profileUrl, nickname } = userInfo;
        res.status(200).json({
          data: {
            userInfo: { id, username, email, profileUrl, nickname }
          }, 
          message: &quot;ok&quot;
        });
     }
  }

};</code></pre>
<pre><code class="language-js">// &#39;refreshTokenRequest.js&#39; from &#39;controllers/users&#39; Directory
const { User } = require(&#39;../../models&#39;);
const jwt = require(&#39;jsonwebtoken&#39;);
require(&#39;dotenv&#39;).config();

module.exports = {

  get: async (req, res) =&gt; {
    if (!req.headers.cookie) {
      res.status(400).json({
        data: null, 
        message: &quot;invalid refresh token&quot;
      });
    }
    const REFRESH_TOKEN = req.headers.cookie.split(&#39;=&#39;)[1];
    const refreshTokenData = await jwt.verify(REFRESH_TOKEN, process.env.REFRESH_SECRET);
    if (!refreshTokenData) {
      res.status(401).json({
        data: null,
        message: &quot;invalid refresh token please login again&quot;
      });
    }
    const userInfo = await User.findOne({
      where: {
        id: refreshTokenData.id
      }
    });
    if (!userInfo) {
      res.status(403).json({
        data: null,
        message: &quot;refresh token has been tempered&quot;
      });
    } else {
      const { id, email, username, profileUrl, nickname } = userInfo;
      const newAccessToken = await jwt.sign(
        { id, email, username, profileUrl, nickname }, 
        process.env.ACCESS_SECRET, 
        {expiresIn:&#39;2h&#39;}
      );
      res.status(200).json({
        data:{
          accessToken: newAccessToken,
          userInfo: { id, email, username, profileUrl, nickname },
        },
        message: &quot;ok&quot;
      });
    }
  }

};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP5: Express Router]]></title>
            <link>https://velog.io/@charlie-lyc/FP5-Express-Router</link>
            <guid>https://velog.io/@charlie-lyc/FP5-Express-Router</guid>
            <pubDate>Sat, 02 Jan 2021 03:59:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Express</li>
</ul>
</blockquote>
<ul>
<li>Express Router</li>
</ul>
<pre><code class="language-js">// &#39;users.js&#39; from &#39;routes&#39; Directory
const express = require(&#39;express&#39;);
const router = express.Router();
const { usersController } = require(&#39;../controllers&#39;);

router.post(&#39;/register&#39;, usersController.register.post);
router.post(&#39;/login&#39;, usersController.login.post);
router.post(&#39;/logout&#39;, usersController.logout.post);
router.get(&#39;/accessTokenRequest&#39;, usersController.accessTokenRequest.get);
router.get(&#39;/refreshTokenRequest&#39;, usersController.refreshTokenRequest.get);
router.get(&#39;/mypage/read&#39;, usersController.mypage.get);
router.put(&#39;/mypage/update&#39;, usersController.mypage.put);

module.exports = router;</code></pre>
<pre><code class="language-js">// &#39;posts.js&#39; from &#39;routes&#39; Directory
const express = require(&#39;express&#39;);
const router = express.Router();
const {postsController} = require(&#39;../controllers&#39;);

router.post(&#39;/create&#39;, postsController.create.post);
router.get(&#39;/read&#39;, postsController.read.getPosts);
router.get(&#39;/user/read&#39;, postsController.read.getUserPosts);
router.get(&#39;/:id/read&#39;, postsController.read.getPost);
router.get(&#39;/search&#39;, postsController.search.get);
router.get(&#39;/:id/tags/read&#39;, postsController.tags.get);
router.put(&#39;/:id/update&#39;, postsController.update.put);
router.delete(&#39;/:id/remove&#39;, postsController.remove.delete);

module.exports = router;</code></pre>
<pre><code class="language-js">// &#39;comments.js&#39; from &#39;routes&#39; Directory
const express = require(&#39;express&#39;);
const router = express.Router();
const { commentsController } = require(&#39;../controllers&#39;);

router.post(&#39;/create&#39;, commentsController.create.post);
router.get(&#39;/post/:postId/read&#39;, commentsController.read.getComment);
router.get(&#39;/:id/read&#39;, commentsController.read.getComments);
router.put(&#39;/:id/update&#39;, commentsController.update.put);
router.delete(&#39;/:id/remove&#39;, commentsController.remove.delete);

module.exports = router;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP4: Express Server]]></title>
            <link>https://velog.io/@charlie-lyc/FP4-Express-Server</link>
            <guid>https://velog.io/@charlie-lyc/FP4-Express-Server</guid>
            <pubDate>Fri, 01 Jan 2021 18:36:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>Express</li>
</ul>
</blockquote>
<ul>
<li>Cors</li>
<li>Cookie-parser</li>
</ul>
<pre><code class="language-js">// &#39;app.js&#39; from &#39;root&#39; Directory 
const express = require(&#39;express&#39;);
const cookieParser = require(&#39;cookie-parser&#39;);
const cors = require(&#39;cors&#39;);
require(&#39;dotenv&#39;).config();

const app = express();
// const path = require(&#39;path&#39;);
// app.use(express.static(path.join(__dirname, &#39;/public&#39;)));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(cors({
  origin: [ &#39;http://localhost:3000&#39;],
  methods: [&#39;OPTIONS&#39;,&#39;GET&#39;,&#39;POST&#39;,&#39;PUT&#39;,&#39;DELETE&#39;],
  credentials: true
}));

const usersRouter = require(&#39;./routes/users&#39;);
const postsRouter = require(&#39;./routes/posts&#39;);
const commentsRouter = require(&#39;./routes/comments&#39;);
app.use(&#39;/users&#39;, usersRouter);
app.use(&#39;/posts&#39;, postsRouter);
app.use(&#39;/comments&#39;, commentsRouter);

app.get(&#39;/&#39;, (req, res) =&gt; {
  // res.status(200).send(&#39;Hello world&#39;);
  res.redirect(&#39;/posts/read&#39;);
});

const port = process.env.baseUrl_port;
const baseUrl = process.env.baseUrl || localhost;
app.listen(port, () =&gt; {
  console.log(`App is running at http://${baseUrl}:${port}`);
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP3: AWS EC2, RDS, S3 / Sequelize-CLI]]></title>
            <link>https://velog.io/@charlie-lyc/FP3-AWS-EC2-RDS-S3-Sequelize-CLI</link>
            <guid>https://velog.io/@charlie-lyc/FP3-AWS-EC2-RDS-S3-Sequelize-CLI</guid>
            <pubDate>Fri, 01 Jan 2021 18:21:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>EC2</strong></p>
</blockquote>
<ul>
<li>&#39;Hello World&#39; 서버 생성 및 배포</li>
</ul>
<blockquote>
<p><strong>RDS</strong> </p>
</blockquote>
<ul>
<li>MySQL 서버 생성</li>
<li>EC2 연결</li>
<li>데이터 베이스 연결 설정: <code>config</code> Directory</li>
</ul>
<blockquote>
<p><strong>S3</strong> </p>
</blockquote>
<ul>
<li>React 클라이언트 최소 버전 배포</li>
</ul>
<blockquote>
<p><strong>Sequelize-CLI</strong></p>
</blockquote>
<ul>
<li>스키마 작성</li>
<li>모델 생성 : <code>models</code> Directory</li>
<li>마이그레이션:<code>migrations</code> Directory</li>
</ul>
<pre><code class="language-js">// &#39;config.js&#39; from &#39;config&#39; Directory
require(&#39;dotenv&#39;).config();

module.exports = {
  &quot;development&quot;: {
    &quot;username&quot;: process.env.DB_USER,
    &quot;password&quot;: process.env.DB_PASSWORD,
    &quot;database&quot;: process.env.DB_NAME,
    &quot;host&quot;: process.env.DB_HOST,
    &quot;port&quot;: process.env.DB_PORT,
    &quot;dialect&quot;: &quot;mysql&quot;,
    &quot;logging&quot;: false
  },
  &quot;test&quot;: {
    &quot;username&quot;: process.env.DB_USER,
    &quot;password&quot;: process.env.DB_PASSWORD,
    &quot;database&quot;: process.env.DB_NAME,
    &quot;host&quot;: process.env.DB_HOST,
    &quot;port&quot;: process.env.DB_PORT,
    &quot;dialect&quot;: &quot;mysql&quot;,
    &quot;logging&quot;: false
  },
  &quot;production&quot;: {
    &quot;username&quot;: process.env.DB_USER,
    &quot;password&quot;: process.env.DB_PASSWORD,
    &quot;database&quot;: process.env.DB_NAME,
    &quot;host&quot;: process.env.DB_HOST,
    &quot;port&quot;: process.env.DB_PORT,
    &quot;dialect&quot;: &quot;mysql&quot;,
    &quot;logging&quot;: false
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP2: Software Requirements - 2]]></title>
            <link>https://velog.io/@charlie-lyc/FP2-Software-Requirements-2</link>
            <guid>https://velog.io/@charlie-lyc/FP2-Software-Requirements-2</guid>
            <pubDate>Tue, 22 Dec 2020 19:23:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>8. API 문서 작성:</strong> 팀원과 상의해서 프로젝트에 어떤 API가 필요할지 정하고 API Docs로 정리.</p>
</blockquote>
<blockquote>
<p><strong>9. 프로젝트 태스크 카드 작성 &amp; 분배:</strong> 주어진 가용 시간에 맞추어 Github의 Project Reposotory에 Issue를 생성해서 Task Card 작성.</p>
</blockquote>
<ul>
<li>각 태스크 카드는 3시간을 넘지 않도록 작성</li>
<li>맡은 역할에 따라 본인의 역량에 따라 태스크 카드들을 분배    </li>
</ul>
<blockquote>
<p><strong>10. 마일스톤 작성</strong></p>
</blockquote>
<ul>
<li>구현하고자 하는 기능들에 대해 목표 정하기</li>
<li>각 목표에 맞게 Milestone 설정</li>
</ul>
<blockquote>
<p><strong>11. 팀 룰 작성</strong></p>
</blockquote>
<ul>
<li>Commit Message 규칙</li>
<li>Branch 규칙</li>
<li>Pull Request 관리</li>
<li>Isuue 관리</li>
<li>Coding 스타일<ul>
<li>변수 이름</li>
<li>파일, 생성자 이름</li>
<li>디자인 패턴(MVC Pattern, Atomic Pattern 등등)</li>
<li>Lint</li>
<li>버전 관리</li>
</ul>
</li>
<li>이 외에 필요한 Team Rule 팀원간 논의를 통해 설정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[FP1: Software Requirements - 1]]></title>
            <link>https://velog.io/@charlie-lyc/FP1-Software-Requirements</link>
            <guid>https://velog.io/@charlie-lyc/FP1-Software-Requirements</guid>
            <pubDate>Mon, 21 Dec 2020 17:19:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>1. 프로젝트 선정:</strong> 팀원간의 상의를 통해 아이디어를 산출하고, 그 중에서 Project Requirements 기능들이 전부 들어갈 수 있는 프로젝트 하나를 선택.</p>
</blockquote>
<blockquote>
<p><strong>2. 팀장, 팀명, 프로젝트명 정하기:</strong> 프로젝트명으로 Github Repository 생성.</p>
</blockquote>
<blockquote>
<p><strong>3. 팀원별 역할 정하기:</strong> 팀원간의 상의를 통해 Front-end, Back-end 역할 정하기. Full-stack을 맡게 되는 팀원이 있을 경우 반드시 분명한 이유 설명.</p>
</blockquote>
<blockquote>
<p><strong>4. 프로젝트 기획 및 범위 설정:</strong> Bare Minimum, Advanced 순서로 프로젝트에 구현하고자 하는 기능 목록 작성. 필수 Project Requirements 기능들은 반드시 포함.</p>
</blockquote>
<blockquote>
<p><strong>5. 시스템 아키텍쳐 설계:</strong> Functional Flow와 Wire Frame 작성</p>
</blockquote>
<blockquote>
<p><strong>6. 컴포넌트 구성:</strong> 팀원간 상의를 통해 Component 구성 작성</p>
</blockquote>
<blockquote>
<p><strong>7. 데이터베이스 스키마 작성:</strong> 기획한 프로젝트에 맞게 Databaxe Schema 작성</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL30: ReST API / CommonJS]]></title>
            <link>https://velog.io/@charlie-lyc/TIL30-ReST-API-CommonJS</link>
            <guid>https://velog.io/@charlie-lyc/TIL30-ReST-API-CommonJS</guid>
            <pubDate>Mon, 07 Dec 2020 17:08:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>ReST</strong>(Representational State Transfer)</p>
</blockquote>
<ul>
<li>웹 서비스를 만드는데 사용되는 제약(constraint) 모음 </li>
<li>웹을 망가뜨리지 않고 HTTP를 개선하기 위해 리소스마다 서로 다른 API 규칙을 적용</li>
<li>ReST에서 정보의 가장 핵심적인 추상화는 Resource</li>
<li>이름 붙일 수 있는 정보면 어떤 것이든 리소스가 될 수 있음</li>
</ul>
<blockquote>
<p><strong>Constraints</strong></p>
</blockquote>
<ul>
<li>Client-Server : HTTP</li>
<li>Stateless : HTTP</li>
<li>Cacheable : HTTP</li>
<li>Uniform Interface : HTTP</li>
<li>Layered System : HTTP</li>
<li>Code on Demand(Optional) : JavaScript</li>
</ul>
<blockquote>
<p><strong>Best Practices</strong></p>
</blockquote>
<ol>
<li>리소스를 나타내는 데 <strong>명사</strong>를 사용<ul>
<li>4가지 대분류 : collection, document, store, controller<pre><code class="language-js">/* 차례대로 collection, document, store, controller */
/user-management/users
/user-management/users/{id}
/cart-management/users/{id}/carts
/cart-management/users/{id}/cart/checkout</code></pre>
</li>
</ul>
</li>
<li><strong>일관성</strong>이 핵심<ul>
<li>계층 구조를 나타낼 때는 &#39;<strong>/</strong>&#39; 사용</li>
<li>URI 가독성을 높이기 위해 &#39;<strong>-</strong>&#39; 사용</li>
<li>URI에 <strong>소문자</strong> 사용</li>
<li>URI 끝에 &#39;/&#39; 사용하지 말 것</li>
<li>&#39;_&#39;은 사용하지 말 것</li>
<li>파일 확장자 사용하지 말 것</li>
</ul>
</li>
<li><strong>CRUD기능 이름</strong>은 URI에 <strong>사용하지 말 것</strong>: GET, POST, PUT, DELETE</li>
<li>filter가 필요하면 <strong>Query Component</strong> 사용<pre><code class="language-js">/* Query 사용*/
/managed-devices?region=USA&amp;brand=XYZ&amp;sort=installation-date</code></pre>
</li>
</ol>
<blockquote>
<p><strong>CommonJS</strong></p>
</blockquote>
<ul>
<li>모든 모듈은 자신만의 독립적인 실행 영역이 있음</li>
<li>모듈 정의는 전역객체인 <code>exports</code> 객체를 이용</li>
<li>모듈 사용은 <code>require</code> 함수를 이용</li>
</ul>
<p><strong>module.exports</strong> VS <strong>exports</strong></p>
<p>(1) module.exports</p>
<pre><code class="language-js">// hello.js
exports.anything = function() {
  console.log(&#39;I am anything.&#39;);
};
// hello-runner.js
const hello = require(&#39;./hello&#39;);
console.log(hello); // {anything: Function}
hello.anything(); // I am anything.</code></pre>
<p>(2) exports</p>
<pre><code class="language-js">// hello.js
module.exports.anything = function() {
  console.log(&#39;I am anything.&#39;);
};
// hello-runner.js
const hello = require(&#39;./hello&#39;);
console.log(hello); // {anything: Function}
hello.anything(); // I am anything.</code></pre>
<p><code>exports</code>는 <code>module.exports</code>사용을 도와주는 helper로써 단지 <code>module.exports</code>를 참조할 뿐임</p>
<pre><code class="language-js">// hello.js file
module.exports = {a: 1}
// hello-runner.js
const hello = require(&#39;./hello&#39;);
console.log(hello); // {a: 1}</code></pre>
<pre><code class="language-js">// hello.js file
exports = {a: 1}
// hello-runner.js
const hello = require(&#39;./hello&#39;);
console.log(hello); // { }</code></pre>
<p>자료 출처: <a href="https://www.codestates.com/">코드스테이츠(CodeStates)</a></p>
]]></description>
        </item>
    </channel>
</rss>