個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證官民共存型オープンデータ管理システム — 表記ゆれ正規化つきマージエンジンを備えたオープンデータ基盤 (AGPL-3.0)
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗官民共存型オープンデータ管理システム — 行政と民間が双方向にデータを持ち寄り、 住所などの表記ゆれを自動で名寄せ・マージして、公開カタログと REST API で利活用するための基盤です。
既存の CKAN / Socrata / OpenDataSoft などが「行政が一方向に公開する」モデルであるのに対し、 OpenData Bridge は 官民双方向の持ち寄り + 表記ゆれ正規化つきマージエンジン を差別化の核としています。
公開インスタンス: **https://opendata-bridge.vercel.app**(サービス紹介は /about)
| 機能 | 概要 |
|---|---|
| 組織・認証 | 組織(行政 / 民間)単位のアカウント。セッションは署名付き JWT(HttpOnly Cookie)。 |
| データセット管理 | CSV アップロード、メタデータ(タイトル・説明・ライセンス・タグ・更新頻度・公開範囲)管理。 |
| 外部データソース連携 | 外部の REST API(JSON)や CSV の URL をデータセットに関連付け、手動 / 定期(毎日)で取り込み。取得結果は CSV アップロードと同じ実体に書き込まれるため、カタログ・マージ・公開 API がそのまま使える。 |
| 承認フロー | 下書き → 承認待ち → 公開 / 差し戻し。公開は組織の ADMIN のみ承認可能。 |
| 公開カタログ | 公開データセットをキーワード・組織種別・タグ・自治体(都道府県 / 市区町村) で横断検索。CSV ダウンロード対応。 |
| 地域メタデータ | データセットに「対象地域」、組織に「所在地」を設定。対象地域が未設定のデータセットは組織の所在地で絞り込まれる。市区町村は総務省の全国地方公共団体コード(47 都道府県 / 1,741 市区町村)に基づくマスタから選択。 |
| マージエンジン | 2 つのデータセットをキー列 + 正規化レベルで名寄せ結合。マージ型は 項目拡張型(片方の行をすべて残して列を足す)と 共通抽出型(両方にある行だけ残す)の 2 種。住所・カナ・電話・日付などの表記ゆれを吸収し、双方向のカバー率などの指標とアンマッチ例を提示。結果は出典(来歴)と版を保持した新データセットとして保存し、元データの更新に追従できる。詳細は docs/design/merge-design.md。 |
| REST API v1 | API キー(Bearer)認証で公開データセットの一覧・メタデータ・本体(JSON / CSV)を取得。呼び出し量を自動計測。 |
別システムのデータベースが公開している API を、データセットに 関連付けて随時引き込む 仕組みです。CSV アップロードを置き換えるものではなく、同じ器を埋める 2 つ目の経路として 共存します。
CSV アップロード ─┐
├─→ storage の datasets/{id}.csv ─→ カタログ / ダウンロード / マージ / REST API v1
外部 API 取り込み ─┘
取得したデータは既存の CSV ストレージ(lib/storage.ts)へ書き出されます。この一点に
揃えているため、下流の機能は一切変更していません。API 取り込みのデータセットも、
そのままマージエンジンの入力にでき、公開カタログにも載り、REST API v1 からも読めます。
| 項目 | 対応内容 |
|---|---|
| 取得方式 | REST API(JSON)/ CSV の URL |
| 認証 | なし / Bearer トークン / 任意のヘッダ / クエリパラメータ |
| レコード位置 | ドット区切りで指定(CKAN なら result.records)。直下が配列なら省略可 |
| 列マッピング | 取得元のキー → 保存する列名。未指定なら全キーを自動で列にする |
| 同期 | 手動(いつでも)/ 定期(毎日・syncMode = SCHEDULED) |
| 接続テスト | 保存前に取得を試し、列と先頭 10 行を確認できる |
| 履歴 | 成功 / 失敗・行数・所要時間・実行種別を SyncRun に記録 |
設定はデータセット詳細画面 /dashboard/datasets/[id] の「外部データソース」パネルから行います。
定期同期の有効化: CRON_SECRET を環境変数に設定してください(未設定なら定期同期は
503 で無効。手動同期は使えます)。スケジュールは vercel.json の
0 18 * * *(UTC)= 毎日 03:00 JST です。
取り込みの設計上の判断:
lib/sources/fetch.ts)。
クラウドのメタデータ(169.254.169.254)や内部ホストへは到達できません。lib/crypto.ts)。相手に提示する
必要があるためハッシュ化はできませんが、平文では保存しません。画面にも再表示されません。/ — ランディングページ/catalog — 公開データカタログ(検索・フィルタ) / /catalog/[id] — データセット詳細・ダウンロード/signup /login — 組織登録 / ログイン/dashboard — ダッシュボード
/dashboard/datasets — 自組織のデータセット一覧 / new・[id](外部データソース設定を含む)・[id]/edit/dashboard/merge — マージウィザード/dashboard/api-keys — API キー発行・失効・利用量/dashboard/settings — 組織設定DATABASE_URL と直接続の DIRECT_URL を使用)storage/datasets/)/ Supabase Storage(非公開バケット)を環境変数で自動切替(lib/storage.ts)jose(JWT)+ bcryptjscsv-parse / csv-stringify前提:
# 1. 取得
git clone <this-repo>
cd opendata-manage-system
# 2. 依存インストール(postinstall で prisma generate が走ります)
npm ci
# 3. 環境変数を用意
cp .env.example .env
# .env を編集し、以下を設定してください。
# - DATABASE_URL: PostgreSQL の接続文字列(Supabase のプーラー 6543 + ?pgbouncer=true、
# またはローカル PostgreSQL)
# - DIRECT_URL: マイグレーション用の直接続(Supabase の 5432。ローカル PostgreSQL の
# 場合は DATABASE_URL と同じ値でも可)
# - SESSION_SECRET: 十分長いランダム値(例: openssl rand -base64 32)
# SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY は未設定でOK
# (未設定時は CSV をローカルの storage/datasets/ に保存)
# 4. DB マイグレーション(PostgreSQL に接続してテーブル作成)
npx prisma migrate deploy # prisma/migrations の初期マイグレーションを適用
# (スキーマを変更して作り直す場合は: npx prisma migrate dev)
# 5. シードデータ投入(組織2・ユーザー2・公開データセット4件)
npx prisma db seed
# 6. 開発サーバー
npm run dev # http://localhost:3000
ストレージについて:
SUPABASE_URLとSUPABASE_SERVICE_ROLE_KEYが両方設定されていれば CSV は Supabase Storage(非公開バケット)へ、未設定ならローカルのstorage/datasets/へ 保存されます。どちらを使うかはlib/storage.tsが環境変数だけで自動判定します。
シード投入後、以下のアカウントでログインできます(パスワードは共通で demo1234)。
| メールアドレス | 組織 | 種別 | 権限 |
|---|---|---|---|
[email protected] | 鳥取県庁 | 行政 | ADMIN |
[email protected] | 山陰データラボ株式会社 | 民間 | ADMIN |
投入される公開データセット(すべて PUBLISHED / 一般公開):
1 と 3 は住所が一部同一地点を指しており、マージウィザードで キー列「住所」/ 正規化レベル「住所 (address)」で結合すると 表記ゆれを吸収して 約 40% のマッチ率 で名寄せできます(マージ機能のデモ)。
シードは冪等です。npx prisma db seed を何度実行しても、既存の組織・ユーザー・
データセットと CSV を作り直し、常に上記 4 件の状態になります。
元 CSV は prisma/seed-data/ にコミットされており、実行時に lib/storage.ts 経由で
保存されます(ローカルは storage/datasets/、Supabase 設定時は Supabase Storage の非公開バケット)。
API キーは /dashboard/api-keys で発行します。認証は Bearer 方式です。
curl -H "Authorization: Bearer odb_あなたのキー" \
"http://localhost:3000/api/v1/datasets?q=避難所&org_type=GOVERNMENT"
エンドポイント・認可・エラー形式の詳細は docs/api.md を参照してください。
DB は Supabase Postgres、CSV ファイルは Supabase Storage(非公開バケット) を使います。
ホスティングは Vercel です。Vercel の Marketplace Integration(Supabase) で接続すると、
DB 接続用の環境変数が 自動注入 され、手動で設定するのは SESSION_SECRET だけで済みます。
従来どおり
DATABASE_URL/DIRECT_URLなどを手動設定する方法も引き続き使えます (「B. 手動で環境変数を設定する場合」を参照)。アプリは注入変数・手動変数のどちらでも動くようlib/db-env.tsで接続 URL を解決します。
Vercel ダッシュボードで Add New… → Project を開き、GitHub の
tottorigeek/opendata-bridge を Import します(Framework は Next.js が自動検出)。
プロジェクトの Storage(または Integrations)タブ → Marketplace から Supabase を選び、 新規 Supabase プロジェクトを作成(リージョンは Tokyo 推奨)、または既存プロジェクトを接続します。 接続すると、以下の環境変数が Production / Preview に自動注入されます (出典: Supabase 公式ドキュメント):
POSTGRES_PRISMA_URL … プーラー(Prisma 用)接続文字列 → 本アプリの通常クエリ用 DATABASE_URL として利用POSTGRES_URL_NON_POOLING … 直接続 → マイグレーション用 DIRECT_URL として利用POSTGRES_URL / SUPABASE_URL / SUPABASE_JWT_SECRET ほかSQL を手で流す必要はありません。 テーブルはデプロイ時のビルドコマンドが
prisma/migrationsの初期マイグレーションから自動作成します。Marketplace 版のプーラー URL には
pgbouncer=trueが付かない 既知の問題がありますが、 本アプリはプーラー URL(ポート 6543 /pooler.supabase.com)を検出して自動付与するため 追加設定は不要です。
Supabase ダッシュボード → Storage → New bucket で、名前 datasets のバケットを作成します。
Public バケットにはしない(非公開のまま)でください。CSV は必ずアプリのルート経由で配信します。
別名にする場合は、環境変数 SUPABASE_STORAGE_BUCKET にその名前を設定します。
Storage を使うには
SUPABASE_SERVICE_ROLE_KEYが必要です。 Marketplace Integration は service_role キーを この名前では注入しません(公式にはSUPABASE_SECRET_KEY等を注入)。 CSV を Supabase Storage に保存する場合は、Supabase の Project Settings → API の service_role キーを、Vercel の環境変数にSUPABASE_SERVICE_ROLE_KEYという名前で 手動追加してください(未設定ならローカルstorage/datasets/動作にフォールバックします)。
SESSION_SECRET を手動追加Vercel の Settings → Environment Variables(Production / Preview 両方)に、
SESSION_SECRET を追加します(値は openssl rand -base64 32 で生成)。
Integration を使う場合、手動追加が必須なのは基本これだけです
(Storage を使うなら上記 SUPABASE_SERVICE_ROLE_KEY も)。
Deploy を実行します。ビルドコマンド(package.json の build =
node scripts/migrate-deploy.mjs && next build)が、注入変数・手動変数のどちらからでも
接続 URL を解決し、ビルド時に prisma/migrations の初期マイグレーションを
直接続(DIRECT_URL / POSTGRES_URL_NON_POOLING)経由で適用します。
以降は GitHub への push で自動再デプロイされます。
関数のリージョンは DB と同じ地域にしてください。 vercel.json の
regionsでhnd1(東京)を指定しています。Vercel の既定は米国東部(iad1)で、 東京の Supabase と組み合わせると DB への問い合わせのたびに太平洋を往復し、 DB を使うページの表示が 1〜2 秒遅くなります。Supabase を別リージョンに作った場合は、regionsをそのリージョンに近い値(一覧)に変えてください。
シードは Vercel 上では自動実行されません。本番の環境変数をローカルに取得して一度だけ実行します。
Vercel CLI の vercel env pull を使うと、Integration が注入した変数も
まとめて取得できます。
vercel link # 対象プロジェクトに紐付け(初回のみ)
vercel env pull .env.production # 注入変数を含む環境変数を取得
# Storage にもシード CSV を入れる場合は、上記で service role キーが取得できていなければ
# .env.production に SUPABASE_SERVICE_ROLE_KEY を追記しておく
set -a; . ./.env.production; set +a
npx prisma db seed # scripts/seed.mjs 経由で接続 URL を解決して実行
SUPABASE_URL(またはNEXT_PUBLIC_SUPABASE_URL)とSUPABASE_SERVICE_ROLE_KEYが 揃っているとシードの CSV も Supabase Storage の非公開バケットに保存されます。揃っていないと DB だけが投入され CSV 本文が Storage に入らない(=ダウンロードできない)ため注意してください。
Integration を使わず、Settings → Environment Variables(Production / Preview 両方)に自分で設定する
従来の方法も使えます。この場合は次を設定します(Storage を使わないなら下 2 つは省略可)。
| 変数 | 取得場所 |
|---|---|
DATABASE_URL | Supabase の Connect(画面上部)→ ORMs / Prisma の Transaction pooler(ポート 6543)。末尾に ?pgbouncer=true を付ける。 |
DIRECT_URL | Supabase の Connect → Session pooler(pooler.supabase.com のポート 5432)。※ Direct connection(db.<ref>.supabase.co)は IPv6 専用のため、Vercel のビルド環境からは到達できません。 |
SESSION_SECRET | 手元で生成: openssl rand -base64 32。 |
SUPABASE_URL | Project Settings → API の Project URL(https://<project-ref>.supabase.co)。Storage 利用時のみ。 |
SUPABASE_SERVICE_ROLE_KEY | Project Settings → API の service_role キー(サーバー専用シークレット)。Storage 利用時のみ。 |
既にほかのアプリが使っている Supabase プロジェクトに相乗りする場合、public スキーマが空でないため
prisma migrate deploy が P3005(The database schema is not empty) で失敗します。
その場合は本アプリ専用の PostgreSQL スキーマに分離します(テーブル名の衝突も完全に回避できます)。
CREATE SCHEMA IF NOT EXISTS opendata;DATABASE_URL … ...?pgbouncer=true&schema=opendataDIRECT_URL … ...?schema=opendataテーブル(_prisma_migrations 含む)はすべて opendata スキーマ内に作成され、public には触れません。
Supabase のテーブルエディタではスキーマ切替で opendata を選ぶと確認できます。
| 対策 | 内容 |
|---|---|
| API キー | DB には SHA-256 ハッシュのみ保存(ApiKey.keyHash)。全文は発行時レスポンスでしか返さず以降は復元不可。一覧表示は keyPrefix(先頭 8 文字)から組み立てる。 |
| セッション失効 | JWT に jti を持たせ Session テーブルで台帳管理。ログアウトで revokedAt を立てるため、トークンが漏れても即座に無効化できる(revokeAllUserSessions() で全端末失効)。 |
| レート制限 | RateLimit テーブルによる固定ウィンドウ方式(サーバーレスではインスタンス間でメモリを共有できないため DB を集計点にする)。ログイン 10回/5分(+ IP 30回/5分)、サインアップ 5回/時、公開 API 600回/分、マージ実行 20回/5分。 |
| ユーザー列挙対策 | ログイン失敗時、ユーザー不在でもダミーハッシュに対して bcrypt を実行し、応答時間の差から登録有無が漏れないようにする。 |
| CSV インジェクション | ダウンロード・API の CSV 出力で、= + - @ 等で始まるセルにシングルクォートを前置(-1.5 のような数値は除外)。他組織が Excel で開く前提のため必須。 |
| マージの DoS 対策 | 入力 20 万行 / 出力 50 万行の上限。多対多結合による行数爆発を結合ループ内で検知して 413 を返す。 |
| 組織種別の詐称対策 | 種別は登録時の自己申告のため、Organization.verified が false の間はカタログで「行政(未確認)」と中立表示する。 |
| セキュリティヘッダ | next.config.ts で CSP / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / Permissions-Policy / HSTS(本番のみ)を全レスポンスに付与。X-Powered-By は無効化。 |
組織に verified を付与する方法: 現状は管理 UI が無いため、Supabase の SQL Editor で
UPDATE "Organization" SET "verified" = true WHERE id = '<組織ID>';を実行します。 公式ドメインのメールや公文書などで実在を確認してから付与してください。
マイグレーションの注意:
0002_security_hardeningは既存の平文 API キーを PostgreSQL のsha256()でその場ハッシュ化するため、発行済みキーは失効しません。sha256()は PostgreSQL 11 以降が必要です(Supabase は対応済み)。
lib/storage.ts が service_role キーで認証したサーバー SDK 経由でのみ本文を読み書きします。
公開 URL・署名 URL は使いません。ダウンロードは /api/datasets/[id]/download が
組織 / 公開設定に応じて認可した上で本文をストリーム配信します。SUPABASE_SERVICE_ROLE_KEY はサーバー専用シークレットです。クライアント側コードや
NEXT_PUBLIC_* 変数には絶対に置かないでください。SESSION_SECRET は既定値のまま運用しないでください(Cookie 署名鍵)。NODE_ENV=production で配信されるため、セッション Cookie の secure 属性が有効になります。GNU Affero General Public License v3.0(AGPL-3.0) で公開しています。全文は LICENSE を参照してください。
同梱データ(市区町村マスタ)の出典は NOTICE に記載しています。
脆弱性の報告は SECURITY.md、コントリビューションについては CONTRIBUTING.md を参照してください。