diff --git a/app/controllers/boards/columns/cards_controller.rb b/app/controllers/boards/columns/cards_controller.rb new file mode 100644 index 000000000..ae6602f14 --- /dev/null +++ b/app/controllers/boards/columns/cards_controller.rb @@ -0,0 +1,19 @@ +class Boards::Columns::CardsController < ApplicationController + include BoardScoped + + before_action :set_column + + def index + set_page_and_extract_portion_from @column.cards.active.latest.with_golden_first.preloaded + fresh_when etag: @page.records + + respond_to do |format| + format.json + end + end + + private + def set_column + @column = @board.columns.find(params[:column_id]) + end +end diff --git a/app/views/boards/columns/cards/index.json.jbuilder b/app/views/boards/columns/cards/index.json.jbuilder new file mode 100644 index 000000000..c1dc1dff1 --- /dev/null +++ b/app/views/boards/columns/cards/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @page.records, partial: "cards/card", as: :card diff --git a/app/views/columns/_column.json.jbuilder b/app/views/columns/_column.json.jbuilder index 5bb56bdd6..16f5604d0 100644 --- a/app/views/columns/_column.json.jbuilder +++ b/app/views/columns/_column.json.jbuilder @@ -1,4 +1,5 @@ json.cache! column do json.(column, :id, :name, :color) json.created_at column.created_at.utc + json.cards_url board_column_cards_url(column.board, column) end diff --git a/config/routes.rb b/config/routes.rb index 4d5acf988..55262e78e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,7 +39,11 @@ Rails.application.routes.draw do resource :closed end - resources :columns + resources :columns do + scope module: :columns do + resources :cards, only: :index + end + end end resources :cards, only: :create diff --git a/docs/api/sections/columns.md b/docs/api/sections/columns.md index 73bd1c728..c4fc35d3b 100644 --- a/docs/api/sections/columns.md +++ b/docs/api/sections/columns.md @@ -14,20 +14,22 @@ __Response:__ "id": "03f5v9zkft4hj9qq0lsn9ohcm", "name": "Recording", "color": "var(--color-card-default)", - "created_at": "2025-12-05T19:36:35.534Z" + "created_at": "2025-12-05T19:36:35.534Z", + "cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcm/cards" }, { "id": "03f5v9zkft4hj9qq0lsn9ohcn", "name": "Published", "color": "var(--color-card-4)", - "created_at": "2025-12-05T19:36:35.534Z" + "created_at": "2025-12-05T19:36:35.534Z", + "cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcn/cards" } ] ``` ## `GET /:account_slug/boards/:board_id/columns/:column_id` -Returns the specified column. +Returns the specified column metadata. __Response:__ @@ -36,10 +38,93 @@ __Response:__ "id": "03f5v9zkft4hj9qq0lsn9ohcm", "name": "In Progress", "color": "var(--color-card-default)", - "created_at": "2025-12-05T19:36:35.534Z" + "created_at": "2025-12-05T19:36:35.534Z", + "cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcm/cards" } ``` +## `GET /:account_slug/boards/:board_id/columns/:column_id/cards` + +Returns a paginated list of open cards in the specified workflow column. +This only includes cards triaged into that column. Cards in "Maybe?", "Not Now", and "Done" +are not included. + +The response items have the same shape as `GET /:account_slug/cards`. + +__Response:__ + +```json +[ + { + "id": "03f5vaeq985jlvwv3arl4srq2", + "number": 1, + "title": "First!", + "status": "published", + "description": "Hello, World!", + "description_html": "

Hello, World!

", + "image_url": null, + "has_attachments": false, + "tags": ["programming"], + "closed": false, + "postponed": false, + "golden": false, + "last_active_at": "2025-12-05T19:38:48.553Z", + "created_at": "2025-12-05T19:38:48.540Z", + "url": "http://fizzy.localhost:3006/897362094/cards/4", + "board": { + "id": "03f5v9zkft4hj9qq0lsn9ohcm", + "name": "Fizzy", + "all_access": true, + "created_at": "2025-12-05T19:36:35.534Z", + "auto_postpone_period_in_days": 30, + "url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm", + "creator": { + "id": "03f5v9zjw7pz8717a4no1h8a7", + "name": "David Heinemeier Hansson", + "role": "owner", + "active": true, + "email_address": "david@example.com", + "created_at": "2025-12-05T19:36:35.401Z", + "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7", + "avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar" + } + }, + "column": { + "id": "03f5v9zkft4hj9qq0lsn9ohcn", + "name": "In Progress", + "color": "var(--color-card-4)", + "created_at": "2025-12-05T19:36:35.534Z", + "cards_url": "http://fizzy.localhost:3006/897362094/boards/03f5v9zkft4hj9qq0lsn9ohcm/columns/03f5v9zkft4hj9qq0lsn9ohcn/cards" + }, + "creator": { + "id": "03f5v9zjw7pz8717a4no1h8a7", + "name": "David Heinemeier Hansson", + "role": "owner", + "active": true, + "email_address": "david@example.com", + "created_at": "2025-12-05T19:36:35.401Z", + "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7", + "avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar" + }, + "assignees": [ + { + "id": "03f5v9zjw7pz8717a4no1h8a7", + "name": "David Heinemeier Hansson", + "role": "owner", + "active": true, + "email_address": "david@example.com", + "created_at": "2025-12-05T19:36:35.401Z", + "url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7", + "avatar_url": "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7/avatar" + } + ], + "has_more_assignees": false, + "comments_url": "http://fizzy.localhost:3006/897362094/cards/4/comments", + "reactions_url": "http://fizzy.localhost:3006/897362094/cards/4/reactions" + } +] +``` + ## `POST /:account_slug/boards/:board_id/columns` Creates a new column on the board. diff --git a/test/controllers/boards/columns/cards_controller_test.rb b/test/controllers/boards/columns/cards_controller_test.rb new file mode 100644 index 000000000..bb65bdfb5 --- /dev/null +++ b/test/controllers/boards/columns/cards_controller_test.rb @@ -0,0 +1,29 @@ +require "test_helper" + +class Boards::Columns::CardsControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in_as :kevin + end + + test "index as JSON" do + column = columns(:writebook_in_progress) + + get board_column_cards_path(column.board, column), as: :json + + assert_response :success + assert_kind_of Array, @response.parsed_body + assert_equal [ cards(:text).number ], @response.parsed_body.pluck("number") + assert_equal "1", response.headers["X-Total-Count"] + end + + test "cannot access cards on board without access as JSON" do + board = boards(:private) + column = board.columns.create!(name: "Secret") + + logout_and_sign_in_as :jason + + get board_column_cards_path(board, column), as: :json + + assert_response :not_found + end +end diff --git a/test/controllers/boards/columns_controller_test.rb b/test/controllers/boards/columns_controller_test.rb index 9fef675f5..664e91ea1 100644 --- a/test/controllers/boards/columns_controller_test.rb +++ b/test/controllers/boards/columns_controller_test.rb @@ -55,6 +55,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest assert_response :success assert_equal board.columns.count, @response.parsed_body.count + assert_equal board_column_cards_url(board, board.columns.sorted.first), @response.parsed_body.first["cards_url"] end test "show as JSON" do @@ -64,6 +65,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest assert_response :success assert_equal column.id, @response.parsed_body["id"] + assert_equal board_column_cards_url(column.board, column), @response.parsed_body["cards_url"] end test "create as JSON" do @@ -76,6 +78,7 @@ class Boards::ColumnsControllerTest < ActionDispatch::IntegrationTest assert_response :created assert_equal board_column_path(board, Column.last, format: :json), @response.headers["Location"] assert_equal "New Column", @response.parsed_body["name"] + assert_equal board_column_cards_url(board, Column.last), @response.parsed_body["cards_url"] end test "update as JSON" do