Skip to content

Latest commit

 

History

296 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Travel Log — 観光地訪問記録アプリ

観光地への「訪問記録」を主役にしたアプリ。

地図 訪問記録
地図画面 訪問記録

ランクで大きさと色が、シリーズでピンの中のアイコンが決まる。訪問記録は日時・写真・メモを残せる。

このアプリでできること

定番の観光地に「ランク」が付いて地図に並ぶ Wikipedia(ja)の月次ページビューから知名度をA〜Eの5段階にしたものをランクとして持ち、地図ピンの大きさと色になる。有名なところほど大きく目立つので、地図を眺めるだけで「この辺の目玉はどこか」が読める
訪問記録で、観光スポットを1つずつ埋めていける 行った場所に訪問記録(いつ・何回・メモ・写真)を付けると、ピンが緑+✓に変わる。同じ場所へ何度行っても記録でき、後から編集も、訪問回数を増やさない追記もできる
観光地データを管理画面から取り込める スポットデータはこのリポジトリに同梱していない(ライセンスの都合)。管理画面の「GitHubリポジトリからスポット種別取り込み」で travel-log-data から取り込める

その他の機能

機能 説明
絞り込み ランク・シリーズ・訪問状態・カテゴリ・訪問日で絞れる。訪問日は「今日」「過去1年」をカレンダーを開かずに選べる
見た目の3軸 ランク(A〜E。色と大きさ)・シリーズ(1スポットに1つ。ピンの中のアイコンと形)・カテゴリ(複数可。絞り込み専用)
訪問予定 行きたい場所のブックマーク。訪問を記録すると自動的に外れる
訪問予定リスト 複数スポットを順序付きでまとめた旅程。地図に紫の矢印で経路を描き、Googleマップの経路検索へそのまま渡せる。回り終わったリストはアーカイブして一覧から下げられる(中身は残り、スポット画面の「アーカイブ」から読み直せる)
訪問記録への追記 後から思い出したこと・分かったことを、訪問回数を増やさずに同じ訪問記録へ書き足せる。「◯月◯日に追記」として元の記録の下に並び、写真も付けられる
未訪問記録 「行ったが見られなかった」「事前の下調べ」を、訪問済みに数えない記録として残せる。日時を入れればその日の経路に含まれ、入れなければ下調べのメモになる
非表示スポット 興味のない公開スポットを、自分の地図・一覧からだけ隠せる(他のユーザーには影響しない)
口コミ 公開・本文のみのシンプルな投稿(同じスポットに何件でも書ける)
スポット種別 観光地・郵便局・御朱印など。種別ごとに独立したURL・シリーズ・カテゴリ・対象地域を持ち、管理者が自由に追加・削除できる
経路(巡った順の矢印) 巡った順に繋いだ線と矢印を地図に出す(CSVで取り込む)。線をタップすると経由地の一覧と区間ごとの説明が開く
訪問日の軌跡 訪問記録のある日をカレンダーから選ぶと、その日に回った順に矢印で結んだ軌跡が出る。期間指定もできる
旅程の天気 訪問予定リストの各スポットに予定日の予報アイコンが出る。予定日の前後1週間を並べて天気の良い日を探し、旅程の長さを保ったまま予定日をずらせる
重ね表示 別のスポット種別を半透明で同じ地図に重ねられる
対象地域 種別ごとに日本(都道府県)・特定の国(州・県)・世界全体(国)から選べる
AIに聞く スポットの詳細から、Claude・ChatGPT・Perplexity・Grok・Geminiに所在地と座標を添えて質問できる(営業時間とアクセスを先に答えるよう頼んである)
周辺を探す 管理者が地図の右クリック/長押しから検索語(「ランチ」など)を入れると、周辺の候補を地図に番号つきで並べる。まず地図データ(Overture Maps と OpenStreetMap)から1秒かからず出し、足りなければAIのweb検索で同じ一覧に探し足せる。右側のパネルで外す・選ぶ・まとめて追加ができる。任意機能で、知識サーバー(chiezo)への接続先を設定し、種別ごとに有効にしたときだけ出る
情報を集めさせる 周辺を探すの「待たない版」。知識サーバーに依頼だけして離れ、集まった頃にスポットの候補として取り出す(AIのレポートのような使い方)。地域を順に舐めながら集めていき、地図で範囲を指定すると「そこは収集済みか」まで答える —— まだなら、その地域を先に集めさせられる。座標は地図データから引き直すので、当てられなかったものには印が付く
PWA対応 ホーム画面に追加して、アドレスバーなしの独立アプリとして起動できる(オフライン対応は未実装)

スクリーンショット

地図 訪問記録
地図画面 訪問記録
スポット一覧 スポット詳細
スポット一覧 スポット詳細モーダル
管理画面
管理画面

技術スタック

領域 採用技術
フロントエンド Next.js (App Router) + TypeScript
UI Tailwind CSS
地図 MapLibre GL JS(OpenStreetMap タイル)
天気予報 Open-Meteo(CC BY 4.0。旅程の各スポットの予報アイコン)
バックエンド Next.js Route Handlers + PostgreSQL(Docker上でローカル完結)

セットアップ

docker compose -f docker-compose.dev.yml up --build

Node や Postgres をローカルにインストールする必要はない。初回起動時、 アプリが起動前に 01_schema.sql を自動実行してテーブルと既定のスポット種別 (tourist=観光地、データは空)を作成する。以降スキーマに変更が入った場合も、 起動するたびに未適用のマイグレーションが自動で当たる。

http://localhost:7040 を開くと /login にリダイレクトされる。初回はアカウントが 存在しないため「アカウントを作成」フォームが表示されるので、メールアドレスと パスワード(8文字以上)を入力して初回アカウントを作成する(自動的に管理者になる)。 他のユーザーを増やしたい場合は、管理者が/[type]/adminの「ユーザー管理」から追加する。

スマホの実機で確認するときは、.envALLOWED_DEV_ORIGINS=<ホスト名かIP>(カンマ区切りで複数可)を 設定してから起動する。未設定のままlocalhost以外で開くと画面が真っ白になる。

ログイン画面

観光地データを含め、スポットデータは同梱していないため、ログイン後に 外部データ(travel-log-data)の手順でCSVインポートする。

Docker を使わない場合

ローカルに Postgres を別途用意し、.env.example.env.local としてコピーして DATABASE_URL / SESSION_SECRET を設定した上で npm install && npm run dev でも 起動できる。その場合は db/init/01_schema.sqldb/migrations/*.sql を手動で実行する。

Googleログインの設定(任意)

メールログインに加えて、Googleアカウントでのログインも利用できる(設定しない場合は メールログインのみ)。

  1. Google Cloud Console で OAuthクライアントID(種類: ウェブアプリケーション)を作成する
  2. 「承認済みのリダイレクトURI」に http://localhost:7040/api/auth/google/callback を追加する(本番環境ではそのドメインのURLも追加する)
  3. 発行された クライアントID / クライアントシークレット を設定する
    • Docker Compose の場合: リポジトリ直下に .env ファイルを作成し、 GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET を書く(Next.js が読む .env.local とは別物なので注意)
    • Docker を使わない場合: .env.local に同じ2つの値を追加する

既存のメールアカウントと同じメールアドレスでGoogleログインした場合は自動的に紐付く。 既定では自由なサインアップはできず、管理者が作成したアカウントのみログインできる (GOOGLE_AUTO_SIGNUP=trueを設定した環境では、Googleでログインした人が一般ユーザーとして 自動登録される。設定すると、URLを知っていてGoogleアカウントを持つ人は誰でも入れる)。

リバースプロキシ(HTTPS終端)の背後で動かす場合、アプリが受け取るリクエストは プロキシからのプレーンHTTPになる。リダイレクトURIのスキームはX-Forwarded-Proto / X-Forwarded-Hostから判断しているため、これらを送らないプロキシ(NAS内蔵の リバースプロキシ機能など、設定項目が無いものもある)ではリダイレクトURIが http://で組まれ、Googleコンソールに登録したhttps://のURIと一致せず認証に失敗する。 その場合はPUBLIC_BASE_URLに公開URLを設定する(設定するとヘッダより優先される)。

PUBLIC_BASE_URL=https://travel.example.com
  • .env(Docker Compose)/ .env.local(Dockerを使わない場合)/ standalone用のコピー(docker-compose.standalone.yml)冒頭のx-public-base-url のいずれかに書く
  • パス部分は使わず、スキーム・ホスト・ポートだけを見る
  • https://を設定するとセッションCookieにSecure属性が付く。同じインスタンスに LAN内からhttp://<ホスト>:7040で直接アクセスしてもログインできなくなる点に注意 (公開URL経由でアクセスすること)
  • 直接http://<ホスト>:7040で使う場合や、X-Forwarded-*を送るプロキシ(nginx等で 設定済み)の場合は設定不要
周辺を探す(任意)

地図の右クリック/長押しメニューに「この周辺を探す」(スポット管理者・管理者のみ)を 出す機能。検索語・半径・件数(10〜30)と探し方を選ぶと、周辺の候補が地図に番号つきの 印で並ぶ。右側のパネルで外す・チェックする(見出しの「すべて選ぶ」で一括)・ シリーズとランクを直す・まとめて追加する。 シリーズには地図データや AI が付けたジャンル(「ラーメン」「カフェ」…)が初期値で入り、 その種別で初めて出てきたジャンルは、追加と同時にシリーズの見た目(短いラベルと色)ごと 登録される —— 地図のピンがジャンルごとに見分けられるようになる。 まとめて同じ札を付けたいときは「カテゴリを全件に追加」を使う。 追加したものだけが通常のスポット(手動追加扱い)になり、候補そのものは保存されない。

まず地図データを引き、AI は任意の後段として足りないぶんを補う。

かかる時間 やること
地図データ(必ず走る) 数秒(半径による) 正確な名前と座標を漏らさず集める
AI で精査・補完(チェックしたときだけ) 15秒〜2分 一覧から選ぶ・ジャンルとランクを付ける・地図に無いものを足す

「AI に精査させる」にチェックを入れると、地図データの結果が先に出たあとでAI に渡す。 AI が選んだ候補にはチェックと「AIが選定」の印が付き、ジャンル・ランク・一言も入る (選ばれなかった行は薄くなるが消えないので、選び直せる)。地図データに無いスポットは 同じ一覧に足される。待っている間もパネルと地図は見られる。

地図データは件数の上限を持たない(半径の中にあるものを全部並べる)。件数を決めるのは 半径で、新宿での実測は 300m=1,798件(2.2秒)/ 500m=3,300件(3.9秒)/ 1km=5,267件(6.6秒)。 広く取るほど一覧が長くなるので、まずは狭い半径から。件数の選択はAIに聞くときだけ出る (精査に渡す件数と足させる上限を兼ねる。返させる件数がそのまま待ち時間になるため)。

探す範囲は地図に薄い青の円で出る(半径を選び直すとその場で変わる)。探している間は **左クリックでも「ここでもう一度探す」**が出るので、隣の区画へ続けて探せる。 各行の「座標と店名を見比べる」は、候補の座標から店名で引いた場所までの経路を Google マップで開く。1枚の地図に両方の印が出て、ずれがそのまま距離で読める(同じ場所なら 数十m、違う場所を指していれば数百m以上)。

まず地図データで雑に集め、要るときだけAIに精査させる(一覧から選び、ジャンル・ランク・ 一言を付け、地図データに無いスポットを足す)、が想定の流れ。AIが足したぶんは同じ一覧に 混ざるので、地図の印もパネルの行も色で出どころが分かる(地図データは青、AI は紫、 登録済みは灰色)。地図データは日本の種別のみ。

その場で待たずに集めさせることもできる(管理画面の「情報を集めさせる」)。 集めたいものをふつうの言葉で書くと、AI が抽出条件とプロンプトを書く —— まず地図データから名簿を機械的に作り、日本全体を密度で割った区画を 知識サーバーが順に配る。全区画を 3日で一周して精査し、そのあと 7日で一周して1件ずつ詳しく調べる(新しい店を足し、閉店や重複を落とす)。 集まったものは地図の長押しメニューの**「この周辺の集めた情報」**から、周辺を探すと 同じパネルで選んで追加する。座標は同じく地図データから引き直し、当てられなかったものは 「座標なし」の印が付いて、住所から引き直すまで選べない。

その範囲がまだ集まっていなければ、そう出る。 円に重なる区画のうちまだ見ていない 数を挙げ、「この範囲をいま集める」で先に回らせられる(定時の巡回の予定は動かない)。 間違いに気づいたスポットは、管理画面の間違い報告の一覧から名指しで直させられる。 管理画面では、いま知識サーバーが収集中かどうかと、巡回ごとの一周の進み具合も見られる。

依頼しても勝手には走らない。 知識サーバー側の管理画面で「有効にする」を押すまでは 止まったままで、travel-log 側からは動かせない(外のアプリが勝手に AI を走らせないため)。

地図データは Overture Maps と OpenStreetMap の両方を引いて重ねる。穴の位置が違うためで、 店の数と店のURLは Overture(新宿500mの飲食店で 1,651件 / URL は見た200件中193件)、 都道府県と構造化された住所は OpenStreetMap(同 403件 / 51件)が埋める。

AI で探すときは念の入れ方も選ぶ(既定は「さっくり」)。

頼み方 実測(10件)
さっくり(既定) web の検索を 2 回までに抑え、裏取りをさせない 17 秒
しっかり 1 件ずつ確かめ、根拠の URL を必須にする 41 秒

遅さの一番の原因は裏取りの検索回数で、上限を言い渡すだけで半分以下になる。

AI で探した後は、パネル見出しの「AIとのやり取り」で頼んだ本文と返ってきた本文を そのまま確かめられる(整形しないので、プロンプトを直すときはこれを見る)。

AIへの中継は知識サーバー chiezo/v1/ai/complete で、 APIキーはあちらが持つ(このアプリには置かない)。使うには2段の設定が要る。

  1. .env(Docker Compose)か .env.local に接続先を書く。未設定なら機能そのものが出ない
  2. /[type]/admin の「スポット種別の設定」で、その種別の「周辺をAIで探す」を有効にする (既定は無効。AIの枠を使うので、使う種別だけ開ける)
CHIEZO_BASE_URL=http://chiezo.example.lan:7010
# 相手の既定(任意。未設定なら antigravity)。管理画面で選んだ相手があればそちらが優先
CHIEZO_AI_BACKEND=codex

同じホストの Docker で chiezo を動かしているのに「時間内に応答しなかった」になるときは、 コンテナからホストの公開ポートへ戻る経路が塞がれている(NAS のファイアウォール等)。 その場合は compose ファイルの networks: のコメントを外して chiezo のネットワークに相乗りし、 接続先を http://chiezo-app:7010 にする(手順と注意は compose ファイルのコメントにある)。

AI で探すときの相手(chiezo に登録された AI のうち web 検索を持つもの)と、そのモデル・ 考える深さは、探索の画面の「AIの設定」で選ぶ(既定は畳んである)。相手が落ちている間に 逃がすためと、その場の目的で速さを選ぶためのもの。選んだ内容はその端末に覚える。

  • 軽いモデル・低い深さほど早い。深さは未選択なら自動で low を使う(相手が対応するとき)
  • AI の候補の位置は chiezo の地図辞典(Overture → OSM の順)で確かめる。開いたばかりの 店は載っていないのが普通なので「位置未確認」は珍しくない。地図で見て違えば、 追加後に「位置を修正」で直す
  • ライセンスは辞典ごとに違う(OpenStreetMap は ODbL、Overture Maps は CDLA Permissive 2.0 / Apache 2.0)。自分のインスタンスで周辺を引く用途を想定していて、地図表示には 出典を出している。travel-log-data へ還元するときは、継承条件のある OSM 由来の 名前・座標をそのまま持ち込まないこと(本体は MIT)。どちらの辞典から来たかは 説明文に書かれる
  • 当たった候補は、座標・住所・公式サイトを地図データの値で取り直す。 AIに残るのは 「どの店を挙げるか」と一言の要約だけ。実測で AI の座標は当たった店でも数十m、 遠いものは 446m ずれていた(地図上では別の建物に立つ)
  • 地図データで見つからなかった候補は「地図データに無い」と出し、既定では選ばれない。 開いたばかりの店なら普通のことなので、地図で確かめてから足せばよい。 頼んだ半径の外に出た候補にも印を出す
  • 説明文には「AIがwebから収集(日付)」と参照URLが自動で入るので、手で書いた説明と 見分けがつく。要約の中身の真偽はAIの答え次第
  • 商用のグルメサイト・地図サービスのAPIは「自前DBへの保存・再配布禁止」の条件があり 出どころにできないため、web検索を持つAIに調べさせる形にしている

本番運用

サーバーを持たずに公開する場合は Vercel + Supabase の手順を参照 (無料プランで動くが、訪問記録のZIPエクスポートだけは使えない)。以下はDocker運用の手順。

mainへのpushでGitHub Actions(.github/workflows/docker-publish.yml)が本番用イメージを ビルドしてghcr.io/rtcode337/travel-log:latest(+コミットSHAタグ)へ公開する。 本番ホストでは本リポジトリのクローン(docker-compose.ymlを使う)を置き、 イメージはビルドせずpullして使う。

# 初回のみ: SESSION_SECRETを設定(リポジトリ直下の.envに書いておくのが楽)
echo "SESSION_SECRET=$(openssl rand -base64 32)" >> .env

# 初回・更新とも共通
docker compose pull && docker compose up -d
  • Composeのプロジェクト名は本番・開発・standaloneともtravel-log。以前は travel-log-prod/travel-log-devに分けていたため、それ以前から動かしている ホストでは初回だけ旧スタックを止めてから起動すること(止めずにupすると、 同じdataを奪い合う新旧2つのスタックが並ぶ)

    docker compose -p travel-log-prod down          # 開発機では -p travel-log-dev -f docker-compose.dev.yml
    docker compose pull && docker compose up -d
  • 公開されるイメージはghcr.io/rtcode337/travel-logの1つ。スキーマとマイグレーションSQLも これに焼き込まれ、アプリが起動時に当てる(かつてはtravel-log-db-initという専用イメージが あったが、同じDBへ繋いでいるアプリに寄せた)

  • データの置き場はdata/の1つだけ(リポジトリ直下)。この下にdb/(Postgresの実データ)・ photos/(添付写真)・exports/(エクスポートのZIP)をinitサービスが起動時に作る。 バックアップはdata/をコピーすればよい(停止してからコピーすること)。 standaloneでもホストに用意するのは親ディレクトリ1つで、YAML冒頭のx-data-dirに書く

  • appは非rootで動く。既定は10001:10001で、ホストに実在しない番号にしてある (万一コンテナから抜け出されても、ホストのユーザーのファイルには届かない)。 そのぶんdata/はホストから見て「知らないユーザー」の持ち物になるので、 バックアップから書き戻すときやホストで直接編集したいときは.envTRAVEL_LOG_UID/TRAVEL_LOG_GIDid -u/id -gを設定する (standaloneはYAML冒頭のx-run-as)。所有者合わせは起動前にinitが 自動でやるので、chownを手で打つ必要はない

  • 置き場をdata/にまとめる前から動かしているホストは、更新時に1回だけ移すこと。 docker compose downのあと:

    mkdir -p data/db
    sudo mv data/18 data/db/          # Postgresの実データ
    sudo mv photos/* data/photos/     # data/photos・data/exports が無ければ先に mkdir
    sudo mv exports/* data/exports/

    standaloneは冒頭のx-db-data-dir/x-photos-dir/x-exports-dirx-data-dir1つに 変わっているので、コピー側のYAMLも差し替える。移さずに起動すると空のDBが作られ、 初期状態のアプリが立ち上がる(旧データは消えない)。 さらに古いdb/data/のままのホストは、先にmv db/data dataを済ませてから上を実行する

  • PostgreSQL 16 時代のデータを持つ既存環境は、更新前に1回だけデータ移行が必要 (docs/postgres-18-upgrade.md)。移行せずに起動すると dbコンテナが起動に失敗する(データは壊れない)

  • DBスキーマの更新は自動docker compose upするとappが待ち受けを始める前に未適用の マイグレーションを順に当てる(失敗した場合は待ち受けに進まないので、古いスキーマのまま 動くことはない)。適用状況は docker compose exec db psql -U travel_log -d travel_log -c "select * from schema_migrations"、 ログはdocker compose logs appで確認できる(migrate:で始まる行)

  • GitHub Actions(GITHUB_TOKEN)から公開したパッケージはリポジトリに自動リンクされ、 可視性もリポジトリと同じ(=public)になるため、追加設定なしで匿名pullできる。 リポジトリをprivateにした場合は本番ホストでdocker login ghcr.io (read:packages権限のPAT)が必要になる

  • イメージはlinux/amd64linux/arm64のマルチアーキで公開しており、pull時に ホストに合う方が自動選択される

  • 特定時点に戻したいときはdocker-compose.ymlのイメージタグをlatestから sha-xxxxxxx(Actionsが付けるコミットSHAタグ)に一時的に変えてpullし直す (マイグレーションは前進のみで、巻き戻しスクリプトは持たない。スキーマ変更を伴う リリースを戻す場合はDBのバックアップからのリストアが必要)

  • Actionsはビルド番号(20260722-1035-9162ba9のようなJST日時+短縮コミットハッシュ)を イメージに埋め込み、管理画面/[種別キー]/adminの見出し横に表示する。今動いている イメージがいつのどのコミットのものか、pull後の反映確認に使える(ローカル開発時など 埋め込みが無い場合は「開発ビルド」と表示される)

リポジトリを置けない環境(NASのコンテナマネージャー等)

.envもクローンも置けず、管理画面にYAMLを貼り付けて起動するタイプの環境向けに docker-compose.standalone.example.ymlを用意している。 ${...}を使わず値を直書きし、bindマウントを絶対パスで書いたもの(サービス構成・ 起動順はdocker-compose.ymlと同じ)。docker-compose.standalone.ymlとしてコピーし (コピー側は.gitignore済み。秘密を直書きするため雛形は直接編集しない)、冒頭の 「ここだけ編集」——3つの置き場(db / data / photos)の絶対パスと SESSION_SECRET——を書き換えて貼り付ければ起動する。

SESSION_SECRETは空のままだとログイン時にSESSION_SECRET is not setで失敗するので、 必ず32バイト程度のランダム文字列を入れること。

画面

すべて/[種別キー]/...の形式(例: /tourist/map)で、種別ごとに切り替えて使う。 ルートパス/はログイン後、最後に開いていた種別(無ければ管理画面で設定した既定の種別)の/mapへ自動リダイレクトされる。

パス 内容
/[type]/map 地図(ホーム)。ランク・シリーズ・訪問状態・カテゴリでフィルタ。ピンタップ→スポット詳細モーダルへ。別の種別のダウンロード済み公開スポットを半透明で重ねて表示することもできる(複数の種別を同時に重ねられる。タップで読み取り専用の詳細を表示し、そこから訪問記録と今の種別の訪問予定リストへの追加ができる)。経路・訪問順・訪問予定リストの詳細からはGoogle マップの経路検索で開ける(訪問予定リストの詳細では、地点の左端の≡をつかんで回る順番を入れ替えられる)。左下に今表示中のスポット種別名を小さく表示
/[type]/spots 「都道府県から探す」(地域別ドリルダウン)と「シリーズから探す」(検索+絞り込み+ページング)の2タブ
/[type]/admin (管理者・スポット管理者専用)スポットの承認待ちキュー・追加・編集・削除・CSVインポート・経路(巡った順の矢印)のインポート・travel-log-dataへの還元用エクスポート(手動追加した公開スポットと画面から削除したCSV由来スポットの一覧をMarkdownでダウンロード)・間違い報告のあったスポットの一覧(名前と理由をまとめてAIに渡すテキストにできる/まとめて取り消せる)。adminのみキー一覧を指定しての削除・GitHubリポジトリ(travel-log-data形式)からの一括取り込み・スポット種別の管理・ユーザー管理も可能
/[type]/account 自分のメールアドレス・ロール・現在の種別の表示、ログアウト、訪問記録ZIPのダウンロード(管理者が作成したとき)、アカウント削除。スポット種別の切り替えは/[type]/mapの左下の種別チップから行う
/login メールログイン、または Google でログイン(任意、要設定)

権限とスポット承認フロー

ロール できること
管理者(admin) 全操作可能。スポット承認・編集・削除、スポット種別の管理、ユーザー管理。初回セットアップで作成した唯一のアカウントが自動的に管理者になる
スポット管理者(spot_admin) スポットの承認・編集・削除・公開作成(adminと同等)。種別設定・ユーザー管理は不可
モデレーター(moderator) 地図上でのスポット追加(非公開または承認待ち)。承認待ちキューの閲覧のみ
一般ユーザー(user) 閲覧、自分の訪問記録・訪問予定の管理、非公開スポットの追加のみ

新しいアカウントは/[type]/adminの「ユーザー管理」(admin専用)から作成する (既定では自由サインアップ不可。GOOGLE_AUTO_SIGNUPについては「セットアップ」のGoogleログインの節を参照)。

利用者は自分でアカウント画面からアカウント削除できる。訪問記録・写真・訪問予定・口コミ・非公開スポットは 消え、登録した公開スポットは登録者の情報だけを外して残る(他のユーザーの地図から消さないため)。 最後の管理者はアカウントを削除できない。

/[type]/map上で右クリック(モバイルは長押し)するとスポットを追加できる。送信時のstatusは ロールにより既定が異なり(userは非公開、それ以外は承認待ち)、admin/spot_adminは /[type]/adminの承認待ちキューから個別承認、または「すべて承認」で一括公開できる。 追加フォームの「訪問を記録」(名前欄の下)を開いたまま送信すると、そのスポットに訪問記録が1件つく (いま訪れている場所を、追加と記録の2手順を踏まずに残せる)。

訪問予定・未訪問記録・口コミ・訪問写真

スポット詳細モーダルから、以下を管理できる。

  • 訪問予定(非公開): 「行きたい場所」のブックマーク。訪問を記録すると自動的に外れる
  • 未訪問記録(非公開): 訪問記録と同じフォーム・同じ訪問履歴に「訪問済みに数えない 記録」として残すメモ(訪問記録フォームのチェックボックスから)。 訪問日を入れると「訪れたが改めて来たい」記録としてその日の訪問順の経路に含まれ、 訪問予定からも外れる。訪問日なしは下調べのメモになり、訪問予定は残る。 どちらもピンは緑にならず、一覧では「未訪問」バッジ付きで表示される
  • 非表示スポット: 興味のない公開スポットを自分の地図・一覧から隠すユーザーごとの設定。 解除は/[type]/spotsの「非表示にしたスポット」から
  • 間違い報告(管理者・スポット管理者のみ): 中身がおかしい公開スポットに理由を添えて 報告する(理由は空でもよい)。スポット自体には影響せず、/[type]/adminの一覧に集まる。 一覧からは名前と理由だけのテキストにまとめてAIに渡せ、片付いたらまとめて取り消せる
  • 口コミ(公開): 星評価はなく本文のみ。投稿するたびに増える掲示板方式で、同じスポットに何件でも書ける
  • 写真(非公開): 自分の訪問記録にのみ添付。ブラウザ側で縮小・圧縮した上でdata/photos/ フォルダ(Dockerではbindマウント)に保存され、/api/photos/...経由(本人のみ)で配信する。 サムネイルをタップすると拡大表示になり、ピンチ(かダブルタップ)で拡大縮小、 スワイプで同じ訪問記録の前後の写真へ移れる(追記に付けた写真も同じ並びに続く)
  • 訪問記録のエクスポート: 管理者が/[type]/adminで対象ユーザーのメールアドレスを 指定して実行すると、そのユーザーの訪問記録を全スポット種別ぶんまとめたZIPが バックグラウンドで作られる(visits-<種別キー>.csv=訪問のメモ+スポット情報、 photos/=添付写真)。出来上がったZIPは管理画面と、対象ユーザー本人のアカウント画面から ダウンロードできる。ZIPはdata/exports/に置かれ、同じユーザーのものは最新1件だけ残る

スポット詳細にはWikipedia検索による概要表示機能もある(種別ごとにON/OFF・参照言語版を設定可)。

外部データ(travel-log-data)

スポットの初期データ(シード用CSV)は本リポジトリには同梱せず、別リポジトリ travel-log-dataにスポット種別ごとの CSVとして置き、/[type]/adminのCSVインポート機能で取り込む(tourist=観光地も含め全種別共通)。

name,name_kana,lat,lng,region,rank,series,categories,description,key
厳島神社,いつくしまじんじゃ,34.2960222,132.3198944,広島県,A,神社,,海に浮かぶ大鳥居,厳島神社
  • 必須列: name, lat, lng, regionrankはA〜Eか空欄(ランクを使う種別のみ意味を持つ)。 series/categoriesは自由入力。 categoriesは1スポットに複数付けられ、パイプ区切りで書く(列ごと省略した場合は 既存スポットのカテゴリを変更しない)。keyは省略可の種別内一意な参照キー (経路のCSVがスポットを指すのに使う)
  • 上記以外の列がヘッダーにあるとインポートを中止する。綴り違いや旧フォーマットの CSVが、値の欠けた状態で黙って取り込まれるのを防ぐため
  • 差分更新(key一致を最優先、無ければname+lat+lngの完全一致で同一判定)。 一致した既存スポットは内容が違えばCSVの内容で上書きされるため、CSV側の修正も 再アップロードだけで反映され、同じCSVを何度アップロードしても重複登録されない
  • スポットを巡った順に矢印で繋ぐ経路は、別ファイルroutes.csv(列: route,series,seq,spot_key,description,leg_description)を同じ管理画面から スポットCSVの後に取り込む(スキーマの詳細はtravel-log-data/README.md参照)
  • CSVから行を消してもDBからは消えない(差分更新はCSVに無い行に触らないため)。 削除は/[type]/adminの「キー一覧を指定して削除」(admin専用)にkeyを1行1つ 貼り付けて行う。travel-log-data側のexclude.txt(削除したスポットのkeyを追記して いくファイル)をそのまま貼る想定で、該当が無いキーはエラーにせず読み飛ばす

観光地(tourist)データのdescriptionはWikipedia記事冒頭文の引用(CC BY-SA 4.0)、 lat/lngはWikipedia記事座標(CC BY-SA 4.0)またはWikidata P625(CC0)由来のため、 それぞれの出典表示はtravel-log-data側で行っている(本リポジトリのMITライセンスはアプリのコードにのみ適用)。 ランクの決め方などデータの詳細はtravel-log-data/README.mdを参照。

スポット種別のカスタマイズ

管理者は/[type]/adminから新しい種別を追加でき、種別ごとに次を設定できる。

  • 一般公開のON/OFF(既定OFF=admin/spot_admin限定)、口コミ・Wikipediaリンクの有効/無効
  • 対象地域(日本/特定の国/世界)、Wikipedia検索の言語版
  • ランク(A〜E)を使うかどうか
  • シリーズの一覧・見た目(ピンの中のアイコン/文字・形、ランクを使わない種別では色)、カテゴリの一覧

キー・表示名の手入力フォームのほか、{ key, label, settings?, series?, categories? }形式の JSONファイルアップロードでも一括設定できる(travel-log-dataリポジトリの <スポットキー>/settings.jsonが実例。スキーマの詳細はtravel-log-data/README.md参照)。

ライセンス

MIT License

About

訪問記録・写真・訪問予定を地図で管理するセルフホスト型トラベルログPWA。Next.js + PostgreSQL製で、観光地に限らず任意の「スポット種別」を作って使い回せる

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages