Open52

【Write Code Every Day】Go × TypeScript(+ Nuxt.js)編

ピン留めされたアイテム
ななみななみ

これは何?

t_wadaさんのソフトウェアエンジニアとしての姿勢と心構えを受けて、「毎日コードを書く」と「アウトプットする」を意識したいと思い、【Write Code Every Day】と題して日々の開発状況を2023/4/3から記録している。
本スクラップは第二弾。第一弾に関しては下のスクラップをどうぞ。

https://zenn.dev/nanami69/scraps/74d2580c2d498e

第二弾の言語

Go × TypeScript(+ Nuxt.js)を選択。
Go は全く触ったことがないが人気も高いので1度触ってみたかった。
TypeScript は業務で1番書いていると言っても過言ではない。
しかしフレームワークとしては React を採用しており、できれば触ったことないフレームワークを選びたかった。
Nuxt.js は Vue.js がベースになっている JavaScript フレームワークであり、最近SNSでもよく見るので勉強がてら触ってみることに。

第二弾のゴール

訪れた映画館を写真付きでマップに登録できるアプリを完成させる。

リポジトリ:

ななみななみ

2023/4/17

本日のゴール

  • Go を用いた簡単な API の実装

やったこと

  • リポジトリの作成
  • Go の環境構築
  • 簡単な API の実装

該当コミット

ななみななみ

Go の環境構築

インストール

brew install go

go.mod の作成

go.modはGoモジュールのパスを書いておくファイル。モジュールの中に含まれる全てのパッケージインポート用のパスプレフィックスが書かれる。

go mod init github.com/nanami69/theater-tracker

go 1.20このプロジェクトがGoのバージョン1.20以上をサポートすることを示している。

go.mpd
module github.com/nanami69/theater-tracker

go 1.20
ななみななみ

API 実装

Echo のダウンロード

Goのパッケージマネージャーであるgo getを使用して、Echo というWebフレームワークのv4系列をダウンロードする。

go get github.com/labstack/echo/v4

具体的には、GitHub上のgithub.com/labstack/echoリポジトリのv4系列をダウンロードする。/v4を含めることで、Echo のバージョンを指定しています。v4系列は、Goのモジュールシステムをサポートしているため、go modコマンドを使用して依存関係を管理できる。

コマンドを実行すると、ローカルにEchoのv4系列がダウンロードされ、go.modファイルに依存関係が追加される。コード内で Echo を使用するためには、import "github.com/labstack/echo/v4という行を追加する必要がある。

go.mod
module github.com/nanami69/theater-tracker

go 1.20

require (
	github.com/labstack/echo/v4 v4.10.2 // indirect
	github.com/labstack/gommon v0.4.0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.17 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	github.com/valyala/fasttemplate v1.2.2 // indirect
	golang.org/x/crypto v0.6.0 // indirect
	golang.org/x/net v0.7.0 // indirect
	golang.org/x/sys v0.5.0 // indirect
	golang.org/x/text v0.7.0 // indirect
)

main.go

main.go
package main

import (
	"net/http"
	"github.com/labstack/echo/v4"
)

type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func getUser(c echo.Context) error {
	// id := c.Param("id")
	// ダミーデータとしてユーザーを返す
	user := &User{
		ID:    1,
		Name:  "John Doe",
		Email: "john@example.com",
	}
	return c.JSON(http.StatusOK, user)
}

func main() {
	e := echo.New()
	// ルーティングの設定
	e.GET("/users/:id", getUser)
	// サーバーの起動
	e.Logger.Fatal(e.Start(":8080"))
}

変数を宣言して値を代入するショートハンドとして:= が利用できる。(使わない場合はvar e = echo.New())
ただし、すでに定義済みの変数ではショートハンドは使えないので注意する。

e.Logger.Fatalは、エラーが発生した場合にサーバーの起動を停止するために使用される。

/users/:idにおいて、idにコロンがついているが、これは動的なパスを意味していて、ハンドラ内でidを使って、特定のユーザー情報を取得することができるようになる。

ななみななみ

Go の実行

build

go build

↑を実行するとgo.sumというファイルが生成される。
依存モジュールのチェックサムが記録され、使っているモジュールの内容に変更があったかどうかを検出している。

run

go run main.go

http://localhost:8080/users/1にアクセスすると指定したAPIの結果が見れるようになる。

ななみななみ

2023/4/18

本日のゴール

  • Nuxt.js の新規プロジェクトを作成する
  • ルーティングの設定

やったこと

  • backendfrontendディレクトリを作成し Go ファイルはbackendに移動
    • 以下作業はfrontendディレクトリで行う
  • create-nuxt-appのインストール
  • プロジェクトの作成
  • pages/about.vueの作成
  • ルーティング設定
  • 動作確認

該当コミット

ななみななみ

Nuxt.js の新規プロジェクト

Nuxt.js の CLI ツールであるcreate-nuxt-appをインストール

npm install -g create-nuxt-app

プロジェクトの作成

npx create-nuxt-app .


https://qiita.com/cheez921/items/fdfd224099f686e3173d#7-レンダリングモード
https://nishimura.club/nuxt-spa-ssr-ssg
https://dev.ore-shika.com/post/nuxtjs-install-option/

ビルドとローカルサーバーの立ち上げ

npm run dev

http://localhost:3000/にアクセスすると Nuxt.js のデフォルトページが表示される。

ななみななみ

ルーティング設定

nuxt.config.jsはNuxt.js プロジェクトの設定ファイルであり、ルーティング設定やビルド設定など記載できる。
以下の例では、/aboutパスにアクセスすると、pages/about.vueコンポーネントが表示される。

nuxt.config.js
export default {
  // ...
  router: {
    extendRoutes(routes, resolve) {
      routes.push({
        name: 'about',
        path: '/about',
        component: resolve(__dirname, 'pages/about.vue')
      })
    }
  }
}
ななみななみ

2023/4/19

本日のゴール

  • ルートコンポーネントを作成する

やったこと

  • pages/App.vueを作成

該当コミット

ななみななみ

ルートコンポーネント

Nuxt.jsのルートコンポーネントは、ページのレンダリング前に実行されるグローバルミドルウェアで、アプリケーション全体に共通するレイアウトを定義するために使用される。つまり、複数のページで共通するヘッダーやフッターなどの部分をルートコンポーネントで定義することが可能。

また、ルートコンポーネントは、ページが読み込まれる前に行う初期化処理を実行するのにも使えるので、ユーザーの認証状態を確認したり、アプリケーションで共有するデータを取得する処理を実行することができる。

簡単に言うと、Nuxt.jsのルートコンポーネントは、アプリケーション全体で共通する処理や表示を行うためのコンポーネントであり、ページコンポーネントがレンダリングされる前に実行される。

ななみななみ

2023/4/20

本日のゴール

  • マップを表示するためのライブラリの導入と設定を行う

やったこと

  • Leaflet.js のインストールと設定
  • MapView コンポーネントの作成
  • MapView コンポーネントの読み込み(about.vue)
  • 動作確認

該当コミット

ななみななみ

Leaflet.js

インストール

Leaflet はWeb地図サービスで広く使われるオープンソースの JavaScript ライブラリ

npm install leaflet

設定

pluginsディレクトリを作成し、その下に設定ファルを作成する。
Nuxt.jsの特定のファイルをインポートしたり、外部のライブラリをインストールしたりするために使用されるディレクトリです。このディレクトリ内のJavaScriptファイルは、Nuxt.jsアプリケーションが初期化される前に自動的に実行される。
https://develop365.gitlab.io/nuxtjs-2.8.X-doc/ja/guide/directory-structure/

pages ディレクトリにはアプリケーションのビュー及びルーティングファイルを入れます。Nuxt.js フレームワークはこのディレクトリ内のすべての .vue ファイルを読み込み、アプリケーションのルーターを作成します。
このディレクトリは特別な設定なしでは名前を変更できません。

plugins/leaflet.client.js
import L from 'leaflet';

const leafletPlugin = {
  install: (app) => {
    app.config.globalProperties.$L = L;
  },
};

export default leafletPlugin;

import L from 'leaflet';で leaflet を import し、const leafletPluginでプラグインを定義。
プラグインは、install メソッドを持ち、Vue アプリケーションの起動時に呼び出される。
install メソッド内で、Vue アプリケーションのグローバルオブジェクトに$Lプロパティを追加することにより、Vue コンポーネント内でthis.$Lを使用して、leaflet オブジェクトをアクセスすることができるようになる。

plugins/leaflet.client.jsを追加。

nuxt.config.js
export default {
  // ...
  plugins: [
    // ...
    { src: '~/plugins/leaflet.client.js', mode: 'client' },
  ],
};
ななみななみ

マップの表示

とりあえず先日作成したabout.vueに表示してみる。

components/MapView.vue
<template>
    <div>
      <div id="map"></div>
    </div>
  </template>
  
  <script>
  import L from 'leaflet';
  
  export default {
    mounted() {
      const map = L.map('map').setView([35.681167, 139.767052], 13);
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
    }
  }
  </script>
  
  <style>
  #map {
    height: 500px;
  }
  </style>
about.vue
<template>
  <div>
    <h1>About Page</h1>
    <p>This is the about page</p>
    <map-view></map-view>
  </div>
</template>

<script>
import leafletPlugin from '~/plugins/leaflet.client';
import MapView from '~/components/MapView';

export default {

  plugins: [leafletPlugin],
  components: {
    MapView
  },
}
</script>

ななみななみ

2023/4/21

本日のゴール

  • フォームの作成

やったこと

  • pages/form.vueの作成
  • components/Button.vueの作成
    • フォーム用だけでなく汎用的なボタンとして作成
  • pages/about.vueにフォームへ遷移するボタンを追加
  • 動作確認

該当コミット

ななみななみ

Nuxt.js では@でイベントハンドラーを設定する。

<Button @click="goToFormPage">Go to Form Page</Button>

以下で/formに遷移する。

goToFormPage() {
      this.$router.push('/form')
    }
ななみななみ

2023/4/22

本日のゴール

  • 地図の表示を正しくする

やったこと

  • nuxt.config.jsの修正
  • スタイルの調整

該当コミット

ななみななみ

2023/4/23

本日のゴール

  • フォームのバリデーションを追加する

やったこと

  • 最大文字数の設定
  • form.vueの修正
  • 動作確認

該当コミット

ななみななみ

computedについて

computedは Vue.js で算出プロパティを作成するためのオプションの一つで、関数を定義することで算出プロパティを作成することができる。算出プロパティは、算出式に応じて自動的に値が更新されるため、値が変更されたときに自動的に再描画される。

    computed: {
        hasErrors() {
            return !!this.nameError || !!this.addressError || !!this.commentError;
        }
    },

methodとの違い

  • computed
    • 依存関係に基づいて値を返す。つまり、参照されているデータが更新された場合に自動的に再計算される。
    • メソッドとして定義されるが、プロパティとして使用される。
    • データを処理する場合に最適。
  • method
    • 引数を受け取り、必要に応じて結果を返す。
    • データが更新されても自動的に再評価されない。手動で呼び出す必要がある。
    • Vue.js のコンポーネントでイベントをハンドリングする際によく使用される。
ななみななみ

2023/4/24

本日のゴール

  • フォームの送信処理を実装する
    • フロントエンドとバックエンド間で通信できるようにする

やったこと

  • バックエンド側でregisterCinemaAPIを作成
  • CORSの設定
  • フロントエンド側でAPI叩く部分を実装
  • 動作確認

該当コミット

ななみななみ

API実装

axios は Vue.js で使用されるHTTPクライアントライブラリの1つ。ブラウザとNode.jsの両方で使用できる。Nuxt.js では、Vue.js の機能が組み込まれているため、Vue.js で使用できる axios をそのまま使用可能。
axios は Promise ベースの API を提供し、シンプルな HTTPリクエストを実行することができる。

const instance = axios.create({
                baseURL: 'http://localhost:8080'
            });
            // バックエンドにフォームの内容を送信する
            instance.post('/register-cinema', {
                address: this.address
            })
            .then((response) => {
                console.log(response.data.message)
                // マップにピンを立てる処理を書く
            })
            .catch((error) => {
                console.log(error);
            });

baseURL にhttp://localhost:8080を指定することでバックエンド側のGoと通信可能となる。

ななみななみ

CORS 設定

Echo フレームワークには echo/middleware というミドルウェアが用意されており、これを使用することで簡単に CORS のヘッダを設定することができる。

import (
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()

	// CORSの設定(デプロイ前に設定を見直す)
	e.Use(middleware.CORS())
	// ルーティングの設定
	e.GET("/users/:id", getUser)
	e.POST("/register-cinema", registerCinema)
	// サーバーの起動
	e.Logger.Fatal(e.Start(":8080"))
}

これによりすべてのオリジンからのリクエストが許可されるため、本番環境などでは必要に応じて許可するオリジンを指定する必要がある。

ななみななみ

2023/4/25

本日のゴール

  • 入力した住所から緯度経度を取得する

やったこと

  • 外部APIを用いた緯度経度の取得

該当コミット

ななみななみ

ジオコーディング

ジオコーディングとは、住所や地名などの人間が理解しやすい形式の場所情報を計算機が処理しやすい形式に変換すること。一般的には、住所を地理座標(緯度・経度)に変換することを指す。

主な外部APIには以下のようなものがある。

  • Geocoding API
  • 国土地理院API
  • Yahoo!ジオコーダAPI

今回は国土地理院APIを利用することにした。

https://msearch.gsi.go.jp/address-search/AddressSearch?q=<住所>
ななみななみ

外部APIを叩く部分の実装

リクエスト送信部分

    // APIに送信するリクエストを作成
    apiURL := "https://msearch.gsi.go.jp/address-search/AddressSearch?q=" + url.QueryEscape(address)
    req, err := http.NewRequest("GET", apiURL, nil)
    if err != nil {
        return "", "", err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    // リクエストを送信
    client := new(http.Client)
    resp, err := client.Do(req)
    if err != nil {
        return "", "", err
    }
    defer resp.Body.Close()

パース部分

// レスポンスをパース
    var res []struct {
        Geometry struct {
            Coordinates []float64 `json:"coordinates"`
        } `json:"geometry"`
    }
    err = json.NewDecoder(resp.Body).Decode(&res)
    if err != nil {
        return "", "", err
    }

    // レスポンスから緯度経度を取得
    if len(res) == 0 {
        return "", "", fmt.Errorf("no result")
    }
    longitude := fmt.Sprintf("%f", res[0].Geometry.Coordinates[0])
    latitude := fmt.Sprintf("%f", res[0].Geometry.Coordinates[1])

    return latitude, longitude, nil

パース部分はAPIによって返ってくる構造が違うので、そこを調べる必要がある。
私は面倒だったので、Googleのアドレス部分で直接APIを叩き、返ってきたjson(正確にはGeoJSON)をコピーして ChatGPT に投げて構造化してもらった。

ちなみにGeoJSONは、地理的な情報を表現するためのオープンフォーマットのファイル形式。
JSONの構造を持ち、位置情報を含む地理情報を表現することができる。
GeoJSONフォーマットは、座標を表すためのPoint、LineString、Polygonなどの様々なジオメトリタイプをサポートしており、特定の地域に関する情報を表現するために、Feature、FeatureCollection、GeometryCollectionのようなジオメトリコレクションや、プロパティ情報を含むFeatureをサポートしている。

ななみななみ

2023/04/26

本日のゴール

  • 取得した緯度経度をもとにマップにピンを立てる

やったこと

  • 取得した緯度経度の受け渡し処理追加(form.vue->about.vue)
  • about.vue(親)からMapView.vue(子)に対しピン追加の指示処理追加
  • 動作確認

該当コミット

ななみななみ

緯度経度を取得したら about.vue に遷移

form.vue
// バックエンドにフォームの内容を送信する
            instance.post('/register-cinema', {
                address: this.address
            })
            .then((response) => {
                console.log(response.data.lat)
                console.log(response.data.lng)
                // aboutページにリダイレクトしつつ、パラメータとしてlatとlngを渡す
                this.$router.push({
                    name: 'about',
                    params: {
                        lat: response.data.lat,
                        lng: response.data.lng
                    }
                })
            })

受け取る側のabout.vueは mounted で lat と lng があった場合はaddPin関数を呼び出すように修正。

about.vue
mounted() {
    const lat = this.$route.params.lat;
    const lng = this.$route.params.lng;
    if (lat && lng) {
      this.addPin(lat, lng);
    }
  }
ななみななみ

親コンポーネントから子コンポーネントへピンの追加を指示

about.vue
addPin(lat, lng) {
      const position = [lat, lng];
      // MapViewコンポーネントに位置情報をemitする
      this.$refs.map.$emit('addMarker', position);
    }

子コンポーネントである MapView ではaddMarkerイベントを受付け、受け取ったらL.markerでピンを追加する。

$refsは、Vue.js で定義された特別な変数で、テンプレート内の要素に ref 属性を付けておくことで、その要素への参照を得ることができる。これを使うことで、コンポーネント内で定義されたメソッドなどから、テンプレート内の要素を直接参照することが可能になる。

$refsで参照された要素には、イベントを発生させることもできる。$emitメソッドを使用すると、コンポーネントに対してカスタムイベントを発生させることができ、このとき、イベント名とオプションでデータを渡せる。

MapView.vue
  mounted() {
    const map = L.map('map').setView([36.2048, 138.2529], 6);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

    // 親コンポーネントからemitされた位置情報を受け取り、マーカーを追加
    this.$on('addMarker', (position) => {
      if (this.mapObject) {
        L.marker(position).addTo(this.mapObject);
      } else {
        this.$once('mounted', () => {
          L.marker(position).addTo(this.mapObject);
        });
      }
    });

    this.mapObject = map;
  },

MapView コンポーネント内では、$onメソッドを使用してイベントを待ち受け、引数として受け取った位置情報をもとにマーカーを追加する。

ななみななみ

2023/4/27

本日のゴール

  • マップに映画館の名前をポップアップで表示する

やったこと

該当コミット

ななみななみ

ポップアップ

markerオブジェクトに対してbindPopupメソッドを指定する。

MapView.vue
var marker = L.marker(position).addTo(this.mapObject);
marker.bindPopup(cinemaInfo[2]).openPopup();
ななみななみ

×印でポップアップを閉じることができ、ピンをクリックすると再びポップアップが表示される。

ななみななみ

2023/4/28

本日のゴール

  • ポップアップでhtmlを埋め込めるようにする
  • +リファクタ

やったこと

  • ポップアップの引数にhtmlタグ埋め込み
  • 名前だけでなくコメントも表示するよう変更
  • 変数取り出し部分のリファクタ

該当コミット

ななみななみ
var marker = L.marker(position).addTo(this.mapObject); // markerオブジェクトを作成
marker.bindPopup(`<h3>${name}</h3><p>${comment}</p>`).openPopup();

ななみななみ

2023/4/29

本日のゴール

  • ファイルをバックエンド側に送信して処理する

やったこと

  • API叩く時のデータにファイルを含める
  • 送信データをフォームデータに変更
  • Go側でファイルの処理を追加
  • コンソールログでファイルの受け取りを確認

該当コミット

ななみななみ

API叩く際のデータをフォームデータに変更

form.vue
const instance = axios.create({
                baseURL: 'http://localhost:8080',
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            });
            const formData = new FormData();
            formData.append('name', this.name);
            formData.append('address', this.address);
            formData.append('photo', this.photo);
            formData.append('comment', this.comment);

multipart/form-dataは、ウェブページからファイルをアップロードするための標準的な方法の1つ。HTTPリクエストに含まれるデータを、複数の部分に分割して送信することができる。

一般的に、multipart/form-dataを使用するときには、HTTPリクエストの Content-Type ヘッダーにmultipart/form-dataを指定する。

ななみななみ

ファイル処理

main.go
// ファイルを受け取る
    file, err := c.FormFile("photo")
    if err != nil {
        if err == http.ErrMissingFile {
            // ファイルが送信されていない場合の処理
        } else {
            // その他のエラーが発生した場合の処理
            return fmt.Errorf("resieve:%s", err.Error())
        }
    } else {
        // ファイルが正常に受け取れた場合の処理
        f, err := file.Open()
        if err != nil {
            return fmt.Errorf("not opne:%s", err.Error())
        }
        defer f.Close()

        data, err := ioutil.ReadAll(f)
        if err != nil {
            return fmt.Errorf("read file:%s", err.Error())
        }
        // Base64エンコード
    	fileBase64 = base64.StdEncoding.EncodeToString(data)
        fmt.Println("files: uploaded")
    }

c.FormFile("photo")

HTTPリクエストのmultipart/form-dataのphotoフィールドからファイルを取得するために使う。multipart/form-dataは、ファイルなどのバイナリデータを含むフォームデータを送信するためのエンコーディング方式。

file.Open()

os.File型のファイルを開くための関数。ファイルを開くと、読み書きが可能になる。

ioutil.ReadAll(f)

io.Reader型のfからすべてのデータを読み込むための関数。ioutilパッケージは、入出力に関するいくつかの便利な関数を提供する。

base64.StdEncoding.EncodeToString(data)

data のバイト配列を、Base64エンコーディングされた文字列に変換する。Base64エンコーディングは、バイナリデータをASCIIテキスト形式に変換するための方法で、データを安全に転送するために使用される。

ななみななみ

2023/4/30

本日のゴール

やったこと

  • MySQLのインストール(brew install mysql@5.7)
  • MySQL Workbench のインストール(8.0.24)
  • 認証情報をyamlファイルに保存し.gitignore
  • MySQLでデータベースおよびテーブルとカラムの作成
  • main.goでフォームの情報をデータベースに保存する処理の追加
  • MySQL Workbench にフォームの情報が登録されていることを確認

該当コミット

ななみななみ

MySQLのインストールは以下の記事参考
https://prog-8.com/docs/mysql-env

データベースの作成

CREATE DATABASE movie_theater;

カラムの設定

USE movie_theater;
CREATE TABLE theaters (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    address VARCHAR(255) NOT NULL,
    latitude VARCHAR(255) NOT NULL,
    longitude VARCHAR(255) NOT NULL,
    photo BLOB
);

BLOBは、Binary Large Object(バイナリ大型オブジェクト)の略称。
バイナリデータを格納するために使用されるデータ型で、画像や動画、音声などのメディアファイル、プログラムのバイナリファイル、圧縮ファイルなどを格納することができる。
BLOB型は、データベースに格納されたバイナリデータを直接読み書きすることができるため、データの扱いが容易で、高速なアクセスが可能。

ななみななみ

DBへの保存

main.go
func saveToDB(name string, address string, latitude string, longitude string, photo string) error {
    // 設定ファイルのパスを指定する
	viper.SetConfigFile("./config/config.yml")

	// 設定ファイルを読み込む
	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("failed to read config file: %s", err))
	}

	// 設定ファイルの内容を構造体にマッピングする
	var config Config
	err = viper.Unmarshal(&config)
	if err != nil {
		panic(fmt.Errorf("failed to unmarshal config file: %s", err))
	}

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", config.User, config.Password, config.Host, config.Port, config.DBName)

    // DBに接続
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return err
    }
    defer db.Close()

    // INSERT文を作成して実行
    query := "INSERT INTO theaters (name, address, latitude, longitude, photo) VALUES (?, ?, ?, ?, ?)"
    _, err = db.Exec(query, name, address, latitude, longitude, photo)
    if err != nil {
        return err
    }

    return nil
}

認証情報はymlファイルに書いてそこに見に行く方式を採用した。
今はファイル情報をとりあえずファイル名としているが、今後はvue側で画像表示ができるようにファイル情報全体を保持したい。(もしくはURL)

visper

import "github.com/spf13/viper"

viper は、Go言語向けの設定管理ライブラリ。様々な形式の設定ファイル(JSON、YAML、TOMLなど)や環境変数、コマンドライン引数から設定値を読み込んで、プログラム中で利用することができる。

viper は、Go言語で書かれたプログラムであればどんなプログラムでも利用することができ、viperを利用することで設定ファイルの読み書き、環境変数の取得、コマンドライン引数の解析など、設定管理に関するコードを効率的に記述することが可能。

ななみななみ

2023/5/1

本日のゴール

  • リファクタリング

やったこと

  • MapView.vueのリファクタ
  • about.vueのリファクタ

該当コミット

ななみななみ
MapView.vue
  mounted() {
    const map = L.map("map").setView([36.2048, 138.2529], 6);
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(
        map
      );
      this.mapObject = map;
  },

MapView.vue
  mounted() {
    this.initializeMap();
  },
const map = L.map("map").setView([36.2048, 138.2529], 6);
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(
        map
      );
      this.mapObject = map;
      this.$emit("mapInitialized");
// 以下略

$emitを使って、子コンポーネントから親コンポーネントへ情報を伝えるよう変更

about.vue
<template>
  <div>
    <h1>About Page</h1>
    <p>This is the about page</p>
    <Button @click="goToFormPage">Go to Form Page</Button>
    <map-view @mapInitialized="onMapInitialized" ref="map"></map-view>
  </div>
</template>

<script>
import leafletPlugin from '~/plugins/leaflet.client';
import MapView from '~/components/MapView';
import Button from '~/components/Button'

export default {
  plugins: [leafletPlugin],
  components: {
    MapView,
    Button
  },
  methods: {
    goToFormPage() {
      this.$router.push('/form')
    },
    addPin(lat, lng, name, comment, photo) {
      const cinemaInfo = [lat, lng, name, comment,photo];
      if (this.isMapInitialized) {
        this.$refs.map.addMarker(cinemaInfo);
      } else {
        this.pendingMarker = cinemaInfo;
      }
    },
    onMapInitialized() {
      this.isMapInitialized = true;
      if (this.pendingMarker) {
        this.$refs.map.addMarker(this.pendingMarker);
        this.pendingMarker = null;
      }
    }
  },

MapViewコンポーネントが初期化されるまで待つ必要があるため、addPinメソッドを変更し、MapViewコンポーネントが初期化された後に位置情報を追加するよう変更する。
isMapInitialized フラグと pendingMarker データを導入し、位置情報を待機する。

また、MapViewコンポーネントが初期化されたら、onMapInitialized メソッドを呼び出し、 pendingMarker があれば追加するようにした。これにより、マーカーを追加するために待たなくても、MapViewコンポーネントが初期化されるまでaboutページを表示できるようになる。

ななみななみ

2023/5/2

本日のゴール

  • フォームのバリデーションが効かなくなっていたので修正する

やったこと

  • 送信ボタンの disabled を修正

該当コミット

ななみななみ
<button type="submit" v-bind:disabled="isValid">送信</button>

名前と住所が入っていること、名前、アドレス、コメントで文字数オーバーがないことを確認。

computed: {
        isValid() {
            const hasErrors = !!this.nameError || !!this.addressError || !!this.commentError;
            const isFilled = !!this.name && !!this.address;
            return hasErrors || !isFilled;
        }
    },
ななみななみ

2023/5/4

本日のゴール

  • 外部ファイル化したAPIの利用

やったこと

  • ディレクトリ構成の修正
  • インポート文の修正
  • 動作確認

該当コミット

ななみななみ

ファイル分割したローカルパッケージにある関数名は大文字にする。
パッケージ名はディレクトリ名と同じにする。

main.goでのインポート

main.go
package main

import (
	// 略

	"github.com/nanami69/theater-tracker/backend/pkg/geocoding"
)
ななみななみ

2023/5/9

本日のゴール

  • 地図に画像を表示する

やったこと

  • imgタグの修正
  • 動作確認

該当コミット

ななみななみ

Data URL (Base64) をimgタグのsrcに指定

addMarker(cinemaInfo) {
      var [lat, lng, name, comment, photo] = cinemaInfo;
      var position = [lat, lng];
      var marker = L.marker(position).addTo(this.mapObject); // markerオブジェクトを作成
      marker
        .bindPopup(
          `<h3>${name}</h3><p>${comment}</p><img src="data:image/jpeg;base64,${photo}" alt="cinema photo" width="200px">`
        )
        .openPopup();
    },
ななみななみ

2023/5/15

本日の目標

  • DBにファイル名ではなくファイルデータを保存できるようにする

やったこと

  • base64エンコードしたものをBLOBに変換
  • DBのBLOBの容量を拡張
  • テーブルへのデータ追加部分をファイル名ではなくファイルデータに変更
  • 動作確認

該当コミット

ななみななみ

base64エンコードしたものをBLOBに変換

// Base64エンコード
fileBase64 = base64.StdEncoding.EncodeToString(data)
blobData = []byte(fileBase64)

DBのBLOBの容量を拡張

-- カラムのデータサイズを拡張
ALTER TABLE テーブル名 MODIFY カラム名 MEDIUMBLOB;