From b931e2031cc8ed3aac099035bc52cbd53a17e887 Mon Sep 17 00:00:00 2001 From: activitysmith-bot Date: Sat, 7 Nov 2026 08:33:11 +0000 Subject: [PATCH 1/2] chore: regenerate SDK --- generated/activitysmith_openapi.rb | 5 + .../activitysmith_openapi/api/devices_api.rb | 100 +++++ .../api/live_activities_api.rb | 4 +- .../api/push_notifications_api.rb | 67 ++++ .../models/account_device.rb | 367 ++++++++++++++++++ .../models/account_device_list_response.rb | 239 ++++++++++++ .../account_device_list_response_meta.rb | 299 ++++++++++++++ .../push_notification_dismiss_response.rb | 257 ++++++++++++ generated/openapi-source.json | 4 +- 9 files changed, 1338 insertions(+), 4 deletions(-) create mode 100644 generated/activitysmith_openapi/api/devices_api.rb create mode 100644 generated/activitysmith_openapi/models/account_device.rb create mode 100644 generated/activitysmith_openapi/models/account_device_list_response.rb create mode 100644 generated/activitysmith_openapi/models/account_device_list_response_meta.rb create mode 100644 generated/activitysmith_openapi/models/push_notification_dismiss_response.rb diff --git a/generated/activitysmith_openapi.rb b/generated/activitysmith_openapi.rb index 2264a0c..fbded1c 100644 --- a/generated/activitysmith_openapi.rb +++ b/generated/activitysmith_openapi.rb @@ -17,6 +17,9 @@ require 'activitysmith_openapi/configuration' # Models +require 'activitysmith_openapi/models/account_device' +require 'activitysmith_openapi/models/account_device_list_response' +require 'activitysmith_openapi/models/account_device_list_response_meta' require 'activitysmith_openapi/models/activity_metric' require 'activitysmith_openapi/models/activity_metric_value' require 'activitysmith_openapi/models/alert_payload' @@ -62,6 +65,7 @@ require 'activitysmith_openapi/models/push_interruption_level' require 'activitysmith_openapi/models/push_notification_action' require 'activitysmith_openapi/models/push_notification_action_type' +require 'activitysmith_openapi/models/push_notification_dismiss_response' require 'activitysmith_openapi/models/push_notification_replace_request' require 'activitysmith_openapi/models/push_notification_replace_response' require 'activitysmith_openapi/models/push_notification_request' @@ -74,6 +78,7 @@ # APIs require 'activitysmith_openapi/api/app_icon_badges_api' +require 'activitysmith_openapi/api/devices_api' require 'activitysmith_openapi/api/live_activities_api' require 'activitysmith_openapi/api/metrics_api' require 'activitysmith_openapi/api/push_notifications_api' diff --git a/generated/activitysmith_openapi/api/devices_api.rb b/generated/activitysmith_openapi/api/devices_api.rb new file mode 100644 index 0000000..85736a3 --- /dev/null +++ b/generated/activitysmith_openapi/api/devices_api.rb @@ -0,0 +1,100 @@ +=begin +#ActivitySmith API + +#Send push notifications and Live Activities to your own devices via a single API key. + +The version of the OpenAPI document: 1.0.0 + +Generated by: https://openapi-generator.tech +Generator version: 7.7.0 + +=end + +require 'cgi' + +module OpenapiClient + class DevicesApi + attr_accessor :api_client + + def initialize(api_client = ApiClient.default) + @api_client = api_client + end + # List paired devices + # Lists the iPhones and iPads paired to the account, active devices first. Use it to check that a device is paired before you send, or to find device IDs for Channels. An empty list means no device is paired yet: install the ActivitySmith app from the App Store and sign in. Channel-scoped API keys cannot list devices. + # @param [Hash] opts the optional parameters + # @option opts [Integer] :limit Maximum devices to return, 1 to 100. Defaults to 100. (default to 100) + # @option opts [Integer] :offset Devices to skip. Use meta.next_offset from the previous page. (default to 0) + # @option opts [Boolean] :include_inactive Include devices that can no longer receive deliveries, such as unpaired phones. (default to false) + # @return [AccountDeviceListResponse] + def list_account_devices(opts = {}) + data, _status_code, _headers = list_account_devices_with_http_info(opts) + data + end + + # List paired devices + # Lists the iPhones and iPads paired to the account, active devices first. Use it to check that a device is paired before you send, or to find device IDs for Channels. An empty list means no device is paired yet: install the ActivitySmith app from the App Store and sign in. Channel-scoped API keys cannot list devices. + # @param [Hash] opts the optional parameters + # @option opts [Integer] :limit Maximum devices to return, 1 to 100. Defaults to 100. (default to 100) + # @option opts [Integer] :offset Devices to skip. Use meta.next_offset from the previous page. (default to 0) + # @option opts [Boolean] :include_inactive Include devices that can no longer receive deliveries, such as unpaired phones. (default to false) + # @return [Array<(AccountDeviceListResponse, Integer, Hash)>] AccountDeviceListResponse data, response status code and response headers + def list_account_devices_with_http_info(opts = {}) + if @api_client.config.debugging + @api_client.config.logger.debug 'Calling API: DevicesApi.list_account_devices ...' + end + if @api_client.config.client_side_validation && !opts[:'limit'].nil? && opts[:'limit'] > 100 + fail ArgumentError, 'invalid value for "opts[:"limit"]" when calling DevicesApi.list_account_devices, must be smaller than or equal to 100.' + end + + if @api_client.config.client_side_validation && !opts[:'limit'].nil? && opts[:'limit'] < 1 + fail ArgumentError, 'invalid value for "opts[:"limit"]" when calling DevicesApi.list_account_devices, must be greater than or equal to 1.' + end + + if @api_client.config.client_side_validation && !opts[:'offset'].nil? && opts[:'offset'] < 0 + fail ArgumentError, 'invalid value for "opts[:"offset"]" when calling DevicesApi.list_account_devices, must be greater than or equal to 0.' + end + + # resource path + local_var_path = '/account/devices' + + # query parameters + query_params = opts[:query_params] || {} + query_params[:'limit'] = opts[:'limit'] if !opts[:'limit'].nil? + query_params[:'offset'] = opts[:'offset'] if !opts[:'offset'].nil? + query_params[:'include_inactive'] = opts[:'include_inactive'] if !opts[:'include_inactive'].nil? + + # header parameters + header_params = opts[:header_params] || {} + # HTTP header 'Accept' (if needed) + header_params['Accept'] = @api_client.select_header_accept(['application/json']) + + # form parameters + form_params = opts[:form_params] || {} + + # http body (model) + post_body = opts[:debug_body] + + # return_type + return_type = opts[:debug_return_type] || 'AccountDeviceListResponse' + + # auth_names + auth_names = opts[:debug_auth_names] || ['apiKeyAuth'] + + new_options = opts.merge( + :operation => :"DevicesApi.list_account_devices", + :header_params => header_params, + :query_params => query_params, + :form_params => form_params, + :body => post_body, + :auth_names => auth_names, + :return_type => return_type + ) + + data, status_code, headers = @api_client.call_api(:GET, local_var_path, new_options) + if @api_client.config.debugging + @api_client.config.logger.debug "API called: DevicesApi#list_account_devices\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}" + end + return data, status_code, headers + end + end +end diff --git a/generated/activitysmith_openapi/api/live_activities_api.rb b/generated/activitysmith_openapi/api/live_activities_api.rb index c106b10..19a3288 100644 --- a/generated/activitysmith_openapi/api/live_activities_api.rb +++ b/generated/activitysmith_openapi/api/live_activities_api.rb @@ -20,7 +20,7 @@ def initialize(api_client = ApiClient.default) @api_client = api_client end # End a Live Activity (legacy manual lifecycle) - # Legacy manual lifecycle endpoint. For new integrations, use DELETE /live-activity/stream/{stream_key} to end a managed Live Activity stream. This endpoint remains supported for existing integrations and advanced lifecycle control. Ends a Live Activity and archives its lifecycle. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can send the latest number_of_steps here if the workflow changed after start. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. + # Legacy manual lifecycle endpoint. For new integrations, use DELETE /live-activity/stream/{stream_key} to end a managed Live Activity stream. This endpoint remains supported for existing integrations and advanced lifecycle control. Ends a Live Activity and archives its lifecycle. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can send the latest number_of_steps here if the workflow changed after start. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. Ending a Live Activity that already ended returns 200 with ignored: true and sends nothing to devices, so retries are safe. # @param live_activity_end_request [LiveActivityEndRequest] # @param [Hash] opts the optional parameters # @return [LiveActivityEndResponse] @@ -30,7 +30,7 @@ def end_live_activity(live_activity_end_request, opts = {}) end # End a Live Activity (legacy manual lifecycle) - # Legacy manual lifecycle endpoint. For new integrations, use DELETE /live-activity/stream/{stream_key} to end a managed Live Activity stream. This endpoint remains supported for existing integrations and advanced lifecycle control. Ends a Live Activity and archives its lifecycle. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can send the latest number_of_steps here if the workflow changed after start. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. + # Legacy manual lifecycle endpoint. For new integrations, use DELETE /live-activity/stream/{stream_key} to end a managed Live Activity stream. This endpoint remains supported for existing integrations and advanced lifecycle control. Ends a Live Activity and archives its lifecycle. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can send the latest number_of_steps here if the workflow changed after start. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. Ending a Live Activity that already ended returns 200 with ignored: true and sends nothing to devices, so retries are safe. # @param live_activity_end_request [LiveActivityEndRequest] # @param [Hash] opts the optional parameters # @return [Array<(LiveActivityEndResponse, Integer, Hash)>] LiveActivityEndResponse data, response status code and response headers diff --git a/generated/activitysmith_openapi/api/push_notifications_api.rb b/generated/activitysmith_openapi/api/push_notifications_api.rb index ba419eb..39dd473 100644 --- a/generated/activitysmith_openapi/api/push_notifications_api.rb +++ b/generated/activitysmith_openapi/api/push_notifications_api.rb @@ -19,6 +19,73 @@ class PushNotificationsApi def initialize(api_client = ApiClient.default) @api_client = api_client end + # Dismiss a push notification + # Clears a Push Notification you already sent from the Lock Screen and Notification Center on every device it was sent to, using the `notification_id` from the send response. Notification history keeps it. Devices clear the notification in the background, which can take a moment or wait until the ActivitySmith app next opens. A successful response means the dismissal was recorded, not that every device has cleared it yet. Devices running an ActivitySmith app version before 1.17.0 show a silent notification titled \"Push Notification dismissed\" in its place until the user clears it. Calling this again for the same notification returns the same response and sends nothing. A dismissed notification cannot be replaced. + # @param notification_id [String] The `notification_id` returned when the Push Notification was sent. + # @param [Hash] opts the optional parameters + # @return [PushNotificationDismissResponse] + def dismiss_push_notification(notification_id, opts = {}) + data, _status_code, _headers = dismiss_push_notification_with_http_info(notification_id, opts) + data + end + + # Dismiss a push notification + # Clears a Push Notification you already sent from the Lock Screen and Notification Center on every device it was sent to, using the `notification_id` from the send response. Notification history keeps it. Devices clear the notification in the background, which can take a moment or wait until the ActivitySmith app next opens. A successful response means the dismissal was recorded, not that every device has cleared it yet. Devices running an ActivitySmith app version before 1.17.0 show a silent notification titled \"Push Notification dismissed\" in its place until the user clears it. Calling this again for the same notification returns the same response and sends nothing. A dismissed notification cannot be replaced. + # @param notification_id [String] The `notification_id` returned when the Push Notification was sent. + # @param [Hash] opts the optional parameters + # @return [Array<(PushNotificationDismissResponse, Integer, Hash)>] PushNotificationDismissResponse data, response status code and response headers + def dismiss_push_notification_with_http_info(notification_id, opts = {}) + if @api_client.config.debugging + @api_client.config.logger.debug 'Calling API: PushNotificationsApi.dismiss_push_notification ...' + end + # verify the required parameter 'notification_id' is set + if @api_client.config.client_side_validation && notification_id.nil? + fail ArgumentError, "Missing the required parameter 'notification_id' when calling PushNotificationsApi.dismiss_push_notification" + end + if @api_client.config.client_side_validation && notification_id.to_s.length < 1 + fail ArgumentError, 'invalid value for "notification_id" when calling PushNotificationsApi.dismiss_push_notification, the character length must be great than or equal to 1.' + end + + # resource path + local_var_path = '/push-notification/{notification_id}/dismiss'.sub('{' + 'notification_id' + '}', CGI.escape(notification_id.to_s)) + + # query parameters + query_params = opts[:query_params] || {} + + # header parameters + header_params = opts[:header_params] || {} + # HTTP header 'Accept' (if needed) + header_params['Accept'] = @api_client.select_header_accept(['application/json']) + + # form parameters + form_params = opts[:form_params] || {} + + # http body (model) + post_body = opts[:debug_body] + + # return_type + return_type = opts[:debug_return_type] || 'PushNotificationDismissResponse' + + # auth_names + auth_names = opts[:debug_auth_names] || ['apiKeyAuth'] + + new_options = opts.merge( + :operation => :"PushNotificationsApi.dismiss_push_notification", + :header_params => header_params, + :query_params => query_params, + :form_params => form_params, + :body => post_body, + :auth_names => auth_names, + :return_type => return_type + ) + + data, status_code, headers = @api_client.call_api(:POST, local_var_path, new_options) + if @api_client.config.debugging + @api_client.config.logger.debug "API called: PushNotificationsApi#dismiss_push_notification\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}" + end + return data, status_code, headers + end + # Replace a push notification # Replaces a Push Notification you already sent, using the `notification_id` from the send response. The replacement takes the original's place on the Lock Screen and in Notification Center instead of appearing as a second notification. It is delivered silently, with no sound or screen wake, to the same recipients as the original. Notification history is updated in place. # @param notification_id [String] The `notification_id` returned when the Push Notification was sent. diff --git a/generated/activitysmith_openapi/models/account_device.rb b/generated/activitysmith_openapi/models/account_device.rb new file mode 100644 index 0000000..53457b9 --- /dev/null +++ b/generated/activitysmith_openapi/models/account_device.rb @@ -0,0 +1,367 @@ +=begin +#ActivitySmith API + +#Send push notifications and Live Activities to your own devices via a single API key. + +The version of the OpenAPI document: 1.0.0 + +Generated by: https://openapi-generator.tech +Generator version: 7.7.0 + +=end + +require 'date' +require 'time' + +module OpenapiClient + # An iPhone or iPad paired to the account, one entry per device and owner. + class AccountDevice + # Email of the account member who paired the device. + attr_accessor :user_email + + # Device ID. Use it to assign the device to Channels. + attr_accessor :device_id + + # Device platform, such as ios. iPhones and iPads both report ios; use system_name to tell them apart. + attr_accessor :device_type + + # True when the device can receive Push Notifications or Live Activities. + attr_accessor :is_active + + # Whether each delivery token is active, keyed by token type (push, start_live_activity). + attr_accessor :token_status + + # ActivitySmith app version on the device. + attr_accessor :app_version + + # Device name set on the device. + attr_accessor :device_name + + # Operating system name, such as iOS or iPadOS. + attr_accessor :system_name + + # Operating system version. + attr_accessor :system_version + + # When the device was first paired. + attr_accessor :created_at + + # When the device last updated its pairing. + attr_accessor :updated_at + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'user_email' => :'user_email', + :'device_id' => :'device_id', + :'device_type' => :'device_type', + :'is_active' => :'is_active', + :'token_status' => :'token_status', + :'app_version' => :'app_version', + :'device_name' => :'device_name', + :'system_name' => :'system_name', + :'system_version' => :'system_version', + :'created_at' => :'created_at', + :'updated_at' => :'updated_at' + } + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'user_email' => :'String', + :'device_id' => :'String', + :'device_type' => :'String', + :'is_active' => :'Boolean', + :'token_status' => :'Hash', + :'app_version' => :'String', + :'device_name' => :'String', + :'system_name' => :'String', + :'system_version' => :'String', + :'created_at' => :'Time', + :'updated_at' => :'Time' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + :'device_type', + :'app_version', + :'device_name', + :'system_name', + :'system_version', + :'created_at', + :'updated_at' + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `OpenapiClient::AccountDevice` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!self.class.attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `OpenapiClient::AccountDevice`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'user_email') + self.user_email = attributes[:'user_email'] + else + self.user_email = nil + end + + if attributes.key?(:'device_id') + self.device_id = attributes[:'device_id'] + else + self.device_id = nil + end + + if attributes.key?(:'device_type') + self.device_type = attributes[:'device_type'] + else + self.device_type = nil + end + + if attributes.key?(:'is_active') + self.is_active = attributes[:'is_active'] + else + self.is_active = nil + end + + if attributes.key?(:'token_status') + if (value = attributes[:'token_status']).is_a?(Hash) + self.token_status = value + end + else + self.token_status = nil + end + + if attributes.key?(:'app_version') + self.app_version = attributes[:'app_version'] + else + self.app_version = nil + end + + if attributes.key?(:'device_name') + self.device_name = attributes[:'device_name'] + else + self.device_name = nil + end + + if attributes.key?(:'system_name') + self.system_name = attributes[:'system_name'] + else + self.system_name = nil + end + + if attributes.key?(:'system_version') + self.system_version = attributes[:'system_version'] + else + self.system_version = nil + end + + if attributes.key?(:'created_at') + self.created_at = attributes[:'created_at'] + else + self.created_at = nil + end + + if attributes.key?(:'updated_at') + self.updated_at = attributes[:'updated_at'] + else + self.updated_at = nil + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @user_email.nil? + invalid_properties.push('invalid value for "user_email", user_email cannot be nil.') + end + + if @device_id.nil? + invalid_properties.push('invalid value for "device_id", device_id cannot be nil.') + end + + if @is_active.nil? + invalid_properties.push('invalid value for "is_active", is_active cannot be nil.') + end + + if @token_status.nil? + invalid_properties.push('invalid value for "token_status", token_status cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @user_email.nil? + return false if @device_id.nil? + return false if @is_active.nil? + return false if @token_status.nil? + true + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + user_email == o.user_email && + device_id == o.device_id && + device_type == o.device_type && + is_active == o.is_active && + token_status == o.token_status && + app_version == o.app_version && + device_name == o.device_name && + system_name == o.system_name && + system_version == o.system_version && + created_at == o.created_at && + updated_at == o.updated_at + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [user_email, device_id, device_type, is_active, token_status, app_version, device_name, system_name, system_version, created_at, updated_at].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Deserializes the data based on type + # @param string type Data type + # @param string value Value to be deserialized + # @return [Object] Deserialized data + def self._deserialize(type, value) + case type.to_sym + when :Time + Time.parse(value) + when :Date + Date.parse(value) + when :String + value.to_s + when :Integer + value.to_i + when :Float + value.to_f + when :Boolean + if value.to_s =~ /\A(true|t|yes|y|1)\z/i + true + else + false + end + when :Object + # generic object (usually a Hash), return directly + value + when /\AArray<(?.+)>\z/ + inner_type = Regexp.last_match[:inner_type] + value.map { |v| _deserialize(inner_type, v) } + when /\AHash<(?.+?), (?.+)>\z/ + k_type = Regexp.last_match[:k_type] + v_type = Regexp.last_match[:v_type] + {}.tap do |hash| + value.each do |k, v| + hash[_deserialize(k_type, k)] = _deserialize(v_type, v) + end + end + else # model + # models (e.g. Pet) or oneOf + klass = OpenapiClient.const_get(type) + klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) + end + end + + # Returns the string representation of the object + # @return [String] String presentation of the object + def to_s + to_hash.to_s + end + + # to_body is an alias to to_hash (backward compatibility) + # @return [Hash] Returns the object in the form of hash + def to_body + to_hash + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + # Outputs non-array value in the form of hash + # For object, use to_hash. Otherwise, just return the value + # @param [Object] value Any valid value + # @return [Hash] Returns the value in the form of hash + def _to_hash(value) + if value.is_a?(Array) + value.compact.map { |v| _to_hash(v) } + elsif value.is_a?(Hash) + {}.tap do |hash| + value.each { |k, v| hash[k] = _to_hash(v) } + end + elsif value.respond_to? :to_hash + value.to_hash + else + value + end + end + + end + +end diff --git a/generated/activitysmith_openapi/models/account_device_list_response.rb b/generated/activitysmith_openapi/models/account_device_list_response.rb new file mode 100644 index 0000000..d62501e --- /dev/null +++ b/generated/activitysmith_openapi/models/account_device_list_response.rb @@ -0,0 +1,239 @@ +=begin +#ActivitySmith API + +#Send push notifications and Live Activities to your own devices via a single API key. + +The version of the OpenAPI document: 1.0.0 + +Generated by: https://openapi-generator.tech +Generator version: 7.7.0 + +=end + +require 'date' +require 'time' + +module OpenapiClient + class AccountDeviceListResponse + attr_accessor :devices + + attr_accessor :meta + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'devices' => :'devices', + :'meta' => :'meta' + } + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'devices' => :'Array', + :'meta' => :'AccountDeviceListResponseMeta' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `OpenapiClient::AccountDeviceListResponse` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!self.class.attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `OpenapiClient::AccountDeviceListResponse`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'devices') + if (value = attributes[:'devices']).is_a?(Array) + self.devices = value + end + else + self.devices = nil + end + + if attributes.key?(:'meta') + self.meta = attributes[:'meta'] + else + self.meta = nil + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @devices.nil? + invalid_properties.push('invalid value for "devices", devices cannot be nil.') + end + + if @meta.nil? + invalid_properties.push('invalid value for "meta", meta cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @devices.nil? + return false if @meta.nil? + true + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + devices == o.devices && + meta == o.meta + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [devices, meta].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Deserializes the data based on type + # @param string type Data type + # @param string value Value to be deserialized + # @return [Object] Deserialized data + def self._deserialize(type, value) + case type.to_sym + when :Time + Time.parse(value) + when :Date + Date.parse(value) + when :String + value.to_s + when :Integer + value.to_i + when :Float + value.to_f + when :Boolean + if value.to_s =~ /\A(true|t|yes|y|1)\z/i + true + else + false + end + when :Object + # generic object (usually a Hash), return directly + value + when /\AArray<(?.+)>\z/ + inner_type = Regexp.last_match[:inner_type] + value.map { |v| _deserialize(inner_type, v) } + when /\AHash<(?.+?), (?.+)>\z/ + k_type = Regexp.last_match[:k_type] + v_type = Regexp.last_match[:v_type] + {}.tap do |hash| + value.each do |k, v| + hash[_deserialize(k_type, k)] = _deserialize(v_type, v) + end + end + else # model + # models (e.g. Pet) or oneOf + klass = OpenapiClient.const_get(type) + klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) + end + end + + # Returns the string representation of the object + # @return [String] String presentation of the object + def to_s + to_hash.to_s + end + + # to_body is an alias to to_hash (backward compatibility) + # @return [Hash] Returns the object in the form of hash + def to_body + to_hash + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + # Outputs non-array value in the form of hash + # For object, use to_hash. Otherwise, just return the value + # @param [Object] value Any valid value + # @return [Hash] Returns the value in the form of hash + def _to_hash(value) + if value.is_a?(Array) + value.compact.map { |v| _to_hash(v) } + elsif value.is_a?(Hash) + {}.tap do |hash| + value.each { |k, v| hash[k] = _to_hash(v) } + end + elsif value.respond_to? :to_hash + value.to_hash + else + value + end + end + + end + +end diff --git a/generated/activitysmith_openapi/models/account_device_list_response_meta.rb b/generated/activitysmith_openapi/models/account_device_list_response_meta.rb new file mode 100644 index 0000000..205536d --- /dev/null +++ b/generated/activitysmith_openapi/models/account_device_list_response_meta.rb @@ -0,0 +1,299 @@ +=begin +#ActivitySmith API + +#Send push notifications and Live Activities to your own devices via a single API key. + +The version of the OpenAPI document: 1.0.0 + +Generated by: https://openapi-generator.tech +Generator version: 7.7.0 + +=end + +require 'date' +require 'time' + +module OpenapiClient + class AccountDeviceListResponseMeta + # Devices matching the filter. + attr_accessor :total + + attr_accessor :limit + + attr_accessor :offset + + attr_accessor :has_more + + # Offset for the next page, or null on the last page. + attr_accessor :next_offset + + attr_accessor :include_inactive + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'total' => :'total', + :'limit' => :'limit', + :'offset' => :'offset', + :'has_more' => :'has_more', + :'next_offset' => :'next_offset', + :'include_inactive' => :'include_inactive' + } + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'total' => :'Integer', + :'limit' => :'Integer', + :'offset' => :'Integer', + :'has_more' => :'Boolean', + :'next_offset' => :'Integer', + :'include_inactive' => :'Boolean' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + :'next_offset', + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `OpenapiClient::AccountDeviceListResponseMeta` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!self.class.attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `OpenapiClient::AccountDeviceListResponseMeta`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'total') + self.total = attributes[:'total'] + else + self.total = nil + end + + if attributes.key?(:'limit') + self.limit = attributes[:'limit'] + else + self.limit = nil + end + + if attributes.key?(:'offset') + self.offset = attributes[:'offset'] + else + self.offset = nil + end + + if attributes.key?(:'has_more') + self.has_more = attributes[:'has_more'] + else + self.has_more = nil + end + + if attributes.key?(:'next_offset') + self.next_offset = attributes[:'next_offset'] + else + self.next_offset = nil + end + + if attributes.key?(:'include_inactive') + self.include_inactive = attributes[:'include_inactive'] + else + self.include_inactive = nil + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @total.nil? + invalid_properties.push('invalid value for "total", total cannot be nil.') + end + + if @limit.nil? + invalid_properties.push('invalid value for "limit", limit cannot be nil.') + end + + if @offset.nil? + invalid_properties.push('invalid value for "offset", offset cannot be nil.') + end + + if @has_more.nil? + invalid_properties.push('invalid value for "has_more", has_more cannot be nil.') + end + + if @include_inactive.nil? + invalid_properties.push('invalid value for "include_inactive", include_inactive cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @total.nil? + return false if @limit.nil? + return false if @offset.nil? + return false if @has_more.nil? + return false if @include_inactive.nil? + true + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + total == o.total && + limit == o.limit && + offset == o.offset && + has_more == o.has_more && + next_offset == o.next_offset && + include_inactive == o.include_inactive + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [total, limit, offset, has_more, next_offset, include_inactive].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Deserializes the data based on type + # @param string type Data type + # @param string value Value to be deserialized + # @return [Object] Deserialized data + def self._deserialize(type, value) + case type.to_sym + when :Time + Time.parse(value) + when :Date + Date.parse(value) + when :String + value.to_s + when :Integer + value.to_i + when :Float + value.to_f + when :Boolean + if value.to_s =~ /\A(true|t|yes|y|1)\z/i + true + else + false + end + when :Object + # generic object (usually a Hash), return directly + value + when /\AArray<(?.+)>\z/ + inner_type = Regexp.last_match[:inner_type] + value.map { |v| _deserialize(inner_type, v) } + when /\AHash<(?.+?), (?.+)>\z/ + k_type = Regexp.last_match[:k_type] + v_type = Regexp.last_match[:v_type] + {}.tap do |hash| + value.each do |k, v| + hash[_deserialize(k_type, k)] = _deserialize(v_type, v) + end + end + else # model + # models (e.g. Pet) or oneOf + klass = OpenapiClient.const_get(type) + klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) + end + end + + # Returns the string representation of the object + # @return [String] String presentation of the object + def to_s + to_hash.to_s + end + + # to_body is an alias to to_hash (backward compatibility) + # @return [Hash] Returns the object in the form of hash + def to_body + to_hash + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + # Outputs non-array value in the form of hash + # For object, use to_hash. Otherwise, just return the value + # @param [Object] value Any valid value + # @return [Hash] Returns the value in the form of hash + def _to_hash(value) + if value.is_a?(Array) + value.compact.map { |v| _to_hash(v) } + elsif value.is_a?(Hash) + {}.tap do |hash| + value.each { |k, v| hash[k] = _to_hash(v) } + end + elsif value.respond_to? :to_hash + value.to_hash + else + value + end + end + + end + +end diff --git a/generated/activitysmith_openapi/models/push_notification_dismiss_response.rb b/generated/activitysmith_openapi/models/push_notification_dismiss_response.rb new file mode 100644 index 0000000..a3b2bb2 --- /dev/null +++ b/generated/activitysmith_openapi/models/push_notification_dismiss_response.rb @@ -0,0 +1,257 @@ +=begin +#ActivitySmith API + +#Send push notifications and Live Activities to your own devices via a single API key. + +The version of the OpenAPI document: 1.0.0 + +Generated by: https://openapi-generator.tech +Generator version: 7.7.0 + +=end + +require 'date' +require 'time' + +module OpenapiClient + # Dismissal result. Devices clear the notification after this response, so it does not confirm that any device has cleared it. + class PushNotificationDismissResponse + # True when the dismissal is recorded. + attr_accessor :success + + # ID of the dismissed Push Notification. + attr_accessor :notification_id + + # When the notification was first dismissed. Repeat calls return the same time. + attr_accessor :dismissed_at + + # Attribute mapping from ruby-style variable name to JSON key. + def self.attribute_map + { + :'success' => :'success', + :'notification_id' => :'notification_id', + :'dismissed_at' => :'dismissed_at' + } + end + + # Returns all the JSON keys this model knows about + def self.acceptable_attributes + attribute_map.values + end + + # Attribute type mapping. + def self.openapi_types + { + :'success' => :'Boolean', + :'notification_id' => :'String', + :'dismissed_at' => :'Time' + } + end + + # List of attributes with nullable: true + def self.openapi_nullable + Set.new([ + ]) + end + + # Initializes the object + # @param [Hash] attributes Model attributes in the form of hash + def initialize(attributes = {}) + if (!attributes.is_a?(Hash)) + fail ArgumentError, "The input argument (attributes) must be a hash in `OpenapiClient::PushNotificationDismissResponse` initialize method" + end + + # check to see if the attribute exists and convert string to symbol for hash key + attributes = attributes.each_with_object({}) { |(k, v), h| + if (!self.class.attribute_map.key?(k.to_sym)) + fail ArgumentError, "`#{k}` is not a valid attribute in `OpenapiClient::PushNotificationDismissResponse`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect + end + h[k.to_sym] = v + } + + if attributes.key?(:'success') + self.success = attributes[:'success'] + else + self.success = nil + end + + if attributes.key?(:'notification_id') + self.notification_id = attributes[:'notification_id'] + else + self.notification_id = nil + end + + if attributes.key?(:'dismissed_at') + self.dismissed_at = attributes[:'dismissed_at'] + else + self.dismissed_at = nil + end + end + + # Show invalid properties with the reasons. Usually used together with valid? + # @return Array for valid properties with the reasons + def list_invalid_properties + warn '[DEPRECATED] the `list_invalid_properties` method is obsolete' + invalid_properties = Array.new + if @success.nil? + invalid_properties.push('invalid value for "success", success cannot be nil.') + end + + if @notification_id.nil? + invalid_properties.push('invalid value for "notification_id", notification_id cannot be nil.') + end + + if @dismissed_at.nil? + invalid_properties.push('invalid value for "dismissed_at", dismissed_at cannot be nil.') + end + + invalid_properties + end + + # Check to see if the all the properties in the model are valid + # @return true if the model is valid + def valid? + warn '[DEPRECATED] the `valid?` method is obsolete' + return false if @success.nil? + return false if @notification_id.nil? + return false if @dismissed_at.nil? + true + end + + # Checks equality by comparing each attribute. + # @param [Object] Object to be compared + def ==(o) + return true if self.equal?(o) + self.class == o.class && + success == o.success && + notification_id == o.notification_id && + dismissed_at == o.dismissed_at + end + + # @see the `==` method + # @param [Object] Object to be compared + def eql?(o) + self == o + end + + # Calculates hash code according to all attributes. + # @return [Integer] Hash code + def hash + [success, notification_id, dismissed_at].hash + end + + # Builds the object from hash + # @param [Hash] attributes Model attributes in the form of hash + # @return [Object] Returns the model itself + def self.build_from_hash(attributes) + return nil unless attributes.is_a?(Hash) + attributes = attributes.transform_keys(&:to_sym) + transformed_hash = {} + openapi_types.each_pair do |key, type| + if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = nil + elsif type =~ /\AArray<(.*)>/i + # check to ensure the input is an array given that the attribute + # is documented as an array but the input is not + if attributes[attribute_map[key]].is_a?(Array) + transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) } + end + elsif !attributes[attribute_map[key]].nil? + transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]]) + end + end + new(transformed_hash) + end + + # Deserializes the data based on type + # @param string type Data type + # @param string value Value to be deserialized + # @return [Object] Deserialized data + def self._deserialize(type, value) + case type.to_sym + when :Time + Time.parse(value) + when :Date + Date.parse(value) + when :String + value.to_s + when :Integer + value.to_i + when :Float + value.to_f + when :Boolean + if value.to_s =~ /\A(true|t|yes|y|1)\z/i + true + else + false + end + when :Object + # generic object (usually a Hash), return directly + value + when /\AArray<(?.+)>\z/ + inner_type = Regexp.last_match[:inner_type] + value.map { |v| _deserialize(inner_type, v) } + when /\AHash<(?.+?), (?.+)>\z/ + k_type = Regexp.last_match[:k_type] + v_type = Regexp.last_match[:v_type] + {}.tap do |hash| + value.each do |k, v| + hash[_deserialize(k_type, k)] = _deserialize(v_type, v) + end + end + else # model + # models (e.g. Pet) or oneOf + klass = OpenapiClient.const_get(type) + klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) + end + end + + # Returns the string representation of the object + # @return [String] String presentation of the object + def to_s + to_hash.to_s + end + + # to_body is an alias to to_hash (backward compatibility) + # @return [Hash] Returns the object in the form of hash + def to_body + to_hash + end + + # Returns the object in the form of hash + # @return [Hash] Returns the object in the form of hash + def to_hash + hash = {} + self.class.attribute_map.each_pair do |attr, param| + value = self.send(attr) + if value.nil? + is_nullable = self.class.openapi_nullable.include?(attr) + next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}")) + end + + hash[param] = _to_hash(value) + end + hash + end + + # Outputs non-array value in the form of hash + # For object, use to_hash. Otherwise, just return the value + # @param [Object] value Any valid value + # @return [Hash] Returns the value in the form of hash + def _to_hash(value) + if value.is_a?(Array) + value.compact.map { |v| _to_hash(v) } + elsif value.is_a?(Hash) + {}.tap do |hash| + value.each { |k, v| hash[k] = _to_hash(v) } + end + elsif value.respond_to? :to_hash + value.to_hash + else + value + end + end + + end + +end diff --git a/generated/openapi-source.json b/generated/openapi-source.json index 498433c..6f55ecd 100644 --- a/generated/openapi-source.json +++ b/generated/openapi-source.json @@ -1,6 +1,6 @@ { "repository": "ActivitySmithHQ/activitysmith-backend", - "commit": "7bf9c3d88459fe3f122810979a45da0a1e6fd2d6", + "commit": "278d980633d4b1fc735ca96948fdd8ff8fd27642", "path": "openapi.json", - "sha256": "6987f90b106e28a0ab8058f43309e7a9f045c7c54d27ce90bbdd5966c3f99f7a" + "sha256": "0dbbba13bf497eceea4de0759943481a6e085cdf9a9a5445a60cf2b27457de40" } From e9663b7fafb267fee2968c7787414a22336c6488 Mon Sep 17 00:00:00 2001 From: bardonadam Date: Sat, 7 Nov 2026 18:11:33 +0700 Subject: [PATCH 2/2] Release 1.15.0: dismiss Push Notifications and list devices Add notifications.dismiss (alias dismiss_push_notification) and a devices resource with devices.list (alias list_account_devices) over the regenerated client. Cover both with fake and real generated-client tests, run the docs examples against a mocked transport, and bump the version to 1.15.0 with a changelog entry. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 + lib/activitysmith.rb | 1 + lib/activitysmith/client.rb | 4 +- lib/activitysmith/devices.rb | 30 +++++ lib/activitysmith/notifications.rb | 11 ++ lib/activitysmith/version.rb | 2 +- test/client_test.rb | 11 ++ test/dismiss_devices_test.rb | 14 +++ test/resources_test.rb | 54 ++++++++ test/support/dismiss_devices_serialization.rb | 119 ++++++++++++++++++ 10 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 lib/activitysmith/devices.rb create mode 100644 test/dismiss_devices_test.rb create mode 100644 test/support/dismiss_devices_serialization.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index cb78d18..2ed8882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.15.0 + +Adds Push Notification dismissal. Pass a `notification_id` to `notifications.dismiss` to clear a Push Notification you already sent from the Lock Screen and Notification Center. History keeps it. Also adds `metrics.create` to create a Lock Screen Widget metric and set its label and format, such as currency or percent, and `devices.list` to list the iPhones and iPads paired to your account. + ## 1.14.0 Adds the ability to replace a Push Notification you already sent with a new one. diff --git a/lib/activitysmith.rb b/lib/activitysmith.rb index d7e6981..b9f83f1 100644 --- a/lib/activitysmith.rb +++ b/lib/activitysmith.rb @@ -6,4 +6,5 @@ require_relative "activitysmith/notifications" require_relative "activitysmith/live_activities" require_relative "activitysmith/metrics" +require_relative "activitysmith/devices" require_relative "activitysmith/client" diff --git a/lib/activitysmith/client.rb b/lib/activitysmith/client.rb index 707b40e..156266c 100644 --- a/lib/activitysmith/client.rb +++ b/lib/activitysmith/client.rb @@ -2,7 +2,7 @@ module ActivitySmith class Client - attr_reader :notifications, :live_activities, :metrics + attr_reader :notifications, :live_activities, :metrics, :devices def initialize(api_key:) raise ArgumentError, "ActivitySmith: api_key is required" if api_key.to_s.strip.empty? @@ -19,6 +19,7 @@ def initialize(api_key:) @notifications = Notifications.new(OpenapiClient::PushNotificationsApi.new(api_client)) @live_activities = LiveActivities.new(OpenapiClient::LiveActivitiesApi.new(api_client)) @metrics = Metrics.new(OpenapiClient::MetricsApi.new(api_client)) + @devices = Devices.new(OpenapiClient::DevicesApi.new(api_client)) @app_icon_badges = OpenapiClient::AppIconBadgesApi.new(api_client) end @@ -53,6 +54,7 @@ def generated_client_present? "OpenapiClient::PushNotificationsApi", "OpenapiClient::LiveActivitiesApi", "OpenapiClient::MetricsApi", + "OpenapiClient::DevicesApi", "OpenapiClient::AppIconBadgesApi" ].reject { |name| constant_defined?(name) } diff --git a/lib/activitysmith/devices.rb b/lib/activitysmith/devices.rb new file mode 100644 index 0000000..dcacce1 --- /dev/null +++ b/lib/activitysmith/devices.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActivitySmith + class Devices + def initialize(api) + @api = api + end + + # Lists the iPhones and iPads paired to the account, active devices first. + def list(limit: nil, offset: nil, include_inactive: nil, **opts) + query = { limit: limit, offset: offset, include_inactive: include_inactive }.compact + @api.list_account_devices(opts.merge(query)) + end + + # Generated-style alias. + def list_account_devices(opts = {}) + @api.list_account_devices(opts) + end + + def method_missing(name, *args, &block) + return @api.public_send(name, *args, &block) if @api.respond_to?(name) + + super + end + + def respond_to_missing?(name, include_private = false) + @api.respond_to?(name, include_private) || super + end + end +end diff --git a/lib/activitysmith/notifications.rb b/lib/activitysmith/notifications.rb index 63b2d8b..63ab606 100644 --- a/lib/activitysmith/notifications.rb +++ b/lib/activitysmith/notifications.rb @@ -30,6 +30,17 @@ def replace_push_notification(notification_id, push_notification_replace_request replace(notification_id, push_notification_replace_request, opts, tags: tags, **request_fields) end + # Clears a sent Push Notification from the Lock Screen and Notification Center, + # using the notification_id returned by send. History keeps it. + def dismiss(notification_id, opts = {}) + @api.dismiss_push_notification(notification_id, opts) + end + + # Generated-style alias. + def dismiss_push_notification(notification_id, opts = {}) + dismiss(notification_id, opts) + end + def method_missing(name, *args, &block) return @api.public_send(name, *args, &block) if @api.respond_to?(name) diff --git a/lib/activitysmith/version.rb b/lib/activitysmith/version.rb index 60e0a3a..6a95fb9 100644 --- a/lib/activitysmith/version.rb +++ b/lib/activitysmith/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module ActivitySmith - VERSION = "1.14.0" + VERSION = "1.15.0" end diff --git a/test/client_test.rb b/test/client_test.rb index 155ddd0..bda24d7 100644 --- a/test/client_test.rb +++ b/test/client_test.rb @@ -45,6 +45,14 @@ def initialize(api_client) end end + class DevicesApi + attr_reader :api_client + + def initialize(api_client) + @api_client = api_client + end + end + class AppIconBadgesApi attr_reader :api_client, :calls @@ -82,6 +90,9 @@ def test_constructs_when_generated_client_is_present assert_respond_to client.live_activities, :end_stream refute_nil client.metrics assert_respond_to client.metrics, :update + refute_nil client.devices + assert_respond_to client.devices, :list + assert_respond_to client.notifications, :dismiss assert_respond_to client, :badge_count metrics_api = client.metrics.instance_variable_get(:@api) diff --git a/test/dismiss_devices_test.rb b/test/dismiss_devices_test.rb new file mode 100644 index 0000000..df4c350 --- /dev/null +++ b/test/dismiss_devices_test.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'test_helper' +require 'open3' +require 'rbconfig' + +class DismissAndDevicesSerializationTest < Minitest::Test + def test_real_generated_client_in_isolation + # Other wrapper tests install fake OpenapiClient classes globally. + output, status = Open3.capture2e(RbConfig.ruby, '-Ilib', 'test/support/dismiss_devices_serialization.rb') + assert status.success?, output + assert_includes output, '0 failures, 0 errors' + end +end diff --git a/test/resources_test.rb b/test/resources_test.rb index 6f81d48..444d6a0 100644 --- a/test/resources_test.rb +++ b/test/resources_test.rb @@ -25,6 +25,11 @@ def replace_push_notification(notification_id, request, opts = {}) { ok: true } end + def dismiss_push_notification(notification_id, opts = {}) + @calls << [:dismiss_push_notification, notification_id, opts] + { success: true, notification_id: notification_id } + end + def send_push_notification_with_http_info(request, opts = {}) @calls << [:send_push_notification_with_http_info, request, opts] [:ok, 200, {}] @@ -82,7 +87,56 @@ def update_metric_value(key, request, opts = {}) end end +class FakeDevicesApi + attr_reader :calls + + def initialize + @calls = [] + end + + def list_account_devices(opts = {}) + @calls << [:list_account_devices, opts] + { devices: [] } + end +end + class ResourcesTest < Minitest::Test + def test_notifications_dismiss_and_alias + api = FakePushApi.new + resource = ActivitySmith::Notifications.new(api) + + assert_equal({ success: true, notification_id: "pn1" }, resource.dismiss("pn1")) + resource.dismiss_push_notification("pn2") + + assert_equal( + [ + [:dismiss_push_notification, "pn1", {}], + [:dismiss_push_notification, "pn2", {}] + ], + api.calls + ) + end + + def test_devices_list_and_alias + api = FakeDevicesApi.new + resource = ActivitySmith::Devices.new(api) + + assert_equal({ devices: [] }, resource.list) + resource.list(include_inactive: true, limit: 10) + resource.list(include_inactive: false, offset: 0) + resource.list_account_devices(limit: 5) + + assert_equal( + [ + [:list_account_devices, {}], + [:list_account_devices, { limit: 10, include_inactive: true }], + [:list_account_devices, { offset: 0, include_inactive: false }], + [:list_account_devices, { limit: 5 }] + ], + api.calls + ) + end + def test_notifications_short_and_legacy_methods api = FakePushApi.new resource = ActivitySmith::Notifications.new(api) diff --git a/test/support/dismiss_devices_serialization.rb b/test/support/dismiss_devices_serialization.rb new file mode 100644 index 0000000..11c9bb3 --- /dev/null +++ b/test/support/dismiss_devices_serialization.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require_relative '../test_helper' +require 'json' +require 'stringio' + +class DismissAndDevicesTest < Minitest::Test + SEND_RESPONSE = '{"success":true,"notification_id":"pn_123","devices_notified":1,"timestamp":"2026-11-07T09:00:00Z"}' + DISMISS_RESPONSE = '{"success":true,"notification_id":"pn_123","dismissed_at":"2026-11-07T09:01:00Z","future_field":true}' + DEVICES_RESPONSE = '{"devices":[{"user_email":"owner@example.com","device_id":"dev_1","device_type":"ios",' \ + '"is_active":true,"token_status":{"push":true,"start_live_activity":true},"app_version":"1.17.0","device_name":"iPhone",' \ + '"system_name":"iOS","system_version":"27.0","created_at":"2026-11-01T09:00:00Z",' \ + '"updated_at":"2026-11-07T09:00:00Z"}],' \ + '"meta":{"total":1,"limit":100,"offset":0,"has_more":false,"next_offset":null,"include_inactive":false}}' + METRIC_RESPONSE = '{"metric":{"public_id":"m1","key":"mrr","label":"MRR","format":"currency","currency_code":"USD",' \ + '"unit":null,"unit_spacing":"none","latest_value":12450,"latest_value_at":"2026-11-07T09:00:00Z",' \ + '"created_at":"2026-11-07T09:00:00Z","updated_at":"2026-11-07T09:00:00Z"}}' + + # Builds a real client whose shared ApiClient never touches the network. + def mocked_client(captured) + activitysmith = ActivitySmith::Client.new(api_key: 'mock-only') + api_client = activitysmith.notifications.instance_variable_get(:@api).api_client + api_client.define_singleton_method(:call_api) do |http_method, path, opts = {}| + request = build_request(http_method, path, opts) + body = request.options[:body] + captured << { + method: http_method, + path: path, + params: request.options[:params], + body: body.nil? || body.empty? ? nil : JSON.parse(body) + } + json = case path + when '/push-notification' then SEND_RESPONSE + when %r{\A/push-notification/[^/]+/dismiss\z} then DISMISS_RESPONSE + when '/account/devices' then DEVICES_RESPONSE + else METRIC_RESPONSE + end + response = Struct.new(:body, :headers).new(json, { 'Content-Type' => 'application/json' }) + [deserialize(response, opts[:return_type]), 200, {}] + end + activitysmith + end + + def test_dismiss_posts_and_parses_response + [:dismiss, :dismiss_push_notification].each do |method| + captured = [] + activitysmith = mocked_client(captured) + response = activitysmith.notifications.public_send(method, 'pn_123') + + assert_equal [{ method: :POST, path: '/push-notification/pn_123/dismiss', params: {}, body: nil }], captured + assert_instance_of OpenapiClient::PushNotificationDismissResponse, response + assert_equal true, response.success + assert_equal 'pn_123', response.notification_id + refute_nil response.dismissed_at + end + end + + def test_devices_list_sends_query_and_parses_response + captured = [] + activitysmith = mocked_client(captured) + + result = activitysmith.devices.list + activitysmith.devices.list(include_inactive: true, limit: 10) + activitysmith.devices.list_account_devices(offset: 100) + + assert_equal( + [ + { method: :GET, path: '/account/devices', params: {}, body: nil }, + { method: :GET, path: '/account/devices', params: { limit: 10, include_inactive: true }, body: nil }, + { method: :GET, path: '/account/devices', params: { offset: 100 }, body: nil } + ], + captured + ) + assert_instance_of OpenapiClient::AccountDeviceListResponse, result + assert_equal 'dev_1', result.devices.first.device_id + assert_equal false, result.meta.has_more + end + + def test_devices_list_encodes_query_string + ActivitySmith::Client.new(api_key: 'mock-only') + request = OpenapiClient::ApiClient.new.build_request(:GET, '/account/devices', query_params: { include_inactive: true, limit: 10 }) + url = Typhoeus::EasyFactory.new(request).get.effective_url + assert_equal 'https://activitysmith.com/api/account/devices?include_inactive=true&limit=10', url + end + + def test_doc_examples_run_as_written + captured = [] + activitysmith = mocked_client(captured) + original_stdout = $stdout + $stdout = StringIO.new + begin + sent = activitysmith.notifications.send({ title: "🙋 Agent needs your input", message: "Approve the database migration?" }) + activitysmith.notifications.dismiss(sent.notification_id) + result = activitysmith.devices.list + result.devices.each do |device| + puts [device.device_name, device.system_name, device.is_active].join(" ") + end + activitysmith.devices.list(include_inactive: true) + activitysmith.metrics.create("mrr", label: "MRR", format: "currency", currency_code: "USD") + activitysmith.metrics.update("mrr", 12450) + printed = $stdout.string + ensure + $stdout = original_stdout + end + + assert_equal "iPhone iOS true\n", printed + assert_equal( + [ + [:POST, '/push-notification', {}, { 'title' => '🙋 Agent needs your input', 'message' => 'Approve the database migration?' }], + [:POST, '/push-notification/pn_123/dismiss', {}, nil], + [:GET, '/account/devices', {}, nil], + [:GET, '/account/devices', { include_inactive: true }, nil], + [:PUT, '/metrics/mrr', {}, { 'label' => 'MRR', 'format' => 'currency', 'currency_code' => 'USD' }], + [:POST, '/metrics/mrr/value', {}, { 'value' => 12450 }] + ], + captured.map { |call| call.values_at(:method, :path, :params, :body) } + ) + end +end