<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>developer-jyyun.log</title>
        <link>https://velog.io/</link>
        <description>쑥쑥쑥쑥 레벨업🌱🌼🌳</description>
        <lastBuildDate>Fri, 03 May 2024 07:09:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>developer-jyyun.log</title>
            <url>https://velog.velcdn.com/images/developer-jyyun/profile/1ecd6cff-3387-435b-a78a-c51471c34d7c/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. developer-jyyun.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/developer-jyyun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[s3 setting]]></title>
            <link>https://velog.io/@developer-jyyun/s3-setting</link>
            <guid>https://velog.io/@developer-jyyun/s3-setting</guid>
            <pubDate>Fri, 03 May 2024 07:09:29 GMT</pubDate>
            <description><![CDATA[<p>###</p>
<h3 id="권한-편집">권한 편집</h3>
<p>권한&#39; 눌러서 &#39;버킷 정책&#39; </p>
<pre><code class="language-js">{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;1&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:GetObject&quot;, //모든 유저는 GET 가능
            &quot;Resource&quot;: &quot;arn:aws:s3:::버킷명/*&quot;
        },
        {
            &quot;Sid&quot;: &quot;2&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: {
                &quot;AWS&quot;: &quot;arn:aws:iam::AWS계정ID:root&quot; // 관리자 계정은 PUT, DELETE 가능
            },
            &quot;Action&quot;: [
                &quot;s3:PutObject&quot;,
                &quot;s3:DeleteObject&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:s3:::버킷명/*&quot;
        }
    ]
} </code></pre>
<ul>
<li>자료 읽기 : s3:GetObject</li>
<li>쓰기 : s3:PutObject</li>
<li>삭제 : s3:DeleteObject</li>
</ul>
<ul>
<li><p>Principal은 어떤 유저인지 명시하는 부분인데 * 쓰면 모든유저,
특정 AWS 계정을 넣고 싶으면 arn:aws:iam::AWS계정ID:root 넣기
계정 ID는 AWS 사이트 오른쪽 위 숫자</p>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/a5d9e36f-33aa-4c88-824c-97e54d8afafc/image.png" alt=""></p>
</li>
<li><p>Resource에는 버킷명 적기 </p>
</li>
</ul>
<h3 id="corscross-origin-리소스-공유-편집">CORS(Cross-origin 리소스 공유) 편집</h3>
<pre><code class="language-js">[
    {
        &quot;AllowedHeaders&quot;: [
            &quot;*&quot;
        ],
        &quot;AllowedMethods&quot;: [// PUT,POST 허용
            &quot;PUT&quot;,
            &quot;POST&quot;
        ],
        &quot;AllowedOrigins&quot;: [// 모든 도메인
            &quot;*&quot;
        ],
        &quot;ExposeHeaders&quot;: [
            &quot;ETag&quot;
        ]
    }
] </code></pre>
<h3 id="액세스-키-발급">액세스 키 발급</h3>
<p>AM 대시보드로 이동 -&gt; 액세스 키 OR 우측 상단 토글 보안 자격 증명
액세스 키 만들기 버튼 클릭하면 액세스 키, 비밀 액세스 키가 발급된다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ReferenceError: location is not defined]]></title>
            <link>https://velog.io/@developer-jyyun/ReferenceError-location-is-not-defined</link>
            <guid>https://velog.io/@developer-jyyun/ReferenceError-location-is-not-defined</guid>
            <pubDate>Thu, 02 May 2024 09:18:48 GMT</pubDate>
            <description><![CDATA[<h2 id="👿-error">👿 error</h2>
<pre><code>&gt; board@0.1.0 build
&gt; next build       

   ▲ Next.js 14.1.3
   - Environments: .env.local

   Creating an optimized production build ...
 ✓ Compiled successfully
   Linting and checking validity of types  .. ⨯ ESLint: Failed to load config &quot;next/babel&quot; to extend from. Referenced from: E:\boot_camp\next\board\.eslintrc.json
 ✓ Linting and checking validity of types
 ✓ Collecting page data    
   Generating static pages (11/16) [==  ]
ReferenceError: location is not defined
    at E:\boot_camp\next\board\.next\server\chunks\621.js:1:27100
    at E:\boot_camp\next\board\.next\server\chunks\621.js:1:27874
    at t.startTransition (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:108548)
    at Object.push (E:\boot_camp\next\board\.next\server\chunks\621.js:1:27862)
    at d (E:\boot_camp\next\board\.next\server\app\write\page.js:1:3643)
    at nM (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:47419)
    at nL (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:64674)
    at nI (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:46806)
    at nM (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:47570)
    at nM (E:\boot_camp\next\board\node_modules\next\dist\compiled\next-server\app-page.runtime.prod.js:12:61663)
Error occurred prerendering page &quot;/write&quot;. Read more: https://nextjs.org/docs/messages/prerender-error
</code></pre><h2 id="✨-해결">✨ 해결</h2>
<p>기존 router.push 로직을 useEffect로 감싸주었다.
=&gt; 이렇게 하면 router.push 등의 코드가 서버 측에서 오류를 발생시키지 않고 useEffect는 컴포넌트가 렌더링된 후에 클라이언트 측에서만 실행되므로, 이 타이밍에 클라이언트 측 전용 로직을 안전하게 실행할 수 있다.</p>
<p>감사합니당..<a href="https://www.reddit.com/r/nextjs/comments/11fuwys/nextjs_13_referenceerror_location_is_not_defined/">BavarianDuck89</a>..💖</p>
<pre><code class="language-js">// 기존
 if (!session) {
      router.push(&quot;/api/auth/signin&quot;);
    }

// 변경
  useEffect(() =&gt; {
    if (!session) {
      router.push(&quot;/api/auth/signin&quot;);
    }
  }, [session, router]);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JAVA 🔥🔥 정리 및 오답노트 🔥🔥]]></title>
            <link>https://velog.io/@developer-jyyun/JAVA-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8</link>
            <guid>https://velog.io/@developer-jyyun/JAVA-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8</guid>
            <pubDate>Mon, 22 Apr 2024 16:05:50 GMT</pubDate>
            <description><![CDATA[<h2 id="기본-개념정리">기본 개념정리</h2>
<ul>
<li><h4 id="print-줄바꿈❌❌❌--println--줄바꿈⭕⭕⭕"><code>print</code> 줄바꿈❌❌❌,  <code>println</code> : 줄바꿈⭕⭕⭕!!!!</h4>
</li>
<li><h4 id="띄어쓰기-여부-확인"><strong>띄어쓰기 여부 확인!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</strong></h4>
</li>
<li><h4 id="생성자">생성자</h4>
<p>생성자는 객체를 생성할 때 호출되는 특별한 메서드</p>
</li>
<li><h4 id="상속-관계에-있을-때-생성자-호출">상속 관계에 있을 때 생성자 호출</h4>
<p>자바에서 상속 관계에 있을 때 생성자를 호출하면, 자식 클래스의 생성자 실행에 앞서 부모 클래스의 생성자가 먼저 호출된다. super를 사용해 명시적으로 부모 클래스의 생성자를 지정할 수 있으며, 이 경우 지정된 부모 생성자가 실행된다. 명시적으로 부모 생성자를 호출하지 않으면, 묵시적으로 인자 없는 부모 클래스의 기본 생성자가 자동으로 호출된다. 
부모 클래스의 생성자가 실행을 마치고 나면, 자식 클래스의 생성자로 돌아와서 코드를 계속 실행한다.</p>
</li>
<li><h3 id="오버-로딩">오버 로딩</h3>
</li>
<li><p><em>같은 이름의 메서드*</em>를 <strong><code>매개변수</code>의 개수 또는 타입에 따라 여러번 정의</strong>하는 것</p>
<pre><code class="language-java"> class Calculator {
     // add 메서드, 오버로딩을 사용하여 정의
     int add(int a, int b) {
         return a + b;
     }

     // 매개변수의 개수가 다른 add 메서드
     int add(int a, int b, int c) {
         return a + b + c;
     }    
   }

 Calculator calc = new Calculator();
 System.out.println(calc.add(5, 10));        // 15
 System.out.println(calc.add(5, 10, 15));   // 30</code></pre>
<ul>
<li><h3 id="오버-라이딩">오버 라이딩</h3>
</li>
</ul>
</li>
<li><p><em><code>상속 관계</code>*</em> 에서 부모 클래스의 메서드를 <strong>자식 클래스가 재정의</strong> 하는 것. (메서드명, 매개변수의 타입과 개수, 반환 타입은 동일해야 함)</p>
<pre><code class="language-java"> class Animal {
   void sound() {
         System.out.println(&quot;동물 소리&quot;);
     }
 }

 class Dog extends Animal {
     // Animal 클래스의 sound 메서드 오버라이딩
     void sound() {
         System.out.println(&quot;멍멍&quot;);
     }
 }
 Animal dog = new Dog();
dog.sound();  // 멍멍</code></pre>
</li>
<li><h3 id="하이딩">하이딩</h3>
<p>상위 클래스의 <strong><code>static</code></strong> 메서드를 하위 클래스에서 같은 유형으로 <strong>다시 선언</strong>하는 것. 하이딩된 메서드는 메서드 호출이 클래스 <strong><code>타입</code>에 따라 결정</strong>된다. (상속 관계에서만 발생하며 정적 메서드에 한정된 특성)</p>
<pre><code class="language-java">class Base {
    static void display() {
        System.out.println(&quot;부모 클래스의 디스플레이 메서드&quot;);
    }
}

class Derived extends Base {
    // Base 클래스의 display 메서드 히딩
    static void display() {
        System.out.println(&quot;자식 클래스의 디스플레이 메서드&quot;);
    }
}
Base.display();      // 부모 클래스의 디스플레이 메서드
Derived.display();   // 자식 클래스의 디스플레이 메서드</code></pre>
</li>
<li><h4 id="오버라이딩-vs-하이딩">오버라이딩 vs 하이딩</h4>
<pre><code class="language-java">class A {
  static void f() { System.out.print(&quot;1 &quot;); } // 부모 클래스의 정적 메서드
  void g() { System.out.print(&quot;2 &quot;); } // 부모 클래스의 인스턴스 메서드
}
class B extends A {
  static void f() { System.out.print(&quot;3 &quot;); } // 하이딩: A 클래스의 f() 메서드를 하이딩
  void g() { System.out.print(&quot;4 &quot;); } // 오버라이딩: A 클래스의 g() 메서드를 오버라이딩
}
class C {
  public static void main(String[] args) {
      A a = new B();
      a.f(); // 하이딩된 메서드 호출, A의 f()가 호출됨
      a.g(); // 오버라이딩된 메서드 호출, B의 g()가 호출됨
  }
}</code></pre>
</li>
<li><h3 id="추상-클래스-abstract">추상 클래스 (abstract)</h3>
<pre><code class="language-java">// 추상 클래스 정의
abstract class 동물 {
  abstract void 울다();  // 추상 메서드, 구현되지 않음

  // 추상 클래스 내에서도 일반 메서드 구현이 가능
  void 숨쉬다() {
      System.out.println(&quot;숨을 쉽니다.&quot;);
  }
}
// 추상 클래스를 상속받은 구체적인 클래스 정의
class 개 extends 동물 {
  // 추상 메서드 &#39;울다&#39; 구현
  void 울다() {
      System.out.println(&quot;멍멍!&quot;);
  }
}
class 고양이 extends 동물 {
  // 추상 메서드 &#39;울다&#39; 구현
  void 울다() {
      System.out.println(&quot;야옹!&quot;);
  }
}
public class 추상클래스예제 {
  public static void main(String[] args) {
      개 개 = new 개();  // &#39;개&#39; 클래스의 인스턴스 생성
      개.울다();  // 구현된 메서드 호출
      개.숨쉬다();  // 상속받은 일반 메서드 호출

      고양이 고양이 = new 고양이();  // &#39;고양이&#39; 클래스의 인스턴스 생성
      고양이.울다();  // 구현된 메서드 호출
      고양이.숨쉬다();  // 상속받은 일반 메서드 호출
  }
}


</code></pre>
</li>
</ul>
<ul>
<li>하나 이상의 추상 메서드(abstract method)를 포함하는 클래스로, 이 메서드들은 선언만 있고 구현이 없어서, <strong>이를 상속받는 하위 클래스에서 반드시 구현해야 한다.</strong></li>
<li>추상 클래스 자체로는 인스턴스를 생성할 수 없다</li>
</ul>
<ul>
<li><h3 id="인터페이스">인터페이스</h3>
<ul>
<li>모든 메서드가 기본적으로 추상 메서드인 형태 =&gt; 객체 생성 불가</li>
<li>구현된 상태 없이 전부 시그니처(선언)만 있는 구조</li>
<li>implements와 함께 쓰인다! <del><strong><em>(extends 아니야!!!!!!)</em></strong></del><h2 id="실수-double-float">실수 double, float</h2>
</li>
<li>Java에서는 유형을 지정하지 않고 십진수를 사용하면 기본적으로 <code>double</code>로 처리
(명시적으로 float로 만들려면 24.8f와 같이 숫자 뒤에 f 또는 F를 추가해야 함)<pre><code class="language-java"></code></pre>
</li>
</ul>
<p>class Calculate {<br>   public int cal(int a, int b) {</p>
<pre><code>   return a - b; // a와 b의 차를 반환</code></pre><p>   }</p>
<p>   // 두 실수(float)의 덧셈을 수행하는 메서드
   public float cal(float a, float b) {</p>
<pre><code>   return a + b; // a와 b의 합을 반환</code></pre><p>   }</p>
<p>   // 두 실수(double)의 덧셈을 수행하는 메서드
   public double cal(double a, double b) {</p>
<pre><code>   return a + b; // a와 b의 합을 반환</code></pre><p>   }</p>
<p>   // 세 정수의 덧셈을 수행하는 메서드
   public int cal(int a, int b, int c) {</p>
<pre><code>   return a + b + c; // a, b, c의 합을 반환</code></pre><p>   }
}</p>
<p>class Example {
   public static void main(String[] args) {</p>
<pre><code>   Calculate a = new Calculate(); // &#39;Calculate&#39; 클래스의 인스턴스 생성

   System.out.println(a.cal(31, 69, 25)); // 출력: 125

   // 실수(double) 덧셈 메소드 호출 및 결과 출력
   System.out.println(a.cal(24.8, 5.1)); // 출력: 29.9</code></pre><p>   }
}</p>
<pre><code>
</code></pre></li>
</ul>
<h2 id="사용된-기법">사용된 기법</h2>
<pre><code class="language-java">
class Adder {
    // 오버로딩(Overloading): 같은 이름의 메서드를 매개변수의 유형과 개수를 다르게 해서 여러 개 정의
    public int add(int a, int b) { return a+b; } // 정수 덧셈
    public double add(double a, double b) { return a+b; } // 실수 덧셈
}

// Computer 클래스: Adder 클래스를 상속(Inheritance)받아 새로운 기능을 추가
class Computer extends Adder {
    // 캡슐화(Encapsulation) 및 정보 은닉(Information Hiding): 변수 x를 private로 선언하여 외부 접근 제한    
    private int x;

    // 오버로딩된 add 메서드: 매개변수가 세 개인 정수 덧셈
    public int cal(int a, int b, int c) {
        if (a == 1) return add(b, c); // 상속받은 Adder의 add 메서드 사용
        else return x;
    }
}

class Adder_Main {
    public static void main(String[] args) { 
        Computer c = new Computer();

        // 오버로딩된 메서드 호출: int 형식과 double 형식에 맞게 add 메서드가 호출됨
        System.out.println(&quot;100 + 200 = &quot; + c.add(100, 200)); 
        System.out.println(&quot;5.7 + 9.8 = &quot; + c.add(5.7, 9.8));
    }
}</code></pre>
<h2 id="변수의-유효범위">변수의 유효범위</h2>
<pre><code class="language-java">  // 기본 클래스 X 정의
class X {
    int i;
    X() { i = 10; }  // 생성자에서 i를 10으로 초기화
    void print() { System.out.print(i+&quot;, &quot;); }  // i값 출력
}

// X를 상속받는 클래스 Y 정의
class Y extends X {
    int i;  // 클래스 X와 동일한 이름의 i 변수를 가짐
    int j;
    Y() { 
        i = 15;  // Y의 i를 15로 초기화
        j = 20;  // Y의 j를 20으로 초기화
    }
    void print() { System.out.print(j+&quot;, &quot;); }  // Y의 j값 출력
    void superprint() { super.print(); }  // X의 print() 호출
}

// Y를 상속받는 클래스 Z 정의
class Z extends Y {
    int k;
    Z() {
        super();  // 부모 클래스 Y의 생성자 호출
        k = 30;  // Z의 k를 30으로 초기화
    }
    void print() { System.out.print(k+&quot;, &quot;); }  // Z의 k값 출력
    void test() {
        print();  // Z의 print() 호출
        superprint();  // Y의 print() 호출
        System.out.print(super.j+&quot;, &quot;);  // Y의 j값 출력
        System.out.println(i);  // Z의 i값 출력 (여기서는 Y의 i가 출력됨)
    }
}

// 메인 클래스와 메인 메서드 정의
public class Main {
    public static void main(String[] args) {
        Z z = new Z();  // Z 클래스의 인스턴스 생성
        z.test();  // Z의 test() 메서드 호출
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[C언어 🔥오답노트🔥  ]]></title>
            <link>https://velog.io/@developer-jyyun/C%EC%96%B8%EC%96%B4-%EC%A3%BC%EC%9D%98</link>
            <guid>https://velog.io/@developer-jyyun/C%EC%96%B8%EC%96%B4-%EC%A3%BC%EC%9D%98</guid>
            <pubDate>Sun, 21 Apr 2024 16:39:22 GMT</pubDate>
            <description><![CDATA[<ul>
<li><h2 id="sizeof-사용하여-배열의-크기-계산하기">sizeof() 사용하여 배열의 크기 계산하기</h2>
<blockquote>
<ul>
<li>🔥  문자열 배열의 마지막엔 <code>null값</code> 들어간다!!사이즈 계산 시 <code>null값</code> 포함해주자!</li>
</ul>
</blockquote>
<ul>
<li>🔥 int : 4byte !!!!</li>
<li>🔥 char : 1byte !!!!
<img src="https://velog.velcdn.com/images/developer-jyyun/post/2ae0ca2a-77e9-4c97-894c-65e4087b9b86/image.png" alt=""></li>
</ul>
<pre><code class="language-c">// 흥달쌤 프로그래밍 특강 c언어 46번
#include &lt;stdio.h&gt;

int main() {
  char ch[5];
  char str[] = &quot;abcde&quot;;
  int num[] = {1, 2, 3, 4, 5};
  printf(&quot;%d, &quot;, sizeof(ch));
  printf(&quot;%d, &#39;, sizeof(str));
  printf(&quot;%d\n&quot;, sizeof(num)/sizeof(int));
  return 0;
  }</code></pre>
<details>
<summary>해설</summary>
  <div>
    - sizeof(ch): 5
  char ch[5] : 5개의 공간 가진 배열 

<ul>
<li><p>sizeof(str) : 6
char str[]: &quot;abcde&quot;; 
| a | b | c | d | e | \0 |
✨ 문자열 배열에는 <em><strong>마지막에 <code>null문자</code>가 포함되어</strong></em> length가 6 </p>
<ul>
<li>sizeof(num)/sizeof(int) : 5
int num[] = | 1 | 2 | 3 | 4 | 5 | 
sizeof(num) = 4byte*5 = 20byte
sizeof(int) = 4byte</li>
</ul>
<p>정답 : 5, 6, 5</p>
</div>
</details>
</li>
</ul>
</li>
<li><h2 id="문자열-포인터">문자열 포인터</h2>
<blockquote>
</blockquote>
<ul>
<li>printf 함수가 <strong><code>%s</code></strong>를 사용하여 문자열을 출력할 때, 함수는 <strong>주어진 메모리 주소에서 시작해서 null 문자를 만날 때까지 연속된 문자들</strong>을 출력한다.</li>
<li>printf 함수가  <strong><code>%c</code></strong>를 사용하여 문자 출력할 때, <strong>해당 주소의 단일 문자</strong>만을 출력한다.<ul>
<li>포인터 p가 가리키는 값이 숫자일 경우에도 printf 함수의 출력 타입을 반드시 확인하자!!! <strong><code>%c</code></strong>일 경우 <strong>아스키 코드로 변환</strong>해주기!!</li>
</ul>
</li>
</ul>
<pre><code class="language-c">// 흥달쌤 프로그래밍 특강 c언어 54번
#include &lt;stdio.h&gt;
int main(void){
    int code = 65;
    int *p = &amp;code;
    printf(&quot;%c&quot;, (*p)++);
    return 0   
}</code></pre>
<pre><code>포인터 p가 가리키는 값 (*p) : 65
%c : 단일 문자 출력. 65 ➡ A</code></pre><blockquote>
<p><strong>_ 🔥🔥 아래 두개 차이 확인하기!!!!!!! 🔥🔥_</strong></p>
<ul>
<li>printf(&quot;%c\n&quot;, <code>*(p + 3)</code>); 
➡ 포인터 p에서 3칸 이동한 위치의 문자 출력</li>
<li>printf(&quot;%c\n&quot;, <code>*p + 2</code>); 
➡ 포인터 p가 가리키는 첫 번째 문자의 아스키 값에 2를 더한 값 출력</li>
</ul>
</blockquote>
<pre><code class="language-c">// 흥달쌤 프로그래밍 특강 c언어 56번
#include &lt;stdio.h&gt;
int main(void) {
    char *p = &quot;KOREA&quot;;
    printf(&quot;%s\n&quot;, p); // &quot;KOREA&quot; 문자열 전체를 출력
    printf(&quot;%s\n&quot;, p + 3); // 포인터 p에서 3칸 이동한 &quot;EA&quot; 부분부터 문자열을 출력
    printf(&quot;%c\n&quot;, *p); // 포인터 p가 가리키는 첫 번째 문자 &#39;K&#39;를 출력
    printf(&quot;%c\n&quot;, *(p + 3)); // 포인터 p에서 3칸 이동한 위치의 문자 &#39;E&#39;를 출력
    printf(&quot;%c\n&quot;, *p + 2); // 포인터 p가 가리키는 첫 번째 문자 &#39;K&#39;의 아스키 값에 2를 더한 값 &#39;M&#39;을 출력
    return 0;
}
</code></pre>
<p><em><strong>*p + 2</strong>는 포인터 p의 값(*P=K) &#39;K&#39;의 아스키 코드에 2를 더한 값에 해당하는 문자를 출력한다. &#39;K&#39;의 아스키 코드는 75이므로, 여기에 2를 더하면 77이고, 이는 &#39;M&#39;에 해당한다. 따라서 &#39;M&#39;이 출력됨!
K+2 = M<br>K (75) L (76) M (77)</em></p>
</li>
<li><h2 id="포인터--전치후치-연산자">포인터 + 전치/후치 연산자</h2>
<p>참고) <a href="https://cafe.naver.com/sosozl/5281">https://cafe.naver.com/sosozl/5281</a></p>
<blockquote>
<ul>
<li><code>a = *p++</code> : a = p의 값 가져옴.  이후 <code>p= p+1</code><strong>(주소+1)</strong></li>
</ul>
</blockquote>
<ul>
<li><code>b = (*p)++</code> : b = p의 값 가져옴.  이후 <code>*p = *p+1</code><strong>(p의 값+1)</strong></li>
<li><code>*p++ = 10</code> : p의 값에 10 넣고, 이후 <code>p=p+1</code><strong>(주소+1)</strong></li>
<li><code>*p+=10</code> : <code>*p=*p+10</code></li>
<li><code>*p++ += 10</code> : <code>*p=*p+10</code><strong>(p의 값+10)</strong> 이후 <code>p= p+1</code><strong>(주소+1)</strong></li>
</ul>
</li>
</ul>
<ul>
<li><h2 id="gets">gets()</h2>
<p>사용자로부터 문자열 입력받아 저장할 배열의 주소를 인자값으로 받는다.
// 홍길동, 김철수, 박영희 순으로 입력받았다고 가정했을 때 아래 코드의 결과: </p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
char n[30]; // 전역 배열 n 선언. 최대 29개의 문자와 널 종료 문자(&#39;\0&#39;)를 저장할 수 있음.

// getname(): 사용자로부터 문자열을 입력받아 n 배열에 저장하고, n의 주소를 반환하는 함수
char* getname(){
  gets(n); // 표준 입력으로부터 문자열을 받아 n에 저장
  return n; // n의 주소 반환
}

int main() {
  char* n1 = getname(); // 첫 번째 사용자 입력을 받아 n1이 가리킴
  char* n2 = getname(); // 두 번째 사용자 입력을 받아 n2가 가리킴
  char* n3 = getname(); // 세 번째 사용자 입력을 받아 n3이 가리킴

  // n1, n2, n3이 모두 같은 n 배열의 주소를 가리키므로, 마지막으로 입력된 문자열을 출력함
  printf(&quot;%s\n&quot;, n1);
  printf(&quot;%s\n&quot;, n2);
  printf(&quot;%s\n&quot;, n3);

  return 0; // 프로그램 종료
}</code></pre>
<ul>
<li><p>n1, n2, n3 모두 같은 배열 n을 가리키게 된다. </p>
</li>
<li><p>getname 함수 호출 시 n 배열에 저장된 값은 새로운 입력으로 덮어쓰여진다.</p>
</li>
<li><p>&quot;홍길동&quot;을 입력한 후 n1이 이를 가리키게 되고, 다음 &quot;김철수&quot; 입력 시 n 배열에 &quot;홍길동&quot; 대신 &quot;김철수&quot;가 저장되며 n2도 이를 가리키게 된다. 마지막으로 &quot;박영희&quot;가 입력되면, &quot;김철수&quot;는 &quot;박영희&quot;로 덮어쓰여지고, n3 역시 &quot;박영희&quot;를 가리키게 된다.</p>
</li>
<li><p>최종적으로 printf로 n1, n2, n3을 출력할 때 모두 동일한 n 배열의 내용인 &quot;박영희&quot;를 출력한다. </p>
<p>답)
박영희
박영희
박영희</p>
</li>
</ul>
</li>
<li><h2 id="strstr-strncpy">strstr(), strncpy()</h2>
<blockquote>
<ul>
<li><code>strstr(pa, pb);</code> // pa가 가리키는 문자열에서 pb가 가리키는 문자열을 찾음</li>
</ul>
</blockquote>
<ul>
<li><code>strncpy(pd, &quot;77&quot;, 3);</code> // pd가 가리키는 위치부터 3개 문자를 &quot;77&quot;로 복사함. 아래 코드의 경우 &quot;PP1&quot;이 &quot;77&quot;로 바뀜</li>
</ul>
</li>
</ul>
<pre><code class="language-c">
// 배열의 시작 주소는 100이라 가정

#include &lt;stdio.h&gt;
#include &lt;string.h&gt; 

int main() {
    char list[] = &quot;22QPP1&quot;; // 문자열을 초기화
    const char *pa, *pb; // 포인터 변수를 선언
    char *pc, *pd; // 변경 가능한 포인터 변수를 선언

    pa = &amp;list[1]; // 101번지 가리킴
    list[2] = &#39;K&#39;; // 102번지 가리킴
    pb = &amp;list[3]; // 103번지 가리킴
    pc = list; // //100번지 가리킴
    pd = strstr(pa, pb); 
    // pa(주소 101에서 시작하는 &quot;2KPP1&quot;)가 가리키는 문자열에서 
    // pb(주소 103에서 시작하는 &quot;PP1&quot;)가 가리키는 문자열의 위치를 찾음

    printf(&quot;pd: %s\n&quot;, pd); 
    // pd(주소 103에서 시작하는 &quot;PP1&quot;)가 가리키는 문자열을 출력

    if (pd != 0) { // pd가 NULL이 아니라면 (즉, 문자열이 찾아졌다면)
        strncpy(pd, &quot;77&quot;, 3); // pd가 가리키는 위치(주소 103)부터 &quot;77&quot;이라는 문자열을 3개의 문자로 복사. 이 경우 &quot;PP1&quot;이 &quot;77&quot;로 변경됨
        printf(&quot;pc: %s\n&quot;, pc); // pc(주소 100에서 시작하는 &quot;22K77&quot;)가 가리키는 문자열을 출력
    }

    printf(&quot;pb: %s\n&quot;, pb); // pb(주소 103에서 시작하는 &quot;77&quot;)가 가리키는 문자열을 출력

    return 0; // 프로그램을 종료
}</code></pre>
<ul>
<li><h2 id="2차원-배열">2차원 배열</h2>
<p>🔥 <code>%p</code> : 포인터의 값을 출력</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main() {
    int a[2][3] = { {1,2,3}, {4,5,6} }; // 2차원 배열 초기화
  printf(&quot;%p\n&quot;, a); // ① 첫 번째 원소의 주소 출력, 100 
  printf(&quot;%p\n&quot;, &amp;a); // ② 배열 전체의 주소 출력, 100 
  printf(&quot;%p\n&quot;, a[0]); // ③ 첫 번째 행의 주소 출력, 100 
  printf(&quot;%p\n&quot;, *a); // ④ 첫 번째 행을 가리키는 포인터의 주소 출력, 100 
  printf(&quot;%p\n&quot;, &amp;a[0][0]); // ⑤ 첫 번째 원소의 주소 출력, 100
  printf(&quot;%p\n&quot;, a[0]+1); // ⑥ a[0][1] 주소를 가리킴.  주소 104   
  printf(&quot;%p\n&quot;, *a+1); // ⑦  a[0][1]의 주소를 가리킴. 주소 104
  printf(&quot;%p\n&quot;, &amp;a[0][0]+1); //⑧  a[0][1] 주소를 가리킴.  104
  printf(&quot;%p\n&quot;, a+1); // ⑨ 첫 번째 행 다음의 행 a[2], 즉 두 번째 행의 주소를 가리킴  112    
  printf(&quot;%p\n&quot;, *(a+1));  // ⑩ 두 번째 행의 시작 주소 112, 
  printf(&quot;%p\n&quot;, &amp;a[0]+1); // ⑪ 두 번째 행의 주소를 가리킴 a[2] 주소 112 
  printf(&quot;%d\n&quot;, **(a+1)); // ⑫  두 번째 행의 첫 번째 원소 값 출력: 4
  printf(&quot;%p\n&quot;, &amp;a+1); // ⑬ 전체 배열 다음의 주소 124

</code></pre>
</li>
</ul>
<pre><code>  return 0;</code></pre><p>  }</p>
<pre><code>![](https://velog.velcdn.com/images/developer-jyyun/post/14c2e53d-1c69-416f-9809-3509f8204a8b/image.png)



🔥
a+1 : a[1]
*(a+1) : a[1][0]
**(a+1) : 4


- ## 완전수 : 2, 28, 496
  - `완전수`란 자기 자신을 제외한 모든 양의 약수를 더하면 자기 자신이 되는 수.
  코드보고 완전수 문제 파악하기! (23년 3회 기출, 22년 3회 기출)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[DB - SQL_ 다중행 연산자(IN/ANY/ALL/UNION/UNION ALL/EXISTS/NOT EXISTS )]]></title>
            <link>https://velog.io/@developer-jyyun/DB-SQL-%EB%8B%A4%EC%A4%91%ED%96%89-%EC%97%B0%EC%82%B0%EC%9E%90INANYALLUNIONUNION-ALLEXISTSNOT-EXISTS</link>
            <guid>https://velog.io/@developer-jyyun/DB-SQL-%EB%8B%A4%EC%A4%91%ED%96%89-%EC%97%B0%EC%82%B0%EC%9E%90INANYALLUNIONUNION-ALLEXISTSNOT-EXISTS</guid>
            <pubDate>Wed, 17 Apr 2024 14:18:31 GMT</pubDate>
            <description><![CDATA[<h2 id="in">IN</h2>
<ul>
<li>** NULL 값 처리할 수 없다!!!!!!**</li>
<li>IN 연산자는 주어진 목록 내에 특정 값이 존재하는지를 확인할 때 사용된다.</li>
<li>IN 다음에 오는 괄호 안에는 비교 대상이 되는 값들의 목록이나 하위 쿼리(subquery)가 올 수 있다. </li>
<li>IN을 사용할 때, 주어진 값이 목록에 포함되어 있다면 참(true)을, 그렇지 않다면 거짓(false)을 반환한다.</li>
</ul>
<pre><code class="language-sql">SELECT 컬럼명 FROM 테이블명 WHERE 컬럼명 IN (값1, 값2, 값3, ...);</code></pre>
<p>위 쿼리는 컬럼명이 명시된 값 리스트 (값1, 값2, 값3, ...) 중 하나와 일치하는 모든 행을 찾는다.</p>
<h2 id="not-in">NOT IN</h2>
<p> 주어진 리스트 안에 포함되지 않은 값들을 필터링</p>
<pre><code class="language-sql">-- &#39;기획부&#39;와 &#39;인사부&#39;를 제외한 나머지 부서에 속한 직원들의 이름과 부서
SELECT 이름, 부서
FROM 사원
WHERE 부서 NOT IN (&#39;기획부&#39;, &#39;인사부&#39;);</code></pre>
<pre><code class="language-sql">-- 
&#39;기획부&#39;와 &#39;인사부&#39;에 속하지 않은 직원들 중에서 급여의 최댓값 검색
SELECT MAX(급여) AS 최대급여
FROM 사원
WHERE 부서 NOT IN (&#39;기획부&#39;, &#39;인사부&#39;);</code></pre>
<ul>
<li>서브 쿼리 사용하여 특정 조건에 맞는 데이터를 조회
```sql
SELECT 사원명 FROM 사원
WHERE 부서 = &#39;영업부&#39; </li>
<li><ul>
<li>영업부 소속 사원의 이름을 반환하는 조건 
AND 거주지 IN (  </li>
</ul>
</li>
<li><ul>
<li>영업부 사원 중 기획부 사원과 동일한 거주지를 가진 사원의 이름을 추가로 필터링하는 조건 
 SELECT 거주지 FROM 사원 WHERE 부서 = &#39;기획부&#39;
   -- 서브 쿼리: &#39;기획부&#39; 사원들의 거주지를 반환하는 조건
);    </li>
</ul>
</li>
<li><ul>
<li>결과 :: 기획부 사원들과 같은 거주지에 사는 영업부 사원명 조회</li>
</ul>
</li>
</ul>
<h2 id="any">ANY</h2>
<p> ANY는 결과 집합의 &#39;어떤 값이라도&#39; 조건을 만족하면 참이 된다.</p>
<pre><code class="language-sql">-- P123 프로젝트에 참여한 직원들의 급여보다 더 많은 급여를 받는 직원들의 이름과 급여를 조회
SELECT 이름, 급여
FROM 사원
WHERE 급여 &gt; ANY (
    SELECT 급여 FROM 프로젝트참여
    WHERE 프로젝트ID = &#39;P123&#39;
);</code></pre>
<h2 id="all">ALL</h2>
<p> ALL은 &#39;모든 값&#39;이 조건을 만족해야 참이 된다.</p>
<pre><code class="language-sql">-- 모든 &#39;개발부&#39; 부서 직원의 급여보다 더 많은 급여를 받는 직원들의 이름과 급여를 조회
SELECT 이름, 급여
FROM 사원
WHERE 급여 &gt; ALL (
    SELECT 급여 FROM 사원
    WHERE 부서 = &#39;개발부&#39;
);</code></pre>
<h2 id="union">UNION</h2>
<p>중복 제거한 합집합</p>
<h2 id="union-all">UNION ALL</h2>
<p>중복 제거하지 않은 합집합</p>
<h2 id="exists">EXISTS</h2>
<p>일반적으로 하위 쿼리와 함께 사용되어 특정 조건을 만족하는 데이터가 존재하는지 확인한다.
조건이 참이면 TRUE를, 거짓이면 FALSE를 반환한다.</p>
<pre><code class="language-sql">SELECT 이름
FROM 사원 A
WHERE EXISTS (
    SELECT 1 FROM 프로젝트참여 P
    WHERE P.사원번호 = A.사원번호
    AND P.프로젝트ID = &#39;P123&#39;
);</code></pre>
<ul>
<li>IN은 주어진 목록이나 하위 쿼리의 결과에 특정 열의 값이 포함되어 있는지 확인할 때 사용. (비교 대상이 되는 값이나 값들의 집합을 명확하게 알고 있을 때 사용)</li>
<li>EXISTS는 결과의 특정 값들보다는 결과의 존재 여부가 중요할 때 사용한다. 특히 하위 쿼리의 결과가 복잡하거나, 메인 쿼리의 선택된 행과 직접적인 관계가 있을 때 유용하다. (IN 보다 속도 빠름)</li>
</ul>
<h2 id="not-exists">NOT EXISTS</h2>
<blockquote>
<p> 부양 가족이 없는 직원의 이름 찾기
 [EMPLOYEE] 직원 정보 저장 릴레이션 (NAME, SSN, ADDR, PHONE)
 [DEPENDENT] 부양가족 정보 저장 릴레이션 (SSN, DEPNAME, AGE)
 ** 단 EMPLOYEE.SSN은 기본키, DEPENDENT.SSN은 외래키이다.</p>
</blockquote>
<pre><code class="language-sql">SELECT NAME
FROM EMPLOYEE
WHERE NOT EXISTS (   
    SELECT *
    FROM DEPENDENT
    WHERE EMPLOYEE.SSN = DEPENDENT.SSN
);</code></pre>
<ul>
<li>EMPLOYEE 테이블의 모든 행(직원)에 대해 확인</li>
<li>각 직원의 SSN이 DEPENDENT 테이블의 SSN과 일치하는 행이 있는지 서브 쿼리를 통해 검사</li>
<li>만약 DEPENDENT 테이블에 직원의 SSN과 일치하는 SSN이 있다면, 해당 직원은 부양가족이 존재한다는 의미이므로, 그 직원은 결과에 포함되지 않는다.
반대로, 만약 DEPENDENT 테이블에 직원의 SSN과 일치하는 SSN이 없다면, 그 직원은 부양가족이 없다는 의미이며, 이 경우에 해당하는 직원의 이름이 결과에 포함된다.</li>
</ul>
<p>NOT EXISTS 연산자가 사용될 때, 해당 서브쿼리의 조건에 해당하는 행은 검색 결과에서 제외된다. 즉, EMPLOYEE 테이블에서 직원을 검색할 때, 서브쿼리(EMPLOYEE.SSN = DEPENDENT.SSN)의 조건에 해당하는 행들, 즉 DEPENDENT 테이블에 부양가족이 있는 직원은 결과에서 제외된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB - SQL SELECT문_JOIN]]></title>
            <link>https://velog.io/@developer-jyyun/DB-SQL-SELECT%EB%AC%B8JOIN</link>
            <guid>https://velog.io/@developer-jyyun/DB-SQL-SELECT%EB%AC%B8JOIN</guid>
            <pubDate>Wed, 17 Apr 2024 10:03:46 GMT</pubDate>
            <description><![CDATA[<h1 id="join">JOIN</h1>
<ul>
<li>INNER JOIN</li>
<li>OUTER JOIN (외부 조인)<ul>
<li>LEFT OUTER JOIN,</li>
<li>RIGHT OUTER JOIN,</li>
<li>FULL OUTER JOIN</li>
</ul>
</li>
<li>CROSS JOIN (교차 조인)</li>
<li>SELF JOIN (자기 조인)</li>
</ul>
<h2 id="inner-join">INNER JOIN</h2>
<p>두 테이블의 교집합만을 반환. 즉, 두 테이블에서 <strong>조인 조건을 만족하는 행만</strong> 선택됨.</p>
<pre><code class="language-sql">SELECT * FROM 테이블1, 테이블2
WHERE 테이블1.공통필드 = 테이블2.공통필드;</code></pre>
<p>이 경우, 테이블1과 테이블2는 공통필드가 같은 행에 대해서만 결합되며, SQL 서버는 WHERE 절의 조건을 충족하는 행들만 결과로 반환한다. 이 방식은 INNER JOIN과 동일한 결과를 생성하지만, JOIN 키워드를 사용하는 현대적인 SQL 작성법에 비해 가독성이 떨어지고 오해의 여지가 있기 때문에 권장되지 않는다.</p>
<p>더 명확하고 현대적인 방법으로 같은 결과를 얻기 위해, INNER JOIN 키워드를 사용하여 조인 조건을 ON 절에 명시하는 것이 좋다.</p>
<pre><code class="language-sql">SELECT * FROM 테이블1
INNER JOIN 테이블2 ON 테이블1.공통필드 = 테이블2.공통필드;</code></pre>
<h2 id="outer-join">OUTER JOIN</h2>
<ul>
<li><h3 id="left-outer-join-left-join">LEFT OUTER JOIN (LEFT JOIN)</h3>
</li>
<li><p><em>왼쪽 테이블의 모든 행*</em>과 <strong>오른쪽 테이블에서 조인 조건을 만족하는 행</strong>을 반환한다.
조인 조건을 만족하지 않는 왼쪽 테이블의 행은 <strong>NULL로 채워진 오른쪽 테이블의 열과 함께 반환</strong>된다.</p>
<pre><code class="language-sql">SELECT A.학번, A.이름, B.과목명
FROM 학생 A
LEFT OUTER JOIN 수강 B ON A.학번 = B.학번;</code></pre>
</li>
<li><h3 id="right-outer-join-right-join">RIGHT OUTER JOIN (RIGHT JOIN)</h3>
</li>
<li><p><em>오른쪽 테이블의 모든 행*</em>과 <strong>왼쪽 테이블에서 조인 조건을 만족하는 행</strong>을 반환한다. 조인 조건을 만족하지 않는 <strong>오른쪽 테이블의 행은 NULL로 채워진 왼쪽 테이블의 열과 함께 반환</strong>된다.</p>
<pre><code class="language-sql">SELECT A.학번, A.이름, B.과목
FROM 학생 A
RIGHT OUTER JOIN 성적 B ON A.학번 = B.학번;</code></pre>
</li>
<li><h3 id="full-outer-join-full-join">FULL OUTER JOIN (FULL JOIN)</h3>
<p>왼쪽 테이블과 오른쪽 테이블의 <strong>모든 행을 반환</strong>하며, 어느 한쪽 테이블에서만 조건을 만족하는 행은 다른 테이블의 해당 필드가 <strong>NULL</strong>로 채워져 반환된다.</p>
<pre><code class="language-sql">SELECT A.학번, A.이름, B.과목
FROM 학생 A
FULL OUTER JOIN 성적 B ON A.학번 = B.학번;
</code></pre>
</li>
</ul>
<h2 id="cross-join">CROSS JOIN</h2>
<p>두 테이블의 모든 행이 서로 결합되는 조인으로,** 특정 조인 조건을 명시하지 않는다. <strong>(ON 절 | WHERE 절이 없거나, WHERE절이 있더라도 조인조건 명시하지 않음)
**두 테이블 간의 모든 가능한 조합을 결과로 반환한다.</strong></p>
<ul>
<li><p>ON 절, WHERE 절이 없음(특정 조인 조건 없음)</p>
<pre><code class="language-sql">// 테이블 A의 필드와 레코드 수는 각각 2,5개
// 테이블 B의 필드와 레코드 수는 각각 3,5개일 때
// 아래 SQL문을 실행하면 레코드와 필드의 수는?
SELECT * FROM A, B

// 필드 수 : 5, 레코드 수 : 25, 
</code></pre>
</li>
</ul>
<ul>
<li>특별한 조인 조건(ON 절) 없이 WHERE 절을 사용하여 추가적인 필터링을 수행하는 쿼리 <pre><code class="language-sql">SELECT COUNT(*) AS CNT FROM T1 CROSS JOIN T2
WHERE T1.NAME LIKE T2.RULE
``
</code></pre>
</li>
</ul>
<h2 id="self-join">SELF JOIN</h2>
<p>테이블이 자기 자신과 조인되는 경우. 주로 테이블 내에서 계층적 또는 순차적 데이터 관계를 조회할 때 사용된다. 자기 조인은 테이블의 복사본을 만들고 서로 다른 별칭을 사용하여 같은 테이블을 조인한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB - SQL_ SELECT문 관련 정리]]></title>
            <link>https://velog.io/@developer-jyyun/DB-SQL-SELECT%EB%AC%B8-%EA%B4%80%EB%A0%A8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@developer-jyyun/DB-SQL-SELECT%EB%AC%B8-%EA%B4%80%EB%A0%A8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 15 Apr 2024 20:12:03 GMT</pubDate>
            <description><![CDATA[<ul>
<li><h3 id="select문">SELECT문</h3>
```sql<h1 id="select문-구조">SELECT문 구조</h1>
SELECT 
속성명
/*  
속성명, 속성명, 속성명     <ul>
<li>DISTINCT 속성명 
집계함수(속성명)</li>
</ul>
</li>
<li>/
FROM 테이블명</li>
</ul>
<p>WHERE 조건</p>
<h1 id="and--or--between--and--not--like--is-null--is-not-null">AND | OR | BETWEEN ~ AND | NOT | LIKE | IS NULL | IS NOT NULL</h1>
<p>GROUP BY
HAVING 조건 집계함수(속성명)</p>
<p>ORDER BY 속성 #ASC|DESC</p>
<pre><code>&gt; 🔥 주의!
* AND ::  WHERE, HAVING절에서만 사용 :: 여러 조건을 동시에 만족해야 할 때 사용
* ,(콤마) :: SELECT 절에서 여러 열(column)을 선택할 때, ORDER BY 절에서 여러 열에 대해 정렬 순서를 지정할 때, 그리고 GROUP BY 절에서 그룹화할 열을 나열할 때 사용.
&gt;
```sql
// 평균성적 테이블에서 평균 필드값이 90이상 95이하인 학생들을 검색하여 
// 학년 필드 기준으로 내림차순, 반 필드를 기준으로 오름차순 정렬하여 표시
    WHERE 평균 &gt;= 90 AND 평균 &lt;= 95
    ORDER BY 학년 DESC , 반 ASC;</code></pre><blockquote>
</blockquote>
<pre><code class="language-sql">//이름이 &#39;홍길동&#39;이면서 도시가 &#39;서울&#39;인 사람 검색
SELECT * FROM people
WHERE name = &#39;홍길동&#39; AND city = &#39;서울&#39;;</code></pre>
<pre><code class="language-sql">//이름과 도시를 선택하고, 이름으로 정렬한 후 도시로 정렬
SELECT name, city FROM people
ORDER BY name, city;</code></pre>
<pre><code class="language-sql">//학생 테이블에서 grade와 class별로 평균 점수(average_score)를 계산
SELECT grade, class, AVG(average_score) AS avg_score
FROM students
GROUP BY grade, class;</code></pre>
<ul>
<li><h4 id="집계함수그룹함수">집계함수(그룹함수)</h4>
<p>GROUP BY 절에 지정된 그룹별로 속성의 값을 집계
집계함수는 SELECT~FROM  사이, HAVING 절에만 들어갈 수 있다!
WHERE절 내부에서 사용 불가!</p>
<ul>
<li><p>COUNT : 그룹별 튜플 수 구하는 함수  </p>
<ul>
<li><strong>COUNT(*)</strong>:  NULL 포함한 전체 행 수를 반환.</li>
<li><strong>COUNT(컬럼명)</strong>: 지정된 컬럼에서 NULL 값을 가진 행은 제외한 행 수 반환.</li>
</ul>
</li>
<li><p>SUM : 그룹별 합계를 구하는 함수</p>
</li>
<li><p>AVG : 그룹별 평균을 구하는 함수</p>
</li>
<li><p>MAX : 그룹별 최대값을 구하는 함수</p>
</li>
<li><p>MIN : 그룹별 최소값을 구하는 함수</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="중복-제거">중복 제거</h4>
학생테이블에서 학과의 중복을 제거하고 검색하는 SQL문<blockquote>
</blockquote>
SELECT <strong>DISTINCT</strong> 학과 FROM 학생</li>
</ul>
<ul>
<li><h4 id="union-union-all">UNION, UNION ALL</h4>
<ul>
<li>UNION : 중복 제거 한 합집합</li>
<li>UNION ALL : 중복 제거하지 않은 합집합</li>
</ul>
</li>
<li><h4 id="as-별칭">AS 별칭</h4>
<p>[성적] 테이블에서 &#39;언어&#39; 필드와 &#39;수리&#39; 필드를 더한 후 합계라는 이름으로 표시</p>
<blockquote>
<p>SELECT 언어<strong>+</strong>수리 <strong>AS 합계</strong> FROM 성적;</p>
</blockquote>
</li>
<li><h4 id="between--and-를-and로-표현하기">BETWEEN ~ AND 를 AND로 표현하기</h4>
<blockquote>
<p>SELECT *
FROM 성적
WHERE (<strong>점수 BETWEEN 90 AND 95</strong>) AND 학과＝‘컴퓨터공학과’；
➡  <code>점수 &gt;= 90 AND 점수 &lt;=95</code></p>
</blockquote>
</li>
</ul>
<ul>
<li><h4 id="null값인-경우-null이-아닌-경우">NULL값인 경우, NULL이 아닌 경우</h4>
<blockquote>
<p>SELECT *
FROM 테이블명
WHERE 속성명 *<em>IS NULL *</em>;</p>
<ul>
<li>NULL이 아닌 경우 : <strong>IS NOT NULL</strong></li>
</ul>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported. ]]></title>
            <link>https://velog.io/@developer-jyyun/Error-Only-plain-objects-and-a-few-built-ins-can-be-passed-to-Client-Components-from-Server-Components.-Classes-or-null-prototypes-are-not-supported</link>
            <guid>https://velog.io/@developer-jyyun/Error-Only-plain-objects-and-a-few-built-ins-can-be-passed-to-Client-Components-from-Server-Components.-Classes-or-null-prototypes-are-not-supported</guid>
            <pubDate>Mon, 01 Apr 2024 02:23:13 GMT</pubDate>
            <description><![CDATA[<h2 id="👿-error">👿 error</h2>
<p><span style="color:red; font-style:italic; background:#eee;">Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.</span></p>
<ul>
<li><p>mongo DB 데이터를 불러오고 map함수 적용하는 과정에서 위와 같은 에러가 발생하였다.</p>
<pre><code class="language-js">   const commentsData = await commentsCollection
  .find({
    document: new ObjectId(params.id),
  });</code></pre>
</li>
</ul>
<blockquote>
<p>이 오류는 서버 컴포넌트에서 클라이언트 컴포넌트로 전달된 데이터가 Next.js에서 요구하는 형식을 따르지 않을 때 발생.
 서버 컴포넌트에서 클라이언트 컴포넌트로 MongoDB 객체 같은 비평문 객체(non-plain object)를 직접 전달할 때 발생.
(commentsData : MongoDB 쿼리에서 반환된 Cursor 객체)
이를 배열로 변환하기 위해서는 toArray() 메소드를 사용해야 한다. </p>
</blockquote>
<h2 id="✨-해결">✨ 해결</h2>
<pre><code class="language-js"> const commentsData = await commentsCollection
    .find({
      document: new ObjectId(params.id),
    })
    .toArray();</code></pre>
<p>   .toArray()붙여주니 해결되었다.</p>
<p> 떠올려보니 기존 objectId를 prop으로 넘겨줄 때에도 유사한 에러메시지를 확인했었다...!!
<a href="https://velog.io/@developer-jyyun/Warning-Only-plain-objects-can-be-passed-to-Client-Components-from-Server-Components">Warning: Only plain objects can be passed to Client Components from Server Components.</a></p>
<p>앞으로도 꼼꼼히 기록해둬야징..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[next-auth][error][JWT_SESSION_ERROR] ]]></title>
            <link>https://velog.io/@developer-jyyun/next-autherrorJWTSESSIONERROR</link>
            <guid>https://velog.io/@developer-jyyun/next-autherrorJWTSESSIONERROR</guid>
            <pubDate>Fri, 29 Mar 2024 10:49:29 GMT</pubDate>
            <description><![CDATA[<h2 id="👿-error">👿 error</h2>
<pre><code>[next-auth][error][JWT_SESSION_ERROR] 
https://next-auth.js.org/errors#jwt_session_error decryption operation failed {
  message: &#39;decryption operation failed&#39;,
  stack: &#39;JWEDecryptionFailed: decryption operation failed\n&#39; +
    &#39;    at gcmDecrypt (webpack-internal:///(rsc)/./node_modules/jose/dist/node/cjs/runtime/decrypt.js:67:15)\n&#39; +
    &#39;    at decrypt (webpack-internal:///(rsc)/./node_modules/jose/dist/node/cjs/runtime/decrypt.js:92:20)\n&#39; +
    &#39;    at flattenedDecrypt (webpack-internal:///(rsc)/./node_modules/jose/dist/node/cjs/jwe/flattened/decrypt.js:143:52)\n&#39; +
    &#39;    at async compactDecrypt (webpack-internal:///(rsc)/./node_modules/jose/dist/node/cjs/jwe/compact/decrypt.js:18:23)\n&#39; +
    &#39;    at async jwtDecrypt (webpack-internal:///(rsc)/./node_modules/jose/dist/node/cjs/jwt/decrypt.js:8:23)\n&#39; +
    &#39;    at async Object.decode (webpack-internal:///(rsc)/./node_modules/next-auth/jwt/index.js:66:7)\n&#39; +
    &#39;    at async Object.session (webpack-internal:///(rsc)/./node_modules/next-auth/core/routes/session.js:43:28)\n&#39; +      
    &#39;    at async AuthHandler (webpack-internal:///(rsc)/./node_modules/next-auth/core/index.js:165:27)\n&#39; +
    &#39;    at async getServerSession (webpack-internal:///(rsc)/./node_modules/next-auth/next/index.js:159:19)\n&#39; +
    &#39;    at async POST (webpack-internal:///(rsc)/./src/app/api/comment/route.js:19:21)\n&#39; +
    &#39;    at async E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\compiled\\next-server\\app-route.runtime.dev.js:6:63809\n&#39; +
    &#39;    at async eU.execute (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\compiled\\next-server\\app-route.runtime.dev.js:6:53964)\n&#39; +
    &#39;    at async eU.handle (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\compiled\\next-server\\app-route.runtime.dev.js:6:65062)\n&#39; +
    &#39;    at async doRender (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:1315:42)\n&#39; +       
    &#39;    at async cacheEntry.responseCache.get.routeKind (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:1537:28)\n&#39; +
    &#39;    at async DevServer.renderToResponseWithComponentsImpl (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:1445:28)\n&#39; +
    &#39;    at async DevServer.renderPageComponent (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:1842:24)\n&#39; +
    &#39;    at async DevServer.renderToResponseImpl (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:1880:32)\n&#39; +
    &#39;    at async DevServer.pipeImpl (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:893:25)\n&#39; +
    &#39;    at async NextNodeServer.handleCatchallRenderRequest (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\next-server.js:269:17)\n&#39; +
    &#39;    at async DevServer.handleRequestImpl (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\base-server.js:789:17)\n&#39; +
    &#39;    at async E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\dev\\next-dev-server.js:331:20\n&#39; +
    &#39;    at async Span.traceAsyncFn (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\trace\\trace.js:151:20)\n&#39; +      
    &#39;    at async DevServer.handleRequest (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\dev\\next-dev-server.js:328:24)\n&#39; +
    &#39;    at async invokeRender (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\lib\\router-server.js:174:21)\n&#39; +
    &#39;    at async handleRequest (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\lib\\router-server.js:353:24)\n&#39; +
    &#39;    at async requestHandlerImpl (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\lib\\router-server.js:377:13)\n&#39; +
    &#39;    at async Server.requestListener (E:\\boot_camp\\next\\board\\node_modules\\next\\dist\\server\\lib\\start-server.js:140:13)&#39;,
  name: &#39;JWEDecryptionFailed&#39;
}</code></pre><p>NEXTAUTH_SECRET에 임의의 문자열을 넣으면 된다고 해서..정말 임의의 짧은 문자열을 넣어뒀더니 위와 같은 에러가 왕창....허허허헣허헣</p>
<h2 id="✨-해결">✨ 해결</h2>
<ul>
<li>OpenSSL 다운로드 후 설치
  참고:: <a href="https://warmdeveloper.tistory.com/64">https://warmdeveloper.tistory.com/64</a></li>
<li>터미널에 <code>openssl rand -base64 32</code> 입력하니 32바이트 길이의 랜덤 데이터가 Base64 인코딩된 형태로 출력되었당..!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[7. 회원가입, 로그인(Credentials provider + JWT 사용하기)]]></title>
            <link>https://velog.io/@developer-jyyun/7.-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A1%9C%EA%B7%B8%EC%9D%B8Credentials-provider-JWT-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@developer-jyyun/7.-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A1%9C%EA%B7%B8%EC%9D%B8Credentials-provider-JWT-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 29 Mar 2024 02:26:33 GMT</pubDate>
            <description><![CDATA[<h2 id="1-회원가입-페이지">1. 회원가입 페이지</h2>
<ul>
<li>비밀번호 암호화해서 저장하기 위한 bcrypt 라이브러리 설치</li>
</ul>
<pre><code>npm install bcrypt</code></pre><ul>
<li><p>회원가입 페이지 생성 및 코드 작성
src/app/register/page.js</p>
</li>
<li><p>api 작성
src/app/api/auth/signup/route.js</p>
</li>
<li><p><code>await bcrypt.hash(data.password, 10);</code>
비밀번호 해싱 후 기존 password 대신 해싱된 password 데이터 덮어쓴 후 db에 추가해주기!</p>
</li>
</ul>
<pre><code class="language-js">import { connectToDatabase } from &quot;@/utils/database&quot;;
import { NextResponse } from &quot;next/server&quot;;
import bcrypt from &quot;bcrypt&quot;;

export async function POST(req) {
  const { collection } = await connectToDatabase(&quot;user_cred&quot;);

  try {
    const data = await req.json();
    const hashedPassword = await bcrypt.hash(data.password, 10); // 비밀번호 해싱
    const userData = {
      ...data,
      password: hashedPassword, // &#39;password&#39; 필드를 해시된 값으로 덮어씀
    };
    await collection.insertOne(userData);
    return NextResponse.json({ message: &quot;사용자 등록 성공&quot; }); // 응답 메시지 반환
  } catch (error) {
    console.error(error);
    return NextResponse.json(
      { error: &quot;서버 오류가 발생했습니다.&quot; },
      { status: 500 }
    );
  }
}
</code></pre>
<h2 id="2-idpassword-로그인-페이지">2. id/password 로그인 페이지</h2>
<h3 id="credentials-provider-설정하기">Credentials provider 설정하기</h3>
<pre><code class="language-js">// 기존 코드 생략
import CredentialsProvider from &quot;next-auth/providers/credentials&quot;;
import bcrypt from &quot;bcrypt&quot;;

const authOptions = {
  providers: [
    GithubProvider({
    // ...
    }),
   // 기존 코드 생략

    CredentialsProvider({
      //1. 로그인페이지 폼 자동생성해주는 코드
      name: &quot;credentials&quot;,
      credentials: {
        name: { label: &quot;name&quot;, type: &quot;text&quot; },
        email: { label: &quot;email&quot;, type: &quot;email&quot; },
        password: { label: &quot;password&quot;, type: &quot;password&quot; },
      },

      //2. 로그인요청시 실행되는코드
      //직접 DB에서 아이디,비번 비교하고
      //아이디,비번 맞으면 return 결과, 틀리면 return null 해야함
      async authorize(credentials) {
        const db = (await connectDB).db(&quot;board&quot;);
        const user = await db
          .collection(&quot;user_cred&quot;)
          .findOne({ email: credentials.email });
        if (!user) {
          console.log(&quot;해당 이메일이 존재하지 않습니다.&quot;);
          return null;
        }
        const pwcheck = await bcrypt.compare(
          credentials.password,
          user.password
        );
        if (!pwcheck) {
          console.log(&quot;비밀번호가 일치하지 않습니다.&quot;);
          return null;
        }
        return user;
      },
    }),
  ],

  //3. jwt + jwt 만료일설정
  session: {
    strategy: &quot;jwt&quot;,
    maxAge: 30 * 24 * 60 * 60, //로그인 상태 유지 기간(30일)
  },

  callbacks: {
    //4. jwt 만들 때 실행되는 코드
    //user변수는 DB의 유저정보담겨있고 token.user에 추가 정보 저장하면 jwt에 들어감.
    jwt: async ({ token, user }) =&gt; {
      if (user) {
        token.user = {};
        token.user.name = user.name;
        token.user.email = user.email;
      }
      return token;
    },
    //5. 유저 세션이 조회될 때 마다 실행되는 코드
    session: async ({ session, token }) =&gt; {
      session.user = token.user;
      return session;
    },
  },

  secret: process.env.NEXTAUTH_SECRET, //jwt생성시쓰는암호
  adapter: MongoDBAdapter(connectDB),
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST, authOptions };
</code></pre>
<h2 id="3-todo">3. TODO</h2>
<ul>
<li><p>회원 가입 시 admin 계정, 일반 유저 구분해주기
  role:admin/nomal</p>
</li>
<li><p>유효성 검사</p>
<ul>
<li>input 빈 값 체크</li>
<li>email 중복체크</li>
<li>비밀번호 유효성</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[6. Auth.js 사용한 소셜로그인]]></title>
            <link>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86.-OAUTH-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86.-OAUTH-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Fri, 29 Mar 2024 00:41:28 GMT</pubDate>
            <description><![CDATA[<h2 id="1-next-auth-authjs-라이브러리">1. Next-Auth (Auth.js) 라이브러리</h2>
<h3 id="--nextauth-라이브러리-설치">- NextAuth 라이브러리 설치</h3>
<pre><code>  npm install next-auth </code></pre><p>Next-auth 라이브러리를 사용하면 기본적으로 JWT 방식.
유저 세션데이터를 DB에 저장해두지 않고 JWT만 유저에게 보내고
유저가 로그인이 필요한 페이지 방문시 유저가 제출한 JWT만 검사해서 입장시켜주는 방식</p>
<h3 id="--nextauth--initialization">- <a href="https://next-auth.js.org/configuration/initialization">NextAuth :: initialization</a></h3>
<ul>
<li><p>/app/api/auth/[...nextauth]/route.ts</p>
<pre><code class="language-js">import NextAuth from &quot;next-auth&quot;

const handler = NextAuth({
...
})

export { handler as GET, handler as POST }</code></pre>
</li>
</ul>
<h3 id="--github-google-kakao-키-발급">- github, google, kakao 키 발급</h3>
<h4 id="🌞-nextauth--oauth">🌞 <a href="https://next-auth.js.org/configuration/providers/oauth">NextAuth :: OAuth</a></h4>
<details>
<summary> 키 발급 방법
  </summary>
  <div  style="background:#f5f5f5; padding: 10px 5px">
   <ul>
    <li>
      <a href="https://github.com/settings/applications/2371755">
      Github</a><Br>
      <small>
        Github.com 로그인 ➡ 우측상단 Settings ➡ Developer settings ➡ New OAuth app 만들기 
      </small>
    </li>
    <li>
          <a href="https://console.cloud.google.com/projectcreate?previousPage=%2Fiam-admin%2Fsettings%3Fproject%3Dmy-sns-409301&organizationId=0">
      google cloud console</a><Br>
      <a href="https://jisilver-k.tistory.com/101)">구글 키 생성 참고페이지</a>
    </li>
    <li>
         <a href="https://developers.kakao.com/console/app">kakao developers</a>
      <ul>
         <li>애플리케이션 추가하기 ➡ 새 프로젝트 등록 </li>
            <li>
        카카오 로그인 ➡ 
              <a href="https://developers.kakao.com/console/app/1053986/product/login">Redirect URI 등록 </a> <br>
              http://localhost:3000/api/auth/callback/kakao/
        </li>
        <li>
 플랫폼 ➡ web 사이트 도메인 등록 : http://localhost:3000
        </li>
        <li>
          카카오 로그인 ➡ 카카오 로그인 활성화
<br>
          카카오 로그인 ➡ 동의항목 ➡ 필요한 항목 선택하기
        </li>
      </ul>
    </li>
</ul>
  </div>

</details>


<ul>
<li>id, secret key : .env.local에 추가</li>
<li>board/src/app/api/auth/[...nextauth]\route.js<pre><code class="language-js">import NextAuth from &quot;next-auth&quot;;
import { connectDB } from &quot;@/utils/database&quot;;
import { MongoDBAdapter } from &quot;@next-auth/mongodb-adapter&quot;;
import GithubProvider from &quot;next-auth/providers/github&quot;;
import GoogleProvider from &quot;next-auth/providers/google&quot;;
import KakaoProvider from &quot;next-auth/providers/kakao&quot;;
</code></pre>
</li>
</ul>
<p>const authOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    KakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID,
      clientSecret: process.env.KAKAO_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET, //jwt생성시쓰는암호
};</p>
<p>const handler = NextAuth(authOptions);</p>
<p>export { handler as GET, handler as POST, authOptions };</p>
<pre><code>

### - SessionProvider 감싸기
&gt; #### SessionProvider
- NextAuth.js 라이브러리의 일부로, 애플리케이션 내에서 사용자 세션을 관리하는 컴포넌트.
-  로그인, 로그아웃 상태를 추적하고, 사용자의 세션 정보를 애플리케이션 전반에 걸쳐 유지한다.
- 애플리케이션의 최상위에 위치하여, 애플리케이션 전체에서 사용자의 인증 상태와 세션 정보에 쉽게 접근할 수 있도록 한다.
- 컨텍스트 제공: SessionProvider는 React의 컨텍스트(Context) API를 사용하여, 하위 컴포넌트들이 현재 사용자의 세션 정보에 쉽게 접근할 수 있도록 한다. 이를 통해 개발자는 어느 컴포넌트에서든지 사용자의 로그인 상태나 세션 데이터를 활용할 수 있다.
- useSession이라는 리액트 훅을 통해 접근하며, 주로 layout에 세션 컨텍스트인 &lt;SessionProvider /&gt;로 감싸준다. 
단, SessionPrvider는 클라이언트 컴포넌트에서 사용하기 때문에 별도의 AuthProvider 컴포넌트를 생성하여 SessionPrvider로 감싼 뒤 layout에 넣어주었다. 


```js
// board/src/components/provider/AuthProvider.js

&quot;use client&quot;;
import { SessionProvider } from &quot;next-auth/react&quot;;

export default function AuthProvider({ children }) {
  return &lt;SessionProvider&gt;{children}&lt;/SessionProvider&gt;;
}

// board/src/app/layout.js

import { Inter } from &quot;next/font/google&quot;;
import &quot;./globals.css&quot;;
import AuthProvider from &quot;../components/provider/AuthProvider&quot;;
import Header from &quot;@/components/Header&quot;;

const inter = Inter({ subsets: [&quot;latin&quot;] });

export const metadata = {
  title: &quot;✨게시판 프로젝트&quot;,
  description: &quot;게시판 ~~~~&quot;,
};

export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body className={inter.className}&gt;
        &lt;AuthProvider&gt;
          &lt;Header /&gt;
          {children}
        &lt;/AuthProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre><h3 id="--loginlogout-button-usesession">- login/logout button, useSession</h3>
<ul>
<li><p>useSession은 client Component에서 사용</p>
</li>
<li><p>useSession을 통해 로그인된 유저 정보를 출력</p>
</li>
<li><p>조건부 렌더링</p>
<ul>
<li><p>현재 로그인된 유저 정보가 있을 경우 
유저 프로필,이름 &amp; 로그아웃 버튼 노출   </p>
<ul>
<li>카카오 로그인 시 이름이 노출되도록,</li>
<li>깃헙, 구글 로그인 시 메일주소가 노출되도록 </li>
<li>카카오에만 email속성이 없어서 session.user.email이 존재할 경우 email 노출, 없을 경우 name을 노출시켰다.</li>
</ul>
</li>
<li><p>유저 정보가 없을 경우 : 로그인버튼 노출</p>
</li>
</ul>
</li>
</ul>
<pre><code class="language-js">// board/src/components/AuthButton.js
&quot;use client&quot;;

import TextButton from &quot;./UI/TextButton&quot;;
import { useSession, signIn, signOut } from &quot;next-auth/react&quot;;

export default function AuthButton({}) {
  const { data: session } = useSession();
  if (session) {
    console.log(session);
    return (
      &lt;div className=&quot;flex&quot;&gt;
        &lt;img
          className=&quot;avatar&quot;
          src={session.user.image}
          alt={session.user.name}
        /&gt;
        {session.user.email ? (
          &lt;span className=&quot;px-1&quot;&gt;{session.user.email}&lt;/span&gt;
        ) : (
          &lt;span className=&quot;px-1&quot;&gt;{session.user.name}&lt;/span&gt;
        )}

        &lt;TextButton
          text=&quot;logOut&quot;
          onClickFn={() =&gt; {
            signOut();
          }}
        /&gt;
      &lt;/div&gt;
    );
  }
  return (
    &lt;&gt;
      &lt;TextButton text=&quot;logIn&quot; onClickFn={() =&gt; signIn()} /&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h2 id="2-oauth--session방식">2. OAuth + session방식</h2>
<h3 id="adapter"><a href="https://next-auth.js.org/adapters">adapter</a></h3>
<ul>
<li><p><a href="https://medium.com/@rohitkumarkhatri/next-auth-in-app-router-of-next-js-7df037f7a2ad">참고 페이지 </a></p>
</li>
<li><p>MongoDB adapter 설정</p>
<pre><code>npm install @next-auth/mongodb-adapter 
</code></pre></li>
</ul>
<pre><code>


- board/src/app/api/auth/[...nextauth]/route.js
```js
import { connectDB } from &quot;@/utils/database&quot;;
import { MongoDBAdapter } from &quot;@next-auth/mongodb-adapter&quot;;

const authOptions = NextAuth({
  providers: [
    //깃헙,구글,카카오 등..
  ],
  secret: process.env.NEXTAUTH_SECRET, 
  //어댑터 추가!
  adapter: MongoDBAdapter(connectDB),
});
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
</code></pre><p>어댑터 추가해 준 뒤 로그인 하면 몽고db에 3개의 폴더가 생성된다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/133ea5e2-f58b-48d8-b6ab-055b2514b5a5/image.png" alt=""> </p>
</blockquote>
<ul>
<li>sessions : 현재 로그인된 유저 정보, 로그인 유효기간 포함</li>
<li>users는 유저(이메일로 구분)</li>
<li>accounts는 유저 계정 
하나의 유저는 여러개의 계정을 가지고 있을 수 있음.
유저는 1명이지만 계정은 2개 이상 생성이 가능(이메일이 같으면 같은 유저라고 자동으로 간주)</li>
</ul>
<h3 id="글-작성-시-user-정보-db에-저장">글 작성 시 user 정보 db에 저장</h3>
<ul>
<li><p>getServerSession(authOptions); 사용하여 유저의 세션정보 가져와서, 유저가 글을 등록할 때 유저 이름, 이메일, 프로필 이미지 모두 db에 저장되도록 수정해주었다.</p>
</li>
<li><p>로그아웃 상태일 때 401에러를 응답하도록 하였는데, 이후 추가적으로 글 작성 페이지에서 로그인하지 않은 유저는 로그인페이지로 리다이렉트 시켜주었다.</p>
</li>
</ul>
<pre><code class="language-js">// board\src\app\api\new\route.js

import { connectToDatabase } from &quot;@/utils/database&quot;;
import { NextResponse } from &quot;next/server&quot;;
import { getServerSession } from &quot;next-auth&quot;;
import { authOptions } from &quot;../auth/[...nextauth]/route&quot;;

export async function POST(req) {
  const { postCollection } = await connectToDatabase();
  const session = await getServerSession(authOptions);
  console.log(session);
  if (!session) {
    return NextResponse.json(
      { error: &quot;로그인이 필요합니다.&quot; },
      { status: 401 }
    );
  }
  try {
    const data = await req.json(); 
       // 생략..

    const postData = {
      ...data,
      email: session.user.email,
      name: session.user.name,
      image: session.user.image,
    };
    console.log(postData);

    await postCollection.insertOne(postData);
    return NextResponse.json(postData);
  } catch (error) {
   // 에러처리
  }
}
</code></pre>
<h3 id="수정-및-삭제-권한">수정 및 삭제 권한</h3>
<ul>
<li>기존 ListItem 컴포넌트는 로그인 여부와 관계 없이 각 게시물의 수정/삭제 버튼이 모두에게 노출되었고, 권한도 부여되었다.</li>
<li>권한있는 유저만 수정/삭제 하도록 조건부 렌더링으로 수정하였다.
useSession을 통해 유저의 세션정보의 email과, props로 받은 userInfo의 email속성과 비교하여 이 두개가 동일할 경우에만 수정/삭제 버튼을 노출시키고, 로그인한 유저와 글쓴이가 다른 경우에는 작성자의 프로필이미지와 이름을 노출시키도록 수정 하였다.</li>
</ul>
<pre><code class="language-js">import { useSession } from &quot;next-auth/react&quot;;

export default function ListItem({ userInfo, id }) {

const { data: session } = useSession();
const isAuthor = session?.user?.email === userInfo.email;

 return (
         {isAuthor ? 
          ({/*수정삭제아이콘 노출 */})
           : 
          ({/*userInfo.image, userInfo.name 노출 */})
         }
)}</code></pre>
<h2 id="🔥-오늘의-트러블슈팅">🔥 오늘의 트러블슈팅</h2>
<h4 id="👿-error-1">👿 error 1</h4>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/f2fa34a4-f8f2-47b5-a794-e3b7b426d8b1/image.png" alt=""></p>
<p>에러메시지가 왕창..
next-auth/mongodb-adapter 패키지가 mongodb 버전 5.x 또는 4.x를 요구하기 때문에 충돌이 발생.
mongodb 4.0버전으로 재설치..</p>
<pre><code>npm uninstall mongodb
npm install mongodb@4</code></pre><h4 id="👿-error-2">👿 error 2</h4>
<p>사용자가 처음에 GitHub으로 가입했고, 같은 이메일로 Google을 통해 가입하려고 시도할 때 발생</p>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/d819a9f6-6678-44d7-adf5-60056852c5a2/image.png" alt="err"></p>
<p>NextAuth에서 제공하는 GitHub, Google, Kakao 같은 여러 소셜 로그인 방법을 사용할 때, 하나의 이메일 주소에 여러 제공자를 연결하는 것은 기본적으로 지원되지 않는다. 보안상의 이유로 자동으로 계정을 연결하는 기능을 제공하지 않기 때문..</p>
<h4 id="✨-해결">✨ 해결</h4>
<p><a href="https://stackoverflow.com/questions/76851751/next-auth-with-google-provider-to-confirm-your-identity-sign-in-with-the-same">관련 이슈 : stackoverflow</a> 
<a href="authjs.dev/guides/providers/custom-provider">공식 문서 : allowDangerousEmailAccountLinking: true</a></p>
<p>allowDangerousEmailAccountLinking: true,추가해주면 해결!</p>
<pre><code class="language-js">const authOptions = {
  providers: [
    GithubProvider({
    // 생략...
      allowDangerousEmailAccountLinking: true,
    }),   
  ], 
};</code></pre>
<h2 id="-진행상황--todo-">:: 진행상황 &amp; TODO ::</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 몽고 DB setting</li>
<li><input checked="" disabled="" type="checkbox"> MongoDB 입출력<ul>
<li><input disabled="" type="checkbox"> 글 목록 조회 기능(/list)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 글 제목,~~ </li>
<li><input disabled="" type="checkbox"> 날짜 데이터바인딩<ul>
<li><input disabled="" type="checkbox"> 글 상세 페이지(/detail/[id]/page.js)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 제목, 내용, </li>
<li><input disabled="" type="checkbox"> 게시 날짜, 댓글 데이터바인딩<ul>
<li><input checked="" disabled="" type="checkbox"> 글 작성 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 글 수정 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 글 삭제 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 404페이지 만들기</li>
<li><input checked="" disabled="" type="checkbox"> error 페이지 만들기</li>
<li><input checked="" disabled="" type="checkbox"> loading 페이지 만들기</li>
<li><input checked="" disabled="" type="checkbox"> Next-auth 회원인증기능</li>
<li><input disabled="" type="checkbox"> S3 파일업로드 </li>
</ul>
</li>
</ul>
<ul>
<li><input disabled="" type="checkbox"> 글 작성 페이지 마크다운 작성페이지로 변경</li>
<li><input disabled="" type="checkbox"> 캐싱, 에러처리 등 부가기능</li>
<li><input disabled="" type="checkbox"> 스타일링</li>
<li><input disabled="" type="checkbox"> AWS 클라우드 배포</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OAuth 로그인 에러] To confirm your identity, sign in with the same account you used originally.]]></title>
            <link>https://velog.io/@developer-jyyun/OAuth-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%90%EB%9F%AC-To-confirm-your-identity-sign-in-with-the-same-account-you-used-originally</link>
            <guid>https://velog.io/@developer-jyyun/OAuth-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%90%EB%9F%AC-To-confirm-your-identity-sign-in-with-the-same-account-you-used-originally</guid>
            <pubDate>Fri, 29 Mar 2024 00:38:01 GMT</pubDate>
            <description><![CDATA[<h2 id="👿-error">👿 error</h2>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/d819a9f6-6678-44d7-adf5-60056852c5a2/image.png" alt="err"></p>
<p>처음에 GitHub으로 가입했고, 동일한 이메일로 Google을 통해 가입하려고 시도할 때 발생</p>
<p>NextAuth에서 제공하는 GitHub, Google, Kakao 같은 여러 소셜 로그인 방법을 사용할 때, 하나의 이메일 주소에 여러 제공자를 연결하는 것은 기본적으로 지원되지 않는다. 보안상의 이유로 자동으로 계정을 연결하는 기능을 제공하지 않기 때문..</p>
<h2 id="✨-해결">✨ 해결</h2>
<p><a href="https://stackoverflow.com/questions/76851751/next-auth-with-google-provider-to-confirm-your-identity-sign-in-with-the-same">관련 이슈 : stackoverflow</a> 
<a href="authjs.dev/guides/providers/custom-provider">공식 문서 : allowDangerousEmailAccountLinking: true</a></p>
<p>allowDangerousEmailAccountLinking: true,추가해주면 해결!</p>
<pre><code class="language-js">const authOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
      allowDangerousEmailAccountLinking: true,
    }),

  ],

};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. 게시글 삭제]]></title>
            <link>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B85.-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%82%AD%EC%A0%9C</link>
            <guid>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B85.-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%82%AD%EC%A0%9C</guid>
            <pubDate>Mon, 25 Mar 2024 09:24:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌞 MongoDB 데이터 삭제하기</p>
</blockquote>
<pre><code class="language-js">await db.collection(&#39;콜렉션이름&#39;).deleteOne(
    {게시물정보}   
);</code></pre>
<ul>
<li><a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.deleteOne/">공식문서.deleteOne()</a></li>
</ul>
<h2 id="삭제-기능-구현하기">삭제 기능 구현하기</h2>
<h3 id="--listpage">- ListPage</h3>
<ul>
<li>ListItem <strong>클라이언트 컴포넌트로 분리</strong></li>
<li>ListItem 컴포넌트에 postData prop으로 전달<ul>
<li>id는 string형태로 전달해주자!</li>
</ul>
</li>
</ul>
<pre><code class="language-js">//  src/app/list/page.js

import styles from &quot;./list.module.css&quot;;
import { connectToDatabase } from &quot;@/utils/database&quot;;
import ListItem from &quot;./_components/ListItem&quot;;
import Link from &quot;next/link&quot;;
import TextButton from &quot;@/components/UI/TextButton&quot;;

export default async function ListPage() {
  const { postCollection } = await connectToDatabase();
  let postData = await postCollection.find().toArray();
  return (
    &lt;&gt;
      &lt;Link href=&quot;/write&quot;&gt;
        &lt;TextButton text=&quot;게시글 작성&quot; /&gt;
      &lt;/Link&gt;
      &lt;ul className={styles[&quot;list-bg&quot;]}&gt;
        {postData.map((item, index) =&gt; (
          &lt;ListItem title={item.title} id={item._id.toString()} /&gt;
        ))}
      &lt;/ul&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h3 id="--listitem-컴포넌트">- ListItem 컴포넌트</h3>
<ul>
<li><p>코드의 일관성 유지, 재사용성을 위해 공통 컴포넌트를 만들어주었다.</p>
<ul>
<li>Message 컴포넌트 :  text, type 두 가지 prop을 받습는다. text prop은 화면에 표시될 메시지 내용을, type prop은 메시지의 스타일 유형을 결정하도록 하였다.</li>
<li>IconButton은 icon, onclickFn, text(선택적) 세 가지 prop을 받을 수 있도록 하였다.</li>
</ul>
</li>
<li><p>handleDeleteBtnClick()함수는 비동기적으로 서버에 삭제 요청을 보내고, 응답에 따라 성공 또는 오류 메시지를 나타내도록 하였다. 삭제 요청 성공 시, 목록 페이지로 리디렉션하고, 일정 시간 후 메시지가 사라지도록 하였다.</p>
</li>
<li><p>삭제 요청 시 body: JSON.stringify({ _id: id })통해 id를 서버로 전송해줘야 한다!</p>
</li>
</ul>
<pre><code class="language-js">//src/app/list/_components/ListItem.js

&quot;use client&quot;;

import Link from &quot;next/link&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { TiEdit } from &quot;react-icons/ti&quot;;
import { MdOutlineDeleteSweep } from &quot;react-icons/md&quot;;
import IconButton from &quot;@/components/UI/IconButton&quot;;

import styles from &quot;./ListItem.module.css&quot;;
import { useState } from &quot;react&quot;;
import Message from &quot;@/components/UI/Message&quot;;

export default function ListItem({ title, id }) {
  const [message, setMessage] = useState(&quot;&quot;);
  const [messageType, setMessageType] = useState(&quot;&quot;);
  const router = useRouter();

  const handleDeleteBtnClick = async (e) =&gt; {
    console.log(id);
    try {
      const response = await fetch(&quot;/api/delete&quot;, {
        method: &quot;DELETE&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ _id: id }),
      });
      if (!response.ok) {
        throw new Error(&quot;네트워크 응답이 올바르지 않습니다.&quot;);
      }
      router.push(`/list`);
      router.refresh();
      setMessageType(&quot;success&quot;);
      setMessage(&quot;게시물이 삭제되었습니다.&quot;);
      setTimeout(() =&gt; {
        setMessage(&quot;&quot;);
      }, 3000);
    } catch (error) {
      console.error(error);
      setMessageType(&quot;error&quot;);
      setMessage(error.message);
    }
  };

  return (
    &lt;&gt;
      {message.length &gt; 0 &amp;&amp; &lt;Message text={message} type={messageType} /&gt;}
      &lt;li className={`${styles[&quot;list-item&quot;]} flex`}&gt;
        &lt;div className={styles[&quot;text-box&quot;]}&gt;
          &lt;Link href={`/detail/${id}`}&gt;
            &lt;h4&gt;{title}&lt;/h4&gt;
            &lt;p&gt;1월 1일&lt;/p&gt;
          &lt;/Link&gt;
        &lt;/div&gt;
        &lt;div className={`flex ${styles[&quot;icon-wrap&quot;]}`}&gt;
          &lt;Link href={`/edit/${id}`}&gt;
            &lt;IconButton icon={&lt;TiEdit /&gt;} /&gt;
          &lt;/Link&gt;

          &lt;IconButton
            icon={&lt;MdOutlineDeleteSweep /&gt;}
            onclickFn={handleDeleteBtnClick}
          /&gt;
        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h3 id="--게시물-삭제-api">- 게시물 삭제 API</h3>
<ul>
<li>deleteOne() 메서드 통해 게시물 삭제</li>
</ul>
<pre><code class="language-js">// /app/api/delete/route.js
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;
import { NextResponse } from &quot;next/server&quot;;

export async function DELETE(req) {
  try {
    const { _id } = await req.json();
    const { postCollection } = await connectToDatabase();
    const result = await postCollection.deleteOne({ _id: new ObjectId(_id) });
    console.log(result);

    return NextResponse.json({
      message: &quot;게시글이 성공적으로 삭제되었습니다.&quot;,
    });
  } catch (error) {
    console.error(error);
    return NextResponse.json(
      { error: &quot;서버 오류가 발생했습니다.&quot; },
      { status: 500 }
    );
  }
}
</code></pre>
<h2 id="추가작업">추가작업</h2>
<p>app폴더에 loading.js, error.js, not-found.js파일 생성</p>
<h2 id="-진행상황--todo-">:: 진행상황 &amp; TODO ::</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 몽고 DB setting</li>
<li><input checked="" disabled="" type="checkbox"> MongoDB 입출력<ul>
<li><input disabled="" type="checkbox"> 글 목록 조회 기능(/list)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 글 제목,~~ </li>
<li><input disabled="" type="checkbox"> 날짜 데이터바인딩<ul>
<li><input disabled="" type="checkbox"> 글 상세 페이지(/detail/[id]/page.js)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 제목, 내용, </li>
<li><input disabled="" type="checkbox"> 게시 날짜, 댓글 데이터바인딩<ul>
<li><input checked="" disabled="" type="checkbox"> 글 작성 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 글 수정 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 글 삭제 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 404페이지 만들기</li>
<li><input checked="" disabled="" type="checkbox"> error 페이지 만들기</li>
<li><input checked="" disabled="" type="checkbox"> loading 페이지 만들기</li>
<li><input disabled="" type="checkbox"> S3 파일업로드 </li>
<li><input disabled="" type="checkbox"> Next-auth 회원인증기능</li>
<li><input disabled="" type="checkbox"> 글 작성 페이지 마크다운 작성페이지로 변경</li>
<li><input disabled="" type="checkbox"> 캐싱, 에러처리 등 부가기능</li>
<li><input disabled="" type="checkbox"> 스타일링</li>
<li><input disabled="" type="checkbox"> AWS 클라우드 배포</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB - SQL 분류(DDL,DML,DCL)]]></title>
            <link>https://velog.io/@developer-jyyun/SQL-%EB%AC%B8%EB%B2%95%EC%9D%98-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@developer-jyyun/SQL-%EB%AC%B8%EB%B2%95%EC%9D%98-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Mon, 25 Mar 2024 07:38:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1-데이터-정의어">1. 데이터 정의어</h2>
<h3 id="ddl---data-definition-language">DDL - Data Definition Language</h3>
<p><code>CREATE, ALTER, DROP, TRUNCATE</code>
    데이터 구조를 <strong>정의</strong>하는데 사용되는 명령
    스키마, 도메인, 테이블, 뷰, 인덱스를 <strong>정의, 변경, 삭제</strong></p>
<ul>
<li><h4 id="1-create--스키마-도메인-테이블-뷰-인덱스를-정의">1) CREATE : 스키마, 도메인, 테이블, 뷰, 인덱스를 <strong>정의</strong></h4>
<pre><code class="language-sql">CREATE SCHEMA {스키마_명} AUTHORIZATION {사용자_ID};</code></pre>
<ul>
<li>프로시저 생성 <pre><code class="language-sql">CREATE PROCEDURE 프로시저명(파라미터)
(변수1 IN 변수타입, 변수2 OUT 변수타입, 변수3 IN OUT 변수타입...)
</code></pre>
</li>
</ul>
<p>IS</p>
<pre><code>변수처리부</code></pre><p>BEGIN</p>
<pre><code>처리내용</code></pre><p>EXCEPTION</p>
<pre><code>예외처리부</code></pre><p>END;</p>
</li>
</ul>
<ul>
<li><p>트리거 생성 </p>
<ul>
<li>실행시기 :  BEFORE(이벤트 전),AFTER(이벤트 후)</li>
</ul>
<pre><code class="language-sql">CREATE TRIGGER 트리거명(실행시기)(옵션) ON 테이블명  
BEGIN
트리거 BODY
END;</code></pre>
<ul>
<li><h4 id="2-alter--테이블에-대한-정의형식을-변경하는데-사용">2) ALTER : 테이블에 대한 정의(형식)을 <strong>변경</strong>하는데 사용</h4>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>속성 추가, 변경, 삭제</strong> :** ADD, MODIFY, DROP COLUMN **</p>
<blockquote>
</blockquote>
<p>// 회원 테이블에 AGE 속성 추가
ALTER TABLE 회원 <strong>ADD</strong> AGE <del>~</del>;</p>
<blockquote>
</blockquote>
<p>// 회원 테이블에 AGE 속성 INT로 변경
ALTER TABLE 회원 <strong>MODIFY</strong> ** INT(11)</p>
<blockquote>
</blockquote>
<p>// 회원 테이블에 AGE 속성 삭제
ALTER TABLE 회원 <strong>DROP COLUMN</strong> AGE;</p>
</li>
<li><p><strong>INDEX 변경, 재구성, 비활성화</strong> : <strong>RENAME TO, REBUILD, UNUSABLE</strong></p>
<blockquote>
<p> // 회원명 INDEX를 성명으로 변경
ALTER INDEX 회원명 <strong>RENAME TO</strong> 성명 </p>
</blockquote>
<p>// 회원명 INDEX의 속도가 저하되거나 깨졌을 경우 INDEX 재구성
ALTER INDEX 회원명 <strong>REBUILD</strong></p>
<blockquote>
</blockquote>
<p>// 회원명 INDEX 비활성화
ALTER INDEX 회원명 <strong>UNUSABLE</strong></p>
</li>
</ul>
<ul>
<li><h4 id="3-drop--스키마-도메인-테이블-뷰-인덱스-구조-자체를-삭제함">3) DROP : 스키마, 도메인, 테이블, 뷰, 인덱스 <strong>구조 자체를 삭제</strong>함</h4>
<p>각각의 DROP 구문 뒤에<strong><em>는 CASCADE 또는 RESTRICT 옵션을 선택적으로 사용</em></strong>할 수 있다.</p>
<ul>
<li>CASCADE: 지정된 객체를 삭제하고, 그에 의존하는 모든 객체도 함께 삭제</li>
<li>RESTRICT: 의존하는 다른 객체가 있을 경우 삭제를 거부</li>
</ul>
<pre><code class="language-sql">DROP SCHEMA 스키마_명 CASCADE;
 DROP TABLE 테이블_명 RESTRICT;
 DROP VIEW USER_VIEW CASCADE;
 DROP INDEX USER_INDEX RESTRICT;</code></pre>
</li>
</ul>
<ul>
<li><h4 id="4-truncate--데이터베이스-구조는-유지한-채-내용을-삭제초기화">4) TRUNCATE : 데이터베이스 구조는 유지한 채 <strong>내용을 삭제(초기화)</strong></h4>
<pre><code class="language-sql">TRUNCATE [TABLE] {테이블_명} ;
</code></pre>
</li>
</ul>
<blockquote>
<h3 id="제약-조건-적용">제약 조건 적용</h3>
</blockquote>
<ul>
<li>PRIMARY KEY : 테이블의 기본키 정의<ul>
<li>기본으로 NOT NULL, UNIQUE 제약이 포함 <strong>(개체 무결성)</strong></li>
</ul>
</li>
<li>FOREIGN KEY : 외래키 정의<ul>
<li>참조 대상을 테이블이름(열이름)으로 명시</li>
<li><strong>참조 무결성</strong> 위배 상황 발생 시 처리 방법으로 옵션 지정 가능</li>
<li>```sql
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE
CASCADE;  </li>
</ul>
</li>
<li>UNIQUE : 고유값 지정<ul>
<li>테이블 내에서 열은 유일한 값을 가져야 함.</li>
<li>테이블 내에서 동일한 값을 가져서는 안 되는 항목에 지정.</li>
<li>```
USER_ID VARCHAR(10) UNIQUE NOT NULL   </li>
<li>NOT NULL : 테이블 내에서 관련 열의 값은 NULL일 수 없음</li>
<li>필수 입력 항목에 대해 제약 조건으로 설정</li>
<li><pre><code class="language-sql">USER_ID VARCHAR(10) NOT NULL </code></pre>
</li>
<li>CHECK : 개발자가 정의하는 제약 조건</li>
</ul>
</li>
<li>상황에 따라 다양한 조건 설정 가능</li>
<li><pre><code class="language-sql">CONSTRAINT user_jumin CHECK(LENGTH(jumin)=13)

</code></pre>
</li>
</ul>
<h2 id="2-데이터-조작어">2. 데이터 조작어</h2>
<h3 id="dml---data-manipulation-language">DML - DATA Manipulation Language</h3>
<p> <code>SELECT, INSERT, UPDATE, DELETE</code>
      데이터를 관리(데이터 검색, 등록, 삭제, 수정)하는 명령</p>
<ul>
<li><p>1) SELECT : 데이터베이스에 있는 데이터 조회/검색 
<code>SELECT 속성명 FROM 테이블명 [WHERE 조건]</code></p>
<pre><code class="language-sql">SELECT {속성_명} FROM {테이블_명};  
ex) SELECT name FROM USER;  
</code></pre>
<h4 id="❤-select문-쿼리-순서">❤ SELECT문 쿼리 순서</h4>
<pre><code class="language-sql">SELECT 
 속성명
/*  
 속성명, 속성명, 속성명     
 *    
 DISTINCT 속성명 
 집계함수(속성명)
*/
FROM 테이블명

WHERE 조건
# AND | OR | BETWEEN ~ AND | NOT | LIKE | IS NULL | IS NOT NULL

GROUP BY
HAVING 조건 집계함수(속성명)
ORDER BY 속성 #ASC|DESC
</code></pre>
</li>
</ul>
<ul>
<li><p>2) INSERT : 데이터베이스에 들어갈 데이터 추가
<code>INSERT INTO 테이블명 VALUES 데이터</code></p>
<pre><code class="language-sql">INSERT INTO {테이블_명}[(속성명1, 속성명2, …)]
VALUES(데이터1, 데이터2, …);  
ex) INSERT INTO user(name, birth) VALUES(&#39;jy&#39;,&#39;890212&#39;);</code></pre>
<ul>
<li><p>3) UPDATE : 데이터베이스에 있는 데이터 수정
<code>UPDATE 테이블명 SET 속성명 = &#39;데이터&#39; [WHERE 조건]</code></p>
<pre><code class="language-sql">UPDATE {테이블_명}
SET {속성명} = {데이터} [[,{속성명} = {데이터}],…]
[WHERE {조건}];
ex) UPDATE user SET name=&#39;chuno&#39; WHERE name=&#39;chu&#39;;</code></pre>
</li>
<li><p>4) DELETE : 데이터베이스에 있는 데이터 삭제
<code>DELETE FROM 테이블명 [WHERE 조건]</code></p>
<pre><code class="language-sql">DELETE FROM {테이블_명}[WHERE {조건}];   
ex) DELETE FROM user WHERE name=&#39;jy&#39;;</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="3-데이터-제어어">3. 데이터 제어어</h2>
<h3 id="1-dcl---data-control-language">1) DCL - Data Control Language</h3>
<p>  <code>GRANT, REVOKE</code>
     데이터베이스에 접근하고, 객체들을 사용하도록 권한을 주고 회수하는 명령</p>
<ul>
<li><p>GRANT : 데이터베이스 사용자에게 사용 권한 부여</p>
<blockquote>
<p>   <strong>권한</strong>
SELECT,INSERT, UPDATE, DELETE, DROP, INDEX,
REFERENCES(테이블의 특정 열을 외래키로 참조 할 수 있는 권한), 
ALL PRIVILEGES(테이블에 대한 모든 권한 부여)</p>
</blockquote>
</li>
</ul>
<pre><code> ```sql</code></pre><p>  GRANT 권한 ON 테이블명 TO 유저
     ```</p>
<ul>
<li>REVOKE : 데이터베이스 사용자에게 사용 권한 회수<pre><code class="language-sql">REVOKE 권한 ON 테이블명 FROM 유저</code></pre>
</li>
</ul>
<h3 id="2-tcl---transaction-control-language">2) TCL - Transaction Control Language</h3>
<p> <code>COMMIT, ROLLBACK, SAVEPOINT</code>
       논리적인 작업의 단위를 묶어 이에 의해 조작된 결과를 작업 단위별로 제어하는 명령어</p>
<ul>
<li><p>COMMIT : 명령에 의해 수행된 결과를 실제 물리적 디스크로 저장하고 데이터베이스 조작 작업이 정상적으로 완료되었음을 알려줌</p>
<ul>
<li><p>ROLLBACK : 데이터베이스 조작 작업이 비정상적으로 종료되었을 때 원래의 상태로 복구</p>
<pre><code class="language-sql">ROLLBACK TO {세이브포인트_명}
ex) ROLLBACK TO s1;     </code></pre>
</li>
<li><p>SAVE POINT :  트랜젝션 내에 ROLLBACK 할 위치인 저장점을 지정하는 명령어</p>
<pre><code class="language-sql">SAVE POINT {세이브포인트_명}
ex) SAVE POINT S1;</code></pre>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB - 관계대수 / 관계해석]]></title>
            <link>https://velog.io/@developer-jyyun/DB-%EA%B4%80%EA%B3%84%EB%8C%80%EC%88%98-%EA%B4%80%EA%B3%84%ED%95%B4%EC%84%9D</link>
            <guid>https://velog.io/@developer-jyyun/DB-%EA%B4%80%EA%B3%84%EB%8C%80%EC%88%98-%EA%B4%80%EA%B3%84%ED%95%B4%EC%84%9D</guid>
            <pubDate>Mon, 25 Mar 2024 00:31:29 GMT</pubDate>
            <description><![CDATA[<h2 id="관계대수--관계해석">관계대수 / 관계해석</h2>
<h3 id="관계대수-절차적">관계대수: 절차적</h3>
<h4 id="--순수관계-연산자">- 순수관계 연산자</h4>
<ul>
<li>σ(Select) <ul>
<li>행 선택(수평적 부분집합)</li>
</ul>
</li>
<li>π(Project) <ul>
<li>열 선택(수평적 부분집합)</li>
</ul>
</li>
<li>⋈(Join) <ul>
<li>공통 속성 합쳐서 새로운 릴레이션 만듦</li>
</ul>
</li>
<li>÷(Division) <ul>
<li>두 릴레이션 A, B에 대해 B의 모든 조건 만족하는 튜플을 A에서 분리하여 프로젝션</li>
</ul>
</li>
</ul>
<h4 id="--일반-집합-연산자--비절차적">- 일반 집합 연산자 : 비절차적</h4>
<ul>
<li>UNION(∪) : 합집합</li>
<li>Intersection(∩) : 교집합</li>
<li>Difference(-) :차집합</li>
<li>CARTESIAN Product(×) : 교차곱 (카티션 프로덕트)</li>
</ul>
<h3 id="관계해석--비절차적">관계해석 : 비절차적</h3>
<h4 id="--연산자">- 연산자</h4>
<ul>
<li>∨ : OR</li>
<li>∧ : AND</li>
<li>￢ :NOT     </li>
</ul>
<h4 id="--정량자">- 정량자</h4>
<ul>
<li>∀ : 모든 가능한 튜플 &quot;For All&quot;</li>
<li>∃ : 어떤 튜플 하나라도 존재</li>
</ul>
<h2 id="예제">예제</h2>
<blockquote>
<p> <strong>관계해석 / 관계대수</strong>
㉠ 프리디키트 해석으로 질의어를 표현한다. 
㉡ 튜플 관계 해석과 도메인 관계해석이 있다.
㉢ 기본적으로 관계해석과 관계대수는 관계 데이터를 처리하는 기능과 능력 면에서 동등하다.
㉣ 원하는 정보와 그 정보를 어떻게 유도하는가를 기술하는 절차적인 언어이다.</p>
</blockquote>
<pre><code>  ㉠ 관계해석 ㉡ 관계해석 ㉢ 관계해석 ㉣ 관계대수</code></pre><blockquote>
<p>①는(은) 원하는 정보와 그 정보를 어떻게 유도하는가를 기술하는 절차적인 특징을 가지며, ②는(은) 원하는 정보가 무엇이라는 것만 정의하는 비절차적인 특징을 가진다. 그러나 ②과(와) ①는(은) 관계 데이터베이스를 처리하는 기능과 능력 면에서 동등하다. ②는(은) 원래 수학의 프레디켓 해석에 기반을 두고 있으며, 관계 데이터 모델의 제안자인 Codd가 특별히 관계 데이터 베이스에 적용할 수 있도록 설계, 제안하였다. </p>
</blockquote>
<pre><code>① 관계대수 ② 관계해석</code></pre><blockquote>
<p> 고객 릴레이션에서 등급이 gold이고 나이가 25 이상인 고객들을 검색하는 관계대수를 작성하시오.</p>
</blockquote>
<pre><code>σ 등급 = ‘gold’ ∧ 나이 ≥ 25 (고객)</code></pre><blockquote>
<p> 고객 릴레이션에서 등급이 gold인 고객의 고객아이디와 등급을 가져오는 관계대수를 작성하시오.</p>
</blockquote>
<pre><code>π 고객아이디, 등급 ( σ 등급 = ‘gold’ (고객) )</code></pre><blockquote>
<p>다음 SQL문장과 동일한 관계대수의 의미를 약술하시오.
  π 이름 ( σ 학과=‘물리학과’ (학생) )</p>
</blockquote>
<pre><code>학생 테이블에서 물리학과인 학생 이름 조회</code></pre><blockquote>
<p>다음 SQL문장과 동일한 관계대수를 작성하시오. 
 SELECT SNO, NAME FROM STUDENT WHERE AGE &gt; 20;</p>
</blockquote>
<pre><code>π SNO, NAME ( σ AGE&gt;20 (STUDENT) )</code></pre><blockquote>
<p>다음 SQL문장과 동일한 관계대수를 작성하시오. 
SELECT name, dept FROM student WHERE year &gt;= 3;</p>
</blockquote>
<pre><code>π name, dept ( σ year≥3 (student) ) </code></pre><blockquote>
<ul>
<li>**  다음 SQL문장과 동일한 관계대수를 작성하시오. </li>
<li>*SELECT SNO, NAME FROM T1, T2 ON T1.SNO = T2.SNO</li>
</ul>
</blockquote>
<pre><code>π SNO, NAME (T1 ⨝T1.SNO = T2.SNO T2)</code></pre><blockquote>
<p>✨ <strong>해설</strong></p>
</blockquote>
<ul>
<li><p>sql - join
<code>SELECT ... FROM 테이블1 JOIN 테이블2 ON 조인조건</code>
<small>조인조건은 두 테이블 간의 관계를 정의하는 부분으로, 대개 <code>테이블1의 속성 = 테이블2</code>의 속성의 형태를 가진다.</small></p>
</li>
<li><p>관계대수 ⋈
<code>테이블명 ⋈ 조건 테이블명</code></p>
</li>
<li><p>다시 풀어보기!</p>
<ul>
<li>÷(Division) 관련 문제!!</li>
<li>db특강 17번 19번</li>
</ul>
</li>
</ul>
<h2 id="🔥-주의-🔥">🔥 주의 🔥</h2>
<blockquote>
<p>✨ <em>a가 b보다 <strong>크거나 같다</strong></em></p>
</blockquote>
<ul>
<li>관계대수 : a ≥ b</li>
<li>sql : a&gt;=b</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[4. 게시글 수정]]></title>
            <link>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84.-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84.-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%88%98%EC%A0%95</guid>
            <pubDate>Thu, 21 Mar 2024 16:53:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌞 MongoDB 데이터 수정하기</p>
</blockquote>
<pre><code class="language-js">await db.collection(&#39;콜렉션이름&#39;).updateOne(
    {게시물정보}, 
    { 업데이트연산자 : {바꿀데이터}}
);</code></pre>
<ul>
<li><a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.updateOne/">공식문서.updateOne()</a><blockquote>
</blockquote>
</li>
<li><code>$set</code> 연산자: 지정한 필드의 값을 변경하거나, 해당 필드가 없는 경우 새로운 필드를 추가(덮어쓰기) </li>
<li><code>$unset</code> 연산자: 특정 필드를 제거합니다. 지정된 필드가 문서에 존재한다면 그 필드가 삭제되며, 필드가 없다면 아무런 작용을 하지 않음.<ul>
<li><code>deleteOne()</code> 메서드와의 차이 : 필드 레벨에서의 변경이 필요한 경우 <code>$unset</code>을 사용하고, 전체 문서를 삭제하고자 하는 경우 <code>.deleteOne()</code>을 사용</li>
<li><code>$inc</code> 연산자: 기존 값이 숫자일 경우 숫자 증감 시 사용. </li>
</ul>
</li>
</ul>
<h2 id="수정페이지-생성">수정페이지 생성.</h2>
<h3 id="--editpage">- EditPage</h3>
<ul>
<li>수정 아이콘 누를 시 해당 id 경로로 동적 라우팅 </li>
<li>.findOne() 메서드 사용하여 해당 게시물id를 가진 글을 DB에서 가져오기</li>
<li>EditForm 컴포넌트에 editData를 prop으로 전달</li>
<li>EditForm <strong>클라이언트 컴포넌트로 분리</strong></li>
</ul>
<pre><code class="language-js">//  app/edit/[id]/page.js
import EditForm from &quot;@/components/EditForm&quot;;
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;

export default async function EditPage({ params }) {
  const { postCollection } = await connectToDatabase();
  let data = await postCollection..findOne()({ _id: new ObjectId(params.id) });  

  const editData = { ...data, _id: data._id.toString() };
  return &lt;EditForm editData={editData} /&gt;;
}
</code></pre>
<h3 id="--editform-컴포넌트">- EditForm 컴포넌트</h3>
<pre><code>&lt;input type=&quot;hidden&quot; name=&quot;_id&quot;value={editData._id}/&gt;</code></pre><ul>
<li>수정할 게시물을 식별하기 위해 _id 값을 폼 데이터에 포함시키고, 폼을 제출할 때 _id도 함께 서버로 전송해줘야 한다!
클라이언트에서 게시물을 수정할 때, 수정하려는 특정 게시물의 _id를 서버에 전달해야 서버 측에서 해당 게시물을 데이터베이스에서 찾아 업데이트할 수 있다.</li>
<li><code>input type=&quot;hidden</code>을 통해 UI에 노출시키지 않을 수 있다.</li>
<li><code>router.refresh()</code> : 이전과 바뀐점을 분석해서 바뀐부분만 새로고침 (soft refresh)</li>
</ul>
<pre><code class="language-js">// src/components/EditForm.js
&quot;use client&quot;;
import styles from &quot;./edit.module.css&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { useEffect, useState } from &quot;react&quot;;

export default function EditForm({ editData }) {
  const [title, setTitle] = useState(&quot;&quot;);
  const [content, setContent] = useState(&quot;&quot;);
  const [message, setMessage] = useState(&quot;&quot;);
  const router = useRouter();

  useEffect(() =&gt; {
    setTitle(editData.title);
    setContent(editData.content);
  }, []);

  const handleChange = (e) =&gt; {
    const { name, value } = e.target;
    if (name === &quot;title&quot;) {
      setTitle(value);
    } else if (name === &quot;content&quot;) {
      setContent(value);
    }
  };

  const handleSubmit = async (e) =&gt; {
    e.preventDefault();
    const _id = e.target._id.value;
    const title = e.target.title.value;
    const content = e.target.content.value;
    try {
      const response = await fetch(&quot;/api/edit&quot;, {
        method: &quot;PATCH&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ _id, title, content }), 
      });

      if (!response.ok) {
        throw new Error(&quot;네트워크 응답이 올바르지 않습니다.&quot;);
      }
      setMessage(&quot;글이 수정되었습니다😊&quot;);
      router.push(&quot;/list&quot;);
      router.refresh();
    } catch (error) {
      console.error(error);
      setMessage(error.message);
    }
  };

  const handleCancel = () =&gt; {
    router.push(&quot;/list&quot;);
  };

  return (
    &lt;div className={styles[&quot;flex-col&quot;]}&gt;
      &lt;h2&gt;게시글 수정&lt;/h2&gt;

      {message &amp;&amp; &lt;p&gt;{message}&lt;/p&gt;}
      &lt;form
        className={`${styles.form} ${styles[&quot;flex-col&quot;]}`}
        onSubmit={handleSubmit}
      &gt;
        &lt;input
          type=&quot;hidden&quot;
          name=&quot;_id&quot;
          defaultValue={editData._id.toString()}
        /&gt;

        &lt;input
          className={styles.input}
          name=&quot;title&quot;
          placeholder=&quot;글제목&quot;
          value={title}
          onChange={handleChange}
        /&gt;
        &lt;textarea
          name=&quot;content&quot;
          cols=&quot;30&quot;
          rows=&quot;10&quot;
          value={content}
          onChange={handleChange}
        /&gt;
        &lt;button type=&quot;button&quot; onClick={handleCancel}&gt;
          취소
        &lt;/button&gt;
        &lt;button type=&quot;submit&quot;&gt;수정&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="--api">- API</h3>
<ul>
<li>updateOne() 메서드 통해 게시물 업데이트</li>
</ul>
<pre><code class="language-js">// /app/api/edit/route.js
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;
import { NextResponse } from &quot;next/server&quot;;

export async function PATCH(req) {
  try {
    // 요청 본문에서 데이터 추출
    const { title, content, _id } = await req.json(); 

    const { postCollection } = await connectToDatabase();
    const result = await postCollection.updateOne(
      { _id: new ObjectId(_id)}, // 업데이트할 게시글 식별
      { $set: { title, content } } // 업데이트할 내용
    );
    console.log(result);

    return NextResponse.json({
      message: &quot;게시글이 성공적으로 수정되었습니다.&quot;,
    });
  } catch (error) {
    console.error(error);
    return NextResponse.json(
      { error: &quot;서버 오류가 발생했습니다.&quot; },
      { status: 500 }
    );
  }
}
</code></pre>
<h2 id="🔥-오늘의-트러블슈팅">🔥 오늘의 트러블슈팅</h2>
<h3 id="👿-error">👿 <strong>error</strong></h3>
<p><em><strong>Warning: Only plain objects can be passed to Client Components from Server Components.</strong></em></p>
<pre><code class="language-js">import EditForm from &quot;@/components/EditForm&quot;;
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;

export default async function EditPage({ params }) {
  const { postCollection } = await connectToDatabase();
  let editData = await postCollection.findOne({ _id: new ObjectId(params.id) });

  /* 
  Warning: Only plain objects can be passed to Client Components from Server Components. Objects with toJSON methods are not supported. Convert it manually to a simple value before passing it to props.
  {_id: {buffer: ...}, title: &quot;hi&quot;, content: ...} */

  return &lt;EditForm editData={editData} /&gt;;
}
</code></pre>
<p>클라이언트 컴포넌트에 plain object가 아닌 객체(예: ObjectId 객체)를 전달하려고 했기 때문에 발생</p>
<h3 id="✨-해결">✨ 해결</h3>
<p>MongoDB에서 가져온 데이터를 클라이언트 컴포넌트로 전달하기 전에 _id 값을 문자열로 변환!</p>
<pre><code class="language-js"> const editData = { ...data, _id: data._id.toString() };</code></pre>
<p>_id 필드가 문자열이 되어 클라이언트 컴포넌트로 안전하게 전달될 수 있다. </p>
<pre><code class="language-js">import EditForm from &quot;@/components/EditForm&quot;;
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;

export default async function EditPage({ params }) {
  const { postCollection } = await connectToDatabase();
  let data = await postCollection.findOne({ _id: new ObjectId(params.id) });

 // MongoDB에서 가져온 데이터를 클라이언트 컴포넌트로 전달하기 전에 _id 값을 문자열로 변환
  const editData = { ...data, _id: data._id.toString() };

  return &lt;EditForm editData={editData} /&gt;;
}
</code></pre>
<h2 id="-진행상황--todo-">:: 진행상황 &amp; TODO ::</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 몽고 DB setting</li>
<li><input checked="" disabled="" type="checkbox"> MongoDB 입출력<ul>
<li><input disabled="" type="checkbox"> 글 목록 조회 기능(/list)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 글 제목,~~ </li>
<li><input disabled="" type="checkbox"> 날짜 데이터바인딩<ul>
<li><input disabled="" type="checkbox"> 글 상세 페이지(/detail/[id]/page.js)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 제목, 내용, </li>
<li><input disabled="" type="checkbox"> 게시 날짜, 댓글 데이터바인딩<ul>
<li><input checked="" disabled="" type="checkbox"> 글 작성 페이지</li>
<li><input checked="" disabled="" type="checkbox"> 글 수정 페이지</li>
<li><input disabled="" type="checkbox"> 글 삭제 페이지</li>
<li><input disabled="" type="checkbox"> S3 파일업로드 </li>
<li><input disabled="" type="checkbox"> Next-auth 회원인증기능</li>
<li><input disabled="" type="checkbox"> 404페이지 만들기</li>
<li><input disabled="" type="checkbox"> 글 작성 페이지 마크다운 작성페이지로 변경</li>
<li><input disabled="" type="checkbox"> 캐싱, 에러처리 등 부가기능</li>
<li><input disabled="" type="checkbox"> 스타일링</li>
<li><input disabled="" type="checkbox"> AWS 클라우드 배포</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Module not found: Can't resolve 'child_process]]></title>
            <link>https://velog.io/@developer-jyyun/Module-not-found-Cant-resolve-childprocess</link>
            <guid>https://velog.io/@developer-jyyun/Module-not-found-Cant-resolve-childprocess</guid>
            <pubDate>Thu, 21 Mar 2024 16:26:29 GMT</pubDate>
            <description><![CDATA[<h2 id="👿-error">👿 error</h2>
<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/ea7a4d40-c7dd-48cb-b7c1-5275450b1a78/image.png" alt="err"></p>
<p>&quot;Module not found: Can&#39;t resolve &#39;child_process&#39;&quot;는  Next.js 애플리케이션에서 서버 측 코드가 클라이언트 측에서 실행되려고 할 때 발생한다. child_process 모듈은 Node.js 환경에서만 사용할 수 있다.</p>
<h2 id="✨-해결">✨ 해결</h2>
<p>서버와 클라이언트 컴포넌트 분리하여 해결
페이지 전체를 클라이언트 컴포넌트로 변경하는 것은 비효율적
필요한 해당 부분만 클라이언트 컴포넌트로 분리하자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Warning: Only plain objects can be passed to Client Components from Server Components.]]></title>
            <link>https://velog.io/@developer-jyyun/Warning-Only-plain-objects-can-be-passed-to-Client-Components-from-Server-Components</link>
            <guid>https://velog.io/@developer-jyyun/Warning-Only-plain-objects-can-be-passed-to-Client-Components-from-Server-Components</guid>
            <pubDate>Thu, 21 Mar 2024 16:04:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/developer-jyyun/post/6fd3783d-a3a7-4eb6-a467-c380174c82ef/image.png" alt="err"></p>
<h2 id="👿-error">👿 <strong>error</strong></h2>
<p><em><strong>Warning: Only plain objects can be passed to Client Components from Server Components.</strong></em></p>
<pre><code class="language-js">import EditForm from &quot;@/components/EditForm&quot;;
import { connectToDatabase } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;;

export default async function EditPage({ params }) {
  const { postCollection } = await connectToDatabase();
  let editData = await postCollection.findOne({ _id: new ObjectId(params.id) });


  return &lt;EditForm editData={editData} /&gt;;
}
</code></pre>
<p>클라이언트 컴포넌트에 plain object가 아닌 객체(예: ObjectId 객체)를 전달하려고 했기 때문에 발생</p>
<h2 id="✨-해결">✨ 해결</h2>
<p>MongoDB에서 가져온 데이터를 클라이언트 컴포넌트로 전달하기 전에 _id 값을 문자열로 변환!</p>
<pre><code class="language-js"> const editData = { ...data, _id: data._id.toString() };</code></pre>
<p>toString() 메서드를 통해 _id 필드가 문자열이 되어 클라이언트 컴포넌트로 안전하게 전달될 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[3. api 구성 및 라우팅 설정(app router와 pages router 비교)]]></title>
            <link>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83.-api</link>
            <guid>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83.-api</guid>
            <pubDate>Tue, 19 Mar 2024 17:12:28 GMT</pubDate>
            <description><![CDATA[<p>App router 사용해야지! 하고선 api routes 방식으로 작성한 api폴더를 냅다 app폴더로 이동시키고 에러와 싸웠다..
항상 공식문서를 확인하자...⭐</p>
<p>기존 미니프로젝트에서는 페이지라우터를, 팀 파이널 프로젝트에서는 앱라우터를 사용했었는데
백엔드와의 협업 프로젝트였기 때문에 외부 API를 호출하는 방식으로만 진행되어 API Routes, Route Handlers 모두 사용할 기회가 없었다.</p>
<p>혼자 next.js를 공부했을 때는 페이지라우터 방식을 우선적으로 접했기 때문에 api routes가 익숙한 상태..
Route Handlers 사용은 익숙하지 않아서인지 너무..복잡하당......😭</p>
<p>그래서 정리하는 Pages router의 API Routes 코드 =&gt; App router의 Route Handlers와 비교하고 코드 수정하기</p>
<h2 id="공식문서-비교하기">공식문서 비교하기</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/routing/api-routes">pages router :: API Routes
</a></li>
<li><a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">app router :: Route Handlers
</a></li>
</ul>
<br>



<h2 id="app-router---route-handlers">App Router - Route Handlers</h2>
<ul>
<li><p><strong>구조</strong>: /app/api/ 디렉토리 내에 라우트를 정의
예) /app/api/text/route.js 파일에 API 로직을 구현</p>
</li>
<li><p>** 명시적인 HTTP 메서드 이름 사용<strong>: App Router 방식에서는 파일 내에 직접 HTTP 메서드 이름(</strong>GET, POST, PUT, DELETE** 등)을 사용하여 함수를 정의. 별도의 req, res 매개변수가 필요 없다.
(특정 경우에 요청 객체에서 추가 데이터를 추출하거나, 요청의 다른 속성을 사용해야 할 때 req 매개변수를 함수에 추가)</p>
<pre><code class="language-js">export async function GET() {
  // GET 요청을 처리하는 로직
}
export async function POST() {
  // POST 요청을 처리하는 로직
}</code></pre>
</li>
<li><p><strong>응답 객체 생성 방식: *<em>: *</em>NextResponse와 표준 Response 객체</strong>를 사용하여 HTTP 응답을 생성하고 반환할 수 있다.</p>
<ul>
<li><strong>NextResponse.json(data)</strong>을 사용하면, <strong>자동으로 Content-Type 헤더가 application/json으로 설정</strong>되고, <strong><strong>상태 코드는 기본적으로 200 OK</strong></strong>로 설정된다. 따라서 별도로 상태 코드나 헤더를 지정할 필요가 없다.</li>
</ul>
<pre><code class="language-js">// App Router_ return NextResponse 방식 예시
return NextResponse.json(
    { error: &quot;서버 오류가 발생했습니다.&quot; },
    { status: 500 }
  );
  &gt;
// App Router_ return NextResponse 방식 예시2
export async function GET() {
  const data = await db.collection(&quot;post&quot;).find().toArray();
  return NextResponse.json(data);}
}
`

- **Response** 객체는 Fetch API의 일부로, HTTP 응답을 나타낸다. **new Response(body, options)**을 통해 생성할 수 있으며, options 객체를 통해 상태 코드, 헤더 등을 명시적으로 설정할 수 있다.
  - **에러 응답 반환**: 에러 상황이 발생했을 때, new Response()를 사용하여 에러 메시지와 함께 커스터마이즈된 응답 객체를 생성하고 이를 반환한다.

```js
// App Router_ return new Response 방식 예시
return new Response(JSON.stringify({ error: &quot;에러 메시지&quot; }), {
  status: 400,
  headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
});

// App Router_ return new Response 방식 예시2
export async function GET() {
  const data = await db.collection(&quot;post&quot;).find().toArray();
  return new Response(JSON.stringify(data), {
    status: 200,
    headers: {
      &quot;Content-Type&quot;: &quot;application/json&quot;,
    },
  });
}</code></pre>
</li>
</ul>
<h2 id="pages-router---api-routes">Pages Router - api routes</h2>
<ul>
<li><strong>구조</strong>: /pages/api/ 디렉토리 내에 API 엔드포인트를 정의한다.
예) /pages/api/test.js 파일에서 API 로직을 구현합니다.</li>
<li><strong>handler 함수 내에서 HTTP 메서드 분기 처리</strong>: Pages Router 방식에서는 주로 handler라는 이름의 함수를 사용하여 모든 요청을 처리한다. 함수 내부에서 req.method를 확인하여 HTTP 메서드에 따라 다른 로직을 실행하게 된다.
```js
// Pages Router 방식 예시
export default async function handler(req, res) {
   // req로부터 요청 데이터 접근, res를 통해 응답 전송
  if (req.method === &quot;GET&quot;) {<pre><code>// GET 요청을 처리하는 로직, </code></pre>  } else if (req.method === &quot;POST&quot;) {<pre><code>// POST 요청을 처리하는 로직, </code></pre>  }
}
`</li>
<li><strong>응답 객체 생성 방식: *<em>: *</em>res.status().json() 메서드 체이닝을 사용</strong>하여 HTTP 상태 코드와 JSON 응답을 클라이언트에 전송한다. Next.js에서 제공하는 기본 응답 메서드를 활용한다.</li>
<li>직관적인 응답 처리: 에러 상황을 처리하기 위해 <strong>res 객체의 메서드</strong>를 사용하여 직관적으로 응답을 구성하고 클라이언트에 전송한다.<pre><code class="language-js">// Pages Router 방식 예시
res.status(400).json({ error: &quot;에러 메시지&quot; });</code></pre>
</li>
</ul>
<h2 id="게시글-작성-기능-구현">게시글 작성 기능 구현</h2>
<details>
  <summary> 기존 pages router 코드 (GET)</summary>     

<pre><code class="language-js">// /pages/api/post/new.js
import { connectDB } from &quot;@/util/database&quot;

export default async function handler(req, res){
  const db = (await connectDB).db(&quot;board&quot;)
  let result = await db.collection(&#39;post&#39;).find().toArray()
  res.status(200).json(result)
}</code></pre>
</details>

<details>
  <summary>기존 pages router 코드 (POST)</summary>     

<pre><code class="language-js">
  // pages/api/post/new.js

import { connectDB } from &quot;@/util/database&quot;;

export default async function handler(req, res) {
  if (req.method === &quot;POST&quot;) {
    console.log(req.body); //유저가 보낸 데이터
    if (req.body.title === &quot;&quot; || req.body.content === &quot;&quot;) {
      return res.status(500).json(&quot;제목과 내용을 모두 입력 해주세요&quot;);
    }
    try {
      const db = (await connectDB).db(&quot;blog&quot;);
      const result = await db.collection(&quot;post&quot;).insertOne(req.body);
      return res.status(200).redirect(302, &quot;/list&quot;);
    } catch (error) {
      console.error(error);
    }
  }
}</code></pre>
<pre><code class="language-js">// /src/app/write/page.js
export default function Write() {
  return (
    &lt;div&gt;
      &lt;h2&gt;글 작성&lt;/h2&gt;
      &lt;form action=&quot;/api/post/new&quot; method=&quot;POST&quot;&gt;
        &lt;input name=&quot;title&quot; placeholder=&quot;글제목&quot; /&gt;
        &lt;textarea name=&quot;content&quot; placeholder=&quot;글내용&quot; cols=&quot;30&quot; rows=&quot;10&quot;&gt;&lt;/textarea&gt;
        &lt;button type=&quot;button&quot;&gt;글 작성&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
</details>


<h3 id="app-router---route-handlers-적용">App Router - Route Handlers 적용</h3>
<h4 id="get요청-api-작성하기">GET요청 api 작성하기</h4>
<p>mongo db의 전체 게시글 목록 불러오기! </p>
<pre><code class="language-js">// /app/api/new/route.js
const client = await connectDB;
const db = client.db(&quot;board&quot;);

export async function GET() {
  const data = await db.collection(&quot;post&quot;).find().toArray();
  return NextResponse.json(data);
}</code></pre>
<h4 id="post-요청-처리하는-api-작성하기">POST 요청 처리하는 api 작성하기</h4>
<blockquote>
<p>** 🌞 MongoDB에 데이터 저장하는 법
**await db.collection(&#39;collection이름&#39;).insertOne(저장할object자료) </p>
</blockquote>
<ul>
<li><a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.insertOne/">공식문서.insertOne()</a></li>
<li><a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">app router :: Route Handlers</a><pre><code class="language-js">// /app/api/new/route.js
</code></pre>
</li>
</ul>
<p>import { connectDB } from &quot;@/utils/database&quot;;</p>
<p>const client = await connectDB;
const db = client.db(&quot;board&quot;);</p>
<p>export async function POST(req, res) {
  try {
    const data = await req.json(); // 요청 본문 파싱
    console.log(data);
    if (!data.title || data.title.trim() === &quot;&quot;) {
      return NextResponse.json(
        { error: &quot;제목은 필수입니다.&quot; },
        { status: 400 }
      );
    }
    await db.collection(&quot;post&quot;).insertOne(data);
    return NextResponse.json(data);
  } catch (error) {
    console.error(error);
    return NextResponse.json(
      { error: &quot;서버 오류가 발생했습니다.&quot; },
      { status: 500 }
    );
  }
}</p>
<pre><code>#### 게시물 등록하기 - handleSubmit v1

handleSubmit 함수 작성하기.
처음엔 평소 익숙한 방법으로 작성하였다.  
폼의 각 입력 필드에서 직접 데이터를 가져와서 객체형태로 전달하기!
기존 코드에 제목, 내용이 비어있을 경우 에러처리, 메시지를 추가해주었다.

```js
  const handleSubmit = async (e) =&gt; {
    e.preventDefault();
    const title = e.target.title.value;
    const content = e.target.content.value;

    try {
      if (title.length === 0 &amp;&amp; content.length === 0) {
        throw new Error(&quot;모든 항목을 입력해주세요.&quot;);
      } else if (title.length === 0) {
        throw new Error(&quot;제목을 입력해주세요.&quot;);
      } else if (content.length === 0) {
        throw new Error(&quot;내용을 입력해주세요&quot;);
      }

      const response = await fetch(&quot;/api/new&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ title, content }),
      });

      if (!response.ok) {
        throw new Error(&quot;네트워크 응답이 올바르지 않습니다.&quot;);
      }

      setMessage(&quot;새 글이 등록되었습니다😊&quot;);
      router.push(&quot;/list&quot;);
    } catch (error) {
      console.error(error, &quot;&quot;);
      setMessage(error.message);
    }
  };</code></pre><p>그러던 중 new FormData, Object.fromEntries(formData)에 대한 코드를 알게 되어 새롭게 적용해보기로 했다!</p>
<blockquote>
<p><strong>new FormData(e.target)</strong>는 폼에 입력된 데이터를 FormData 객체로 변환.
이 객체는 폼 필드 각각을 키-값 쌍으로 저장한다.</p>
<p><strong>Object.fromEntries(formData)</strong>는 이 FormData 객체를 일반 JavaScript 객체로 변환하는 과정이다.</p>
<p>formData의 키-값 쌍을 사용해 객체의 속성과 값을 설정한다. 이렇게 변환된 객체는 JSON으로 쉽게 변환될 수 있어, 서버로 데이터를 보낼 때 사용하기 편리하다.</p>
</blockquote>
<ul>
<li>예시</li>
</ul>
<pre><code>  &lt;input name=&quot;username&quot; value=&quot;JohnDoe&quot;&gt; 와 같은 입력 필드가 있다면, 
  FormData 객체 내에서 이 필드는 &quot;username&quot;: &quot;JohnDoe&quot;라는 키-값 쌍으로 저장되고,
  폼을 제출할 때 이러한 키-값 쌍이 FormData 객체에 자동으로 설정된다!</code></pre><h4 id="게시물-등록하기---handlesubmit-v2">게시물 등록하기 - handleSubmit v2</h4>
<pre><code class="language-js">const handleSubmit = async (e) =&gt; {
    e.preventDefault();
    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);
    try {

      //.get() 메서드를 사용하여 특정 키에 대한 값을 검색할 수 있다.
      const title = formData.get(&quot;title&quot;);
      const content = formData.get(&quot;content&quot;);

      if (!title.trim() &amp;&amp; !content.trim()) {
       // 객체에서는 length의 직접적인 사용이 불가.
       // if (!title || title.trim().length === 0)와 같이
       // length 속성을 사용하기 전에 null 체크를 수행해야 함.
       // title이 null이 아닌 경우에만 title.length를 사용할 수 있다.


        throw new Error(&quot;모든 항목을 입력해주세요.&quot;);
      } else if (!title.trim()) {
        throw new Error(&quot;제목을 입력해주세요.&quot;);
      } else if (!content.trim()) {
        throw new Error(&quot;내용을 입력해주세요&quot;);
      }

      const response = await fetch(&quot;/api/new&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error(&quot;네트워크 응답이 올바르지 않습니다.&quot;);
      }

      setMessage(&quot;새 글이 등록되었습니다😊&quot;);
      router.push(&quot;/list&quot;);
    } catch (error) {
      console.error(error, &quot;&quot;);
      setMessage(`${error.message} `);
    }
  };</code></pre>
<p>v1: 폼의 각 입력 필드에서 직접 데이터를 가져오는 방식. 
폼의 필드 수가 적고, 처리해야 할 데이터가 많지 않을 때 적합</p>
<p>v2: FormData 객체와 Object.fromEntries()를 사용하여 폼 데이터를 처리. 폼의 필드가 많고, 다루어야 할 데이터가 복잡하거나 동적일 때 적절하다. 코드가 좀 더 일반화되어 있어 다양한 폼에 쉽게 적용할 수 있다.</p>
<p>결론은 폼의 필드수가 적기 때문에 다시 v1 코드로 돌아갔다.</p>
<h4 id="게시물-등록하기----전체-코드">게시물 등록하기 -  전체 코드</h4>
<pre><code class="language-js">// /src/app/write/page.js
&quot;use client&quot;;

import { useRouter } from &quot;next/navigation&quot;;
import { useState } from &quot;react&quot;;
import styles from &quot;./write.module.css&quot;;

export default function WritePage() {
  const router = useRouter();
  const [message, setMessage] = useState(&quot;&quot;);

  const handleSubmit = async (e) =&gt; {
    e.preventDefault();
    const title = e.target.title.value;
    const content = e.target.content.value;

    try {
      if (title.length === 0 &amp;&amp; content.length === 0) {
        throw new Error(&quot;모든 항목을 입력해주세요.&quot;);
      } else if (title.length === 0) {
        throw new Error(&quot;제목을 입력해주세요.&quot;);
      } else if (content.length === 0) {
        throw new Error(&quot;내용을 입력해주세요&quot;);
      }

      const response = await fetch(&quot;/api/new&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ title, content }),
      });

      if (!response.ok) {
        throw new Error(&quot;네트워크 응답이 올바르지 않습니다.&quot;);
      }

      setMessage(&quot;새 글이 등록되었습니다😊&quot;);
      router.push(&quot;/list&quot;);
    } catch (error) {
      console.error(error, &quot;&quot;);
      setMessage(error.message);
    }
  };
  return (
    &lt;div className={styles[&quot;flex-col&quot;]}&gt;
      &lt;h2&gt;게시물 작성&lt;/h2&gt;

      {message &amp;&amp; &lt;p&gt;{message}&lt;/p&gt;}
      &lt;form
        className={`${styles.form} ${styles[&quot;flex-col&quot;]}`}
        onSubmit={handleSubmit}
      &gt;
        &lt;input className={styles.input} name=&quot;title&quot; placeholder=&quot;글제목&quot; /&gt;
        &lt;textarea name=&quot;content&quot; cols=&quot;30&quot; rows=&quot;10&quot; /&gt;
        &lt;button type=&quot;submit&quot;&gt;글 작성&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>확실히 pages router 방식이 훨씬 편하당...................</p>
<h2 id="-진행상황--todo-">:: 진행상황 &amp; TODO ::</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 몽고 DB setting</li>
<li><input checked="" disabled="" type="checkbox"> MongoDB 입출력<ul>
<li><input disabled="" type="checkbox"> 글 목록 조회 기능(/list)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 글 제목,~~ </li>
<li><input disabled="" type="checkbox"> 날짜 데이터바인딩<ul>
<li><input disabled="" type="checkbox"> 글 상세 페이지(/detail/[id]/page.js)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 제목, 내용, </li>
<li><input disabled="" type="checkbox"> 게시 날짜, 댓글 데이터바인딩<ul>
<li><input checked="" disabled="" type="checkbox"> 글 작성 페이지</li>
<li><input disabled="" type="checkbox"> 글 수정 페이지</li>
<li><input disabled="" type="checkbox"> 글 삭제 페이지</li>
<li><input disabled="" type="checkbox"> S3 파일업로드 </li>
<li><input disabled="" type="checkbox"> Next-auth 회원인증기능</li>
<li><input disabled="" type="checkbox"> 404페이지 만들기</li>
<li><input disabled="" type="checkbox"> 글 작성 페이지 마크다운 작성페이지로 변경</li>
<li><input disabled="" type="checkbox"> 캐싱, 에러처리 등 부가기능</li>
<li><input disabled="" type="checkbox"> 스타일링</li>
<li><input disabled="" type="checkbox"> AWS 클라우드 배포</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2. 데이터출력]]></title>
            <link>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2.-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@developer-jyyun/Next.js-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2.-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Tue, 19 Mar 2024 17:09:00 GMT</pubDate>
            <description><![CDATA[<h2 id="listpage">ListPage</h2>
<blockquote>
<p>** 🌞 MongoDB에서 데이터 가져오기 
**await db.collection(&#39;collection이름&#39;).find()</p>
</blockquote>
<ul>
<li><p><a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.find/">공식문서.find()</a></p>
</li>
<li><p>find() 메서드는 주어진 조건과 일치하는 모든 문서(데이터)를 검색합니다.  </p>
</li>
<li><p>검색 결과는 커서(cursor) 형태로 반환됩니다. </p>
</li>
<li><p>이 커서는 문서의 컬렉션을 나타내며, 이를 통해 반복(iterate)하거나, 필요한 만큼의 문서를 가져오는 등의 작업을 할 수 있습니다.</p>
</li>
<li><p>결과가 없는 경우에는 비어 있는 커서를 반환합니다.</p>
<ul>
<li>예시:   db.collection(&#39;posts&#39;).find({author: &#39;John Doe&#39;})는 
&#39;John Doe&#39;가 작성한 모든 게시물을 찾아 커서를 반환합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-js">  let data = await db.collection(&quot;post&quot;).find().toArray();
</code></pre>
<p>  .find()메서드 통해 데이터 불러와서 배열로 출력한 뒤, 글 목록 컴포넌트 생성 후 map() 메서드 통해서 데이터 불러와 title 데이터바인딩 해주었다.
날짜는 이후에..</p>
<details>
  <summary> ListPage 전체코드</summary>  

<pre><code class="language-js">import { connectDB } from &quot;@/utils/database&quot;;
import styles from &quot;./list.module.css&quot;;
import Link from &quot;next/link&quot;;

export default async function ListPage() {
  const client = await connectDB;
  const db = client.db(&quot;board&quot;);
  let postData = await db.collection(&quot;post&quot;).find().toArray();

  return (
    &lt;ul className={styles[&quot;list-bg&quot;]}&gt;
      {postData.map((item, index) =&gt; (
        &lt;li className={styles[&quot;list-item&quot;]} key={item._id}&gt;
          &lt;Link href={`/detail/${item._id}`}&gt;
            &lt;h4&gt;{item.title}&lt;/h4&gt;
            &lt;p&gt;1월 1일&lt;/p&gt;
          &lt;/Link&gt;
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
</code></pre>
</details>


<h2 id="detailpage">DetailPage</h2>
<ul>
<li>게시글 목록 클릭 시 해당 게시물 보여주기 (/detail/[id]/page.js)<ul>
<li><a href="https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes">Dynamic Routes</a><ul>
<li><a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.findOne/#mongodb-method-db.collection.findOne">findOne({key:value})</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>** 🌞 MongoDB에서 데이터 하나만 가져오기
**await db.collection(&#39;collection이름&#39;).findOne ({찾고 싶은 key:&quot;찾고 싶은 value&quot;}) </p>
</blockquote>
<h4 id="👿-씬나게-작성한-에러나는-코드">👿 씬나게 작성한 에러나는 코드...</h4>
<pre><code class="language-js">import { connectDB } from &quot;@/utils/database&quot;;

export default async function DetailPage({ params }) {
  const client = await connectDB;
  const db = client.db(&quot;board&quot;);
  let data = await db.collection(&quot;post&quot;).findOne({
    _id: params.id,
  });
  console.log(data);
  return (
    &lt;div&gt;
      &lt;p&gt;DetailPage :{params.id}&lt;/p&gt;
      &lt;h2&gt;{data.title}&lt;/h2&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>내가 이해한 것 : findOne ({찾고 싶은 key:&quot;value&quot;}) 넣어 주면 데이터 중 해당 항목만 불러옴
동적 라우팅을 통해 params로 id를 받아올 수 있었기 때문에 아래와 같이 params.id를 냅다 넣어줬다.
DetailPage :{params.id}를 통해 id가 제대로 출력되는 것도 확인    </p>
<pre><code class="language-js">      let data = await db.collection(&quot;post&quot;).findOne({
    _id: params.id,
  });
</code></pre>
<p>  근데 왜 안돼???????????????????????????</p>
<p>  정신 똑띠 차리고 콘솔 확인...또 확인했는데
<img src="https://velog.velcdn.com/images/developer-jyyun/post/d9919f11-1ecc-46bd-9388-ef14c1e95df2/image.png" alt="console" style="display:inline-block; text-align:left;">
  _id: id가 아니라
  _id: new ObjectId(&#39;어쩌구저쩌구엄청긴id값&#39;)이넹...😂
  코드를 아래와 같이 수정해주니 드디어 나온다!!</p>
<pre><code class="language-js">// board\src\app\detail\[id]\page.js
import { connectDB } from &quot;@/utils/database&quot;;
import { ObjectId } from &quot;mongodb&quot;; //import 필요!!

export default async function DetailPage({ params }) {
  const client = await connectDB;
  const db = client.db(&quot;board&quot;);

  const data = await db.collection(&quot;post&quot;).findOne({
    _id: new ObjectId(params.id),
  });

  return (
    &lt;div&gt;
      &lt;h2&gt;{data.title}&lt;/h2&gt;
      &lt;p&gt;{data.content}&lt;/p&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>import { ObjectId } from &quot;mongodb&quot;; 임포트도 잊지 말자!</p>
<br>

<h2 id="-진행상황--todo-">:: 진행상황 &amp; TODO ::</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 몽고 DB setting</li>
<li><input checked="" disabled="" type="checkbox"> MongoDB 입출력<ul>
<li><input disabled="" type="checkbox"> 글 목록 조회 기능(/list)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 글 제목,~~ </li>
<li><input disabled="" type="checkbox"> 날짜 데이터바인딩<ul>
<li><input disabled="" type="checkbox"> 글 상세 페이지(/detail/[id]/page.js)</li>
</ul>
</li>
<li><input checked="" disabled="" type="checkbox"> 제목, 내용, </li>
<li><input disabled="" type="checkbox"> 게시 날짜, 댓글 데이터바인딩<ul>
<li><input checked="" disabled="" type="checkbox"> 글 작성 페이지</li>
<li><input disabled="" type="checkbox"> 글 수정 페이지</li>
<li><input disabled="" type="checkbox"> 글 삭제 페이지</li>
<li><input disabled="" type="checkbox"> S3 파일업로드 </li>
<li><input disabled="" type="checkbox"> Next-auth 회원인증기능</li>
<li><input disabled="" type="checkbox"> 404페이지 만들기</li>
<li><input disabled="" type="checkbox"> 글 작성 페이지 마크다운 작성페이지로 변경</li>
<li><input disabled="" type="checkbox"> 캐싱, 에러처리 등 부가기능</li>
<li><input disabled="" type="checkbox"> 스타일링</li>
<li><input disabled="" type="checkbox"> AWS 클라우드 배포</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>