SeeFT は技大祭(NUTFes)のシフト管理システムです。
api/: Go 1.16 + Echo v4 + GORM v1.25 + PostgreSQLmobile/lib/: Flutter(Dart >= 3.6.0)、fvm管理、Hive+SharedPreferencesで永続化gas/: Google Apps Script(スプレッドシートにバインド)
admin/ と raw/ は使用していません。
make up # docker compose up(API + DB + admin)
make up-db # DB のみ起動
make up-api # DB → API の順で起動
make down # 全サービス停止
make build # コンテナビルド
make exec # api コンテナにシェルログイン
make seed # DB シード投入
make tidy # go mod tidy(コンテナ内)
make mobile-up # fvm flutter run -d web-server --web-port 45029 --dart-define-from-file=env/.envMac 環境は mac-up / mac-build / mac-seed、本番は prod-up / prod-build / prod-seed。
api/lib/
├── di/ # 依存性注入(di.InitializeServer)
├── entity/ # ビジネスエンティティ
├── usecase/ # ビジネスロジック + *sql.Rows の Scan
├── internals/
│ ├── controller/ # HTTP I/O(Echo)。DB アクセス禁止
│ └── repository/ # SQL 実行のみ。*sql.Rows を返す
└── externals/{db,server,slack}
mobile/lib/
├── pages/ # 画面(StatefulWidget)
├── widgets/ # 再利用 Widget
├── models/ # データモデル
├── utils/ # api.dart, logger.dart, permanent_store.dart
├── configs/ # importer.dart(共通 import 集約)
└── theme/ # tokens.dart(AppColors, AppFontSizes)
gas/{shift,task,user,rescue}/ # ドメイン別。コード.js / onChange.js 等
SQL は必ずプレースホルダで書く
query := "SELECT * FROM bureaus WHERE id = $1"
rows, err := db.QueryContext(ctx, query, id)文字列連結("... " + id)は SQL インジェクション脆弱性のため禁止。
エラーレスポンスは JSON 形式で返す
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})return err で Echo の自動処理に委ねるのは旧スタイル。
空リストは []Type{} を返す
return []entity.Task{}, nilnil を返すと JSON が null になりクライアントが壊れる。
命名規則: Interface は XxxxController / XxxxUseCase / XxxxRepository(PascalCase)、実装 struct は xxxController(lowerCamelCase)、Factory は NewXxxController(deps...)、ファイル名は snake_case。Repository メソッドは All / Find / FindByXxx / Create / Update / Destroy。
その他: HTTP ステータスは 200 / 201 / 204 / 400 / 404 / 500 を基本。Optional フィールドは sql.NullString / sql.NullInt64 で受け取り .Valid チェック後に entity の string / int に詰める。コメントは日本語、GoDoc 形式は使わない。
API はシングルトン経由のみ
import 'package:seeft_mobile/configs/importer.dart';
final users = await api.getUsers();package:http を mobile/lib/utils/api.dart 以外で直接 import するのは禁止。
非同期またぎは mounted チェック
final data = await api.fetchData();
if (!mounted) return;
setState(() => _data = data);dispose 後に setState を呼ぶと例外になるため必須。
ログは logger、print 禁止
logger.i('loaded: ${items.length}');
logger.e('failed', error: e, stackTrace: st);print() は新規コードでは使わない(既存コードの残骸は順次置換)。
その他: ファイル名は snake_case、Widget は PascalCase、State は _WidgetNameState。private メンバ・関数は _ プレフィックス。Widget には可能な限り const コンストラクタ。色・フォントは AppColors.main / AppFontSizes.md 経由(生 hex 禁止)。
API URL は PropertiesService から取得
const baseUrl = PropertiesService.getScriptProperties().getProperty("API_BASE_URL");URL のハードコードは禁止。本番 / 開発の切り替えに PropertiesService を使う。
破壊的操作の前に確認ダイアログ
const confirm = ui.alert("削除しますか?", ui.ButtonSet.OK_CANCEL);
if (confirm === ui.Button.CANCEL) {
Logger.log("キャンセルされました");
return;
}ロックは try-catch-finally で必ず解放
const lock = LockService.getScriptLock();
lock.waitLock(30000);
try {
// バッチ操作
} finally {
lock.releaseLock();
}その他: doPost / doGet は ContentService.createTextOutput(...).setMimeType(ContentService.MimeType.TEXT) を返す。新規コードは const 使用(var 禁止)。ログは Logger.log を基本(console.log はデバッグ用途で併用可)。
- ブランチ名:
feat/{username}/{issue-number}/{description}またはfix/... - コミットメッセージは日本語、
feat:/fix:プレフィックス - ドキュメント変更(AGENTS.md・README 等)も issue → branch → PR の正規フローを通す
- PR は
.github/pull_request_template.mdのフォーマットに従う - PR 本文で
resolve #XXXと書くと issue が自動 close される
- 新規 SQL はプレースホルダで書く
- 設定値(API URL・シークレット)は環境変数 /
PropertiesServiceから取得 - Flutter で非同期またぎ後の
setState()前にmountedチェック - GAS で
LockService取得後はfinallyでreleaseLock() - 空リストは
[]Type{}を返す
- 新規ライブラリの導入(特に Flutter の状態管理系)
- 既存 entity の JSON キー命名変更(mobile / gas に影響)
- API レスポンス形式の変更
- DB スキーマ変更(マイグレーション)
- SQL を文字列連結で組み立てる
- API URL やシークレットをハードコードする
package:httpをmobile/lib/utils/api.dart以外で import するprint()を新規コードで使う(mobile/lib/)mobile/lib/に Riverpod / Provider / Bloc 等の状態管理ライブラリを導入する.envや認証情報をコミットする
新旧の規約が混在する箇所があります。新規コードは上記ルールに従い、既存は段階的に整理します。
- Repository の SQL 文字列連結(残 8 ファイル / 約 41 件) → #266
- JSON キー命名: 古い entity は camelCase、新しい entity は snake_case。クライアント影響のため既存維持
- エラーレスポンス: 古い controller は
return err、新しいものは map 形式 - 空リスト返却: 一部 UseCase が
nilを返す箇所あり(順次[]Type{}へ)