<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>at_This_Very_Moment.log</title>
        <link>https://velog.io/</link>
        <description>어제보다 나은 오늘을 만드는 중</description>
        <lastBuildDate>Fri, 13 May 2022 05:51:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. at_This_Very_Moment.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kaitlin_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[nestjs] middleware]]></title>
            <link>https://velog.io/@kaitlin_k/nestjs-middleware</link>
            <guid>https://velog.io/@kaitlin_k/nestjs-middleware</guid>
            <pubDate>Fri, 13 May 2022 05:51:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="nestjs">nestjs</h3>
<p>nestjs 공식문서를 부분적으로 정리한 내용입니다. </p>
</blockquote>
<h3 id="middleware-작성하기">middleware 작성하기</h3>
<p>미들웨어는 라우트 핸들러가 실행되기 <strong>전에</strong> 실행되는 함수이다. 미들웨어는 요청과 응답 객체에 접근할 수 있으며 <code>next()</code>로 미들웨어를 호출한다. 
네스트 미들웨어는 기본적으로 익스프레스 미들웨어와 동일하다. 다음은 익스프레스 공식문서에 작성된 미들웨어에 대한 설명이다.
<img src="https://velog.velcdn.com/images/kaitlin_k/post/288a4aa3-0b4a-401e-936a-7debedca987d/image.png" alt=""></p>
<hr>
미들웨어 함수는 다음과 같은 기능을 수행한다:
- 코드를 실행한다.
- 요청과 응답객체를 변경할 수 있다.
- 요청-응답 사이클을 끝낼 수 있다.
- 다음 미들웨어를 호출한다.
- 만약 현재 실행중인 미들웨어가 요청-응답 사이클을 끝내지 않는다면, `next()`를 호출해서 다음 미들웨어가 실행되도록 해야한다. 
<hr>

<pre><code class="language-js">import { Injectable, NestMiddleware } from &#39;@nestjs/common&#39;;
import { Request, Response, NextFunction } from &#39;express&#39;;

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(&#39;LoggerMiddleware is working now&#39;);
    next();
  }
}</code></pre>
<ul>
<li>네스트에서는 커스텀 미들웨어를 함수 또는 클래스의 형태로 적용할 수 있으며 <code>@Injectable()</code> 데코레이터를 사용한다. </li>
<li>함수가 특별한 요구사항이 없는 경우, 클래스는 <code>NestMiddleware</code> 인터페이스를 implement해야한다.</li>
<li>네스트 미들웨어 역시 dependency injection이 가능하다. 기존과 마찬가지로 <code>constructor</code>를 통해 설정할 수 있다.</li>
</ul>
<h3 id="middleware-적용하기">middleware 적용하기</h3>
<p>생성한 미들웨어를 적용해보자. @Module에서 미들웨어를 불러올때는 모듈 클래스의 <code>configure()</code> 메소드를 사용한다. 미들웨어를 사용하는 모듈은 <code>NestModule</code>을 implement해야한다. 다음은 LoggerMidleware를 AppModule 레벨에 적용한 예시이다. </p>
<pre><code class="language-js">import { Module, NestModule, MiddlewareConsumer } from &#39;@nestjs/common&#39;;
import { LoggerMiddleware } from &#39;./common/middleware/logger.middleware&#39;;
import { CatsModule } from &#39;./cats/cats.module&#39;;

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(&#39;cats&#39;);
  }
}

//만약 특정 메소드에 대해서만 미들웨어를 실행시키고 싶은 경우
.forRoutes({ path: &#39;cats&#39;, method: RequestMethod.GET });</code></pre>
<ul>
<li>위의 예시는 LoggerMiddleware를 <code>/cats</code> 라우트 핸들러에 적용했다. 따라서 <code>/cats</code>로 들어오는 모든 요청에 대해서는 LoggerMiddleware가 실행될 것이다.</li>
<li>또한, forRoutes 메소드에 객체를 전달하여 미들웨어가 요청들 중 특정 메소드에 대해서만 실행되도록 설정할 수 있다. <ul>
<li>route의 path에 와일드카드를 사용할 수도 있다. </li>
</ul>
</li>
<li>configure 메소드는 async/await 키워드를 사용해서 비동기로 실행시킬 수 있다. </li>
</ul>
<h3 id="middleware-consumer">middleware consumer</h3>
<pre><code class="language-js">import { Module, NestModule, MiddlewareConsumer } from &#39;@nestjs/common&#39;;
import { LoggerMiddleware } from &#39;./common/middleware/logger.middleware&#39;;
import { CatsModule } from &#39;./cats/cats.module&#39;;
import { CatsController } from &#39;./cats/cats.controller&#39;;

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}</code></pre>
<p>위의 예시에 configure 메소드 내에 consumer의 타입이 MiddlewareConsumer로 지정되어있다. 이는 helper class로 미들웨어를 다루기 위한 내장 메소드를 제공한다. 위에서 작성된대로 fluent style로 체이닝할 수 있다.</p>
<ul>
<li><p>forRoutes 메소드 인자로는 string, multiple strings, a RouteInfo Object, a controller class, multiple controller classes 등이 올 수 있다. 일반적으로는, 여러개의 controller들을 쉼표로 이어 작성할 것이다.</p>
<ul>
<li>위의 예시에서는 하나의 컨트롤러(CatsController)에 대한 미들웨어를 설정해주었다. </li>
</ul>
</li>
<li><p>apply 메소드는 하나의 미들웨어를 입력받거나 여러개를 입력받을 수 있다. </p>
<ul>
<li><code>consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);</code></li>
<li>이 부분은 <a href="https://docs.nestjs.com/middleware#multiple-middleware">공식문서</a> 참고!</li>
</ul>
</li>
<li><p>특정 라우트에 대해서 설정해줄 수 있는 것과 마찬가지로 <code>exclude</code> 메소드를 사용하여 특정 라우트를 제외시킬 수도 있다. exclude 메소드는 string, multiple strings, a RouteInfo Object 등을 인자로 받는다. 아래의 예시는 GET /cats, POST /cats, cats/.* 등에 대해서는 미들웨어가 실행되지 않는다.</p>
</li>
</ul>
<pre><code class="language-js">consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: &#39;cats&#39;, method: RequestMethod.GET },
    { path: &#39;cats&#39;, method: RequestMethod.POST },
    &#39;cats/(.*)&#39;,
  )
  .forRoutes(CatsController);</code></pre>
<h3 id="functional-middleware">Functional middleware</h3>
<p>앞선 예시들에서는 클래스로 미들웨어를 생성했는데, 함수로도 할 수 있다. 다음은 클래스 기반 미들웨어를 함수형태로 변경한 예시이다. </p>
<pre><code class="language-js">//logger.middleware.ts
import { Request, Response, NextFunction } from &#39;express&#39;;

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

//app.module.ts
mport { MiddlewareConsumer, Module, NestModule } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { CatsController } from &#39;./cats/cats.controller&#39;;
import { CatsModule } from &#39;./cats/cats.module&#39;;
import { logger } from &#39;./common/middleware/logger.middleware&#39;;

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(logger).forRoutes(CatsController); //apply 시키는 함수명만 변경됨
  }
}</code></pre>
<h3 id="global-middleware">Global middleware</h3>
<p>모든 라우트에서 실행되는 미들웨어를 만들고 싶다면, main.ts 파일에서 생성된 app에 대해 <code>use()</code>를 사용하면 된다.</p>
<pre><code class="language-js">//main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);</code></pre>
<ul>
<li>함수형 미들웨어를 쓰는 경우, app.use를 사용할 수 있고, 클래스 미들웨어를 쓴다면, <code>.forRoutes(&#39;*&#39;)</code>와 같이 작성해줄 수 있다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[nestjs] modules]]></title>
            <link>https://velog.io/@kaitlin_k/nest-modules</link>
            <guid>https://velog.io/@kaitlin_k/nest-modules</guid>
            <pubDate>Fri, 13 May 2022 05:05:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="nestjs">nestjs</h3>
<p>nestjs 공식문서를 부분적으로 정리한 내용입니다. </p>
</blockquote>
<h3 id="shared-modules">shared modules</h3>
<pre><code class="language-js">import { Global, Module } from &#39;@nestjs/common&#39;;
import { CatsController } from &#39;./cats.controller&#39;;
import { CatsService } from &#39;./cats.service&#39;;

@Global() //어디서든 사용가능한 모듈로 만들어주는 데코레이터 -&gt; 다른 모듈에서 따로 import 안해도 됨.
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}
</code></pre>
<ul>
<li><p>생성된 모듈은 모두 공유가능하다. 만약 catsservice를 다른 모듈에서도 사용하고 싶다면, @module 데코레이터의 
<code>exports</code> 속성에 위와 같이 작성해주고, 사용할 모듈의 <code>@Module</code> 데코레이터에서 import해오면 된다. 
-&gt; modules can export their internal providers</p>
<ul>
<li><p>re-export도 가능하다. </p>
</li>
<li><p>Angular에서는 글로벌 스코프로 provider가 등록되어 어디서든 사용가능하다. 네스트는 모듈 스코프에 provider를
캡슐화하기 때문에 먼저 import해서 써야한다. 만약, 모든 곳에서 사용가능하도록 만들고 싶다면, <code>@Global()</code> 데코레이터를 사용하면 된다. </p>
<ul>
<li>글로벌 모듈을 모든 곳에서 사용하는 것은 추천하지 않으며 보일러플레이트를 줄일때 사용하는 것이 적절하다. <code>import</code> 해오는 걸 추천!</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="dynamic-modules"><a href="https://docs.nestjs.com/fundamentals/dynamic-modules">dynamic modules</a></h3>
<pre><code class="language-js">//database.module.ts
import { Module, DynamicModule } from &#39;@nestjs/common&#39;;
import { createdDatabaseProviders } from &#39;./database.providers&#39;;
import { Connection } from &#39;./connection.provider&#39;;

@Module({
  providers: [Connection],
})

export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProvider(options, entities);
    return {
      global: true, // 동적 모듈을 글로벌 스코프로 만들고 싶다면 이 속성 추가
      module: DatabaseModule, 
      providers: provider,
      exports: providers,
    }
  }
}

//app.module.ts
import { Module } from &#39;@nestjs/common&#39;;
import { DatabaseModule } from &#39;./database/database.module&#39;;
import { User } from &#39;./users/entities/user.entity&#39;;

@Module({
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}

// 만약 동적 모듈을 import한 모듈에서 다시 export하는 경우
@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],
})</code></pre>
<p>네스트에는 dynamic module이라는 기능이 있는데, 이를 통해 모듈을 커스터마이징하거나 provider를 동적으로 설정할 수 있다. </p>
<ul>
<li><code>forRoot()</code>는 동기 또는 비동기의 동적 모듈을 리턴한다 </li>
<li>동적 모듈에 의해 리턴된 속성들은 <code>@Module</code> 데코레이터에 의해 정의된 모듈 메타 데이터를 확장(extend)한다. (rather than override) 따라서 정적으로 선언된 Connection provider와 동적으로 생성된 레포 provider가 export될 수 있는 것이다. <ul>
<li>이부분은 추가 공부 필요!  </li>
</ul>
</li>
<li>이렇게 생성된 모듈은 다른 모듈에서 import해서 사용할 수 있다. <ul>
<li>만약 동적 모듈을 import한 모듈에서 다시 export하고 싶다면 export 배열에는 forRoot 메소드를 작성하지 않아도 된다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[nestjs] Controllers]]></title>
            <link>https://velog.io/@kaitlin_k/nestjs-Controllers</link>
            <guid>https://velog.io/@kaitlin_k/nestjs-Controllers</guid>
            <pubDate>Thu, 12 May 2022 08:58:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="nestjs">nestjs</h3>
<p>nestjs 공식문서를 부분적으로 정리한 내용입니다. </p>
</blockquote>
<hr>

<blockquote>
<h3 id="controllers">controllers</h3>
<p>controller는 요청을 받고 그에 따른 응답을 클라이언트에게 넘겨주는 역할을 한다.</p>
</blockquote>
<p>routing은 특정 controller가 특정 요청을 받도록 제어하는 역할을 한다. 대체로 각 controller는 하나 이상의 라우트를 가지며, 다른 라우트들은 다른 액션을 수행한다.</p>
<p>기본적인 controller를 만들기 위해 클래스와 데코레이터를 사용한다. 데코레이터는 클래스와 필요한 메타데이터를 연관짓고(associate) 네스트가 라우팅 맵을 만들수 있도록 한다. (tie requests to the corresponding controllers)</p>
<blockquote>
<h3 id="routing">routing</h3>
</blockquote>
<p>다음 예시에서는 기본적인 컨트롤러 정의에 필요한 <strong>@Controller 데코레이터</strong>를 사용한다. <strong>옵셔널 라우트 패스의 접두어</strong>로 cats를 사용했는데, 이처럼 라우트 패스 접두어를 사용하면 데코레이터로 하여금 연관된 라우트들을 그룹핑하기 쉽게 만들며, 코드반복을 최소화할 수 있다. </p>
<pre><code class="language-js">//cats.controller.ts
import { Controller, Get } from &#39;@nestjs/common&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(): string {
    return &#39;This action returns all cats&#39;;
  }
}</code></pre>
<ul>
<li><p><code>@Get</code>
Nest가 요청에 대한 특정 엔드포인트를 만들 수 있도록 한다. 이 경우 <code>GET /cats</code>로 요청을 보낼 수 있게 된다. 여기서 cats로 생성된 것은 우리가 파일명을 <code>cats.controller.ts</code>로 생성했기 때문이며 @Get의 인자로 어떠한 것도 작성해주지 않았기 때문이다. 만약, <code>@Get(&#39;profile&#39;)</code>로 작성되었다면 <code>GET /cats/profile</code>이 될 것이다.</p>
</li>
<li><p><code>findAll</code></p>
<ul>
<li><code>GET /cats</code>로 요청이 왔다면, 네스트는 요청을 <code>findAll</code>로 라우팅한다. 이 메소드 이름은 임의로 설정된 것이며 변경가능하다. </li>
<li>이 메소드는 200 상태코드와 관련된 응답을 리턴하며, 이 예시에서는 문자열을 리턴한다. </li>
<li>네스트가 응답을 보내는 두가지 방식이 있는데, 자세한 내용은 <a href="https://docs.nestjs.com/controllers">공식문서</a>를 참고하자</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="request-object">Request object</h3>
</blockquote>
<p>네스트는 <code>@Req()</code> 데코레이터를 핸들러의 시그니처(signature)에 작성하도록 함으로써 요청 객체 (request object)에 접근할 수 있도록 한다.</p>
<ul>
<li>아래의 예시 코드에서는 express로부터 Request 객체를 import해오는데, 네스트에서 디폴트로 사용하는 플랫폼은 Express이며, 다른 플랫폼으로는 fastify 등이 있다. </li>
<li>만약 에러가 발생한다면, <code>npm i -D @types/express</code>로 패키지를 설치한다.</li>
<li>여기서 요청 객체에는 요청 쿼리스트링, 파라미터, 헤더, 바디 등의 속성이 포함되어있다. 대부분의 경우에는 이러한 속성들을 각각 꺼내서 사용하지 않고, 지정된 데코레이터를 대신 사용하는데, 예를 들면 <code>@Body()</code>, <code>@Query()</code> 등이 있다. <ul>
<li>자세한 데코레이터에 대한 설명은 <a href="https://docs.nestjs.com/controllers">공식문서</a>를 참고하자</li>
</ul>
</li>
<li>네스트는 <code>@Res</code> 데코레이터도 제공하는데, 네이티브 플랫폼의 응답 객체 인터페이스로, 이를 사용할 경우 메소드 핸들러에 <code>@Res()</code>를 작성해주어야 한다. 이렇게 직접 작성해주는 경우, 위에서 언급한 네스트가 응답을 보내는 두가지 방식 중 Library-specific mode에 해당된다. 따라서, 직접 <code>res.json()</code> 또는 <code>res.send()</code>의 형태로 응답을 보내주어야한다.<pre><code class="language-js">//cats.controller.ts
import { Controller, Get, Req } from &#39;@nestjs/common&#39;;
import { Request } from &#39;express&#39;;
</code></pre>
</li>
</ul>
<p>@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return &#39;This action returns all cats&#39;;
  }
}</p>
<pre><code>
&gt; ### Resources
네스트는 모든 HTTP 메소드에 대한 데코레이터를 제공한다. 
 `@Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), @Head(), and @All`

- `@Post`
앞서서 고양이 리소스 데이터를 fetch해오는 엔드포인트를 작성했다면, 새로운 레코드를 생성하는 엔드포인트를 만들 수 있다. 이는 다음의 예시처럼 `@Post`를 사용한다.



```js
import { Controller, Get, Post, Req } from &#39;@nestjs/common&#39;;
import { Request } from &#39;express&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Post() //추가
  create(): string {
    return &#39;This action adds a new cat&#39;;
  } //추가

  @Get()
  findAll(@Req() request: Request): string {
    return &#39;This action returns all cats&#39;;
  }
}</code></pre><blockquote>
<h3 id="route-wildcards">Route wildcards</h3>
</blockquote>
<ul>
<li>다음 예시에는 아스트리크가 와일드카드로 사용되었는데, abcd, ab_cd, abecd 등의 경우가 매칭된다. 아스트리크 외에도 <code>?</code>, <code>+</code>, <code>*</code>, <code>()</code> 등이 사용될 수 있다.</li>
</ul>
<pre><code class="language-js">@Get(&#39;ab*cd&#39;)
findAll() {
  return &#39;This route uses a wildcard&#39;;
}</code></pre>
<blockquote>
<h3 id="status-code">status code</h3>
</blockquote>
<ul>
<li>상태코드는 디폴트로 200이 전달되며 (post의 경우 201) <code>@HttpCode</code> 데코레이터를 사용해 변경할 수 있다.<ul>
<li><code>import { HttpCode } from @nestjs/common</code><pre><code class="language-js">@Post()
@HttpCode(204)
create() {
return &#39;This action adds a new cat&#39;;
}</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="headers">Headers</h3>
</blockquote>
<ul>
<li>커스텀 헤더를 정의할때에는 <code>@Header</code> 데코레이터를 사용하거나 특정 라이브러리에 해당되는 응답 객체를 사용할 수 있다. (<code>req.header()</code>)<ul>
<li><code>import { Header } from @nestjs/common</code><pre><code class="language-js">@Post()
@Header(&#39;Cache-Control&#39;, &#39;none&#39;)
create() {
return &#39;This action adds a new cat&#39;;
}</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="redirection">Redirection</h3>
</blockquote>
<ul>
<li>특정 URL로 리다이렉트 시킬때에는 <code>@Redirect()</code> 데코레이터 또는 <code>res.redirect()</code>와 같은 특정 응답 객체를 사용할 수 있다. </li>
<li><code>@Redirect(url, statusCode)</code>의 형태이며 두 개의 인자 모두 옵셔널하다. 상태코드의 디폴트 값은 302이다.</li>
<li>만약 동적으로 url 등을 변경하고 싶다면, url과 status를 값으로 가지는 객체를 리턴시키면 된다. 리턴된 객체의 값이 <code>@Redirect()</code> 데코레이터에 전달된 값을 덮어씌운다. </li>
</ul>
<pre><code class="language-js">@Get()
@Redirect(&#39;https://nestjs.com&#39;, 301)

//동적으로 변경하고 싶은 경우
@Get(&#39;docs&#39;)
@Redirect(&#39;https://docs.nestjs.com&#39;, 302)
getDocs(@Query(&#39;version&#39;) version) {
  if (version &amp;&amp; version === &#39;5&#39;) {
    return { url: &#39;https://docs.nestjs.com/v5/&#39; };
  }
}</code></pre>
<blockquote>
<h3 id="route-parameters">Route parameters</h3>
</blockquote>
<ul>
<li><p>요청 URL에 포함되어있는 파라미터 정보는 <code>@Get()</code> 데코레이터에 다음과 같이 파라미터 토큰을 작성하여 꺼내올 수 있다. 여기서는 특정 id의 고양이 데이터를 조회하는 것이므로 메소드 이름이 findOne인 것도 확인하 수 있다.</p>
</li>
<li><p>두번째 예시와 같이 파라미터의 특정 값을 꺼내올 수 있다. </p>
<pre><code class="language-js">@Get(&#39;:id&#39;)
findOne(@Param() params): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

@Get(&#39;:id&#39;)
findOne(@Param(&#39;id&#39;) id:string): string {
 returh `This action returns a #${id} cat`
}</code></pre>
</li>
</ul>
<blockquote>
<h3 id="asynchronicity">Asynchronicity</h3>
</blockquote>
<ul>
<li>자바스크립트는 데이터를 비동기 방식으로 추출한다. 따라서 네스트도 async 함수를 지원한다. </li>
<li>모든 async 함수는 프로미스를 리턴한다. 즉 네스트가 resolve할 수 있는 연기된/미뤄진 값(deferred value)를 리턴할 수 있다는 것을 의미한다.</li>
<li>아래 두가지 경우 모두 사용가능하며 필요에 따라 선택해서 사용하면 된다. 두번째 예시는 조금 더 강력한 기능을 제공하는데, 네스트의 라우트 핸들러는 RxJS ovservable streams를 리턴할 수 있다.  <ul>
<li><code>RxJS observable streams</code>에 대해서는 추가적인 공부가 필요할 것 같다.<pre><code class="language-js">@Get()
async findAll(): Promise&lt;any[]&gt; {
return [];
}
</code></pre>
</li>
</ul>
</li>
</ul>
<p>//RxJS observable streams
@Get()
findAll(): Observable&lt;any[]&gt; {
  return of([]);
}</p>
<pre><code>
&gt; ### Request payloads

- 타입스크립트를 사용한다면, DTO(Data Transfer Object) 스키마를 정해야한다. DTO란, 네트워크를 통해 전달되는 데이터 형태를 정의한 객체이다. 이는 타입스크립트의 interface, class 등으로 정의할 수 있다. 
  - 네스트에서 추천하는 방법은 클래스를 활용한 것이다. 그 이유는, 클래스는 자바스크립트 ES6 문법의 일부이므로, 자바스크립트로 컴파일 되었을때에도 사용가능하다. (인터페이스는 자바스크립트로 컴파일되지 않음) 
- [ValidationPipe](https://docs.nestjs.com/techniques/validation#stripping-properties)가 메소드 핸들러에게 전달되지 않아야할 속성들을 필터링할 수 있는데, 이 경우에는 acceptable한 속성들만 화이트리스트로 만들 수 있다. 화이트리스트에 포함되지 않은 속성들은 자동으로 객체에서 제거된다. 
```js
//create-cat.dto.ts
export class CreateCatDTO {
  name: string;
  age: number;
  breed: string;
}

//cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return &#39;This action adds a new cat&#39;;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[aws codepipeline 배포 에러]]></title>
            <link>https://velog.io/@kaitlin_k/aws-codepipeline-%EB%B0%B0%ED%8F%AC-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@kaitlin_k/aws-codepipeline-%EB%B0%B0%ED%8F%AC-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sun, 24 Apr 2022 16:06:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="scriptfailed">ScriptFailed</h3>
<p><strong>스크립트 이름</strong>: <code>scripts/start.sh</code>
<strong>메시지</strong>: <code>Script at specified location: scripts/start.sh run as user root failed with exit code 255</code>
<strong>로그</strong>:
LifecycleEvent - ApplicationStart
Script - scripts/start.sh
[stderr]pm2: No such file or directory</p>
</blockquote>
<p>배포 자동화를 하는 중 다음과 같은 위와 같은 에러가 발생하였다. 에러 이름만 보면 start.sh 파일이 없다는 내용이지만, <code>/opt/codedeploy-agent/deployment-root/deployment-logs</code>를 살펴보니 다음과 같은 내용을 확인할 수 있었다. </p>
<p><img src="https://velog.velcdn.com/images/kaitlin_k/post/ce1bde2f-cd72-4c45-8179-1d852ba9753f/image.png" alt=""></p>
<p>로그를 살펴보니 처음으로 에러가 발생한 부분은 scripts/start.sh 파일이었고 <code>pm2: No such file or directory</code>라는 에러가 발생하고 나서 scripts/initialize.sh 파일이 계속 실행이 되었는데 그때부터 npm 에러가 계속 발생했다. </p>
<p>특히 <code>node v8.10.0</code>이라는 부분과 쭉 내려가다보면 <code>pm2@5.2.0: wanted: {&quot;node&quot;:&quot;&gt;=10.0.0&quot;} (current: {&quot;node&quot;:&quot;8.10.0&quot;,&quot;npm&quot;:&quot;3.5.2&quot;})</code>라는 내용도 확인할 수 있었다. </p>
<p>따라서, 검색해본 결과 노드 버전이 너무 낮아서 생긴 문제였다. <a href="https://jjeongil.tistory.com/1275">NodeSource에서 Node.js 및 npm 설치</a>를 참고하여 14버전의 노드를 설치했다. 노드 버전을 높여서 설치하니 성공적으로 배포되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD(4) multer]]></title>
            <link>https://velog.io/@kaitlin_k/TDD4-multer</link>
            <guid>https://velog.io/@kaitlin_k/TDD4-multer</guid>
            <pubDate>Sat, 16 Apr 2022 09:45:47 GMT</pubDate>
            <description><![CDATA[<p>게시물을 업로드할때, 폼데이터인 이미지 파일은 body-parser로 파싱될 수 없기 때문에 multer를 사용해야한다. 나는 multer, multer-s3, aws-sdk를 사용해서 서버의 특정 폴더(ex. uploads)를 생성해서 저장하는 형태가 아니라 s3의 버킷에 업로드하는 것으로 구현했다. </p>
<h3 id="multer-설정">multer 설정</h3>
<p>우선 multer의 storage와 limits 설정은 다음과 같이 해두었다. 이때, accessKey와 secretAccessKey 등은 자신의 AWS 계정에서 <code>액세스 키(액세스 키 ID 및 비밀 액세스 키)</code>를 발급받아야 하며, aws-cli도 설치한 후 aws configure에서 액세스 키와 비밀 액세스 키를 저장해주어야한다. </p>
<pre><code class="language-js">//config/multer.js
const multer = require(&quot;multer&quot;);
const multerS3 = require(&quot;multer-s3&quot;);
const aws = require(&quot;aws-sdk&quot;);
require(&quot;dotenv&quot;).config();

const s3 = new aws.S3({
  accessKeyId: process.env.S3_ACCESSKEYID,
  secretAccessKey: process.env.S3_SECRETKEY,
  region: process.env.S3_REGION,
});

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket:
      process.env.NODE_ENV !== &quot;test&quot;
        ? process.env.S3_BUCKETNAME
        : process.env.S3_TESTBUCKETNAME,
    acl: &quot;public-read&quot;,
    key: function (req, file, cb) {
      cb(
        null,
        Math.floor(Math.random() * 1000).toString() +
          Date.now() +
          &quot;.&quot; +
          file.originalname.split(&quot;.&quot;).pop()
      );
    },
  }),
  limits: {
    fileSize: 5 * 1024 * 1024,
  },
});

module.exports = upload;</code></pre>
<p>supertest로 게시물 업로드 api에 요청을 보내면, multer를 통해 파싱된 저장경로와 body가 ctr.createPost의 req로 전달된다.</p>
<pre><code class="language-js">//api/post/index.js
router.post(&quot;/&quot;, upload.single(&quot;image&quot;), ctrl.createPost);</code></pre>
<h3 id="테스트-코드">테스트 코드</h3>
<h4 id="attach">attach</h4>
<p>테스트 코드를 작성할때 폼 데이터와 바디를 전송하는데에서 한참 헤맸다. 그동안은 <code>request(app).post(&quot;/users&quot;).send(전송할 데이터).end(done)</code>과 같이 <code>send</code>로 데이터를 전송해주었는데, 폼 데이터를 전송할때에는 <code>attach</code>를 사용한다. 여러개의 파일을 전송할 때에는 .attach를 이어서 작성해주면 된다. attach는 첫번째 인자로 input의 name에서 설정한 값을 작성하고, 두번째 인자로 파일의 경로를 작성한다. 
테스트 이미지 파일을 전송하기 위해서 testImg 폴더를 생성해서 임의의 이미지 파일을 추가했고, 두번째 인자로 해당 파일의 경로를 작성했다.</p>
<h4 id="field">field</h4>
<p>ctrl.createPost에서 버킷의 경로는 <code>req.file.location</code>으로 전달받고, body는 <code>req.body.content</code>로 전달받는다. 그런데, 테스트 코드를 작성할때에는 attach와 send를 사용하면 두개를 동시에 쓸 수 없다는 에러가 발생한다. 따라서, 이 때에는 field로 데이터를 전송했다. </p>
<h4 id="limits-error-handling">limits error handling</h4>
<p>multer 설정에서 파일 사이즈를 설정해줄 수 있는데, 파일 사이즈 초과 이미지를 업로드할 경우 에러 핸들링 미들웨어로 에러를 처리할 수 있다. express 문서에 따르면, multer에서 에러가 발생할 경우 multer는 express로 에러를 넘겨준다. 따라서 <code>if(err instance of multer.MulterError)</code>로 multer 에러를 처리해줄 수 있다. 
나는 index.js에 작성한 에러처리 미들웨어에서 <code>if(err.name === &#39;MulterError&#39;)</code>로 핸들링해주었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD(3) beforeEach vs before]]></title>
            <link>https://velog.io/@kaitlin_k/TDD3-beforeEach-vs-before</link>
            <guid>https://velog.io/@kaitlin_k/TDD3-beforeEach-vs-before</guid>
            <pubDate>Mon, 11 Apr 2022 12:39:44 GMT</pubDate>
            <description><![CDATA[<p>테스트 코드를 작성하면서 beforeEach와 before으로 인해 테스트 케이스에 통과하지 않는 문제가 자주 발생했다. 여러 차례의 삽질을 거듭하면서 beforeEach와 before를 사용하는 경우를 구분해보았다. </p>
<h2 id="beforeeach">beforeEach</h2>
<p>beforeEach는 describe 블록 내에 <strong>모든 테스트케이스가 동작하기 직전에</strong> 실행된다. 여기서 중요한 점은 &quot;모든&quot; 테스트케이스 &quot;직전&quot;에 실행된다는 점이다. 따라서 주로 데이터베이스에 더미 데이터를 추가해야하는 경우에 beforeEach를 사용했다. </p>
<pre><code class="language-js">describe(&quot;Set up Database&quot;, () =&gt; {
  const users = [
    {
      userId: &quot;test1&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test1_nickname&quot;,
    },
    {
      userId: &quot;test2&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test2_nickname&quot;,
    },
    {
      userId: &quot;test3&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test3_nickname&quot;,
    },
    {
      userId: &quot;test7&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test7_nickname&quot;,
    },
  ];

  beforeEach(&quot;setting middleware&quot;, () =&gt; {
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));
    app.use(cookieParser());
  });

  beforeEach(&quot;sync DB&quot;, (done) =&gt; {
    models.sequelize.sync({ force: true }).then(() =&gt; { done(); });
  });

  for (const user of users) {
    beforeEach(&quot;insert data&quot;, (done) =&gt; {
      request(app).post(&quot;/users&quot;).send(user).end(done);
    });
  }
  // 이하 생략
}        </code></pre>
<h2 id="before">before</h2>
<p>before는 beforeEach보다 뒤에 작성되었다고 하더라도 같은 describe 블록 내에 쓰였다면 describe 바로 다음에 1회만 실행된다. 하나의 describe 블록에서 beforeEach를 먼저 쓰고, before를 다음에 쓰더라도 before가 먼저 1회만 실행된다. 따라서 before는 api 요청을 보내서 반환받은 res의 body를 변수에 저장하거나 statusCode 등을 저장할 때 사용했다.</p>
<pre><code class="language-js">describe(&quot;POST /users 회원가입&quot;, () =&gt; {
  const user = {
    userId: &quot;test4&quot;,
    password: &quot;helloworld123!&quot;,
    nickname: &quot;test4_nickname&quot;.
  };
  let body;
  let statusCode;
  before(&quot;create new user&quot;, (done) =&gt; {
    request(app)
    .post(&quot;/users&quot;)
    .send(user)
    .end((err, res) =&gt; {
      body = res.body;
      statusCode = res.status;
      done();
    });
  });
  describe(&quot;성공시&quot;, () =&gt; {
    it(&quot;creatHashedPassword 함수를 통해 비밀번호는 암호화되어 저장되어야한다&quot;, () =&gt; {
      body.isCreated.should.be.true();
    });

    it(&quot;회원가입 성공시 201을 반환한다&quot;, () =&gt; {
      statusCode.should.equal(201, statusCode);
    });

    // 이하 생략
  });
});  </code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인 인증방식 ]]></title>
            <link>https://velog.io/@kaitlin_k/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D%EB%B0%A9%EC%8B%9D-bdaqezm3</link>
            <guid>https://velog.io/@kaitlin_k/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D%EB%B0%A9%EC%8B%9D-bdaqezm3</guid>
            <pubDate>Sun, 10 Apr 2022 12:19:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="session-based-authorization">Session Based Authorization</h2>
</blockquote>
<p>서버는 클라이언트로부터 유저가 로그인 시 입력한 아이디와 비밀번호를 전달받아 이 데이터가 db에 존재하는지 확인한다. 만약 존재한다면, 서버는 클라이언트의 쿠키에 <code>sessionID</code>를 저장하고, 서버에도 <code>sessionID</code>를 저장한다. 따라서, 클라이언트가 요청을 보낼때마다 서버 또는 데이터베이스에 요청을 보내 유효한 <code>sessionID</code>인지 확인한다. </p>
<h3 id="session의-한계">session의 한계</h3>
<p>만약, 저장된 sessionID가 많다면 시간이 오래 걸릴 수 있을 것 같다. 그리고 서버 확장의 관점에서 본다면, 로그인을 요청해서 sessionID를 저장하고 있는 서버로만 요청이 가야한다는 한계를 갖는다. 이를 해결하는 방법으로는 우선 sticky session이 있다. </p>
<h3 id="sticky-session">sticky session</h3>
<p><img src="https://images.velog.io/images/kaitlin_k/post/dc36eeed-cfe6-40f8-9b11-6681044de549/image.png" alt=""></p>
<p>유저1이 1번 서버에 세션을 생성했다면, 앞으로 유저1의 모든 요청은 1번 서버로 보내집니다. 이를 가능하게 하는 것이 바로 <code>load balancer</code>이다. 로드 밸런서는 요청한 브라우저에 쿠키를 생성하여 해당 쿠키를 이용해 각 서버로 요청을 리다이렉트 시켜주는 역할을 한다.  </p>
<ul>
<li><p>장점
유저는 하나의 고정된 세션으로만 요청을 보내므로 세션의 정합성이 보장된다. </p>
</li>
<li><p>단점</p>
<ul>
<li>고정된 세션을 사용함으로 특정 서버에 트래픽이 집중될 수 있다. 4개의 세션 중 하나의 세션에만 트래픽이 몰리더라도, 다른 3개의 세션은 사용될 수 없다. </li>
<li>고정된 세션에 장애가 발생하면 해당 서버를 사용하는 유저들의 세션 정보를 모두 잃어버리게 된다.</li>
</ul>
</li>
</ul>
<h3 id="session-clustering">session clustering</h3>
<p><img src="https://images.velog.io/images/kaitlin_k/post/4e608363-6c04-4c96-98db-53b9c19460e5/image.png" alt=""></p>
<p><code>session clustering</code>은 여러개의 서버가 하나처럼 작동하도록 하는 기술이다. <code>sticky session</code>에서는 하나의 서버가 죽으면, 해당 서버를 사용하는 유저 정보를 모두 잃어버린다는 단점이 있었는데, 이는 다른 서버들이 있기 때문에 문제가 생기지 않는다. </p>
<p>session clustering은 사용하는 WAS에 따라 다른 방법이 적용되기 때문에 프로젝트에서 어떤 WAS를 사용하는지 살펴보아야한다. 
<code>여기서 WAS(Web Application Server)란, 사용자의 입력을 받아 서버에서 무언가를 처리하고 그 결과를 보여주는 동적인 데이터를 처리 웹서버이다.</code></p>
<p>가장 대표적인 WAS인 톰캣이 지원하는 session clustering 방식에 대해 알아보도록 하자.</p>
<h4 id="all-to-all-session-replication">all-to-all session replication</h4>
<p>하나의 세션에 변경이 일어나면 다른 모든 세션에 복제되는 것을 말한다. 이는 소규모 클러스터에 적합한 방식이다.  </p>
<ul>
<li>장점<ul>
<li>유저의 요청이 들어왔을때, 어떤 서버로 요청이 들어가도 무관하여 하나의 서버로 트래픽이 몰리는 한계를 극복할 수 있다.</li>
</ul>
</li>
<li>단점<ul>
<li>세션을 복사/전파하기 때문에 모든 서버에 동일한 세션 정보가 저장된다. 이는 오버헤드가 크고 효율적인 메모리 관리가 어렵다.</li>
<li>데이터 변경이 발생할때마다 세션을 복사하기 때문에 네트워크 트래픽이 증가한다. </li>
<li>세션 전파 작업 중 모든 서버에 세션이 전파되기까지의 시간차로 세션 불일치와 같은 문제가 발생할 수 있다. </li>
</ul>
</li>
</ul>
<h3 id="session-storage">session storage</h3>
<p>각 서버의 세션 저장소에 세션정보를 저장하는 것이 아니라, 독립된 세션 저장소에 세션 정보를 저장한다. 따라서 여러 서버들이 이 독립된 세션 저장소에서 세션정보를 읽어온다.</p>
<ul>
<li><p>장점</p>
<ul>
<li>모든 세션 저장소들이 하나의 독립된 세션 저장소의 데이터를 공유하기 때문에 session clustering과 마찬가지로 sticky session의 특정 서버로의 트래픽 과부하 문제가 발생하지 않는다. </li>
<li>세션이 재설정되었을때, 세션 저장소에 있는 세션 정보만 수정하면 된다. 따라서, WAS들간의 불필요한 네트워크 통신과정을 진행하지 않아도 된다.</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>만약 독립된 세션 저장소에 문제가 생긴다면 모든 WAS에 영향을 미친다. (이는 세션 저장소를 하나 더 구성하여 해결할 수 있다.)</li>
<li>이러한 세션 저장소로는 <code>MySQL</code>, <code>oracleDB</code> 등의 RDBMS와 영속성이 필요하지 않다면 <code>memcached</code>를, 영속성이 필요하다면 <code>redis</code>를 사용할 수 있다. </li>
</ul>
</li>
</ul>
<blockquote>
<h2 id="token-based-authorization-jwt">Token Based Authorization (JWT)</h2>
</blockquote>
<p>마찬가지로, 유저정보가 db에 존재한다면, 서버는 클라이언트의 쿠키에 토큰을 저장한다. 따라서 클라이언트는 다음 요청부터는 토큰을 실어서 요청을 보낸다. 따라서 서버는 이 토큰이 유효한지 확인한다. session based authorization과는 다르게 서버에서 session id를 관리하고 있지 않다는 특징을 갖는다. </p>
<h3 id="구성">구성</h3>
<p>생성된 토큰은 헤더, 페이로드, 시그니처로 구성되어있다. </p>
<p><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</code></p>
<ul>
<li><p>헤더
암호화할 알고리즘과 토큰의 타입(이 경우는 JWT)이 정의되어있다. Base64Url로 암호화되어 있다.</p>
</li>
<li><p>페이로드
토큰을 만드는데 사용되는 데이터가 저장되어있다. 헤더와 마찬가지로 Base64Url로 암호화되어 있으므로 디코딩이 가능하기 때문에 비밀번호, 카드번호 등의 중요한 정보는 저장해서는 안된다. </p>
</li>
<li><p>시그니처
헤더와 페이로드를 설정한 비밀키로 암호화된 값이 저장된다. 만약 토큰이 탈취되어 페이로드의 토큰 유효기간을 임의로 변경한다면 Base64URL로 새롭게 암호화된 페이로드는 다른 값을 가진다. 하지만, 시그니처는 비밀키를 모르면 변경할 수 없다. 따라서, 이 토큰은 더이상 유효하지 않다. </p>
</li>
</ul>
<br>

<h3 id="어디에-저장할-것인가">어디에 저장할 것인가</h3>
<p>| <center></center> | cookies | local storage | session storage | <center></center> |
| :- | :-: | :-: | :-: |
| capacity |4kb | 10mb | 5mb |
| browsers | HTML4/ HTML5 | HTML5 | HTML5 |
| accessible from | Any window | Any window | Same tab |
| expires | Manually set | NEVER (unless logout or manually remove) | On tab close |
| storage location | Browser (but sent to server) | Browser only | Browser only |</p>
<h4 id="web-storage-local-storage-session-storage">web storage: local storage, session storage</h4>
<ul>
<li><p>자바스크립트를 사용해서 제어가 가능하다.
자바스크립트로 값을 쉽게 저장하고 가져오고, 삭제할 수 있어 편하다.</p>
</li>
<li><p>XSS 공격에 취약하다.</p>
<ul>
<li><h5 id="xsscross-site-scripting란-공격자가-상대방의-브라우저에-스크립트가-실행되도록-해-사용자의-세션을-가로채거나-웹사이트를-변조하거나-악의적-콘텐츠를-삽입하거나-피싱-공격을-진행하는-것">XSS(Cross Site Scripting)란, 공격자가 상대방의 브라우저에 스크립트가 실행되도록 해 사용자의 세션을 가로채거나, 웹사이트를 변조하거나, 악의적 콘텐츠를 삽입하거나, 피싱 공격을 진행하는 것</h5>
</li>
</ul>
</li>
<li><p>로컬스토리지에 저장된 액세스 토큰은 Bearer 스키마를 사용해 Authorization 헤더에 담아 HTTP 요청을 보내야한다. <code>Authorization Bearer ${access_token}</code> 서버에서는 이 내용을 파싱해서 토큰을 확인한다.</p>
</li>
</ul>
<h4 id="cookie">cookie</h4>
<ul>
<li><p>웹스토리지와 마찬가지로 자바스크립트를 통해 제어가 가능하다.</p>
</li>
<li><p>쿠키의 httpOnly 옵션을 true로 설정하면, 자바스크립트로 쿠키 접근을 제한할 수 있어 로컬 스토리지에 비해 XSS 공격에 대한 보안을 높일 수 있다.</p>
</li>
<li><p>쿠키는 해당 도메인에 대해 자동으로 모든 HTTP 요청에 포함되어 보내진다. 
웹스토리지와 다르게 매 요청마다 헤더를 직접 설정해주지 않아도 된다.</p>
</li>
<li><p>쿠키는 4kb로 사이즈가 제한된다. </p>
</li>
<li><p>CSRF 공격에 취약하다.</p>
<ul>
<li><h5 id="csrfcross-site-request-forgery는-웹사이트-취약점-공격의-하나로-사용자가-자신의-의지와는-무관하게-공격자가-의도한-행위수정-삭제-등록-등를-특정-웹사이트에-요청하게-하는-공격">CSRF(Cross-site request forgery)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격</h5>
</li>
<li><p>CSRF가 발생하는 시나리오
A.com에서 생성한 쿠키는 B.com에서는 확인할 수 없다. A.com으로 보내는 HTTP 요청들은 브라우저가 자동으로 쿠키를 헤더에 담아서 보낸다. HttpOnly 옵션을 설정한 쿠키의 경우 자바스크립트로도 쿠키를 얻을 수 없다. 그렇다면 어떻게 쿠키를 가로챌 수 있을까?
예를 들어, 해커가 hacking.com이라는 사이트를 만들어서 A.com을 사용하는 유저에게 해당 해킹 사이트로 접속을 유도하는 이메일을 보냈다고 해보자. 해킹 사이트의 HTML 페이지는 <code>&lt;img src= &quot;https://A.com/travel_update?.src=Korea&amp;.dst=Hell&quot;&gt;</code>라는 이미지 태그를 갖는다. 따라서, 유저가 해당 사이트에 접속했다면, 브라우저는 이미지 파일을 받아오기 위해 도착지를 임의로 변경하는 공격용 URL을 열 것이다. 브라우저에 A.com의 액세스 토큰이 저장되어있다면, 이 쿠키를 담아서 요청을 보낼 것이다. 
결국, 유저가 알지 못하는 사이에 도착지가 변경되는 문제가 발생한다. </p>
</li>
<li><p>CSRF를 해결하는 방법
다른 사이트에서 자사의 쿠키를 가지고 인증하여 생기는 보안 이슈는 &quot;다른 사이트&quot;의 접근을 제한하는 것과 &quot;자사의 쿠키 사용&quot;을 제한하는 것으로 해결 할 수 있다. </p>
<ul>
<li><a href="https://velog.io/@kaitlin_k/CORS">백엔드에서 CORS 설정하기</a> 
다른 사이트의 접근 제한은 CORS 설정을 통해 해줄 수 있다. 브라우저는 보안을 위해 기본적으로 SOP(Same-Origin Policy) 정책을 따르는데, 다른 사이트에서 접근을 일부 허용해주는 정책이 CORS(Cross-Origin Resource Sharing)이다. express에서 CORS를 사용하기 위해서는 cors 미들웨어를 설치해야한다.</li>
<li><a href="https://velog.io/@kaitlin_k/cookie-%EC%98%B5%EC%85%98">쿠키의 sameSite 옵션 설정하기</a>
자사의 쿠키 사용을 제한하는 것은 쿠키의 sameSite 옵션으로 설정해줄 수 있다. express에서는 쿠키를 사용하기 위해서 cookie-parser 미들웨어를 설치해야한다. </li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<h4 id="reference">reference</h4>
</blockquote>
<ul>
<li><a href="https://velog.io/@dnjwm8612/%EB%8B%A4%EC%A4%91-%EC%84%9C%EB%B2%84-%ED%99%98%EA%B2%BD-%EC%84%B8%EC%85%98-%EB%B6%88%EC%9D%BC%EC%B9%98">다중 서버 환경? 세션 불일치?</a></li>
<li><a href="https://hyuntaeknote.tistory.com/6">다중 서버 환경에서 Session은 어떻게 공유하고 관리할까?</a></li>
<li><a href="https://jwt.io/introduction">JWT 공식문서</a></li>
<li><a href="https://www.youtube.com/watch?v=GihQAC1I39Q">javascript cookies vs local storage vs session</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[클린코드 2장 의미있는 이름]]></title>
            <link>https://velog.io/@kaitlin_k/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@kaitlin_k/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C</guid>
            <pubDate>Fri, 08 Apr 2022 10:40:40 GMT</pubDate>
            <description><![CDATA[<h3 id="📚-클린코드-로버트-c마틴-인사이트을-읽고-일부-정리한-내용입니다">📚 클린코드 (로버트 C.마틴, 인사이트)을 읽고 일부 정리한 내용입니다</h3>
<p>클린코드 2장은 의미있는 이름을 정하는 것의 중요성과 방법에 대해 설명한다. 여러가지 조건들이 있는데, 필요하고 중요하다는 것은 알지만 막상 코드를 작성하다보면 지키지 않게 되는 부분들이 있다. 그래서 앞으로는 적용하고자 기억하고 싶은 내용을 정리해보려고 한다. </p>
<ul>
<li>의도를 분명히 밝혀라</li>
<li>그릇된 정보를 피하라</li>
<li>의미있게 구분하라 </li>
<li>발음하기 쉬운 이름을 사용하라</li>
<li>검색하기 쉬운 이름을 사용하라</li>
<li>인코딩을 피하라</li>
<li>자신의 기억력을 자랑하지 마라</li>
<li>클래스 이름, 메서드 이름</li>
<li>기발한 이름은 피하라</li>
<li>말장난을 하지 마라</li>
<li>해법 영역에서 가져온 이름을 사용하라</li>
<li>문제 영역에서 가져온 이름을 사용하라</li>
</ul>
<blockquote>
<p><strong><em>한 개념에 한 단어를 사용하라</em></strong></p>
</blockquote>
<p>똑같은 메서드를 클래스마다 <em>fetch, retrieve, get으로 제각각 부르면 혼란스럽다.</em> 어느 클래스에서 어떤 이름으로 작성했는지 기억하기 어렵기 때문이다. 마찬가지로 <em>controller, manager, driver를 섞어 쓰면 혼란스럽다.</em> DeviceManager와 ProtocolController는 근본적으로 어떻게 다른가? 일관성있는 어휘는 코드를 사용할 프로그래머가 반갑게 여길 선물이다.</p>
<blockquote>
<p><em><strong>의미있는 맥락을 추가하라</strong></em></p>
</blockquote>
<p>firstName, lastName, street, state, houseNumber, city, zipcode라는 변수를 보면 주소라는 사실을 알아차린다. 하지만 어느 메서드가 state라는 변수 하나만 사용한다면? 변수 state가 주소 일부라는 사실을 금방 알아챌까?<br>
이때 addr이라는 접두어를 추가하면 맥락이 좀 더 분명해진다. 변수가 좀 더 큰 구조에 속한다는 사실이 적어도 독자(프로그래머)에게는 분명해진다. 물론 Address라는 클래스를 생성하면 더 좋다. 그러면 변수가 좀 더 큰 개념에 속한다는 사실이 컴파일러에게도 분명해진다. 두 코드를 비교해보며 맥락을 분명하게 변수를 작성하는 것을 이해해보자.</p>
<p>아래의 코드는 맥락이 불분명한 변수로 작성된 코드이다. 처음 봤을때는 만약 나도 비슷한 코드를 작성해야한다면 이런식으로 작성하지 않을까 싶었다. 하지만 책에 따르면, 다음과 같은 개선점을 찾을 수 있다.</p>
<ul>
<li>함수 이름은 맥락의 일부만 제공하며 알고리즘이 나머지 맥락을 제공한다.</li>
<li>함수를 끝까지 읽어보고 나서야 number, verb, pluralModifier라는 변수 세 개가 guess statistics 메시지에 사용된다는 사실이 드러난다.</li>
<li>독자가 맥락을 유추해야만 하며 메서드를 훑어서는 세 변수의 의미가 불분명하다.</li>
<li>불필요한 맥락을 없애라</li>
</ul>
<pre><code class="language-js">//맥락이 불분명한 변수
private void printGuessStatistics(char candidate, int count) {
  String number;
  String verb;
  String pluralModifier;
  if(count === 0) {
    number = &quot;no&quot;;
    verb = &quot;are&quot;;
    pluralModifier = &quot;s&quot;;
  } else if(count === 1) {
    number = &quot;1&quot;;
    verb = &quot;is&quot;;
    pluralModifier = &quot;&quot;;
  } else {
    number = Integer.toString(count);
    verb = &quot;are&quot;;
    pluralModifier = &quot;s&quot;;
  }
  String guessMessage = String.format(&quot;There %s %s %s%s&quot;, verb, number, candidate, pluralModifier);
  print(guessMessage);
}</code></pre>
<p>다음과 같이 개선된 코드는 함수가 길지만 세 변수를 함수 전반에서 사용한다.</p>
<ul>
<li>함수를 작은 조각으로 쪼개고자 GuessStatisticsMessage라는 클래스를 만들고 세 변수를 클래스에 넣음으로서 세 변수의 맥락이 분명해진다.</li>
</ul>
<p>개선된 코드는 읽으면서 오히려 코드 길이가 길어지고 변수명이 굉장히 길어져 어색하게 느껴졌다. 하지만 코드를 처음 본 사람에게는 이해하기 쉬운 코드라는 생각이 들었다.</p>
<pre><code class="language-js">//맥락이 분명한 변수
public class GuessStatisticsMessage {
  private String number;
  private String verb;
  private String pluralModifier;
  public String make (char candidate, int count) {
    createPluralDependentMessageParts(count);
    return String.format(&quot;There %s %s %s%s&quot;, verb, number, candidate, pluralModifier);
  }
  private voide createPluralDependentMessageParts(int count) {
    if(count === 0) {
      thereAreNoLetters();
    } else if (count === 1) {
      thereIsOneLetter();
    } else {
      thereAreManyLetters(count)
    }
  }
  private voide thereAreManyLetters(int count) {
    number = Integer.toString(count);
    verb = &quot;are&quot;;
    pluralModifier = &quot;s&quot;;
  }
  private void thereIsOneLetter() {
    number = &quot;1&quot;;
    verb = &quot;is&quot;;
    pluralModifier = &quot;&quot;;
  }
  private void thereAreNoLetters() {
    number = &quot;no&quot;;
    verb = &quot;are&quot;;
    pluralModifier = &quot;s&quot;;
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD(2) hook]]></title>
            <link>https://velog.io/@kaitlin_k/TDD2-hook</link>
            <guid>https://velog.io/@kaitlin_k/TDD2-hook</guid>
            <pubDate>Tue, 05 Apr 2022 14:22:01 GMT</pubDate>
            <description><![CDATA[<p>테스트 코드를 작성할때 더미데이터만 저장된 데이터베이스를 사용하는데, post, delete, put 등의 요청을 실행하면 데이터가 변경된다. 따라서, 각 테스트 케이스를 실행하기 전에 데이터베이스를 초기화하고 더미데이터를 벌크 인서트 해주는 작업이 필요하다. </p>
<p>그때 사용할 수 있는 것이 <code>before</code>, <code>after</code>, <code>beforeEach</code>, <code>afterEach</code> 훅이다.</p>
<ul>
<li><strong>before</strong>는 describe 블록 내에세 it 코드들이 실행되기 전에 1회만 실행된다.</li>
<li><strong>beforeEach</strong>는 describe 블록 내에서 it 코드들이 실행되기 전에 매번 실행된다.</li>
<li><strong>after</strong>는 describe 블록 내에서 it 코드들이 모두 실행된 후에 1회만 실행된다.</li>
<li><strong>afterEach</strong>는 describe 블록 내에서 it 코드들이 실행된 후에 매번 실행된다.</li>
</ul>
<h3 id="간단한-hook-사용">간단한 hook 사용</h3>
<pre><code class="language-js">describe(&quot;suite1&quot;, () =&gt; {
  before(() =&gt; console.log(&quot;before1&quot;));
  beforeEach(() =&gt; console.log(&quot;beforeEach1&quot;)); 
  after(() =&gt; console.log(&quot;after&quot;));
  afterEach(() =&gt; console.log(&quot;afterEach&quot;));
  it(&quot;test1&quot;, () =&gt; console.log(&quot;test1&quot;));
  it(&quot;test2&quot;, () =&gt; console.log(&quot;test2&quot;));
});


/*
suite
before
beforeEach
test1
  ✅test1
afterEach
beforeEach
test2
  ✅test2
afterEach
after
*/</code></pre>
<h3 id="비동기-hook">비동기 hook</h3>
<p>모든 훅들은 동기 또는 비동기로 동작시킬 수 있다. 테스트를 진행하기 전에 데이터베이스에 더미 데이터를 저장시킬 수 있다. </p>
<pre><code class="language-js">describe(&quot;Set up Database&quot;, () =&gt; {
  const users = [
    {
      userId: &quot;test1&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test1_nickname&quot;,
    },
    {
      userId: &quot;test2&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test2_nickname&quot;,
    },
    {
      userId: &quot;test3&quot;,
      password: &quot;helloworld123!&quot;,
      nickname: &quot;test3_nickname&quot;,
    },
  ];
  beforeEach(&quot;Sync DB&quot;, (done) =&gt; {
    models.sequelize.sync({ force: true })
    .then(() =&gt; done()})
  });
  beforeEach(&quot;Bulk insert data&quot;, (done) =&gt; {
    models.User.bulkCreate(users);
    done();
  });

  describe(&quot;GET /users&quot;, () =&gt; {}); //생략
  describe(&quot;GET /users/:id&quot;, () =&gt; {});    //생략
  describe(&quot;DELETE /users/:id&quot;, () =&gt; {}); //생략

});</code></pre>
<blockquote>
<h4 id="reference">reference</h4>
</blockquote>
<ul>
<li><a href="https://mochajs.org/#hooks">hook</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD(1) 시작하기]]></title>
            <link>https://velog.io/@kaitlin_k/TDD</link>
            <guid>https://velog.io/@kaitlin_k/TDD</guid>
            <pubDate>Sun, 03 Apr 2022 05:33:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="테스트-주도-개발-tdd">테스트 주도 개발 TDD</h3>
<p>Test Driven Development
노드에서는 mocha, should, superTest 등의 라이브러리를 사용해서 TDD를 할 수 있다. </p>
</blockquote>
<h4 id="mocha">Mocha</h4>
<p>TDD를 할 수 있는 대표적인 자바스크립트 프레임워크로 테스트코드를 실행하는 테스트 러너(test runner)</p>
<ul>
<li>설치: <code>npm i mocha --save-dev</code></li>
<li>테스트 수트: 테스트 환경으로 모카에서는 describe()로 구현한다.</li>
<li>테스트 케이스: 실제 테스트를 말하며 모카에서는 it()으로 구현한다.</li>
<li>테스트 실행: <code>node_modules/.bin/mocha utils.spec.js</code> </li>
</ul>
<hr>

<p>#1 노드에서 제공하는 <code>assert</code> 모듈을 사용해서 capitalize와 add 함수가 동작하는지 확인하는 테스트 코드를 작성해보자.</p>
<ul>
<li><code>assert.equal(actual, expected[, message])</code><ul>
<li><code>== operator</code>로 두 값을 비교하며, 두 값이 모두 <code>NaN</code>인 경우 일치하는 것으로 간주한다. </li>
<li>따라서 <code>assert.strictEqual()</code> 사용을 권장한다. <ul>
<li>`=== operator&#39;로 두 값을 비교</li>
</ul>
</li>
<li>optional한 message 값은 두 값이 일치하지 않을때 <code>AssertionError</code>가 발생하는데, 이때 message 프로퍼티로 사용된다. 이 값을 명시하지 않은 경우 기본 에러 메시지가 사용된다. message가 에러 객체인 경우 <code>AssertionError</code>를 대체해 사용된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-js">//utils.js 사용할 함수를 저장한 모듈
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function add(num1, num2) {
  return num1+num2;
}

module.exports = {
  capitalize,
  add
} 

//utils.spec.js (테스트 코드는 .spec.js로 네이밍)
const utils = require(&quot;./utils&quot;);
const assert = require(&quot;assert&quot;); 

describe(&quot;utils.js 모듈의 capitalize 함수는&quot;, function () {
  it(&quot;문자열의 첫번째 문자를 대문자로 변환한다.&quot;, function () {
    const result = utils.capitalize(&quot;hello&quot;);
    assert.equal(result, &quot;Hello&quot;);
  });
});

describe(&quot;utils.js 모듈의 add 함수는&quot;, function (){
  it(&quot;두 값을 더한다.&quot;, function (){
    const result = utils.add(10, 20);
    assert.equal(result, 30);
  });
});</code></pre>
<p><img src="https://media.vlpt.us/images/kaitlin_k/post/76517a4c-a69d-45f7-9550-9cfa2346ac58/Screenshot%20from%202022-04-03%2010-52-58.png" alt=""></p>
<hr>

<p>#2 서드파티 모듈 should를 사용해서 테스트 코드를 작성해보자</p>
<ul>
<li><code>npm i mocha --save-dev</code>로 설치하기</li>
<li>노드에서 제공하는 <code>assert</code> 모듈을 가져와서 사용했던 것과 다르게 <code>should</code>는 여러가지 <code>assertion</code> 함수를 지원하므로 따로 모듈을 불러오지 않아도 된다.</li>
</ul>
<pre><code class="language-js">const should = require(&quot;should&quot;);

describe(&quot;utils.js 모듈의 capitalize 함수는&quot;, function () {
  it(&quot;문자열의 첫번째 문자를 대문자로 변환한다.&quot;, function (){
    const result = utils.capitalize(&quot;hello&quot;);
    result.should.be.eqaul(&quot;Hello&quot;);
  });
});</code></pre>
<hr>

<p>#3 익스프레스 통합 테스트용 라이브러리인 SuperTest를 사용해서 통합 테스트 코드(API 기능 테스트)를 작성해보자</p>
<ul>
<li><code>npm i supertest --save-dev</code>로 설치하기 </li>
<li><code>supertest</code> 모듈을 변수 <code>request</code>에 저장하고, 생성한 서버 <code>app</code>을 <code>request</code>에 인자로 전달한다.</li>
<li>비동기 요청을 보내는 경우에는 done을 it의 콜백에 전달하고, 테스트 케이스를 마친 후에 done을 실행시키도록 한다.<pre><code class="language-js">const should = require(&quot;should&quot;);
const request = require(&quot;supertest&quot;);
const app = require(&quot;../../index&quot;);
const models = require(&quot;../../models&quot;);
</code></pre>
</li>
</ul>
<p>describe(&quot;GET /users&quot;, () =&gt; {
  describe(&quot;성공시&quot;, () =&gt; {
    it(&quot;유저 객체를 담은 배열을 반환한다&quot;, (done) =&gt; {
      request(app).get(&quot;/users&quot;).end((err, res) =&gt; {
        res.body.should.be.instanceOf(Array);
        done();
      });
    });
  });
})</p>
<pre><code>
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] Stack, Queue]]></title>
            <link>https://velog.io/@kaitlin_k/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Stack</link>
            <guid>https://velog.io/@kaitlin_k/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-Stack</guid>
            <pubDate>Sat, 12 Feb 2022 12:25:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="stack">Stack</h3>
<p>스택은 책을 쌓는 것처럼 데이터를 차곡차곡 쌓아 올리는 자료구조이다. </p>
</blockquote>
<h4 id="특징">특징</h4>
<ul>
<li>LIFO (Last In First Out)
가장 마지막으로 들어온 데이터가 가장 먼저 나갈 수 있다.</li>
<li>데이터가 순서대로 저장되고, 가장 마지막으로 입력된 데이터가 유의미할 때 적합하다.</li>
<li>비어있는 스택에서 원소를 추출하려고 할 경우 stack underflow가 발생하고, 스택이 넘치는 경우를 stack overflow라고 한다.</li>
</ul>
<h4 id="활용">활용</h4>
<p>LIFO의 특징을 활용하면 다음과 같은 경우에 스택 자료구조를 사용할 수 있다. </p>
<ul>
<li>웹브라우저 방문기록(뒤로가기) 
페이지를 이동할때마다 스택에 페이지가 푸시된다. 뒤로가기 버튼을 누르면 스택에 저장된 가장 마지막 데이터를 보여준다. </li>
<li>역순 문자열 만들기
문자열 순서대로 스택에 푸시되고, 모든 문자열이 스택에 저장되었다면, 차례로 pop한다. </li>
<li>실행취소 (Ctrl+Z)
뒤로가기와 마찬가지로 실행기록을 스택에 차례로 푸시한다. 실행 취소 시 마지막 기록을 pop한 후 이전 기록으로 돌아간다.</li>
<li>재귀적 알고리즘 구현</li>
</ul>
<h4 id="구현">구현</h4>
<p>배열을 사용하지 않은 경우 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-js">class Stack {
  constructor() {
    this.storage = {};
    this.top = 0;
  }
  size() {
    return this.top;
  }
  push(element) {
    this.storage[this.top] = element;
    this.top += 1;
  }
  pop() {
    if(this.size()&lt;1) {
      return;
    }
    const result = this.storage[this.top-1];
    delete this.storage[this.top-1];
    this.top -= 1;
    return result;
  }
}</code></pre>
<blockquote>
<h3 id="queue">Queue</h3>
<p>데이터가 줄을 서서 기다리는 것처럼 FIFO 특징을 갖는 자료구조이다.</p>
</blockquote>
<h4 id="특징-1">특징</h4>
<ul>
<li>FIFO (First In First Out)
먼저 들어온 데이터가 먼저 나간다. 스택이 하나의 문을 통해 push, pop 되었다면 큐는 입구와 출구가 따로 나누어져있다. </li>
<li>데이터가 입력된 순서대로 실행되어야 하는 경우 적합하다.</li>
</ul>
<h4 id="활용-1">활용</h4>
<ul>
<li>프린터 인쇄 대기열</li>
<li>콜센터 고객 대기</li>
<li>BFS 알고리즘 구현 </li>
</ul>
<h4 id="구현-1">구현</h4>
<p>자바스크립트의 배열의 <code>shift</code>, <code>push</code> 사용해 큐를 흉내내서 구현할 수 있다. 하지만, <code>shift</code>로 첫번째 요소를 제거할 경우, 배열 내부에서는 재정렬이 일어나므로 시간 복잡도가 실제 큐에 비해 커진다. 실제로 큐에서는 첫번째 요소를 제거하는데 O(1)이 걸리지만, 배열을 이용한 경우, 첫번째 요소를 제거하고 배열 전체를 순회하면서 남은 배열들의 요소를 앞으로 한칸씩 당겨주는 데에 추가적인 시간이 소요된다. 따라서 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-js">class Queue {
  constructor() {
    this.storage = {};
    this.front = 0;
    this.rear = 0;
  }

  size() {
    return this.rear - this.front;
  }

  enqueue(element) {
    this.storate[this.rear] = element;
    this.rear += 1;
  }

  dequeue() {
    if(this.size() === 0) {
      return;
    }
    const result = this.storate[this.front];
    delete this.storate[this.front];
    this.front += 1;
    return result;
  }
} </code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv3] 프로그래머스 MySQL 문제모음]]></title>
            <link>https://velog.io/@kaitlin_k/Lv3-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-MySQL-%EB%AC%B8%EC%A0%9C%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@kaitlin_k/Lv3-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-MySQL-%EB%AC%B8%EC%A0%9C%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Fri, 11 Feb 2022 05:27:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오랜기간-보호한-동물1">오랜기간 보호한 동물(1)</h3>
</blockquote>
<ul>
<li>Left Join</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59044">https://programmers.co.kr/learn/courses/30/lessons/59044</a></li>
</ul>
<h4 id="나의-풀이">나의 풀이</h4>
<p><code>ANIMAL_INS</code>와 <code>ANIMAL_OUTS</code>를 <code>LEFT JOIN</code>시켜 NULL값을 가진 행만 찾아낸다. </p>
<pre><code class="language-js">SELECT A.NAME, A.DATETIME
    FROM ANIMAL_INS A 
    LEFT JOIN ANIMAL_OUTS B
        ON A.ANIMAL_ID = B.ANIMAL_ID
WHERE B.ANIMAL_ID IS NULL
ORDER BY A.DATETIME
LIMIT 3
;</code></pre>
<hr>


<blockquote>
<h3 id="오랜기간-보호한-동물2">오랜기간 보호한 동물(2)</h3>
</blockquote>
<ul>
<li>Join</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59411">https://programmers.co.kr/learn/courses/30/lessons/59411</a></li>
</ul>
<h4 id="나의-풀이-1">나의 풀이</h4>
<p>가장 보호기간이 길다는 것은 나간날짜-들어온날짜가 가장 크다는 것을 의미한다.</p>
<pre><code class="language-js">SELECT INS.ANIMAL_ID, INS.NAME
    FROM ANIMAL_INS INS
    JOIN ANIMAL_OUTS OUTS
        ON INS.ANIMAL_ID = OUTS.ANIMAL_ID
ORDER BY (OUTS.DATETIME-INS.DATETIME) DESC
LIMIT 2
;</code></pre>
<hr>


<blockquote>
<h3 id="보호소에서-중성화한-동물">보호소에서 중성화한 동물</h3>
</blockquote>
<ul>
<li>Join</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59045">https://programmers.co.kr/learn/courses/30/lessons/59045</a></li>
</ul>
<h4 id="나의-풀이-2">나의 풀이</h4>
<pre><code class="language-js">SELECT A.ANIMAL_ID, A.ANIMAL_TYPE, A.NAME 
    FROM ANIMAL_INS A
    JOIN ANIMAL_OUTS B
        ON A.ANIMAL_ID = B.ANIMAL_ID
WHERE A.SEX_UPON_INTAKE LIKE &#39;Intact%&#39; AND (B.SEX_UPON_OUTCOME LIKE &#39;Spayed%&#39; OR B.SEX_UPON_OUTCOME LIKE &#39;Neutered%&#39;)
ORDER BY A.ANIMAL_ID;</code></pre>
<h4 id="다른-분의-풀이">다른 분의 풀이</h4>
<p>WHERE 조건을 A와 B의 중성화 데이터가 다른 것으로 주어, 중성화된 동물 데이터를 찾아내었다. 두 테이블의 값이 다르다는 것은, 들어올때는 중성화가 되어있지 않았지만 나갈때에는 중성화가 되었다는 것을 의미한다. (이미 중성화가 된 채로 들어왔다면 나갈때에도 중성화가 되어있으므로 변하지 않음)</p>
<pre><code class="language-js">SELECT A.ANIMAL_ID, A.ANIMAL_TYPE, A.NAME 
    FROM ANIMAL_INS A
    JOIN ANIMAL_OUTS B
        ON A.ANIMAL_ID = B.ANIMAL_ID
WHERE A.SEX_UPON_INTAKE != B.SEX_UPON_OUTCOME
ORDER BY A.ANIMAL_ID;</code></pre>
<hr>

<blockquote>
<h3 id="있었는데요-없었습니다">있었는데요 없었습니다</h3>
</blockquote>
<ul>
<li>Join</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59043">https://programmers.co.kr/learn/courses/30/lessons/59043</a></li>
</ul>
<h4 id="나의-풀이-3">나의 풀이</h4>
<p>보호 시작일(ANIMAL_INS.DATETIME)보다 입양일(ANIMAL_OUTS.DATETIME)이 더 빠른(더 오래된) 동물의 데이터를 조회해야한다. </p>
<p>어떤 날짜를 크기비교하면, 1970년 1월 1일부터 해당 날짜까지의 차를 밀리세컨드로 구한 값을 비교한다. 따라서 <code>ANIMAL_INS.DATETIME&gt;ANIMAL_OUTS.DATETIME</code>은 <code>ANIMAL_INS.DATETIME</code>이 더 최근 날짜이므로, <code>getTime</code>을 했을때 더 값이 크다는 것을 의미한다.</p>
<pre><code class="language-js">SELECT A.ANIMAL_ID, A.NAME 
    FROM ANIMAL_INS A
    JOIN ANIMAL_OUTS B
        ON A.ANIMAL_ID = B.ANIMAL_ID
WHERE A.DATETIME&gt;B.DATETIME
ORDER BY A.DATETIME
;</code></pre>
<hr>

<blockquote>
<h3 id="없어진-기록-찾기">없어진 기록 찾기</h3>
</blockquote>
<ul>
<li>Left Join, Right Join</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59042">https://programmers.co.kr/learn/courses/30/lessons/59042</a></li>
</ul>
<h4 id="나의-풀이-4">나의 풀이</h4>
<p>Left Join과 Right Join은 기능이 동일하므로, 테이블 작성 순서만 변경해주면 된다. 
<code>A LEFT JOIN B === B RIGHT JOIN A</code></p>
<pre><code class="language-js">//Left Join
SELECT OUTS.ANIMAL_ID, OUTS.NAME 
    FROM ANIMAL_OUTS OUTS
    LEFT JOIN ANIMAL_INS INS
        ON OUTS.ANIMAL_ID = INS.ANIMAL_ID
WHERE INS.ANIMAL_ID IS NULL
ORDER BY OUTS.ANIMAL_ID;

//Right Join
SELECT OUTS.ANIMAL_ID, OUTS.NAME 
    FROM ANIMAL_INS INS
    RIGHT JOIN ANIMAL_OUTS OUTS
        ON OUTS.ANIMAL_ID = INS.ANIMAL_ID
WHERE INS.ANIMAL_ID IS NULL
ORDER BY OUTS.ANIMAL_ID;</code></pre>
<blockquote>
<h3 id="헤비-유저가-소유한-장소">헤비 유저가 소유한 장소</h3>
</blockquote>
<ul>
<li>서브쿼리</li>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/77487">https://programmers.co.kr/learn/courses/30/lessons/77487</a></li>
</ul>
<h4 id="다른-분의-풀이-1">다른 분의 풀이 (1)</h4>
<ul>
<li>2회이상 조회된 HOST_ID 목록을 서브 쿼리 테이블로 만든다.</li>
<li>WHERE문에서 IN을 사용하여 서브쿼리 테이블에 존재하는 HOST_ID를 찾아 이에 해당하는 행만 조회한다.</li>
</ul>
<pre><code class="language-js">SELECT *
    FROM PLACES
WHERE HOST_ID IN (
  SELECT HOST_ID 
      FROM PLACES
      GROUP BY HOST_ID
      HAVING COUNT(HOST_ID) &gt; 1
)
ORDER BY ID
;</code></pre>
<h4 id="다른-분의-풀이-2">다른 분의 풀이 (2)</h4>
<ul>
<li>EXISTS는 괄호 안의 서브쿼리는 값이 있다면 true를 리턴하고, 없다면 false를 리턴한다. </li>
<li>IN은 데이터 셋을 생성하고, 매번 데이터를 확인해야하기 때문에 성능이 떨어지는 반면, EXISTS는 해당값이 존재하는지가 true, false로 바로 확인할 수 있으므로 IN보다 성능이 좋다.</li>
<li>IN 보다는 EXISTS를 쓰는 것이 적절하다.<pre><code class="language-js">SELECT * FROM PLACES P1
WHERE EXISTS (
SELECT 1 FROM PLACES P2
WHERE P1.HOST_ID = P2.HOST_ID
GROUP BY HOST_ID
HAVING COUNT(ID) &gt; 1
)
ORDER BY ID
;</code></pre>
</li>
</ul>
<hr> 

<h4 id="exists와-in-비교">EXISTS와 IN 비교</h4>
<ul>
<li><p>EXISTS</p>
<ul>
<li>EXISTS 뒤의 괄호 안에는 서브쿼리만 들어갈 수 있다.</li>
<li>처리순서: 메인쿼리 -&gt; 서브쿼리 (순서대로)
서브쿼리에서 메인쿼리의 정보를 가져올 수 있으므로 서브에서 메인의 정보를 가져와 조건을 설정할 수 있다.</li>
<li>서브쿼리에 대한 결과가 존재하는지만 확인한다.</li>
<li>NULL 값에 대해 TRUE를 리턴한다.</li>
</ul>
</li>
<li><p>IN</p>
<ul>
<li>IN 뒤에 특정 값이나 서브쿼리가 들어갈 수 있다.</li>
<li>처리순서: 서브쿼리 -&gt; 메인쿼리 (서브먼저)
따라서 서브쿼리에서 메인쿼리의 정보를 가져올 수 없다.</li>
<li>NULL 값에 대해 FALSE를 리턴한다. 
<code>WHERE name IN (null, &#39;hello&#39;)</code>인 경우를 풀어쓰면 다음과 같다. <code>WHERE name = null OR name = &#39;hello&#39;</code> null값은 <code>IS NULL</code>로 비교되어야하는데 등호로 비교되고 있으므로 false가 나오게 되어 <code>name=&#39;hello&#39;</code>인 것들로만 필터링된다. NOT IN의 경우도 마찬가지이다. <code>WHERE name NOT IN (null, &#39;hello&#39;)</code>를 풀어쓰면 <code>WHERE name != null AND name !=&#39;hello&#39;</code>이다. null값은 <code>IS NOT NULL</code>로 비교되어야하므로 false를 반환하여 empty row를 반환한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv2] 중성화 여부 파악하기]]></title>
            <link>https://velog.io/@kaitlin_k/Lv2-%EC%A4%91%EC%84%B1%ED%99%94-%EC%97%AC%EB%B6%80-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kaitlin_k/Lv2-%EC%A4%91%EC%84%B1%ED%99%94-%EC%97%AC%EB%B6%80-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 10 Feb 2022 02:29:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="프로그래머스">프로그래머스</h4>
</blockquote>
<ul>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/59409">https://programmers.co.kr/learn/courses/30/lessons/59409</a></li>
</ul>
<h3 id="나의-풀이">나의 풀이</h3>
<pre><code class="language-js">SELECT ANIMAL_ID, NAME, 
  CASE
    WHEN (SEX_UPON_INTAKE LIKE &#39;Spayed%&#39;) THEN &#39;O&#39;
    WHEN (SEX_UPON_INTAKE LIKE &#39;Neutered%&#39;) THEN &#39;O&#39;
    ELSE &#39;X&#39;
  END AS &#39;중성화&#39;
FROM ANIMAL_INS
ORDER BY ANIMAL_ID;</code></pre>
<hr>

<blockquote>
<h3 id="mysql-조건문">MySQL 조건문</h3>
<p>IF, IFNULL, ISNULL, CASE WHEN</p>
</blockquote>
<h4 id="if">IF</h4>
<p><code>IF(조건문, 참일때 값, 거짓일때 값)</code>
조건이 참 거짓으로 2가지인 경우에 사용하기 적합하다. 위의 풀이를 IF 조건문으로 작성하면 다음과 같다.</p>
<pre><code class="language-js">SELECT ANIMAL_ID, NAME, 
  IF(SEX_UPON_INTAKE LIKE &#39;Spayed%&#39; OR SEX_UPON_INTAKE LIKE &#39;Neutered%&#39;, &#39;O&#39;, &#39;X&#39;) AS &#39;중성화&#39;
FROM ANIMAL_INS
ORDER BY ANIMAL_ID;</code></pre>
<h4 id="ifnull">IFNULL</h4>
<p>필드값이 NULL일때, 대체값을 설정해서 조회할 수 있다. 
<code>IFNULL(칼럼명, 대체값)</code></p>
<pre><code class="language-js">SELECT email, IFNULL(nickname, &#39;no name&#39;) FROM users;</code></pre>
<h4 id="isnull">ISNULL</h4>
<p>ISNULL은 괄호안에 작성할 표현이 NULL이라면 1을 리턴하고 NULL이 아니라면 0을 리턴한다.
<code>ISNULL(값)</code></p>
<pre><code class="language-js">SELECT ISNULL(&quot;&quot;); //0
SELECT ISNULL(null); //1
SELECT ISNULL(&quot;Hello world!&quot;); //0</code></pre>
<h4 id="case-when">CASE WHEN</h4>
<p>IF가 조건문이 참, 거짓인 2가지 경우로 나눌때 적합하다면 CASE WHEN은 3가지 이상의 경우로 분기를 나눠주어야할때 적합하다.</p>
<pre><code class="language-js">CASE
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    WHEN conditionN THEN resultN
    ELSE result //ELSE문이 없다면 NULL을 리턴한다
END AS &#39;필드명&#39;;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv2] 영어 끝말잇기]]></title>
            <link>https://velog.io/@kaitlin_k/Lv2-%EC%98%81%EC%96%B4-%EB%81%9D%EB%A7%90%EC%9E%87%EA%B8%B0</link>
            <guid>https://velog.io/@kaitlin_k/Lv2-%EC%98%81%EC%96%B4-%EB%81%9D%EB%A7%90%EC%9E%87%EA%B8%B0</guid>
            <pubDate>Wed, 09 Feb 2022 00:44:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="프로그래머스">프로그래머스</h4>
</blockquote>
<ul>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/12981">https://programmers.co.kr/learn/courses/30/lessons/12981</a></li>
</ul>
<h3 id="나의-풀이">나의 풀이</h3>
<ul>
<li><p>끝말잇기가 끝나는 경우는 다음과 같다.</p>
<ul>
<li>앞사람이 말한 단어의 마지막 글자와 다음사람이 말한 단어의 첫번째 글자가 다른 경우</li>
<li>이전에 등장했던 단어를 말한 경우</li>
</ul>
</li>
<li><p>리턴해야하는 값은 <code>[차례, 라운드]</code>이다. </p>
<ul>
<li>i는 1부터 시작하고, 인원은 최소 2명 이상이다.</li>
<li>차례는 인덱스+1을 n으로 나눈 나머지이다. 0번째 인덱스는 1번 사람이고, 1번째 인덱스는 2번 사람이다. 만약 나머지가 0이라면, n번째 사람이다. </li>
<li>라운드는 인덱스+1한 나머지가 1일때마다 다시 첫번째 사람으로 돌아온다.<pre><code class="language-js">function solution(n, words) {
let round = 1;
for(let i=1; i&lt;words.length; i++) {
const prev = words[i-1];
const now = words[i];
if((i+1)%n===1) round++;
if(prev[prev.length-1] !== now[0] || words.indexOf(now) !== i) {
  const turn = (i+1)%n || n;
  return [turn, round];
}
}
return [0,0]
}</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="다른-분의-풀이">다른 분의 풀이</h3>
<p>나는 turn과 round라는 변수를 따로 만들어서 사용했는데, 이 풀이에서는 answer라는 변수값을 사용해서 두가지 정보를 만들어낸 점이 너무 신기했다. 그런데, answer에 idx값이 할당되어서 끝말잇기를 끝내는 지점을 찾아냈음에도 계속 reduce가 실행되는 것이 조금 아쉬웠다. </p>
<ul>
<li><code>answer</code>는 끝말잇기가 끝난 인덱스이다. 이를 활용해서 몇번째 라운드인지, 몇번째 사람이 틀렸는지를 알 수 있다. 끝말잇기가 틀리지 않고 모두 끝났다면 <code>answer=0</code>이다. </li>
<li>answer를 n으로 나누고 1을 더해 몇번째 사람이 틀렸는지 구한다.</li>
<li>answer+1한 값을 n으로 나눈 값을 올림해 몇번째 라운드인지 구한다. </li>
<li>answer=0이라면 <code>[0,0]</code>을 리턴한다. <pre><code class="language-js">function solution(n, words) {
let answer = 0; //끝말잇기가 끝난 인덱스
words.reduce((prev, now, idx) =&gt; {
  answer = answer || ((words.slice(0, idx).indexOf(now) !== -1 || prev !== now[0]) ? idx : answer);
  return now[now.length-1];
}, &quot;&quot;);
return answer ? [answer%n+1, Math.ceil((answer+1)/n)] : [0,0];
}                        </code></pre>
</li>
</ul>
<hr> 

<p>위에서 작성한 풀이에서 reduce를 중단하는 코드를 추가해 작성해보았다. reduce에서는 break를 쓸 수 없고, return 시키면 첫번째 인자에 값이 undefined가 담기므로 사용할 수 없다. 따라서, (옵션)인자로 입력받는 네번째 array를 mutate시키는 점을 활용해야한다. </p>
<p>이때 <code>splice</code>를 사용해 현재 인덱스부터 모든 요소를 제거시켜 reduce를 종료시킬 수 있다.</p>
<pre><code class="language-js">function solution(n, words) {
    let answer = 0;
    words.reduce((prev, now, idx, arr) =&gt; {
        answer = answer || ((words.slice(0, idx).indexOf(now) !== -1 || prev !== now[0]) ? idx : answer);
        if(answer) arr.splice(idx); //추가한 부분
        return now[now.length-1];
    }, &quot;&quot;)

    return answer ? [answer%n+1, Math.ceil((answer+1)/n)] : [0,0];
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv2] 오픈채팅방]]></title>
            <link>https://velog.io/@kaitlin_k/Lv2-%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</link>
            <guid>https://velog.io/@kaitlin_k/Lv2-%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</guid>
            <pubDate>Tue, 08 Feb 2022 01:55:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="프로그래머스">프로그래머스</h4>
</blockquote>
<ul>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/42888">https://programmers.co.kr/learn/courses/30/lessons/42888</a></li>
</ul>
<h3 id="나의-풀이">나의 풀이</h3>
<ul>
<li><p>함수 <code>handleEvent</code>에서 uid에 대한 최종적인 닉네임을 <code>obj</code>에 저장한다. </p>
<ul>
<li>닉네임 변경 후에는 모든 기록에서도 닉네임이 변경되어한다. 매번 닉네임이 바뀔때마다 기록을 바꾸지 않고, uid에 대한 최종적인 닉네임을 저장해서 한번에 바꾸도록 한다.  </li>
<li>uid 고유한 값이므로 동일한 닉네임을 가졌더라도 다른 uid를 갖는다. </li>
</ul>
</li>
<li><p>배열 <code>arr</code>의 요소를 반복문으로 방문하여, &#39;Enter&#39;, &#39;Leave&#39;인 것들을 찾아 결과를 배열 <code>res</code>에 푸시한다.  </p>
<ul>
<li>배열 arr에는 record의 요소들이 배열의 형태로 저장되어있다. 
<code>const arr = [[&#39;Enter&#39;, &#39;uid1234&#39;, &#39;Muzi],[&#39;Enter&#39;, &#39;uid4567&#39;, &#39;Prodo&#39;], ...]</code></li>
</ul>
</li>
</ul>
<pre><code class="language-js">function solution(record) {
  const obj = {};
  const arr = [];
  const res = [];
  const handleEvent = (str) =&gt; {
    const words = str.split(&quot; &quot;);
    if(words[0] === &#39;Enter&#39; || words[0] === &#39;Change&#39;) {
      obj[words[1]] = words[2];
    }
    arr.push(words);
  }
  for(let i=0; i&lt;record.length; i++) {
    handleEvent(record[i]);
  }
  for(let i=0; i&lt;arr.length; i++) {
    const name = obj[arr[i][1]];
    if(arr[i][0] === &#39;Enter&#39;) {
      res.push(`${name}님이 들어왔습니다.`);
    }
    else if(arr[i][0] === &#39;Leave&#39;) {
      res.push(`${name}님이 나갔습니다.`);
    }
  }
  return res;
}</code></pre>
<hr>

<h3 id="다른분의-풀이">다른분의 풀이</h3>
<p>나는 반복문과 함수를 분리해서 작성했는데, 이 풀이에서는 <code>forEach</code> 반복문안에서 <code>split</code>, 구조분해할당을 해주어 가독성 있고, 이해하기 쉽다.
그리고, 각 변수에 값을 적절히 저장해서 사용했는데, <code>action</code>은 <code>userInfo</code>와 <code>stateMapping</code>에서 필요한 내용을 찾기 위한 키가 저장되어있다. </p>
<ul>
<li><code>userInfo</code>에 uid에 대한 닉네임을 저장한다.</li>
<li><code>action</code>에 Enter와 Leave에 대한 이벤트와 대상을 저장한다. </li>
<li><code>stateMapping</code>에는 Enter와 Leave에 대해 각각 작성할 문구를 저장한다.</li>
</ul>
<pre><code class="language-js">function solution(record) {
  const userInfo = {};
  const action = [];
  const stateMapping = {
    &#39;Enter&#39; : &#39;님이 들어왔습니다.&#39;,
    &#39;Leave&#39; : &#39;님이 나갔습니다.&#39;
  }
  record.forEach( v =&gt; {
    const [state, id, nick] = v.split(&quot; &quot;);
    if(state !== &quot;Change&quot;) { //state가 Enter, Leave인 경우 
      action.push([state, id])
    }
    if(nick) { //state가 Enter, Change인 경우
      userInfo[id] = nick;
    }
  });
  return action.map(([state, uid]) =&gt; {
    return `${userInfo[uid]}${stateMapping[state]}`;
  });
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[robotPath]]></title>
            <link>https://velog.io/@kaitlin_k/robotPath</link>
            <guid>https://velog.io/@kaitlin_k/robotPath</guid>
            <pubDate>Sun, 06 Feb 2022 03:18:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h3 id="문제">문제</h3>
<p>세로와 가로의 길이가 각각 M, N인 방의 지도가 2차원 배열로 주어졌을 때, 1은 장애물을 의미하고 0 이동이 가능한 통로를 의미합니다. 로봇은 지도 위를 일분에 한 칸씩 상하좌우로 이동할 수 있습니다. 로봇의 위치와 목표 지점이 함께 주어질 경우, <strong>로봇이 목표 지점까지 도달하는 데 걸리는 최소 시간</strong>을 리턴해야 합니다.</p>
<h3 id="입력">입력</h3>
<ul>
<li>인자 1 : room<ul>
<li>배열을 요소로 갖는 배열</li>
<li><code>room.length</code>는 M</li>
<li><code>room[i]</code>는 number 타입을 요소로 갖는 배열</li>
<li><code>room[i].length</code>는 N</li>
<li><code>room[i][j]</code>는 세로로 i, 가로로 j인 지점의 정보를 의미</li>
<li><code>room[i][j]</code>는 0 또는 1<br></li>
</ul>
</li>
<li>인자 2 : src<ul>
<li>number 타입을 요소로 갖는 배열</li>
<li><code>src.length</code>는 2</li>
<li><code>src[i]</code>는 0 이상의 정수</li>
<li>src의 요소는 차례대로 좌표평면 위의 y좌표, x좌표</li>
</ul>
</li>
<li>인자 3 : dst<ul>
<li>number 타입을 요소로 갖는 배열</li>
<li><code>dst.length</code>는 2</li>
<li><code>dst[i]</code>는 0 이상의 정수</li>
<li>dst의 요소는 차례대로 좌표평면 위의 y좌표, x좌표
출력</li>
<li>number 타입을 리턴해야 합니다.</li>
</ul>
</li>
<li>주의사항<ul>
<li>M, N은 20 이하의 자연수입니다.</li>
<li>src, dst는 항상 로봇이 지나갈 수 있는 통로입니다.</li>
<li>src에서 dst로 가는 경로가 항상 존재합니다.</li>
</ul>
</li>
</ul>
<h3 id="레퍼런스-코드">레퍼런스 코드</h3>
<pre><code class="language-js">const robotPath = function (room, src, dst) {
  const aux = (M, N, candi, time) =&gt; {
    const [row, col] = candi;
    if(row&lt;0 || col&lt;0 || row&gt;=M || col&gt;=N) return;
    if(room[row][col] ===0 || room[row][col]&gt;time) {
      room[row][col] = time;
    } else {
      return;
    }

    aux(M, N, [row+1, col], time+1); 
    aux(M, N, [row-1, col], time+1);
    aux(M, N, [row, col-1], time+1);
    aux(M, N, [row, col+1], time+1);
  };

  aux(room.length, room[0].length, src, 1);
  //시작지점까지 걸린 시간은 0분이지만 방문했음을 표시하기 위해 1로 시작한다.

  const [r, c] = dst;
  return room[r][c]-1; //1분부터 시작했으므로 다시 1을 빼준다.
}</code></pre>
<hr>

<h3 id="디버깅">디버깅</h3>
<p>실행순서가 이해되지 않아서 다음과 같이 값을 설정하고 디버깅 해보았다. </p>
<pre><code class="language-js">let room = [
  [0, 1],
  [0, 0],
];
let src = [0,0];
let dst = [1,1];
robotPath(room, src, dst);</code></pre>
<p>다음은 18번째에서 호출한 aux가 첫번째 자식 노드를 끝까지 탐색하는 과정을 작성해보았다. </p>
<ul>
<li><p>첫번째 <code>aux(room.length, room[0].length, src, 1)</code> 실행</p>
<ul>
<li>콜스택에는 하나의 aux가 담겨있고, 6번째줄 조건문에 걸려서 room[0][0]=1이 저장된다.</li>
<li>조건문을 빠져나오면 <strong>12번째에서 또다시 두번째 aux를 호출</strong>한다. (아직 18번째에서 호출한 aux는 끝나지 않은 상태)</li>
<li>또다시 12번째에서 세번째 aux를 호출한다. (지금까지 첫번째, 두번째 aux는 끝나지 않은 상태)<ul>
<li>그런데, row가 배열의 범위를 넘어갔으므로 리턴되었다. </li>
</ul>
</li>
<li>세번째 aux가 리턴되었으므로 두번째 aux가 13번째에서 네번쨰 aux를 호출한다. (두번째 aux로 돌아왔고 네번째 aux가 호출됨)<ul>
<li>네번째 aux는 조건을 만족하지 않아 리턴된다. </li>
</ul>
</li>
<li>두번째 aux가 14번째줄에서 다섯번째 aux를 호출한다. <ul>
<li>다섯번째 aux는 조건을 만족하지 않아 리턴된다.</li>
</ul>
</li>
<li>두번째 aux가 15번째줄에서 여섯번째 aux를 호출한다.<ul>
<li>여섯번째 aux는 <code>room[row][col]===1</code>로 장애물이므로 리턴된다. </li>
</ul>
</li>
<li>드디어 두번째 aux가 끝났다.</li>
</ul>
</li>
<li><p>다시 첫번째 aux로 돌아와서 <strong>첫번째 aux가 13번째줄에서 aux</strong>를 호출한다. </p>
</li>
</ul>
<hr>

<p>배열의 모든 경로를 DFS 탐색한 후에 도착지점에 저장된 시간을 리턴한다. 풀이의 구조를 보면, 함수 aux를 작성하고, 함수 내부에서 함수를 반복호출한다. 이때 각각은 상,하,좌,우로 이동하는 것이다. 
함수를 정의한 후에 함수를 호출한다. 호출된 함수의 모든 실행이 끝나면, 배열 room의 dst값을 조회하여 걸린 시간을 리턴한다. </p>
<p>디버깅없이 풀이만으로는 이해하기가 어려웠는데 디버깅을 하면서 보니까 가장 마지막 depth까지 확인하고 한 depth위로 돌아와 해당 노드의 마지막 depth까지 다시 확인하고, 이 과정을 반복하는 것을 알 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv1] 신고결과 받기]]></title>
            <link>https://velog.io/@kaitlin_k/Lv1-%EC%8B%A0%EA%B3%A0%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@kaitlin_k/Lv1-%EC%8B%A0%EA%B3%A0%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Sat, 05 Feb 2022 04:51:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="프로그래머스">프로그래머스</h4>
</blockquote>
<ul>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/92334">https://programmers.co.kr/learn/courses/30/lessons/92334</a></li>
</ul>
<h3 id="나의-풀이">나의 풀이</h3>
<p>문제를 풀때 배열을 사용하면 반복문으로 원하는 데이터를 찾기까지 최악의 경우 O(N)이 걸리므로 배열의 사용을 지양하고 객체를 사용했다. 하지만 반복문을 많이 사용하다보니 테스트케이스에서 시간이 굉장히 오래걸렸다.  </p>
<ul>
<li><code>list</code>는 유저가 신고한 유저들이 객체형태로 저장되어있다. </li>
<li><code>count</code>는 유저가 신고당한 횟수가 객체형태로 저장되어있다. </li>
<li><code>report</code> 배열을 반복해서 방문하면서, <code>list</code> 객체에 신고한 유저를 추가하고, <code>count</code> 객체에 신고횟수를 더해준다.</li>
<li>유저 순서대로 신고메일 횟수를 리턴해야하므로 유저의 배열을 반복문으로 방문한다. <ul>
<li>유저(A)가 신고한 다른 유저(B) 한명이라도 있다면 count 객체의 B 유저가 신고당한 횟수를 확인한다. k번 이상 신고당했다면, A유저가 받는 메일 갯수를 ++한다. </li>
<li>배열 <code>res</code>에 메일 갯수를 push한다.</li>
</ul>
</li>
<li><code>res</code>를 리턴한다. </li>
</ul>
<pre><code class="language-js">function solution(id_list, report, k) {
  const list = {};
  const count = {};
  const res = [];
  for(let i=0; i&lt;id_list.length; i++) {
    list[id_list[i]]={};
    count[id_list[i]]=0;
  }
  for(let i=0; i&lt;report.length; i++) {
    const ppl = report[i].split(&quot; &quot;);
    const user = ppl[0];
    const reportee = ppl[1];
    if(!list[user][reportee]) {
      list[user][reportee] = 1;
      count[reportee]++;
    }
  }
  for(const user in list) {
    let mails = 0;
    if(list[user]) {
      for(const reportee in list[user]) {
        if(count[reportee] &gt;= k) {
          mails++
        }
      }
    }
    res.push(mails);
  }
  return res;
}   </code></pre>
<h3 id="다른-분의-풀이">다른 분의 풀이</h3>
<p><code>Set</code>을 사용해서 한 유저가 다른 유저를 여러번 신고하는 경우를 제거했고, <code>Map</code>을 사용해서 각 변수의 기능을 분리해서 저장했다. 특히 3번째 테스트케이스가 가장 오래 걸리는데, 여기서 192ms정도가 소요되어 나의 풀이에 비해 63ms정도 빠르다.</p>
<ul>
<li>report 배열에서  new Set을 사용해 반복되는 요소를 제거했고, map 메소드를 사용해서 <code>[신고한 유저, 신고당한 유저]</code>의 형태로 report에 저장한다. </li>
<li>counts은 new Map을 사용해 유저가 신고 당한 횟수를 저장한다. </li>
<li>good은 new Map을 사용해 신고한 유저가 받게 되는 메일 갯수를 저장한다. </li>
<li>마지막으로 id_list 배열에 map 메소드를 사용하여 유저 순서대로 받게되는 메일 갯수를 배열 형태로 리턴한다. <pre><code class="language-js">function solution(id_list, report, k) {
let reports = [...new Set(report)].map((el) =&gt; {
  return el.split(&quot; &quot;);
});
let counts = new Map();
for(const bad of reports) {
  counts.set(bad[1], counts.get(bad[1])+1 || 1);
}
let good = new Map();
for(const report of reports) {
  if(counts.get(report[1])&gt;=k) {
    good.set(report[0], good.get(report[0])+1 || 1);
  }
}
let answer = id_list.map((el) =&gt; good.get(el) ||0);
return answer;
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[crypto를 사용한 비밀번호 암호화]]></title>
            <link>https://velog.io/@kaitlin_k/%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@kaitlin_k/%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Fri, 04 Feb 2022 06:52:04 GMT</pubDate>
            <description><![CDATA[<h2 id="crypto">Crypto</h2>
<p>Crypto 모듈을 사용해 솔트를 생성하고, 해싱알고리즘을 사용해 암호화한 후 인코딩한 결과를 데이터베이스에 저장할 수 있다.</p>
<blockquote>
<h3 id="randombytes-async">randomBytes (ASYNC)</h3>
<p><code>randomBytes(bytes, callback)</code>
randomBytes라는 메소드는 콜백함수를 인자로 받을 수 있다. 콜백함수가 주어질 경우, 비동기적으로 동작시킬 수 있다. 아래의 코드를 보면, randomBytes내의 콜백함수 실행결과가 마지막에 찍힌다.</p>
</blockquote>
<pre><code class="language-js">console.log(&quot;start&quot;);
crypto.randomBytes(128, (err, buf) =&gt; {
  if(err) {
    console.log(err); 
    return;
  }
  console.log(&quot;The random data is: &quot;+buf.toString(&quot;base64&quot;));
});
console.log(&quot;end&quot;);
//start
//end
//The random data is ...</code></pre>
<blockquote>
<h3 id="randombytes-sync">randomBytes (SYNC)</h3>
<p><code>randomBytes(bytes)</code>
randomBytes에 콜백함수를 전달하지 않으면, 동기적으로 실행된다. 따라서 randomBytes가 생성될때까지 다음 코드를 실행하지 못한다. 따라서, 아래의 코드를 실행해보면 순차적으로 콘솔이 찍히는 것을 확인할 수 있다.</p>
</blockquote>
<pre><code class="language-js">console.log(&quot;start&quot;);
const buf = crypto.randomBytes(128);
console.log(buf);
console.log(&quot;The random data is: &quot;+buf.toString(&quot;base64&quot;));
console.log(&quot;end&quot;);
//start
//&lt;Buffer d3 .......&gt;
//The random data is ...
//end</code></pre>
<blockquote>
<h3 id="createhash-update-digest">createHash, update, digest</h3>
<p>동기적으로 <code>salt</code>를 만든 후 <code>비밀번호+salt</code>를 sha512 해시 알고리즘으로 해싱한 후 <code>base64</code>로 인코딩해 데이터베이스에 저장한다. </p>
</blockquote>
<pre><code class="language-js">//controllers/signup.js
const crypto = require(&quot;crypto&quot;);
const { User } = require(&quot;../../models&quot;); //seqeulize 사용함

module.exports = async (req, res) =&gt; {
  try {
    const { nickname, email, password } = req.body;
    const salt = crypto.randomBytes(128).toString(&quot;base64&quot;); //동기적으로 실행됨
    const hashPassword = crypto
      .createHash(&quot;sha512&quot;) //해시 알고리즘
      .update(password + salt) //변환할 문자열
      .digest(&quot;base64&quot;); //인코딩 알고리즘

    const data = await User.create({
      nickname,
      email,
      salt,
      password: hashPassword,
    });
    return res.status(201).end();
  } catch (err) {
    throw err;
  }
};</code></pre>
<blockquote>
<h3 id="pbkdf2-async">pbkdf2 (ASYNC)</h3>
<p><code>pbkdf2Sync(password, salt, iterations, keylen, digest, callback)</code></p>
</blockquote>
<pre><code class="language-js">const db = require(&quot;../../db/db&quot;);
const crypto = require(&quot;crypto&quot;);
module.exports = (req, res) =&gt; {
  const { email, password } = req.body;
  console.log(password);
  crypto.randomBytes(128, (err, buf) =&gt; {
    if (err) {
      console.log(err);
      return;
    }
    const salt = buf.toString(&quot;base64&quot;);
    crypto.pbkdf2(
      password,
      salt,
      100000,
      64,
      &quot;sha512&quot;,
      async (err, derivedKey) =&gt; {
        if (err) throw err;
        const hashedPassword = derivedKey.toString(&quot;base64&quot;);
        console.log(hashedPassword);
        const sql = &quot;INSERT INTO user (email, salt, password) VALUES (?,?,?)&quot;;
        const params = [email, salt, hashedPassword];
        await db.query(sql, params);
        return res.status(201).end();
      }
    );
  });
};</code></pre>
<blockquote>
<h3 id="pbkdf2-sync-async-await">pbkdf2 (SYNC, Async, await)</h3>
<p>node의 내장 모듈인 util을 사용하여 함수의 리턴값을 프로미스의 형태로 변환해 .then 또는 async, await를 사용할 수 있다. 위의 코드에서 콜백함수 안에 콜백함수를 호출하는 것을 가독성있게 다음과 같이 변경할 수 있다. 
(아래의 모듈은 회원가입하거나 비밀번호를 바꿀때 공통적으로 사용되는 내용을 createHash로 저장한 것이다.)</p>
</blockquote>
<pre><code class="language-js">//funcs/createHash.js
const crypto = require(&quot;crypto&quot;); //node 내장모듈
const util = require(&quot;util&quot;); //node 내장모듈
module.exports = async (password) =&gt; {
  try {
    const randomBytesPromise = util.promisify(crypto.randomBytes);
    const pbkdf2Promise = util.promisify(crypto.pbkdf2);

    const buf = await randomBytesPromise(64);
    const salt = buf.toString(&quot;base64&quot;);
    const hashedPassword = await pbkdf2Promise(
      password,
      salt,
      100000,
      64,
      &quot;sha512&quot;
    );
    return [salt, hashedPassword.toString(&quot;base64&quot;)];
  } catch (err) {
    throw err;
  }
};</code></pre>
<blockquote>
<h3 id="pbkdf2sync-sync">pbkdf2Sync (SYNC)</h3>
<p><code>pbkdf2Sync(password, salt, iterations, keylen, digest)</code>
crypto에서 제공하는 pbkdf2메소드에서는 콜백함수를 사용해 비동기적으로 실행했다면, pbkdf2Sync 메소드를 사용해 동기적으로 해싱할 수 있다. 바로 위에서 .then 또는 await를 사용하기 위해 util에서 promisify 메소드를 사용했던 과정을 생략할 수 있다.</p>
</blockquote>
<pre><code class="language-js">const salt = crypto.randomBytes(128).toString(&#39;base64&#39;);
const hashPassword = crypto
.pbkdf2Sync(password, salt, 100000, 64, &#39;sha512&#39;)
.toString(&#39;hex&#39;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Lv2] 구명보트]]></title>
            <link>https://velog.io/@kaitlin_k/Lv2-%EA%B5%AC%EB%AA%85%EB%B3%B4%ED%8A%B8</link>
            <guid>https://velog.io/@kaitlin_k/Lv2-%EA%B5%AC%EB%AA%85%EB%B3%B4%ED%8A%B8</guid>
            <pubDate>Tue, 01 Feb 2022 08:29:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="프로그래머스">프로그래머스</h4>
</blockquote>
<ul>
<li><a href="https://programmers.co.kr/learn/courses/30/lessons/42885">https://programmers.co.kr/learn/courses/30/lessons/42885</a></li>
</ul>
<h3 id="나의-풀이1-효율성-테스트-통과-❌">나의 풀이(1) 효율성 테스트 통과 ❌</h3>
<p>이 문제는 탐욕 알고리즘 중 <a href="https://velog.io/@kaitlin_k/%ED%83%90%EC%9A%95-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">짐 나르기 문제</a>와 동일한 풀이여서 <strong>배열을 사용</strong>해 쉽게 풀었다. 그런데, 효율성 테스트를 하나도 통과하지 못했다. </p>
<pre><code class="language-js">function solution(people, limit) {
  let boat = 0;
  people.sort((a,b) =&gt; b-a);

  while(people.length) {
    boat++;
    const person = people.shift();
    if(person+people[people.length-1]&lt;=limit) {
      people.pop();
    }
  }
  return boat;
}      </code></pre>
<h3 id="나의-풀이2-효율성-테스트-통과-⭕-투-포인터">나의 풀이(2) 효율성 테스트 통과 ⭕ (투 포인터)</h3>
<p>다른 풀이로 투 포인터를 작성해보았는데, 효율성 테스트가 모두 통과되었다.<a href="https://velog.io/@kaitlin_k/%ED%83%90%EC%9A%95-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">짐 나르기 문제</a>에서 사용한 두 번째 풀이법을 떠올리며 풀었다. </p>
<pre><code class="language-js">function solution(people, limit) {
  people.sort((a,b) =&gt; b-a);
  let leftIdx = 0; //가장 무거운 사람 
  let rightIdx = people.length-1; //가장 가벼운 사람
  let boat = 0;

  while(leftIdx&lt;rightIdx) {
    boat++;
    if(people[leftIdx]+people[rightIdx]&lt;=limit) {
      rightIdx--;
    }
    leftIdx++;
  }
  if(leftIdx===rightIdx) boat++;
  return boat;</code></pre>
<hr> 

<h3 id="투-포인터">투 포인터</h3>
<p><strong>정렬된 배열</strong>을 <strong>두개의 포인터</strong>를 이용해 값에 순차적으로 접근한다. 두 포인터를 이동하며 두 개의 합 또는 연산결과를 비교하며 문제를 해결하는 알고리즘 패턴이다. </p>
<h4 id="예제">예제</h4>
<p>특정 숫자들이 들어있는 배열이 주어졌을 때, 배열 안에 가장 첫 번째로 두 숫자의 합이 0이 되는 값 2개를 배열에 담아 리턴하는 함수를 만들어라. (값이 없다면 false를 리턴할 것)</p>
<ul>
<li><p><strong>이중반복문</strong>을 사용한 풀이 👉 시간복잡도 O(N^2)</p>
<pre><code class="language-js">function getZero(arr) {
for(let i=0; i&lt;arr.length; i++) {
  for(let j=i+1; j&lt;arr.length; j++) {
    if(arr[i]+arr[j] === 0) {
      return [arr[i], arr[j]]
    }
  }
}
return false;
}</code></pre>
</li>
<li><p><strong>투 포인터</strong>를 사용한 풀이 👉 시간복잡도 O(N) 
이중반복문이 아닌 하나의 반복문으로 문제를 해결할 수 있다.</p>
<pre><code class="language-js">function getZero(arr) {
arr.sort((a,b) =&gt; a-b); //오름차순 정렬
let leftIdx = 0; //가장 작은 수 
let rightIdx = arr.length-1; //가장 큰 수

while(leftIdx&lt;rightIdx) {
  const sum = arr[leftIdx]+arr[rightIdx];
  if(sum === 0) {
    return [arr[leftIdx], arr[rightIdx]];
  }
  if(sum &lt; 0) { //합이 0보다 작다면, leftIdx를 오른쪽으로 옮긴다
    leftIdx++;
  } else { //합이 0보다 크다면, rightIdx를 왼쪽으로 옮긴다
    rightIdx--;
  }
}
return false;
}  </code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB]connection pool]]></title>
            <link>https://velog.io/@kaitlin_k/DBconnection-pool</link>
            <guid>https://velog.io/@kaitlin_k/DBconnection-pool</guid>
            <pubDate>Tue, 01 Feb 2022 06:34:19 GMT</pubDate>
            <description><![CDATA[<p>클라이언트가 데이터베이스에 접근하기 위해서는 connection이 필요하다. connection pool은 여러개의 connection을 생성해두어 저장해두고 필요할때 꺼내쓰고 반환하는 기법을 말한다.
<img src="https://images.velog.io/images/kaitlin_k/post/463e45cb-69c1-4e9d-8d2b-48f85c7e2658/image.png" alt=""></p>
<h3 id="connection-pool이-필요한-이유">connection pool이 필요한 이유?</h3>
<p>데이터베이스 connection을 생성한는 것은 비용이 많이 드는 작업이다. 매번 connection을 생성해서 연결하고 종료하기 때문에 비효율적이다. 따라서 풀에 여러개의 connection들을 active한 상태로 유지시킨후 필요할때 빌려서 사용한 후 반환한다. 만약 모든 connection이 사용중이라면, 클라이언트는 대기상태로 전환되고, connection이 반환되면, 대기 상태에 있는 클라이언트에게 순차적으로 제공된다. </p>
<ul>
<li>매 연결마다 connection을 만들고 종료(release)시키는 <strong>비용을 줄일 수 있다</strong>. </li>
<li>미리 생성된 conenction을 사용하기 때문에 <strong>DB 접근 시간을 단축</strong>할 수 있다. </li>
<li>DB에 접근하는 connection 수를 제한하여 <strong>DB에 발생할 수 있는 과부하를 방지</strong>할 수 있다.</li>
</ul>
<p>connection 연결 요청이 있을 경우, 풀에 있는 connection 중 사용가능한 것을 할당하여 새로운 connection을 만들지 않아도 된다. 이는 여러 클라이언트로부터 동시에 여러개의 connection 요청들이 있을때, 퍼포먼스가 극대화된다. </p>
<h3 id="mysql에서-connection-pool을-사용할때-장점">MySQL에서 connection pool을 사용할때 장점</h3>
<p>유료 DBMS는 타임아웃이라는 기능이 있어 connection을 자동으로 종료(release)시켜주준다. 무료 DBMS인 MySQL에는 이러한 타임아웃기능이 없지만 connection pool을 사용하면 auto release를 지원한다. </p>
<p>connection pool을 만들고 pool.query를 했을때, 내부적으로 <code>getConnection()</code>, <code>connection.query()</code>, <code>connection.release()</code>가 자동으로 일어난다. </p>
<h3 id="createpool-해보기">createPool 해보기</h3>
<pre><code class="language-js">//db/db.js
const mysql = require(&quot;mysql2&quot;);

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  waitForConnections: true,
  connectionLimit: 300,
  queueLimit: 0,
});

const db = pool.promise(); 
//👉 promise pool을 리턴시켜 controller에서 db를 가져와서 사용할때 .then, .catch, async/await를 사용할 수 있다
module.exports = db;

//controller/users/signup.js
const db = require(&quot;../../db/db&quot;);

module.exports = async (req, res) =&gt; {
  try {
    const {email, password} = req.body;
    const sql = 
    &quot;INSERT INTO user (email, salt, password) 
     VALUES (?,?,?)&quot;;
    const params = [email, &#39;123&#39;, password];
    await db.query(sql, params);
    return res.status(201).end();
  } catch(err) {
    throw err;
  }
}</code></pre>
<h3 id="만약-createpool을-하고-poolpromise를-하지-않는다면">만약 createPool을 하고, <code>pool.promise()</code>를 하지 않는다면?</h3>
<p>이는 promise가 아닌 데이터를 <code>.then</code>, <code>.catch</code>, 또는 <code>async/await</code>로 사용했을때 발생한 에러이다. 따라서, 에러에서는 <code>con.promise().query()</code>의 형태로 사용하거나 <code>mysql2/promise</code>를 사용하거나 위에서 한 것처럼 <code>pool.promise()</code>의 형태를 export하여 에러를 해결할 수 있다.  </p>
<blockquote>
<h4 id="node131605-unhandledpromiserejectionwarning-error">(node:131605) UnhandledPromiseRejectionWarning: Error:</h4>
<p><code>You have tried to call .then(), .catch(), or invoked await on the result of query that is not a promise, which is a programming error. Try calling con.promise().query(), or require(&#39;mysql2/promise&#39;) instead of &#39;mysql2&#39; for a promise-compatible version of the query interface. To learn how to use async/await or Promises check out documentation at https://www.npmjs.com/package/mysql2#using-promise-wrapper, or the mysql2 documentation at https://github.com/sidorares/node-mysql2/tree/master/documentation/Promise-Wrapper.md</code></p>
</blockquote>
<blockquote>
<h4 id="reference">reference</h4>
</blockquote>
<ul>
<li><a href="https://codeforgeek.com/node-mysql-connection-pool-example/">Node.js and MySQL Connection Pool Example</a></li>
<li><a href="https://www.npmjs.com/package/mysql2#installation">Using Promise Wrapper</a></li>
<li><a href="https://cotak.tistory.com/105">[MySQL] Connection Pool을 사용하는 이유</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>