사진 게시판 API 만들기 [5]

Perfect 미니 프로젝트 [5]

  1. 게시물 내용 수정 API

CRUD 중 Update에 해당하는 API를 만들어 봅니다!!

2017년 8월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

지난 내용 돌아보기

게시물 내용 수정 API

지난 번에 올려둔 게시물의 내용을 수정하는 API를 구성해보려고 합니다. 일단 처음 라우팅 해 둔대로 게시물 내용을 수정하는 API의 핸들러는 modifyArticleHandler(request:response:) 함수입니다. 게시물 내용을 수정하는 API 데이터 모양을 살펴볼까요?

요청(Request)

  • HTTP Method : POST
  • Content-Type: multipart/form-data
 매개변수 자료형  값의 범위/기본 값  비고   필수여부
image  binary data 이전 데이터 이미지 데이터  N
user_name string  사용자 이름이 이전 사용자 이름과 일치하지 않으면 수정에 실패 Y
description string  이전 데이터 이미지 설명  N
title string  이전 데이터 이미지 제목  N

응답(Response)

Key  자료형  비고  필수여부 
article_id  string  수정된 게시물 고유 식별자 
 image_url string  이미지 주소 
user_name  string  사용자 이름 
description   string 이미지 설명 
title  string  이미지 제목 

사진 게시물 등록 API와 크게 다른 모양은 아닙니다만, 필수 매개변수 여부가 다릅니다. 이전 게시물 내용이 이미 등록되어 있으므로, 변경을 원하는 매개변수만 담아서 요청하도록 되어있습니다. 다만, 사용자 이름은 필수항목이군요. 응답 데이터의 모양도 다르지 않습니다.
로직이 크게 다르지 않을 것 같은데요? 한 번 함께해보시죠 🙂

( 사실 게시물 작성 API, 게시물 정보 API와 로직이 비슷한 부분이 많아 공용으로 사용할 함수를 많이 만들 수도 있지만, 포스팅에서 이전 내용을 수정하는 것이 여긴 귀찮은 것이 아니므로... 일단 중복되는 코드가 많아도 해당 로직으로 꾸역꾸역 구현해 봤습니다. 차후에 여러분 스스로 중복 코드를 줄여보세요 😀 )

API 핸들러의 코드를 작성하기 위해 Souces/handlers.swift 파일의 modifyArticleHandler(request:response:) 함수 내부에 코드를 작성해봅니다.

일단 부족한 매개변수가 없는지 먼저 확인합니다.

    // 응답 컨텐츠 형식은 JSON
    response.setHeader(.contentType, value: ContentsType.json)

    // 부족한 매개변수가 없는지 확인
    if let lakedParams = lakedParams(paramsNeeded: [JSONKey.userName], paramsReceived: request.postParams) {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need more params \(lakedParams)"])
    }

그 후, 요청정보를 통해 게시물 고유번호를 받아보겠습니다.

    // 요청 경로에서 이미지 고유번호를 가져옵니다
    guard let articleId: String = request.pathComponents.last else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"need article id"])
        return
    }

이제 가져온 게시물 고유번호를 통해 MongoDB에서 문서를 찾아서 게시물 정보를 가져옵니다. 지난 번에 만들어둔 articleDocumentDicrionaries(query:skip:limit:)함수를 사용합니다. 위의 코드 아래에 이어서 작성합니다.

    // MongoDB에서 문서를 찾으려면 쿼리를 위한 BSON 객체를 만들 필요가 있습니다.
    // 새로 생성한 BSON 객체에 고유번호를 넣어서 쿼리 객체를 생성합니다
    let query: BSON = BSON()
    let oid: BSON.OID = BSON.OID(articleId)
    query.append(oid: oid)

    // 게시물 정보를 담은 딕셔너리 배열을 가져온 후 첫 번째 요소를 꺼냅니다
    guard let articleDocumentDictionary = articleDocumentDictionaries(query: query, limit: 1)?.first else {
        sendCompleteResponse(response, status: .internalServerError)
        return
    }

DB에서 정보를 가져왔으니 request를 통해 전달받은 사용자 이름과 DB에 저장되어 있던 사용자 이름이 같은지 확인합니다.

    // 전달받은 사용자 이름과 DB에 작성된 게시물의 사용자 이름과 같은지 확인합니다
    guard let dbUserName = articleDocumentDictionary[JSONKey.userName] as? String,
        let paramUserName = request.param(name:JSONKey.userName) else {

            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"can not check user name"])
            return

    }

    guard dbUserName == paramUserName else {
        sendCompleteResponse(response, status: .badRequest, jsonBody: [JSONKey.message:"diffrent user name"])
        return
    }

매개변수로 전달되어온 데이터가 새로이 있다면 DB에 저장할 데이터를 업데이트 해줍니다.

    // DB에 저장할 데이터
    // 차후에 응답 데이터로도 사용합니다
    var jsonDictionary = articleDocumentDictionary

    // 매개변수로 사진 제목이 전달되었다면 수정
    if let title = request.param(name: JSONKey.title) {
        jsonDictionary[JSONKey.title] = title
    }

    // 매개변수로 사진 설명이 전달되었다면 수정
    if let description = request.param(name: JSONKey.description) {
        jsonDictionary[JSONKey.description] = description
    }

이제 이미지만 남았습니다. 이미지가 업로드 되었다면 새로이 서버에 저장하고, 지난 이미지를 삭제합니다. 업로드 되지 않았다면 저장할 데이터 형태만 살짝 바꿔줍니다.

    // 이미지가 저장될 디렉터리
    guard let imageDirectory = imageDir else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: nil)
        return
    }

    // 원본 이미지 파일 이름 가져오기
    guard let originImageUrl: String = articleDocumentDictionary[JSONKey.imageUrl] as? String, let originImageFileName: String = originImageUrl.filePathComponents.last else {

        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error: "can not find origin image"])
        return
    }

    // 원본 이미지 서버 내부 경로
    let originImageFilePath: String = imageDirectory.path + originImageFileName

    // 새로운 이미지 서버 내부 경로
    let imageFilePath: String!

    // 최종 이미지 파일 이름
    let imageFileName: String!

    // DB 저장시에는 URL과 게시물 ID가 필요없으므로 미리 삭제
    jsonDictionary.removeValue(forKey: JSONKey.imageUrl)
    jsonDictionary.removeValue(forKey: JSONKey.articleId)

    // 이미지 파일이 업로드 되었는지 확인
    if let imageInformation: MimeReader.BodySpec = request.postFileUploads?.first,
        let imageFile = imageInformation.file {

        // 고유한 이미지 이름을 위해 타임스템프 값을 활용
        let timestamp: Int = icuDateToSeconds(getNow())

        // 사용자이름_타임스템프.jpg 형식으로 파일이름 지정
        imageFileName = paramUserName + "_" + String(timestamp) + ".jpg"

        // 이미지가 저장될 경로
        imageFilePath = imageDirectory.path + imageFileName

        // 이미지 저장에 실패할 경우 실패 응답 보내기
        do {
            try imageFile.copyTo(path: imageFilePath, overWrite: false)
        } catch {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
            return
        }

        // 기존 파일 삭제해주기
        File(originImageFilePath).delete()

        jsonDictionary[JSONKey.imagePath] = imageFilePath

    } else {    // 이미지 파일을 변경하지 않는 경우

        // 이미지 서버 내부 경로
        imageFilePath = imageDirectory.path + originImageFileName

        imageFileName = originImageFileName
    }

이제 마지막으로 변경된 딕셔너리를 DB에 업데이트 해주기만 하면 됩니다. 새로운 게시물 작성 API에서 사용하였던 컬렉션의 save(document:) 메서드는 OID가 같다면 업데이트를 수행합니다.

    // mongoDB 컬렉션 가져오기
    guard let collection = DB.collection else {
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:"data base initialize failed"])
        return
    }

    // DB에 저장
    do {
        let jsonString = try jsonDictionary.jsonEncodedString()

        let document: BSON = try BSON(json: jsonString)

        // 문서 사용 후에는 닫아주는게 좋겠습니다
        defer {
            document.close()
        }

        // 문서 고유 아이디 부여
        document.append(oid: oid)

        // 컬렉션에 문서 저장 - save 메서드는 업데이트 겸용입니다
        let result = collection.save(document: document)

        if case .success = result { } else {
            sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:result])
            return
        }

    } catch {

        Log.error(message: error.localizedDescription)
        sendCompleteResponse(response, status: .internalServerError, jsonBody: [JSONKey.error:error.localizedDescription])
        return
    }

이제 DB에 저장도 마쳤으니 응답 데이터를 전송하는 일만 남았습니다. 필요없는 정보 대신에 클라이언트에서 필요한 정보로 대체해 준 후 전달합니다.

    // 응답 데이터에는 이미지 경로 대신 이미지 URL 전송
    jsonDictionary[JSONKey.imagePath] = nil
    jsonDictionary[JSONKey.imageUrl] = server.serverAddress + ":\(server.serverPort)" + "/image/" + imageFileName

    // 모든 작업을 완료하고 JSON 응답
    sendCompleteResponse(response, status: .created, jsonBody: jsonDictionary)

기존 게시물 정보입니다.

수정 후 게시물 정보입니다. 게시물 ID는 동일하지만 이미지 URL도 달라져 있고, 제목과 내용도 변경되었네요!

*Update*까지 해봤군요! 다음엔 *Delete*로 마무리 해봅니다!

오늘은 여기까지~!

다음에 또 만나요~~ 😀

## 참고문서

* [PerfectDocs - HTTPRequest](http://perfect.org/docs/HTTPRequest.html)
* [PerfectDocs - MongoDB](http://perfect.org/docs/MongoDB.html)

by yagom

---

facebook : https://www.facebook.com/yagompage
facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.
RSS Feed 받기   

댓글 남기기

Close