API: Support created_at for API card and comment creation (#2056)

* Add support to set `created_at`

Discussed in
https://github.com/basecamp/fizzy/pull/1766#issuecomment-3637846074,
this allows users to import cards from another system with entries from
the past. For instance I'm importing all our Github issues (including
the closed onces) to Fizzy.

* Iron out published state tracking

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
This commit is contained in:
Jankees van Woezik
2025-12-11 04:57:38 +01:00
committed by GitHub
parent 90452a4236
commit 477b4d65f2
6 changed files with 37 additions and 23 deletions
+1 -1
View File
@@ -51,6 +51,6 @@ class Cards::CommentsController < ApplicationController
end
def comment_params
params.expect(comment: :body)
params.expect(comment: [ :body, :created_at ])
end
end
+2 -3
View File
@@ -17,8 +17,7 @@ class CardsController < ApplicationController
end
format.json do
card = @board.cards.create! card_params.merge(creator: Current.user)
card.publish
card = @board.cards.create! card_params.merge(creator: Current.user, status: "published")
head :created, location: card_path(card, format: :json)
end
end
@@ -62,6 +61,6 @@ class CardsController < ApplicationController
end
def card_params
params.expect(card: [ :status, :title, :description, :image, tag_ids: [] ])
params.expect(card: [ :status, :title, :description, :image, :created_at, tag_ids: [] ])
end
end
+7 -19
View File
@@ -4,37 +4,25 @@ module Card::Statuses
included do
enum :status, %w[ drafted published ].index_by(&:itself)
attr_reader :initial_status
before_save :update_created_at_on_publication
before_save :remember_initial_status
before_save :mark_if_just_published
after_create -> { track_event :published }, if: :published?
scope :published_or_drafted_by, ->(user) { where(status: :published).or(where(status: :drafted, creator: user)) }
end
attr_accessor :was_just_published
alias_method :was_just_published?, :was_just_published
def publish
transaction do
self.created_at = Time.current
published!
track_event :published
end
end
def was_just_published?
initial_status&.drafted? && status_in_database.inquiry.published?
end
private
def update_created_at_on_publication
if will_save_change_to_status? && status_in_database.inquiry.drafted?
self.created_at = Time.current
end
end
# So that we can check it in callbacks when other operations in the transaction clean the changes.
def remember_initial_status
if will_save_change_to_status?
@initial_status ||= status_in_database.to_s.inquiry
end
def mark_if_just_published
self.was_just_published = true if published? && status_changed?
end
end
+2
View File
@@ -494,6 +494,7 @@ Creates a new card in a board.
| `status` | string | No | Initial status: `published` (default), `drafted` |
| `image` | file | No | Header image for the card |
| `tag_ids` | array | No | Array of tag IDs to apply to the card |
| `created_at` | datetime | No | Override creation timestamp (ISO 8601 format) |
__Request:__
@@ -699,6 +700,7 @@ Creates a new comment on a card.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `body` | string | Yes | The comment body (supports rich text) |
| `created_at` | datetime | No | Override creation timestamp (ISO 8601 format) |
__Request:__
@@ -48,6 +48,18 @@ class Cards::CommentsControllerTest < ActionDispatch::IntegrationTest
assert_equal card_comment_path(card, Comment.last, format: :json), @response.headers["Location"]
end
test "create as JSON with custom created_at" do
card = cards(:logo)
custom_time = Time.utc(2024, 1, 15, 10, 30, 0)
assert_difference -> { card.comments.count }, +1 do
post card_comments_path(card), params: { comment: { body: "Backdated comment", created_at: custom_time } }, as: :json
end
assert_response :created
assert_equal custom_time, Comment.last.created_at
end
test "show as JSON" do
comment = comments(:logo_agreement_kevin)
+13
View File
@@ -160,6 +160,19 @@ class CardsControllerTest < ActionDispatch::IntegrationTest
assert_equal [ tags(:mobile), tags(:web) ].sort, card.tags.sort
end
test "create as JSON with custom created_at" do
custom_time = Time.utc(2024, 1, 15, 10, 30, 0)
assert_difference -> { Card.count }, +1 do
post board_cards_path(boards(:writebook)),
params: { card: { title: "Backdated card", created_at: custom_time } },
as: :json
end
assert_response :created
assert_equal custom_time, Card.last.created_at
end
test "update as JSON" do
card = cards(:logo)
put card_path(card, format: :json), params: { card: { title: "Update test" } }