superthread 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +492 -0
  5. data/exe/suth +19 -0
  6. data/lib/superthread/cli/accounts.rb +240 -0
  7. data/lib/superthread/cli/activity.rb +210 -0
  8. data/lib/superthread/cli/base.rb +355 -0
  9. data/lib/superthread/cli/boards.rb +131 -0
  10. data/lib/superthread/cli/cards.rb +530 -0
  11. data/lib/superthread/cli/checklists.rb +223 -0
  12. data/lib/superthread/cli/comments.rb +86 -0
  13. data/lib/superthread/cli/completion.rb +306 -0
  14. data/lib/superthread/cli/concerns/board_resolvable.rb +70 -0
  15. data/lib/superthread/cli/concerns/confirmable.rb +55 -0
  16. data/lib/superthread/cli/concerns/date_parsable.rb +196 -0
  17. data/lib/superthread/cli/concerns/list_resolvable.rb +53 -0
  18. data/lib/superthread/cli/concerns/space_resolvable.rb +52 -0
  19. data/lib/superthread/cli/concerns/sprint_resolvable.rb +55 -0
  20. data/lib/superthread/cli/concerns/tag_resolvable.rb +49 -0
  21. data/lib/superthread/cli/concerns/user_resolvable.rb +52 -0
  22. data/lib/superthread/cli/concerns/workspace_resolvable.rb +83 -0
  23. data/lib/superthread/cli/config.rb +129 -0
  24. data/lib/superthread/cli/formatter.rb +388 -0
  25. data/lib/superthread/cli/lists.rb +85 -0
  26. data/lib/superthread/cli/main.rb +121 -0
  27. data/lib/superthread/cli/members.rb +19 -0
  28. data/lib/superthread/cli/notes.rb +64 -0
  29. data/lib/superthread/cli/pages.rb +128 -0
  30. data/lib/superthread/cli/projects.rb +124 -0
  31. data/lib/superthread/cli/replies.rb +94 -0
  32. data/lib/superthread/cli/search.rb +34 -0
  33. data/lib/superthread/cli/setup.rb +253 -0
  34. data/lib/superthread/cli/spaces.rb +141 -0
  35. data/lib/superthread/cli/sprints.rb +32 -0
  36. data/lib/superthread/cli/tags.rb +86 -0
  37. data/lib/superthread/cli/ui/gum_prompt.rb +58 -0
  38. data/lib/superthread/cli/ui/plain_prompt.rb +73 -0
  39. data/lib/superthread/cli/ui.rb +263 -0
  40. data/lib/superthread/cli/workspaces.rb +105 -0
  41. data/lib/superthread/cli.rb +12 -0
  42. data/lib/superthread/client.rb +207 -0
  43. data/lib/superthread/configuration.rb +354 -0
  44. data/lib/superthread/connection.rb +57 -0
  45. data/lib/superthread/error.rb +164 -0
  46. data/lib/superthread/mention_formatter.rb +96 -0
  47. data/lib/superthread/model.rb +178 -0
  48. data/lib/superthread/models/board.rb +59 -0
  49. data/lib/superthread/models/card.rb +321 -0
  50. data/lib/superthread/models/checklist.rb +91 -0
  51. data/lib/superthread/models/checklist_item.rb +69 -0
  52. data/lib/superthread/models/comment.rb +71 -0
  53. data/lib/superthread/models/concerns/archivable.rb +32 -0
  54. data/lib/superthread/models/concerns/presentable.rb +113 -0
  55. data/lib/superthread/models/concerns/timestampable.rb +91 -0
  56. data/lib/superthread/models/list.rb +67 -0
  57. data/lib/superthread/models/member.rb +40 -0
  58. data/lib/superthread/models/note.rb +56 -0
  59. data/lib/superthread/models/page.rb +70 -0
  60. data/lib/superthread/models/project.rb +83 -0
  61. data/lib/superthread/models/space.rb +71 -0
  62. data/lib/superthread/models/sprint.rb +53 -0
  63. data/lib/superthread/models/tag.rb +52 -0
  64. data/lib/superthread/models/team.rb +68 -0
  65. data/lib/superthread/models/user.rb +76 -0
  66. data/lib/superthread/models.rb +12 -0
  67. data/lib/superthread/object.rb +285 -0
  68. data/lib/superthread/objects/collection.rb +179 -0
  69. data/lib/superthread/resources/base.rb +204 -0
  70. data/lib/superthread/resources/boards.rb +150 -0
  71. data/lib/superthread/resources/cards.rb +363 -0
  72. data/lib/superthread/resources/comments.rb +163 -0
  73. data/lib/superthread/resources/notes.rb +61 -0
  74. data/lib/superthread/resources/pages.rb +110 -0
  75. data/lib/superthread/resources/projects.rb +117 -0
  76. data/lib/superthread/resources/search.rb +46 -0
  77. data/lib/superthread/resources/spaces.rb +104 -0
  78. data/lib/superthread/resources/sprints.rb +37 -0
  79. data/lib/superthread/resources/tags.rb +52 -0
  80. data/lib/superthread/resources/users.rb +29 -0
  81. data/lib/superthread/version.rb +6 -0
  82. data/lib/superthread/version_checker.rb +174 -0
  83. data/lib/superthread.rb +30 -0
  84. metadata +259 -0
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ # Container classes for API response wrappers.
5
+ module Objects
6
+ # Represents a collection of objects returned from list endpoints.
7
+ # Wraps arrays with additional metadata and convenience methods.
8
+ #
9
+ # @example
10
+ # cards = client.cards.assigned(workspace_id, user_id: user_id)
11
+ # cards.each { |card| puts card.title }
12
+ # cards.count # => 5
13
+ # cards.empty? # => false
14
+ # cards.to_a # => [#<Superthread::Models::Card ...>, ...]
15
+ class Collection
16
+ include Enumerable
17
+
18
+ # @return [Array<Superthread::Object, Superthread::Model>] the items in the collection
19
+ attr_reader :items
20
+
21
+ # @return [Hash{Symbol => Object}] raw response data
22
+ attr_reader :data
23
+
24
+ # Creates a new collection from API response data.
25
+ #
26
+ # @param data [Hash{Symbol => Object}] the raw API response
27
+ # @param key [Symbol, String, nil] the key containing the items array (auto-detected if nil)
28
+ # @param item_class [Class, nil] the class to use for items (auto-detected if nil)
29
+ def initialize(data, key: nil, item_class: nil)
30
+ @data = data.is_a?(Hash) ? data.transform_keys(&:to_sym) : {}
31
+ @item_class = item_class
32
+
33
+ # Extract items from the response
34
+ items_data = extract_items(key)
35
+ @items = items_data.map { |item| wrap_item(item) }
36
+ end
37
+
38
+ # Constructs a collection from an API response.
39
+ #
40
+ # @param data [Hash{Symbol => Object}, Array<Hash>] the API response
41
+ # @param key [Symbol, String, nil] the key containing items (auto-detected if nil)
42
+ # @param item_class [Class, nil] the class for items (auto-detected if nil)
43
+ # @return [Superthread::Objects::Collection] the constructed collection
44
+ def self.from_response(data, key: nil, item_class: nil)
45
+ # If the response is already an array, wrap it
46
+ if data.is_a?(Array)
47
+ new({items: data}, key: :items, item_class: item_class)
48
+ else
49
+ new(data, key: key, item_class: item_class)
50
+ end
51
+ end
52
+
53
+ # Iterates over items in the collection.
54
+ #
55
+ # @param block [Proc] receives each item in the collection
56
+ # @yieldparam item [Superthread::Object, Superthread::Model] an item
57
+ # @return [Enumerator] if no block given
58
+ def each(&block)
59
+ @items.each(&block)
60
+ end
61
+
62
+ # Returns the number of items in the collection.
63
+ #
64
+ # @return [Integer] item count
65
+ def count
66
+ @items.count
67
+ end
68
+ alias_method :size, :count
69
+ alias_method :length, :count
70
+
71
+ # Checks if the collection is empty.
72
+ #
73
+ # @return [Boolean] true if no items
74
+ def empty?
75
+ @items.empty?
76
+ end
77
+
78
+ # Returns the first item.
79
+ #
80
+ # @return [Superthread::Object, Superthread::Model, nil] first item or nil if empty
81
+ def first
82
+ @items.first
83
+ end
84
+
85
+ # Returns the last item.
86
+ #
87
+ # @return [Superthread::Object, Superthread::Model, nil] last item or nil if empty
88
+ def last
89
+ @items.last
90
+ end
91
+
92
+ # Accesses an item by index.
93
+ #
94
+ # @param index [Integer] the zero-based index
95
+ # @return [Superthread::Object, Superthread::Model, nil] item at index or nil if out of bounds
96
+ def [](index)
97
+ @items[index]
98
+ end
99
+
100
+ # Converts the collection to an array.
101
+ #
102
+ # @return [Array<Superthread::Object, Superthread::Model>] copy of items array
103
+ def to_a
104
+ @items.dup
105
+ end
106
+ alias_method :to_ary, :to_a
107
+
108
+ # Converts the collection to an array of hashes.
109
+ #
110
+ # @return [Array<Hash{Symbol => Object}>] array of hash representations
111
+ def to_h
112
+ @items.map(&:to_h)
113
+ end
114
+
115
+ # Returns raw response metadata (everything except items).
116
+ #
117
+ # @return [Hash{Symbol => Object}] metadata from the API response
118
+ def metadata
119
+ @data.except(*items_keys)
120
+ end
121
+
122
+ private
123
+
124
+ # Common keys that contain item arrays in API responses.
125
+ ITEMS_KEYS = %i[items cards boards lists users projects spaces sprints
126
+ pages notes comments tags members results data].freeze
127
+
128
+ # Returns the list of common item keys.
129
+ #
130
+ # @return [Array<Symbol>] keys that typically contain item arrays
131
+ def items_keys
132
+ ITEMS_KEYS
133
+ end
134
+
135
+ # Extracts items array from the response data.
136
+ #
137
+ # @param key [Symbol, String, nil] the key to extract, or nil to auto-detect
138
+ # @return [Array<Hash>] the extracted items array
139
+ def extract_items(key)
140
+ if key
141
+ @data[key.to_sym] || []
142
+ else
143
+ # Auto-detect items key
144
+ ITEMS_KEYS.each do |k|
145
+ return @data[k] if @data[k].is_a?(Array)
146
+ end
147
+ # If nothing found, check if the response itself has a single array value
148
+ @data.values.find { |v| v.is_a?(Array) } || []
149
+ end
150
+ end
151
+
152
+ # Wraps a hash item in the appropriate object class.
153
+ #
154
+ # @param item [Hash, Object] the item to wrap
155
+ # @return [Superthread::Object, Superthread::Model] the wrapped item
156
+ def wrap_item(item)
157
+ return item unless item.is_a?(Hash)
158
+
159
+ if @item_class
160
+ if shale_model?(@item_class)
161
+ @item_class.from_response(item)
162
+ else
163
+ @item_class.new(item)
164
+ end
165
+ else
166
+ Superthread::Object.construct_from(item)
167
+ end
168
+ end
169
+
170
+ # Checks if a class is a Shale model.
171
+ #
172
+ # @param klass [Class] the class to check
173
+ # @return [Boolean] true if the class is a Shale model
174
+ def shale_model?(klass)
175
+ Superthread::Model.shale_class?(klass)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ # API resource classes for interacting with Superthread endpoints.
5
+ module Resources
6
+ # Base class for all API resources.
7
+ #
8
+ # Provides HTTP helpers, ID validation, and response handling shared
9
+ # by all resource classes. Subclasses should use the protected HTTP
10
+ # methods to interact with the Superthread API.
11
+ class Base
12
+ # Initializes a new resource instance.
13
+ #
14
+ # @param client [Superthread::Client] the API client for making requests
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+
19
+ private
20
+
21
+ # Performs a GET request and returns raw response data.
22
+ #
23
+ # @param path [String] the API endpoint path
24
+ # @param params [Hash{Symbol => Object}, nil] optional query parameters
25
+ # @return [Hash{Symbol => Object}] the parsed response body
26
+ def http_get(path, params: nil)
27
+ @client.request(method: :get, path: path, params: params)
28
+ end
29
+
30
+ # Performs a POST request and returns raw response data.
31
+ #
32
+ # @param path [String] the API endpoint path
33
+ # @param body [Hash{Symbol => Object}, nil] optional request body
34
+ # @return [Hash{Symbol => Object}] the parsed response body
35
+ def http_post(path, body: nil)
36
+ @client.request(method: :post, path: path, body: body)
37
+ end
38
+
39
+ # Performs a PATCH request and returns raw response data.
40
+ #
41
+ # @param path [String] the API endpoint path
42
+ # @param body [Hash{Symbol => Object}, nil] optional request body
43
+ # @return [Hash{Symbol => Object}] the parsed response body
44
+ def http_patch(path, body: nil)
45
+ @client.request(method: :patch, path: path, body: body)
46
+ end
47
+
48
+ # Performs a DELETE request and returns raw response data.
49
+ #
50
+ # @param path [String] the API endpoint path
51
+ # @return [Hash{Symbol => Object}] the parsed response body
52
+ def http_delete(path)
53
+ @client.request(method: :delete, path: path)
54
+ end
55
+
56
+ # Performs a GET request and returns a typed object.
57
+ #
58
+ # @param path [String] the API endpoint path
59
+ # @param params [Hash{Symbol => Object}, nil] optional query parameters
60
+ # @param object_class [Class, nil] the model class to instantiate
61
+ # @param unwrap_key [Symbol, nil] key to extract from response before wrapping
62
+ # @return [Superthread::Object] the response wrapped in an object
63
+ def get_object(path, params: nil, object_class: nil, unwrap_key: nil)
64
+ @client.request_object(
65
+ method: :get, path: path, params: params,
66
+ object_class: object_class, unwrap_key: unwrap_key
67
+ )
68
+ end
69
+
70
+ # Performs a POST request and returns a typed object.
71
+ #
72
+ # @param path [String] the API endpoint path
73
+ # @param body [Hash{Symbol => Object}, nil] optional request body
74
+ # @param object_class [Class, nil] the model class to instantiate
75
+ # @param unwrap_key [Symbol, nil] key to extract from response before wrapping
76
+ # @return [Superthread::Object] the response wrapped in an object
77
+ def post_object(path, body: nil, object_class: nil, unwrap_key: nil)
78
+ @client.request_object(
79
+ method: :post, path: path, body: body,
80
+ object_class: object_class, unwrap_key: unwrap_key
81
+ )
82
+ end
83
+
84
+ # Performs a PATCH request and returns a typed object.
85
+ #
86
+ # @param path [String] the API endpoint path
87
+ # @param body [Hash{Symbol => Object}, nil] optional request body
88
+ # @param object_class [Class, nil] the model class to instantiate
89
+ # @param unwrap_key [Symbol, nil] key to extract from response before wrapping
90
+ # @return [Superthread::Object] the response wrapped in an object
91
+ def patch_object(path, body: nil, object_class: nil, unwrap_key: nil)
92
+ @client.request_object(
93
+ method: :patch, path: path, body: body,
94
+ object_class: object_class, unwrap_key: unwrap_key
95
+ )
96
+ end
97
+
98
+ # Performs a DELETE request and returns a typed object.
99
+ #
100
+ # @param path [String] the API endpoint path
101
+ # @param object_class [Class, nil] the model class to instantiate
102
+ # @param unwrap_key [Symbol, nil] key to extract from response before wrapping
103
+ # @return [Superthread::Object] the response wrapped in an object
104
+ def delete_object(path, object_class: nil, unwrap_key: nil)
105
+ @client.request_object(
106
+ method: :delete, path: path,
107
+ object_class: object_class, unwrap_key: unwrap_key
108
+ )
109
+ end
110
+
111
+ # Performs a GET request and returns a collection of objects.
112
+ #
113
+ # @param path [String] the API endpoint path
114
+ # @param params [Hash{Symbol => Object}, nil] optional query parameters
115
+ # @param item_class [Class, nil] the model class for collection items
116
+ # @param items_key [Symbol, nil] key containing the array in response
117
+ # @return [Superthread::Objects::Collection] the collection of items
118
+ def get_collection(path, params: nil, item_class: nil, items_key: nil)
119
+ @client.request_collection(
120
+ method: :get, path: path, params: params,
121
+ item_class: item_class, items_key: items_key
122
+ )
123
+ end
124
+
125
+ # Performs a POST request and returns a collection of objects.
126
+ #
127
+ # @param path [String] the API endpoint path
128
+ # @param body [Hash{Symbol => Object}, nil] optional request body
129
+ # @param item_class [Class, nil] the model class for collection items
130
+ # @param items_key [Symbol, nil] key containing the array in response
131
+ # @return [Superthread::Objects::Collection] the collection of items
132
+ def post_collection(path, body: nil, item_class: nil, items_key: nil)
133
+ @client.request_collection(
134
+ method: :post, path: path, body: body,
135
+ item_class: item_class, items_key: items_key
136
+ )
137
+ end
138
+
139
+ # Validates and sanitizes an ID to prevent path traversal attacks.
140
+ #
141
+ # Only allows alphanumeric characters, hyphens, and underscores.
142
+ #
143
+ # @param name [String] descriptive name for error messages (e.g., "workspace_id")
144
+ # @param value [String] the ID value to validate
145
+ # @return [String] the sanitized ID
146
+ # @raise [Superthread::PathValidationError] if the value is nil, empty, or contains invalid characters
147
+ # @example
148
+ # safe_id("workspace_id", "ws-123_abc") # => "ws-123_abc"
149
+ # safe_id("card_id", "../evil") # raises PathValidationError
150
+ def safe_id(name, value)
151
+ raise Superthread::PathValidationError, "#{name} must be a non-empty string" if value.nil? || value.to_s.empty?
152
+
153
+ cleaned = value.to_s.strip.gsub(/[^a-zA-Z0-9_-]/, "")
154
+
155
+ if cleaned.empty?
156
+ raise Superthread::PathValidationError,
157
+ "#{name} must contain only letters, numbers, hyphen, or underscore"
158
+ end
159
+
160
+ cleaned
161
+ end
162
+
163
+ # Filters nil values from a params hash.
164
+ #
165
+ # Accepts arbitrary keyword arguments and returns only non-nil values.
166
+ # Used internally to clean up API request parameters.
167
+ #
168
+ # @param args [Hash{Symbol => Object}] arbitrary key-value pairs to filter
169
+ # @option args [Object] :any any key-value pair (nil values will be removed)
170
+ # @return [Hash{Symbol => Object}] hash with nil values removed
171
+ def compact_params(**args)
172
+ args.compact
173
+ end
174
+
175
+ # Formats {{@mentions}} in content to HTML user-mention tags.
176
+ #
177
+ # @param workspace_id [String] the workspace identifier for member lookup
178
+ # @param content [String, nil] text that may contain {{@Name}} patterns
179
+ # @return [String, nil] content with mentions converted to HTML tags
180
+ def format_mentions(workspace_id, content)
181
+ return content if content.nil?
182
+
183
+ MentionFormatter.new(@client, workspace_id).format(content)
184
+ end
185
+
186
+ # Builds an API path prefixed with the workspace ID.
187
+ #
188
+ # @param workspace_id [String] the workspace identifier
189
+ # @param path [String] additional path segments to append
190
+ # @return [String] the full API path (e.g., "/ws-123/cards")
191
+ def workspace_path(workspace_id, path = "")
192
+ ws = safe_id("workspace_id", workspace_id)
193
+ "/#{ws}#{path}"
194
+ end
195
+
196
+ # Returns a success response object for delete operations.
197
+ #
198
+ # @return [Superthread::Object] a response object with success: true
199
+ def success_response
200
+ Superthread::Object.new(success: true)
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Superthread
4
+ module Resources
5
+ # API resource for board operations.
6
+ #
7
+ # Provides methods for creating, listing, updating, and deleting boards
8
+ # and their lists (columns) via the Superthread API.
9
+ class Boards < Base
10
+ # Creates a new board in a space.
11
+ #
12
+ # @param workspace_id [String] the workspace identifier
13
+ # @param space_id [String] the space identifier (maps to project_id in API)
14
+ # @param title [String] the board title
15
+ # @param params [Hash{Symbol => Object}] optional board parameters
16
+ # @option params [String] :content the board description content
17
+ # @option params [String] :icon the board icon identifier
18
+ # @option params [String] :color the board color hex code
19
+ # @option params [String] :layout the board layout type
20
+ # @return [Superthread::Models::Board] the created board
21
+ def create(workspace_id, space_id:, title:, **params)
22
+ ws = safe_id("workspace_id", workspace_id)
23
+ body = compact_params(title: title, project_id: space_id, **params)
24
+ post_object("/#{ws}/boards", body: body,
25
+ object_class: Models::Board, unwrap_key: :board)
26
+ end
27
+
28
+ # Lists all boards in a space.
29
+ #
30
+ # @param workspace_id [String] the workspace identifier
31
+ # @param space_id [String] the space identifier to filter by
32
+ # @param bookmarked [Boolean, nil] when true, returns only bookmarked boards
33
+ # @param archived [Boolean, nil] when true, includes archived boards
34
+ # @return [Superthread::Objects::Collection<Superthread::Models::Board>] the boards in the space
35
+ def list(workspace_id, space_id:, bookmarked: nil, archived: nil)
36
+ ws = safe_id("workspace_id", workspace_id)
37
+ params = compact_params(project_id: space_id, bookmarked: bookmarked, archived: archived)
38
+ get_collection("/#{ws}/boards", params: params,
39
+ item_class: Models::Board, items_key: :boards)
40
+ end
41
+
42
+ # Gets a specific board with its lists and cards.
43
+ #
44
+ # @param workspace_id [String] the workspace identifier
45
+ # @param board_id [String] the board identifier
46
+ # @return [Superthread::Models::Board] the board with nested lists and cards
47
+ def find(workspace_id, board_id)
48
+ ws = safe_id("workspace_id", workspace_id)
49
+ board = safe_id("board_id", board_id)
50
+ get_object("/#{ws}/boards/#{board}",
51
+ object_class: Models::Board, unwrap_key: :board)
52
+ end
53
+
54
+ # Updates a board's attributes.
55
+ #
56
+ # @param workspace_id [String] the workspace identifier
57
+ # @param board_id [String] the board identifier
58
+ # @param params [Hash{Symbol => Object}] the attributes to update
59
+ # @option params [String] :title the new board title
60
+ # @option params [String] :content the new board description
61
+ # @option params [String] :icon the new board icon identifier
62
+ # @option params [String] :color the new board color hex code
63
+ # @option params [Boolean] :archived whether the board is archived
64
+ # @return [Superthread::Models::Board] the updated board
65
+ def update(workspace_id, board_id, **params)
66
+ ws = safe_id("workspace_id", workspace_id)
67
+ board = safe_id("board_id", board_id)
68
+ patch_object("/#{ws}/boards/#{board}", body: compact_params(**params),
69
+ object_class: Models::Board, unwrap_key: :board)
70
+ end
71
+
72
+ # Duplicates a board to a destination space.
73
+ #
74
+ # @param workspace_id [String] the workspace identifier
75
+ # @param board_id [String] the board identifier to duplicate
76
+ # @param space_id [String] the destination space identifier
77
+ # @param title [String, nil] the title for the duplicated board (defaults to original)
78
+ # @param copy_cards [Boolean, nil] when true, copies all cards to the new board
79
+ # @param create_missing_tags [Boolean, nil] when true, creates tags that do not exist in target space
80
+ # @return [Superthread::Models::Board] the duplicated board
81
+ def duplicate(workspace_id, board_id, space_id:, title: nil, copy_cards: nil, create_missing_tags: nil)
82
+ ws = safe_id("workspace_id", workspace_id)
83
+ board = safe_id("board_id", board_id)
84
+ body = compact_params(project_id: space_id, title: title, copy_cards: copy_cards, create_missing_tags: create_missing_tags)
85
+ post_object("/#{ws}/boards/#{board}/copy", body: body,
86
+ object_class: Models::Board, unwrap_key: :board)
87
+ end
88
+
89
+ # Deletes a board permanently.
90
+ #
91
+ # @param workspace_id [String] the workspace identifier
92
+ # @param board_id [String] the board identifier to delete
93
+ # @return [Superthread::Object] a response object with success: true
94
+ def destroy(workspace_id, board_id)
95
+ ws = safe_id("workspace_id", workspace_id)
96
+ board = safe_id("board_id", board_id)
97
+ http_delete("/#{ws}/boards/#{board}")
98
+ success_response
99
+ end
100
+
101
+ # Creates a list (column) on a board.
102
+ #
103
+ # @param workspace_id [String] the workspace identifier
104
+ # @param board_id [String] the board identifier to add the list to
105
+ # @param title [String] the list title
106
+ # @param params [Hash{Symbol => Object}] optional list parameters
107
+ # @option params [String] :content the list description content
108
+ # @option params [String] :icon the list icon identifier
109
+ # @option params [String] :color the list color hex code
110
+ # @option params [String] :behavior the list behavior type (e.g., "done")
111
+ # @return [Superthread::Models::List] the created list
112
+ def create_list(workspace_id, board_id:, title:, **params)
113
+ ws = safe_id("workspace_id", workspace_id)
114
+ body = compact_params(board_id: board_id, title: title, **params)
115
+ post_object("/#{ws}/lists", body: body,
116
+ object_class: Models::List, unwrap_key: :list)
117
+ end
118
+
119
+ # Updates a list's attributes.
120
+ #
121
+ # @param workspace_id [String] the workspace identifier
122
+ # @param list_id [String] the list identifier
123
+ # @param params [Hash{Symbol => Object}] the attributes to update
124
+ # @option params [String] :title the new list title
125
+ # @option params [String] :content the new list description
126
+ # @option params [String] :icon the new list icon identifier
127
+ # @option params [String] :color the new list color hex code
128
+ # @option params [String] :behavior the new list behavior type
129
+ # @return [Superthread::Models::List] the updated list
130
+ def update_list(workspace_id, list_id, **params)
131
+ ws = safe_id("workspace_id", workspace_id)
132
+ list = safe_id("list_id", list_id)
133
+ patch_object("/#{ws}/lists/#{list}", body: compact_params(**params),
134
+ object_class: Models::List, unwrap_key: :list)
135
+ end
136
+
137
+ # Deletes a list permanently.
138
+ #
139
+ # @param workspace_id [String] the workspace identifier
140
+ # @param list_id [String] the list identifier to delete
141
+ # @return [Superthread::Object] a response object with success: true
142
+ def delete_list(workspace_id, list_id)
143
+ ws = safe_id("workspace_id", workspace_id)
144
+ list = safe_id("list_id", list_id)
145
+ http_delete("/#{ws}/lists/#{list}")
146
+ success_response
147
+ end
148
+ end
149
+ end
150
+ end