<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>arin_0303.log</title>
        <link>https://velog.io/</link>
        <description>헤맨만큼 내 땅</description>
        <lastBuildDate>Thu, 12 Mar 2026 15:38:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>arin_0303.log</title>
            <url>https://velog.velcdn.com/images/arin_0303/profile/b1f1e9d7-0278-48e1-aa78-ac766b501242/image.webp</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. arin_0303.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/arin_0303" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Python] 문자열 관련 함수]]></title>
            <link>https://velog.io/@arin_0303/Python-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B4%80%EB%A0%A8-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@arin_0303/Python-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B4%80%EB%A0%A8-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 12 Mar 2026 15:38:56 GMT</pubDate>
            <description><![CDATA[<p>문자열은 자체적으로 <strong>내장 함수</strong>를 가지고 있기 때문에 변수 이름 뒤에 &#39;.&#39;을 붙인 다음 함수 이름을 써주면 된다. </p>
<h3 id="문자-개수-세기count">문자 개수 세기(count)</h3>
<pre><code class="language-python">a = &quot;hobby&quot;
print(a.count(&#39;b&#39;))</code></pre>
<p>&lt;출력 결과&gt; </p>
<pre><code>2</code></pre><hr>
<h3 id="위치-알려주기find">위치 알려주기(find)</h3>
<pre><code class="language-python">a = &quot;hobby&quot;
print(a.find(&#39;b&#39;))</code></pre>
<p>&lt;출력 결과&gt;</p>
<pre><code>2 </code></pre><p>-&gt; b가 처음으로 나온 위치를 알려준다.</p>
<hr>
<h3 id="문자열-삽입join">문자열 삽입(join)</h3>
<pre><code class="language-python">print(&quot;,&quot;.join(&#39;abcd&#39;))</code></pre>
<p>&lt;출력 결과&gt;</p>
<pre><code>a,b,c,d</code></pre><p>-&gt; 문자열 각 사이에 &#39;,&#39;를 삽입한다. 
join함수는 문자열뿐만 아니라 리스트나 튜플에도 사용될 수 있다.</p>
<hr>
<h3 id="소문자---대문자-변환upper-lower">소문자 &lt;-&gt; 대문자 변환(upper, lower)</h3>
<pre><code class="language-python">a = &quot;HOBby&quot;
print(a.upper())
print(a.lower())</code></pre>
<p>&lt;출력 결과&gt;</p>
<pre><code>HOBBY
hobby</code></pre><hr>
<h3 id="문자열-바꾸기replace">문자열 바꾸기(replace)</h3>
<pre><code class="language-python">a = &quot;Hello, World!&quot;
print(a.replace(&quot;World!&quot;, &quot;Python!&quot;))
print(a) # 원본은 바뀌지 않는다</code></pre>
<p>&lt;출력 결과&gt;</p>
<pre><code>Hello, Python!
Hello, World!</code></pre><hr>
<h3 id="기준을-가지고-문자열-나누기split">기준을 가지고 문자열 나누기(split)</h3>
<pre><code class="language-python">a = &quot;Hello, World!&quot;
print(a.split()) # 공백을 기준으로 나누기
b  = &quot;apple:banana:grape&quot;
print(b.split(&#39;:&#39;)) # 쉼표를 기준으로 나누기</code></pre>
<p>&lt;출력 결과&gt;</p>
<pre><code>[&#39;Hello,&#39;, &#39;World!&#39;]
[&#39;apple&#39;, &#39;banana&#39;, &#39;grape&#39;]
</code></pre><p>참고로, 문자열을 나눌땐 split()말고도 인덱싱과 슬라이싱이 있다. </p>
<ul>
<li>인덱싱: a[0]</li>
<li>슬라이싱: a[2:5]</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 디스크 컨트롤러]]></title>
            <link>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%EB%94%94%EC%8A%A4%ED%81%AC-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC</link>
            <guid>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%EB%94%94%EC%8A%A4%ED%81%AC-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC</guid>
            <pubDate>Tue, 03 Mar 2026 20:53:47 GMT</pubDate>
            <description><![CDATA[<p>문제 링크: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/42627">https://school.programmers.co.kr/learn/courses/30/lessons/42627</a>
<img src="https://velog.velcdn.com/images/arin_0303/post/26bb4341-ae7d-45c4-a3e0-3be05d393554/image.png" style="margin: 10px;">
<img src="https://velog.velcdn.com/images/arin_0303/post/4391992b-6e64-48c1-9586-676607626bc0/image.png" style="margin: 10px;">
<img src="https://velog.velcdn.com/images/arin_0303/post/1b9e4862-0c33-428f-b544-2c247e73f0ab/image.png" style="margin: 10px;"></p>
<img src="https://velog.velcdn.com/images/arin_0303/post/46daceb4-3e4d-4d2e-852a-025df5e11063/image.png" style="margin: 10px;">

<img src="https://velog.velcdn.com/images/arin_0303/post/8d127af7-698e-414c-854f-1cb98d8a69ee/image.png" style="margin: 10px;">
<img src="https://velog.velcdn.com/images/arin_0303/post/6466d888-9ab0-4296-b111-52c527defaf9/image.png" style="margin: 10px;">

<pre><code class="language-python">import heapq

def solution(jobs): # [요청 시각, 소요 시간]
    # 요청 시각 기준 정렬
    jobs.sort()

    curr_time = 0           # 현재 시간
    total = 0               # 총 반환시간
    idx = 0                 # jobs 인덱스
    complete_count = 0      # 처리 완료 개수
    heap = []               # 대기 큐

    while complete_count &lt; len(jobs): # 모든 작업 끝날 때까지
        # 현재 시간까지 요청된 작업 모두 힙에 저장
        while idx &lt; len(jobs) and jobs[idx][0] &lt;= curr_time:
            request, duration = jobs[idx] 
            heapq.heappush(heap, (duration, request)) # 튜플 형태로 heap에 저장(1순위: 소요시간, 2순위: 요청 시각)
                                                      # 작업 번호는 고려x -&gt; 소요시간, 요청시각 같으면 평균 반환시간 동일
            idx += 1 # 다음 작업 번호

        # 힙에서 꺼내서 작업 실행
        if heap: 
            duration, request = heapq.heappop(heap)
            curr_time += duration
            total += curr_time - request # 반환 시간 = 종료 시각 - 요청 시각
            complete_count += 1
        else: # 처리할 작업이 아직 안들어왔으면 현재 시간을 다음 요청 시각으로 점프
            curr_time = jobs[idx][0] # 그 다음 작업 번호의 요청 시각

    return total // len(jobs)</code></pre>
<h3 id="핵심-1">핵심 1</h3>
<p>jobs 리스트는 요청 시각 기준으로 정렬한 후, heap에 &#39;소요 시간&#39;과 &#39;요청 시각&#39;을 따로 넣어줘야한다. 이때 heap 자료구조를 사용하는 이유는, 소요시간이 최소인 아이템을 가장 먼저 뽑아야하기 때문이다. </p>
<blockquote>
<p>우선순위: 소요시간 &gt; 요청 시각 &gt; 아이템 번호</p>
</blockquote>
<p><code>heapq.heappush(heap, (duration, request))</code>
튜플에 작업 번호 <code>idx</code>를 넣지 않았다. 그 이유는 소요시간과 요청시각이 같은 작업이면 완전히 동일한 작업이기 때문에 그것들 중에서 어떤 것을 먼저 수행해도 반환 시간(종료 시각 - 요청 시각)의 평균은 같기 때문이다. </p>
<h3 id="핵심-2">핵심 2</h3>
<p><code>curr_time</code>을 기준으로 작업을 수행하고, <code>curr_time</code>을 점점 이동시킨다.</p>
<blockquote>
<p>1) heap에 저장 
2) heap에서 꺼내서 작업 수행(<code>duration</code>이 최소인 아이템부터 꺼내진다)</p>
</blockquote>
<p><code>요청 시각 &lt;= 현재 시각</code> 인 아이템들을 대상으로 1번 작업을 먼저 수행한 후, 2번을 수행한다. </p>
<blockquote>
<p>cf) 요청 시각: <code>jobs[idx][0]</code>
    여기서 <code>idx</code>는 jobs의 <strong>작업 번호</strong>이다.</p>
</blockquote>
<p>만약 해당 조건에 해당하는 아이템이 없어서 heap 비었으면 <code>curr_time</code>을 다음 작업 번호의 요청 시각으로 갱신해준다.
(<code>curr_time = jobs[idx][0]</code>)</p>
<h3 id="핵심-3">핵심 3</h3>
<p>하나의 작업을 완료하면 <code>curr_time</code>에 <code>duration</code>만큼 더해서 갱신해주고 반환 시간<code>(total)</code>을 계산해서 갱신해준다.</p>
<h3 id="접근하기-어려웠던-점">접근하기 어려웠던 점</h3>
<p>jobs 리스트를 가지고 어떻게 우선순위 큐를 구현해야하는지가 어려웠다. 
일단 2차원 배열 정렬 개념이 머릿속에 잡혀있지 않았고, 요청 시각 순으로 정렬된 jobs를 작업 목록으로 활용하고 heap은 대기 큐로 활용할 생각을 못했다. </p>
<p>또한 jobs는 <code>[요청 시각, 소요 시간]</code> 인데 이 상태로 힙에 저장하면 소요 시간이 가장 우선순위가 높다는 문제 조건을 충족할 수 없다. 따라서 heap에는 <code>(소요 시간, 요청 시각)</code> 처럼 순서를 바꾼 튜플을 한 아이템으로 저장해야 했다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[집합]]></title>
            <link>https://velog.io/@arin_0303/%EC%A7%91%ED%95%A9</link>
            <guid>https://velog.io/@arin_0303/%EC%A7%91%ED%95%A9</guid>
            <pubDate>Tue, 03 Mar 2026 08:26:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/arin_0303/post/7b76c5d6-c480-41b8-9342-85cba78526b7/image.png" alt=""></p>
<p><strong>집합이란?</strong></p>
<p>중복을 허용하지 않고, 순서가 없는 원소들의 모임입니다. 파이썬의 set 타입이 해당됩니다.</p>
<p>Set 역시 내부적으로 해시 테이블을 기반으로 동작하기 때문에 데이터 조회 속도가 매우 빠릅니다.</p>
<p><strong>장점</strong>
원소의 존재 여부를 매우 빠르게 확인할 수 있습니다. (평균 시간 복잡도: O(1))</p>
<p>데이터를 추가하는 순간 자동으로 중복이 제거됩니다.</p>
<p>교집합, 합집합 등 유용한 집합 연산을 기본으로 지원합니다.</p>
<p><strong>단점</strong></p>
<ul>
<li>순서가 없기 때문에 인덱스([0], [1])로 접근할 수 없습니다.</li>
<li>Key-Value 형태가 아닌, 값 자체만 저장합니다.</li>
</ul>
<p><strong>언제 사용할까?</strong></p>
<ul>
<li><p>목록에서 중복된 값을 간단히 제거하고 싶을 때</p>
</li>
<li><p>데이터의 존재 여부만 빠르게 확인하고 싶을 때 (백준 1920번 문제)</p>
</li>
<li><p>데이터 그룹 간의 공통점이나 차이점을 찾아야 할 때 (교집합, 차집합 등)</p>
<pre><code># 리스트의 중복 제거하기
my_list = [1, 2, 2, 3, 4, 4, 4, 5]
unique_set = set(my_list)
print(unique_set) 
# 출력: {1, 2, 3, 4, 5}</code></pre></li>
<li><ul>
<li>주요 함수 및 사용법 (Python Set 기준)**</li>
</ul>
</li>
<li><p>add(value): 원소 추가</p>
</li>
<li><p>remove(value): 원소 삭제 (삭제할 원소가 없으면 에러 발생)</p>
</li>
<li><p>discard(value): 원소 삭제 (삭제할 원소가 없어도 에러 발생 안 함)</p>
</li>
<li><p>in: 존재 여부 확인</p>
</li>
</ul>
<pre><code>my_set = {1, 2, 3}
my_set.add(4)      # {1, 2, 3, 4}
my_set.remove(2)   # {1, 3, 4}

# 존재 여부 확인
if 3 in my_set:
    print(&quot;3은 집합에 포함되어 있습니다.&quot;)</code></pre><p><strong>집합 연산</strong></p>
<ul>
<li><p>&amp; 또는 intersection(): 교집합 (둘 다에 있는 원소)</p>
</li>
<li><p>| 또는 union(): 합집합 (모든 원소)</p>
</li>
<li><ul>
<li>또는 difference(): 차집합 (한쪽에만 있는 원소)<pre><code>set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
</code></pre></li>
</ul>
</li>
</ul>
<p>print(set_a &amp; set_b)  # 교집합: {3, 4}
print(set_a | set_b)  # 합집합: {1, 2, 3, 4, 5, 6}
print(set_a - set_b)  # 차집합: {1, 2}</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 다리를 지나는 트럭]]></title>
            <link>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD</link>
            <guid>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD</guid>
            <pubDate>Tue, 24 Feb 2026 11:05:18 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/arin_0303/post/ac4f3926-8169-4a64-ae96-9912942bb812/image.png" style="margin: 10px;">
<img src="https://velog.velcdn.com/images/arin_0303/post/9749797d-4b2c-4f77-91df-37d388849091/image.png" style="margin: 10px;">


<pre><code class="language-python">from collections import deque

def solution(bridge_length, weight, truck_weights):
    # 다리에 올라갈 수 있는 트럭 수 bridge_length
    # 다리가 견딜 수 있는 무게 weight
    # 트럭 별 무게 truck_weights

    trucks = deque(truck_weights) # 대기 트럭 큐
    bridge = deque([0] * bridge_length) # 다리 큐
    time = 0
    current_weight = 0

    while trucks or current_weight &gt; 0: # 대기 트럭이 남아있거나 다리 위에 트럭이 남아있으면 반복
        time += 1
        out_truck = bridge.popleft() # 다리를 지난 버스
        current_weight -= out_truck

        if(trucks and current_weight + trucks[0] &lt;= weight):
            in_truck = trucks.popleft() # 다리를 건널 버스
            bridge.append(in_truck) # 다리에 버스 추가
            current_weight += in_truck # 버스 무게 추가
        else:
            bridge.append(0) # 다리에서 pop한 자리 채워주기
    return time</code></pre>
<h3 id="1-문제-유형">1. 문제 유형</h3>
<p><strong>시뮬레이션 + 큐 2개 사용</strong></p>
<h4 id="시뮬레이션-문제-특징">시뮬레이션 문제 특징</h4>
<ul>
<li>“매 초마다”</li>
<li>“각 단계에서”</li>
<li>“상태가 변한다”</li>
<li>“그대로 구현하시오”</li>
</ul>
<hr>
<h3 id="2-처음에-잘못-생각한-접근">2. 처음에 잘못 생각한 접근</h3>
<ul>
<li>한 번에 여러 트럭의 weight를 계산하려고 했다.</li>
<li>슬라이싱과 sum으로 한 번에 처리하려 했다.</li>
<li>이 문제는 한 번에 계산하는 문제가 아니라, 시간 흐름에 따라 상태가 변하는 문제였다.</li>
</ul>
<hr>
<h3 id="3-핵심-깨달음">3. 핵심 깨달음</h3>
<ul>
<li>시간 단위(1초)로 상태가 변하는 문제는 시뮬레이션을 먼저 떠올린다.</li>
<li>순서대로 처리되는 구조는 <code>deque</code>를 사용하면 자연스럽게 구현할 수 있다.</li>
<li>다리처럼 길이가 고정된 구조는 <code>deque([0] * bridge_length)</code>로 초기화한다.</li>
<li>대기열이 비어도, 시스템(다리 위)에 남은 요소가 있으면 반복을 계속해야 한다.</li>
</ul>
<hr>
<h3 id="4-구현-실수-정리">4. 구현 실수 정리</h3>
<ul>
<li><p><code>current_weight &lt;= weight</code>가 아니라</p>
<p>  <code>current_weight + trucks[0] &lt;= weight</code>로 검사해야 한다.</p>
</li>
<li><p>트럭을 올리지 못한 경우 <code>bridge.append(0)</code>을 해줘야 한다.</p>
</li>
<li><p><code>while trucks</code>가 아니라 <code>while trucks or current_weight &gt; 0</code>이어야 한다.</p>
</li>
<li><p><code>&amp;</code> 대신 <code>and</code>를 사용해야 한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컨테이너 네트워크 통일하기]]></title>
            <link>https://velog.io/@arin_0303/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%9D%BC</link>
            <guid>https://velog.io/@arin_0303/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%9D%BC</guid>
            <pubDate>Sun, 01 Feb 2026 09:28:57 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p><code>myapp</code>컨테이너와 <code>myapp-dev</code>컨테이너와 연결된 Caddy(로드밸런서)가 <code>myapp-dev</code> 컨테이너를 찾지 못해 <code>myapp-dev</code> 컨테이너로 연결되는 url 접속시 503에러가 발생했다.
<img src="https://velog.velcdn.com/images/arin_0303/post/1f746185-51cf-4c0c-b9d6-c6e9d4790c2f/image.png" alt=""></p>
<h3 id="원인">원인</h3>
<p>두 컨테이너가 서로 다른 디렉토리에서 실행되어, Docker가 각각 별개의 네트워크(<code>_webnet</code>)를 생성했다. 그로인해 이미 Caddy와 연결된 <code>myapp</code> 컨테이너는 접속이 잘 되지만 추가로 연결하려는 <code>myapp-dev</code>는 인식을 못한 것이다. 
<img src="https://velog.velcdn.com/images/arin_0303/post/e71f8ff2-f171-4494-a031-8332bd0ead5d/image.png" alt=""></p>
<h3 id="해결">해결</h3>
<p>워크플로우 스크립트<code>deploy-main.yml</code>과 <code>deploy-dev.yml</code>의 docker compose 명령어에 <code>-p</code> 옵션을 추가했다.</p>
<blockquote>
<p>deploy-dev.yml</p>
</blockquote>
<pre><code class="language-bash">- name: Deploy Docker services
  run: | 
   ssh prod &#39;cd /opt/app-backup &amp;&amp; sudo docker compose -p promptplace -f docker-compose.yml up -d --build app caddy&#39;</code></pre>
<p>deploy-main.yml</p>
<pre><code class="language-bash">- name: Deploy Docker services (Dev)
  run: |
   ssh prod &#39;cd /opt/app-dev &amp;&amp; sudo docker compose -p promptplace -f docker-compose.yml up -d --build app-dev&#39;</code></pre>
<p>이를 통해 실행 경로가 다르더라도 두 환경이 promptplace라는** 하나의 네트워크**를 공유하도록 설정을 변경했다. (참고로 여기서 promptplace는 내 프로젝트 이름이다.)
이제 Caddy가 동일한 네트워크 상에 있는 Dev 컨테이너를 정상적으로 인식하고 트래픽을 라우팅한다.
<img src="https://velog.velcdn.com/images/arin_0303/post/9bc1038d-f82d-4115-8ec1-a82da4a80649/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transaction(트랜잭션)]]></title>
            <link>https://velog.io/@arin_0303/Transaction%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</link>
            <guid>https://velog.io/@arin_0303/Transaction%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</guid>
            <pubDate>Sun, 01 Feb 2026 09:01:02 GMT</pubDate>
            <description><![CDATA[<p><strong>트랜잭션(Transaction)</strong>이란? 데이터베이스에서 여러 작업을 하나의 작업 단위로 묶어 처리하는 기능이다. 모든 작업이 전부 성공해야만 실제로 적용되고, 하나라도 실패하면 전체가 취소(rollback)된다. </p>
<h4 id="트랜잭션이-필요한-이유">트랜잭션이 필요한 이유</h4>
<p>회원가입 로직에서 </p>
<ol>
<li>회원정보 <code>member</code>를 DB에 저장</li>
<li>회원 선호 <code>preferences</code>를 DB에 저장</li>
</ol>
<p>-&gt; 2번에서 실패했을 때 1번이 이미 저장돼버리면 데이터가 불완전하게 남는다. 
이를 막기 위해 <strong>1번과 2번 모두가 성공한 후, 실제로 적용</strong>되게 해야한다. 바로 그걸 트랜잭션이 해준다. </p>
<h4 id="트랜잭션의-4가지-핵심-원칙acid">트랜잭션의 4가지 핵심 원칙(ACID)</h4>
<p>Atomicity(원자성): 모두 성공하거나, 모두 실패
Consistency(일관성): 데이터는 항상 유효한 상태를 유지
Isolation(고립성): 동시에 실행되어도 서로 간섭하지 않음
Durability(지속성): 트랜잭션이 완료되면 DB에 영구 반영됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 기능개발]]></title>
            <link>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@arin_0303/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Tue, 13 Jan 2026 10:51:17 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/arin_0303/post/2c536c0a-a2bb-4ac9-b7c1-7fd68c42562b/image.png" style="margin: 10px;">
<img src="https://velog.velcdn.com/images/arin_0303/post/aad460c9-f8a7-4616-9b1a-8abe8d05fbfb/image.png" style="margin: 10px;">

<hr>
<pre><code class="language-python">import math

def solution(progresses, speeds):
    answer = []
    days = [] # 작업 일 수 

    for i in range(len(progresses)):
        d = math.ceil((100 - progresses[i])/speeds[i])
        days.append(d)

    count = 0
    max_day = days[0]

    for day in days:
        if day &lt;= max_day:
            # 묻어가기
            count += 1
        else:
            answer.append(count)
            max_day = day
            count = 1 # 현재 day추가

    answer.append(count) # 마지막 배포
    return answer</code></pre>
<hr>
<h3 id="📌-1-유형">📌 1. 유형</h3>
<p>앞에 있는 기능이 배포되어야 뒤에 있는 기능도 배포 가능. 
-&gt; <strong>FIFO(큐)</strong></p>
<h3 id="📌2-헷갈린-부분">📌2. 헷갈린 부분</h3>
<p>첫번째 기능이 완료되면 두번째 기능이 완료되지 않아도 세번째 기능을 배포 가능하다고 생각했다. 그러나 이 문제는 앞에 기능이 완료되어야 뒤에 기능도 배포 가능하기 때문에 불가능하다. 따라서 <code>O(N)</code>으로 탐색이 가능하다. </p>
<h3 id="📌3-핵심">📌3. 핵심</h3>
<p>이중 for문 쓰지 말고,     <code>max_day (기준값)</code> 변수 하나만 갱신하면 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 10. AWS (VPC & Internet Gateway & EC2)]]></title>
            <link>https://velog.io/@arin_0303/Chapter-10.-AWS-VPC-Internet-Gateway-EC2</link>
            <guid>https://velog.io/@arin_0303/Chapter-10.-AWS-VPC-Internet-Gateway-EC2</guid>
            <pubDate>Fri, 26 Dec 2025 15:44:39 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>AWS</strong>: 우리가 직접 서버를 구매/설치하지 않고도, 필요할 때 빌려서 쓰는 개념</p>
</li>
<li><p><strong>리전(Region):</strong> AWS의 물리적 데이터센터의 묶음</p>
<ul>
<li><p>전 세계에서 지리적으로 구분되어 위치해 있다. 가까운 리전을 선택해 빠르고 안정적으로 서비스 운영 가능</p>
<p>예시:</p>
</li>
<li><p><code>ap-northeast-2</code> → 서울 리전</p>
</li>
<li><p><code>us-west-1</code> → 미국 서부 리전 (캘리포니아)</p>
</li>
<li><p><code>eu-central-1</code> → 프랑크푸르트 리전</p>
</li>
<li><p>하나의 리전은 여러 개의 가용영역으로 구성된다.</p>
</li>
</ul>
</li>
<li><p><strong>가용영역(Availability Zone, AZ):</strong> 리전을 한번 더 분산해서 배치한 것</p>
<ul>
<li>하나의 리전 내에 있는 물리적으로 독립된 데이터센터 단위</li>
<li>예를 들어 서울 리전(<code>ap-northeast-2</code>)은 <code>ap-northeast-2a</code>, <code>ap-northeast-2b</code> 같은 <strong>2~3개의 AZ</strong>로 구성된다.</li>
</ul>
</li>
<li><p><strong>VPC(Virtual Private Cloud)</strong></p>
<ul>
<li>AWS 리전 내에서 <strong>내가 만든 가상의 네트워크 공간</strong>. 이 안에 내가 서버(EC2), 데이터베이스(RDS), 보안 설정 등을 구성한다.</li>
</ul>
</li>
<li><p><strong>서브넷(Subnet)</strong></p>
<ul>
<li>VPC 안에 있는 작은 네트워크 공간</li>
<li>나뉜 각 서브넷은 원하는 가용영역에 배치하여 사용한다.</li>
</ul>
</li>
</ul>
<p>VPC = 아파트 단지
→ AZ = 동 (A동, B동 등)
→ Subnet = 각 동 안의 층
→ EC2, RDS = 층에 사는 사람들</p>
<pre><code class="language-cpp">서울 리전 (ap-northeast-2)
└── VPC (10.0.0.0/16)
    ├── Subnet-A (10.0.1.0/24, ap-northeast-2a)
    │   └── EC2 인스턴스 A
    └── Subnet-B (10.0.2.0/24, ap-northeast-2b)
        └── RDS 인스턴스 B</code></pre>
<p>VPC 서브넷 IP 주소 대역에서는 5개의 주소를 host에 할당할 수 없다. </p>
<ol>
<li>첫 주소 : 서브넷의 네트워크 대역</li>
<li>두번째 : VPC 라우터에 할당</li>
<li>세번째 : Amazon이 제공하는 DNS에 할당</li>
<li>미래를 위해 예약</li>
<li>브로드 캐스트 주소</li>
</ol>
<p>🌟 <strong>VPC 내부적으로 라우터가 있고, 그렇다면 VPC 내부 서브넷끼리 통신이 되겠네? [매우 중요]</strong></p>
<h2 id="포트포워딩">포트포워딩</h2>
<p>하나의 공용 아이피 주소를 가진 공유기가 자신의 포트를 통해 올바른 사설 아이피 주소를 가진 디바이스에게 데이터를 주는 것</p>
<p><em>포트 포워딩의 포트와 소켓에서 포트는 서로 연관이 없음!</em></p>
<h2 id="pubic-subnet">pubic subnet</h2>
<p>VPC 서브넷 중 외부와 통신이 원활하게 되는 서브넷 대역</p>
<h2 id="private-subnet">private subnet</h2>
<p>외부와 통신이 되지 않는 서브넷 대역</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 8. 프론트엔드 연동과 Swagger ]]></title>
            <link>https://velog.io/@arin_0303/Chapter-8.-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%B0%EB%8F%99%EA%B3%BC-Swagger</link>
            <guid>https://velog.io/@arin_0303/Chapter-8.-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%B0%EB%8F%99%EA%B3%BC-Swagger</guid>
            <pubDate>Fri, 26 Dec 2025 15:34:01 GMT</pubDate>
            <description><![CDATA[<h3 id="swagger-관련-라이브러리-설치">swagger 관련 라이브러리 설치</h3>
<p><code>npm add swagger-autogen swagger-ui-express</code>
<img src="https://velog.velcdn.com/images/arin_0303/post/99e5e7d2-1cbe-4601-bfba-641637f88c75/image.png" alt=""></p>
<h3 id="swagger-세팅">swagger 세팅</h3>
<pre><code class="language-jsx">import swaggerAutogen from &quot;swagger-autogen&quot; 
import swaggerUiExpress from &quot;swagger-ui-express&quot;

app.use(
  &quot;/docs&quot;,
  swaggerUiExpress.serve,
  swaggerUiExpress.setup({}, {
    swaggerOptions: {
      url: &quot;/openapi.json&quot;,
    },
  })
);

app.get(&quot;/openapi.json&quot;, async (req, res, next) =&gt; {
  // #swagger.ignore = true
  const options = {
    openapi: &quot;3.0.0&quot;,
    disableLogs: true,
    writeOutputFile: false,
  };
  const outputFile = &quot;/dev/null&quot;; // 파일 출력은 사용하지 않습니다.
  const routes = [&quot;./src/index.js&quot;];
  const doc = {
    info: {
      title: &quot;UMC 7th&quot;,
      description: &quot;UMC 7th Node.js 테스트 프로젝트입니다.&quot;,
    },
    host: &quot;localhost:3000&quot;,
  };

  const result = await swaggerAutogen(options)(outputFile, routes, doc);
  res.json(result ? result.data : null);
});</code></pre>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/fb24d7e4-4dcd-4f3f-86b1-65f40405feba/image.png" alt="">
<img src="https://velog.velcdn.com/images/arin_0303/post/d2d6c163-d292-4eb6-97b1-772664bbfea1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 7. Express 미들웨어 & API 응답 통일 & 에러 핸들링]]></title>
            <link>https://velog.io/@arin_0303/Chapter-7.-Express-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-API-%EC%9D%91%EB%8B%B5-%ED%86%B5%EC%9D%BC-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@arin_0303/Chapter-7.-Express-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-API-%EC%9D%91%EB%8B%B5-%ED%86%B5%EC%9D%BC-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Fri, 26 Dec 2025 15:30:14 GMT</pubDate>
            <description><![CDATA[<h3 id="express-미들웨어-appuse-와-appmethod">Express 미들웨어 (app.use() 와 app.METHOD())</h3>
<p>아래는 경로가 없는 미들웨어 함수이기 때문에 이 함수는 모든 경로, 모든 HTTP 메서드 요청에 대해서 실행된다 </p>
<pre><code class="language-jsx">const myLogger = (req, res, next) =&gt; {
    console.log(&quot;LOGGED&quot;);
    next(); // 다음 미들웨어 또는 라우트 핸들러로 제어를 넘김
}

**app.use**(myLogger); 

/* ----&gt; 위에 두 개의 코드를 하나로 합친 것이다. 
app.use((req, res, next) =&gt; {
    console.log(&quot;LOGGED&quot;);
    next();         
});
*/</code></pre>
<p>&#39;/&#39; 경로로 들어오는 모든 HTTP 요청에 대해 실행되는 미들웨어 함수는 app.use()와 &#39;/&#39;를 이용한다.  </p>
<pre><code class="language-jsx">**app.use(&#39;/&#39;**, (req, res, next) =&gt; {
  console.log(&#39;Request Type:&#39;, req.method)
  next();
});</code></pre>
<p><code>&#39;/&#39;</code> 경로로 들어오는 GET 요청에 대해 실행되는 미들웨어 함수는 <code>app.get()</code> 과 <code>&#39;/&#39;</code> 를 이용한다.</p>
<pre><code class="language-jsx">app.get(&#39;/&#39;, (req, res) =&gt; {
    console.log(&quot;/&quot;);
    res.send(&quot;Hello UMC!&quot;);
}</code></pre>
<h3 id="express-미들웨어-next">Express 미들웨어 (next())</h3>
<blockquote>
<p>next() 는 다음 미들웨어 함수 or 해당 라우트 핸들러로 제어를 넘기는 함수이다. 따라서 next()를 호출하지 않으면 요청이 다음 단계로 진행되지 않는다. </p>
</blockquote>
<pre><code class="language-jsx">const myLogger = (req, res, next) =&gt; {
  console.log(&quot;LOGGED&quot;);
  // next();
}

app.use(myLogger);

app.get(&#39;/&#39;, (req, res) =&gt; {
  console.log(&quot;/&quot;);
  res.send(&quot;Hello UMC!&quot;);
});

app.get(&#39;/hello&#39;, (req, res) =&gt; {
  console.log(&#39;/hello&#39;);
  res.send(&#39;Hello world!&#39;);
});</code></pre>
<p><code>next()</code> 를 주석처리 했더니 <code>&#39;/&#39;</code> 경로로 요청을 해도 LOGGED만 출력되고 <code>&#39;/hello&#39;</code> 경로로 요청을 해도 LOGGED만 출력되는 것을 확인했다. </p>
<p>즉, <code>app.use(myLogger)</code>미들웨어만 실행되고 이후에 실행되어야 할 라우트 핸들러는 실행되지 않는 것이다. 이렇게 되면 <code>res.send()</code> 코드가 실행되지 않아 응답이 전송되지 않고 이로 인해 <strong>무한 로딩 상태</strong> or <strong>타임 아웃</strong>으로 끝난다.
<img src="https://velog.velcdn.com/images/arin_0303/post/65929128-8e42-4557-9c9d-1f1d9dc1a0ab/image.png" alt=""></p>
<h3 id="error-처리">Error 처리</h3>
<pre><code class="language-jsx">export const handleMemberSignUp = async (req, res, next) =&gt; {
  console.log(&quot;회원가입을 요청했습니다!&quot;);
  console.log(&quot;[MemberController] request body:&quot;, req.body); // 값이 잘 들어오나 확인하기 위한 테스트용

  const member = await memberSignUp(bodyToMember(req.body)); 
  // res.status(StatusCodes.OK).json({ 
  //   resultType: &quot;SUCCESS&quot;,
  //   error: null,
  //   success: member,
  // });

  res.status(StatusCodes.OK).success(member);
};
</code></pre>
<p><strong>member.service.js</strong></p>
<blockquote>
<p>member 저장 + preference 저장 두 db 작업을 트랜잭션을 사용하여 하나로 묶었다. 
해당 트랜잭션 함수를 repository 계층으로 분리하는 작업은 이후에 리팩토링 할 예정이다. </p>
</blockquote>
<pre><code class="language-jsx">export const memberSignUp = async (data) =&gt; {
  try{
    const result = await prisma.$transaction(async (tx) =&gt; {
      // 1. 이메일 중복 검사
      const existing = await tx.member.findFirst({where:{email:data.email}});
      if(existing)
        throw new error(&quot;이미 존재하는 이메일입니다.&quot;);
      // 2. member 생성
      const created = await tx.member.create({
        data:{
          email: data.email,
          name: data.name,
          gender: data.gender,
          age: data.age,
          address: data.address,
          specAddress: data.specAddress,
          phoneNumber: data.phoneNumber,
        }
      });

      const memberId = created.id;

      // 3. member_prefer 생성 
      for (const categoryId of data.preferences) {
        await tx.memberPrefer.create({
          data: {
            memberId: memberId,
            categoryId: categoryId,
            createdAt: new Date(),
            updatedAt: new Date(),
          },
        });
      }
      // 4. member + member_prefer 정보 조회 
      const member = await tx.member.findUnique({ where: { id: memberId } });
      const preferences = await tx.memberPrefer.findMany({
        where: { memberId },
        orderBy: { categoryId: &#39;asc&#39; },
        select: { categoryId: true }
      });

      return responseFromMember({ member, preferences });
    });

    return result;

  } catch(error) {
    // rollback
    console.error(&quot;회원가입 실패: &quot;, error.message);
    throw error;
  }
</code></pre>
<h3 id="get-apimember">GET /api/member</h3>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/9d0c63e7-9ea0-4e2a-b588-a24712265e65/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 6. ORM 사용해보기]]></title>
            <link>https://velog.io/@arin_0303/Chapter-6.-ORM-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@arin_0303/Chapter-6.-ORM-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 26 Dec 2025 15:15:52 GMT</pubDate>
            <description><![CDATA[<h2 id="실습-인증">실습 인증</h2>
<ul>
<li><p>prisma 라이브러리 설치</p>
<pre><code class="language-bash">npm install @prisma/client prisma</code></pre>
</li>
<li><p>prisma 설정 파일 만들기</p>
<pre><code>npm exec prisma init</code></pre><p><img src="https://velog.velcdn.com/images/arin_0303/post/bb6081e8-a5e3-4551-9d3e-fcb5bd7ebb60/image.png" alt=""></p>
<pre><code>// .env
// 나의 mysql정보를 넣으면 됨 
DATABASE_URL=&quot;mysql://&lt;DB_USER&gt;:&lt;DB_PASSWORD&gt;@&lt;DB_HOST&gt;:&lt;DB_PORT&gt;/&lt;DB_NAME&gt;&quot;</code></pre></li>
<li><p>schema.prisma 수정</p>
<pre><code>//prisma/schema.prisma
generator client {
    provider = &quot;prisma-client-js&quot;
}

datasource db {
    provider = &quot;mysql&quot;
    url = env(&quot;DATABASE_URL&quot;)
}</code></pre></li>
<li><p>Member 모델 추가</p>
<pre><code class="language-jsx">// prisma/schema.prisma

model Member {
  id            Int       @id @default(autoincrement())
  name          String?   @db.VarChar(20)
  gender        String?   @db.VarChar(10)
  age           Int?
  address       String?   @db.VarChar(40)
  specAddress   String?   @map(&quot;spec_address&quot;) @db.VarChar(40)
  phoneNum      String?   @map(&quot;phone_num&quot;)    @db.VarChar(13)
  status        String?   @db.VarChar(15)
  inactiveDate  DateTime? @map(&quot;inactive_date&quot;) @db.DateTime(6)
  socialType    String?   @map(&quot;social_type&quot;)   @db.VarChar(10)
  createdAt     DateTime? @map(&quot;created_at&quot;)    @db.DateTime(6)
  updatedAt     DateTime? @map(&quot;updated_at&quot;)    @db.DateTime(6)
  email         String?   @db.VarChar(50)
  point         Int?

  @@map(&quot;member&quot;)
}
</code></pre>
</li>
<li><p>prisma의 schema 파일 수정시 자동으로 서버 재시작하도록 설정</p>
<pre><code class="language-jsx">// package.json
// nodemon 수정
&quot;dev&quot;: &quot;nodemon -e js,json,prisma --exec \&quot;prisma generate &amp;&amp; node src/index.js\&quot;&quot; </code></pre>
</li>
<li><p>다른 코드 전체에서 공유하도록 설정 </p>
<pre><code class="language-jsx">// src/dbConfig.js
import { PrismaClient } from &quot;@prisma/client&quot;;

export const prisma = new PrismaClient();</code></pre>
</li>
<li><p>데이터 하나 찾기</p>
<pre><code class="language-jsx">await prisma.member.findFirst({ 
  where: { id: 1 } 
});</code></pre>
</li>
<li><p>데이터 생성하기</p>
<pre><code class="language-jsx">await prisma.user.create({
  data: {
      name: &quot;엘빈&quot;,
      email: &quot;test@example.com&quot;,
      ...,
    }
});
</code></pre>
</li>
<li><p>addUser 함수를 ORM을 사용하도록 수정</p>
<pre><code class="language-jsx">// src/member.repository.js

**import { prisma } from &quot;../db.config.js&quot;; // prisma 임포트** 

export const addUser = async (data) =&gt; {
  const user = await prisma.user.findFirst({ where: { email: data.email } });
  if (user) {
    return null;
  }

  const created = await prisma.user.create({ data: data });
  return created.id;
};
</code></pre>
</li>
<li><p>addMember를 ORM으로 바꾼 후 첫 API Test
<img src="https://velog.velcdn.com/images/arin_0303/post/97f8ca2f-c6e5-4d5c-b6e8-f3c6da4eef37/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 5. API 및 프로젝트 설정 기초]]></title>
            <link>https://velog.io/@arin_0303/Chapter-5.-API-%EB%B0%8F-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%A4%EC%A0%95-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@arin_0303/Chapter-5.-API-%EB%B0%8F-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%A4%EC%A0%95-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 26 Dec 2025 15:01:18 GMT</pubDate>
            <description><![CDATA[<h2 id="실습-인증">실습 인증</h2>
<ol>
<li><p>github 라벨 생성
<img src="https://velog.velcdn.com/images/arin_0303/post/4ebfebdb-c616-44e2-a3ca-6d90d1660dc3/image.png" alt=""></p>
</li>
<li><p>github 이슈 생성
<img src="https://velog.velcdn.com/images/arin_0303/post/3bc08cc2-6267-4a35-a065-8ad2476bff32/image.png" alt=""></p>
</li>
<li><p>생성한 이슈에 branch 생성</p>
<pre><code class="language-bash">git fetch origin
git checkout feature/create-article-api</code></pre>
</li>
<li><p>.gitignore 설정</p>
</li>
<li><p>.env 설정</p>
<pre><code class="language-jsx">// .env
PORT=3030</code></pre>
<pre><code class="language-jsx">// src/index.js
import dotenv from &quot;dotenv&quot;
dotenv.config(); // .env파일을 읽어와서 process.env에 저장
const port = process.env.PORT;

console.log(&quot;현재 포트:&quot;, process.env.PORT);</code></pre>
</li>
<li><p>db 연결</p>
<pre><code class="language-jsx">// .env
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=mysql3143
DB_NAME=mission_db</code></pre>
<pre><code class="language-jsx">// src/db.config.js
import mysql from &quot;mysql2/promise&quot;;
import dotenv from &quot;dotenv&quot;;

dotenv.config();

export const pool = mysql.createPool({
 host: process.env.DB_HOST || &quot;localhost&quot;, // mysql의 hostname
 user: process.env.DB_USER || &quot;root&quot;, // user 이름
 port: process.env.DB_PORT || 3306, // 포트 번호
 database: process.env.DB_NAME || &quot;mission_db&quot;, // 데이터베이스 이름
 password: process.env.DB_PASSWORD || &quot;mysql3143&quot;, // 비밀번호
 waitForConnections: true,
 // Pool에 획득할 수 있는 connection이 없을 때,
 // true면 요청을 queue에 넣고 connection을 사용할 수 있게 되면 요청을 실행하며, false이면 즉시 오류를 내보내고 다시 요청
 connectionLimit: 10, // 몇 개의 커넥션을 가지게끔 할 것인지
 queueLimit: 0, // getConnection에서 오류가 발생하기 전에 Pool에 대기할 요청의 개수 한도
});
</code></pre>
</li>
<li><p>/api/user요청 데이터</p>
<pre><code class="language-json">{
   &quot;name&quot;: &quot;이름&quot;,
   &quot;gender&quot;: &quot;여성/남성&quot;,
   &quot;age&quot;: &quot;23&quot;,
   &quot;birth&quot;: &quot;20030315&quot;,
   &quot;address&quot;: &quot;주소&quot;,
   &quot;specAddress&quot;: &quot;세부주소&quot;,
   &quot;email&quot;: &quot;이메일&quot;,
   &quot;categories&quot;: [&quot;도시락&quot;, &quot;고기&quot;]
}</code></pre>
</li>
<li><p>DTO 구현 (responseFromUser )</p>
<pre><code class="language-jsx">export const responseFromUser = ({ member, preferences }) =&gt; {
 return {
   id: member.id,
   email: member.email,
   name: member.name,
   gender: member.gender,
   age: member.age,
   address: member.address,
   specAddress: member.spec_address,
   phoneNumber: member.phone_num,
   preferences: preferences.map(p =&gt; p.category_id)
 };
};</code></pre>
</li>
<li><p>API test<code>(/api/user)</code>
<img src="https://velog.velcdn.com/images/arin_0303/post/89b0dec8-bc2f-4aaa-a15e-e49812c0ee4b/image.png" alt="">
<img src="https://velog.velcdn.com/images/arin_0303/post/b512c4c2-1793-4063-9119-d7ffb99309c5/image.png" alt=""></p>
</li>
</ol>
<h2 id="미션-인증">미션 인증</h2>
<p><strong>Issue:</strong> 5주차 mission
<strong>branch:</strong> feature/chapter-05
<img src="https://velog.velcdn.com/images/arin_0303/post/6787a7dc-f568-4f69-a868-8d211e51bfb2/image.png" alt=""></p>
<h3 id="1-특정-지역에-가게-추가하기-api">1. 특정 지역에 가게 추가하기 API</h3>
<ul>
<li>post 요청</li>
<li>store 테이블, region테이블</li>
<li>이미 저장되어있는 지역에 해당하는 가게를 추가</li>
<li>프론트가 regionId를 넘겨주면 백이 해당 id에 해당하는 지역에 가게 추가<pre><code class="language-jsx">// request body 
{
   &quot;regionId&quot;: 1,
   &quot;name&quot;: &quot;마포곱창&quot;,
   &quot;address&quot;: &quot;서울시 마포구 합정동 123&quot;,
   &quot;score&quot;: 4.5
}</code></pre>
</li>
<li>검증 사항<ul>
<li>요청된 regionId가 region 테이블의 id에 포함되어있는 숫자인지</li>
<li>검증사항을 통과했으면</li>
<li>store테이블에 요청 데이터 저장</li>
<li>store테이블에 저장된 데이터들을 사용자에게 반환
 <img src="https://velog.velcdn.com/images/arin_0303/post/be7737b8-91ee-4248-8b5a-88d19de456c0/image.png" alt="">
 <img src="https://velog.velcdn.com/images/arin_0303/post/9a786cd8-664e-4cd0-8d0e-289ac7fabe04/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="2-가게에-리뷰-추가하기-api">2. 가게에 리뷰 추가하기 API</h3>
<ul>
<li>post 요청</li>
<li>review 테이블, review_image테이블
<img src="https://velog.velcdn.com/images/arin_0303/post/5ae6118d-f79a-4055-a858-56bc0750b2b6/image.png" alt="">
<img src="https://velog.velcdn.com/images/arin_0303/post/61656dd0-0629-4663-8b5f-c27864b41f71/image.png" alt=""></li>
</ul>
<h3 id="3-가게의-미션을-도전-중인-미션에-추가미션-도전하기-api">3. 가게의 미션을 도전 중인 미션에 추가(미션 도전하기) API</h3>
<ul>
<li>post 요청</li>
<li>member_mission 테이블
&lt;이미 도전 중인 미션&gt;
<img src="https://velog.velcdn.com/images/arin_0303/post/42f0a422-4954-433a-aeea-da3cd386aa5e/image.png" alt="">
&lt;이미 완료된 미션&gt;
<img src="https://velog.velcdn.com/images/arin_0303/post/530c8508-89e7-4107-b329-06ed1276e78a/image.png" alt="">
&lt;새로 도전하는 미션&gt;
<img src="https://velog.velcdn.com/images/arin_0303/post/a8fe8141-26a9-42c0-afd3-1b9329d34a88/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 4. ES6와 프로젝트 파일 구조의 이해(2)]]></title>
            <link>https://velog.io/@arin_0303/Chapter-4.-ES6%EC%99%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%9D%B4%ED%95%B42</link>
            <guid>https://velog.io/@arin_0303/Chapter-4.-ES6%EC%99%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%9D%B4%ED%95%B42</guid>
            <pubDate>Fri, 26 Dec 2025 14:42:02 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-아키텍처">프로젝트 아키텍처</h2>
<h3 id="1-service-oriented-architectureservice-layer-pattern">1. Service-Oriented Architecture(Service Layer Pattern)</h3>
<blockquote>
<p>Controller - Service Layer - Data Access Layer (Repository Layer)    </p>
</blockquote>
<ul>
<li><strong>Controller</strong>: 단순히 요청과 응답 역할만 한다(라우팅 역할)</li>
<li><strong>Service Layer</strong>: Controller로 부터 전달된 요청에 대한 로직을 적용하는 계층</li>
<li><strong>Data Access Layer(Repository Layer)</strong>: Service계층에서 데이터베이스 접근이 필요한 경우 데이터에 대한 코드가 작성되는 계층 </li>
</ul>
<h3 id="2-mvc-패턴">2. MVC 패턴</h3>
<blockquote>
<p>Controller - Model - View</p>
</blockquote>
<ul>
<li><strong>Controller</strong>: 요청과 응답 역할과 필요한 로직을 수행한다.</li>
<li><strong>Model</strong>: 데이터, 비즈니스 로직 담당</li>
<li><strong>View</strong>: 사용자가 보게 될 화면 담당(HTML 또는 템플릿 파일에 데이터를 삽입하여 컨트롤러에게 넘긴다)</li>
</ul>
<h3 id="mvc와-soa의-차이점">MVC와 SOA의 차이점</h3>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/f46bc94b-b1d0-40cc-b418-092116c44f1a/image.png" alt=""></p>
<h3 id="3-그-외-다른-프로젝트-구조">3. 그 외 다른 프로젝트 구조</h3>
<ol>
<li>Clean Architecture</li>
<li>Microservices Architecture</li>
<li>RESTful Architecture</li>
<li>Service Layer Pattern</li>
<li>Event-Driven Architecture</li>
</ol>
<h2 id="비즈니스-로직">비즈니스 로직</h2>
<blockquote>
<p>비즈니스 로직은 지켜야 할 도메인 규칙을 코드로 옮긴 부분이다. </p>
</blockquote>
<p>규칙이란, 사용자가 무언가를 요청했을 때 허용할지 말지, 어떻게 처리할지를 정하는 조건과 계산이다. </p>
<p>ex) </p>
<ul>
<li>주문은 얼마 이상이어야 한다</li>
<li>재고 없으면 주문 불가</li>
<li>관리자만 삭제할 수 있다</li>
<li>무료배송은 3만원 이상부터</li>
</ul>
<p>SOA 아키텍처에서 비즈니스 로직은 대부분 Service계층에서 작성된다</p>
<h2 id="dto">DTO</h2>
<blockquote>
<p><strong>DTO</strong>(Data Transfer Object)는 계층 간 또는 외부로 데이터를 전달할 때, <strong>필요한 정보만</strong> 안전하게 담는 전용 객체이다. </p>
</blockquote>
<ul>
<li><p>DTO를 사용하는 이유</p>
<ol>
<li><p>필요한 데이터만 보내기 위해(DB 모델은 너무 많은 정보가 있기 때문에 다 보낼 필요가 없음) </p>
</li>
<li><p>보안(민감한 정보 숨기기)</p>
</li>
<li><p>계층 간 역할 분리 (도메인 모델vs전송 모델)</p>
</li>
<li><p>API 응답 형식 통일</p>
<p>ex) 유저 정보 응답</p>
</li>
</ol>
<p>  <strong>User 모델 (전체 정보)</strong></p>
<pre><code class="language-jsx">  // models/User.js
  const User = {
    id: 1,
    name: &quot;홍길동&quot;,
    password: &quot;secret123&quot;,
    email: &quot;gildong@example.com&quot;,
    role: &quot;admin&quot;,
    createdAt: &quot;...&quot;,
    updatedAt: &quot;...&quot;
  };</code></pre>
<p>  프론트에 필요한 정보만 주기 위해 DTO 객체를 사용한 예시 </p>
<pre><code class="language-jsx">  // dtos/UserDTO.js
  class UserDTO {
    constructor(user) {
      this.id = user.id;
      this.name = user.name;
      this.email = user.email;
    }
  }

  module.exports = UserDTO;
</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 4. ES6와 프로젝트 파일 구조의 이해(1)]]></title>
            <link>https://velog.io/@arin_0303/Chapter-4.-ES6%EC%99%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%9D%B4%ED%95%B41</link>
            <guid>https://velog.io/@arin_0303/Chapter-4.-ES6%EC%99%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%9D%B4%ED%95%B41</guid>
            <pubDate>Fri, 26 Dec 2025 14:34:50 GMT</pubDate>
            <description><![CDATA[<h2 id="es">ES</h2>
<p><strong>ES (Ecma Script) :</strong> ECMA international에서 정한 자바스크립트의 표준, 규격</p>
<p>ES는 버전이 계속 업데이트되어 ES5, ES6 등등의 버전이 있다. </p>
<p>ES6이후로도 업데이트가 되고있지만, ES6가 가장 큰 변화를 가져온 버전이기 때문에 중요하게 다뤄진다.</p>
<h2 id="es6">ES6</h2>
<h3 id="es6의-주요-변화-및-특징">ES6의 주요 변화 및 특징</h3>
<ol>
<li><p><strong><code>let</code>과 <code>const</code>도입</strong></p>
<p> var의 단점을 보완하기 위해 let과 const를 도입했다. </p>
<ul>
<li><p><code>var</code> (문제점 3가지)</p>
<ul>
<li><p><strong>스코프 범위가 function</strong></p>
<pre><code class="language-jsx">  if(true){
      var a =10;
  }
  console.log(a); // 10 (블록 밖에서도 접근이 가능하여 문제 발생 가능) </code></pre>
</li>
<li><p><strong>중복 선언 가능</strong></p>
<pre><code class="language-jsx">  var name = &quot;cindy&quot;;
  var name = &quot;chill&quot;; // 중복 선언 가능하여 문제 발생 가능 </code></pre>
</li>
<li><p><strong>Hoisting이 가능</strong></p>
<p>  Hoisting이란, 인터프리터가 코드를 실행하기 전에 함수, 변수, 클래스 또는 임포트(import)의 선언문을 해당 범위의 맨 위로 끌어올리는 것처럼 보이는 현상이다. </p>
<p>  그래서 아래 코드처럼 아직 선언되지도 않은 변수를 사용할 수 있는 현상이 발생한다. </p>
<pre><code class="language-jsx">  console.log(name); // undefined(-&gt;원래 오류가 나야 정상인데, 변수를 인식하고있음) 
  var name = &quot;cindy&quot;
  console.log(name); // cindy</code></pre>
</li>
</ul>
</li>
<li><p><code>let</code></p>
<ul>
<li><p>스코프 범위가 중괄호이다(for, if, function)</p>
<pre><code class="language-jsx">  if (true) {
    let b = 20;
  }
  console.log(b); // 오류 발생 (b는 블록 안에서만 유효)</code></pre>
</li>
<li><p>재할당 가능</p>
</li>
<li><p>중복 선언 불가능</p>
</li>
</ul>
</li>
<li><p><code>const</code></p>
<ul>
<li>스코프 범위가 중괄호이다(for, if, function)</li>
<li>재할당 불가능</li>
<li>중복 선언 불가능</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>화살표 함수 (Arrow Functions)가 추가됨</strong>
 표기: <code>⇒</code></p>
<p> 화살표 함수는 동적으로 this가 바인딩 되지 않고 <strong>고정</strong>되어서 버그가 줄어든다.</p>
<p> 참고) this의 동적 vs 정적</p>
<ul>
<li><p><strong>동적인 this</strong></p>
<p>예시 ) 일반함수</p>
<pre><code class="language-javascript">const person = {
  name: &quot;Alice&quot;,
  greet: function() {
    function innerFunc() {
      console.log(this.name); // this == window
    }
    innerFunc(); // 일반함수로 호출됨 
  }
};

person.greet(); // undefined 또는 에러(window.name이 없음)</code></pre>
</li>
<li><p><strong>정적인 this</strong></p>
<p>  예시) 화살표 함수</p>
<pre><code class="language-javascript">  const person = {
    name: &quot;Bob&quot;,
    greet: function() {
      const arrowFunc = () =&gt; {
        console.log(this.name); // this == person객체
      };
      arrowFunc(); // 화살표함수로 호출됨
    }
  };

  person.greet(); // Bob</code></pre>
</li>
</ul>
</li>
<li><p><strong>클래스 문법 제공</strong>
 이전에는 prototype 기반이었지만 이젠 클래스 기반으로 더 직관적인 객체 생성 가능 ! </p>
<pre><code class="language-jsx"> class Animal {
   constructor(name) {
     this.name = name;
   }

   speak() {
     console.log(`${this.name}이(가) 소리를 낸다.`);
   }
 }

 const dog = new Animal(&#39;강아지&#39;);
 dog.speak(); // 강아지가 소리를 낸다.</code></pre>
</li>
<li><p><strong>템플릿 리터럴 (백틱)</strong></p>
<p> 이전에는 문자열을 이어붙일 때 <code>+</code>를 썼지만 이젠 백틱(````)과 <code>${}</code> 문법 사용 가능</p>
<pre><code class="language-jsx"> const name = &#39;철수&#39;;
 const age = 20;

 const message = `안녕하세요, 제 이름은 ${name}이고 나이는 ${age}살입니다.`;
 console.log(message);</code></pre>
</li>
<li><p><strong>구조 분해 할당 (Destructuring)</strong>
 배열이나 객체에서 필요한 값을 변수로 꺼내기 쉽게 해준다.</p>
<pre><code class="language-jsx"> // 배열
 const [x,y] = [1,2];
 console.log(x); // 1

 // 객체
 const person = {name: &quot;아린&quot;, age: 25};
 const { name, age } = person;
 console.log(name); // 아린</code></pre>
</li>
<li><p><strong>스프레드 연산자 <code>…</code></strong>
 배열/객체를 복사하거나 병합할 때 사용한다. </p>
<p> 함수 인자처럼 펼쳐 쓸 수도 있다.</p>
<pre><code class="language-jsx">  const arr1 = [1,2];
   const arr2 = [...arr1, 3, 4];
   console.log(arr2); // [1,2,3,4]

   const user = {name:&quot;아린&quot;};
   const newUser = {...user, age:25};
   console.log(newUser); // { name: &#39;아린&#39;, age: 25 }</code></pre>
</li>
<li><p>*<em>기본 매개변수 *</em>
함수에 인자를 안 넣으면 사용되는 기본 값 설정 가능</p>
<pre><code class="language-jsx">function greet(name = &quot;손님&quot;) {
   console.log(`안녕하세요, ${name}님!`);
}

greet(); // 안녕하세요, **손님**님!
greet(&quot;아린&quot;); // 안녕하세요, 아린님!</code></pre>
</li>
<li><p><strong>Promise (비동기 처리)</strong>
 then, catch로 콜백 지옥을 피하게 도와주는 기능이다. </p>
<pre><code class="language-jsx">const fetchData = new Promise((resolve, reject) =&gt; {
 const success = true;
 if (success) {
   resolve(&quot;데이터 성공!&quot;);
 } else {
   reject(&quot;에러 발생!&quot;);
 }
});

fetchData.then(data =&gt; console.log(data)).catch(err =&gt; console.error(err));</code></pre>
</li>
<li><p><strong>모듈(import, export)</strong>
 파일을 분리해서 코드를 재사용하기 쉽게 만들었다</p>
<pre><code class="language-jsx"> // math.js
 export const add = (a, b) =&gt; a + b;

 // app.js
 import { add } from &#39;./math.js&#39;;
 console.log(add(3, 4)); // 7</code></pre>
</li>
</ol>
<ol start="10">
<li><p><strong>Symbol</strong>
고유한 값을 만들기 위한 유일한 키</p>
<p>→ 다른 키와 절대 충돌 안나게 할 때 사용    </p>
<pre><code class="language-jsx">const id = Symbol(&#39;id&#39;);
const user = {[id]:123};

console.log(user[id]); // 123</code></pre>
</li>
<li><p><strong>Set / Map</strong></p>
<ul>
<li>set: 중복없는 값들의 모음</li>
<li>Map: 키-값 쌍을 저장하는 자료구조<pre><code class="language-jsx">const set = new Set([1, 2, 2, 3]);
console.log(set); // Set { 1, 2, 3 }
</code></pre>
</li>
</ul>
<p>const map = new Map();
map.set(&#39;name&#39;, &#39;아린&#39;);
map.set(&#39;age&#39;, 25);
console.log(map.get(&#39;name&#39;)); // 아린</p>
<pre><code>### ES6를 중요시 하는 이유
ES6는 JavaScript 코드의 효율성, 가독성, 유지보수성을 높여주는 핵심적인 변화를 가져왔기 때문에 현대 JavaScript 개발의 표준이라고 할 수 있다.  
</code></pre></li>
</ol>
<h2 id="es-module">ES Module</h2>
<p>ES Module이란 import와 export를 통해 모듈을 관리할 수 있게 해주는 시스템이다. </p>
<p>&lt;사용법&gt;</p>
<ol>
<li><strong>package.json</strong>에 <code>&quot;type&quot;:&quot;module&quot;</code> 설정</li>
</ol>
<pre><code class="language-jsx">{
    &quot;type&quot;: &quot;module&quot;
}</code></pre>
<ol start="2">
<li><p><code>.mjs 확장자</code> , <code>import 구문</code> 사용</p>
<p> a. 내보내기</p>
<pre><code class="language-jsx"> // math.js
 export const add = (a, b) =&gt; a + b;
 export const subtract = (a, b) =&gt; a - b;</code></pre>
<p> b. 가져오기</p>
<pre><code class="language-jsx"> // app.js
 import { add, subtract } from &#39;./math.js&#39;;

 console.log(add(2, 3)); // 5
 console.log(subtract(5, 3)); // 2</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 3. API URL의 설계 & 프로젝트 세팅 ]]></title>
            <link>https://velog.io/@arin_0303/Chapter-3.-API-URL%EC%9D%98-%EC%84%A4%EA%B3%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@arin_0303/Chapter-3.-API-URL%EC%9D%98-%EC%84%A4%EA%B3%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Fri, 26 Dec 2025 14:09:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/arin_0303/post/6f5916b8-7f43-4cc7-90fa-03639f63b761/image.png" alt=""></p>
<hr>
<h3 id="내가-작성한-api-명세서에-대한-피드백">내가 작성한 API 명세서에 대한 피드백</h3>
<ol>
<li><p>API URL에 user id로 사용자 정보에 접근하는 방식으로 했는데,
이는 보안상 좋지 않은 방법이므로 JWT 토큰에서 user id를 추출하는 방식 활용하기 </p>
</li>
<li><p>RESTful API 원칙에 따라 단수형이 아닌 복수형으로 사용하고, 명사 중심으로 설계하기</p>
</li>
<li><p>미션 성공 누르기 API</p>
<ul>
<li><p>현재 파라미터로 <code>PATCH</code> 요청을 구현했는데, 
RESTful API 원칙, 일관성 등등 여러 측면에서 <code>Request Body</code>로 구현하는 게 더 적합</p>
<ul>
<li>주로 <code>GET</code>은 파라미터로</li>
<li><code>POST</code>, <code>PUT</code>, <code>PATCH</code>는 <code>Request Body</code>로 전달</li>
</ul>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 2. 실전 SQL - 어떤 Query를 작성해야 할까? ]]></title>
            <link>https://velog.io/@arin_0303/Chapter-2.-%EC%8B%A4%EC%A0%84-SQL-%EC%96%B4%EB%96%A4-Query%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@arin_0303/Chapter-2.-%EC%8B%A4%EC%A0%84-SQL-%EC%96%B4%EB%96%A4-Query%EB%A5%BC-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Fri, 26 Dec 2025 14:01:09 GMT</pubDate>
            <description><![CDATA[<h2 id="join">Join</h2>
<ul>
<li><strong>join의 종류</strong><ul>
<li>Inner Join</li>
<li>Outer Join<ul>
<li>Left Join</li>
<li>Right Join</li>
<li>Full Join</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="inner-join">Inner Join</h3>
<p>두 테이블의 공통된 부분만 가져올 때 사용</p>
<p>아래는 해시태그가 UMC인 책의 이름을 가져오는 쿼리문</p>
<pre><code class="language-sql">select b.* 
from book as b
inner join book_hash_tag as bht on b.id = bht.book_id
inner join hash_tag as ht on bht.hash_tag_id = ht.id
where ht.name = &quot;UMC&quot;;</code></pre>
<h3 id="outer-join">Outer Join</h3>
<p>교집합 외부에 존재하는 데이터를 가져올 때 사용
Left 조인이 일반적으로 가장 많이 사용됨</p>
<p>아래는 left join을 그림으로 표현한 이미지.</p>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/095525cd-9af6-4b3f-84c5-0408212c304f/image.png" alt=""></p>
<p>이처럼 모든 좌측 테이블을 가져오되, 조인 불가능한 것은 NULL로 채운다.</p>
<h3 id="pagination-limit-offset">pagination (limit, offset)</h3>
<p>아래는 <code>offset</code>을 이용해 <strong>최신순 정렬</strong>을 구현한 쿼리문 </p>
<pre><code class="language-sql">select * from book 
order by created_at desc
limit 15 offset 0;</code></pre>
<ul>
<li><code>limit</code>: 가져올 데이터의 양</li>
<li><code>offset</code>: 가져올 데이터의 초기 위치 값</li>
<li>즉, <code>limit 15 offset 0</code> 은 0에서부터 15개의 데이터를 가져오라는 뜻이다.</li>
</ul>
<h3 id="pagination-cursor">pagination (cursor)</h3>
<p>cursor: 마지막으로 조회한 컨텐츠를 의미</p>
<p>아래는 cursor를 이용해 <strong>최신순 정렬</strong>을 구현한 쿼리문 </p>
<p> Ex) 마지막으로 본 book_id가 3일 때 , 해당 아이디의 책이 생성된 날짜 이전(<code>created_at &lt;</code> )의 책들을 내림차순으로 정렬하여 최근에 생성된 책부터 15권을 출력하라 </p>
<pre><code class="language-sql">select * from book 
where created_at &lt; (select created_at from book where id = 3)
order by created_at desc;</code></pre>
<hr>
<h2 id="🎯미션-기록">🎯미션 기록</h2>
<ol>
<li><p>내가 진행중, 진행 완료한 미션 모아서 보는 쿼리(페이징 포함)</p>
<pre><code class="language-sql">select * from user_mission um
where status = &quot;progress&quot; or status = &quot;complete&quot;
limit ? offset (?-1)*?; </code></pre>
</li>
<li><p>리뷰 작성하는 쿼리,</p>
</li>
</ol>
<ul>
<li>사진의 경우는 일단 배제<pre><code class="language-sql">insert into review (user_id, restaurant_id, rating, body, created_at, updated_at) 
values(?, ?, ?, ?, now(), now());</code></pre>
</li>
</ul>
<ol start="3">
<li><p>홈 화면 쿼리
(현재 선택 된 지역에서 도전이 가능한 미션 목록, 페이징 포함)</p>
<pre><code class="language-sql">select *
from mission as m 
join user_mission as um on m.id = um.mission_id
join restaurant on um.restaurant_id = restaurant .id
join region on region.id = restaurant.region_id
where region.name = ?
limit ? offset(?-1)*?;</code></pre>
</li>
<li><p>마이 페이지 화면 쿼리</p>
<pre><code class="language-sql">select * from user;</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 1. Database 설계]]></title>
            <link>https://velog.io/@arin_0303/Chapter-1.-Database-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@arin_0303/Chapter-1.-Database-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Fri, 26 Dec 2025 13:50:39 GMT</pubDate>
            <description><![CDATA[<h3 id="사용자-관련-요구-사항">사용자 관련 요구 사항</h3>
<ol>
<li><strong>카카오 소셜 로그인</strong>을 구현 할 예정이다.</li>
<li><strong>회원 탈퇴 기능</strong>이 필요하다.</li>
<li><strong>이름, 닉네임, 전화번호, 성별</strong>이 필요하다.</li>
</ol>
<h3 id="책-관련-요구-사항">책 관련 요구 사항</h3>
<ol>
<li><strong>사용자가 책 여러 권을 대여</strong>할 수 있다.</li>
<li>책은 <strong>하나의 카테고리</strong>가 있다.</li>
<li>책은 <strong>제목, 설명에 대한 정보</strong>가 필요하다.</li>
<li>책 소개 페이지에 <strong>해시태그</strong>가 붙을 수 있고,</li>
</ol>
<p><strong>책 한 권에 해시태그 여러 개</strong>가, <strong>해시태그 하나가 여러 책</strong>에 붙을 수 있다.
5. 사용자가 책 설명 페이지에서 책에 <strong>좋아요</strong>를 누를 수 있다.
6. 책 <strong>카테고리 별로 현재 몇 개의 책이 있는지 집계</strong>가 필요하다.</p>
<h3 id="알림-관련-요구-사항">알림 관련 요구 사항</h3>
<ol>
<li>알림은 <strong>공지 관련 알림, 책 반납 시간 임박 알림, 마케팅 알림이 있을 수 있다.</strong></li>
</ol>
<p>아래는 요구사항에 대한 나의 테이블 설계도이다. </p>
<pre><code>&lt;소셜로그인 테이블&gt;
소셜아이디
사용자아이디(pk)


&lt;사용자 테이블&gt;
사용자아이디(pk)
닉네임
전화번호
성별
활성화 여부

&lt;책 테이블&gt;
책 아이디(pk)
카테고리
제목
설명

&lt;해시태그&gt;
태그아이디(pk)
태그이름


&lt;책-태그 테이블&gt;
책아이디(fk)
태그아이디(fk)

&lt;사용자-책 좋아요 테이블&gt;
사용자아이디(fk)
책아이디(fk)

&lt;알림 테이블&gt;
알림아이디(pk)
알림 카테고리(종류: 공지, 반납 시간 임박, 마케팅)
알림 내용</code></pre><h3 id="user-테이블">user 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>name</td>
<td>varchar(20)</td>
<td></td>
</tr>
<tr>
<td>gender</td>
<td>enum(&#39;m&#39;, &#39;w&#39;, &#39;none&#39;)</td>
<td></td>
</tr>
<tr>
<td>birth</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>address</td>
<td>varchar(200)</td>
<td></td>
</tr>
<tr>
<td>point</td>
<td>bigint</td>
<td>default 0</td>
</tr>
<tr>
<td>social_type</td>
<td>varchar(20)</td>
<td></td>
</tr>
<tr>
<td>social_id</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="agree-테이블-이용약관">agree 테이블 (이용약관)</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>title</td>
<td>text</td>
<td></td>
</tr>
<tr>
<td>detail</td>
<td>text</td>
<td></td>
</tr>
<tr>
<td>essential</td>
<td>enum(&#39;necessary&#39;, &#39;unnecessary&#39;)</td>
<td>필수 여부</td>
</tr>
</tbody></table>
<hr>
<h3 id="user_agree-테이블">user_agree 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>user_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>agree_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="food_category-테이블">food_category 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>name</td>
<td>varchar(50)</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="favorite_food-테이블">favorite_food 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>user_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>food_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="restaurant-테이블">restaurant 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>name</td>
<td>varchar(100)</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="mission-테이블">mission 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td>사장님 구분 번호</td>
</tr>
<tr>
<td>restaurant_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>point</td>
<td>int</td>
<td></td>
</tr>
<tr>
<td>deadline</td>
<td>date</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="user_mission-테이블">user_mission 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>user_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>mission_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>status</td>
<td>enum(&#39;progress&#39;, &#39;complete&#39;)</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="review-테이블">review 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>user_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>restaurant_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>rating</td>
<td>float</td>
<td></td>
</tr>
<tr>
<td>body</td>
<td>text</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="review_image-테이블">review_image 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>review_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>image_url</td>
<td>text</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="user_point-테이블">user_point 테이블</h3>
<table>
<thead>
<tr>
<th>칼럼명</th>
<th>타입</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>id (PK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>user_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>restaurant_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>mission_id (FK)</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>point</td>
<td>bigint</td>
<td></td>
</tr>
<tr>
<td>created_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
<tr>
<td>updated_at</td>
<td>datetime(6)</td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chapter 0. 서버 처음 해보기]]></title>
            <link>https://velog.io/@arin_0303/Chapter-0.-%EC%84%9C%EB%B2%84-%EC%B2%98%EC%9D%8C-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@arin_0303/Chapter-0.-%EC%84%9C%EB%B2%84-%EC%B2%98%EC%9D%8C-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 26 Dec 2025 11:57:30 GMT</pubDate>
            <description><![CDATA[<p>이 그림은 인터넷 통신이 어떤 식으로 구성되는지 나타낸 것이다.</p>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/2f8c0b27-08e3-4f05-aae6-104b20f2da27/image.png" alt=""></p>
<p>인터넷 통신은 end system 간 패킷을 주고받는 방식으로 이루어진다.<br>클라이언트(end system)는 요청을 보낼 때 패킷에 최종 목적지의 IP 주소를 담아 네트워크로 전송한다.</p>
<p>패킷을 받은 라우터는 패킷 안에 있는 목적지 IP 주소를 확인한 후,<br>라우팅 테이블을 참조하여 다음 홉(next hop)으로 패킷을 전달한다.</p>
<p>패킷이 최종 목적지 호스트(end system)에 도달하면<br>Network Layer에서 Transport Layer로 전달되고,<br>최종적으로 애플리케이션이 데이터를 처리한다.</p>
<hr>
<h3 id="osi-7-layer-참고">OSI 7 Layer 참고</h3>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/3f6698b2-287e-4553-a2b0-a2c3ebd9f3cb/image.png" alt=""></p>
<p>같은 IP 내에서 여러 프로세스를 구분하기 위해<br>Transport Layer는 포트 번호(port number)를 사용한다.</p>
<p>같은 IP 주소를 사용하더라도  </p>
<ul>
<li>port 80  </li>
<li>port 443  </li>
<li>port 3306  </li>
</ul>
<p>과 같이 포트 번호에 따라 서로 다른 애플리케이션 동작을 수행한다.</p>
<hr>
<p>인터넷 통신 과정에서는 여러 문제가 발생할 수 있다.</p>
<ol>
<li><strong>만약 상대의 컴퓨터가 꺼져 있다면?</strong>  </li>
<li><strong>만약 중간에 패킷이 소실된다면?</strong>  </li>
<li><strong>뒤에 보낸 패킷이 먼저 도착한다면?</strong></li>
</ol>
<blockquote>
<p>이러한 문제들은 TCP를 통해 해결할 수 있다.</p>
</blockquote>
<hr>
<h2 id="tcp">TCP</h2>
<h3 id="1-상대의-컴퓨터가-꺼져-있다면">1. 상대의 컴퓨터가 꺼져 있다면?</h3>
<p>최종 목적지의 컴퓨터가 꺼져 있다면 패킷을 받을 수 없다.<br>따라서 패킷을 보내기 전에 클라이언트는<br>목적지 IP의 특정 포트에서 TCP 프로세스가<br>연결을 수락할 수 있는 상태인지 확인한다.</p>
<blockquote>
<p>이때 사용되는 과정이 3-way handshake이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/b7390f89-694b-4d54-8a49-3a53cc3dff39/image.png" alt=""></p>
<p>클라이언트는 서버에 SYN 패킷을 전송하고,<br>서버의 응답에 대해 ACK 패킷을 전송하면<br>3-way handshake가 완료된다.</p>
<p>이를 통해 서로 통신을 시작할 준비가 되었음이 확인된다.</p>
<hr>
<h3 id="2-중간에-패킷이-소실된다면">2. 중간에 패킷이 소실된다면?</h3>
<p>TCP는 ACK를 통해 수신 여부를 확인한다.<br>일정 시간 동안 ACK가 도착하지 않으면<br>해당 패킷이 소실되었다고 판단하고 재전송을 수행하여<br>데이터 전달을 보장한다.</p>
<hr>
<h3 id="3-뒤에-보낸-패킷이-먼저-도착한다면">3. 뒤에 보낸 패킷이 먼저 도착한다면?</h3>
<p>패킷의 크기가 큰 경우 데이터는 여러 개로 쪼개져 전송되며,<br>이 과정에서 뒤에 보낸 패킷이 먼저 도착할 수 있다.</p>
<p>TCP는 이 문제를 sequence number로 해결한다.<br>각 패킷에 순서 번호를 부여하여 전송하고,<br>수신 측은 이를 기반으로 데이터를 재정렬한다.</p>
<p>만약 중간에 누락된 패킷이 있다면<br>해당 패킷에 대해 재전송을 요청하여 문제를 해결한다.</p>
<hr>
<h2 id="udp">UDP</h2>
<blockquote>
<p>UDP는 신뢰성보다 속도를 우선하는 프로토콜이다.</p>
</blockquote>
<p>UDP는 TCP와 달리  </p>
<ul>
<li>3-way handshake  </li>
<li>데이터 전달 보증  </li>
<li>순서 보장  </li>
</ul>
<p>을 제공하지 않는다.</p>
<p>기존 IP 위에 포트 번호와 체크섬 등<br>최소한의 헤더만 추가한 프로토콜로,<br>TCP에 비해 신뢰성은 낮지만 빠르다는 장점이 있다.</p>
<hr>
<blockquote>
<p>정리하면,<br>IP는 전달을 담당하고<br>TCP는 신뢰성을 보장하며<br>UDP는 속도를 우선한다.</p>
</blockquote>
<h2 id="web-server와-was">Web Server와 WAS</h2>
<h3 id="web-server">Web Server</h3>
<p>Web Server는 html, css, 이미지와 같은 정적 리소스를 처리해주는 서버를 말한다. 대표적으로는 Apache, Nginx 등이 있다. </p>
<h3 id="was">WAS</h3>
<p>WAS는 DB 조회, 로직 처리와 같은 동적 리소스를 처리해주는 서버를 말한다. 대표적으로는 Spring Boot의 내장 서버인 Tomcat이 있다. </p>
<blockquote>
<p>WAS는 동적 리소소뿐만 아니라 정적 리소스까지 처리할 수 있다. 그러나 현재 Web Server를 많이 쓰고있다. </p>
</blockquote>
<p>왜그럴까? </p>
<p>이는 비용, 보안 측면에서 여러 이유가 있다. 이건 추후에 다뤄보도록 하겠다. </p>
<h2 id="실습-인증">실습 인증</h2>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/755a48d7-827e-4659-b053-be3a9c54490e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/arin_0303/post/768de5ea-dfc0-4015-ae71-c54532b9b254/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 20055_컨베이어 벨트 위의 로봇 ]]></title>
            <link>https://velog.io/@arin_0303/%EB%B0%B1%EC%A4%80-20055%EC%BB%A8%EB%B2%A0%EC%9D%B4%EC%96%B4-%EB%B2%A8%ED%8A%B8-%EC%9C%84%EC%9D%98-%EB%A1%9C%EB%B4%87</link>
            <guid>https://velog.io/@arin_0303/%EB%B0%B1%EC%A4%80-20055%EC%BB%A8%EB%B2%A0%EC%9D%B4%EC%96%B4-%EB%B2%A8%ED%8A%B8-%EC%9C%84%EC%9D%98-%EB%A1%9C%EB%B4%87</guid>
            <pubDate>Fri, 05 Dec 2025 09:46:51 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/arin_0303/post/ed61fa02-4a8a-4caf-9d4a-d4708c23779e/image.png" style="margin: 10px;">

<img src="https://velog.velcdn.com/images/arin_0303/post/af5c7f34-57b6-4312-9a30-e84930faa798/image.png" style="margin: 10px;">

<img src="https://velog.velcdn.com/images/arin_0303/post/adcf5148-d8a2-4a5e-beca-38d93652ee67/image.png" style="margin: 10px;">

<p><a href="https://www.acmicpc.net/problem/20055" target="_blank">🔗문제 링크 바로가기</a></p>
<p>이 문제는 질문 이해가 관건인 것같다. 본문만 이해하면 구현하는건 크게 무리가 없는 것같다. 
아래에 내가 헷갈렸던 부분을 정리해놨다. </p>
<ul>
<li>로봇이 올라갈 때마다 해당 칸의 내구도가 1씩 감소한다.</li>
<li>로봇은 뒤쪽 컨베이어벨트 칸에 올라갈 수 없다. 따라서 로봇은 0~(N-1)번째 칸에만 존재할 수 있다. 따라서 로봇이 (N-1)번째 칸에 올라가면 내려줘야한다. 왜그러냐고 물으면 이유는 없다. 그냥 문제 조건일 뿐이다...</li>
</ul>
<p><strong>&lt;각 단계&gt;</strong>
    - 1단계: 벨트가 회전하면 로봇도 그에 따라 회전한다. 
    - 2단계: 앞 칸이 비어있으면 로봇은 스스로 이동한다. 
    - 3단계: 0번째 칸에 로봇을 올린다(올릴 수 있으면)</p>
<ul>
<li>각 단계별로 회전을 무한대로 하는 것이 아니라 1단계에서 1칸씩 이동하면 2단계로 넘어가서 이동할 수 있는 로봇들은 1칸씩 이동한다. 그리고 3단계로 넘어가 하나의 로봇을 올리는 것이다. </li>
</ul>
<pre><code class="language-python">from collections import deque
import sys
input = sys.stdin.readline

N, K = map(int, input().split())
A = deque(list(map(int, input().split()))) # 내구도
robots = deque([0] * N) # 로봇 위치(앞쪽 N칸만)

step = 0

while True:
    step += 1

    # 1. 벨트 + 로봇 회전
    A.rotate(1) # 벨트 회전
    robots.rotate(1) # 벨트가 회전함에 따라 로봇도 같이 회전
    robots[-1] = 0 # 내리는 위치에서 로봇 제거

    # 2. 로봇 이동(앞 칸이 비어있으면 스스로 이동)
    for i in range(N-2, -1, -1): # 뒤에서부터
        if robots[i] == 1 and robots[i+1] == 0 and A[i+1] &gt; 0:
            robots[i] = 0
            robots[i+1] = 1
            A[i+1] -= 1

    robots[-1] = 0 # 내리는 위치에서 로봇 제거

    # 3. 올리는 칸에 로봇 올리기
    if A[0] &gt; 0 and robots[0] == 0:
        robots[0] = 1
        A[0] -= 1

    # 4. 내구도 0인 칸의 개수 세기
    if A.count(0) &gt;= K:
        break

print(step)

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[컨테이너 restarting 상태 해결하기]]></title>
            <link>https://velog.io/@arin_0303/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-restarting-%EC%83%81%ED%83%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@arin_0303/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-restarting-%EC%83%81%ED%83%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Wed, 03 Dec 2025 16:31:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/arin_0303/post/88ca79c1-51fb-4e45-b8f4-859e96138b4b/image.png" alt=""></p>
<h3 id="문제">문제</h3>
<p>morgan 라이브러리를 추가하고 main에 반영했더니 깃헙 Action에서 컨테이너가 unhealthy해서 배포가 실패했다고 떴다.
<img src="https://velog.velcdn.com/images/arin_0303/post/9cccf7a9-e0c6-407f-bd99-caa0eb2e3962/image.png" alt=""></p>
<p>ssh 접속하여 <code>docker ps</code>로 확인해보니 컨테이너 상태가 <code>Restarting</code>으로 표시되었다. 원래는 <code>healthy</code>로 표시되어야 한다. 
<img src="https://velog.velcdn.com/images/arin_0303/post/3e2176c8-96b7-4b51-85be-4b519390f786/image.png" alt=""></p>
<h3 id="원인">원인</h3>
<p>내가 작성한 docker-compose.yml를 보면, 사용자가 수동으로 stop하기 전까지 컨테이너를 재시작하라는 명령어 때문에 계속 restart 상태였던 것같다. 
<img src="https://velog.velcdn.com/images/arin_0303/post/22752eb3-a5f3-4be2-a7dc-78f15083b691/image.png" alt="">
자세한 로그를 살펴보기 위해 <code>sudo docker logs myapp</code>로 로그를 출력해보니 morgan 모듈을 찾지 못했다는 에러가 떴다.
<img src="https://velog.velcdn.com/images/arin_0303/post/e04093a1-8009-4f68-8da1-3049ef015000/image.png" alt=""></p>
<p>내가 코드에서 morgan을 사용했고, 서버는 해당 모듈을 node_modules에서 찾으려 했는데 devDependencies로만 설치하고 production 환경에서는 설치가 안되어서 서버가 찾지 못한 것이다.
이 에러 때문에 프로세스가 종료되고 <strong>컨테이너가 계속 restart 하려고 했던 것이다.</strong></p>
<h3 id="해결">해결</h3>
<p>dev용 모듈과 production용 모듈을 구분하여 설치한다. </p>
<pre><code class="language-bash">pnpm install express morgan
pnpm install @types/morgan --save-dev</code></pre>
<p>만약 해당 모듈을 사용하는 코드를 배포 하고싶다면, 첫 번째 명령어로 설치해야한다. 
기존에는 두 번째 명령어만 작성해서 인식을 못했던 것이다.</p>
]]></description>
        </item>
    </channel>
</rss>