<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>moon_l.log</title>
        <link>https://velog.io/</link>
        <description>세상에 이로운 영향력을 퍼뜨리고 싶은 프론트엔드 개발자 입니다.</description>
        <lastBuildDate>Thu, 09 May 2024 16:10:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>moon_l.log</title>
            <url>https://velog.velcdn.com/images/moon_l/profile/b43ff24f-c380-47a1-a565-da21668fb027/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. moon_l.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/moon_l" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[문제해결] 소셜로그인]]></title>
            <link>https://velog.io/@moon_l/%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@moon_l/%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Thu, 09 May 2024 16:10:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="문제상황">문제상황</h2>
</blockquote>
<ol>
<li>next-auth를 사용하여 구현한 소셜로그인이 로드벨런싱이 적용된 AWS 환경에서 리다이렉트 처리가 되지 않는 문제 발생 </li>
<li>프론트단에서 소셜로그인에 따라 (인가코드 발급 -&gt; 로그인 토큰 요청 -&gt; 사용자 정보 요청) 방식으로 SSR을 사용하여 리다이렉트 페이지 구현</li>
</ol>
<h4 id="ssr-인가코드-발급---로그인-토큰-요청---사용자-프로필-조회---유틸-함수로-분기처리-및-props-리턴---authgooglenaverkakaotsx-파일에서-ssr로-리턴되는-props-사용---유틸-함수-실행---홈-리다이렉트">SSR 인가코드 발급 -&gt; 로그인 토큰 요청 -&gt; 사용자 프로필 조회 -&gt; 유틸 함수로 분기처리 및 props 리턴 -&gt; /auth/(google,naver,kakao).tsx 파일에서 SSR로 리턴되는 props 사용 -&gt; 유틸 함수 실행 -&gt; 홈 리다이렉트</h4>
<p><strong>1. 인가코드 발급 로그인창 요청 -&gt; 소셜 로그인</strong></p>
<pre><code class="language-typescript">// 요청 URL의 상세 파라미터값은 공식문서를 참고해보길 바란다.

// 구글로그인 요청 URL은 https://accounts.google.com/o/oauth2/v2/auth 이다.  
const googleAuthURL = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/google&amp;response_type=code&amp;scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile`;
window.location.href = googleAuthURL;

// 네이버로그인 요청 URL은 https://nid.naver.com/oauth2.0/authorize 이다.
const naverAuthURL = `https://nid.naver.com/oauth2.0/authorize?response_type=code&amp;client_id=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/naver&amp;state=1234`;
window.location.href = naverAuthURL;

// 카카오 로그인 요청 URL은 https://kauth.kakao.com/oauth/authorize 이다.
const kakaoAuthURL = `https://kauth.kakao.com/oauth/authorize?response_type=code&amp;client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/kakao`;
window.location.href = kakaoAuthURL;</code></pre>
<p><strong>2.로그인 완료 -&gt; SSR 로그인 엑세스 토큰 요청 -&gt; 사용자 정보 조회</strong></p>
<ul>
<li>리다이렉트 URL 경로 -&gt; pages/auth/(google,naver,kakao).tsx
  a. 구글: <a href="http://localhost:3000/auth/google">http://localhost:3000/auth/google</a>
  b. 네이버: <a href="http://localhost:3000/auth/naver">http://localhost:3000/auth/naver</a>
  c. 카카오: <a href="http://localhost:3000/auth/kakao">http://localhost:3000/auth/kakao</a></li>
</ul>
<pre><code class="language-typescript">const getServerSideProps = ({ query }) =&gt; {
  // query 값
  {
    code: &#39;testestestestestest&#39;, // 발급받은 인가코드
    scope: &#39;email profile&#39; // 범위,
    authuser: &#39;0&#39;,
    prompt: &#39;none&#39;
  }

  // 로그인 엑세스 토큰 요청
  const getLoginToken = await axios({
    method: &#39;POST&#39;,
    url: &#39;https://oauth2.googleapis.com/token&#39;,
    data: {
      grant_type: &#39;authorization_code&#39;, // 인증 구분 값
      client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, // 개발자센터에서 발급받은 클라이언트 아이디
      client_secret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET, // 개발자센터에서 발급받은 CLIENT_SECRET
      redirect_uri: `${process.env.NEXT_PUBLIC_REDIRECT_URL}/google`, // 리다이렉트 URL 프론트 지정
      code: query.code, // 발급받은 인가코드 넣어주기
    },
    headers: {
      &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;, // 필수 값
    },
  });

  // 소셜 사용자 정보 조회
  const getProfile = await axios({
    method: &#39;GET&#39;,
    url: &#39;https://www.googleapis.com/oauth2/v3/userinfo&#39;,
    params: {
      access_token: getLoginToken.data.access_token,
    },
  });
}</code></pre>
<p><strong>3.유틸 함수 분기처리 및 SSR 프롭스 리턴</strong></p>
<pre><code class="language-typescript">// 이 함수 또한 getServerSideProps에 포함되어 있다.
// 프로파일이 있을경우로 분기를 나누었다.
    if (getProfile) {
      return await socialLogin(&#39;GOOGLE&#39;, getLoginToken.data, getProfile.data);
    } else {
      return loginRedirect(&#39;프로필 정보를 가져오지 못했습니다. 로그인페이지로 이동합니다.&#39;);
    }</code></pre>
<pre><code class="language-typescript">// 첫 번째 인자인 _provider 인자로 소셜 타입을 확인하여 이메일을 필터링한다.
// 두 번째 인자인 _tokenInfo 객체의 속성값인 access_token를 리턴해준다.
export const socialLogin = (_provider: string, _tokenInfo: any, _profileInfo: any) =&gt; {
  const filterSocialEmail =
    _provider === &#39;NAVER&#39;
      ? _profileInfo.response.email
      : _provider === &#39;KAKAO&#39;
      ? _profileInfo.kakao_account.email
      : _provider === &#39;GOOGLE&#39; || _provider === &#39;FACEBOOK&#39;
      ? _profileInfo.email
      : &#39;&#39;;
  return {
    props: {
      userEmail: filterSocialEmail, // 소셜 타입에 따른 이메일
      access_token: _tokenInfo.access_token, // 로그인 엑세스 토큰
    },
  };
};</code></pre>
<p><strong>4./auth/(google,naver,kakao).tsx 파일 -&gt; SSR 프롭스 사용 -&gt; 리다이렉트 함수 실행</strong></p>
<pre><code class="language-typescript">
// SSR을 제외한 리다이렉트 컴포넌트의 코드이다.
const GoogleRedirectPage: NextPage = ({ userEmail, access_token }) =&gt; {
  useSocialLoginRedirect(&#39;google&#39;, userEmail, access_token);
  return &lt;&gt;&lt;/&gt;;
};

export default GoogleRedirectPage;

// useSocialLoginRedirect === 여러 컴포넌트에서 사용 가능하게 리액트 훅으로 사용하였다.
// useRouter를 함수의 인자로 넘겨주는 이유는 SSR 구성이라 useRouter를 ts 스크립트단에서는 사용하지 못하기 때문이다.
const useSocialLoginRedirect = (_provider: string, _userEmail: string, _access_token: string) =&gt; {
  const router = useRouter();

  useEffect(() =&gt; {
    if (_provider !== undefined &amp;&amp; _userEmail !== undefined &amp;&amp; _access_token !== undefined) {
      homeRedirect(router, _provider, _access_token);
    }
  }, [_provider, _userEmail, _access_token]);

  return &lt;&gt;&lt;/&gt;;
};

export default useSocialLoginRedirect;

// 해당 함수가 ts 파일에 있기 때문에 useRouter를 인자로 받아온다.
export const homeRedirect = (_router: NextRouter, _provider: string, _access_token: string) =&gt; {

  // 엑세스토큰 여부에 따라 router.replace로 홈 리다이렉트
  if (_access_token !== &#39;&#39;) {
    const expires = dayjs().add(30, &#39;days&#39;).toDate();
    setCookie(&#39;provider&#39;, _provider, { expires, path: &#39;/&#39; }); // 유효기간 설정 소셜 타입 저장
    setCookie(&#39;sns_access_token&#39;, _access_token, { expires, path: &#39;/&#39; }); // 유효기간 설정 토큰 저장
    _router.replace(&#39;/&#39;); 
  } else {
    console.log(&#39;소셜로그인 실패&#39;);
  }
};</code></pre>
<h2 id="요약-정리">요약 정리</h2>
<ul>
<li>예시는 모두 구글로 되어 있지만 방식에는 차이가 없기 때문에 소셜 타입만 바꿔주면 된다.</li>
</ul>
<ol>
<li>로그인 창 요청 -&gt; 인가코드 발급</li>
<li>각 소셜로그인 개발자 센터에 등록된 리다이렉트 URL로 이동 ( /pages/auth/( google,naver,kakao ) )</li>
<li>SSR로 인가코드 받아와 로그인 토큰, 사용자 프로필 조회</li>
<li>리다이렉트 유틸 함수 리턴 -&gt; 엑세스 토큰 여부에 따라 홈 또는 로그인 페이지로 이동</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OAuth] 네이버로그인]]></title>
            <link>https://velog.io/@moon_l/OAuth-%EB%84%A4%EC%9D%B4%EB%B2%84%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@moon_l/OAuth-%EB%84%A4%EC%9D%B4%EB%B2%84%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Fri, 03 May 2024 05:00:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ol>
<li>네이버 개발자센터 애플리케이션 등록</li>
<li>인가 코드 발급</li>
<li>로그인 토큰 발급</li>
<li>사용자 정보 가져오기</li>
</ol>
</blockquote>
<h3 id="1-네이버-개발자센터-애플리케이션-등록"><strong>1. 네이버 개발자센터 애플리케이션 등록</strong></h3>
<ul>
<li>네이버 API 가이드가 워낙 쉽게 되어있어서 개발자센터만 들어가도 수월하게 구현 할 수 있다.
<img src="https://velog.velcdn.com/images/moon_l/post/d353f5da-aeff-40c8-bb1c-856e0e53757e/image.png" alt="네이버 개발자센터 애플리케이션 등록"></li>
</ul>
<br />

<h3 id="2-인가코드-발급"><strong>2. 인가코드 발급</strong></h3>
<ul>
<li>네이버 로그인창 요청 URL은 <a href="https://nid.naver.com/oauth2.0/authorize">https://nid.naver.com/oauth2.0/authorize</a> 이다.<pre><code class="language-typescript">// 샘플
https://nid.naver.com/oauth2.0/authorize
?response_type=code
&amp;client_id=CLIENT_ID
&amp;state=STATE_STRING
&amp;redirect_uri=CALLBACK_URL
</code></pre>
</li>
</ul>
<p>// 실제 요청 URL
<a href="https://nid.naver.com/oauth2.0/authorize">https://nid.naver.com/oauth2.0/authorize</a>
?response_type=code
&amp;client_id=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}
&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/naver
&amp;state=1234</p>
<p>// 응답 성공 시 출력 정보
// code: 인가권한코드, state: URL에 추가한 state 값
code: &#39;&#39;, 
state: &#39;1234&#39; </p>
<pre><code>&lt;br/&gt;

**네이버 로그인창 호출 필수 파라미터**
- client_id: 네이버 개발자센터에서 생성한 클라이언트 아이디
- redirect_uri: 네이버 개발자센터에서 설정한 리다이렉트 URL 이다. 내 프로젝트의 경우 http://localhost:3000/auth/naver로 설정하였다.
- response_type: &#39;code&#39;로 고정이다. (인증과정에 대한 내부 구분값)
- state: 사이트 간 요청 위조(cross-site request forgery) 공격을 방지하기 위해 애플리케이션에서 생성한 상태 토큰값으로 URL 인코딩을 적용한 값을 사용을 위해 사용하지만 지금은 생략하겠다.
&lt;br /&gt;


### **3. 로그인토큰 요청**
- 로그인 토큰 요청 URL은 https://nid.naver.com/oauth2.0/token 이다.

```typescript
// 샘플
https://nid.naver.com/oauth2.0/token
?grant_type=authorization_code
&amp;client_id=jyvqXeaVOVmV
&amp;client_secret=527300A0_COq1_XV33cf
&amp;code=EIc5bFrl4RibFls1
&amp;state=9kgsGTfH4j7IyAkg

// 실제 요청 URL
https://nid.naver.com/oauth2.0/token
?grant_type=&#39;authorization_code&#39;
&amp;client_id=process.env.NEXT_PUBLIC_NAVER_CLIENT_ID
&amp;client_secret=process.env.NEXT_PUBLIC_NAVER_CLIENT_SECRET
&amp;code=query.code
&amp;state=query.state

// 응답 성공 시 출력 정보
access_token: &#39;&#39;  // 로그인 액세스 토큰
refresh_token: &#39;&#39;, // 리프레쉬 토큰 갱신
token_type: &#39;bearer&#39;, // bearer 고정
expires_in: &#39;3600&#39; // 유효기간</code></pre><br />

<p><strong>네이버 로그인 토큰 발급 필수 파라미터</strong></p>
<ul>
<li>client_id: 네이버 개발자센터에서 생성한 클라이언트 아이디</li>
<li>client_secret: 네이버 개발자센터에 생성된 client_secret</li>
<li>grant_type: 인증과정에 대한 구분값으로 발급에 해당하는 &#39;authorization_code&#39;로 요청</li>
<li>code: 인가코드 -&gt; SSR로 받은 query값 사용 (query.code)</li>
<li>state: SSR로 받은 query값 사용 (query.state)  <br />

</li>
</ul>
<h3 id="4-사용자-정보-조회"><strong>4. 사용자 정보 조회</strong></h3>
<ul>
<li>네이버 사용자 정보 요청 URL은 <a href="https://openapi.naver.com/v1/nid/me">https://openapi.naver.com/v1/nid/me</a> 이다.</li>
<li>더 자세한 출력 정보는 [네이버 API 가이드](<a href="https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8">https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8</a>) 여기서 확인 할 수 있다.</li>
</ul>
<pre><code class="language-typescript">// 샘플
curl  -XGET &quot;https://openapi.naver.com/v1/nid/me&quot;
      -H &quot;Authorization: Bearer AAAAPIuf0L+qfDkMABQ3IJ8heq2mlw71D&quot;

// 실제 요청 URL
    url: &#39;https://openapi.naver.com/v1/nid/me&#39;,
      headers: {
        &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;,
        Authorization: &#39;Bearer &#39; + getLoginToken.data.access_token,
      }

// 응답 성공 시 출력 정보
  resultcode: &#39;00&#39;, // API 호출 결과 코드
  message: &#39;success&#39;, // 호출 결과 메시지
  response: {
    id: &#39;&#39;, // 동일인 식별 정보 고유 ID 값
    nickname: &#39;달수박&#39;, // 닉네임
    gender: &#39;M&#39;, // 성별
    email: &#39;&#39;, // 이메일
    name: &#39;&#39; // 이름
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OAuth] 구글로그인 (2)]]></title>
            <link>https://velog.io/@moon_l/OAuth-%EA%B5%AC%EA%B8%80%EB%A1%9C%EA%B7%B8%EC%9D%B8-2</link>
            <guid>https://velog.io/@moon_l/OAuth-%EA%B5%AC%EA%B8%80%EB%A1%9C%EA%B7%B8%EC%9D%B8-2</guid>
            <pubDate>Fri, 26 Apr 2024 08:42:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="구글로그인-구현-순서">구글로그인 구현 순서</h3>
</blockquote>
<ol>
<li>인가코드 발급</li>
<li>로그인토큰 발급</li>
<li>사용자 정보 받기</li>
</ol>
<h3 id="1-인가코드-발급">1. <strong>인가코드 발급</strong></h3>
<ul>
<li>해당 코드는 구글 로그인창을 띄우는 코드이며 구글 로그인 버튼을 클릭했을 때 실행되게끔 작업하였다. client_id와 같은 개인정보는 next.config로 환경변수 처리하였다.<pre><code class="language-typescript">https://accounts.google.com/o/oauth2/v2/auth
?client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}
&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}
&amp;response_type=code
&amp;scope=email profile</code></pre>
<br/>

</li>
</ul>
<p><strong>구글 로그인창 호출 필수 파라미터</strong></p>
<ul>
<li>client_id: 구글 클라우드에서 생성한 클라이언트 아이디</li>
<li>redirect_uri: 구글 클라우드에서 설정한 리다이렉트 URL 이다. 내 프로젝트의 경우 <a href="http://localhost:3000/auth/google%EB%A1%9C">http://localhost:3000/auth/google로</a> 설정하였다.</li>
<li>response_type: &#39;code&#39;로 고정이다.</li>
<li>scope: 토큰 발급 이후 유저 정보에서 어떤 항목을 조회할것인지 작성해준다. 내 프로젝트의 경우 email과 profile만 조회하였다.<br/>

</li>
</ul>
<h3 id="2-ssr로-로그인토큰프로필-조회">2. <strong>SSR로 로그인토큰,프로필 조회</strong></h3>
<p><strong>사용자가 페이지 진입전 서버단에서 먼저 처리하기 위한 SSR</strong></p>
<pre><code class="language-typescript">export const getServerSideProps = async ({ query }) =&gt; {

  // GOOGLE 토큰 정보 조회
  const getTokenInfo = await axios({
    method: &#39;POST&#39;,
    url: &#39;https://oauth2.googleapis.com/token&#39;,
    data: {
      grant_type: &#39;authorization_code&#39;,
      client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
      client_secret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET,
      redirect_uri: `${process.env.NEXT_PUBLIC_REDIRECT_URL}/google`,
      code: query.code,
      access_type: &#39;offline&#39;,
      prompt: &#39;consent&#39;,
    },
    headers: {
      &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;,
    },
  });
}

// 응답 성공 시 출력 정보
{
    access_token: &#39;asdfasdfasdfasdf&#39;
    expires_in: &#39;30230&#39;
    scope: &#39;asdfasdf&#39;
    token_type: &#39;Bearer&#39;
    id_token: &#39;asdfasdf&#39;
}</code></pre>
<br/>

<p><strong>구글 토큰 정보 조회 request</strong></p>
<ul>
<li>grant_type: &#39;authorization_code&#39; 고정 </li>
<li>client_id: 구글 클라우드에서 생성한 클라이언트 아이디</li>
<li>client_secret: 구글 클라우드에서 생성한 보안비밀번호</li>
<li>redirect_uri: 구글 클라우드에서 설정한 리다이렉트 URL</li>
<li>code: 구글로그인후 받아온 쿼리의 인가코드<br/>

</li>
</ul>
<pre><code class="language-typescript">// GOOGLE 프로필 정보 조회
const getProfile = await axios({
      method: &#39;GET&#39;,
      url: &#39;https://www.googleapis.com/oauth2/v3/userinfo&#39;,
      params: {
        access_token: getLoginToken.data.access_token,
      },
    });

// 응답 성공 시 출력 정보
{
  sub: &#39;1354315&#39;,
  name: &#39;asd&#39;,
  given_name: &#39;asd&#39;,
  family_name: &#39;asd&#39;,
  picture: &#39;https://lh3.googleusercontent.com/a/ACg8ocLcWGDZOGv560HyQIOatWSlqKZ4KA552xISUHACPpvIXsVrhQ=s96-c&#39;,
  email: &#39;eksh2@gmail.com&#39;,
  email_verified: true
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OAuth] 구글로그인 (1)]]></title>
            <link>https://velog.io/@moon_l/OAuth-%EA%B5%AC%EA%B8%80%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@moon_l/OAuth-%EA%B5%AC%EA%B8%80%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Thu, 25 Apr 2024 05:16:33 GMT</pubDate>
            <description><![CDATA[<h2 id="google-cloud-setting">Google Cloud Setting</h2>
<ol>
<li><strong>google cloud 프로젝트 생성</strong></li>
</ol>
<ul>
<li><p>구글 클라우드 페이지 접속 -&gt; 오른쪽 상단 콘솔 클릭 -&gt; API 및 서비스 클릭 
<img src="https://velog.velcdn.com/images/moon_l/post/301efb30-6c0c-48cc-9a52-3451ce49e8fd/image.png" alt="google cloud"></p>
</li>
<li><p>프로젝트 생성
<img src="https://velog.velcdn.com/images/moon_l/post/2cdf83d2-a322-4a83-8d69-087018f790cc/image.png" alt="make project"></p>
</li>
</ul>
<ol start="2">
<li><strong>OAuth 동의 설정</strong></li>
</ol>
<ul>
<li><p>왼쪽 네비게이션에서 OAuth 설정 클릭 -&gt; 외부 선택 
<img src="https://velog.velcdn.com/images/moon_l/post/b1086c9f-0a44-4501-a640-067e9dd63d67/image.png" alt="Oauth setting"></p>
</li>
<li><p>상단의 앱 정보와 하단의 개발자 정보 입력 -&gt; 저장 후 계속
<img src="https://velog.velcdn.com/images/moon_l/post/3ae23522-b207-483f-a31b-30e849886a73/image.png" alt="Oauth info"></p>
</li>
<li><p>범위 선택 -&gt; 상단의 3개 체크 후 저장 -&gt; 저장 후 계속
<img src="https://velog.velcdn.com/images/moon_l/post/75a0c53e-b94c-425b-9b69-8b1d042443a3/image.png" alt="Oauth range"></p>
</li>
<li><p>내가 테스트에 사용할 계정 입력 -&gt; 저장 후 계속 -&gt; 완료
<img src="https://velog.velcdn.com/images/moon_l/post/44ad045b-0e21-401a-96b4-2d460a690411/image.png" alt="Oauth email"></p>
</li>
</ul>
<ol start="3">
<li><strong>OAuth 클라이언트 ID</strong></li>
</ol>
<ul>
<li><p>왼쪽 네비게이션 사용자인증정보 -&gt; Oauth 클라이언트 ID 생성
<img src="https://velog.velcdn.com/images/moon_l/post/35d636de-50b1-4981-97fd-7267439aa17f/image.png" alt="Oauth client ID"></p>
</li>
<li><p>필수 정보 및 URL 추가 -&gt; 첫 번째 URL은 로컬 주소 입력 -&gt; 리디렉션 URL은 구글 로그인후 리다이렉트 될 URL 입력
<img src="https://velog.velcdn.com/images/moon_l/post/bab424d6-e657-4d0d-89ba-2ef936a12454/image.png" alt="redirect URL"></p>
</li>
</ul>
<ol start="4">
<li><strong>대시보드를 확인해보면 클라이언트 ID 가 생성 되었을것이다.</strong></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[환경설정] Next.js(ver.14) + styled-components]]></title>
            <link>https://velog.io/@moon_l/%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-Next.jsver.14-styled-components</link>
            <guid>https://velog.io/@moon_l/%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-Next.jsver.14-styled-components</guid>
            <pubDate>Tue, 23 Apr 2024 03:24:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="nextjs--styled-components">Next.js + styled-components</h3>
</blockquote>
<ul>
<li>평소에 emotion을 즐겨 사용하지만 Next.js ver14는 아직 서버컴포넌트에서의 emotion이 제대로 동작하지 않는다는 이슈가 있어 styled-components를 사용하였다.</li>
<li>yarn add styled-components</li>
</ul>
<hr>
<h2 id="styled-components-설정">styled-components 설정</h2>
<ul>
<li>styleRegistry.tsx 파일을 생성한다. useServerInsertedHTML로 웹 구조를 읽어들일 때 head 태그에 style을 넣어주는 작업을 진행한다.  </li>
</ul>
<pre><code class="language-typescript">// 경로: /public/styles/styleRegistry.tsx_

&#39;use client&#39;;
import React, { useState } from &#39;react&#39;;
import { useServerInsertedHTML } from &#39;next/navigation&#39;;
import { ServerStyleSheet, StyleSheetManager } from &#39;styled-components&#39;;

export default function StyledComponentsRegistry({ children }: { children: React.ReactNode }) {
  const [styledComponentsStyleSheet] = useState(() =&gt; new ServerStyleSheet());

  useServerInsertedHTML(() =&gt; {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return &lt;&gt;{styles}&lt;/&gt;;
  });

  if (typeof window !== &#39;undefined&#39;) return &lt;&gt;{children}&lt;/&gt;;

  return &lt;StyleSheetManager sheet={styledComponentsStyleSheet.instance}&gt;{children}&lt;/StyleSheetManager&gt;;
}</code></pre>
<ul>
<li>전역스타일 설정을 위해 GlobalStyle.tsx 파일을 생성해주고 &#39;use client&#39;를 꼭 명시해준다. reset 설정은 커스텀해서 작성해도 되지만 귀찮다면 styled-components에서 제공해주는 reset 모듈을 사용하여도 괜찮다.</li>
<li>yarn add styled-reset</li>
</ul>
<pre><code class="language-typescript">// 경로: /public/styles/GlobalStyle.tsx_

&#39;use client&#39;;
import { createGlobalStyle } from &#39;styled-components&#39;;
import reset from &#39;styled-reset&#39;; // reset 사용시

const GlobalStyles = createGlobalStyle`
${reset} // reset 사용시
`;

export default GlobalStyles;</code></pre>
<ul>
<li>마지막으로, layout.tsx에 두 파일을 import하여 선언해주면 설정 완료!!<pre><code class="language-typescript">import StyledComponentsRegistry from &#39;./page&#39;;
import GlobalStyles from &#39;../../public/styles/GlobalStyle&#39;;
</code></pre>
</li>
</ul>
<p>export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <GlobalStyles />
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  );
}</p>
<p>```</p>
<h3 id="개선할점">개선할점</h3>
<ul>
<li>아직 새로고침을 하거나 새로 렌더링 될 때 스타일이 적용이 안되어있는 상태로 나타나는데 SSR 렌더링에 대해 더 알아볼 필요가 있다. 작업을 진행해보고 이후 글을 수정 할 예정이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[문제해결] PDF 출력]]></title>
            <link>https://velog.io/@moon_l/%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0-PDF-%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@moon_l/%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0-PDF-%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Sat, 30 Mar 2024 12:22:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="문제발생">문제발생</h3>
<p>회사에선 보고서를 생성하는 솔루션을 서비스중이었다.
어느날, PDF 출력이 안된다는 고객 문의가 있었다.
테스트를 해보니 보고서의 양이 많으면 PDF 출력이 안되었다. </p>
</blockquote>
<h3 id="해결과정">해결과정</h3>
<ul>
<li>기존 toPng 사용 후 PDF 출력 -&gt; toJpeg 사용 후 PDF 출력</li>
<li>기존 png 파일들을 모아 PDF 출력하다보니 용량이 크고 시간을 초과하는 경우가 있었다. jpeg로 변환 후 출력해보니 문제가 해결되었다.</li>
<li>png -&gt; jpeg로 변경하며 PDF가 선명하지 않을것을 걱정했지만 선명하게 잘 보여 걱정을 덜었다.<h3 id="간단-요약">간단 요약</h3>
</li>
<li>기존 png 변환 후 PDF 출력
이미지 38장 기준 1분7초 메모리 용량이 너무 커 pdf 저장 불가</li>
<li>JPEG 로 변경 후 PDF 출력
이미지 38장 기준 50초 31.9MB 출력 가능</li>
</ul>
<pre><code class="language-typescript">// 이미지 변환
const changeImg = async (_img: any) =&gt; {
  const canvas = await htmlToImage.toPng(_img,{}); // 기존
  const canvas = await htmlToImage.toJpeg(_img,{}); // 변경 후
  return canvas
};

// 변환된 이미지를 사용하여 PDF를 만드는 함수
const canvas = await converToImg();
const doc = new jsPDF({
  unit: &#39;px&#39;,
  orientation: &#39;p&#39;,
  format: &#39;a4&#39;,
});
doc.addImage(canvas, &#39;PNG&#39;, 0, 0, 길이, 높이); // 기존
doc.addImage(canvas, &#39;JPEG&#39;, 0, 0, 길이, 높이); // 변경 후
</code></pre>
<h3 id="느낀점">느낀점</h3>
<ul>
<li>처음에 이 문제를 맞닥뜨렸을때는 메모리 문제가 아닌가 하는 의견이 있었다. 해서 png로 변환한 이미지 파일이 메모리를 많이 잡아먹는게 아닌가 하는 생각이 들었고 jpeg로 변환 후 출력하여 문제를 해결했었다.</li>
<li>프론트단에서 항상 이슈가되는 이미지 최적화에 대해 이를 이용한 PDF 출력에 대해 더 공부해봐야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 숫자 문자열과 영단어]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%88%AB%EC%9E%90-%EB%AC%B8%EC%9E%90%EC%97%B4%EA%B3%BC-%EC%98%81%EB%8B%A8%EC%96%B4</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%88%AB%EC%9E%90-%EB%AC%B8%EC%9E%90%EC%97%B4%EA%B3%BC-%EC%98%81%EB%8B%A8%EC%96%B4</guid>
            <pubDate>Sat, 30 Mar 2024 06:06:20 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<h4 id="다음은-숫자의-일부-자릿수를-영단어로-바꾸는-예시입니다">다음은 숫자의 일부 자릿수를 영단어로 바꾸는 예시입니다.</h4>
<p>1478 → &quot;one4seveneight&quot;
234567 → &quot;23four5six7&quot;
10203 → &quot;1zerotwozero3&quot;</p>
<h4 id="숫자에-대응하는-영단어">숫자에 대응하는 영단어</h4>
<p>숫자 - 영단어
0 - zero
1 - one
2 -    two
3 -    three
4 -    four
5 -    five
6 -    six
7 -    seven
8 -    eight
9 -    nine</p>
<h3 id="해답">해답</h3>
<ul>
<li>이번 문제가 오래 걸렸던 이유는 문제 파악을 제대로 못했기 때문이다.
이 문제의 핵심은 문자에 대응되는 숫자로 바꿔주는것인데 나는 문자열 전체를 탐색해서 특정 문자를 찾는 방법을 생각했기 때문에 방향성부터 틀렸다고 볼 수 있다.</li>
<li>아래 코드를 보면 매우 간단하게 처리할수 있기 때문에 문제파악의 중요성을 느낄수 있는 문제였다.</li>
</ul>
<pre><code class="language-javascript">function solution(s) {
    const arr = [&#39;zero&#39;, &#39;one&#39;, &#39;two&#39;, &#39;three&#39;, &#39;four&#39;, &#39;five&#39;, &#39;six&#39;, &#39;seven&#39;, &#39;eight&#39;, &#39;nine&#39;]
    arr.forEach((e,i) =&gt; {
        s = s.split(e).join(i)
    })
    return Number(s)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 약수의 개수와 덧셈]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%95%BD%EC%88%98%EC%9D%98-%EA%B0%9C%EC%88%98%EC%99%80-%EB%8D%A7%EC%85%88</guid>
            <pubDate>Tue, 19 Mar 2024 08:50:53 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>두 정수 left와 right가 매개변수로 주어집니다. left부터 right까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 solution 함수를 완성해주세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>left - right - result
13 ~ 17 -&gt; 43
24 ~ 27 -&gt; 52</p>
<h3 id="해답">해답</h3>
<ul>
<li>for문을 사용할때마다 몇번을 반복할지 고민하게 되는데 해당 문제에는 </li>
<li><em>1 ≤ left ≤ right ≤ 1,000*</em> 이라는 제한사항이 제시되어 있었기 때문에 입력 받는 left,right 값을 비교하여 while 문을 작성하였다.</li>
<li>처음엔 모든 약수를 더하는 코드를 짜게 되었는데 문제파악을 다시 하고나서 보니 각각의 left값에 따른 약수 개수에 따라 연산하는 문제였다.</li>
<li>첫번째 코드는 가장 좋아요를 많이 받은 풀이이다. 물론 해당 풀이를 보았지만 isInteger과 Math.sqrt에 대한 이해가 부족하여 사용하지는 않았다.</li>
<li>두번째 풀이를 보면 cnt의 값에따라 answer 변수의 값을 더하거나 빼주는 코드를 작성하였다. 이 풀이에서 눈여겨봐야할 점은 left++ 연산의 위치이다. 해당 연산이 윗줄에 있는지 아랫줄에 있는지에 따라 첫 left값을 포함하고 안하고를 결정하기 때문이다.</li>
<li>도중에 막혀서 풀이를 찾아보고 나름 내 풀이로 해석한 코드이다.</li>
</ul>
<pre><code class="language-javascript">function solution(left, right) {
  var answer = 0;
  for (let i = left; i &lt;= right; i++) {
    if (Number.isInteger(Math.sqrt(i))) {
      answer -= i;
    } else {
      answer += i;
    }
  }
  return answer;
}</code></pre>
<pre><code class="language-javascript">function solution(left, right) {
    let answer = 0;

    while(left &lt;= right) {
    let cnt = 0;
        for(let i = 0; i &lt;= right; i++){
            if(left &gt;= i) {
                if(left % i === 0) {
                    cnt++
                }
            }
        }
        answer = cnt % 2 === 0 ? answer + left : answer - left 
        left++
    }
    return answer
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 이상한 문자 만들기]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%9D%B4%EC%83%81%ED%95%9C-%EB%AC%B8%EC%9E%90-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%9D%B4%EC%83%81%ED%95%9C-%EB%AC%B8%EC%9E%90-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 03 Mar 2024 09:41:23 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>문자열 s는 한 개 이상의 단어로 구성되어 있습니다. 각 단어는 하나 이상의 공백문자로 구분되어 있습니다. 각 단어의 짝수번째 알파벳은 대문자로, 홀수번째 알파벳은 소문자로 바꾼 문자열을 리턴하는 함수, solution을 완성하세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>input: &quot;try hello world&quot;
output: &quot;TrY HeLlO WoRlD&quot;</p>
<h3 id="해답">해답</h3>
<ul>
<li>이중배열처럼 풀이해야 하는 문제는 항상 두개의 반복문을 사용하는 편이다.</li>
<li>이 문제의 핵심은 공백을 기준으로 단어를 나누는거라고 생각한다. 가장먼저 split() 가 생각났고 조건에 맞게 소문자 대문자를 구분해서 문자열을 추가해준뒤 첫번째 반복문이 끝날때 조건문을 설정하여 공백을 추가해주었다.</li>
<li>이후 리턴값에서는 다시 공백을 기준으로 정렬하고 join을 사용해 답을 내놓았다.</li>
</ul>
<pre><code class="language-javascript">function solution(s) {
    let answer = &#39;&#39;;
    s = s.split(&#39; &#39;)
    for(let i = 0; i &lt; s.length; i++){
        for(let j = 0; j &lt; s[i].length; j++){
            if( j === 0 || j % 2 === 0){
                answer += s[i][j].toUpperCase()
            }else {
                answer += s[i][j].toLowerCase()
            }
        }
        if(i &lt; s.length - 1) {
            answer += &#39;\n&#39;
        }
    }
    return answer.split(&#39;\n&#39;).join(&#39; &#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 문자열 내림차순 배치]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C-%EB%B0%B0%EC%B9%98</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C-%EB%B0%B0%EC%B9%98</guid>
            <pubDate>Sun, 03 Mar 2024 09:12:54 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>문자열 s에 나타나는 문자를 큰것부터 작은 순으로 정렬해 새로운 문자열을 리턴하는 함수, solution을 완성해주세요.
s는 영문 대소문자로만 구성되어 있으며, 대문자는 소문자보다 작은 것으로 간주합니다.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>input: &quot;Zbcdefg&quot;<br>output: &quot;gfedcbZ&quot;</p>
<h3 id="해답">해답</h3>
<ul>
<li>sort() 함수의 경우 a,b 인자를 받는데 리턴하는 값이 0보다 작을 경우, a가 b보다 앞에 오도록 정렬하고,리턴하는 값이 0보다 클 경우, b가 a보다 앞에 오도록 정렬한다. 만약, 0을 리턴하면, a와 b의 순서를 변경하지 않는다.</li>
<li>내림차순이기 때문에 큰것이 앞에 와야한다.</li>
</ul>
<pre><code class="language-javascript">function solution(s) {
    const res = [...s].sort((a,b) =&gt; a &gt; b ? -1 : 1)
    return res.join(&#39;&#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 10988]]></title>
            <link>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-10988</link>
            <guid>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-10988</guid>
            <pubDate>Tue, 13 Feb 2024 13:53:32 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>팰린드롬 확인 문제 팰린드롬이란 앞으로 읽을 때와 거꾸로 읽을 때 똑같은 단어를 말한다. </p>
<h3 id="예제-입력---출력">예제 입력 -&gt; 출력</h3>
<ul>
<li><p>입력</p>
<ul>
<li>level</li>
</ul>
</li>
<li><p>출력</p>
<ul>
<li>1</li>
</ul>
</li>
</ul>
<h3 id="풀이">풀이</h3>
<ul>
<li>문자열을 역순으로 바꾼뒤 입력값과 비교하는 방식으로 풀었다.</li>
<li>문자열을 역순으로 바꾸기 위해 split,reverse,join 을 사용하였다.</li>
<li>reverse 메서드 사용을위해 split로 배열로 변환해준뒤 결과값은 join 사용하여 문자열로 출력되게 하였다.</li>
</ul>
<pre><code class="language-javascript">// 통과한 코드
const input = require(&#39;fs&#39;).readFileSync(&#39;input.txt&#39;).toString().split(&#39;\n&#39;)

if(input[0].split(&#39;&#39;).reverse().join(&#39;&#39;) === input[0]){
    console.log(&#39;1&#39;)
}else {
    console.log(&#39;0&#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 4101]]></title>
            <link>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-4101</link>
            <guid>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-4101</guid>
            <pubDate>Wed, 03 Jan 2024 09:43:05 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>두 양의 정수가 주어졌을 때, 첫 번째 수가 두 번째 수보다 큰지 구하는 프로그램을 작성하시오.</p>
<h3 id="입력">입력</h3>
<p>두 수는 백만보다 작거나 같은 양의 정수이다. 입력의 마지막 줄에는 0이 두 개 주어진다.</p>
<h3 id="출력">출력</h3>
<p>각 테스트 케이스마다, 첫 번째 수가 두 번째 수보다 크면 Yes를, 아니면 No를 한 줄에 하나씩 출력한다.</p>
<h3 id="예제-입력---출력">예제 입력 -&gt; 출력</h3>
<ul>
<li><p>입력
1 19<br>4 4<br>23 14
0 0</p>
</li>
<li><p>출력
No
No
Yes</p>
</li>
</ul>
<pre><code class="language-javascript">// 백준 플랫폼은 js 사용자한테는 너무 불친절한 느낌이다
// 기존 내 코드
const str = []
for(let i = 0; i &lt; input.length; i++) {
    if(parseInt(input[i][0]) === 0 &amp;&amp; parseInt(input[i][1]) === 0) {
        return
    }else if(parseInt(input[i][0]) &gt; parseInt(input[i][1])){
        str.push(&#39;Yes&#39;)
    }else {
        str.push(&#39;No&#39;)
    }
}
console.log(str.join(&#39;\n&#39;))

// 통과한 코드
for(let i = 0; i &lt; input.length; i++) {
    if(parseInt(input[i][0]) === 0 &amp;&amp; parseInt(input[i][1]) === 0) {
        return
    }else if(parseInt(input[i][0]) &gt; parseInt(input[i][1])){
        console.log(&#39;Yes&#39;)
    }else {
        console.log(&#39;No&#39;)
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 3003]]></title>
            <link>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-3003</link>
            <guid>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-3003</guid>
            <pubDate>Wed, 03 Jan 2024 08:26:10 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>체스는 총 16개의 피스를 사용하며, 킹 1개, 퀸 1개, 룩 2개, 비숍 2개, 나이트 2개, 폰 8개로 구성되어 있다.
동혁이가 발견한 흰색 피스의 개수가 주어졌을 때, 몇 개를 더하거나 빼야 올바른 세트가 되는지 구하는 프로그램을 작성하시오.</p>
<h3 id="입력">입력</h3>
<p>첫째 줄에 동혁이가 찾은 흰색 킹, 퀸, 룩, 비숍, 나이트, 폰의 개수가 주어진다. 이 값은 0보다 크거나 같고 10보다 작거나 같은 정수이다.</p>
<h3 id="출력">출력</h3>
<p>첫째 줄에 입력에서 주어진 순서대로 몇 개의 피스를 더하거나 빼야 되는지를 출력한다. 만약 수가 양수라면 동혁이는 그 개수 만큼 피스를 더해야 하는 것이고, 음수라면 제거해야 하는 것이다.</p>
<h3 id="예제">예제</h3>
<p>0 1 2 2 2 7 -&gt; 1 0 0 0 0 1
2 1 2 1 2 1 -&gt; -1 0 0 1 0 7</p>
<pre><code class="language-javascript">const input = require(&#39;fs&#39;).readFileSync(&#39;dev/stdin&#39;).toString().split(&#39; &#39;).map(num =&gt; parseInt(num))

const arr = [1,1,2,2,2,8]
const ar = []

for(let i = 0; i &lt; arr.length; i++){
    if((input[i] - arr[i]) &lt; 0){
        ar.push(Math.abs(input[i] - arr[i]))
    }
    else if((input[i] - arr[i]) &gt; 0) {
        ar.push(-(input[i] - arr[i]))
    } 
    else if(input[i] === arr[i]) {
        ar.push(0)
    }
}
console.log(ar.join(&#39; &#39;))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1330]]></title>
            <link>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-1330</link>
            <guid>https://velog.io/@moon_l/%EB%B0%B1%EC%A4%80-1330</guid>
            <pubDate>Sun, 31 Dec 2023 10:35:45 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-javascript">const input = require(&#39;fs&#39;).readFileSync(&#39;dev/stdin&#39;).toString().split(&#39; &#39;)

const a = Number(input[0])
const b = Number(input[1])

if(a &lt; b) {
    console.log(&#39;&lt;&#39;)
} else if(a &gt; b){
    console.log(&#39;&gt;&#39;)
} else if(a == b) {
    console.log(&#39;==&#39;)
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[클래스다이어그램 과 유즈케이스]]></title>
            <link>https://velog.io/@moon_l/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8-%EA%B3%BC-%EC%9C%A0%EC%A6%88%EC%BC%80%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@moon_l/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8-%EA%B3%BC-%EC%9C%A0%EC%A6%88%EC%BC%80%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Sat, 25 Nov 2023 10:49:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>트리구조에 대한 클래스 다이어그램, 유즈케이스 작성 기회가 있어 공부했던 내용을 정리해 보았다.</p>
</blockquote>
<h2 id="클래스다이어그램">클래스다이어그램</h2>
<ol>
<li><p><strong>목적</strong></p>
<ul>
<li>의사소통 또는 설계 논의를 위해</li>
<li>전체 시스템의 구조 및 클래스의 의존성 파악을 위해</li>
<li>유지보수를 위한 설계의 back-end 문서 제작을 위해</li>
</ul>
</li>
<li><p><strong>UML(Unified Modeling Language) 이란?</strong></p>
<ul>
<li>시스템을 모델로 표현해주는 대표적인 모델링 언어</li>
</ul>
</li>
<li><p><strong>UML 클래스의 표현</strong></p>
<details>
<summary>UML클래스다이어그램</summary>
<div>
<ul>
 <li>3-1) 가장 윗부분: 클래스 이름</li>
 <li>3-2) 중간 부분: 속성(클래스의 특징)</li>
 <li>3-3) 마지막 부분: 연산(클래스가 수행하는 책임)</li>
</ul>
<img src="https://velog.velcdn.com/images/moon_l/post/77f55524-9eb4-40c9-adbc-fa95a3183a1d/image.png" />
</div>
</details>
</li>
<li><p><strong>클래스 다이어그램 표현</strong></p>
</li>
</ol>
<ul>
<li><p><strong>접근제어자</strong></p>
<ul>
<li>(+)  == public :: 어떤 클래스의 객체에서도 접근 가능</li>
<li>(-) ==  private :: 이 클래스에서 생성된 객체들만 접근 가능</li>
<li>(#)  == protected :: 이 클래스와 동일 패키지에 있거나 상속 관계에 있는 하위 클래스의 객체들만 접근 가능</li>
<li>(~) == package :: 동일 패키지에 있는 클래스의 객체들만 접근 가능<br/></li>
</ul>
</li>
<li><p><strong>관계 표현</strong></p>
<ul>
<li><strong>의존관계</strong> - 점선화살표 :: 한 객체가 다른 객체에 있는 기능을 사용할 때 나타냄, 두 클래스의 관계가 메서드를 실행 하는 동안과 같은 짧은 시간만 유지된다는 점<ul>
<li><strong>연관관계</strong> - 실선이나 화살표 :: 클래스들이 개념상 서로 연결 되어있음을 나타낸다. 보통은 한 클래스가 다른 클래스에서 제공하는 기능을 사용하는 상황일 때 표시한다. 
단방향은 화살표로 양방향은 실선으로 표시된다.</li>
</ul>
</li>
<li><strong>일반화관계</strong> - 속이 빈 화살표 :: 객체지향 개념에서 <strong>상속관계</strong>라고 말합니다. 한 클래스가 다른 클래스를 포함하는 상위 개념일 때 이를 IS-A 관계라고 하며 UML에서는 일반화 관계로 모델링합니다.<ol>
<li><strong>부모클래스</strong> :: 추상적인 개념이며, 삼각형 표시가 향하는 방향을 의미합니다.</li>
<li><strong>자식클래스</strong> :: 추상적인 개념을 물려받은 구체적인 개념입니다. 삼각형표시가 없는쪽이며 부모클래스는 자식클래스의 공통적인 속성과 연산을 제공하는 틀입니다.</li>
</ol>
</li>
</ul>
<details>
<summary>관계표현 이미지</summary>
<div>
<img src="https://velog.velcdn.com/images/moon_l/post/13c367ef-a189-47ec-b154-1c9f8dee5334/image.png" alt="관계표현" />
</div>
</details>

</li>
</ul>
<h2 id="유즈케이스">유즈케이스</h2>
<ul>
<li>시스템이 제공해주는 서비스와 기능을 나타내며 사용자의 요구사항을 구조화 한 것</li>
</ul>
<ol>
<li><p><strong>유즈케이스 작성시 주의사항</strong></p>
<ul>
<li>어떻게가 아니라 <strong>무엇을</strong> 시스템이 하는가를 담는 것</li>
</ul>
</li>
<li><p><strong>유즈케이스 다이어그램</strong></p>
<ul>
<li><p><strong>구성</strong></p>
<ul>
<li><strong>유스케이스</strong>: 시스템 내에서 일련의 작업을 수행하기 위한 행위들을 나타냄</li>
<li><strong>액터</strong><ul>
<li>프라이머리 액터 (Primary Actor) : 시스템을 사용하고, 직접 이득을 보는 액터이며 졸라맨으로 표기합니다. 보통 시스템의 왼쪽에 표시합니다.</li>
<li>세컨더리 액터 (Secondary Actor) : 프라이머리 액터가 목적을 달성하기 위해 도움을 주는 액터이며 사각형 박스에 &lt;&lt; actor &gt;&gt;를 입력하여 표기합니다. 보통 시스템의 오른쪽에 표시합니다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>관계</strong></p>
<ul>
<li><p><strong>연관관계</strong>: 유스케이스와 액터 사이에 상호작용이 있다는 뜻으로, 실선으로 표시합니다.</p>
<details>
<summary>연관관계 예시</summary>
<div>
<img src="https://velog.velcdn.com/images/moon_l/post/cd68ca12-4c0e-4610-9c1c-7a91be67fc89/image.png" alt="연관관계" />
</div>
</details>
</li>
<li><p><strong>포함관계</strong>: 두 개의 유스케이스 간의 의존성을 나타내며 하나의 유스케이스가 실행될 때 포함 관계에 있는 유스케이스가 반드시 실행되어야 함, 점선화살표를 사용하며 &lt;&lt; include &gt;&gt; 를 중앙에 표시해야한다.</p>
<details>
  <summary>포함관계 예시</summary>
  <div>
    <img src="https://velog.velcdn.com/images/moon_l/post/f8a69243-1016-4a9a-af6e-77f071a8973a/image.png" alt="포함관계" />
  </div>
</details>
</li>
<li><p><strong>확장관계</strong>: 두 개의 유스케이스 간의 확장성을 나타내며 하나의 유스케이스가 실행될 때 확장관계에 있는 유스케이스가 <strong>특정 상황에서만</strong> 실행되어야 함, 점선화살표를 사용하며 &lt;&lt; extend &gt;&gt;를 화살표 중앙에 표시해야한다.</p>
<details>
  <summary>확장관계 예시</summary>
  <div>
    <img src="https://velog.velcdn.com/images/moon_l/post/70334ac2-b2f3-485e-a8e1-19839c0e9500/image.png" alt="확장관계" />
  </div>
</details>
</li>
<li><p><strong>일반화관계</strong>: 부모와 자식 유스케이스들 간의 상속관계를 나타내며 특정 유스케이스들이 하나의 특수화된 유스케이스라 한다, 자식 에서 부모 유스케이스 방향으로 실선 화살표를 사용한다.</p>
<details>
  <summary>포함관계 예시</summary>
  <div>
    <img src="https://velog.velcdn.com/images/moon_l/post/f0e92a3c-c10d-4efe-ab63-67bbe499e1e5/image.png" alt="포함관계" />
  </div>
</details>

</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="마무리">마무리</h2>
<ul>
<li>개발에 앞서 전체적인 흐름에 대해 먼저 생각해보게 되었다.</li>
<li>틀을 짜놓은 상태에서 코드를 작성한다는 점이 좋았다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js(ver.12) + TS + jest + redux-toolkit + reactQuery]]></title>
            <link>https://velog.io/@moon_l/Next.jsver.12-TS-jest-redux-toolkit-reactQuery</link>
            <guid>https://velog.io/@moon_l/Next.jsver.12-TS-jest-redux-toolkit-reactQuery</guid>
            <pubDate>Thu, 05 Oct 2023 13:49:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>프로젝트를 진행하면서 반복되는 PR 요청에 Test 코드 작성이라는 불호령이 떨어졌다.
Next.js + TS + jest + redux-toolkit + reactQuery 사용하는건 뭐 이리 많은지
머리 깨질듯이 아파오는 테스트 코드 작성을 시작한다.</p>
</blockquote>
<h3 id="라이브러리-설치">라이브러리 설치</h3>
<pre><code class="language-javascript">yarn add -D jest @testing-library/jest-dom @types/jest babel-jest jest-environment-jsdom ts-jest</code></pre>
<ul>
<li>설치해야 하는 라이브러리 개수부터 두통이 아려온다. jest가 나오기 전에는 더 복잡했다고 한다. </li>
</ul>
<h3 id="nextjs-설정">Next.js 설정</h3>
<ul>
<li>우리는 Next를 사용하기 때문에 따로 설정 해줘야 하는 것들이 있다. 물론, 12버전 이상부터 jest가 next에 내장되어있어 설정이 비교적 간단하다.</li>
</ul>
<h4 id="babelrc">.babelrc</h4>
<ul>
<li>루트폴더에 생성해준다. Next에서 지원해주다 보니 설정이 매우 간편하다.<pre><code class="language-typescript">{
&quot;presets&quot;: [&quot;next/babel&quot;],
}</code></pre>
</li>
</ul>
<h4 id="jestconfigjs">jest.config.js</h4>
<ul>
<li>루트폴더에 생성해준다. 공식문서에 나와있는 그대로이다. 차이점은 moduleNameMapper 이것인데 테스트 파일에서 컴포넌트나 필요한 유틸 파일을 import 할 때 ../처럼 상대경로로 타고 올라가는게 맘에 안들어서 최상위 루트로 설정 해 놓았다.<pre><code class="language-typescript">const nextJest = require(&#39;next/jest&#39;);
</code></pre>
</li>
</ul>
<p>const createJestConfig = nextJest({
  dir: &#39;./&#39;,
});</p>
<p>const customJestConfig = {
  setupFilesAfterEnv: [&#39;<rootDir>/jest.setup.js&#39;, &#39;jest-plugin-context/setup&#39;],
  moduleDirectories: [&#39;node_modules&#39;, &#39;<rootDir>/&#39;],
  testEnvironment: &#39;jest-environment-jsdom&#39;,
  moduleNameMapper: {
    &#39;^@components(.*)$&#39;: &#39;<rootDir>/src/components$1&#39;,
  },
};</p>
<p>module.exports = createJestConfig(customJestConfig);</p>
<pre><code>
#### jest.setup.js
- 위 jest.config.js에 setup.js를 명시해두었기 때문에 루트폴더에 생성해주면된다.
```typescript
import &#39;@testing-library/jest-dom&#39;; // 요거 한줄이면 된다. 저장.</code></pre><h3 id="테스트를-위한-설정-사전작업이-끝났습니다-다음-글에는-컴포넌트를-테스트-하는-코드를-작성해보겠습니다">테스트를 위한 설정 사전작업이 끝났습니다. 다음 글에는 컴포넌트를 테스트 하는 코드를 작성해보겠습니다.</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 배열만들기5]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EB%B0%B0%EC%97%B4%EB%A7%8C%EB%93%A4%EA%B8%B05</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EB%B0%B0%EC%97%B4%EB%A7%8C%EB%93%A4%EA%B8%B05</guid>
            <pubDate>Wed, 27 Sep 2023 08:58:54 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>문자열 배열 intStrs와 정수 k, s, l가 주어집니다. intStrs의 원소는 숫자로 이루어져 있습니다.
배열 intStrs의 각 원소마다 s번 인덱스에서 시작하는 길이 l짜리 부분 문자열을 잘라내 정수로 변환합니다. 이때 변환한 정수값이 k보다 큰 값들을 담은 배열을 return 하는 solution 함수를 완성해 주세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>intStrs - [&quot;0123456789&quot;,&quot;9876543210&quot;,&quot;9999999999999&quot;]
k - 50000
s - 5
l - 5
result - [56789, 99999]</p>
<h3 id="해답">해답</h3>
<pre><code class="language-javascript">function solution(intStrs, k, s, l) {
    var answer = [];
    for(let i = 0; i &lt; intStrs.length; i++) {
        if(intStrs[i].slice(s, s+l) &gt; k) {
                   answer.push(Number(intStrs[i].slice(s, s+l)))
        }
    }
    return answer
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 크기가 작은 부분 문자열]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%9E%91%EC%9D%80-%EB%B6%80%EB%B6%84-%EB%AC%B8%EC%9E%90%EC%97%B4</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%9E%91%EC%9D%80-%EB%B6%80%EB%B6%84-%EB%AC%B8%EC%9E%90%EC%97%B4</guid>
            <pubDate>Sun, 04 Jun 2023 07:46:29 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>숫자로 이루어진 문자열 t와 p가 주어질 때, t에서 p와 길이가 같은 부분문자열 중에서, 이 부분문자열이 나타내는 수가 p가 나타내는 수보다 작거나 같은 것이 나오는 횟수를 return하는 함수 solution을 완성하세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>p의 길이가 1이므로 t의 부분문자열은 &quot;5&quot;, &quot;0&quot;, 0&quot;, &quot;2&quot;, &quot;2&quot;, &quot;0&quot;, &quot;8&quot;, &quot;3&quot;, &quot;9&quot;, &quot;8&quot;, &quot;7&quot;, &quot;8&quot;이며 이중 7보다 작거나 같은 숫자는 &quot;5&quot;, &quot;0&quot;, &quot;0&quot;, &quot;2&quot;, &quot;2&quot;, &quot;0&quot;, &quot;3&quot;, &quot;7&quot; 이렇게 8개가 있습니다.</p>
<h3 id="해답">해답</h3>
<pre><code class="language-javascript">function solution(t, p) {
    const ar = []
    let cnt = 0;
    let len = p.length;

    for(let i = 0; i &lt; t.length; i++){
       if(t.slice(i, len).length === p.length) ar.push(t.slice(i, len))
        len++
    }

    for(let j = 0; j &lt; ar.length; j++){
        if(Number(ar[j]) &lt;= Number(p)) cnt += 1
    }

    return cnt
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 정수 제곱근 판별]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%A0%95%EC%88%98-%EC%A0%9C%EA%B3%B1%EA%B7%BC-%ED%8C%90%EB%B3%84</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%EC%A0%95%EC%88%98-%EC%A0%9C%EA%B3%B1%EA%B7%BC-%ED%8C%90%EB%B3%84</guid>
            <pubDate>Sun, 04 Jun 2023 07:19:34 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>임의의 양의 정수 n에 대해, n이 어떤 양의 정수 x의 제곱인지 아닌지 판단하려 합니다.
n이 양의 정수 x의 제곱이라면 x+1의 제곱을 리턴하고, n이 양의 정수 x의 제곱이 아니라면 -1을 리턴하는 함수를 완성하세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<ul>
<li><p>입출력 예#1
121은 양의 정수 11의 제곱이므로, (11+1)를 제곱한 144를 리턴합니다.</p>
</li>
<li><p>입출력 예#2
3은 양의 정수의 제곱이 아니므로, -1을 리턴합니다.</p>
</li>
</ul>
<h3 id="해답">해답</h3>
<pre><code class="language-javascript">function solution(n) {
    const num = Math.sqrt(n)
    return Number.isInteger(num) ? (num+1)**2 : -1
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프그] 핸드폰 번호 가리기]]></title>
            <link>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%ED%95%B8%EB%93%9C%ED%8F%B0-%EB%B2%88%ED%98%B8-%EA%B0%80%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@moon_l/%ED%94%84%EA%B7%B8-%ED%95%B8%EB%93%9C%ED%8F%B0-%EB%B2%88%ED%98%B8-%EA%B0%80%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Sat, 27 May 2023 09:55:44 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>프로그래머스 모바일은 개인정보 보호를 위해 고지서를 보낼 때 고객들의 전화번호의 일부를 가립니다.
전화번호가 문자열 phone_number로 주어졌을 때, 전화번호의 뒷 4자리를 제외한 나머지 숫자를 전부 *으로 가린 문자열을 리턴하는 함수, solution을 완성해주세요.</p>
<h3 id="입출력-예">입출력 예</h3>
<p>&quot;01033334444&quot;-&gt;&quot;<strong>***</strong>4444&quot;</p>
<h3 id="해답">해답</h3>
<pre><code class="language-javascript">function solution(phone_number) {
    const str = phone_number.slice(0, phone_number.length - 4);
    const last = phone_number.slice(phone_number.length - 4, phone_number.length);
    const ar = []
    for(let i = 0; i &lt; str.length; i++){
       ar.push(i)
    }
    return ar.fill(&#39;*&#39;, 0 , phone_number.length - 4).join(&#39;&#39;) + last
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>