Skip to content

各棟ごとの合計募金額を取得するAPIを追加#1082

Merged
hikahana merged 10 commits intodevelopfrom
codex/campus-donations-buildings-api
May 1, 2026
Merged

各棟ごとの合計募金額を取得するAPIを追加#1082
hikahana merged 10 commits intodevelopfrom
codex/campus-donations-buildings-api

Conversation

@hikahana
Copy link
Copy Markdown
Collaborator

@hikahana hikahana commented Apr 12, 2026

対応Issue

resolve #0

概要

  • GET /campus_donations/buildings/{year} を追加
  • 年度ごとの各棟の合計募金額を返す API を実装
  • campus_donations -> teachers -> room_teachers -> rooms -> buildings -> years を用いて集計
  • room_teachers 経由の重複計上を避けるため、棟と募金の組み合わせを一度 DISTINCT 化してから合算
  • OpenAPI / oapi-codegen / orval の生成物を更新
  • その他のグルーピングも追加した!!!

画面スクリーンショット等

  • URL
    API追加のみのためなし
    スクリーンショット
image

テスト項目

  • go build ./... が通ること
  • GET /campus_donations/buildings/{year} で各棟の合計募金額が取得できること
  • 新スキーマ相当の確認用DBで 2025 指定時に 電気棟: 3000, 機械・建設棟: 5000 が返ること

以下をコマンド実行したらデータ追加されます。レスポンスも下に貼ります。

docker compose -f compose.db.yml exec -T db mysql -u root -proot finansu_db <<'SQL'
INSERT INTO years (year)
SELECT 2025
WHERE NOT EXISTS (
  SELECT 1 FROM years WHERE year = 2025
);

SET @year_id := (
  SELECT id
  FROM years
  WHERE year = 2025
  ORDER BY id
  LIMIT 1
);

DELETE FROM campus_donations WHERE id IN (9101, 9102, 9103, 9104, 9105, 9106);
DELETE FROM room_teachers WHERE id IN (9101, 9102, 9103, 9104, 9105, 9106);
DELETE FROM rooms WHERE id IN (9101, 9102, 9103, 9104, 9105, 9106);
DELETE FROM teachers WHERE id IN (9101, 9102, 9103, 9104, 9105, 9106);
DELETE FROM users WHERE id IN (9101, 9102);
DELETE FROM buildings WHERE id IN (9101, 9102, 9103, 9104, 9105, 9106);

INSERT INTO users (id, name, bureau_id, role_id) VALUES
  (9101, 'Swagger確認用局員A', 1, 1),
  (9102, 'Swagger確認用局員B', 1, 1);

INSERT INTO buildings (id, name, unit_number) VALUES
  (9101, '機械・建設棟', 1),
  (9102, '機械・建設棟', 2),
  (9103, '電気棟', 1),
  (9104, 'GX棟', 1),
  (9105, '博士棟', 1),
  (9106, 'ラジオアイソトープセンター', 1);

INSERT INTO teachers (id, name, position, department_id, is_black, remark) VALUES
  (9101, 'Swagger確認用教員A', '教授', 1, false, '棟別募金集計APIの動作確認用'),
  (9102, 'Swagger確認用教員B', '准教授', 1, false, '棟別募金集計APIの動作確認用'),
  (9103, 'Swagger確認用教員C', '助教', 1, false, '棟別募金集計APIの動作確認用'),
  (9104, 'Swagger確認用教員D', '助教', 1, false, '棟別募金集計APIの動作確認用'),
  (9105, 'Swagger確認用教員E', '助教', 1, false, '棟別募金集計APIの動作確認用'),
  (9106, 'Swagger確認用教員F', '助教', 1, false, '棟別募金集計APIの動作確認用');

INSERT INTO rooms (id, building_id, floor_number, room_name) VALUES
  (9101, 9101, '5', '501'),
  (9102, 9102, '6', '601'),
  (9103, 9103, '7', '701'),
  (9104, 9104, '1', '101'),
  (9105, 9105, '2', '201'),
  (9106, 9106, '1', '102');

INSERT INTO room_teachers (id, room_id, teacher_id) VALUES
  (9101, 9101, 9101),
  (9102, 9102, 9102),
  (9103, 9103, 9103),
  (9104, 9104, 9104),
  (9105, 9105, 9105),
  (9106, 9106, 9106);

INSERT INTO campus_donations (id, user_id, teacher_id, year_id, price, received_at) VALUES
  (9101, 9101, 9101, @year_id, 1200, '2025-04-01'),
  (9102, 9101, 9102, @year_id, 800, '2025-04-15'),
  (9103, 9101, 9103, @year_id, 2500, '2025-04-20'),
  (9104, 9102, 9104, @year_id, 3600, '2025-04-22'),
  (9105, 9102, 9105, @year_id, 1800, '2025-04-23'),
  (9106, 9101, 9106, @year_id, 900, '2025-04-24');
SQL

レスポンス

[
  {
    "id": 1,
    "name": "機械・建設棟",
    "totalPrice": 2000
  },
  {
    "id": 2,
    "name": "電気棟",
    "totalPrice": 2500
  },
  {
    "id": 999,
    "name": "その他",
    "totalPrice": 6300
  }
]

備考

  • develop には buildings/rooms 統合後のマイグレーションがまだ入っていないため、HTTP動作確認は新スキーマ相当を当てた確認用DBで実施しています。
  • コミット: a80e374b61eb1eb19b7cd17a808d89231e1f43d3

Summary by CodeRabbit

  • New Features

    • Campus donation reporting: new API endpoint to fetch aggregated donation totals per building for a given year (returns building id, name, total price); added OpenAPI spec for it.
  • Documentation

    • Added repository guidelines for implementing external query logic and query-builder usage.
  • Style

    • Frontend JSX/Tailwind class formatting cleaned up across many components; no behavioral changes.

@hikahana hikahana self-assigned this Apr 12, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new endpoint to fetch per-building campus donation totals for a specified year, including handler, use case, repository, domain row model, DI wiring, OpenAPI spec, and minor frontend formatting and docs updates.

Changes

Cohort / File(s) Summary
HTTP Handler
api/externals/handler/campus_donation_handler.go, api/externals/handler/handler.go
Added GetCampusDonationsBuildingsYear handler and injected campusDonationUseCase into Handler/NewHandler.
Use Case
api/internals/usecase/campus_donation_usecase.go, api/internals/usecase/wire.go
New CampusDonationUseCase interface and implementation: reads sql.Rows from repository, scans into domain rows, maps to generated.BuildingTotal, and registered with Wire.
Repository & Domain
api/externals/repository/campus_donation_repository.go, api/internals/domain/campus_donation.go, api/externals/repository/wire.go
Added CampusDonationRepository and AllBuildingTotalsByYear implementing a goqu query (joins, SUM, GROUP BY) returning *sql.Rows; added CampusDonationBuildingTotalRow; registered repository provider.
DI Wiring
api/internals/di/wire_gen.go
Wiring updated to construct NewCampusDonationRepository and NewCampusDonationUseCase, and pass the use case into handler.NewHandler.
API Spec
openapi/openapi.yaml
Added GET /campus_donations/buildings/{year} with integer year path param and buildingTotal response schema (id, name, totalPrice).
Frontend formatting
view/next-project/...
Multiple React/TSX files updated to reformat className string literals / JSX whitespace only; no behavior changes.
Docs / Guidelines
agent.md
Added repository query guidance (prefer goqu, follow dialect/ToSQL pattern).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler as "HTTP Handler\n(api/externals)" 
  participant UseCase as "CampusDonationUseCase\n(api/internals)"
  participant Repo as "CampusDonationRepository\n(api/externals)"
  participant DB as "Database"

  Client->>Handler: GET /campus_donations/buildings/{year}
  Handler->>UseCase: GetBuildingTotalsByYear(ctx, year)
  UseCase->>Repo: AllBuildingTotalsByYear(ctx, year)
  Repo->>DB: QueryContext(SQL with JOINs, SUM, GROUP BY)
  DB-->>Repo: rows
  Repo-->>UseCase: rows
  UseCase->>UseCase: scan rows -> map to BuildingTotal[]
  UseCase-->>Handler: []BuildingTotal
  Handler-->>Client: 200 OK + JSON (building totals)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through code and chased each row,

Summed up gifts beneath each building's glow,
Year marked in path, totals neat and clear,
I twitched my nose — the data hopped near,
A rabbit cheers for every donation's show.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new API endpoint for retrieving total donation amounts by building per year.
Description check ✅ Passed The description follows the template with all required sections completed, including issue reference, overview, screenshots note, test items, and remarks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/campus-donations-buildings-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 12, 2026

Deploying finansu with  Cloudflare Pages  Cloudflare Pages

Latest commit: b704750
Status: ✅  Deploy successful!
Preview URL: https://055fd259.finansu.pages.dev
Branch Preview URL: https://codex-campus-donations-build.finansu.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new feature to retrieve campus donation totals grouped by building for a specific year, including the necessary API handlers, use cases, and repository logic. Feedback highlights a potential logic error in the SQL query that could lead to double-counting donations for teachers associated with multiple buildings. Additionally, a critical bug was identified in the repository where the database statement is closed prematurely, which will cause errors when reading query results in the use case layer. It is also recommended to use integer types for the year parameter across all layers to maintain type consistency.

Comment thread api/externals/repository/campus_donation_repository.go Outdated
Comment on lines +76 to +80
defer func() {
if err := stmt.Close(); err != nil {
fmt.Println(err)
}
}()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

stmt.Close()defer で呼び出されているため、この関数が値を返した直後にステートメントがクローズされます。Goの database/sql パッケージでは、Stmt をクローズするとそのステートメントから生成された Rows もクローズされるため、呼び出し元の usecaserows.Next() を実行する際にエラーが発生します。プリペアドステートメントを明示的に再利用する必要がない場合は、cdr.client 等を介して直接 QueryContext を実行するか、rows の処理が終わるまで stmt をクローズしないように制御する必要があります。

Comment thread api/internals/usecase/campus_donation_usecase.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
openapi/openapi.yaml (1)

1295-1300: Consider constraining year to valid positive values.

Adding minimum: 1 (or your valid lower bound) to the path parameter schema would reject invalid values earlier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openapi/openapi.yaml` around lines 1295 - 1300, The path parameter "year"
currently allows any integer; constrain it by adding a lower bound to its schema
(e.g., minimum: 1) so invalid/non-positive years are rejected early. Locate the
parameter block for name: year in the OpenAPI spec and add a "minimum: 1" entry
under its schema (keeping type: integer), and optionally adjust/add "maximum" or
"format" if you have a specific valid range.
api/externals/repository/campus_donation_repository.go (1)

18-19: Use int for year across repository/usecase boundaries.

This API path parameter is already typed as integer, so keeping year as int here removes unnecessary conversions and keeps contracts stricter end-to-end.

Also applies to: 27-28

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/externals/repository/campus_donation_repository.go` around lines 18 - 19,
Change the year parameter type from string to int across the repository/usecase
boundary: update the CampusDonationRepository interface method
AllBuildingTotalsByYear(ctx context.Context, year int) (*sql.Rows, error) and
any other repository methods referenced on lines 27-28 to accept int, then
update all implementing types/methods (e.g., concrete repository struct methods)
and callers (usecases, handlers, tests) to pass an int instead of a string so
there are no runtime conversions and the contract remains consistent end-to-end.
api/internals/usecase/campus_donation_usecase.go (1)

17-19: Decouple use case from OpenAPI-generated transport models.

Using generated.BuildingTotal in the use case interface couples internal business logic to API schema regeneration. Prefer returning a domain/usecase DTO and map to generated.* in the handler layer.

Also applies to: 46-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/internals/usecase/campus_donation_usecase.go` around lines 17 - 19, The
use case interface CampusDonationUseCase currently returns API transport models
generated.BuildingTotal which couples business logic to OpenAPI codegen;
introduce a domain/usecase DTO (e.g., type BuildingTotal in the usecase or
domain package) and change the method signature of GetBuildingTotalsByYear to
return []usecase.BuildingTotal (or domain.BuildingTotal) and error, then update
the concrete implementation(s) of CampusDonationUseCase to populate and return
that DTO; finally perform the mapping from the new usecase/domain BuildingTotal
to generated.BuildingTotal inside the HTTP handler layer where the OpenAPI
models are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/externals/repository/campus_donation_repository.go`:
- Around line 72-83: The code prepares a statement with cdr.crud.Prepare and
defers stmt.Close() before returning stmt.QueryContext(c, year), which can close
the statement (and underlying resources) before the caller reads *sql.Rows;
instead, avoid deferring stmt.Close() here and call QueryContext directly on the
database connection (e.g., use cdr.crud.QueryContext(c, query, year) or
equivalent) so the returned *sql.Rows remains valid; update the function to
remove the prepare + deferred Close and invoke QueryContext on cdr.crud (or
ensure the caller is responsible for closing rows) referencing cdr.crud.Prepare,
stmt.Close, and stmt.QueryContext.

In `@api/internals/usecase/campus_donation_usecase.go`:
- Around line 25-31: The GetBuildingTotalsByYear method on campusDonationUseCase
currently passes the raw year string to the repository
(AllBuildingTotalsByYear); add input validation at the start of
GetBuildingTotalsByYear to ensure year is a valid 4-digit year (e.g., regex
`^\d{4}$` or parsing to int and range-check) and return a clear validation error
(not a DB error) when invalid; keep the repository call
(AllBuildingTotalsByYear) unchanged and only call it after the year passes
validation to prevent downstream query failures.

---

Nitpick comments:
In `@api/externals/repository/campus_donation_repository.go`:
- Around line 18-19: Change the year parameter type from string to int across
the repository/usecase boundary: update the CampusDonationRepository interface
method AllBuildingTotalsByYear(ctx context.Context, year int) (*sql.Rows, error)
and any other repository methods referenced on lines 27-28 to accept int, then
update all implementing types/methods (e.g., concrete repository struct methods)
and callers (usecases, handlers, tests) to pass an int instead of a string so
there are no runtime conversions and the contract remains consistent end-to-end.

In `@api/internals/usecase/campus_donation_usecase.go`:
- Around line 17-19: The use case interface CampusDonationUseCase currently
returns API transport models generated.BuildingTotal which couples business
logic to OpenAPI codegen; introduce a domain/usecase DTO (e.g., type
BuildingTotal in the usecase or domain package) and change the method signature
of GetBuildingTotalsByYear to return []usecase.BuildingTotal (or
domain.BuildingTotal) and error, then update the concrete implementation(s) of
CampusDonationUseCase to populate and return that DTO; finally perform the
mapping from the new usecase/domain BuildingTotal to generated.BuildingTotal
inside the HTTP handler layer where the OpenAPI models are used.

In `@openapi/openapi.yaml`:
- Around line 1295-1300: The path parameter "year" currently allows any integer;
constrain it by adding a lower bound to its schema (e.g., minimum: 1) so
invalid/non-positive years are rejected early. Locate the parameter block for
name: year in the OpenAPI spec and add a "minimum: 1" entry under its schema
(keeping type: integer), and optionally adjust/add "maximum" or "format" if you
have a specific valid range.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ddd6b8dc-ceb9-4919-ba88-0490989a8fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 2d90fcc and a80e374.

⛔ Files ignored due to path filters (4)
  • api/generated/openapi_gen.go is excluded by !**/generated/**
  • view/next-project/src/generated/hooks.ts is excluded by !**/generated/**
  • view/next-project/src/generated/model/buildingTotal.ts is excluded by !**/generated/**
  • view/next-project/src/generated/model/index.ts is excluded by !**/generated/**
📒 Files selected for processing (9)
  • api/externals/handler/campus_donation_handler.go
  • api/externals/handler/handler.go
  • api/externals/repository/campus_donation_repository.go
  • api/externals/repository/wire.go
  • api/internals/di/wire_gen.go
  • api/internals/domain/campus_donation.go
  • api/internals/usecase/campus_donation_usecase.go
  • api/internals/usecase/wire.go
  • openapi/openapi.yaml

Comment thread api/externals/repository/campus_donation_repository.go Outdated
Comment thread api/internals/usecase/campus_donation_usecase.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
api/externals/repository/campus_donation_repository.go (1)

17-18: Keep *sql.Rows inside the repository boundary.

Returning *sql.Rows leaks DB lifecycle handling to the use case and couples the caller to SQL column order. Since this is a new repository API, I'd return []domain.CampusDonationBuildingTotalRow (or a repository DTO) from here and scan inside the repository instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/externals/repository/campus_donation_repository.go` around lines 17 - 18,
The method signature on the CampusDonationRepository interface
(AllBuildingTotalsByYear) returns *sql.Rows which leaks DB lifecycle and couples
callers to SQL scanning; change the interface to return
([]domain.CampusDonationBuildingTotalRow, error) (or a repo DTO slice),
implement the repository method to perform the query, iterate and sql.Scan each
row into a domain.CampusDonationBuildingTotalRow, close rows in a defer, handle
and wrap query/scan errors, and update any callers to consume the returned slice
instead of managing *sql.Rows.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api/externals/repository/campus_donation_repository.go`:
- Around line 17-18: The method signature on the CampusDonationRepository
interface (AllBuildingTotalsByYear) returns *sql.Rows which leaks DB lifecycle
and couples callers to SQL scanning; change the interface to return
([]domain.CampusDonationBuildingTotalRow, error) (or a repo DTO slice),
implement the repository method to perform the query, iterate and sql.Scan each
row into a domain.CampusDonationBuildingTotalRow, close rows in a defer, handle
and wrap query/scan errors, and update any callers to consume the returned slice
instead of managing *sql.Rows.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b54f6899-5070-4cbd-b38d-a5ee5ec62a12

📥 Commits

Reviewing files that changed from the base of the PR and between 6479f0f and ef712e1.

📒 Files selected for processing (1)
  • api/externals/repository/campus_donation_repository.go

@hikahana hikahana changed the title [wip]各棟ごとの合計募金額を取得するAPIを追加 各棟ごとの合計募金額を取得するAPIを追加 Apr 20, 2026
@Chikuwa0141 Chikuwa0141 self-requested a review April 25, 2026 06:53
Copy link
Copy Markdown
Collaborator

@Chikuwa0141 Chikuwa0141 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

テスト項目にあるテストは通りました!

コードにコメントしたやつ以外だと1人の教員に複数の部屋を割り当てられちゃうから重複して集計する可能性があるかもって思った!
room_teachersのteacher_idをユニークに変えるとかかな?

↑「その可能性はないはずなので考慮しないものとする」ってされてたわ


groupedBuildingTotals := make([]generated.BuildingTotal, 0, len(campusDonationBuildingGroups))
for _, group := range campusDonationBuildingGroups {
totalPrice, ok := totalPriceByGroupID[group.ID]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

金額0円の棟も0円としてレスポンス返してほしいかも。
その方がフロントで扱いやすいんじゃないかと思った。

@hikahana
Copy link
Copy Markdown
Collaborator Author

@Chikuwa0141

コードにコメントしたやつ以外だと1人の教員に複数の部屋を割り当てられちゃうから重複して集計する可能性があるかもって思った!
room_teachersのteacher_idをユニークに変えるとかかな?

これしたいけど、たしか、事務棟とかは一つの部屋だけど複数人いるとかだった気がするからこのままでいきます!

@Chikuwa0141 Chikuwa0141 self-requested a review April 30, 2026 05:06
Copy link
Copy Markdown
Collaborator

@Chikuwa0141 Chikuwa0141 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hikahana hikahana merged commit 1758fcb into develop May 1, 2026
4 checks passed
@hikahana hikahana deleted the codex/campus-donations-buildings-api branch May 1, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants