[firebase실강] 4강

youngseo·2022년 7월 8일
0

FireBase

목록 보기
4/9
post-thumbnail

4강

firebase 환경 세팅

//emuls
$ npm run fb:emuls
//TS:watch function
$ npm run build:watch

emuls를 껐다켜도 DB유지

  • emuls 껐다켜는 경우 데이터에 있는 모든 정보가 날아가게 됩니다. 이를 방지하기 위한 설정을 해보도록 하겠습니다.

루트경로의 package.json

  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "fb:login": "firebase login",
    "fb:logout": "firebase logout",
    "fb:init": "firebase init",
    "fb:emuls": "firebase emulators:start --import=./exports --export-on-exit","fb:deploy": "firebase deplay"
  },

"fb:emuls"에 위와 같은 코드를 추가해줍니다.폴더경로는 원하는 경로로 해도 괜찮습니다.

job 따로 저장

  • 이전 시간에 만든 addDelted함수는 더이상 사용되지 않기에 지워도 괜찮습니다.
  • src> jobs폴더를 만들어 index.ts파일로 addDelted함수를 잘라서 붙여 보관을 해도 좋습니다.
import * as admin from 'firebase-admin'
const db = admin.firestore()

//job
async function addDeleted() {
  const snaps = await db.collection('Todos').get()

  for(const snap of snaps.docs){
    await snap.ref.update({
      deleted: false
    })
  }

  console.log('끝')
}


하나의 job입니다.

🐥파일 업로드 기능

루트 경로에 있는 src가 vite프로젝트입니다. 여기에 파일을 업로드 하는 기능을 만들어보겠습니다.

1. 터미널 하나 추가

  • 새로운 터미널을 하나 더 만들어 hosting이라는 이름으로 설정을 해줍니다.
//hosting

$ npm run dev

2. Src> App.vue

<template>
  <input
    type="file"
    @change="selectFile" />
</template>
<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({
  methods: {
    selectFile(event: Event) {
      const files = (event.target as HTMLInputElement).files as FileList
      for(let i = 0; i<files.length; i++) {
        const file = files[i]
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.addEventListener('load', e => {
          console.log((e.target as FileReader).result)
        })
      }
    }
  }
})
</script>

이렇게 파일을 문자화한 데이터를 서버에 전송하고 DB에 저장하는 과정을 해볼 예정입니다. DB에는 많은 정보가 들어갈 수 있지만 무거운 정보를 넣는 것은 권장하지 않습니다. 따라서 이미지는 파일 형태에 따로 스토로지에 저장을 합니다. 그리고 DB에는 스토로지에 저장된 파일의 주소를 저장합니다.

3.todo 추가

todo.ts

투두 아이템을 만들거나 추가할 때 이미지를 넣을 수 있는 구조를 만들어보겠습니다.

interface Todo {
  id?: string
  title: string,
  image?: string, //✅
  done: boolean
  createdAt: string
  updateAt: string,
  deleted: boolean
}
...
// 투두추가
router.post('/', async (req, res) => {
  const { title, imageBase64 } = req.body
  const date = new Date().toISOString()

  //스토로지에 파일 저장, bucket은 하나의 폴더정도로 이해해도 좋습니다.
  const bucket = admin.storage().bucket('images')
  const [, body] = imageBase64.split(',')
    //실제 파일화를 하려면 buffer라는 객체가 필요합니다.
  const buffer = Buffer.from(body, 'base64')  
  const file = bucket.file('image.png')
  await file.save(buffer)

  //변수로 만들어야 재활용이 가능합니다.
  const todo: Todo = {
    //documnet내용작성
    title,
    image: 'http://localhost:9199/images/image.png', //스토리지 포트번호
    done: false,
    createdAt: date,
    updateAt: date,
    //IOS는 국제표준시로 date를 생성합니다.
    deleted: false
  }
  1. images를 꺼내옵니다.
  2. 이미지가 저장된 주소를 저장할 예정입니다.
  3. 스토로지에 저장할 코드를 작성합니다.
    • const bucket = admin.storage().bucket('images')
    • bucket()은 하나의 폴더개념으로 이해하면 쉽습니다. images라는 이미지파일을 bucket()이라는 폴더에 담습니다.
  4. 현재 초록박스 부분은 파일의 정보이지 내용이 아닙니다. 따라서 bucket에 저장하기 위해서는 앞부분을 제거해줘야합니다.
  • split으로 배열구조로 나눈 후 header,body를 배열 구조분해 할당으로 가져옵니다. 실제 사용할 데이터는 body뿐임으로 아래와 같이 정리를 해줍니다.
  • const [, body] = imageBase64.split(',')
  1. 만든 body부분을 데이터화 하려면 buffer객체가 필요합니다.
  • const buffer = Buffer.from(body, 'base64')
  1. 이렇게 만든 buffer를 저장합니다.
    -const file = bucket.file('image.png')
  2. 저장을 위해 save()메소드를 사용합니다.
  • await file.save(buffer)
  1. image에 파일의 주소를 넣어줍니다. (포트번호는 각각 다를 수 있기에 확인을 해야합니다. 로컬번호/이미지버켓이름/이미지이름)
  • image: 'http://localhost:9199/images/image.png',
  1. 타입에러가 발생하므로 inferface부분에 image의 타입을 명시해줍니다.

이미지의 이름과 확장자의 경우 base64코드에서 추출해서 채워넣거나 프론트엔드에서 이미지에 대한 정보를 서버에 전송해 넣어줘야합니다.

4. axios

App.vue

<script lang="ts">
import {defineComponent} from 'vue'
import axios from 'axios'

export default defineComponent({
  methods: {
    selectFile(event: Event) {
      const files = (event.target as HTMLInputElement).files as FileList
      for(let i = 0; i<files.length; i++) {
        const file = files[i]
        const reader = new FileReader()
        reader.readAsDataURL(file)
        reader.addEventListener('load', async e => {
          console.log((e.target as FileReader).result)
          const {data} = await axios({
            url: 'http://localhost:5001/kdt-test-98de9/us-central1/api/todo',
            method: 'POST',
            data: {
              title: '파일추가!',
              imageBase64: (e.target as FileReader).result
            }
          })
          console.log('투두생성 응답:', data)
        })
      }
    }
  }
})
</script>

필요한 예외처리

  • 용량제한
  • 보안
  • 파일형식
    등등이 존재합니다.

5. 코드 정리

스토로지 파일 저장 코드의 경우 반복해서 사용이 되고 있기 때문에 별도로 관리를 하는 것이 좋습니다. 모듈화를 시킨다면 utils폴더의 index.ts로 만들어줍니다.

5-1 모듈화

import * as admin from 'firebase-admin'

export async function saveFile(base64: string, bucketName = 'images') {
  const bucket = admin.storage().bucket(bucketName)
  const [, body] = base64.split(',') //4.
  const buffer = Buffer.from(body, 'base64')
  const file = bucket.file('image.png')
  await file.save(buffer)

  return `http://localhost:9199/${bucketName}/image.png`
}

5-2 todo.ts

todo.ts

import {saveFile} from '../utils' ✅

router.post('/', async (req, res) => {
  const {title, imageBase64} = req.body //1. images를 꺼내옵니다.
  const date = new Date().toISOString()

  const image= await saveFile(imageBase64)const todo = {
    title,
    image, ✅
    done: false,
    createdAt: date,
    updatedAt: date,
    deleted: false
  }

  const ref = await db.collection('Todos').add(todo) 

  //생성된 데이터를 응답
  res.status(200).json({
    id: ref.id,
    ...todo
  })
})

//투두수정
router.put('/:id', async(req, res) => {
  const {title, done, imageBase64✅} = req.body 
  const {id} = req.params

  const snap = await db.collection('Todos').doc(id).get()
  if(!snap.exists) {
    return res.status(404).json('존재하지 않는 정보입니다.')
  }

  const image= await saveFile(imageBase64)const { createdAt } = snap.data() as Todo
  const updatedAt = new Date().toDateString()

  await snap.ref.update({
    title,
    image,✅
    done,
    updatedAt
  })

  return res.status(200).json({
    id: snap.id,
    title,
    image,✅
    done,
    createdAt,
    updatedAt
  })
})

5-3 jobs

이전에 작성한 데이터들에도 image가 들어갈 수 있도록 해줘야합니다.

utils>index.ts

import * as admin from 'firebase-admin'

const db = admin.firestore()

async function addFields() {
  const snaps = await db.collection('Todos').get()

  for(const snap of snaps.docs){
    const {image} = snap.data() //이미지가 없는 경우에만
    if(!image) {
      await snap.ref.update({
        image: null
      })
    }
  }
  console.log('끝')
}

addFields() //✅한번 실행

todo.ts


interface Todo {
  id?: string
  title: string,
  image?: string | null, ✅
  done: boolean,
  createdAt: string,
  updateAt: string,
  deleted: boolean
}

src>index.ts

admin.initializeApp()

import * as functions from 'firebase-functions'
import * as express from 'express'
import * as cors from 'cors'
import todo from './routes/todo'
import './jobs' //✅한번 저장 후 바로 주석처리를 해줍니다.

6. 배포

현재 우리가 통신하는 서버는 모두 로컬호스트에 맞춰져있습니다. 이제는 실제 파이어베이스 서버에 배포를 할 것이기에 환경변수를 도입해보도록 하겠습니다.

6-1. 환경변수 설치

//hosting

$ npm i -D dotenv

6-2 .env파일 생성

프로젝트의 루트 경로에 .env파일을 만들어줍니다. 이렇게 만든 .env파일은 프로젝트에서 동작하는 환경에서만 동작합니다. 또한 git에 올라가지 않도록 .gitignore에 명시를 해줍니다.

NODE_ENV=development

서버에서의 환경변수 지정

  • 대표적으로 넷니파이에서는 Build & deploy 에서 지정이 가능합니다.

6-3

functions >src > utils> index.ts

import * as admin from 'firebase-admin'

export async function saveFile(base64: string, bucketName = 'images') {
  const bucket = admin.storage().bucket(bucketName)
  const [, body] = base64.split(',') //4.
  const buffer = Buffer.from(body, 'base64')
  const file = bucket.file('image.png')
  await file.save(buffer)

  return process.env.NODE === 'development'
  ? `http://localhost:9199/${bucketName}/image.png` //로컬주소
  : `https://strage.googleapis.com/${bucketName}/image.png` //서버주소
}

6-4

루트경로의 index.ts에 사용한다고 설정을 해줍니다.

import * as admin from 'firebase-admin'
admin.initializeApp()
import * as dotenv from 'dotenv'✅
dotenv.config()

환경변수는 서버리스 함수에서 유용하게 활용을 할 수 있습니다

0개의 댓글