Skip to content

Commit b9fc310

Browse files
committed
README に「このアプリでできること」を置き、用語と説明文を直す
**README の主役を先に書く。** これまでは機能を並べただけで、何のためのアプリかが 読み取れなかった。ページビュー由来のランクで地図に並ぶこと、訪問記録で 観光スポットを 1 つずつ埋めていけること、データは同梱せず管理画面から 取り込めることの 3 点を先頭に置き、残りは「その他の機能」の表にまとめた。 **「スクリーンショットはすべてサンプルデータです」は消した** —— スポットは travel-log-data の実データで、作り物はアカウントと訪問記録のメモだけ。 **訪問日時の説明が実物と合っていなかった。** 既定で現在日時が入っているので、 「空欄のままにすると」では自分で消さないと空にならないことが読み取れない。 「「削除」で空にすると」に直す。 **「退会」は「アカウント削除」に統一した**(画面・エラー文・ドキュメント・識別子)。 自分のアカウントを消す操作であって、サービスからの脱退ではないため。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 8eb3d47 commit b9fc310

10 files changed

Lines changed: 67 additions & 55 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ NextAuthではなく自前実装。`lib/auth/session.ts`がHMAC-SHA256で署名
107107

108108
**新規アカウントの作り方は既定では2つだけ** —— 初回セットアップ(ユーザーが0人のとき`/api/auth/setup`かGoogleログインで作る最初の1人。自動的にadmin)と、管理者による`/[type]/admin`のユーザー管理からの作成。`GOOGLE_AUTO_SIGNUP=true`を設定した環境でだけ、Googleでログインした人を一般ユーザー(`role='user'`)として自動登録する(`lib/auth/google.ts``isGoogleAutoSignupEnabled`)。**有効にするとURLを知っていてGoogleアカウントを持つ人は誰でも入れる**ので、既定はオフのまま。初回adminの作成だけは`insert ... where not exists (select 1 from users)`で1文に閉じてあり、同時に2人がログインしても片方しかadminにならない。
109109

110-
**退会(`DELETE /api/account`)は本人が自分のアカウントを消す口**。消えるのはアカウント行と、FKの`on delete cascade`で連れて消える訪問記録・訪問予定・訪問予定リスト・口コミ・非表示設定・エクスポートジョブ、行が消える前に集めた写真ファイル/ZIPの実体、そして**自分の非公開スポット**(本人にしか見えない=個人のデータのため)。**公開・承認待ち・却下のスポットとルートは残る**(`created_by``on delete set null`。他のユーザーの地図から突然スポットが消えないようにするため)。**最後の管理者は退会できない**(管理者による他人の削除`/api/admin/users/[id]`と同じガードが、こちらにも別途要る)。画面はアカウントタブの赤い節で、取り消せない操作なので**自分のメールアドレスを打ち直させてから**実行する。
110+
**アカウント削除(`DELETE /api/account`)は本人が自分のアカウントを消す口**。消えるのはアカウント行と、FKの`on delete cascade`で連れて消える訪問記録・訪問予定・訪問予定リスト・口コミ・非表示設定・エクスポートジョブ、行が消える前に集めた写真ファイル/ZIPの実体、そして**自分の非公開スポット**(本人にしか見えない=個人のデータのため)。**公開・承認待ち・却下のスポットとルートは残る**(`created_by``on delete set null`。他のユーザーの地図から突然スポットが消えないようにするため)。**最後の管理者は自分のアカウントを削除できない**(管理者による他人の削除`/api/admin/users/[id]`と同じガードが、こちらにも別途要る)。画面はアカウントタブの赤い節で、取り消せない操作なので**自分のメールアドレスを打ち直させてから**実行する。
111111

112112
### PWA(インストール可能化)
113113

README.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,32 @@
88

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

11-
## 主な機能
12-
13-
- **地図中心のスポット閲覧**。ランク・シリーズ・訪問状態・カテゴリ・訪問日での絞り込み
14-
- スポットの見た目は**ランク**(A〜E。色と大きさ。使うかは種別ごとに選ぶ)と**シリーズ**(1スポットに1つ。ピンの中のアイコン・文字と形)で決まり、**カテゴリ**(1スポットに複数可)は絞り込みに使う。観光地では知名度をA〜Eの5段階にしたものをランクに使っている
15-
- **訪問記録**(いつ・何回・メモ・写真)と、同一スポットへの複数回訪問に対応(訪問日時は未入力なら「時期不明」)。記録した内容は後から編集できる
16-
- **訪問予定**(行きたい場所のブックマーク)。訪問を記録すると自動的に外れる
17-
- **未訪問記録**。訪問したけれど休みや時間の都合でちゃんと見られなかった(改めて来たい)ときの記録や、事前の下調べのメモを、訪問記録と同じ場所に「訪問済みに数えない記録」として残せる。訪問日を入れればその日の訪問順の経路に含まれ訪問予定からも外れる。訪問日なしは下調べのメモになり訪問予定は残る
18-
- **非表示スポット**。興味のない公開スポットを、ユーザーごとに自分の地図・一覧から非表示にできる(他のユーザーには影響しない)
19-
- **口コミ**(公開・本文のみのシンプルな投稿)
20-
- スポットには「種別」があり(観光地・郵便局・御朱印など)、種別ごとに独立したURL・独自のシリーズ/カテゴリ/対象地域を持てる。管理者が自由に追加・削除できる
21-
- **経路(巡った順の矢印)**。スポットを巡った順に繋いだラインと進行方向の矢印を地図に表示できる(訪問順のある種別向け。CSVで取り込み、絞り込みモーダルの「経路を表示」トグルでオン/オフする。シリーズ・カテゴリの絞り込みにも連動)。線をタップするとルート詳細(ルート名・説明・全経由地の一覧。経由地の間には区間ごとの説明=移動手段なども出せる)が開く。ルートはスポットと同じ公開/非公開の仕組みを持ち、公開ルートは公開スポットのダウンロード時に一緒に端末へ保存される
22-
- **訪問日での絞り込み**。自分の訪問記録がある日を新しい順のドロップダウンから1日選ぶと、その日に訪問したスポットだけが残り、地図では訪問した順に矢印で結んだ「その日の軌跡」が表示される
23-
- 種別ごとに**対象地域**を選べる(既定は日本=都道府県、特定の国=州・県、世界全体=国ごと)
24-
- **PWA対応**(インストール可能)。スマホの「ホーム画面に追加」やPCブラウザのインストール機能で、アドレスバーなしの独立アプリとして起動できる(オフライン対応は未実装)
11+
## このアプリでできること
12+
13+
| | |
14+
|---|---|
15+
| **定番の観光地に「ランク」が付いて地図に並ぶ** | Wikipedia(ja)の月次ページビューから知名度をA〜Eの5段階にしたものを**ランク**として持ち、地図ピンの**大きさと色**になる。有名なところほど大きく目立つので、地図を眺めるだけで「この辺の目玉はどこか」が読める |
16+
| **訪問記録で、観光スポットを1つずつ埋めていける** | 行った場所に**訪問記録**(いつ・何回・メモ・写真)を付けると、ピンが緑+✓に変わる。同じ場所へ何度行っても記録でき、後から編集もできる |
17+
| **観光地データを管理画面から取り込める** | スポットデータはこのリポジトリに同梱していない(ライセンスの都合)。管理画面の「GitHubリポジトリからスポット種別取り込み」で [travel-log-data](https://github.com/rtcode337/travel-log-data) から取り込める |
18+
19+
## その他の機能
20+
21+
| 機能 | 説明 |
22+
|---|---|
23+
| **絞り込み** | ランク・シリーズ・訪問状態・カテゴリ・訪問日で絞れる |
24+
| **見た目の3軸** | **ランク**(A〜E。色と大きさ)・**シリーズ**(1スポットに1つ。ピンの中のアイコンと形)・**カテゴリ**(複数可。絞り込み専用) |
25+
| **訪問予定** | 行きたい場所のブックマーク。訪問を記録すると自動的に外れる |
26+
| **訪問予定リスト** | 複数スポットを順序付きでまとめた旅程。地図に紫の矢印で経路を描き、Googleマップの経路検索へそのまま渡せる |
27+
| **未訪問記録** | 「行ったが見られなかった」「事前の下調べ」を、訪問済みに数えない記録として残せる。日時を入れればその日の経路に含まれ、入れなければ下調べのメモになる |
28+
| **非表示スポット** | 興味のない公開スポットを、自分の地図・一覧からだけ隠せる(他のユーザーには影響しない) |
29+
| **口コミ** | 公開・本文のみのシンプルな投稿 |
30+
| **スポット種別** | 観光地・郵便局・御朱印など。種別ごとに独立したURL・シリーズ・カテゴリ・対象地域を持ち、管理者が自由に追加・削除できる |
31+
| **経路(巡った順の矢印)** | 巡った順に繋いだ線と矢印を地図に出す(CSVで取り込む)。線をタップすると経由地の一覧と区間ごとの説明が開く |
32+
| **訪問日の軌跡** | 訪問記録のある日をカレンダーから選ぶと、その日に回った順に矢印で結んだ軌跡が出る。期間指定もできる |
33+
| **重ね表示** | 別のスポット種別を半透明で同じ地図に重ねられる |
34+
| **対象地域** | 種別ごとに日本(都道府県)・特定の国(州・県)・世界全体(国)から選べる |
35+
| **AIに聞く** | スポットの詳細から、Claude・ChatGPT・Perplexity・Grok・Geminiに所在地と座標を添えて質問できる |
36+
| **PWA対応** | ホーム画面に追加して、アドレスバーなしの独立アプリとして起動できる(オフライン対応は未実装) |
2537

2638
## スクリーンショット
2739

@@ -37,8 +49,6 @@
3749
|---|---|
3850
| ![管理画面](docs/images/03-admin.png) | |
3951

40-
*スクリーンショットはすべてサンプルデータです。*
41-
4252
## 技術スタック
4353

4454
| 領域 | 採用技術 |
@@ -219,9 +229,9 @@ docker compose pull && docker compose up -d
219229
新しいアカウントは`/[type]/admin`の「ユーザー管理」(admin専用)から作成する
220230
(既定では自由サインアップ不可。`GOOGLE_AUTO_SIGNUP`については「セットアップ」のGoogleログインの節を参照)。
221231

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

226236
`/[type]/map`上で右クリック(モバイルは長押し)するとスポットを追加できる。送信時のstatusは
227237
ロールにより既定が異なり(`user`は非公開、それ以外は承認待ち)、admin/spot_adminは

app/api/account/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { SESSION_COOKIE } from "@/lib/auth/session";
1010
export const maxDuration = 60;
1111

1212
/**
13-
* 退会(本人による自分のアカウントの削除)。
13+
* アカウント削除(本人による自分のアカウントの削除)。
1414
*
1515
* **消えるもの**: アカウント行(メールアドレス・Googleの紐付け・パスワード)と、
1616
* FKの`on delete cascade`で連れて消える訪問記録・訪問予定・訪問予定リスト・
@@ -23,7 +23,7 @@ export const maxDuration = 60;
2323
* (他のユーザーの地図から突然スポットが消えないようにするため)。
2424
*
2525
* 管理者による他人の削除は`/api/admin/users/[id]`のDELETE。**あちらと違い、
26-
* 最後の管理者かどうかのガードはここでも要る** —— 自分で退会して誰も
26+
* 最後の管理者かどうかのガードはここでも要る** —— 自分でアカウントを削除して誰も
2727
* 管理画面に入れなくなるのを防ぐため。
2828
*/
2929
export async function DELETE() {
@@ -41,7 +41,7 @@ export async function DELETE() {
4141
return NextResponse.json(
4242
{
4343
error:
44-
"最後の管理者は退会できません。先に別のユーザーを管理者にしてください。",
44+
"最後の管理者はアカウントを削除できません。先に別のユーザーを管理者にしてください。",
4545
},
4646
{ status: 400 }
4747
);
@@ -81,14 +81,14 @@ export async function DELETE() {
8181
} catch (err) {
8282
await client.query("rollback").catch(() => {});
8383
return NextResponse.json(
84-
{ error: err instanceof Error ? err.message : "退会に失敗しました。" },
84+
{ error: err instanceof Error ? err.message : "アカウントの削除に失敗しました。" },
8585
{ status: 500 }
8686
);
8787
} finally {
8888
client.release();
8989
}
9090

91-
// ファイルの削除はDBのコミット後。失敗しても退会自体は成立させる
91+
// ファイルの削除はDBのコミット後。失敗しても削除自体は成立させる
9292
// (孤児ファイルが残るだけで、参照する行はもう無い)
9393
await deleteVisitPhotos(photoPaths);
9494
for (const path of exportPaths) {

components/AccountView.tsx

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,20 @@ export default function AccountView({ typeKey }: { typeKey: string }) {
3333
router.refresh();
3434
};
3535

36-
// 退会は取り消せないので、メールアドレスを打ち直させてから実行する
36+
// アカウント削除は取り消せないので、メールアドレスを打ち直させてから実行する
3737
// (confirm()1つだと誤タップで消える)
38-
const [withdrawOpen, setWithdrawOpen] = useState(false);
39-
const [withdrawInput, setWithdrawInput] = useState("");
40-
const [withdrawing, setWithdrawing] = useState(false);
41-
const [withdrawError, setWithdrawError] = useState<string | null>(null);
38+
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
39+
const [deleteAccountInput, setDeleteAccountInput] = useState("");
40+
const [deletingAccount, setDeletingAccount] = useState(false);
41+
const [deleteAccountError, setDeleteAccountError] = useState<string | null>(null);
4242

43-
const handleWithdraw = async () => {
44-
setWithdrawing(true);
45-
setWithdrawError(null);
43+
const handleDeleteAccount = async () => {
44+
setDeletingAccount(true);
45+
setDeleteAccountError(null);
4646
const { error } = await api.account.remove();
47-
setWithdrawing(false);
47+
setDeletingAccount(false);
4848
if (error) {
49-
setWithdrawError(error.message);
49+
setDeleteAccountError(error.message);
5050
return;
5151
}
5252
router.push("/login");
@@ -102,21 +102,21 @@ export default function AccountView({ typeKey }: { typeKey: string }) {
102102
</section>
103103
)}
104104
<section className="mb-4 rounded-xl border border-red-200 bg-white p-4">
105-
<h2 className="text-sm font-bold text-red-700">退会</h2>
105+
<h2 className="text-sm font-bold text-red-700">アカウント削除</h2>
106106
<p className="mt-0.5 text-xs text-gray-500">
107107
アカウントと、訪問記録・写真・訪問予定・口コミ・非公開スポットを削除します。
108108
<strong>取り消せません。</strong>
109109
<br />
110110
あなたが登録した公開スポットは、登録者の情報だけを外して残ります
111111
(他のユーザーの地図から消さないため)。
112112
</p>
113-
{!withdrawOpen ? (
113+
{!deleteAccountOpen ? (
114114
<button
115115
type="button"
116-
onClick={() => setWithdrawOpen(true)}
116+
onClick={() => setDeleteAccountOpen(true)}
117117
className="mt-3 rounded-lg border border-red-300 px-3 py-1.5 text-sm text-red-700"
118118
>
119-
退会する
119+
アカウントを削除する
120120
</button>
121121
) : (
122122
<div className="mt-3">
@@ -125,33 +125,33 @@ export default function AccountView({ typeKey }: { typeKey: string }) {
125125
</label>
126126
<input
127127
type="email"
128-
value={withdrawInput}
129-
onChange={(e) => setWithdrawInput(e.target.value)}
128+
value={deleteAccountInput}
129+
onChange={(e) => setDeleteAccountInput(e.target.value)}
130130
autoComplete="off"
131131
className="mt-1 w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
132132
/>
133-
{withdrawError && (
134-
<p className="mt-1 text-xs text-red-600">{withdrawError}</p>
133+
{deleteAccountError && (
134+
<p className="mt-1 text-xs text-red-600">{deleteAccountError}</p>
135135
)}
136136
<div className="mt-3 flex gap-2">
137137
<button
138138
type="button"
139139
onClick={() => {
140-
setWithdrawOpen(false);
141-
setWithdrawInput("");
142-
setWithdrawError(null);
140+
setDeleteAccountOpen(false);
141+
setDeleteAccountInput("");
142+
setDeleteAccountError(null);
143143
}}
144144
className="flex-1 rounded-lg border border-gray-300 py-2 text-sm"
145145
>
146146
やめる
147147
</button>
148148
<button
149149
type="button"
150-
onClick={handleWithdraw}
151-
disabled={withdrawing || !email || withdrawInput.trim() !== email}
150+
onClick={handleDeleteAccount}
151+
disabled={deletingAccount || !email || deleteAccountInput.trim() !== email}
152152
className="flex-1 rounded-lg bg-red-600 py-2 text-sm font-medium text-white disabled:opacity-50"
153153
>
154-
{withdrawing ? "削除中…" : "完全に削除する"}
154+
{deletingAccount ? "削除中…" : "完全に削除する"}
155155
</button>
156156
</div>
157157
</div>

components/VisitFields.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default function VisitFields({
2929
onPhotosChange: (photos: string[]) => void;
3030
/** 写真の縮小処理中を親へ伝える(送信ボタンのdisable用) */
3131
onProcessingChange?: (processing: boolean) => void;
32-
/** 訪問日時欄の下の説明文。未訪問記録では空欄の意味が「時期不明」ではなく
32+
/** 訪問日時欄の下の説明文。未訪問記録では空にした意味が「時期不明」ではなく
3333
* 「下調べ」になるため、呼び出し側で差し替えられるようにしてある */
3434
visitedOnHint?: string;
3535
}) {
@@ -118,7 +118,9 @@ export default function VisitFields({
118118
</button>
119119
)}
120120
<p className="mt-1 text-xs text-gray-400">
121-
{visitedOnHint ?? "空欄のままにすると「時期不明」として記録されます。"}
121+
{/* **既定は現在日時が入っている。** 「空欄のままにすると」では、
122+
自分で消さないと空にならないことが読み取れない */}
123+
{visitedOnHint ?? "「削除」で空にすると「時期不明」として記録されます。"}
122124
</p>
123125
</div>
124126

components/VisitFormModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default function VisitFormModal({
118118
onProcessingChange={setProcessingPhotos}
119119
visitedOnHint={
120120
unvisited
121-
? "空欄のままなら下調べのメモになります(どの経路にも含まれず、訪問予定も残ります)。"
121+
? "「削除」で空にすると下調べのメモになります(どの経路にも含まれず、訪問予定も残ります)。"
122122
: undefined
123123
}
124124
/>

docs/database.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ erDiagram
126126
- 既定では自由サインアップは無く、管理者が作成する。**最初の1アカウントだけ**セットアップ
127127
画面から作成でき、自動的に admin になる(`GOOGLE_AUTO_SIGNUP=true` の環境では、
128128
Googleログインで一般ユーザーが自動作成される)
129-
- 退会(`DELETE /api/account`)で行を消すと、`on delete cascade` の訪問記録・訪問予定・
129+
- アカウント削除(`DELETE /api/account`)で行を消すと、`on delete cascade` の訪問記録・訪問予定・
130130
訪問予定リスト・口コミ・非表示設定・エクスポートジョブが一緒に消える。
131131
スポットとルートは `created_by``on delete set null` なので**残る**
132132
(非公開スポットだけはAPI側で明示的に削除する)

0 commit comments

Comments
 (0)