【Write Code Every Day】Go × TypeScript(+ Nuxt.js)編
これは何?
t_wadaさんのソフトウェアエンジニアとしての姿勢と心構えを受けて、「毎日コードを書く」と「アウトプットする」を意識したいと思い、【Write Code Every Day】と題して日々の開発状況を2023/4/3から記録している。
本スクラップは第二弾。第一弾に関しては下のスクラップをどうぞ。
第二弾の言語
Go × TypeScript(+ Nuxt.js)を選択。
Go は全く触ったことがないが人気も高いので1度触ってみたかった。
TypeScript は業務で1番書いていると言っても過言ではない。
しかしフレームワークとしては React を採用しており、できれば触ったことないフレームワークを選びたかった。
Nuxt.js は Vue.js がベースになっている JavaScript フレームワークであり、最近SNSでもよく見るので勉強がてら触ってみることに。
第二弾のゴール
訪れた映画館を写真付きでマップに登録できるアプリを完成させる。
リポジトリ:
Go の環境構築
インストール
brew install go
go.mod の作成
go.mod
はGoモジュールのパスを書いておくファイル。モジュールの中に含まれる全てのパッケージインポート用のパスプレフィックスが書かれる。
go mod init github.com/nanami69/theater-tracker
go 1.20
このプロジェクトがGoのバージョン1.20以上をサポートすることを示している。
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
という行を追加する必要がある。
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
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 の新規プロジェクトを作成する
- ルーティングの設定
やったこと
-
backend
とfrontend
ディレクトリを作成し Go ファイルはbackend
に移動- 以下作業は
frontend
ディレクトリで行う
- 以下作業は
-
create-nuxt-app
のインストール - プロジェクトの作成
-
pages/about.vue
の作成 - ルーティング設定
- 動作確認
該当コミット
- backend
- frontend
Nuxt.js の新規プロジェクト
create-nuxt-app
をインストール
Nuxt.js の CLI ツールであるnpm install -g create-nuxt-app
プロジェクトの作成
npx create-nuxt-app .
ビルドとローカルサーバーの立ち上げ
npm run dev
http://localhost:3000/
にアクセスすると Nuxt.js のデフォルトページが表示される。
ルーティング設定
nuxt.config.js
はNuxt.js プロジェクトの設定ファイルであり、ルーティング設定やビルド設定など記載できる。
以下の例では、/about
パスにアクセスすると、pages/about.vue
コンポーネントが表示される。
export default {
// ...
router: {
extendRoutes(routes, resolve) {
routes.push({
name: 'about',
path: '/about',
component: resolve(__dirname, 'pages/about.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アプリケーションが初期化される前に自動的に実行される。
pages ディレクトリにはアプリケーションのビュー及びルーティングファイルを入れます。Nuxt.js フレームワークはこのディレクトリ内のすべての .vue ファイルを読み込み、アプリケーションのルーターを作成します。
このディレクトリは特別な設定なしでは名前を変更できません。
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
を追加。
export default {
// ...
plugins: [
// ...
{ src: '~/plugins/leaflet.client.js', mode: 'client' },
],
};
マップの表示
とりあえず先日作成したabout.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>
<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')
}
きちんと公式サイトのバージョンを指定すること
link: [
{ rel: 'stylesheet', href: 'https://unpkg.com/leaflet@1.9.3/dist/leaflet.css' }
],
script: [
{ src: 'https://unpkg.com/leaflet@1.9.3/dist/leaflet.js' }
],
computedについて
computed
は Vue.js で算出プロパティを作成するためのオプションの一つで、関数を定義することで算出プロパティを作成することができる。算出プロパティは、算出式に応じて自動的に値が更新されるため、値が変更されたときに自動的に再描画される。
computed: {
hasErrors() {
return !!this.nameError || !!this.addressError || !!this.commentError;
}
},
methodとの違い
- computed
- 依存関係に基づいて値を返す。つまり、参照されているデータが更新された場合に自動的に再計算される。
- メソッドとして定義されるが、プロパティとして使用される。
- データを処理する場合に最適。
- method
- 引数を受け取り、必要に応じて結果を返す。
- データが更新されても自動的に再評価されない。手動で呼び出す必要がある。
- Vue.js のコンポーネントでイベントをハンドリングする際によく使用される。
2023/4/24
本日のゴール
- フォームの送信処理を実装する
- フロントエンドとバックエンド間で通信できるようにする
やったこと
- バックエンド側で
registerCinema
APIを作成 - 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"))
}
これによりすべてのオリジンからのリクエストが許可されるため、本番環境などでは必要に応じて許可するオリジンを指定する必要がある。
ジオコーディング
ジオコーディングとは、住所や地名などの人間が理解しやすい形式の場所情報を計算機が処理しやすい形式に変換すること。一般的には、住所を地理座標(緯度・経度)に変換することを指す。
主な外部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 に遷移
// バックエンドにフォームの内容を送信する
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
関数を呼び出すように修正。
mounted() {
const lat = this.$route.params.lat;
const lng = this.$route.params.lng;
if (lat && lng) {
this.addPin(lat, lng);
}
}
親コンポーネントから子コンポーネントへピンの追加を指示
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
メソッドを使用すると、コンポーネントに対してカスタムイベントを発生させることができ、このとき、イベント名とオプションでデータを渡せる。
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
本日のゴール
- マップに映画館の名前をポップアップで表示する
やったこと
該当コミット
- バックエンド
- フロントエンド
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叩く際のデータをフォームデータに変更
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
を指定する。
ファイル処理
// ファイルを受け取る
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のインストールは以下の記事参考
データベースの作成
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への保存
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
のリファクタ
該当コミット
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;
},
↓
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を使って、子コンポーネントから親コンポーネントへ情報を伝えるよう変更
<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ページを表示できるようになる。
<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/3
本日のゴール
- 外部APIを叩く処理を外部ファイル化
やったこと
-
pkg
ディレクトリの作成 -
pkg/geocoding_client.go
を作成し、GetLatLng 関数を再定義
該当コミット
2023/5/4
本日のゴール
- 外部ファイル化したAPIの利用
やったこと
- ディレクトリ構成の修正
- インポート文の修正
- 動作確認
該当コミット
ファイル分割したローカルパッケージにある関数名は大文字にする。
パッケージ名はディレクトリ名と同じにする。
main.go
でのインポート
package main
import (
// 略
"github.com/nanami69/theater-tracker/backend/pkg/geocoding"
)
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;