Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Fizzy CLI

A command-line interface for the [Fizzy](https://app.fizzy.do) API.
A command-line interface for the [Fizzy](https://fizzy.do) API. Read API [docs](https://github.com/basecamp/fizzy/blob/main/docs/API.md).

## Installation

Expand Down
5 changes: 5 additions & 0 deletions lib/fizzy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@
require_relative "fizzy/commands/base"
require_relative "fizzy/commands/auth"
require_relative "fizzy/commands/identity"
require_relative "fizzy/commands/board"
require_relative "fizzy/commands/card"
require_relative "fizzy/commands/column"
require_relative "fizzy/commands/user"
require_relative "fizzy/commands/tag"
require_relative "fizzy/cli"
15 changes: 15 additions & 0 deletions lib/fizzy/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,20 @@ def self.exit_on_failure?

desc "identity SUBCOMMAND", "Manage identity"
subcommand "identity", Commands::Identity

desc "board SUBCOMMAND", "Manage boards"
subcommand "board", Commands::Board

desc "card SUBCOMMAND", "Manage cards"
subcommand "card", Commands::Card

desc "column SUBCOMMAND", "Manage columns"
subcommand "column", Commands::Column

desc "user SUBCOMMAND", "Manage users"
subcommand "user", Commands::User

desc "tag SUBCOMMAND", "Manage tags"
subcommand "tag", Commands::Tag
end
end
20 changes: 20 additions & 0 deletions lib/fizzy/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ def account_path(path)
"/#{@account}#{path}"
end

def get_all(path, params = {})
all_data = []
current_params = params.dup

loop do
result = get(path, current_params)
data = result[:data]
all_data.concat(Array(data))

pagination = result[:pagination]
break unless pagination && pagination[:has_next] && pagination[:next_url]

next_uri = URI.parse(pagination[:next_url])
next_params = URI.decode_www_form(next_uri.query || "").to_h
current_params = next_params.transform_keys(&:to_sym)
end

{ data: all_data, pagination: nil }
end

private

def build_uri(path, params = {})
Expand Down
2 changes: 2 additions & 0 deletions lib/fizzy/commands/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def verbose?
def output(result)
response = if result.is_a?(Response)
result
elsif result.nil?
Response.success(data: nil)
else
Response.success(data: result[:data], pagination: result[:pagination])
end
Expand Down
78 changes: 78 additions & 0 deletions lib/fizzy/commands/board.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Fizzy
module Commands
class Board < Base
desc "list", "List all boards"
option :page, type: :numeric, desc: "Page number"
option :all, type: :boolean, default: false, desc: "Fetch all pages"
def list
params = {}
params[:page] = options[:page] if options[:page]

result = if options[:all]
client.get_all(client.account_path("/boards"), params)
else
client.get(client.account_path("/boards"), params)
end
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "show ID", "Show a specific board"
def show(id)
result = client.get(client.account_path("/boards/#{id}"))
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "create", "Create a new board"
option :name, required: true, type: :string, desc: "Board name"
option :all_access, type: :boolean, default: true, desc: "Allow all users to access"
option :auto_postpone_period, type: :numeric, desc: "Auto-postpone period in days"
def create
body = {
board: {
name: options[:name],
all_access: options[:all_access],
auto_postpone_period: options[:auto_postpone_period]
}.compact
}

result = client.post(client.account_path("/boards"), body)
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "update ID", "Update a board"
option :name, type: :string, desc: "Board name"
option :all_access, type: :boolean, desc: "Allow all users to access"
option :user_ids, type: :string, desc: "Comma-separated user IDs for access"
option :auto_postpone_period, type: :numeric, desc: "Auto-postpone period in days"
def update(id)
board_params = {}
board_params[:name] = options[:name] if options.key?(:name)
board_params[:all_access] = options[:all_access] if options.key?(:all_access)
board_params[:auto_postpone_period] = options[:auto_postpone_period] if options.key?(:auto_postpone_period)

if options[:user_ids]
board_params[:user_ids] = options[:user_ids].split(",").map(&:strip)
end

result = client.put(client.account_path("/boards/#{id}"), { board: board_params })
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "delete ID", "Delete a board"
def delete(id)
result = client.delete(client.account_path("/boards/#{id}"))
output(result || Response.success(data: { deleted: true }))
rescue Fizzy::Error => e
output_error(e)
end
end
end
end
105 changes: 105 additions & 0 deletions lib/fizzy/commands/card.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module Fizzy
module Commands
class Card < Base
desc "list", "List cards"
option :board, type: :string, desc: "Filter by board ID"
option :column, type: :string, desc: "Filter by column ID"
option :tag, type: :string, desc: "Filter by tag ID"
option :assignee, type: :string, desc: "Filter by assignee ID"
option :status, type: :string, desc: "Filter by status (published, closed, not_now)"
option :page, type: :numeric, desc: "Page number"
option :all, type: :boolean, default: false, desc: "Fetch all pages"
def list
params = {}
params[:board_id] = options[:board] if options[:board]
params[:column_id] = options[:column] if options[:column]
params[:tag_id] = options[:tag] if options[:tag]
params[:assignee_id] = options[:assignee] if options[:assignee]
params[:status] = options[:status] if options[:status]
params[:page] = options[:page] if options[:page]

result = if options[:all]
client.get_all(client.account_path("/cards"), params)
else
client.get(client.account_path("/cards"), params)
end
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "show NUMBER", "Show a specific card by number"
def show(number)
result = client.get(client.account_path("/cards/#{number}"))
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "create", "Create a new card"
option :board, required: true, type: :string, desc: "Board ID"
option :title, required: true, type: :string, desc: "Card title"
option :description, type: :string, desc: "Card description (rich text/HTML)"
option :description_file, type: :string, desc: "Read description from file"
option :status, type: :string, desc: "Card status"
option :tag_ids, type: :string, desc: "Comma-separated tag IDs"
def create
card_params = {
title: options[:title]
}

if options[:description_file]
card_params[:description] = File.read(options[:description_file])
elsif options[:description]
card_params[:description] = options[:description]
end

card_params[:status] = options[:status] if options[:status]

if options[:tag_ids]
card_params[:tag_ids] = options[:tag_ids].split(",").map(&:strip)
end

result = client.post(client.account_path("/boards/#{options[:board]}/cards"), { card: card_params })
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "update NUMBER", "Update a card"
option :title, type: :string, desc: "Card title"
option :description, type: :string, desc: "Card description (rich text/HTML)"
option :description_file, type: :string, desc: "Read description from file"
option :status, type: :string, desc: "Card status"
option :tag_ids, type: :string, desc: "Comma-separated tag IDs"
def update(number)
card_params = {}
card_params[:title] = options[:title] if options.key?(:title)
card_params[:status] = options[:status] if options.key?(:status)

if options[:description_file]
card_params[:description] = File.read(options[:description_file])
elsif options.key?(:description)
card_params[:description] = options[:description]
end

if options[:tag_ids]
card_params[:tag_ids] = options[:tag_ids].split(",").map(&:strip)
end

result = client.put(client.account_path("/cards/#{number}"), { card: card_params })
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "delete NUMBER", "Delete a card"
def delete(number)
result = client.delete(client.account_path("/cards/#{number}"))
output(result || Response.success(data: { deleted: true }))
rescue Fizzy::Error => e
output_error(e)
end
end
end
end
74 changes: 74 additions & 0 deletions lib/fizzy/commands/column.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Fizzy
module Commands
class Column < Base
desc "list", "List columns for a board"
option :board, required: true, type: :string, desc: "Board ID"
option :page, type: :numeric, desc: "Page number"
option :all, type: :boolean, default: false, desc: "Fetch all pages"
def list
params = {}
params[:page] = options[:page] if options[:page]

result = if options[:all]
client.get_all(client.account_path("/boards/#{options[:board]}/columns"), params)
else
client.get(client.account_path("/boards/#{options[:board]}/columns"), params)
end
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "show ID", "Show a specific column"
option :board, required: true, type: :string, desc: "Board ID"
def show(id)
result = client.get(client.account_path("/boards/#{options[:board]}/columns/#{id}"))
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "create", "Create a new column"
option :board, required: true, type: :string, desc: "Board ID"
option :name, required: true, type: :string, desc: "Column name"
option :color, type: :string, desc: "CSS color variable name"
def create
body = {
column: {
name: options[:name],
color: options[:color]
}.compact
}

result = client.post(client.account_path("/boards/#{options[:board]}/columns"), body)
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "update ID", "Update a column"
option :board, required: true, type: :string, desc: "Board ID"
option :name, type: :string, desc: "Column name"
option :color, type: :string, desc: "CSS color variable name"
def update(id)
column_params = {}
column_params[:name] = options[:name] if options.key?(:name)
column_params[:color] = options[:color] if options.key?(:color)

result = client.put(client.account_path("/boards/#{options[:board]}/columns/#{id}"), { column: column_params })
output(result)
rescue Fizzy::Error => e
output_error(e)
end

desc "delete ID", "Delete a column"
option :board, required: true, type: :string, desc: "Board ID"
def delete(id)
result = client.delete(client.account_path("/boards/#{options[:board]}/columns/#{id}"))
output(result || Response.success(data: { deleted: true }))
rescue Fizzy::Error => e
output_error(e)
end
end
end
end
22 changes: 22 additions & 0 deletions lib/fizzy/commands/tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Fizzy
module Commands
class Tag < Base
desc "list", "List all tags"
option :page, type: :numeric, desc: "Page number"
option :all, type: :boolean, default: false, desc: "Fetch all pages"
def list
params = {}
params[:page] = options[:page] if options[:page]

result = if options[:all]
client.get_all(client.account_path("/tags"), params)
else
client.get(client.account_path("/tags"), params)
end
output(result)
rescue Fizzy::Error => e
output_error(e)
end
end
end
end
Loading
Loading