Svelte 전면을 AWS 후면과 연결해 보십시오

개시하다


이전에는 Svelte를 할 때 ① TODO 애플리케이션(프런트만 있음)을 만들었고, 이후 AWS Amplify에서 시작한 뒤 ② Amplify로 TODO 백엔드를 만들었는데, 여기에 도착하면 ①와 ②를 스트리밍으로 연결해보자.그런 거야.
①와 ②의 기사는 다음과 같다.
https://zenn.dev/masaino/articles/5750eccd109cdf
https://zenn.dev/masaino/articles/080f998d73351f

완성하다


죄송합니다. 완성된 프레젠테이션 화면입니다.
todo_demo
구성은 이런 느낌.
 ┌──────────┐     ┌──────────┐      ┌──────────┐     ┌──────────┐
 │ Frontend │     │  APIGW   │      │  Lambda  │     │          │
 │ (Svelte) ├─────┤► (REST)  ├──────►   with   ├─────► DynamoDB │
 │          │     │          │      │  RubySDK │     │          │
 └──────────┘     └──────────┘      └──────────┘     └──────────┘

코드


Svelte


마지막으로 프런트엔드에서만 이동한 버전으로, API 호출로 변경됨
Todo.svelte
<script>
  const baseURL = 'https://<api-id>.execute-api.<region>.amazonaws.com/dev/tasks'

  let taskName = ""
  let checked = false
  let modalOn = false

  // sort
  function compare( a, b ) {
    console.log('compare called')
    if ( a['updated-at'] > b['updated-at'] ){ return -1; }
    if ( a['updated-at'] < b['updated-at'] ){ return 1; }
    return 0;
  }

  function sortByCreatedAt(todos){
    console.log('sortBy called')
    todos.then(data => {
      data = data.sort(compare);
    });
    return todos
  }

  function statusText(taskStatus){ return taskStatus ? "Active" : "Done" }

  const listTasks = async () => {
    console.log('listTasks called')
    const res = await fetch(baseURL);
    return await sortByCreatedAt(res.json())
  }

  const getTask = async (taskId) => {
    console.log('getTask called')
    const task = await fetch(baseURL + '/' +taskId);
    return await task.json()
  }

  const addTask = async (taskName) => {
    console.log('addTask called!')

    let taskData = {
      "task-id": Date.now().toString(36),
      "is-active": true,
      "task-name": "",
      "updated-at": Date.now().toString(),
      "created-at": Date.now().toString(),
      "user-id": "100"
    }

    taskData['task-name'] = taskName;
    
    const res = await fetch(baseURL, {
      method: 'POST',
      body: JSON.stringify(taskData)
    });
    todos = listTasks()
  }

  const deleteTask = async (taskId) => {
    console.log('deleteTask called!')
    const res = await fetch(baseURL + '/' + taskId, {
      method: 'DELETE',
    });
    todos = listTasks() // assignment to ignite the reactivity
  }

  const completeTask = async (taskId, status) => {
    const res = await fetch(baseURL + '/' + taskId, {
      method: 'PUT',
      body: JSON.stringify({ 'task-id': taskId, 'is-active': !status })
    });
    todos = listTasks()
  }

  let taskDetail = ''
  function showDetail(taskId) {
    console.log('showDetail called')
    modalOn = true
    //return getTask(taskId)
    taskDetail = getTask(taskId)
  }

  // format date
  function fd(date) {
    console.log('fd called')
    let d = new Date(parseInt(date))
    return d.getFullYear() + '/' + d.getMonth() + '/' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes()
  }

  // todos
  let todos = listTasks(); // Initialized as Promise
  $: activeTodos =  checked ? todos : todos.then(data => data.filter(todo => todo['is-active']))

</script>

<main>

  <h1>HELLO TODO!</h1>
  <div class="container is-fluid">
    <div class="columns is-centered">
      <div class="field has-addons">
        <div class="control" style="margin-bottom: 5em;">
          <input class="input" type="text" placeholder="Add a task" bind:value={taskName}>
        </div>
        <div class="control">
          <a class="button is-danger" on:click={() => addTask(taskName)}><i class="fas fa-plus"></i></a>
        </div>
      </div>
    </div>

    <div class="columns" style="width: 200px; margin-left: 60%; margin-bottom: 3em">
      <div class="field">
        <input id="switchRoundedDanger" type="checkbox" name="switchRoundedDanger" class="switch is-rounded is-danger is-rtl" bind:checked={checked}>
        <label for="switchRoundedDanger">Show completed</label>
      </div>
    </div>

    <div class="columns is-centered">
        {#await activeTodos}
          <span class="spinner-loader" style="margin-top: 10em">Loading…</span>
        {:then tasks}
        <table class="table is-hoverable">
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Created at</th>
              <th>Status</th>
              <th>Action</th>
            </tr>
          </thead>
          <tbody>
            {#each tasks as task}
              <tr>
                <td>{task['task-id']}</td>
                <td>{task['task-name']}</td>
                <td>{fd(task['created-at'])}</td>
                <td>{statusText(task['is-active'])}</td>
                <td>
                  <i class="fas fa-check" on:click={() => completeTask(task['task-id'], task['is-active'])}></i> 
                  <i class="fas fa-info" on:click={() => showDetail(task['task-id'])}></i>
                  <i class="far fa-trash-alt" on:click={() => deleteTask(task['task-id'])}></i>
                </td>
              </tr>
            {/each}
          </tbody>
        </table>
        {/await}
    </div>

    <div class="modal { modalOn ? 'is-active' : ''}">
      <div class="modal-background"></div>
      <div class="modal-content">
        <dev class="box">
        {#await taskDetail}
          <span class="spinner-loader" style="margin-top: 10em">Loading…</span>
        {:then td}
        <table class="table is-fullwidth">
          <thead>
            <tr><td>key</td><td>value</td></tr>
          </thead>
          <tbody>
            <tr><td>task-id</td><td><span class="tag">{td['task-id']}</span></td></tr>
            <tr><td>task-name</td><td><span class="tag">{td['task-name']}</span></td></tr>
            <tr><td>created-at</td><td><span class="tag">{fd(td['created-at'])}</span></td></tr>
            <tr><td>updated-at</td><td><span class="tag">{fd(td['updated-at'])}</span></td></tr>
            <tr><td>is-active</td><td><span class="tag">{td['is-active']}</span></td></tr>
            <tr><td>user-id</td><td><span class="tag">{td['user-id']}</span></td></tr>
          </tbody>
        </table>
        {/await}
        </dev>
      </div>
      <button class="modal-close is-large" aria-label="close" on:click="{() => modalOn = false}"></button>
    </div>

  </div>
</main>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
  }

  h1 {
    color: #ff3e00;
    text-transform: uppercase;
    font-size: 4em;
    font-weight: 100;
    margin-bottom: 1.5em;
  }

  td i {
    padding: 5px;
    cursor: pointer;
    color: gray;
  }

  label { font-family: monospace; }
  table { font-family: monospace; }

  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

Lambda


이전 ②의 메일박스 내용과 거의 같다...
lambda_handler.rb
require 'json'
require 'json/add/exception'
require 'aws-sdk-dynamodb'

CORS_HEADER = {
  "Access-Control-Allow-Headers": "Content-Type",
  "Access-Control-Allow-Origin": '*',
  "Access-Control-Allow-Methods": "OPTIONS,POST,PUT,GET"
}

def add_task(table, body)
  begin
    table.put_item({ item: body })  
    { statusCode: 200, headers: CORS_HEADER, body: JSON.generate(body) }
  rescue => e
    { statusCode: 500, body: e.to_json }
  end
end

def delete_task(table, task_id)
  begin
    params = { table_name: table, key: { 'task-id': task_id } }
    table.delete_item(params)
    list_task(table)
  rescue => e
    { statusCode: 500, body: e.to_json }
  end
end

def update_task(table, body)
  begin
    params = {
      table_name: table,
      key: { 'task-id': body['task-id'] },
      attribute_updates: {
        'is-active': { value: body['is-active'], action: "PUT" },
      }
    }
    table.update_item(params) 
    { statusCode: 200, headers: CORS_HEADER, body: JSON.generate(body) }
  rescue => e
    { statusCode: 500, headers: CORS_HEADER, body: e.to_json }
  end
end

def list_task(table)
  begin
    scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" })
    { statusCode: 200, headers: CORS_HEADER, body: JSON.generate(scan_output['items']) }
  rescue => e
    { statusCode: 500, body: e.to_json }
  end
end

def get_task(table, task_id)
  begin
    params = { key: { 'task-id': task_id } }
    task = table.get_item(params)
    { statusCode: 200, headers: CORS_HEADER, body: JSON.generate(task['item']) }
  rescue => e
    { statusCode: 500, body: e.to_json }
  end
end

def lambda_handler(event:, context:)
 
  begin
    http_method = event['httpMethod']
    dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2')
    table = dynamodb.table('todoTable-dev')
    
    case http_method
      when 'GET'
        path_param = event.dig('pathParameters', 'proxy')
        if path_param.nil?
          list_task(table)
        else
          get_task(table, path_param) 
        end
      when 'PUT'    then update_task(table, JSON.parse(event['body']))
      when 'POST'   then result = add_task(table, JSON.parse(event['body']))
      when 'DELETE' then delete_task(table, event['pathParameters']['proxy'])
      else 0
    end
  rescue => e
    { statusCode: 500, body: e.to_json }
  end

end

학습적인 것, 혹은 단지 감상일 뿐이다


토도 앱이라고 만만치 않게 볼 수는 없다.특히 새로운 틀의 작업을 배워야 한다

Svelte 편


1. Await Block은 변태지만 느낌이 좋아요.


처음 보시면 오, 거푸집await으로 만드셨나요!이렇게 생각하는 작법은 괜찮다.
    <h1>Task list</h1>
    {#await todos} 
      <p>Loading...</p>
    {:then tasks}
      {#each tasks as task}
        <p>{task['task-id']}, {task['task-name']}</p>
      {/each}
    {/await}

2.on: 클릭을 통해 발화 함수에 매개 변수를 전달하는 방법


매개 변수 함수 없이 호출하면 돼요.
 <button on:click="{listTask}"></button>
매개 변수가 있다면 이렇게 쓰면 클릭할 때가 아니라 바로 실행합니다
대신
 <button on:click="{getTask(taskId)}"></button>
https://stackoverflow.com/questions/58262380/how-to-pass-parameters-to-onclick-in-svelte

3. 한 동작에 두 함수를 호출할 수 없다


베이처럼 흠뻑 젖을 줄 알았는데 안 될 줄 몰랐어요.응, 앞에 있는 함수에서 뒤에 있는 함수라고 부르면 돼.
 <button on:click="{() => getTask(taskId)}"></button>
https://github.com/sveltejs/svelte/issues/2109

4. selected 등 단축키 사용


상자와 모형을 열 때selectedclass.
 <button on:click="{listTask; getTask}"></button>
이렇게 쓰여있어요.
<style>
  /* ...other CSS... */
  span.cell.selected {
    outline-color: lightblue;
    outline-style: dotted;
  }
</style>

<span class="cell {selected === true ? 'selected' : ''}">
  {value}
</span>
더 나아가 단락수로 이렇게 한다
<span class="cell" class:selected="{selected}">
  {value}
</span>
너무 좋아요.하지만 클라스 이름에 하이픈을 사용하면 움직이지 않아 곤란하다.
출처는 다음과 같다.
https://flaviocopes.com/svelte-dynamically-apply-css/

CORS 편


1.fetch의 no-cors는 만능이 아니다.PUT이면 가만히 있어요.


CORS 주변 사람들은 한번 빠지면 속상해해요.이럴 때no-cors 꼭 도망갈 수 있는 건 아니다.

2. 오류 메시지에 CORS 헤드를 추가하면 디버깅이 용이합니다.


APIGW500의 경우 동일하게 사용 가능

JavaScript 편


1. JS의 데이트는 여전히 고통스럽다


쓰기 싫다

2. Promise 탈출 불가


안에 있는 것만 원하고, 안에 있는 것만 말하려고 아무리 노력해도 보답이 있을 거야Promise.반드시 관계를 잘 해야 한다

Loading CSS에서 이 편집 사용


이런 작은 곳에서는 분위기가 변하기 때문에 조금만 만들어도 동력이 오래 간다.
http://www.css-spinners.com/

DynamoDB 편


1. 정렬은 검색에서 하는 것이 아니라 책상에 정렬 키를 추가합니다...


Firebase였으면 좋겠는데...

2. 정렬 키를 잊어버리면 테이블 다시 만들기


DynamoDB doesn't allow to add a sort key for an already existing table. Your only option is to create a new table with redefined key attributes and then copy the data from the existing table to the newly created table.
정말?무대 위에서 순서를 정했다.

3. :return_values 조금 더


업데이트 시스템을 처리할 때 수치는 돌려주지만 업데이트 전 값ALL_OLD 같은 거라서 잘 안 돼요.당신은 어떤 응용 프로그램을 구상하고 있습니까?

람바다 편


처음에는 고객이 편하다고 생각했는데 람바다 쪽에서 포스터와 퓨트를 받으면 업데이트된 미션인 크리스토퍼 GET를 돌려보내지만 포기하는 게 좋을 것 같다.각 요구 사항은 모두 Atomic이므로 프런트부터 조작하는 것이 그리 번거롭지 않다
수고하셨습니다.

좋은 웹페이지 즐겨찾기