ラブホテル検索アプリを作った話

こんにちは！久しぶりにQiita記事を書きます！
今回は現在地から近い・安いラブホテル検索アプリを開発したので、紹介＆実装の一部を今後振り返れるよう書き記します！！
https://apps.apple.com/jp/app/ラブホテル簡単検索-ホテルイキタイ/id1524262826
是非、覗いて見てください…笑

<img width="1344" alt="6.5incSS1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/7c6fd6ec-3dab-9a88-298f-48f429313b5c.png">
<img width="1344" alt="6.5incSS2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/4c36b1b1-33b1-8b24-3ee7-5e7fe172de36.png">
<img width="1344" alt="6.5incSS3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/675300b6-5e0e-1283-89d4-b07bf9c6bbe0.png">

#作った理由#
- 検索時の情報過多を解消するアプリを作りたいと思った。
（ex.グルメアプリなど多くの情報を入力してそれに適したものを表示してくれるが、その初めの入力作業が億劫に感じた。）
- Pinterestのような、まずレコメンドを表示する。探索と検索両方を合わせ持つアプリを他の分野で作りたいと思った。
- モバイルに適応している（UI、UX）ラブホテル検索サービスがあまりないと感じたから。

上記の点よりラブホテルの検索アプリを作ろうと決めました！！

#GAS(Google Apps Script)とFireStore連携
アプリを作るにあたってまず、都内のラブホテルの情報をGoogleスプレッドシートに書き込み、書き込んだ情報をFireStoreにコレクションとして追加しました。
<img width="653" alt="スクリーンショット 2020-07-26 15.59.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/196871ee-9276-140a-f46a-1a80a424f6b3.png">
<img width="1055" alt="スクリーンショット 2020-07-26 16.01.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/b73ca945-93b3-9c9e-f21a-80cb0842b432.png">
ここでは2つのスプレッドシートを作りました。
一つ目はホテルの簡易な情報、二つ目は詳細な情報です。
これらをFireStoreに追加します。下記の記事を参考にさせていただきました。詳しい手順はこちらをみていただければと思います。
https://medium.com/@m_coder/バックエンドの知識がない自分がfirestore-googleappsscriptで簡易dbを構築した話-def690bb3c4d

```javascript

const sheet = SpreadsheetApp.getActiveSheet();
const data  = sheet.getDataRange().getValues();

var START_ROW = 1
var START_COL = 1

function getSheetValue(sheet) {
  // 1行目（フィールド）の値を取得
  var fieldRange = sheet.getRange(1, 1, 1, sheet.getLastColumn());
  var fieldRowValues = fieldRange.getValues();
  var fields = fieldRowValues[0];
  var rowValues = [];
  var jsonArray = [];
  
  var range = sheet.getRange(START_ROW, START_COL, sheet.getLastRow() - START_ROW + 1, sheet.getLastColumn())
  var values = range.getValues();
  
  for(var i = 0; i < values.length; i++) {
    // １行のデータをpush
    var rowValue = values[i];
    var json = new Object();
    for(var j = 0; j < fields.length; j++) {
      json[fields[j]] = rowValue[j]
    }
    jsonArray.push(json)
  }

  return jsonArray;
}

function firestoreCertification() {
  var certification = {
    "email": "{client_emailの値}", 
    "key": "-----BEGIN PRIVATE KEY-----\n{private_keyの値}\n-----END PRIVATE KEY-----\n",
    "projectId": "{project_idの値}"
  }
  return certification;
}

function createFirestoreDocuments() {
  var arrayData = getSheetValue(sheet);
  arrayData.forEach(createDocument)
}

function createDocument(item) {
  var certArray = firestoreCertification();
  var firestore = FirestoreApp.getFirestore(certArray.email, certArray.key, certArray.projectId);
   firestore.createDocument('HotelList',item);
}
```

これでcreateFirestoreDocuments関数を実行することでFireStoreに書き込みが行われます。
<img width="1137" alt="スクリーンショット 2020-07-26 16.31.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/d8598afa-9221-8945-2b76-f5a5a9af4b24.png">
スプレッドシートに記入していた内容をドキュメントとして書き込むことができました！
新たにスプレッドシートに追加で書き込んでFireStoreにドキュメントを追加したい場合は

```javascript

//新たに追加し始める行
//ex.新たに10行目からスプレッドシートに書き込んだ場合
var START_ROW = 10
```
上記のコードの場合`START_ROW`の値を変えることで可能です。

#CloudFunctionsで全体に書き込みしたい#
上記のスプレッドシートの画像を見ていただけると分かるのですが、２枚目のホテルの詳細が書いてあるスプレッドシートのフィールドに**hotelId**があると思います。
このhotelIdはHotelListコレクションのドキュメントIDを値としています。
※HotelListはスプレッドシート画像1枚目のデータをFireStoreに書き込んだコレクションです。
<img width="955" alt="スクリーンショット 2020-07-26 16.51.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/9213cece-ea74-ee64-3efa-33634bdb4a4b.png">

上記の画像のように該当するhotelIdをクエリして詳細ページに遷移しています。

```swift:HotelDetailModel.swift

class HotelDetailModel {
    var items = [HotelDetailItem]()
    weak var delegate: HotelDetailModelDelegate?
    
    func fetchHotelDetail(with hotelId: String) {
        let query = Firestore.firestore().collection("HotelDetail").whereField("hotelId", isEqualTo: hotelId)
        query.getDocuments { [weak self] (snapshot, error) in
            guard let `self` = self else { return }
            if let snapshot = snapshot {
                self.items = snapshot.documents.map {HotelDetailItem(from: $0.data(), docId: $0.documentID)}
            }
            self.delegate?.didFetchHotelDetail(with: error)
        }
    }
}
```
※一部抜粋
前置き大変長くなって申し訳ありません。とにかくHotelDetailそれぞれのドキュメントに該当するhotelIdをCloudFunctionsでまとめて追加したいという話です…笑

#TypeScriptで実装#

導入はこちらを参考にしました。
https://qiita.com/star__hoshi/items/7dcf5970d28a7ff239fb
以下hotelIdを追加するために書いたコードです。

```typescript:index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin'

admin.initializeApp(functions.config().firebase)
const db = admin.firestore()
type DocumentReference = admin.firestore.DocumentReference

interface HotelItem {
    readonly address: String
    readonly area: String
    readonly imageUrl: String
    readonly name: String
    readonly restMin: number
    readonly stayMin: number
    ref: DocumentReference
}

interface HotelDetail {
    readonly address: String
    readonly name: String
    readonly access: String
    readonly callNumber: number
    readonly hotelUrl: String
    readonly roomNum: number
    readonly imageUrlList: String
    readonly restPlan: String
    readonly stayPlan: String
    hotelId: String
}

  export const requestHotelIdGain = functions.region('asia-northeast1').https.onRequest(async (req, res) => {
    try {
      const result = await fetchHotelId()
      return res.status(200).send(result)
    } catch (error) {
      return res.status(400).send(`Failed ${error}`)
    }
  })

async function fetchHotelId() {

    const hotelListQuery =  db.collection('HotelList')
    const hotelDetailQuery =  db.collection('HotelDetail')
    const hotelListSnap = await hotelListQuery.get()
    const hotelDetailSnap = await hotelDetailQuery.get()
    const batch = db.batch()

    hotelListSnap.docs.forEach(doc => {
        const hotelItem = doc.data() as HotelItem
        hotelDetailSnap.docs.forEach(detailDoc => {
            const hotelDetail = detailDoc.data() as HotelDetail

            if (hotelItem.name === hotelDetail.name) { 
                batch.update(detailDoc.ref,{hotelId: doc.id})
            }
        })
    })
    await batch.commit()
    return new Promise<string>(resolve => resolve(`yeahhh`))

}
```
やったことは**HotelList**と**HotelDetail**のコレクションを取ってきて、
それぞれforEachで一つ一つのドキュメントに`if (hotelItem.name === hotelDetail.name)`で同じホテルの名前であったときに、一致したHotelDetailのドキュメントに一致したHotelListのdocumentIdを書き込んでいます。
`batch.update(detailDoc.ref,{hotelId: doc.id})`
<img width="850" alt="スクリーンショット 2020-07-26 17.26.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282860/da026306-abba-f893-30d8-d7ad19f5e214.png">
無事hotelIdフィールドに該当するdocumentIdが追加されました！！

#最後に#
最後まで見ていただきありがとうございます！！
間違っている点ありましたらご指摘していただけると幸いです！
他にも苦戦した点も後々書こうかと思います！
正直、今回のアプリは使う場面も限られており且つ、多くの人に使ってもらえるかは…ですが、今後も自分で作っていきたいなと思いました！！