17.0 Introduction

Live streaming 구현하려면
엄청난 데이터를 처리해야 해서 강력한 서버가 필요하다.
인코딩, format변경(mp4) 해야 함.
데이터 집약적, 컴퓨팅 집약적임.

역시 우린 서버리스이기 때문에 서버가 없다.


OBS 라이브방송 - 리얼 스트리머들많이 사용함.
or
LiveNow 앱

라이브에서는 RTMP (Real-time message protocol) 사용함.

17.1 Streaming From Dashboard
모든 스트리밍을 하면
URL과 key를 줄텐데 이거 가져가면 키드내핑 당함.
일단 클라우드플레어 같은데서 결제하고 라이브 스트리밍을 해.

그리고 앞서 말한 OBS라이브방송이나 LiveNow같은 앱에 url과 key를 입력하면
OBS에서 보이는게 (https://obsproject.com/) cloudFlare에 올라가게 됨.

OBS

30초 가량 딜레이가 있다.

17.2 Streaming API
이제 API 사용서 해보자.
https://developers.cloudflare.com/stream/stream-live/start-stream-live/

 

Start a live stream · Cloudflare Stream docs

You can start a live stream using the Stream dashboard or the API. After you subscribe to Stream, you can create Live Inputs and begin sending your live video …

developers.cloudflare.com

돈내야해서 안하기로 함.

코드는 작성함.

 const {
  result: {
    uid,
    rtmps: { streamKey, url },
  },
} = await (
  await fetch(
    // https://developers.cloudflare.com/stream/stream-live/start-stream-live/
    `https://api.cloudflare.com/client/v4/accounts/${process.env.CF_ID}/stream/live_inputs`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.CF_STREAM_TOKEN}`,
      },
      body: `{"meta": {"name":${name}},"recording": { "mode": "automatic", "timeoudSeconds":10 }}`,
    }
  )
).json();
cloudflareId: uid, //비디오 보기 위함
cloudflareKey: streamKey, // 방송의 오너에게 줘야 함.
cloudflareUrl: url,// 방송의 오너에게 줘야 함.
sugar댓글

Using the Stream player (스트림 플레이어 사용)
아래 iframe 태그를 사용하여 웹 페이지에 스트림 플레이어를 넣을 수 있습니다.
$VIDEOID에는 비디오 UID(또는 서명된 토큰)을 지정해줍니다.
```
< iframe
src="https://iframe.videodelivery.net/$VIDEOID"
style="border: none"
height="720"
width="1280"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowfullscreen="true"
>< /iframe>
```
https://developers.cloudflare.com/stream/viewing-videos/using-the-stream-player/

@cloudflare/stream-react
Cloudflare Stream용 공식 React 컴포넌트
https://www.npmjs.com/package/@cloudflare/stream-react

Displaying thumbnails (썸네일 표시)
방법 1) 즉석에서 썸네일 생성
https://videodelivery.net/${VIDEO_ID}/thumbnails/thumbnail.jpg?time=68s&height=270
방법 2) API를 사용하여 기본 썸네일 타임스탬프 설정
방법 3) 애니메이션 썸네일 생성
https://developers.cloudflare.com/stream/viewing-videos/displaying-thumbnails/

스트리머는 아래 사진의 주황색 부분이 보일텐데 URL,Key 갖고 하면 된다.
OBS나 LiveNow에 해당 정보 넣으면 됨.

https://developers.cloudflare.com/stream/stream-live/watch-live-stream/#view-by-live-input-uid

By sugar 댓글

View by live input uid
/lifecycle 엔드포인트에 GET 요청을 보내 비디오 ID 또는 라이브 입력 uid에 대한 추가 데이터를 가져올 수 있습니다.
https://developers.cloudflare.com/stream/stream-live/watch-live-stream/#view-by-live-input-uid

Live viewer counts for third party players (실시간 시청자 수 보기)
스트림 플레이어는 기본적으로 실시간 시청자 수를 완벽하게 지원합니다. third party player와 함께 사용할 라이브 비디오의 시청자 수를 얻으려면 /views 엔드포인트에 GET 요청을 하십시오.
```
// request
https://videodelivery.net/55b9b5ce48c3968c6b514c458959d6a/views

// response
{"liveViewers": 113}
```
https://developers.cloudflare.com/stream/getting-analytics/live-viewer-count/

View by video id (녹화된 비디오 정보 가져오기)
https://developers.cloudflare.com/stream/stream-live/watch-live-stream/#view-by-video-id

Replaying recordings
https://developers.cloudflare.com/stream/stream-live/watch-live-stream/#replaying-recordings


18 Code Challenge
이제 구현은 다 끝났고 nextjs 배워보자.
그 전에,
1. 판매자와 대화하기(Talk to seller),
2. 예약중 기능, 예약날자 지나면 리뷰남겨달라는 버튼을 생성해주자.?실제 당근처럼 하면 되겠지.
인런거 연습하면 매우 좋을듯.
꼭 해야 할듯.

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

16 NEXTJS IMAGES  (0) 2023.01.12
15 CLOUDFLARE IMAGES - 사진 다루기  (0) 2023.01.11
14 Streams - seeding  (0) 2023.01.11
13 Profile  (0) 2023.01.09
12 동네생활  (0) 2023.01.06

16.0 Introduction
next.js에서는 img 태그 쓰지마라.
<Image> 써라.

import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} automatically provided
        // height={500} automatically provided
        // blurDataURL="data:..." automatically provided
        // placeholder="blur" // Optional blur-up while loading
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}

이미지 import해서 변수처럼 쓰면된다.

https://nextjs.org/docs/basic-features/image-optimization

Improved Performance: Always serve correctly sized image for each device, using modern image formatsVisual Stability: Prevent Cumulative Layout Shift automatically
Faster Page Loads: Images are only loaded when they enter the viewport, with optional blur-up placeholdersAsset Flexibility: On-demand image resizing, even for images stored on remote server

 

16.1 Local Images
local image와 remote image 다루는게 다르다.
blurry 버전의 이미지 기능은 local image만 가능. 파일을 갖고있어야 함.
_next/image API Handler로 이미지 

http://127.0.0.1:3000/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flocal.a1148338.jpg&w=3840&q=75
뒤의 Q는 퀄리티. 1~100까지.

blurry 이미지 적용하기  = placeholder에 blur적으면 됨.

<Image src={myImage} placeholder="blur" quality={100}/>

 

16.2 Remote Images
API로 받아오는 이미지 최적화 어떻게 하는지 알아보자.
nextjs는 이미지 서버를 물어본다.
next.config.js에 다음과 같이 작성.
imagedelivery.net은 cloudflare의 서버임.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ["imagedelivery.net"],
  },
};

module.exports = nextConfig;

근데 또 에러 뜸

Server Error

Error: Image with src "https://imagedelivery.net/0dNSIQsfAsyuUaSo7XjPgQ/undefined/public" is missing required "width" property.

16.3 Layout Fill
next.js는 이미지 사이즈를 알아야 한다. URL에 실제로 width넣을 필요 없음.
실제 화면에 보여질 사이즈만 알면 된다.

next.js는 내 서버에 이미지를 요청하고, 내가 태그에 적은 사이즈대로 이미지 생성 함.
그래서 실제 사용하는건 cloudflare에 있는게 아님.
로컬처럼 cloud 서비스 사용함.
https://nextjs.org/docs/api-reference/next/image#fill

<div className="relative pb-80">
    <Image
      src={`https://imagedelivery.net/0dNSIQsfAsyuUaSo7XjPgQ/${data?.product?.image}/public`}
      className="h-96 bg-slate-300 object-sacle-down"
      fill
    />
    <h1 className="absolute w-full text-center text-red-500">Hi there</h1>
  </div>

위처럼 relative주면 Image는 기본적으로 abolute라서 안보이게 할 수 있고,
위 태그 처럼 하면 배경이미지로 사용가능.
그리고 object-fit 사용하면 아주 좋음.
https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit

 

object-fit - CSS: Cascading Style Sheets | MDN

The object-fit CSS property sets how the content of a replaced element, such as an <img> or <video>, should be resized to fit its container.

developer.mozilla.org

근데 next.js 13부터는 좀 바뀜
https://nextjs.org/docs/api-reference/next/legacy/image#layout

 

next/legacy/image | Next.js

Backwards compatible Image Optimization with the Legacy Image component.

nextjs.org

16.4 Conclusions:
remote image 작업할 경우.블러 효과 할 수 없음.
blurDataURL= 이런식으로 가능... 근데 굳이.

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

17 LIVE STREAMING, 18 Code Challenge  (0) 2023.01.13
15 CLOUDFLARE IMAGES - 사진 다루기  (0) 2023.01.11
14 Streams - seeding  (0) 2023.01.11
13 Profile  (0) 2023.01.09
12 동네생활  (0) 2023.01.06

이제 훅 연습은 끝!!

15.0 Introduction
cloudfare images로 이미지를 추가해보자~
egress cost = 대역폭(bandwidth에 요금 내는거)
이미지 100,000개에 5달러.
이미지 resizing,최적화에도 돈 안받음.

15.1 Image Preview
blob: Binary Large Object
A binary large object is a collection of binary data stored as a single entity. Blobs are typically images, audio or other multimedia objects, though sometimes binary executable code is stored as a blob. Wikipedia
하나의 entity로 저장된 커다란 이진수 파일임. 주로 멀티미디어 객체가 되겠다.

<input
              {...register("avatar")}
              id="picture"
              type="file"
              className="hidden"
              accept="image/*"
            />

type을 file로 해서 아래처럼 url을 만들면

const file = avatar[0]; //이제 브라우저의 memory에 있겠지
console.log(URL.createObjectURL(file));
//브라우저가 만든 URL이 return됨

브라우저 메모리에 저장된 내가 올린 파일의 URL이 생김.
blob:http://localhost:3000/ead74686-349f-45fc-92d9-769f5ce35922
그럼 다른 브라우저에서는 될까? 안되야겠지?
해보니 안된다

브라우저에게 사용자가 올린 사진에 대한 엑세스를 URL을 통해 달라고 하는 것.
참고로 호출할 때마다 새로운 객체 URL을 생성해서 각각의 URL을 더는 쓰지 않을 땐 URL.revokeObjectURL()을 사용해서 해제해줘야 한단다.

15.2 Direct Creator Uploads
https://developers.cloudflare.com/images/cloudflare-images/upload-images/direct-creator-upload/

 API Token을 사용하는 방식을 이용했을 때
우리 서버가 있다면(우린serverless지)
유저가 올린 사진을 우리 서버에 올리고, 우리서버에서 cloudflare에 올려야 함.
서버가 있다면 이상한 방법이 아니긴 한데 우리 서버에 data가 왔다갔다 하는게 다 돈임.
그러니 유저 브라우저가 하게 하자.
유저에게 secure URL을 주고, 유저는 그 URL에 업로드하게 한다.
token같은거 유출 될일 없다.

Direct Creator Upload
작동원리:
1. 유저 사진 업로드 원함
2. 백엔드에서 key같은걸로 안전하게 CloudFlare(CF)에 URL달라고 요청
3. CF는 빈파일의  URL을 만들어서 백엔드에 주고 백엔드는 유저에게 .
4. 그럼 유저는 그 URL을 이용해 이미지를 direct로 올린다.

참고로 AWS에도 있다고 한다.
lambda 같이 써서 해야 한단다.
presigned url
ref: https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/PresignedUrlUploadObject.html

 
15.2 Direct Creator Uploads
const cloudflareRequest = await fetch(`/api/files`);
const cloudflareURL = await cloudflareRequest.json();

//shortened
const cloudflareRequest = await (await fetch(`/api/files`)).json();​



15.3 Cloudflare Setup
15.4 Direct Upload URL
15.5 Cloudflare Upload

{
    "result": {
      "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
      "uploadURL": "https://upload.imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901"
    },
    "result_info": null,
    "success": true,
    "errors": [],
    "messages": []
  }

 

 

uploadURL은 30분뒤 만료 되니 id만 BE에 저장하자.

file의 세번째 인자는 파일 이름.

15.6 Serving Images
15.7 Resizing Images
URL뒤에 variant(변형)을 둬서 미리 설정한 변형대로 리사이징 해줌.
15.8 Product Images


'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

17 LIVE STREAMING, 18 Code Challenge  (0) 2023.01.13
16 NEXTJS IMAGES  (0) 2023.01.12
14 Streams - seeding  (0) 2023.01.11
13 Profile  (0) 2023.01.09
12 동네생활  (0) 2023.01.06

14.0 Upload Form
늘 하던 패턴의 반복.
Model -> Form(useForm()) -> useMutation() 
14.1 Detail Page
14.2 Send Message
14.3 See Message
14.4 Mutations and Refresh
14.5 Seeding - prisma의 seeding 가짜 데이트베이스 많이 생성 하는 법.

1. seed.ts파일 생성

seed.ts파일 생성

2. 한 유저가 많이 생성 한것처럼 코드짜고

import { PrismaClient } from "@prisma/client";
// next.js의 바깥쪽

const client = new PrismaClient();

async function main() {
  [...Array.from(Array(500).keys())].forEach(async (item) => {
    await client.stream.create({
      data: {
        name: String(item),
        description: String(item),
        price: item,
        user: {
          connect: {
            id: 13, //내 아이디
          },
        },
      },
    });
    console.log(`${item}/500`);
  });
}
main()
  .catch((e) => console.log(e))
  .finally(() => client.$disconnect());


3. npm i ts-node 설치(https://www.npmjs.com/package/ts-node)
이게 뭐냐? TypeScript execution and REPL for node.js, with source map and native ESM support.
REPL이란  : Node. js Read-Eval-Print-Loop (REPL) is an easy-to-use command-line tool, used for processing Node. js expressions. It captures the user's JavaScript code inputs, interprets, and evaluates the result of this code. It displays the result to the screen, and repeats the process till the user quits the shell.
브라우저 개발자 모드에 있는 console대신 쓸수 있는 듯

이거말하는듯

5. package.json맨 아래에 해당 명령어 추

6. 그리고 바로 npx prisma db seed 명령어 치면 에러 남.
SyntaxError: Cannot use import statement outside a module

7. 그럼 package.json에서 다음처럼 수정
참고: https://www.prisma.io/docs/guides/database/seed-database 

그리고 . npx prisma db seed다시 치면
데이터 400개쯤 만들다가 get kicked out 됨.
왜? planetscale은 무료로 1000개정도 커넥션 제공하는데 이건 문제가 안됨.
https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-pool#external-connection-poolers

대신, pool time out 때문에 그렇다.
Pool은 데이터 모으는거고, pool timeout은 이게 정해진 시간 넘도록 특정 쿼리 처리 못하면 exception을 던지고 다음 쿼리로 감.
The default connection pool timeout is 10 seconds. If the query engine does not process a particular query within that time, it throws an exception and moves on to the next query in the queue.
근데 serverless가 아닌 일반 서버는 데이터베이스가 허용하는 연결의 수 제한이 매우 작음
근데 우리는 planetscale이 1000개의 동시연결이 가능하단걸 prisma에게 알려주지 않았다.
그래서 prisma에서 시간 초과가 되었다. 우린 일반 DB보다 훨씬 빨랐기 때문에!

연결 limit을 늘리거나, 커넥션이 더 길어져도 된다면 그걸 늘리면 됨.
schema.prisma 파일에  추가
https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-pool#external-connection-poolers

 

datasource db { provider = "postgresql" url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=2" }





14.6 Pagination
Try not to fry our database
아래처럼 포함하고 있는 관계에도 pagination할 수 있다. 
아니 무조건 해야 한다. 

다 돈이거든

 

prisma 로그 출력하는 방법


참고로 표준출력이란 stdout: https://ko.wikipedia.org/wiki/%ED%91%9C%EC%A4%80_%EC%8A%A4%ED%8A%B8%EB%A6%BC

SELECT `carrot-market`.`Stream`.`id`, `carrot-market`.`Stream`.`created`, `carrot-market`.`Stream`.`updated`, `carrot-market`.`Stream`.`name`, `carrot-market`.`Stream`.`description`, `carrot-market`.`Stream`.`price`, `carrot-market`.`Stream`.`userId` FROM `carrot-market`.`Stream` WHERE 1=1 ORDER BY `carrot-market`.`Stream`.`id` ASC LIMIT ? OFFSET ?

 

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

16 NEXTJS IMAGES  (0) 2023.01.12
15 CLOUDFLARE IMAGES - 사진 다루기  (0) 2023.01.11
13 Profile  (0) 2023.01.09
12 동네생활  (0) 2023.01.06
11 Products  (0) 2023.01.04

이번 섹션은 구현의 반복

13.0 Models 

Review 모델은 User를 두번 가리켜야 한다.
내가 작성한 리뷰, 나에게 작성된 리뷰.
이러면 관계가 애매하다고 경고가 뜨는데
이때, relation에 name을 지어주면 된다.
그러면 user.writtenRiviews 이런 식으로 접근 가능.

모델만들기 -> 기능 만들기 -> 다른 모델 만들기 -> 다른 기능 만들기(x)
모델들 다 만들기 -> 기능 구현해나가기(o)

Enum - model의 모든 필드들이 같고 이름만 다를 때 사용
구조상 Purchase, Fav, Sale이 같음.
그럼 다음처럼 쓰면 된다.

SQL 레벨에선 enum이 저렇게 보인다.

 

13.1 Reviews
사용하던 모델(레코드를 생성했던) 새로운 필드를 추가하면 당연히 에러가 뜨겠지?

그러면 선택은 3가지

1. 기존 DB모두 삭제
2. 새로운 필드를 필수가 아닌 것으로 두기 score        Int?
3. 기본값 두기 score        Int      @default(1)

13.2 Handlers 
만약 Record라는 모델에 enum을 쓰는방식을 썼다면 다음 사진처럼 작성하면 됨.

13.3 Profile Page
13.4 Sales, Purchases, Favorites
13.5 Edit Profile part One
TODO
ENTER 페이지 빼고 모두 useUser사용해서 로그인 여부 체크해야 함.
로그인 이용자들만 볼 수 있게.
https://react-hook-form.com/api/useform/formstate
13.6 Edit Profile part Two

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

15 CLOUDFLARE IMAGES - 사진 다루기  (0) 2023.01.11
14 Streams - seeding  (0) 2023.01.11
12 동네생활  (0) 2023.01.06
11 Products  (0) 2023.01.04
10 AUTHORIZATION - SWR  (0) 2023.01.04

12.0 Models
포스트와 그 포스트에 대한 궁금증과, 답변, 궁금해요 모델 추가.

model Post {
  id         Int         @id @default(autoincrement())
  created    DateTime    @default(now())
  updated    DateTime    @updatedAt
  user       User        @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId     Int
  question   String      @db.MediumText
  answers    Answer[]
  wonderings Wondering[]

  @@index([userId])
}

model Answer {
  id      Int      @id @default(autoincrement())
  created DateTime @default(now())
  updated DateTime @updatedAt
  user    User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId  Int
  post    Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  postId  Int
  answer  String   @db.MediumText

  @@index([userId])
  @@index([postId])
}

model Wondering {
  id      Int      @id @default(autoincrement())
  created DateTime @default(now())
  updated DateTime @updatedAt
  user    User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId  Int
  post    Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  postId  Int

  @@index([userId])
  @@index([postId])
}

 

12.1 Forms and Handlers
12.2 Post Detail
12.3 궁금해요
12.4 Answer
12.5 All Posts

12.6 useCoords
12.7 Geo Search
12.8 Geo Bug


const posts = await client.post.findMany({
        include: {
          user: {
            select: {
              id: true,
              name: true,
              avatar: true,
            },
          },
          _count: {
            select: {
              wonderings: true,
              answers: true,
            },
          },
        },
        where: {
          latitude: { gte: parsedLatitude - 0.01, lte: parsedLatitude + 0.01 },
          longitude: {
            gte: parsedLongitude - 0.01,
            lte: parsedLongitude + 0.01,
          },
        },
      });

 

Type 'number' is not assignable to type 'null'.ts(2322)
해결 null as any로 바꾸면 된다는데

 
next.js에서는 페이지를 미리 만들어 놓기 때문에, 초기값으로 그려진다.

그래서 longitude, latitude가 null이었으니 제대로 동작 안하지. 

 

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

14 Streams - seeding  (0) 2023.01.11
13 Profile  (0) 2023.01.09
11 Products  (0) 2023.01.04
10 AUTHORIZATION - SWR  (0) 2023.01.04
9 AUTHENTICATION  (0) 2022.12.27

11.0 Product Model
hot reloading을 하면 일종의 서버 재시작이 돼서
PrismaClient가 계속 new로 생성됨.
DB에 커넥션 리밋이 있음. Planet Scale에 리밋이 있을 것이다.
좋지 않은 것이니. 고쳐

항상 다음 순으로 한다.
Model 생성 -> data base 수정 -> mutation -> 데이터 가져오기

Model 생성

model Product {
  id          Int      @id @default(autoincrement())
  created     DateTime @default(now())
  updated     DateTime @updatedAt
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId      Int
  image       String
  name        String
  price       Int
  description String   @db.MediumText

  @@index([userId])
}

 

11.1 Upload Form
11.2 Upload API

  const { name, price, description } = req.body;
  const { user } = req.session;
  // vs
  const {
    body: { name, price, description },
    session: { user },
  } = req;


Prisma client(back end)의 type을 갖다 쓸 수있다.

 

11.3 See Products
프로덕트에서 상품 정보와 상품등록한 계정의 정보 선택적으로 가져오기.

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  console.log(req.query);
  const { id } = req.query;
  const product = await client.product.findUnique({
    where: {
      id: Number(id),
    },
    include: {
      user: {
        select: {
          id: true,
          name: true,
          avatar: true,
        },
      },
    },
  });
  res.json({ ok: true, product });
}

UI: skeleton 로딩화면 tailwind css로 만드는 법
https://www.section.io/engineering-education/skeleton-loading-in-nextjs-with-tailwindcss/

11.4 Product Detail
11.5 Related products

interface extends 해서 type만들기

prisma client의 search 조건
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#filter-conditions-and-operators

Docs에 있는 것을 아래처럼 구현

 

 

11.6 Favorite Products

import { NextApiRequest, NextApiResponse } from "next";
import withHandler, { ResponseType } from "@libs/server/withHandler";
import client from "@libs/server/client";
import { withApiSession } from "@libs/server/withSession";

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  const {
    query: { id },
    session: { user },
  } = req;
  const alreadyExists = await client.fav.findFirst({
    where: {
      productId: Number(id),
      userId: user?.id,
    },
  });
  if (alreadyExists) {
    await client.fav.delete({
      //delete하려면 unique 필드가 필요함
      where: {
        id: alreadyExists.id,
      },
    });
  } else {
    await client.fav.create({
      data: {
        user: {
          connect: {
            id: Number(user?.id),
          },
        },
        product: {
          connect: {
            id: Number(id),
          },
        },
      },
    });
  }
  res.json({ ok: true });
}
export default withApiSession(
  withHandler({
    methods: ["POST"],
    handler,
  })
);


11.7 Favorite Products part Two
Optimistic UI Update - 어차피 API성공할테니 응답 기다리지 말고 일단 UI바꾸기

  const [toggleFav] = useMutation(`/api/products/${router.query.id}/fav`);
  const onFavClick = () => {
    toggleFav({});
  };

toggleFav({})는 POST니까 바디 보내야 하지만 empty 바디 보내도 됨.


11.8 Bound Mutations

useSWR로 data와 mutate를 받을 수 있다.
mutate()의 처음 인자는 변경할 데이터, 두번째 인자는 해당 API를 재 호출 할지 선택하는 옵션이다. default는 true.
첫번째 mutate함수 처럼 사용하면, 기존의 swr 캐에 저장된 data가 인자 {product:{name:"potato"}}로 치환 되기 때문에 화면에 potato 관련 정보만 보이고 나머지는 다 날아가버린다.
그래서 두번 째 mutate처럼 써야한다.
원래 data를 전개해서 넣고, product안을 수정하는데 product에서도 나머지는 전개해서 넣고, name만 바꾼다.

이건 그냥 자바스크립트 문법.

다음 처럼 하면 favorite이 Optimistic UI Update 되는거야.
캐시만 변경하는거지.
mutate({ ...data, isLiked: !data.isLiked }, false);

두번째 인자가 false니까 다시 api 불러오지 않게
그리고 주석처리 된 toggleFave({})도 주석풀어야 실제 API콜이 될테니까.
최종본

그럼 왜 bound냐? 왜 bound mutation이냐?
useSWR로 받아온 mutate와 짝지어진 데이터만 변경 가능해서.
다른 request로 받은 놈의 cache도 바꾸고싶다?
다른 화면의 다른 요청의 cache를 바꾸고싶다?
unbound mutation !!

11.9 Unound Mutations

import useSWR, { useSWRConfig } from "swr";
const { mutate } = useSWRConfig();
mutate("/api/users/me", (prev) => prev && { ok: !prev.ok }, false);
//prev -> 현재 caching된 데이터 가져오기. 어디서든 접근 가능.
mutate("/api/users/me") //이렇게 하면 그냥 업데이트

 

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

13 Profile  (0) 2023.01.09
12 동네생활  (0) 2023.01.06
10 AUTHORIZATION - SWR  (0) 2023.01.04
9 AUTHENTICATION  (0) 2022.12.27
8 REFACTORING  (0) 2022.12.27

authentication = 유저가 누구인지 알아내는 것.
authorization = 유저가 권한이 있는지 알아내는 것.

10.0 Introduction
사용자가
incognito 모드로 접속하면 req.session.user?.id 사용 불가.
따라서 에러가 남. 이렇게 검증되지 않은 요청으로부터 핸들러를 보호해보자.

10.1 Protected Handlers

아래 코드 처럼 flag를 저렇게 넣는건 bad practice임. 
어떤것도 유추 할 수 없으니.

export default withApiSession(withHandler("GET", handler, true, false, true));

그래서 두개 이상의 arguments가 있으면 객체로 설정값을 보내라.

export default withApiSession(
  withHandler({
    method: "GET",
    handler,
    isPrivate: true,
  })
);

그럼 훨씬 self explanatory[ɪk│splænətɔːri] 하지.

10.2 useUser Hook
return router.push("/enter"); push는 페이지 이동이라 브라우저 history에 남음
return router.replace("/enter"); //남지않음
페이지가 성공적으로 로드 됐을 때만 히스토리에 남음

페이지마다 같은 api( 유저정보불러오기)를 다시 불러오면 좋지 않지.
메모리에 담아서 캐싱해. SWR을 써보자.

10.3 SWR (Stale-While-Revalidate = http 캐시 무효화 전략)
SWR은 캐싱된 데이터가 있으면 일단 화면을 그리고,
백그라운드에서 API call을 보내서 
데이터가 바뀐게 있는지 체크하고, 있으면 업데이트 한다.
SWR 설치
npm install swr

참고로 저렇게 중복 메시지가 나오는 이유는 리액트가 컴포넌트 2개 만들었기 때문. 그리고
react strict mode이기 때문. next.config.js에서 reactStrictMode를 false로 바꾸면됨.
근데 true해야 에러 더 잘 잡음.

useSWR 은 처음 인자로 url을 받는데 이를 key라고 하고, 두번째는 함수를 받고 fetcher라고 한다.
그래서 URL을 이용하여 fetcher함수를 통해 api call을 하고 data를 주고 에러가 났을 경우 error를 준다.

앞서 말한 key는 데이터의 id이기도 하다.
useSWR 안에는 super_cache라는게있다.

super_cache = {
	"/api/users/me" : {
        "ok": true,
        "profile": {
            "id": 10,
            "phone": "123456",
            "email": null,
            "name": "Anonymous",
            "avatar": null,
            "created": "2022-12-28T07:04:45.224Z",
            "updated": "2022-12-28T07:04:45.224Z"
        }
    }
}

이 super_cache는 앱 전체에서 공유되기 때문에
앱 어디서든 유저가 해당 url로 api을 하면 캐시를 준다!!!
당연히 업데이트 되면 스리슬쩍 바꿔주지 로딩 보여줄 필요 없이.
mutation도있는데 나중에 like 기능 있을 때보고.

useSWR은 유저가 다른 탭에 갔다가 돌아오면 데이터 새로고침 해줌. ㄷㄷ
아무도 모르게...

10.4 useUser Refactor
근데 매번 fetcher를 쓰는 건 번거로움
그래서 

const fetcher = (url: string) => fetch(url).then((response) => response.json());

그래서 _app.tsx에서 SWRConfig 컴포넌트를  불러와서 최상위 엘리먼트를 감싸면 됨!

'클론코딩-캐럿마켓 > 전반' 카테고리의 다른 글

12 동네생활  (0) 2023.01.06
11 Products  (0) 2023.01.04
9 AUTHENTICATION  (0) 2022.12.27
8 REFACTORING  (0) 2022.12.27
7 REACT HOOK FORM  (0) 2022.12.26

+ Recent posts