├── .editorconfig ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── frontapp.gemspec ├── lib ├── frontapp.rb └── frontapp │ ├── client.rb │ ├── client │ ├── attachments.rb │ ├── channels.rb │ ├── comments.rb │ ├── contact_groups.rb │ ├── contacts.rb │ ├── conversations.rb │ ├── drafts.rb │ ├── events.rb │ ├── exports.rb │ ├── inboxes.rb │ ├── links.rb │ ├── messages.rb │ ├── rules.rb │ ├── tags.rb │ ├── teammates.rb │ ├── teams.rb │ └── topics.rb │ ├── error.rb │ ├── utils │ └── hash.rb │ └── version.rb └── spec ├── attachments_spec.rb ├── channels_spec.rb ├── client_spec.rb ├── comments_spec.rb ├── contact_group_spec.rb ├── contacts_spec.rb ├── conversations_spec.rb ├── drafts_spec.rb ├── error_spec.rb ├── events_spec.rb ├── exports_spec.rb ├── fixtures └── sample.txt ├── hash_spec.rb ├── inboxes_spec.rb ├── links_spec.rb ├── messages_spec.rb ├── rules_spec.rb ├── spec_helper.rb ├── tags_spec.rb ├── teammates_spec.rb ├── teams_spec.rb ├── topics_spec.rb └── user_agent_spec.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | 9 | [*.rb] 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | 14 | [*.rake] 15 | indent_style = space 16 | indent_size = 2 17 | trim_trailing_whitespace = true 18 | 19 | [*.js] 20 | indent_style = tab 21 | trim_trailing_whitespace = true 22 | 23 | [*.jsx] 24 | indent_style = tab 25 | trim_trailing_whitespace = true 26 | 27 | [*.json] 28 | indent_style = space 29 | indent_size = 2 30 | trim_trailing_whitespace = true 31 | 32 | [*.json.erb] 33 | indent_style = space 34 | indent_size = 2 35 | trim_trailing_whitespace = true 36 | 37 | [config.ru] 38 | indent_style = space 39 | indent_size = 2 40 | trim_trailing_whitespace = true 41 | 42 | [Rakefile] 43 | indent_style = space 44 | indent_size = 2 45 | trim_trailing_whitespace = true 46 | 47 | [Gemfile] 48 | indent_style = space 49 | indent_size = 2 50 | trim_trailing_whitespace = true 51 | 52 | [*.yml] 53 | indent_style = space 54 | indent_size = 2 55 | trim_trailing_whitespace = true 56 | 57 | [*.html] 58 | indent_style = space 59 | indent_size = 2 60 | trim_trailing_whitespace = true 61 | 62 | [*.html.erb] 63 | indent_style = space 64 | indent_size = 2 65 | trim_trailing_whitespace = true 66 | 67 | [*.css] 68 | indent_style = space 69 | indent_size = 2 70 | trim_trailing_whitespace = true 71 | 72 | [*.scss] 73 | indent_style = space 74 | indent_size = 2 75 | trim_trailing_whitespace = true 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | .rspec 4 | Gemfile.lock 5 | vendor 6 | .idea/ 7 | 8 | # ignore RVM config 9 | .versions.conf 10 | 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at Phusion Passenger: 38 | 39 | [FloorD](https://github.com/floord) (she/her), floor@phusion.nl, English / Dutch / German 40 | 41 | [scarhand](https://github.com/scarhand) (he/his), niels@phusion.nl, English / Dutch 42 | 43 | The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 44 | 45 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 46 | 47 | ## Attribution 48 | 49 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 50 | 51 | [homepage]: http://contributor-covenant.org 52 | [version]: http://contributor-covenant.org/version/1/4/ 53 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Phusion Holding B.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontapp 2 | 3 | Ruby client to work with Frontapp API (https://dev.frontapp.com) 4 | 5 | ## Installation 6 | 7 | ```Bash 8 | gem install frontapp 9 | ``` 10 | 11 | ## Usage 12 | 13 | The auth_token can be obtained from Frontapp, in Settings -> API & Integrations -> API 14 | 15 | Create a Frontapp client 16 | ```ruby 17 | require 'frontapp' 18 | client = Frontapp::Client.new(auth_token: 'token') 19 | ``` 20 | 21 | Optionally, set a custom user agent to identify your integration 22 | ```ruby 23 | client = Frontapp::Client.new(auth_token: 'token', user_agent: 'Eye-Phone Integration (engineering@planet-express.com') 24 | ``` 25 | 26 | ### Attachments 27 | ```ruby 28 | # Download a file attachment 29 | attachment_file = client.download_attachment("fil_55c8c149") 30 | ``` 31 | 32 | ### Channels 33 | ```ruby 34 | # Get all channels 35 | channels = client.channels 36 | 37 | # Get a specific channel 38 | channel = client.get_channel("cha_55c8c149") 39 | 40 | # Update the webhook url of a custom channel 41 | client.update_channel!("cha_55c8c149", { settings: { webhook_url: "my-uri" } }) 42 | 43 | # Create custom channel 44 | channel = client.create_channel!("inb_55c8c149", { settings: { webhook_url: "my-uri" } }) 45 | 46 | # Get the inbox for a channel 47 | inbox = client.get_channel_inbox("cha_55c8c149") 48 | 49 | ``` 50 | 51 | ### Comments 52 | ```ruby 53 | # Create a new comment in a conversation 54 | comment = client.create_comment!("cnv_55c8c149", { author_id: "user@example.com", body: "text" }) 55 | 56 | # Get all comments for a conversation 57 | comments = client.get_conversation_comments("cnv_55c8c149") 58 | 59 | # Get a specific comment 60 | comment = client.get_comment("com_55c8c149") 61 | 62 | # Get all mentions in a comment 63 | mentions = client.get_comment_mentions("com_55c8c149") 64 | ``` 65 | 66 | ### Contact Groups 67 | ```ruby 68 | # Get all contact groups 69 | group = client.contact_groups 70 | 71 | # Create a new contact group 72 | group = client.create_contact_group!({ name: "Name" }) 73 | 74 | # Delete a contact group 75 | client.delete_contact_group!("grp_55c8c149") 76 | 77 | # Get all contacts in a group 78 | contacts = client.get_contact_group_contacts("grp_55c8c149") 79 | 80 | # Add contacts to a group 81 | client.add_contacts_to_contact_group!("grp_55c8c149", { contact_ids: ["name@example.com", "other@example.com"] }) 82 | 83 | ``` 84 | 85 | ### Contacts 86 | ```ruby 87 | # Get all contacts 88 | contacts = client.contacts 89 | 90 | # Get a specific contact 91 | contact = client.get_contact("ctc_55c8c149") 92 | 93 | # Update a contact 94 | client.update_contact!("ctc_55c8c149", { 95 | name: "Name", 96 | description: "Description", 97 | avatar_url: "http://example.com/avatar", 98 | is_spammer: false, 99 | links: ["http://example.com"], 100 | group_names: ["Customer"] 101 | }) 102 | 103 | # Create a new contact 104 | contact = client.create_contact!({ 105 | name: "Name", 106 | description: "Description", 107 | avatar_url: "http://example.com/avatar", 108 | is_spammer: false, 109 | links: ["http://example.com"], 110 | group_names: ["Customer"], 111 | handles: [{ 112 | "handle": "@calculon", 113 | "source": "twitter" 114 | }], 115 | custom_fields: { 116 | job_title: "Engineer" 117 | } 118 | }) 119 | 120 | # Delete a contact 121 | client.delete_contact!("ctc_55c8c149") 122 | 123 | # Get all conversations for a contact 124 | # Optionally include a filter for conversation statuses 125 | conversations = client.get_contact_conversations("ctc_55c8c149", { q: { statuses: ["assigned", "unassigned"] } }) 126 | 127 | # Add a handle to a contact 128 | client.add_contact_handle!("ctc_55c8c149", { handle: "@calculon", source: "twitter" }) 129 | 130 | # Delete a handle from a contact 131 | client.delete_contact_handle!("ctc_55c8c149", { handle: "@calculon", source: "twitter" }) 132 | 133 | # Force delete a handle from a contact, required if it is the last handle 134 | client.delete_contact_handle!("ctc_55c8c149", { handle: "@calculon", source: "twitter", force: true }) 135 | 136 | # Get all notes for a contact 137 | notes = client.get_contact_notes("ctc_55c8c149") 138 | 139 | # Create a new note for a contact 140 | note = client.add_contact_note!("ctc_55c8c149", { 141 | author_id: "user@example.com", 142 | body: "Foobar" 143 | }) 144 | 145 | ``` 146 | 147 | ### Conversations 148 | ```ruby 149 | # Get all conversations 150 | conversations = client.conversations 151 | 152 | # Get a specific conversation 153 | converstation = client.get_conversation("cnv_55c8c149") 154 | 155 | # Update a conversation 156 | client.update_conversation!("cnv_55c8c149", { 157 | assignee_id: "user@example.com", 158 | status: "archived", 159 | inbox_id: "inb_55c8c149", 160 | tags: ["time travel"] 161 | }) 162 | 163 | # Get all inboxes a conversation is in 164 | inboxes = client.get_conversation_inboxes("cnv_55c8c149") 165 | 166 | # Get all teammates following a conversation 167 | followers = client.get_conversation_followers("cnv_55c8c149") 168 | 169 | # Get all events for a conversation 170 | events = client.get_conversation_events("cnv_55c8c149") 171 | 172 | # Get all messages for a conversation 173 | messages = client.get_conversation_messages("cnv_55c8c149") 174 | 175 | # Add conversation links (by link_id) 176 | client.add_conversation_links!("cnv_55c8c149", { 177 | link_ids: ["top_3ii5d", "top_3ih5t"] 178 | }) 179 | 180 | # Add conversation links (by link) (it creates the link if doesn't exist) 181 | client.add_conversation_links!("cnv_55c8c149", { 182 | link_links: ["https://example.com"] 183 | }) 184 | 185 | # Remove conversation links 186 | client.remove_conversation_links!("cnv_55c8c149", { 187 | link_ids: ["top_3ii5d", "top_3ih5t"] 188 | }) 189 | 190 | # Add conversation followers 191 | client.add_conversation_followers!("cnv_55c8c149", { 192 | teammate_ids: ["tea_64ue9", "tea_638yp"] 193 | }) 194 | 195 | # Remove conversation followers 196 | client.remove_conversation_followers!("cnv_55c8c149", { 197 | teammate_ids: ["tea_64ue9", "tea_638yp"] 198 | }) 199 | ``` 200 | 201 | ### Events 202 | ```ruby 203 | # Get all events 204 | events = client.events 205 | 206 | # Get a specific event 207 | event = client.get_event("evt_55c8c149") 208 | 209 | ``` 210 | 211 | ### Inboxes 212 | ```ruby 213 | # Get all inboxes 214 | inboxes = client.inboxes 215 | 216 | # Get a specific inbox 217 | inbox = client.get_inbox("inb_55c8c149") 218 | 219 | # Create a new inbox 220 | inbox = client.create_inbox!({ name: "Support", teammate_ids: [] }) 221 | 222 | # Get all channels for an inbox 223 | channels = client.get_inbox_channels("inb_55c8c149") 224 | 225 | # Get all conversations in an inbox 226 | # Optionally include a filter for conversation statuses 227 | conversations = client.get_inbox_conversations("inb_55c8c149", { q: { statuses: ["assigned", "unassigned"] } }) 228 | 229 | # Get all teammates that have access 230 | teammates = client.get_inbox_teammates("inb_55c8c149") 231 | 232 | ``` 233 | 234 | ### Messages 235 | ```ruby 236 | # Get a specific message 237 | message = client.get_message("msg_55c8c149") 238 | 239 | # Get raw email source for a specific message 240 | message_source = client.get_message_source("msg_55c8c149") 241 | 242 | # Send a new message to a channel 243 | conversation_reference = client.send_message("cha_55c8c149", { 244 | author_id: "user@example.com", 245 | subject: "Good news everyone!", 246 | body: "Why is Zoidberg the only one still alone?", 247 | text: "Why is Zoidberg the only one still alone?", 248 | options: { 249 | tags: [], 250 | archive: true 251 | }, 252 | to: [ 253 | "me@example.com" 254 | ], 255 | cc: [], 256 | bcc: [] 257 | }) 258 | 259 | # Send a reply to a conversation 260 | client.send_reply("cnv_55c8c149", { 261 | author_id: "user@example.com", 262 | subject: "Good news everyone!", 263 | body: "Why is Zoidberg the only one still alone?", 264 | text: "Why is Zoidberg the only one still alone?", 265 | options: { 266 | tags: [], 267 | archive: true 268 | }, 269 | channel_id: "cha_55c8c149", 270 | to: [], 271 | cc: [], 272 | bcc: [] 273 | }) 274 | 275 | # Receive a custom message on a channel 276 | conversation_reference = client.receive_custom_message("cha_55c8c149", { 277 | sender: { 278 | name: "hermes", 279 | handle: "hermes_123" 280 | }, 281 | subject: "Question", 282 | body: "Didn't we used to be a delivery company?", 283 | metadata: {} 284 | }) 285 | 286 | # Import a message into an inbox 287 | conversation_reference = client.import_message("inb_55c8c149", { 288 | sender: { 289 | handle: "user@example.com" 290 | }, 291 | to: [], 292 | cc: [], 293 | bcc: [], 294 | body: "", 295 | body_format: "html", 296 | external_id: "", 297 | created_at: 1453770984.123, 298 | tags: [], 299 | metadata: { 300 | is_inbound: true, 301 | is_archived: true, 302 | should_skip_rules: true 303 | } 304 | }) 305 | 306 | ``` 307 | 308 | ### Rules 309 | ```ruby 310 | # Get all rules 311 | rules = client.rules 312 | 313 | # Get a specific rule 314 | rule = client.get_rule("rul_55c8c149") 315 | 316 | ``` 317 | 318 | ### Tags 319 | ```ruby 320 | # Get all tags 321 | tags = client.tags 322 | 323 | # Get specific tag 324 | tag = client.get_tag("tag_55c8c149") 325 | 326 | # Create tag 327 | tag = client.create_tag!({name: "New tag name"}) 328 | 329 | # Delete tag 330 | tag = client.delete_tag!("tag_55c8c149") 331 | 332 | # Get all conversation for a tag 333 | # Optionally include a filter for conversation statuses 334 | conversations = client.get_tag_conversations("tag_55c8c149", { q: { statuses: ["assigned", "unassigned"] } }) 335 | ``` 336 | 337 | ### Teams 338 | ```ruby 339 | # Get all teams 340 | teams = client.teams 341 | 342 | # Get a specific team 343 | team = client.get_team("tim_55c8c149") 344 | 345 | ``` 346 | 347 | ### Teammates 348 | ```ruby 349 | # Get all teammates 350 | teammates = client.teammates 351 | 352 | # Get a teammate 353 | teammate = client.get_teammate("user@example.com") 354 | 355 | # Update a teammate 356 | client.update_teammate!("user@example.com", { 357 | username: "bender", 358 | first_name: "Bender", 359 | last_name: "Rodriguez", 360 | is_admin: true, 361 | is_available: false 362 | }) 363 | 364 | # Get all conversations for a teammate 365 | conversations = client.get_teammate_conversations("user@example.com", { q: { statuses: ["assigned", "unassigned"] } }) 366 | 367 | # Get all inboxes for a teammate 368 | inboxes = client.get_teammate_inboxes("user@example.com") 369 | ``` 370 | 371 | ### Topics 372 | Topics is deprecated, please use Links instead! 373 | ```ruby 374 | # Get all conversations for a topic 375 | # Optionally include a filter for conversation statuses 376 | conversations = client.get_topic_conversations("top_55c8c149", { q: { statuses: ["assigned", "unassigned"] } }) 377 | ``` 378 | 379 | ### Links 380 | ```ruby 381 | # Get all links 382 | links = client.links 383 | 384 | # Get a link 385 | link = client.get_link("top_55c8c149") 386 | 387 | # Create a new link 388 | link = client.create_link!({ 389 | name: "Nice link", 390 | external_url: "https://www.example.com/nice_link" 391 | }) 392 | 393 | # Update a link 394 | client.update_link!("top_55c8c149", { name: "Something new" }) 395 | 396 | # Get all conversations for a link 397 | # Optionally include a filter for conversation statuses 398 | conversations = client.get_link_conversations("top_55c8c149", { q: { statuses: ["assigned", "unassigned"] } }) 399 | ``` 400 | 401 | ### Exports 402 | ```ruby 403 | # Get all exports 404 | exports = client.exports 405 | 406 | # Get a teammate 407 | export = client.get_export("exp_55c8c149") 408 | 409 | # Create a new export 410 | contact = client.create_export!({ 411 | inbox_id: "inb_55c8c149", 412 | start: 1428889003, 413 | end: 1428889008, 414 | timezone: "America/New_York", 415 | should_export_events: false 416 | }) 417 | 418 | # Create a new export 419 | contact = client.create_export_for_team!("tim_55c8c149", { 420 | inbox_id: "inb_55c8c149", 421 | start: 1428889003, 422 | end: 1428889008, 423 | timezone: "America/New_York", 424 | should_export_events: false 425 | }) 426 | ``` 427 | 428 | # Contributors 429 | 430 | Special thanks to: 431 | 432 | - [tatey](https://github.com/tatey) 433 | - [techpeace](https://github.com/techpeace) 434 | - [jcrate](https://github.com/jcrate) 435 | - [langsharpe](https://github.com/langsharpe) 436 | - [thechrisoshow](https://github.com/thechrisoshow) 437 | - [feolea](https://github.com/feolea) 438 | - [jefferal1995](https://github.com/jefferal1995) 439 | - [panozzaj](https://github.com/panozzaj) 440 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'bundler/gem_tasks' 8 | -------------------------------------------------------------------------------- /frontapp.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib", __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "frontapp/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'frontapp' 7 | s.version = Frontapp::VERSION 8 | s.date = '2018-10-15' 9 | s.summary = "Ruby client for Frontapp API" 10 | s.description = "Ruby client for Frontapp API" 11 | s.authors = ["Niels van der Zanden"] 12 | s.email = 'niels@phusion.nl' 13 | s.files = ["lib/frontapp.rb", "lib/frontapp/client.rb", "lib/frontapp/error.rb", "lib/frontapp/version.rb"] 14 | s.files += Dir.glob("lib/frontapp/client/*.rb") 15 | s.files += Dir.glob("lib/frontapp/utils/*.rb") 16 | s.homepage = 'https://github.com/phusion/frontapp' 17 | s.license = 'MIT' 18 | s.add_dependency 'http', '>= 2.2.1' 19 | s.add_development_dependency 'rspec' 20 | s.add_development_dependency 'webmock' 21 | s.add_development_dependency 'rake', '~> 12.0' 22 | end 23 | -------------------------------------------------------------------------------- /lib/frontapp.rb: -------------------------------------------------------------------------------- 1 | require_relative 'frontapp/utils/hash.rb' 2 | require_relative 'frontapp/client.rb' 3 | require 'openssl' 4 | require 'http' 5 | require 'json' 6 | -------------------------------------------------------------------------------- /lib/frontapp/client.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'http' 3 | require 'json' 4 | require_relative 'client/attachments.rb' 5 | require_relative 'client/channels.rb' 6 | require_relative 'client/comments.rb' 7 | require_relative 'client/contact_groups.rb' 8 | require_relative 'client/contacts.rb' 9 | require_relative 'client/conversations.rb' 10 | require_relative 'client/drafts.rb' 11 | require_relative 'client/events.rb' 12 | require_relative 'client/inboxes.rb' 13 | require_relative 'client/messages.rb' 14 | require_relative 'client/rules.rb' 15 | require_relative 'client/tags.rb' 16 | require_relative 'client/teammates.rb' 17 | require_relative 'client/teams.rb' 18 | require_relative 'client/topics.rb' 19 | require_relative 'client/links.rb' 20 | require_relative 'client/exports.rb' 21 | require_relative 'error' 22 | require_relative 'version' 23 | 24 | module Frontapp 25 | class Client 26 | 27 | include Frontapp::Client::Attachments 28 | include Frontapp::Client::Channels 29 | include Frontapp::Client::Comments 30 | include Frontapp::Client::ContactGroups 31 | include Frontapp::Client::Contacts 32 | include Frontapp::Client::Conversations 33 | include Frontapp::Client::Drafts 34 | include Frontapp::Client::Events 35 | include Frontapp::Client::Inboxes 36 | include Frontapp::Client::Messages 37 | include Frontapp::Client::Rules 38 | include Frontapp::Client::Tags 39 | include Frontapp::Client::Teammates 40 | include Frontapp::Client::Teams 41 | include Frontapp::Client::Topics 42 | include Frontapp::Client::Links 43 | include Frontapp::Client::Exports 44 | 45 | def initialize(options={}) 46 | auth_token = options[:auth_token] 47 | user_agent = options[:user_agent] || "Frontapp Ruby Gem #{VERSION}" 48 | @headers = HTTP.headers({ 49 | Accept: "application/json", 50 | Authorization: "Bearer #{auth_token}", 51 | "User-Agent": user_agent 52 | }) 53 | end 54 | 55 | def list(path, params = {}) 56 | items = [] 57 | last_page = false 58 | query = format_query(params) 59 | url = "#{base_url}#{path}?#{query}" 60 | until last_page 61 | res = @headers.get(url) 62 | if !res.status.success? 63 | raise Error.from_response(res) 64 | end 65 | response = JSON.parse(res.to_s) 66 | 67 | if block_given? 68 | yield(response["_results"]) 69 | else 70 | items.concat(response["_results"]) if response["_results"] 71 | end 72 | 73 | pagination = response["_pagination"] 74 | if pagination.nil? || pagination["next"].nil? 75 | last_page = true 76 | else 77 | url = pagination["next"] 78 | end 79 | end 80 | items 81 | end 82 | 83 | def get(path) 84 | res = @headers.get("#{base_url}#{path}") 85 | if !res.status.success? 86 | raise Error.from_response(res) 87 | end 88 | JSON.parse(res.to_s) 89 | end 90 | 91 | def get_plain(path) 92 | headers_copy = @headers.dup 93 | res = @headers.accept("text/plain").get("#{base_url}#{path}") 94 | if !res.status.success? 95 | raise Error.from_response(res) 96 | end 97 | res.to_s 98 | end 99 | 100 | def get_raw(path) 101 | headers_copy = @headers.dup 102 | res = @headers.get("#{base_url}#{path}") 103 | if !res.status.success? 104 | raise Error.from_response(res) 105 | end 106 | res 107 | end 108 | 109 | def create(path, body) 110 | res = @headers.post("#{base_url}#{path}", json: body) 111 | response = JSON.parse(res.to_s) 112 | if !res.status.success? 113 | raise Error.from_response(res) 114 | end 115 | response 116 | end 117 | 118 | def create_without_response(path, body) 119 | res = @headers.post("#{base_url}#{path}", json: body) 120 | if !res.status.success? 121 | raise Error.from_response(res) 122 | end 123 | end 124 | 125 | def update(path, body) 126 | res = @headers.patch("#{base_url}#{path}", json: body) 127 | if !res.status.success? 128 | raise Error.from_response(res) 129 | end 130 | end 131 | 132 | def delete(path, body = {}) 133 | res = @headers.delete("#{base_url}#{path}", json: body) 134 | if !res.status.success? 135 | raise Error.from_response(res) 136 | end 137 | end 138 | 139 | private 140 | def format_query(params) 141 | res = [] 142 | q = params.delete(:q) 143 | if q && q.is_a?(Hash) 144 | res << q.map do |k, v| 145 | case v 146 | when Symbol, String 147 | "q[#{k}]=#{URI.encode_uri_component(v)}" 148 | when Array then 149 | v.map { |c| "q[#{k}][]=#{URI.encode_uri_component(c.to_s)}" }.join("&") 150 | else 151 | "q[#{k}]=#{URI.encode_uri_component(v.to_s)}" 152 | end 153 | end 154 | end 155 | res << params.map {|k,v| "#{k}=#{URI.encode_uri_component(v.to_s)}"} 156 | res.join("&") 157 | end 158 | 159 | def base_url 160 | "https://api2.frontapp.com/" 161 | end 162 | 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/frontapp/client/attachments.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Attachments 4 | 5 | # Parameters 6 | # Name Type Description 7 | # ---------------------------------------------------------- 8 | # attachment_link_id string Id of the requested attachment 9 | # ---------------------------------------------------------- 10 | def download_attachment(attachment_link_id) 11 | get_raw("download/#{attachment_link_id}") 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/frontapp/client/channels.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Channels 4 | 5 | def channels(params = {}) 6 | list("channels", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ------------------------------- 12 | # channel_id string Id of the requested channel 13 | # ------------------------------- 14 | def get_channel(channel_id) 15 | get("channels/#{channel_id}") 16 | end 17 | 18 | # Only custom channels can be updated through the api! 19 | # 20 | # Parameters 21 | # Name Type Description 22 | # ------------------------------- 23 | # channel_id string Id of the requested channel 24 | # ------------------------------- 25 | # 26 | # Allowed attributes: 27 | # Name Type Description 28 | # --------------------------------------------------- 29 | # settings object 30 | # settings.webhook_url string (optional) custom type only. URL to which will be sent the replies of a custom message. 31 | # --------------------------------------------------- 32 | def update_channel!(channel_id, params = {}) 33 | cleaned = params.permit({ settings: [:webhook_url] }) 34 | update("channels/#{channel_id}", cleaned) 35 | end 36 | 37 | # Only custom channels can be created through the api! 38 | # 39 | # Parameters 40 | # Name Type Description 41 | # ----------------------------- 42 | # inbox_id string Id of the inbox into which the channel messages will go 43 | # ----------------------------- 44 | # 45 | # Allowed attributes: 46 | # Name Type Description 47 | # ---------------------------------------------------- 48 | # type enum Type of the channel. 49 | # settings object 50 | # settings.webhook_url string (optional) custom type only. URL to which will be sent the replies of a custom message. 51 | # ---------------------------------------------------- 52 | def create_channel!(inbox_id, params = {}) 53 | cleaned = params.permit(:type, { settings: [:webhook_url] }) 54 | create("inboxes/#{inbox_id}/channels", cleaned) 55 | end 56 | 57 | # Parameters 58 | # Name Type Description 59 | # ------------------------------- 60 | # channel_id string Id of the requested channel 61 | # ------------------------------- 62 | def get_channel_inbox(channel_id) 63 | get("channels/#{channel_id}/inbox") 64 | end 65 | 66 | end 67 | end 68 | end -------------------------------------------------------------------------------- /lib/frontapp/client/comments.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Comments 4 | 5 | # Parameters 6 | # Name Type Description 7 | # ------------------------------------ 8 | # conversation_id string Id of the requested conversation 9 | # ------------------------------------ 10 | # 11 | # Allowed attributes: 12 | # Name Type Description 13 | # ------------------------------ 14 | # author_id string Id of the teammate creating the comment 15 | # body string Content of the comment 16 | # ------------------------------ 17 | def create_comment!(conversation_id, params = {}) 18 | cleaned = params.permit(:author_id, :body) 19 | create("conversations/#{conversation_id}/comments", cleaned) 20 | end 21 | 22 | # Parameters 23 | # Name Type Description 24 | # ------------------------------------ 25 | # conversation_id string Id of the requested conversation 26 | # ------------------------------------ 27 | def get_conversation_comments(conversation_id) 28 | list("conversations/#{conversation_id}/comments") 29 | end 30 | 31 | # Parameters 32 | # Name Type Description 33 | # ------------------------------- 34 | # comment_id string Id of the requested comment 35 | # ------------------------------- 36 | def get_comment(comment_id) 37 | get("comments/#{comment_id}") 38 | end 39 | 40 | # Parameters 41 | # Name Type Description 42 | # ------------------------------- 43 | # comment_id string Id of the requested comment 44 | # ------------------------------- 45 | def get_comment_mentions(comment_id) 46 | get("comments/#{comment_id}/mentions") 47 | end 48 | 49 | end 50 | end 51 | end -------------------------------------------------------------------------------- /lib/frontapp/client/contact_groups.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module ContactGroups 4 | 5 | def contact_groups(params = {}) 6 | list("contact_groups", params) 7 | end 8 | 9 | # Allowed attributes: 10 | # Name Type Description 11 | # ------------------------------------------- 12 | # name string Name of the group 13 | # ------------------------------------------- 14 | def create_contact_group!(params = {}) 15 | cleaned = params.permit(:name) 16 | create("contact_groups", cleaned) 17 | end 18 | 19 | # Parameters 20 | # Name Type Description 21 | # ------------------------------- 22 | # group_id string Id of the requested group 23 | # ------------------------------- 24 | def delete_contact_group!(group_id) 25 | delete("contact_groups/#{group_id}") 26 | end 27 | 28 | 29 | # Parameters 30 | # Name Type Description 31 | # ------------------------------- 32 | # group_id string Id of the requested group 33 | # ------------------------------- 34 | def get_contact_group_contacts(group_id) 35 | get("contact_groups/#{group_id}/contacts") 36 | end 37 | 38 | # Parameters 39 | # Name Type Description 40 | # ------------------------------- 41 | # group_id string Id of the requested group 42 | # ------------------------------- 43 | # 44 | # Allowed attributes: 45 | # Name Type Description 46 | # -------------------------------------------- 47 | # contact_ids array List of ids or aliases of the contacts to add in the requested group 48 | # -------------------------------------------- 49 | def add_contacts_to_contact_group!(group_id, params = {}) 50 | cleaned = params.permit(:contact_ids) 51 | create_without_response("contact_groups/#{group_id}/contacts", cleaned) 52 | end 53 | end 54 | end 55 | end -------------------------------------------------------------------------------- /lib/frontapp/client/contacts.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Contacts 4 | 5 | def contacts(params = {}) 6 | list("contacts", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ------------------------------- 12 | # contact_id string Id or alias of the requested contact 13 | # ------------------------------- 14 | def get_contact(contact_id) 15 | get("contacts/#{contact_id}") 16 | end 17 | 18 | # Parameters 19 | # Name Type Description 20 | # ------------------------------- 21 | # contact_id string Id or alias of the requested contact 22 | # ------------------------------- 23 | # 24 | # Allowed attributes: 25 | # Name Type Description 26 | # -------------------------------------------- 27 | # name string (optional) Contact name 28 | # description string (optional) Contact description 29 | # avatar_url string (optional) URL of the contact’s avatar 30 | # is_spammer boolean (optional) Whether or not the contact is marked as a spammer 31 | # links array (optional) List of all the links of the contact 32 | # group_names array (optional) List of all the group names the contact belongs to. It will automatically create missing groups. 33 | # custom_fields object (optional) Custom field attributes for this contact. Leave empty if you do not wish to update the attributes. Not sending existing attributes will automatically remove them. 34 | # -------------------------------------------- 35 | def update_contact!(contact_id, params = {}) 36 | cleaned = params.permit(:name, 37 | :description, 38 | :avatar_url, 39 | :is_spammer, 40 | :links, 41 | :group_names, 42 | :custom_fields) 43 | update("contacts/#{contact_id}", cleaned) 44 | end 45 | 46 | # Allowed attributes: 47 | # Name Type Description 48 | # -------------------------------------------- 49 | # name string (optional) Contact name 50 | # description string (optional) Contact description 51 | # avatar_url string (optional) URL of the contact’s avatar 52 | # is_spammer boolean (optional) Whether or not the contact is marked as a spammer 53 | # links array (optional) List of all the links of the contact 54 | # group_names array (optional) List of all the group names the contact belongs to. It will automatically create missing groups. 55 | # handles array List of the contact handles 56 | # custom_fields object (optional) Custom field attributes for this contact. Leave empty if you do not wish to update the attributes. Not sending existing attributes will automatically remove them. 57 | # -------------------------------------------- 58 | def create_contact!(params = {}) 59 | cleaned = params.permit(:name, 60 | :description, 61 | :avatar_url, 62 | :is_spammer, 63 | :links, 64 | :group_names, 65 | :handles, 66 | :custom_fields) 67 | create("contacts", cleaned) 68 | end 69 | 70 | # Name Type Description 71 | # ------------------------------- 72 | # contact_id string Id or alias of the requested contact 73 | # ------------------------------- 74 | def delete_contact!(contact_id) 75 | delete("contacts/#{contact_id}") 76 | end 77 | 78 | # Parameters 79 | # Name Type Description 80 | # ------------------------------- 81 | # contact_id string Id or alias of the requested contact 82 | # ------------------------------- 83 | # 84 | # Allowed params: 85 | # Name Type Description 86 | # ---------------------------------------------- 87 | # q object (optional) Search query. 88 | # q.statuses array (optional) List of the statuses of the conversations you want to list 89 | # ---------------------------------------------- 90 | def get_contact_conversations(contact_id, params = {}) 91 | cleaned = params.permit({ q: [:statuses] }) 92 | list("contacts/#{contact_id}/conversations", cleaned) 93 | end 94 | 95 | # Parameters 96 | # Name Type Description 97 | # ------------------------------- 98 | # contact_id string Id or alias of the requested contact 99 | # ------------------------------- 100 | # 101 | # Allowed attributes: 102 | # Name Type Description 103 | # --------------------------- 104 | # handle string Handle used to reach the contact. Can be an email address, a twitter, handle, a phone number, … 105 | # source enum Can be 'twitter’, 'email’ or 'phone’. 106 | # --------------------------- 107 | def add_contact_handle!(contact_id, params = {}) 108 | cleaned = params.permit(:handle, :source) 109 | create_without_response("contacts/#{contact_id}/handles", cleaned) 110 | end 111 | 112 | # Parameters 113 | # Name Type Description 114 | # ------------------------------- 115 | # contact_id string Id or alias of the requested contact 116 | # ------------------------------- 117 | # 118 | # Allowed attributes: 119 | # Name Type Description 120 | # --------------------------------------- 121 | # handle string Handle used to reach the contact. Can be an email address, a twitter, handle, a phone number, … 122 | # source enum Can be 'twitter’, 'email’ or 'phone’. 123 | # force boolean (optional) Force the deletetion of the contact if the handle is the last one 124 | # --------------------------------------- 125 | def delete_contact_handle!(contact_id, params = {}) 126 | cleaned = params.permit(:handle, :source, :force) 127 | delete("contacts/#{contact_id}/handles", cleaned) 128 | end 129 | 130 | # Parameters 131 | # Name Type Description 132 | # ------------------------------- 133 | # contact_id string Id or alias of the requested contact 134 | # ------------------------------- 135 | def get_contact_notes(contact_id) 136 | list("contacts/#{contact_id}/notes") 137 | end 138 | 139 | # Parameters 140 | # Name Type Description 141 | # ------------------------------- 142 | # contact_id string Id or alias of the requested contact 143 | # ------------------------------- 144 | # 145 | # Allowed attributes: 146 | # Name Type Description 147 | # ------------------------------ 148 | # author_id string Id of the teammate creating the note 149 | # body string Content of the note 150 | # ------------------------------ 151 | def add_contact_note!(contact_id, params = {}) 152 | cleaned = params.permit(:author_id, :body) 153 | create("contacts/#{contact_id}/notes", cleaned) 154 | end 155 | 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/frontapp/client/conversations.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Conversations 4 | 5 | def conversations(params = {}, &block) 6 | list("conversations", params, &block) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ------------------------------------ 12 | # conversation_id string Id of the requested conversation 13 | # ------------------------------------ 14 | def get_conversation(conversation_id) 15 | get("conversations/#{conversation_id}") 16 | end 17 | 18 | # Parameters 19 | # Name Type Description 20 | # ------------------------------------ 21 | # conversation_id string Id of the requested conversation 22 | # ------------------------------------ 23 | # 24 | # Allowed attributes: 25 | # Name Type Description 26 | # --------------------------------------------- 27 | # assignee_id string (optional) ID of the teammate to assign the conversation to. Set it to null to unassign. 28 | # inbox_id string (optional) ID of the inbox to move the conversation to. 29 | # status enum (optional) New status of the conversation 30 | # tags array (optional) List of all the tag names replacing the old conversation tags 31 | # --------------------------------------------- 32 | # The assignee id is their Frontapp handle, e.g. @username 33 | def update_conversation!(conversation_id, params = {}) 34 | cleaned = params.permit(:assignee_id, :inbox_id, :status, :tags) 35 | update("conversations/#{conversation_id}", cleaned) 36 | end 37 | 38 | # Parameters 39 | # Name Type Description 40 | # -------------------------------- 41 | # conversation_id string Id or email of the requested conversation 42 | # -------------------------------- 43 | def get_conversation_inboxes(conversation_id) 44 | get("conversations/#{conversation_id}/inboxes") 45 | end 46 | 47 | # Parameters 48 | # Name Type Description 49 | # -------------------------------- 50 | # conversation_id string Id or email of the requested conversation 51 | # -------------------------------- 52 | def get_conversation_followers(conversation_id) 53 | get("conversations/#{conversation_id}/followers") 54 | end 55 | 56 | # Parameters 57 | # Name Type Description 58 | # -------------------------------- 59 | # conversation_id string Id or email of the requested conversation 60 | # -------------------------------- 61 | def get_conversation_events(conversation_id) 62 | list("conversations/#{conversation_id}/events") 63 | end 64 | 65 | # Parameters 66 | # Name Type Description 67 | # -------------------------------- 68 | # conversation_id string Id or email of the requested conversation 69 | # -------------------------------- 70 | def get_conversation_messages(conversation_id) 71 | list("conversations/#{conversation_id}/messages") 72 | end 73 | 74 | # Parameters 75 | # Name Type Description 76 | # ---------------------------------------------- 77 | # conversation_id string The conversation Id 78 | # link_ids array (optional) Link IDs to add Either link_ids or link_external_urls must be specified but not both 79 | # link_external_urls array (optional) Link external urls to add. Creates links if necessary. Either link_ids or link_external_urls must be specified but not both 80 | # ---------------------------------------------- 81 | def add_conversation_links!(conversation_id, params = {}) 82 | cleaned = params.permit(:link_ids, :link_external_urls) 83 | create_without_response("conversations/#{conversation_id}/links", cleaned) 84 | end 85 | 86 | # Parameters 87 | # Name Type Description 88 | # ---------------------------------------------- 89 | # conversation_id string The conversation Id 90 | # link_ids array of strings link IDs to remove 91 | # ---------------------------------------------- 92 | def remove_conversation_links!(conversation_id, params = {}) 93 | cleaned = params.permit(:link_ids) 94 | delete("conversations/#{conversation_id}/links", cleaned) 95 | end 96 | 97 | # Parameters 98 | # Name Type Description 99 | # ---------------------------------------------- 100 | # conversation_id string The conversation Id 101 | # teammate_ids array of strings follower IDs to remove 102 | # ---------------------------------------------- 103 | def remove_conversation_followers!(conversation_id, params = {}) 104 | cleaned = params.permit(:teammate_ids) 105 | delete("conversations/#{conversation_id}/followers", cleaned) 106 | end 107 | 108 | # Parameters 109 | # Name Type Description 110 | # ---------------------------------------------- 111 | # conversation_id string The conversation Id 112 | # teammate_ids array of strings follower IDs to add 113 | # ---------------------------------------------- 114 | def add_conversation_followers!(conversation_id, params = {}) 115 | cleaned = params.permit(:teammate_ids) 116 | create_without_response("conversations/#{conversation_id}/followers", cleaned) 117 | end 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/frontapp/client/drafts.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Drafts 4 | # See https://dev.frontapp.com/reference/create-draft-reply 5 | def create_draft_reply(conversation_id, params) 6 | # ordered in the same order as the API documentation 7 | cleaned_params = params.permit( 8 | :author_id, 9 | :to, 10 | :cc, 11 | :bcc, 12 | :subject, 13 | :body, 14 | :quote_body, 15 | :attachments, 16 | :mode, 17 | :signature_id, 18 | :should_add_default_signature, 19 | :channel_id, 20 | ) 21 | create("conversations/#{conversation_id}/drafts", cleaned_params) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/frontapp/client/events.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Events 4 | 5 | def events(params = {}) 6 | list("events", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ----------------------------- 12 | # event_id string Id of the requested event 13 | # ----------------------------- 14 | def get_event(event_id) 15 | get("events/#{event_id}") 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/frontapp/client/exports.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Exports 4 | 5 | def exports(params = {}) 6 | list("exports", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ------------------------------ 12 | # export_id string ID of the requested export 13 | # ------------------------------ 14 | def get_export(export_id) 15 | get("exports/#{export_id}") 16 | end 17 | 18 | # Allowed attributes: 19 | # Name Type Description 20 | # ------------------------- 21 | # inbox_id string (optional) ID of the inbox to export the analytics for. If omitted, the export will contain all the inboxes. 22 | # tag_id string (optional) ID the tag to export the analytics for. If omitted, the export will contain all the tags. 23 | # start number Start time of the data to include in the export. 24 | # end number End time of the data to include in the export. 25 | # timezone string (optional) Name of the timezone to format the dates. If omitted, the export will use UTC. 26 | # should_export_events boolean (optional) Whether to export all the events or only messages. Default to false. 27 | # ------------------------- 28 | def create_export!(params = {}) 29 | cleaned = params.permit(:inbox_id, 30 | :tag_id, 31 | :start, 32 | :end, 33 | :timezone, 34 | :should_export_events) 35 | create("exports", cleaned) 36 | end 37 | 38 | # Allowed attributes: 39 | # Name Type Description 40 | # ------------------------- 41 | # inbox_id string (optional) ID of the inbox to export the analytics for. If omitted, the export will contain all the inboxes. 42 | # tag_id string (optional) ID the tag to export the analytics for. If omitted, the export will contain all the tags. 43 | # start number Start time of the data to include in the export. 44 | # end number End time of the data to include in the export. 45 | # timezone string (optional) Name of the timezone to format the dates. If omitted, the export will use UTC. 46 | # should_export_events boolean (optional) Whether to export all the events or only messages. Default to false. 47 | # ------------------------- 48 | def create_export_for_team!(team_id, params = {}) 49 | cleaned = params.permit(:inbox_id, 50 | :tag_id, 51 | :start, 52 | :end, 53 | :timezone, 54 | :should_export_events) 55 | create("teams/#{team_id}/exports", cleaned) 56 | end 57 | 58 | end 59 | end 60 | end -------------------------------------------------------------------------------- /lib/frontapp/client/inboxes.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Inboxes 4 | 5 | def inboxes(params = {}) 6 | list("inboxes", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ----------------------------- 12 | # inbox_id string Id of the requested inbox 13 | # ----------------------------- 14 | def get_inbox(inbox_id) 15 | get("inboxes/#{inbox_id}") 16 | end 17 | 18 | # Allowed attributes: 19 | # Name Type Description 20 | # ------------------------------------------- 21 | # name string Name of the inbox 22 | # teammate_ids array (optional) List of all the teammate ids who will have access to this inbox. If omitted, it will automatically select all the administrators in your company. 23 | # ------------------------------------------- 24 | def create_inbox!(params = {}) 25 | cleaned = params.permit(:name, :teammate_ids) 26 | create("inboxes", cleaned) 27 | end 28 | 29 | # Parameters 30 | # Name Type Description 31 | # ----------------------------- 32 | # inbox_id string Id of the requested inbox 33 | # ----------------------------- 34 | def get_inbox_channels(inbox_id) 35 | get("inboxes/#{inbox_id}/channels") 36 | end 37 | 38 | # Parameters 39 | # Name Type Description 40 | # ----------------------------- 41 | # inbox_id string Id of the requested inbox 42 | # ----------------------------- 43 | # 44 | # Allowed attributes: 45 | # Name Type Description 46 | # ---------------------------------------------- 47 | # q object (optional) Search query. 48 | # q.statuses array (optional) List of the statuses of the conversations you want to list 49 | # ---------------------------------------------- 50 | def get_inbox_conversations(inbox_id, params = {}) 51 | cleaned = params.permit({ q: [:statuses] }) 52 | list("inboxes/#{inbox_id}/conversations", cleaned) 53 | end 54 | 55 | # Parameters 56 | # Name Type Description 57 | # ----------------------------- 58 | # inbox_id string Id of the requested inbox 59 | # ----------------------------- 60 | def get_inbox_teammates(inbox_id) 61 | get("inboxes/#{inbox_id}/teammates") 62 | end 63 | 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /lib/frontapp/client/links.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Links 4 | 5 | def links(params = {}) 6 | cleaned = params.permit({ q: [:statuses] }) 7 | list("links", cleaned) 8 | end 9 | 10 | # Parameters 11 | # Name Type Description 12 | # ------------------------------- 13 | # link_id string Id of the requested link 14 | # ------------------------------- 15 | def get_link(link_id) 16 | get("links/#{link_id}") 17 | end 18 | 19 | # Allowed attributes: 20 | # Name Type Description 21 | # ------------------------------------ 22 | # name string (optional) Name of the link. If none is specified, the external_url is used as a default 23 | # external_url string Underlying identifying link/url of the link 24 | # ------------------------------------ 25 | def create_link!(params = {}) 26 | cleaned = params.permit(:name, :external_url) 27 | create("links", cleaned) 28 | end 29 | 30 | # Parameters 31 | # Name Type Description 32 | # ----------------------------- 33 | # link_id string Id of the requested link 34 | # ----------------------------- 35 | # 36 | # Allowed attributes: 37 | # Name Type Description 38 | # ------------------------------------ 39 | # name string New name of the link 40 | # ------------------------------------ 41 | def update_link!(link_id, params = {}) 42 | cleaned = params.permit(:name) 43 | update("links/#{link_id}", cleaned) 44 | end 45 | 46 | # Parameters 47 | # Name Type Description 48 | # ----------------------------- 49 | # link_id string Id of the requested link 50 | # ----------------------------- 51 | # 52 | # Allowed params: 53 | # Name Type Description 54 | # ------------------------------------------ 55 | # q object (optional) Search query. 56 | # q.statuses array (optional) List of the statuses of the conversations you want to list 57 | # ------------------------------------------ 58 | def get_link_conversations(link_id, params = {}) 59 | cleaned = params.permit({ q: [:statuses] }) 60 | list("links/#{link_id}/conversations", cleaned) 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/frontapp/client/messages.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Messages 4 | 5 | # Parameters 6 | # Name Type Description 7 | # ------------------------------- 8 | # message_id string Id of the requested message 9 | # ------------------------------- 10 | def get_message(message_id) 11 | get("messages/#{message_id}") 12 | end 13 | 14 | # Parameters 15 | # Name Type Description 16 | # ------------------------------- 17 | # message_id string Id of the requested message 18 | # ------------------------------- 19 | def get_message_source(message_id) 20 | get_plain("messages/#{message_id}").b 21 | # .b sets encoding to ASCII-8BIT, which is safer for raw emails than UTF-8 22 | end 23 | 24 | # Parameters 25 | # Name Type Description 26 | # ------------------------------- 27 | # channel_id string Id or address of the channel from which to send the message 28 | # ------------------------------- 29 | # 30 | # Allowed attributes: 31 | # Name Type Description 32 | # ------------------------------------------------ 33 | # author_id string (optional) Id of the teammate on behalf of whom the answer is sent 34 | # sender_name string (optional) Name used for the sender info of the message 35 | # subject string (optional) Subject of the message for email message 36 | # body string Body of the message 37 | # text string (optional) Text version of the body for messages with non-text body 38 | # attachments array (optional) Binary data of the attached files. Available only for multipart request. 39 | # options object (optional) Sending options 40 | # options.tags array (optional) List of tag names to add to the conversation (unknown tags will automatically be created) 41 | # options.archive boolean (optional) Archive the conversation right when sending the reply (Default: true) 42 | # to array List of the recipient handles who will receive this message 43 | # cc array (optional) List of the recipient handles who will receive a copy of this message 44 | # bcc array (optional) List of the recipient handles who will receive a blind copy of this message 45 | # ------------------------------------------------ 46 | def send_message(channel_id, params) 47 | cleaned = params.permit(:author_id, 48 | :sender_name, 49 | :subject, 50 | :body, 51 | :text, 52 | :attachments, 53 | { options: [:tags, :archive] }, 54 | :to, 55 | :cc, 56 | :bcc) 57 | create("channels/#{channel_id}/messages", cleaned) 58 | end 59 | 60 | # Parameters 61 | # Name Type Description 62 | # ------------------------------------ 63 | # conversation_id string Id of the conversation 64 | # ------------------------------------ 65 | # 66 | # Allowed attributes: 67 | # Name Type Description 68 | # ------------------------------------------------ 69 | # author_id string (optional) ID of the teammate on behalf of whom the answer is sent 70 | # sender_name string (optional) Name used for the sender info of the message 71 | # subject string (optional) Subject of the message for email message 72 | # body string Body of the message 73 | # text string (optional) Text version of the body for messages with non-text body 74 | # attachments array (optional) Binary data of the attached files. Available only for multipart request. 75 | # options object (optional) Sending options 76 | # options.tags array (optional) List of tag names to add to the conversation (unknown tags will automatically be created) 77 | # options.archive boolean (optional) Archive the conversation right when sending the reply (Default: true) 78 | # channel_id string (optional) Channel through which to send the message. Defaults to the original conversation channel. For imported messages or messages received on multiple channels, you MUST specify a channel ID. 79 | # to array (optional) List of the recipient handles who will receive this message. By default it will use the recipients of the last received message. 80 | # cc array (optional) List of the recipient handles who will receive a copy of this message. By default it will use the cc'ed recipients of the last received message. 81 | # bcc array (optional) List of the recipient handles who will receive a blind copy of this message 82 | # ------------------------------------------------ 83 | def send_reply(conversation_id, params) 84 | cleaned = params.permit(:author_id, 85 | :sender_name, 86 | :subject, 87 | :body, 88 | :text, 89 | :attachments, 90 | { options: [:tags, :archive] }, 91 | :channel_id, 92 | :to, 93 | :cc, 94 | :bcc) 95 | create_without_response("conversations/#{conversation_id}/messages", cleaned) 96 | end 97 | 98 | # Parameters 99 | # Name Type Description 100 | # ------------------------------------ 101 | # channel_id string Id of the requested custom channel 102 | # ------------------------------------ 103 | # 104 | # Allowed attributes: 105 | # Name Type Description 106 | # ---------------------------------------------------- 107 | # sender object Data of the sender 108 | # sender.contact_id string (optional) ID of the contact in Front corresponding to the sender 109 | # sender.name string (optional) Name of the sender 110 | # sender.handle string Handle of the sender. It can be any string used to uniquely identify the sender 111 | # subject string (optional) Subject of the message 112 | # body string Body of the message 113 | # body_format enum (optional) Format of the message body. (Default: 'markdown') 114 | # attachments array (optional) Binary data of the attached files. Available only for multipart request. 115 | # metadata object (optional) 116 | # metadata.thread_ref string (optional) Custom reference which will be used to thread messages. If you ommit this field, we’ll thread by sender instead 117 | # metadata.headers object (optional) Custom object where any internal information can be stored 118 | # ---------------------------------------------------- 119 | def receive_custom_message(channel_id, params) 120 | cleaned = params.permit({ sender: [:contact_id, :name, :handle] }, 121 | :subject, 122 | :body, 123 | :body_format, 124 | :attachments, 125 | { metadata: [:thread_ref, :headers] }) 126 | create("channels/#{channel_id}/incoming_messages", cleaned) 127 | end 128 | 129 | # Parameters 130 | # Name Type Description 131 | # ----------------------------- 132 | # inbox_id string Id of the inbox into which the message should be append. 133 | # ----------------------------- 134 | # 135 | # Allowed attributes: 136 | # Name Type Description 137 | # ----------------------------------------------------------- 138 | # sender object 139 | # sender.handle string Handle used to reach the contact. Can be an email address, a twitter, handle, a phone number, … 140 | # sender.name string (optional) Name of the contact. 141 | # sender.author_id string (optional) Id of the teammate who is the author of the message. Ignored if the message is inbound. 142 | # to array List of recipient handles who received the message. 143 | # cc array (optional) List of recipient handles who received a copy of the message. 144 | # bcc array (optional) List of the recipeient handles who received a blind copy of the message. 145 | # subject string (optional) Subject of the message. 146 | # body string Body of the message. 147 | # body_format enum (optional) Format of the message body. Ignored if the message type is not email. (Default: 'markdown') 148 | # attachments array (optional) Binary data of the attached files. Available only for multipart request. 149 | # external_id string External identifier of the message. Front won’t import two messages with the same external ID. 150 | # created_at number Date at which the message as been sent or received. 151 | # type enum (optional) Type of the message to import. (Default: 'email') 152 | # assignee_id string (optional) Id of the teammate who will be assigned to the conversation. 153 | # tags array (optional) List of tag names to add to the conversation (unknown tags will automatically be created). 154 | # metadata object 155 | # metadata.thread_ref string (optional) Custom reference which will be used to thread messages. If you ommit this field, we’ll thread by sender instead. 156 | # metadata.is_inbound boolean Whether or not the message is received (inbound) or sent (outbound) by you. 157 | # metadata.is_archived boolean (optional) Whether or not the message should be directly archived once imported. (Default: true) 158 | # metadata.should_skip_rules boolean (optional) Whether or not the rules should apply to this message. (Default: true) 159 | # ----------------------------------------------------------- 160 | def import_message(inbox_id, params) 161 | cleaned = params.permit({ sender: [:handle, :name, :author_id] }, 162 | :to, 163 | :cc, 164 | :bcc, 165 | :subject, 166 | :body, 167 | :body_format, 168 | :attachments, 169 | :external_id, 170 | :created_at, 171 | :type, 172 | :assignee_id, 173 | :tags, 174 | { metadata: [:thread_ref, :is_inbound, :is_archived, :should_skip_rules] }) 175 | create("inboxes/#{inbox_id}/imported_messages", cleaned) 176 | end 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /lib/frontapp/client/rules.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Rules 4 | 5 | def rules(params = {}) 6 | list("rules", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # ---------------------------- 12 | # rule_id string Id of the requested rule 13 | # ---------------------------- 14 | def get_rule(rule_id) 15 | get("rules/#{rule_id}") 16 | end 17 | 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/frontapp/client/tags.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Tags 4 | 5 | def tags(params = {}) 6 | list("tags", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # --------------------------- 12 | # tag_id string Id of the requested tag 13 | # --------------------------- 14 | def get_tag(tag_id) 15 | get("tags/#{tag_id}") 16 | end 17 | 18 | # Allowed attributes: 19 | # Name Type Description 20 | # ------------------------- 21 | # name string Name of the tag to create 22 | # ------------------------- 23 | def create_tag!(params = {}) 24 | cleaned = params.permit(:name) 25 | create("tags", cleaned) 26 | end 27 | 28 | # Parameters 29 | # Name Type Description 30 | # --------------------------- 31 | # tag_id string Id of the requested tag 32 | # --------------------------- 33 | # 34 | # Allowed params: 35 | # Name Type Description 36 | # ---------------------------------------------- 37 | # q object (optional) Search query. 38 | # q.statuses array (optional) List of the statuses of the conversations you want to list 39 | # ---------------------------------------------- 40 | def get_tag_conversations(tag_id, params = {}) 41 | cleaned = params.permit({ q: [:statuses] }) 42 | list("tags/#{tag_id}/conversations", cleaned) 43 | end 44 | 45 | # Parameters 46 | # Name Type Description 47 | # --------------------------- 48 | # tag_id string Id of the requested tag 49 | # --------------------------- 50 | def delete_tag!(tag_id) 51 | delete("tags/#{tag_id}") 52 | end 53 | 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /lib/frontapp/client/teammates.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Teammates 4 | 5 | def teammates(params = {}) 6 | list("teammates", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # -------------------------------- 12 | # teammate_id string Id or email of the requested teammate 13 | # -------------------------------- 14 | def get_teammate(teammate_id) 15 | get("teammates/#{teammate_id}") 16 | end 17 | 18 | # Parameters 19 | # Name Type Description 20 | # -------------------------------- 21 | # teammate_id string Id or email of the requested teammate 22 | # -------------------------------- 23 | # 24 | # Allowed attributes: 25 | # Name Type Description 26 | # ---------------------------------------------- 27 | # username string (optional) New username. It must be unique and can only contains lowercase letters, numbers and underscores. 28 | # first_name string (optional) New first name 29 | # last_name string (optional) New last name 30 | # is_admin boolean (optional) New admin status 31 | # is_available boolean (optional) New availability status 32 | # ---------------------------------------------- 33 | def update_teammate!(teammate_id, params = {}) 34 | cleaned = params.permit(:username, :first_name, :last_name, :is_admin, :is_available) 35 | update("teammates/#{teammate_id}", cleaned) 36 | end 37 | 38 | # Parameters 39 | # Name Type Description 40 | # -------------------------------- 41 | # teammate_id string Id or email of the requested teammate 42 | # -------------------------------- 43 | # 44 | # Allowed params: 45 | # Name Type Description 46 | # ---------------------------------------------- 47 | # q object (optional) Search query. 48 | # q.statuses array (optional) List of the statuses of the conversations you want to list 49 | # ---------------------------------------------- 50 | def get_teammate_conversations(teammate_id, params = {}) 51 | cleaned = params.permit({ q: [:statuses] }) 52 | list("teammates/#{teammate_id}/conversations", cleaned) 53 | end 54 | 55 | # Parameters 56 | # Name Type Description 57 | # -------------------------------- 58 | # teammate_id string Id or email of the requested teammate 59 | # -------------------------------- 60 | def get_teammate_inboxes(teammate_id) 61 | get("teammates/#{teammate_id}/inboxes") 62 | end 63 | 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /lib/frontapp/client/teams.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Teams 4 | 5 | def teams(params = {}) 6 | list("teams", params) 7 | end 8 | 9 | # Parameters 10 | # Name Type Description 11 | # -------------------------------- 12 | # team_id string Id of the requested team 13 | # -------------------------------- 14 | def get_team(team_id) 15 | get("teams/#{team_id}") 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/frontapp/client/topics.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Client 3 | module Topics 4 | 5 | # Parameters 6 | # Name Type Description 7 | # ----------------------------- 8 | # topic_id string Id of the requested topic 9 | # ----------------------------- 10 | # 11 | # Allowed params: 12 | # Name Type Description 13 | # ------------------------------------------ 14 | # q object (optional) Search query. 15 | # q.statuses array (optional) List of the statuses of the conversations you want to list 16 | # ------------------------------------------ 17 | def get_topic_conversations(topic_id, params = {}) 18 | warn "[DEPRECATION] `Topics` is deprecated. Please use `Links` instead." 19 | 20 | get_link_conversations(topic_id, params) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/frontapp/error.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | class Error < StandardError 3 | def self.from_response(response) 4 | error_class = case response.status 5 | when 400 then BadRequestError 6 | when 401 then UnauthorizedError 7 | when 404 then NotFoundError 8 | when 409 then ConflictError 9 | when 429 then TooManyRequestsError 10 | else self 11 | end 12 | error_class.new(response) 13 | end 14 | 15 | def initialize(response) 16 | @response = response 17 | super("Response: #{response.inspect}\nBody: #{response.body}") 18 | end 19 | end 20 | 21 | class BadRequestError < Error; end 22 | class NotFoundError < Error; end 23 | class ConflictError < Error; end 24 | class UnauthorizedError < Error; end 25 | class TooManyRequestsError < Error; end 26 | end 27 | -------------------------------------------------------------------------------- /lib/frontapp/utils/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def permit(*filters) 3 | res = {} 4 | filters.flatten.each do |filter| 5 | case filter 6 | when Symbol, String 7 | res[filter] = self[filter] if self.keys.include?(filter) 8 | when Hash then 9 | key = filter.keys.first 10 | res[key] = self[key].permit(filter[key]) if self.keys.include?(key) 11 | end 12 | end 13 | res 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/frontapp/version.rb: -------------------------------------------------------------------------------- 1 | module Frontapp 2 | VERSION = "0.0.13" 3 | end 4 | -------------------------------------------------------------------------------- /spec/attachments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Attachments' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:attachment_link_id) { "fil_55c8c149" } 14 | let(:download_attachment_response) { 15 | %Q{ 16 | { 17 | "id":"fil_55c8c149", 18 | "filename":"some-file.pdf", 19 | "url":"https://www.example.com/xyz/some-file.pdf", 20 | "content_type":"application/pdf", 21 | "size": 325214, 22 | "metadata":{ 23 | "is_inline":true, 24 | "cid":"c-xyz-123" 25 | } 26 | } 27 | } 28 | } 29 | 30 | it "can get a file attachment details" do 31 | stub_request(:get, "#{base_url}/download/#{attachment_link_id}"). 32 | with( headers: headers). 33 | to_return(status: 200, body: File.read("spec/fixtures/sample.txt")) 34 | frontapp.download_attachment(attachment_link_id) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/channels_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Channels' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:channel_id) { "cha_55c8c149" } 14 | let(:inbox_id) { "inb_55c8c149" } 15 | let(:all_channels_response) { 16 | %Q{ 17 | { 18 | "_links": { 19 | "self": "https://api2.frontapp.com/channels" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "https://api2.frontapp.com/channels/cha_55c8c149", 25 | "related": { 26 | "inbox": "https://api2.frontapp.com/channels/cha_55c8c149/inbox" 27 | } 28 | }, 29 | "id": "cha_55c8c149", 30 | "address": "team@planet-express.com", 31 | "type": "smtp", 32 | "send_as": "team@planet-express.com", 33 | "settings": {} 34 | } 35 | ] 36 | } 37 | } 38 | } 39 | let(:get_channel_response) { 40 | %Q{ 41 | { 42 | "_links": { 43 | "self": "https://api2.frontapp.com/channels/cha_55c8c149", 44 | "related": { 45 | "inbox": "https://api2.frontapp.com/channels/cha_55c8c149/inbox" 46 | } 47 | }, 48 | "id": "cha_55c8c149", 49 | "address": "team@planet-express.com", 50 | "type": "smtp", 51 | "send_as": "team@planet-express.com", 52 | "settings": {} 53 | } 54 | } 55 | } 56 | let(:create_channel_response) { 57 | %Q{ 58 | { 59 | "id": "cha_55c8c149", 60 | "type": "custom", 61 | "address": "dw0a0b7aeg36cb56", 62 | "sendAs": "dw0a0b7aeg36cb56", 63 | "settings": { 64 | "webhook_url": "http://example.com" 65 | } 66 | } 67 | } 68 | } 69 | let(:channel_inbox_response) { 70 | %Q{ 71 | { 72 | "_links": { 73 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 74 | "related": { 75 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 76 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 77 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 78 | } 79 | }, 80 | "id": "inb_55c8c149", 81 | "address": "team@planet-express.com", 82 | "type": "smtp", 83 | "name": "Team", 84 | "send_as": "team@planet-express.com" 85 | } 86 | } 87 | } 88 | 89 | it "can get all channels" do 90 | stub_request(:get, "#{base_url}/channels"). 91 | with( headers: headers). 92 | to_return(status: 200, body: all_channels_response) 93 | frontapp.channels 94 | end 95 | 96 | it "can get a specific channel" do 97 | stub_request(:get, "#{base_url}/channels/#{channel_id}"). 98 | with( headers: headers). 99 | to_return(status: 200, body: get_channel_response) 100 | frontapp.get_channel(channel_id) 101 | end 102 | 103 | it "can update a channel" do 104 | data = { settings: { webhook_url: "/fooo" } } 105 | stub_request(:patch, "#{base_url}/channels/#{channel_id}"). 106 | with( body: data.to_json, 107 | headers: headers). 108 | to_return(status: 204) 109 | frontapp.update_channel!(channel_id, data) 110 | end 111 | 112 | it "can create a channel" do 113 | data = { type: "custom", settings: { webhook_url: "/uri" } } 114 | stub_request(:post, "#{base_url}/inboxes/#{inbox_id}/channels"). 115 | with( body: data.to_json, 116 | headers: headers). 117 | to_return(status: 201, body: create_channel_response) 118 | frontapp.create_channel!(inbox_id, data) 119 | end 120 | 121 | it "can get channel inbox" do 122 | stub_request(:get, "#{base_url}/channels/#{channel_id}/inbox"). 123 | with( headers: headers). 124 | to_return(status: 200, body: channel_inbox_response) 125 | frontapp.get_channel_inbox(channel_id) 126 | end 127 | end -------------------------------------------------------------------------------- /spec/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Client' do 5 | let(:headers) { 6 | { 7 | "Accept" => "application/json", 8 | "Authorization" => "Bearer #{auth_token}", 9 | } 10 | } 11 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 12 | 13 | let(:result_1) { { "id" => "1" } } 14 | let(:result_2) { { "id" => "2" } } 15 | let(:pagination) { { "next" => "#{base_url}/conversations?page=2" } } 16 | let(:response_1) { { "_results" => [result_1], "_pagination" => pagination } } 17 | let(:response_2) { { "_results" => [result_2], "_pagination" => {} } } 18 | 19 | it "can list without a block" do 20 | stub_request(:get, "#{base_url}/conversations"). 21 | with(headers: headers). 22 | to_return(status: 200, body: JSON.generate(response_1)) 23 | stub_request(:get, "#{base_url}/conversations?page=2"). 24 | with(headers: headers). 25 | to_return(status: 200, body: JSON.generate(response_2)) 26 | results = frontapp.conversations 27 | expect(results).to eq([result_1, result_2]) 28 | end 29 | 30 | it "can list with a block" do 31 | stub_request(:get, "#{base_url}/conversations"). 32 | with(headers: headers). 33 | to_return(status: 200, body: JSON.generate(response_1)) 34 | stub_request(:get, "#{base_url}/conversations?page=2"). 35 | with(headers: headers). 36 | to_return(status: 200, body: JSON.generate(response_2)) 37 | results = [] 38 | frontapp.conversations do |conversations| 39 | results.concat(conversations) 40 | end 41 | expect(results).to eq([result_1, result_2]) 42 | end 43 | 44 | it "can list with params" do 45 | stub_request(:get, "#{base_url}/conversations?page=2"). 46 | with(headers: headers). 47 | to_return(status: 200, body: JSON.generate(response_2)) 48 | results = frontapp.conversations(page: 2) 49 | expect(results).to eq([result_2]) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/comments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Comments' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:conversation_id) { "cnv_55c8c149" } 14 | let(:comment_id) { "com_55c8c149" } 15 | let(:create_comment_response) { 16 | %Q{ 17 | { 18 | "_links": { 19 | "self": "https://api2.frontapp.com/comments/inb_55c8c149", 20 | "related": { 21 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 22 | "mentions": "https://api2.frontapp.com/comments/com_55c8c149/mentions" 23 | } 24 | }, 25 | "id": "com_55c8c149", 26 | "author": { 27 | "_links": { 28 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 29 | "related": { 30 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 31 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 32 | } 33 | }, 34 | "id": "tea_55c8c149", 35 | "email": "leela@planet-express.com", 36 | "username": "leela", 37 | "first_name": "Leela", 38 | "last_name": "Turanga", 39 | "is_admin": true, 40 | "is_available": true 41 | }, 42 | "body": "@bender, I thought you were supposed to be cooking for this party.", 43 | "posted_at": 1453770984.123 44 | } 45 | } 46 | } 47 | let(:get_conversation_comments_resonse) { 48 | %Q{ 49 | { 50 | "_links": { 51 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments" 52 | }, 53 | "_results": [ 54 | { 55 | "_links": { 56 | "self": "https://api2.frontapp.com/comments/inb_55c8c149", 57 | "related": { 58 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 59 | "mentions": "https://api2.frontapp.com/comments/com_55c8c149/mentions" 60 | } 61 | }, 62 | "id": "com_55c8c149", 63 | "author": { 64 | "_links": { 65 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 66 | "related": { 67 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 68 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 69 | } 70 | }, 71 | "id": "tea_55c8c149", 72 | "email": "leela@planet-express.com", 73 | "username": "leela", 74 | "first_name": "Leela", 75 | "last_name": "Turanga", 76 | "is_admin": true, 77 | "is_available": true 78 | }, 79 | "body": "@bender, I thought you were supposed to be cooking for this party.", 80 | "posted_at": 1453770984.123 81 | } 82 | ] 83 | } 84 | } 85 | } 86 | let(:get_comment_response) { 87 | %Q{ 88 | { 89 | "_links": { 90 | "self": "https://api2.frontapp.com/comments/inb_55c8c149", 91 | "related": { 92 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 93 | "mentions": "https://api2.frontapp.com/comments/com_55c8c149/mentions" 94 | } 95 | }, 96 | "id": "com_55c8c149", 97 | "author": { 98 | "_links": { 99 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 100 | "related": { 101 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 102 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 103 | } 104 | }, 105 | "id": "tea_55c8c149", 106 | "email": "leela@planet-express.com", 107 | "username": "leela", 108 | "first_name": "Leela", 109 | "last_name": "Turanga", 110 | "is_admin": true, 111 | "is_available": true 112 | }, 113 | "body": "@bender, I thought you were supposed to be cooking for this party.", 114 | "posted_at": 1453770984.123 115 | } 116 | } 117 | } 118 | let(:get_comment_mentions_response) { 119 | %Q{ 120 | { 121 | "_links": { 122 | "self": "https://api2.frontapp.com/comments/com_55c8c149/mentions" 123 | }, 124 | "_results": [ 125 | { 126 | "_links": { 127 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 128 | "related": { 129 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 130 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 131 | } 132 | }, 133 | "id": "tea_55c8c149", 134 | "email": "leela@planet-express.com", 135 | "username": "leela", 136 | "first_name": "Leela", 137 | "last_name": "Turanga", 138 | "is_admin": true, 139 | "is_available": true 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | 146 | it "create a comment" do 147 | data = { 148 | author_id: "alt:email:leela@planet-express.com", 149 | body: "@bender, I thought you were supposed to be cooking for this party." 150 | } 151 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/comments"). 152 | with( body: data.to_json, 153 | headers: headers). 154 | to_return(status: 201, body: create_comment_response) 155 | frontapp.create_comment!(conversation_id, data) 156 | end 157 | 158 | it "can get all comments of a conversation" do 159 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}/comments"). 160 | with( headers: headers). 161 | to_return(status: 200, body: get_conversation_comments_resonse) 162 | frontapp.get_conversation_comments(conversation_id) 163 | end 164 | 165 | it "can get a specific comment" do 166 | stub_request(:get, "#{base_url}/comments/#{comment_id}"). 167 | with( headers: headers). 168 | to_return(status: 200, body: get_comment_response) 169 | frontapp.get_comment(comment_id) 170 | end 171 | 172 | it "can get all mentions in a comment" do 173 | stub_request(:get, "#{base_url}/comments/#{comment_id}/mentions"). 174 | with( headers: headers). 175 | to_return(status: 200, body: get_comment_mentions_response) 176 | frontapp.get_comment_mentions(comment_id) 177 | end 178 | end -------------------------------------------------------------------------------- /spec/contact_group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Contact Groups' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:contact_group_id) { "grp_55c8c149" } 14 | let(:all_contact_groups_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/contact_groups" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/contacts/grp_55c8c149", 24 | "related": { 25 | "contacts": "https://api2.frontapp.com/contact_groups/grp_55c8c149/contacts" 26 | } 27 | }, 28 | "id": "grp_55c8c149", 29 | "name": "Customers" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | let(:create_contact_group_response) { 36 | %Q{ 37 | { 38 | "_links": { 39 | "self": "https://api2.frontapp.com/contacts/grp_55c8c149", 40 | "related": { 41 | "contacts": "https://api2.frontapp.com/contact_groups/grp_55c8c149/contacts" 42 | } 43 | }, 44 | "id": "grp_55c8c149", 45 | "name": "Customers" 46 | } 47 | } 48 | } 49 | let(:get_contact_group_contacts_response) { 50 | %Q{ 51 | { 52 | "_pagination": {}, 53 | "_links": { 54 | "self": "https://api2.frontapp.com/contact_groups/grp_55c8c149/contacts" 55 | }, 56 | "_results": [ 57 | { 58 | "_links": { 59 | "self": "https://api2.frontapp.com/contacts/ctc_55c8c149", 60 | "related": { 61 | "notes": "https://api2.frontapp.com/contacts/ctc_55c8c149/notes", 62 | "conversations": "https://api2.frontapp.com/contacts/ctc_55c8c149/conversations" 63 | } 64 | }, 65 | "id": "ctc_55c8c149", 66 | "name": "Calculon", 67 | "description": "#vip #robot #RIP", 68 | "avatar_url": "http://example.com/calculon.jpg", 69 | "is_spammer": true, 70 | "links": [ 71 | "http://example.com" 72 | ], 73 | "handles": [ 74 | { 75 | "handle": "@calculon", 76 | "source": "twitter" 77 | } 78 | ], 79 | "groups": [ 80 | { 81 | "_links": { 82 | "self": "https://api2.frontapp.com/contacts/grp_55c8c149", 83 | "related": { 84 | "contacts": "https://api2.frontapp.com/contact_groups/grp_55c8c149/contacts" 85 | } 86 | }, 87 | "id": "grp_55c8c149", 88 | "name": "Customers" 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | } 95 | } 96 | 97 | it "can get all contact groups" do 98 | stub_request(:get, "#{base_url}/contact_groups"). 99 | with( headers: headers). 100 | to_return(status: 200, body: all_contact_groups_response) 101 | frontapp.contact_groups 102 | end 103 | 104 | it "can create a contact group" do 105 | data = { name: "Customers" } 106 | stub_request(:post, "#{base_url}/contact_groups"). 107 | with( body: data.to_json, 108 | headers: headers). 109 | to_return(status: 201, body: create_contact_group_response) 110 | frontapp.create_contact_group!(data) 111 | end 112 | 113 | it "can delete a contact group" do 114 | stub_request(:delete, "#{base_url}/contact_groups/#{contact_group_id}"). 115 | with( headers: headers). 116 | to_return(status: 204) 117 | frontapp.delete_contact_group!(contact_group_id) 118 | end 119 | 120 | it "can get all contacts in a contact group" do 121 | stub_request(:get, "#{base_url}/contact_groups/#{contact_group_id}/contacts"). 122 | with( headers: headers). 123 | to_return(status: 200, body: get_contact_group_contacts_response) 124 | frontapp.get_contact_group_contacts(contact_group_id) 125 | end 126 | 127 | it "can add contacts to a contact group" do 128 | data = { contact_ids: ["ctc_55c8c149", "@calculon"] } 129 | stub_request(:post, "#{base_url}/contact_groups/#{contact_group_id}/contacts"). 130 | with( body: data.to_json, 131 | headers: headers). 132 | to_return(status: 204) 133 | frontapp.add_contacts_to_contact_group!(contact_group_id, data) 134 | end 135 | end -------------------------------------------------------------------------------- /spec/contacts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Contacts' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:contact_id) { "ctc_55c8c149" } 14 | let(:all_contacts_response) { 15 | %Q{ 16 | { 17 | "_pagination": {}, 18 | "_links": { 19 | "self": "#{base_url}/contacts" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "#{base_url}/contacts/ctc_55c8c149", 25 | "related": { 26 | "notes": "#{base_url}/contacts/ctc_55c8c149/notes", 27 | "conversations": "#{base_url}/contacts/ctc_55c8c149/conversations" 28 | } 29 | }, 30 | "id": "ctc_55c8c149", 31 | "name": "Calculon", 32 | "description": "#vip #robot #RIP", 33 | "avatar_url": "http://example.com/calculon.jpg", 34 | "is_spammer": true, 35 | "links": [ 36 | "http://example.com" 37 | ], 38 | "handles": [ 39 | { 40 | "handle": "@calculon", 41 | "source": "twitter" 42 | } 43 | ], 44 | "groups": [ 45 | { 46 | "_links": { 47 | "self": "#{base_url}/contacts/grp_55c8c149", 48 | "related": { 49 | "contacts": "#{base_url}/contact_groups/grp_55c8c149/contacts" 50 | } 51 | }, 52 | "id": "grp_55c8c149", 53 | "name": "Customers" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | } 60 | } 61 | let(:get_contact_response) { 62 | %Q{ 63 | { 64 | "_links": { 65 | "self": "#{base_url}/contacts/ctc_55c8c149", 66 | "related": { 67 | "notes": "#{base_url}/contacts/ctc_55c8c149/notes", 68 | "conversations": "#{base_url}/contacts/ctc_55c8c149/conversations" 69 | } 70 | }, 71 | "id": "ctc_55c8c149", 72 | "name": "Calculon", 73 | "description": "#vip #robot #RIP", 74 | "avatar_url": "http://example.com/calculon.jpg", 75 | "is_spammer": true, 76 | "links": [ 77 | "http://example.com" 78 | ], 79 | "handles": [ 80 | { 81 | "handle": "@calculon", 82 | "source": "twitter" 83 | } 84 | ], 85 | "groups": [ 86 | { 87 | "_links": { 88 | "self": "#{base_url}/contacts/grp_55c8c149", 89 | "related": { 90 | "contacts": "#{base_url}/contact_groups/grp_55c8c149/contacts" 91 | } 92 | }, 93 | "id": "grp_55c8c149", 94 | "name": "Customers" 95 | } 96 | ] 97 | } 98 | } 99 | } 100 | let(:create_contact_response) { 101 | %Q{ 102 | { 103 | "_links": { 104 | "self": "#{base_url}/contacts/ctc_55c8c149", 105 | "related": { 106 | "notes": "#{base_url}/contacts/ctc_55c8c149/notes", 107 | "conversations": "#{base_url}/contacts/ctc_55c8c149/conversations" 108 | } 109 | }, 110 | "id": "ctc_55c8c149", 111 | "name": "Calculon", 112 | "description": "#vip #robot #RIP", 113 | "avatar_url": "http://example.com/calculon.jpg", 114 | "is_spammer": true, 115 | "links": [ 116 | "http://example.com" 117 | ], 118 | "handles": [ 119 | { 120 | "handle": "@calculon", 121 | "source": "twitter" 122 | } 123 | ], 124 | "groups": [ 125 | { 126 | "_links": { 127 | "self": "#{base_url}/contacts/grp_55c8c149", 128 | "related": { 129 | "contacts": "#{base_url}/contact_groups/grp_55c8c149/contacts" 130 | } 131 | }, 132 | "id": "grp_55c8c149", 133 | "name": "Customers" 134 | } 135 | ], 136 | "custom_fields": { 137 | "job title": "engineer" 138 | } 139 | } 140 | } 141 | } 142 | let(:get_contact_conversations_response) { 143 | %Q{ 144 | { 145 | "_pagination": {}, 146 | "_links": { 147 | "self": "#{base_url}/contacts/ctc_55c8c149/conversations" 148 | }, 149 | "_results": [ 150 | { 151 | "_links": { 152 | "self": "#{base_url}/conversations/cnv_55c8c149", 153 | "related": { 154 | "events": "#{base_url}/conversations/cnv_55c8c149/events", 155 | "followers": "#{base_url}/conversations/cnv_55c8c149/followers", 156 | "messages": "#{base_url}/conversations/cnv_55c8c149/messages", 157 | "comments": "#{base_url}/conversations/cnv_55c8c149/comments", 158 | "inboxes": "#{base_url}/conversations/cnv_55c8c149/inboxes" 159 | } 160 | }, 161 | "id": "cnv_55c8c149", 162 | "subject": "You broke my heart, Hubert.", 163 | "status": "archived", 164 | "assignee": { 165 | "_links": { 166 | "self": "#{base_url}/teammates/tea_55c8c149", 167 | "related": { 168 | "inboxes": "#{base_url}/teammates/tea_55c8c149/inboxes", 169 | "conversations": "#{base_url}/teammates/tea_55c8c149/conversations" 170 | } 171 | }, 172 | "id": "tea_55c8c149", 173 | "email": "leela@planet-express.com", 174 | "username": "leela", 175 | "first_name": "Leela", 176 | "last_name": "Turanga", 177 | "is_admin": true, 178 | "is_available": true 179 | }, 180 | "recipient": { 181 | "_links": { 182 | "related": { 183 | "contact": "#{base_url}/contacts/ctc_55c8c149" 184 | } 185 | }, 186 | "handle": "calculon@momsbot.com", 187 | "role": "to" 188 | }, 189 | "tags": [ 190 | { 191 | "_links": { 192 | "self": "#{base_url}/tags/tag_55c8c149", 193 | "related": { 194 | "conversations": "#{base_url}/tags/tag_55c8c149/conversations" 195 | } 196 | }, 197 | "id": "tag_55c8c149", 198 | "name": "Robots" 199 | } 200 | ], 201 | "last_message": { 202 | "_links": { 203 | "self": "#{base_url}/messages/msg_55c8c149", 204 | "related": { 205 | "conversation": "#{base_url}/conversations/cnv_55c8c149", 206 | "message_replied_to": "#{base_url}/messages/msg_1ab23cd4" 207 | } 208 | }, 209 | "id": "msg_55c8c149", 210 | "type": "email", 211 | "is_inbound": true, 212 | "created_at": 1453770984.123, 213 | "blurb": "Anything less than immortality is a...", 214 | "author": { 215 | "_links": { 216 | "self": "#{base_url}/teammates/tea_55c8c149", 217 | "related": { 218 | "inboxes": "#{base_url}/teammates/tea_55c8c149/inboxes", 219 | "conversations": "#{base_url}/teammates/tea_55c8c149/conversations" 220 | } 221 | }, 222 | "id": "tea_55c8c149", 223 | "email": "leela@planet-express.com", 224 | "username": "leela", 225 | "first_name": "Leela", 226 | "last_name": "Turanga", 227 | "is_admin": true, 228 | "is_available": true 229 | }, 230 | "recipients": [ 231 | { 232 | "_links": { 233 | "related": { 234 | "contact": "#{base_url}/contacts/ctc_55c8c149" 235 | } 236 | }, 237 | "handle": "calculon@momsbot.com", 238 | "role": "to" 239 | } 240 | ], 241 | "body": "Anything less than immortality is a complete waste of time.", 242 | "text": "Anything less than immortality is a complete waste of time.", 243 | "attachments": [ 244 | { 245 | "filename": "attachment.jpg", 246 | "url": "#{base_url}/download/fil_55c8c149", 247 | "content_type": "image/jpeg", 248 | "size": 10000, 249 | "metadata": { 250 | "is_inline": true, 251 | "cid": "123456789" 252 | } 253 | } 254 | ], 255 | "metadata": {} 256 | }, 257 | "created_at": 1453770984.123 258 | } 259 | ] 260 | } 261 | } 262 | } 263 | let(:get_contact_notes_response) { 264 | %Q{ 265 | { 266 | "_links": { 267 | "self": "#{base_url}/contacts/ctc_55c8c149/notes" 268 | }, 269 | "_results": [ 270 | { 271 | "author": { 272 | "_links": { 273 | "self": "#{base_url}/teammates/tea_55c8c149", 274 | "related": { 275 | "inboxes": "#{base_url}/teammates/tea_55c8c149/inboxes", 276 | "conversations": "#{base_url}/teammates/tea_55c8c149/conversations" 277 | } 278 | }, 279 | "id": "tea_55c8c149", 280 | "email": "leela@planet-express.com", 281 | "username": "leela", 282 | "first_name": "Leela", 283 | "last_name": "Turanga", 284 | "is_admin": true, 285 | "is_available": true 286 | }, 287 | "body": "Calculon is a celebrated actor", 288 | "created_at": 1453770984.123 289 | } 290 | ] 291 | } 292 | } 293 | } 294 | let(:add_contact_note_response) { 295 | %Q{ 296 | { 297 | "author": { 298 | "_links": { 299 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 300 | "related": { 301 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 302 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 303 | } 304 | }, 305 | "id": "tea_55c8c149", 306 | "email": "leela@planet-express.com", 307 | "username": "leela", 308 | "first_name": "Leela", 309 | "last_name": "Turanga", 310 | "is_admin": true, 311 | "is_available": true 312 | }, 313 | "body": "Calculon is a celebrated actor", 314 | "created_at": 1453770984.123 315 | } 316 | } 317 | } 318 | 319 | it "can get all contacts" do 320 | stub_request(:get, "#{base_url}/contacts"). 321 | with( headers: headers). 322 | to_return(status: 200, body: all_contacts_response) 323 | frontapp.contacts 324 | end 325 | 326 | it "can get a specific contact" do 327 | stub_request(:get, "#{base_url}/contacts/#{contact_id}"). 328 | with( headers: headers). 329 | to_return(status: 200, body: get_contact_response) 330 | frontapp.get_contact(contact_id) 331 | end 332 | 333 | it "can update a contact" do 334 | data = { 335 | name: "Calculon", 336 | description: "#vip #robot #RIP", 337 | avatar_url: "http://example.com/calculon.jpg", 338 | is_spammer: true, 339 | links: [ 340 | "http://example.com" 341 | ], 342 | group_names: [ "Customers" ], 343 | custom_fields: { 344 | "job title": "engineer" 345 | } 346 | } 347 | stub_request(:patch, "#{base_url}/contacts/#{contact_id}"). 348 | with( body: data.to_json, 349 | headers: headers). 350 | to_return(status: 204) 351 | frontapp.update_contact!(contact_id, data) 352 | end 353 | 354 | it "can create a contact" do 355 | data = { 356 | name: "Calculon", 357 | description: "#vip #robot #RIP", 358 | avatar_url: "http://example.com/calculon.jpg", 359 | is_spammer: true, 360 | links: [ 361 | "http://example.com" 362 | ], 363 | group_names: [ "Customers" ], 364 | handles: [ 365 | { 366 | handle: "@calculon", 367 | source: "twitter" 368 | } 369 | ], 370 | custom_fields: { 371 | "job title": "engineer" 372 | } 373 | } 374 | stub_request(:post, "#{base_url}/contacts"). 375 | with( body: data.to_json, 376 | headers: headers). 377 | to_return(status: 201, body: create_contact_response) 378 | frontapp.create_contact!(data) 379 | end 380 | 381 | it "can delete a contact" do 382 | stub_request(:delete, "#{base_url}/contacts/#{contact_id}"). 383 | with( headers: headers). 384 | to_return(status: 204) 385 | frontapp.delete_contact!(contact_id) 386 | end 387 | 388 | it "can get all contact's conversations" do 389 | stub_request(:get, "#{base_url}/contacts/#{contact_id}/conversations"). 390 | with( headers: headers). 391 | to_return(status: 200, body: get_contact_conversations_response) 392 | frontapp.get_contact_conversations(contact_id) 393 | end 394 | 395 | it "can add a handle to a contact" do 396 | data = { 397 | handle: "@calculon", 398 | source: "twitter" 399 | } 400 | stub_request(:post, "#{base_url}/contacts/#{contact_id}/handles"). 401 | with( body: data.to_json, 402 | headers: headers). 403 | to_return(status: 204) 404 | frontapp.add_contact_handle!(contact_id, data) 405 | end 406 | 407 | it "can delete a handle of a contact" do 408 | data = { 409 | handle: "@calculon", 410 | source: "twitter" 411 | } 412 | stub_request(:delete, "#{base_url}/contacts/#{contact_id}/handles"). 413 | with( body: data.to_json, 414 | headers: headers). 415 | to_return(status: 204) 416 | frontapp.delete_contact_handle!(contact_id, data) 417 | end 418 | 419 | it "can get all notes for a contact" do 420 | stub_request(:get, "#{base_url}/contacts/#{contact_id}/notes"). 421 | with( headers: headers). 422 | to_return(status: 200, body: get_contact_notes_response) 423 | frontapp.get_contact_notes(contact_id) 424 | end 425 | 426 | it "can add a note to a contact" do 427 | data = { 428 | author_id: "alt:email:leela@planet-express.com", 429 | body: "Calculon is a celebrated actor" 430 | } 431 | stub_request(:post,"#{base_url}/contacts/#{contact_id}/notes"). 432 | with( body: data.to_json, 433 | headers: headers). 434 | to_return(status: 201, body: add_contact_note_response) 435 | frontapp.add_contact_note!(contact_id, data) 436 | end 437 | end 438 | -------------------------------------------------------------------------------- /spec/conversations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Conversations' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:conversation_id) { "cnv_55c8c149" } 14 | let(:all_conversations_response) { 15 | %{ 16 | { 17 | "_pagination": {}, 18 | "_links": { 19 | "self": "https://api2.frontapp.com/conversations" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 25 | "related": { 26 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 27 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 28 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 29 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 30 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 31 | } 32 | }, 33 | "id": "cnv_55c8c149", 34 | "subject": "You broke my heart, Hubert.", 35 | "status": "archived", 36 | "assignee": { 37 | "_links": { 38 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 39 | "related": { 40 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 41 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 42 | } 43 | }, 44 | "id": "tea_55c8c149", 45 | "email": "leela@planet-express.com", 46 | "username": "leela", 47 | "first_name": "Leela", 48 | "last_name": "Turanga", 49 | "is_admin": true, 50 | "is_available": true 51 | }, 52 | "recipient": { 53 | "_links": { 54 | "related": { 55 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 56 | } 57 | }, 58 | "handle": "calculon@momsbot.com", 59 | "role": "to" 60 | }, 61 | "tags": [ 62 | { 63 | "_links": { 64 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 65 | "related": { 66 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 67 | } 68 | }, 69 | "id": "tag_55c8c149", 70 | "name": "Robots" 71 | } 72 | ], 73 | "last_message": { 74 | "_links": { 75 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 76 | "related": { 77 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 78 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 79 | } 80 | }, 81 | "id": "msg_55c8c149", 82 | "type": "email", 83 | "is_inbound": true, 84 | "created_at": 1453770984.123, 85 | "blurb": "Anything less than immortality is a...", 86 | "author": { 87 | "_links": { 88 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 89 | "related": { 90 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 91 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 92 | } 93 | }, 94 | "id": "tea_55c8c149", 95 | "email": "leela@planet-express.com", 96 | "username": "leela", 97 | "first_name": "Leela", 98 | "last_name": "Turanga", 99 | "is_admin": true, 100 | "is_available": true 101 | }, 102 | "recipients": [ 103 | { 104 | "_links": { 105 | "related": { 106 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 107 | } 108 | }, 109 | "handle": "calculon@momsbot.com", 110 | "role": "to" 111 | } 112 | ], 113 | "body": "Anything less than immortality is a complete waste of time.", 114 | "text": "Anything less than immortality is a complete waste of time.", 115 | "attachments": [ 116 | { 117 | "filename": "attachment.jpg", 118 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 119 | "content_type": "image/jpeg", 120 | "size": 10000, 121 | "metadata": { 122 | "is_inline": true, 123 | "cid": "123456789" 124 | } 125 | } 126 | ], 127 | "metadata": {} 128 | }, 129 | "created_at": 1453770984.123 130 | } 131 | ] 132 | } 133 | } 134 | } 135 | let(:get_conversation_response) { 136 | %{ 137 | { 138 | "_links": { 139 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 140 | "related": { 141 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 142 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 143 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 144 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 145 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 146 | } 147 | }, 148 | "id": "cnv_55c8c149", 149 | "subject": "You broke my heart, Hubert.", 150 | "status": "archived", 151 | "assignee": { 152 | "_links": { 153 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 154 | "related": { 155 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 156 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 157 | } 158 | }, 159 | "id": "tea_55c8c149", 160 | "email": "leela@planet-express.com", 161 | "username": "leela", 162 | "first_name": "Leela", 163 | "last_name": "Turanga", 164 | "is_admin": true, 165 | "is_available": true 166 | }, 167 | "recipient": { 168 | "_links": { 169 | "related": { 170 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 171 | } 172 | }, 173 | "handle": "calculon@momsbot.com", 174 | "role": "to" 175 | }, 176 | "tags": [ 177 | { 178 | "_links": { 179 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 180 | "related": { 181 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 182 | } 183 | }, 184 | "id": "tag_55c8c149", 185 | "name": "Robots" 186 | } 187 | ], 188 | "last_message": { 189 | "_links": { 190 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 191 | "related": { 192 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 193 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 194 | } 195 | }, 196 | "id": "msg_55c8c149", 197 | "type": "email", 198 | "is_inbound": true, 199 | "created_at": 1453770984.123, 200 | "blurb": "Anything less than immortality is a...", 201 | "author": { 202 | "_links": { 203 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 204 | "related": { 205 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 206 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 207 | } 208 | }, 209 | "id": "tea_55c8c149", 210 | "email": "leela@planet-express.com", 211 | "username": "leela", 212 | "first_name": "Leela", 213 | "last_name": "Turanga", 214 | "is_admin": true, 215 | "is_available": true 216 | }, 217 | "recipients": [ 218 | { 219 | "_links": { 220 | "related": { 221 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 222 | } 223 | }, 224 | "handle": "calculon@momsbot.com", 225 | "role": "to" 226 | } 227 | ], 228 | "body": "Anything less than immortality is a complete waste of time.", 229 | "text": "Anything less than immortality is a complete waste of time.", 230 | "attachments": [ 231 | { 232 | "filename": "attachment.jpg", 233 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 234 | "content_type": "image/jpeg", 235 | "size": 10000, 236 | "metadata": { 237 | "is_inline": true, 238 | "cid": "123456789" 239 | } 240 | } 241 | ], 242 | "metadata": {} 243 | }, 244 | "created_at": 1453770984.123 245 | } 246 | } 247 | } 248 | let(:get_conversation_inboxes_response) { 249 | %{ 250 | { 251 | "_links": { 252 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 253 | }, 254 | "_results": [ 255 | { 256 | "_links": { 257 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 258 | "related": { 259 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 260 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 261 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 262 | } 263 | }, 264 | "id": "inb_55c8c149", 265 | "address": "team@planet-express.com", 266 | "type": "smtp", 267 | "name": "Team", 268 | "send_as": "team@planet-express.com" 269 | } 270 | ] 271 | } 272 | } 273 | } 274 | let(:get_conversation_followers_response) { 275 | %{ 276 | { 277 | "_links": { 278 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers" 279 | }, 280 | "_results": [ 281 | { 282 | "_links": { 283 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 284 | "related": { 285 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 286 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 287 | } 288 | }, 289 | "id": "tea_55c8c149", 290 | "email": "leela@planet-express.com", 291 | "username": "leela", 292 | "first_name": "Leela", 293 | "last_name": "Turanga", 294 | "is_admin": true, 295 | "is_available": true 296 | } 297 | ] 298 | } 299 | } 300 | } 301 | let(:get_conversation_events_response) { 302 | %{ 303 | { 304 | "_pagination": {}, 305 | "_links": { 306 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149/events" 307 | }, 308 | "_results": [ 309 | { 310 | "_links": { 311 | "self": "https://api2.frontapp.com/events/evt_55c8c149" 312 | }, 313 | "id": "evt_55c8c149", 314 | "type": "assign", 315 | "emitted_at": 1453770984.123, 316 | "source": { 317 | "_meta": { 318 | "type": "rule" 319 | }, 320 | "data": { 321 | "_links": { 322 | "self": "https://api2.frontapp.com/rules/rul_55c8c149" 323 | }, 324 | "id": "rul_55c8c149", 325 | "name": "Important deliveries", 326 | "actions": [ 327 | "Assign to Leela Turanga" 328 | ] 329 | } 330 | }, 331 | "target": { 332 | "_meta": { 333 | "type": "teammate" 334 | }, 335 | "data": { 336 | "_links": { 337 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 338 | "related": { 339 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 340 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 341 | } 342 | }, 343 | "id": "tea_55c8c149", 344 | "email": "leela@planet-express.com", 345 | "username": "leela", 346 | "first_name": "Leela", 347 | "last_name": "Turanga", 348 | "is_admin": true, 349 | "is_available": true 350 | } 351 | }, 352 | "conversation": { 353 | "_links": { 354 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 355 | "related": { 356 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 357 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 358 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 359 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 360 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 361 | } 362 | }, 363 | "id": "cnv_55c8c149", 364 | "subject": "You broke my heart, Hubert.", 365 | "status": "archived", 366 | "assignee": { 367 | "_links": { 368 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 369 | "related": { 370 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 371 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 372 | } 373 | }, 374 | "id": "tea_55c8c149", 375 | "email": "leela@planet-express.com", 376 | "username": "leela", 377 | "first_name": "Leela", 378 | "last_name": "Turanga", 379 | "is_admin": true, 380 | "is_available": true 381 | }, 382 | "recipient": { 383 | "_links": { 384 | "related": { 385 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 386 | } 387 | }, 388 | "handle": "calculon@momsbot.com", 389 | "role": "to" 390 | }, 391 | "tags": [ 392 | { 393 | "_links": { 394 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 395 | "related": { 396 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 397 | } 398 | }, 399 | "id": "tag_55c8c149", 400 | "name": "Robots" 401 | } 402 | ], 403 | "last_message": { 404 | "_links": { 405 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 406 | "related": { 407 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 408 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 409 | } 410 | }, 411 | "id": "msg_55c8c149", 412 | "type": "email", 413 | "is_inbound": true, 414 | "created_at": 1453770984.123, 415 | "blurb": "Anything less than immortality is a...", 416 | "author": { 417 | "_links": { 418 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 419 | "related": { 420 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 421 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 422 | } 423 | }, 424 | "id": "tea_55c8c149", 425 | "email": "leela@planet-express.com", 426 | "username": "leela", 427 | "first_name": "Leela", 428 | "last_name": "Turanga", 429 | "is_admin": true, 430 | "is_available": true 431 | }, 432 | "recipients": [ 433 | { 434 | "_links": { 435 | "related": { 436 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 437 | } 438 | }, 439 | "handle": "calculon@momsbot.com", 440 | "role": "to" 441 | } 442 | ], 443 | "body": "Anything less than immortality is a complete waste of time.", 444 | "text": "Anything less than immortality is a complete waste of time.", 445 | "attachments": [ 446 | { 447 | "filename": "attachment.jpg", 448 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 449 | "content_type": "image/jpeg", 450 | "size": 10000, 451 | "metadata": { 452 | "is_inline": true, 453 | "cid": "123456789" 454 | } 455 | } 456 | ], 457 | "metadata": {} 458 | }, 459 | "created_at": 1453770984.123 460 | } 461 | } 462 | ] 463 | } 464 | } 465 | } 466 | let(:get_conversation_messages_response) { 467 | %{ 468 | { 469 | "_pagination": {}, 470 | "_links": { 471 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages" 472 | }, 473 | "_results": [ 474 | { 475 | "_links": { 476 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 477 | "related": { 478 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 479 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 480 | } 481 | }, 482 | "id": "msg_55c8c149", 483 | "type": "email", 484 | "is_inbound": true, 485 | "created_at": 1453770984.123, 486 | "blurb": "Anything less than immortality is a...", 487 | "author": { 488 | "_links": { 489 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 490 | "related": { 491 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 492 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 493 | } 494 | }, 495 | "id": "tea_55c8c149", 496 | "email": "leela@planet-express.com", 497 | "username": "leela", 498 | "first_name": "Leela", 499 | "last_name": "Turanga", 500 | "is_admin": true, 501 | "is_available": true 502 | }, 503 | "recipients": [ 504 | { 505 | "_links": { 506 | "related": { 507 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 508 | } 509 | }, 510 | "handle": "calculon@momsbot.com", 511 | "role": "to" 512 | } 513 | ], 514 | "body": "Anything less than immortality is a complete waste of time.", 515 | "text": "Anything less than immortality is a complete waste of time.", 516 | "attachments": [ 517 | { 518 | "filename": "attachment.jpg", 519 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 520 | "content_type": "image/jpeg", 521 | "size": 10000, 522 | "metadata": { 523 | "is_inline": true, 524 | "cid": "123456789" 525 | } 526 | } 527 | ], 528 | "metadata": {} 529 | } 530 | ] 531 | } 532 | } 533 | } 534 | 535 | 536 | it "can list all conversations" do 537 | stub_request(:get, "#{base_url}/conversations"). 538 | with( headers: headers). 539 | to_return(status: 200, body: all_conversations_response) 540 | frontapp.conversations 541 | end 542 | 543 | it "can get a specific conversation" do 544 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}"). 545 | with( headers: headers). 546 | to_return(status: 200, body: get_conversation_response) 547 | frontapp.get_conversation(conversation_id) 548 | end 549 | 550 | it "can update a conversation" do 551 | data = { 552 | assignee_id: "alt:email:fry@planet-express.com", 553 | inbox_id: "inb_55c8c149", 554 | status: "archived", 555 | tags: [ "time travel" ] 556 | } 557 | stub_request(:patch, "#{base_url}/conversations/#{conversation_id}"). 558 | with( body: data.to_json, 559 | headers: headers). 560 | to_return(status: 204) 561 | frontapp.update_conversation!(conversation_id, data) 562 | end 563 | 564 | it "can get all the inboxes a conversation is in" do 565 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}/inboxes"). 566 | with( headers: headers). 567 | to_return(status: 200, body: get_conversation_inboxes_response) 568 | frontapp.get_conversation_inboxes(conversation_id) 569 | end 570 | 571 | it "can get all the followers of a conversation" do 572 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}/followers"). 573 | with( headers: headers). 574 | to_return(status: 200, body: get_conversation_followers_response) 575 | frontapp.get_conversation_followers(conversation_id) 576 | end 577 | 578 | it "can get all events for a conversation" do 579 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}/events"). 580 | with( headers: headers). 581 | to_return(status: 200, body: get_conversation_events_response) 582 | frontapp.get_conversation_events(conversation_id) 583 | end 584 | 585 | it "can get all messages in a conversation" do 586 | stub_request(:get, "#{base_url}/conversations/#{conversation_id}/messages"). 587 | with( headers: headers). 588 | to_return(status: 200, body: get_conversation_messages_response) 589 | frontapp.get_conversation_messages(conversation_id) 590 | end 591 | 592 | it "can add conversation links by id" do 593 | data = { 594 | link_ids: ["top_55c8c149"] 595 | } 596 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/links"). 597 | with( headers: headers). 598 | to_return(status: 202) 599 | frontapp.add_conversation_links!(conversation_id, data) 600 | end 601 | 602 | it "can add conversation links by link external url" do 603 | data = { 604 | link_external_urls: ["example.com/my-link"] 605 | } 606 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/links"). 607 | with( headers: headers). 608 | to_return(status: 202) 609 | frontapp.add_conversation_links!(conversation_id, data) 610 | end 611 | 612 | it "can remove conversation links by id" do 613 | data = { 614 | link_ids: ["top_55c8c149"] 615 | } 616 | stub_request(:delete, "#{base_url}/conversations/#{conversation_id}/links"). 617 | with( headers: headers). 618 | to_return(status: 204, body: nil) 619 | frontapp.remove_conversation_links!(conversation_id, data) 620 | end 621 | 622 | it "can remove conversation followers by id" do 623 | data = { 624 | teammate_ids: ["tea_64ue9"] 625 | } 626 | stub_request(:delete, "#{base_url}/conversations/#{conversation_id}/followers"). 627 | with( headers: headers). 628 | to_return(status: 204, body: nil) 629 | frontapp.remove_conversation_followers!(conversation_id, data) 630 | end 631 | 632 | it "can add conversation followers by id" do 633 | data = { 634 | link_ids: ["tea_64ue9"] 635 | } 636 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/followers"). 637 | with( headers: headers). 638 | to_return(status: 202) 639 | frontapp.add_conversation_followers!(conversation_id, data) 640 | end 641 | end 642 | -------------------------------------------------------------------------------- /spec/drafts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Drafts' do 5 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 6 | let(:conversation_id) { 'cnv_55c8c149' } 7 | 8 | # see https://dev.frontapp.com/reference/drafts 9 | let(:create_draft_reply_response) do 10 | %Q{ 11 | { 12 | "_links": { 13 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 14 | "related": { 15 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 16 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 17 | } 18 | }, 19 | "id": "msg_55c8c149", 20 | "type": "email", 21 | "is_inbound": true, 22 | "is_draft": false, 23 | "created_at": 1453770984.123, 24 | "blurb": "Anything less than immortality is a...", 25 | "author": { 26 | "_links": { 27 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 28 | "related": { 29 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 30 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 31 | } 32 | }, 33 | "id": "tea_55c8c149", 34 | "email": "leela@planet-express.com", 35 | "username": "leela", 36 | "first_name": "Leela", 37 | "last_name": "Turanga", 38 | "is_admin": true, 39 | "is_available": true, 40 | "is_blocked": false 41 | }, 42 | "recipients": [ 43 | { 44 | "_links": { 45 | "related": { 46 | "contact": "https://api2.frontapp.com/contacts/crd_55c8c149" 47 | } 48 | }, 49 | "handle": "calculon@momsbot.com", 50 | "role": "to" 51 | } 52 | ], 53 | "body": "Anything less than immortality is a complete waste of time.", 54 | "text": "Anything less than immortality is a complete waste of time.", 55 | "attachments": [ 56 | { 57 | "filename": "attachment.jpg", 58 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 59 | "content_type": "image/jpeg", 60 | "size": 10000, 61 | "metadata": { 62 | "is_inline": true, 63 | "cid": "123456789" 64 | } 65 | } 66 | ], 67 | "metadata": {} 68 | } 69 | } 70 | end 71 | 72 | describe '#create_draft_reply' do 73 | it 'creates a draft valid' do 74 | body = { 75 | author_id: 'alt:email:leela@planet-exress.com', 76 | body: 'Why is Zoidberg the only one still alone?', 77 | to: ['calculon@momsbot.com'], 78 | } 79 | 80 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/drafts") 81 | .with(body: body, headers: standard_headers) 82 | .to_return(status: 200, body: create_draft_reply_response) 83 | 84 | response = frontapp.create_draft_reply(conversation_id, body) 85 | 86 | expect(response).to eq(JSON.parse(create_draft_reply_response)) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/error_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "frontapp" 3 | 4 | RSpec.describe "Errors" do 5 | let(:headers) { 6 | { 7 | "Accept" => "application/json", 8 | "Authorization" => "Bearer #{auth_token}", 9 | } 10 | } 11 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 12 | 13 | it "can raise a bad request error" do 14 | stub_request(:get, "#{base_url}/contacts/1"). 15 | with(headers: headers). 16 | to_return(status: 400, body: "{}") 17 | 18 | expect do 19 | frontapp.get_contact(1) 20 | end.to raise_error(Frontapp::BadRequestError) 21 | end 22 | 23 | it "can raise a not found error" do 24 | stub_request(:get, "#{base_url}/contacts/1"). 25 | with(headers: headers). 26 | to_return(status: 404, body: "{}") 27 | 28 | expect do 29 | frontapp.get_contact(1) 30 | end.to raise_error(Frontapp::NotFoundError) 31 | end 32 | 33 | it "can raise a conflict error" do 34 | stub_request(:get, "#{base_url}/contacts/1"). 35 | with(headers: headers). 36 | to_return(status: 409, body: "{}") 37 | 38 | expect do 39 | frontapp.get_contact(1) 40 | end.to raise_error(Frontapp::ConflictError) 41 | end 42 | 43 | it "can raise a too many requests error" do 44 | stub_request(:get, "#{base_url}/contacts/1"). 45 | with(headers: headers). 46 | to_return(status: 429, body: "{}") 47 | 48 | expect do 49 | frontapp.get_contact(1) 50 | end.to raise_error(Frontapp::TooManyRequestsError) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/events_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Events' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:event_id) { "evt_55c8c149" } 14 | let(:all_events_response) { 15 | %Q{ 16 | { 17 | "_pagination": {}, 18 | "_links": { 19 | "self": "https://api2.frontapp.com/events" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "https://api2.frontapp.com/events/evt_55c8c149" 25 | }, 26 | "id": "evt_55c8c149", 27 | "type": "assign", 28 | "emitted_at": 1453770984.123, 29 | "source": { 30 | "_meta": { 31 | "type": "rule" 32 | }, 33 | "data": { 34 | "_links": { 35 | "self": "https://api2.frontapp.com/rules/rul_55c8c149" 36 | }, 37 | "id": "rul_55c8c149", 38 | "name": "Important deliveries", 39 | "actions": [ 40 | "Assign to Leela Turanga" 41 | ] 42 | } 43 | }, 44 | "target": { 45 | "_meta": { 46 | "type": "teammate" 47 | }, 48 | "data": { 49 | "_links": { 50 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 51 | "related": { 52 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 53 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 54 | } 55 | }, 56 | "id": "tea_55c8c149", 57 | "email": "leela@planet-express.com", 58 | "username": "leela", 59 | "first_name": "Leela", 60 | "last_name": "Turanga", 61 | "is_admin": true, 62 | "is_available": true 63 | } 64 | }, 65 | "conversation": { 66 | "_links": { 67 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 68 | "related": { 69 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 70 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 71 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 72 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 73 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 74 | } 75 | }, 76 | "id": "cnv_55c8c149", 77 | "subject": "You broke my heart, Hubert.", 78 | "status": "archived", 79 | "assignee": { 80 | "_links": { 81 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 82 | "related": { 83 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 84 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 85 | } 86 | }, 87 | "id": "tea_55c8c149", 88 | "email": "leela@planet-express.com", 89 | "username": "leela", 90 | "first_name": "Leela", 91 | "last_name": "Turanga", 92 | "is_admin": true, 93 | "is_available": true 94 | }, 95 | "recipient": { 96 | "_links": { 97 | "related": { 98 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 99 | } 100 | }, 101 | "handle": "calculon@momsbot.com", 102 | "role": "to" 103 | }, 104 | "tags": [ 105 | { 106 | "_links": { 107 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 108 | "related": { 109 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 110 | } 111 | }, 112 | "id": "tag_55c8c149", 113 | "name": "Robots" 114 | } 115 | ], 116 | "last_message": { 117 | "_links": { 118 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 119 | "related": { 120 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 121 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 122 | } 123 | }, 124 | "id": "msg_55c8c149", 125 | "type": "email", 126 | "is_inbound": true, 127 | "created_at": 1453770984.123, 128 | "blurb": "Anything less than immortality is a...", 129 | "author": { 130 | "_links": { 131 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 132 | "related": { 133 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 134 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 135 | } 136 | }, 137 | "id": "tea_55c8c149", 138 | "email": "leela@planet-express.com", 139 | "username": "leela", 140 | "first_name": "Leela", 141 | "last_name": "Turanga", 142 | "is_admin": true, 143 | "is_available": true 144 | }, 145 | "recipients": [ 146 | { 147 | "_links": { 148 | "related": { 149 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 150 | } 151 | }, 152 | "handle": "calculon@momsbot.com", 153 | "role": "to" 154 | } 155 | ], 156 | "body": "Anything less than immortality is a complete waste of time.", 157 | "text": "Anything less than immortality is a complete waste of time.", 158 | "attachments": [ 159 | { 160 | "filename": "attachment.jpg", 161 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 162 | "content_type": "image/jpeg", 163 | "size": 10000, 164 | "metadata": { 165 | "is_inline": true, 166 | "cid": "123456789" 167 | } 168 | } 169 | ], 170 | "metadata": {} 171 | }, 172 | "created_at": 1453770984.123 173 | } 174 | } 175 | ] 176 | } 177 | } 178 | } 179 | let(:get_event_response) { 180 | %Q{ 181 | { 182 | "_links": { 183 | "self": "https://api2.frontapp.com/events/evt_55c8c149" 184 | }, 185 | "id": "evt_55c8c149", 186 | "type": "assign", 187 | "emitted_at": 1453770984.123, 188 | "source": { 189 | "_meta": { 190 | "type": "rule" 191 | }, 192 | "data": { 193 | "_links": { 194 | "self": "https://api2.frontapp.com/rules/rul_55c8c149" 195 | }, 196 | "id": "rul_55c8c149", 197 | "name": "Important deliveries", 198 | "actions": [ 199 | "Assign to Leela Turanga" 200 | ] 201 | } 202 | }, 203 | "target": { 204 | "_meta": { 205 | "type": "teammate" 206 | }, 207 | "data": { 208 | "_links": { 209 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 210 | "related": { 211 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 212 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 213 | } 214 | }, 215 | "id": "tea_55c8c149", 216 | "email": "leela@planet-express.com", 217 | "username": "leela", 218 | "first_name": "Leela", 219 | "last_name": "Turanga", 220 | "is_admin": true, 221 | "is_available": true 222 | } 223 | }, 224 | "conversation": { 225 | "_links": { 226 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 227 | "related": { 228 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 229 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 230 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 231 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 232 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 233 | } 234 | }, 235 | "id": "cnv_55c8c149", 236 | "subject": "You broke my heart, Hubert.", 237 | "status": "archived", 238 | "assignee": { 239 | "_links": { 240 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 241 | "related": { 242 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 243 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 244 | } 245 | }, 246 | "id": "tea_55c8c149", 247 | "email": "leela@planet-express.com", 248 | "username": "leela", 249 | "first_name": "Leela", 250 | "last_name": "Turanga", 251 | "is_admin": true, 252 | "is_available": true 253 | }, 254 | "recipient": { 255 | "_links": { 256 | "related": { 257 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 258 | } 259 | }, 260 | "handle": "calculon@momsbot.com", 261 | "role": "to" 262 | }, 263 | "tags": [ 264 | { 265 | "_links": { 266 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 267 | "related": { 268 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 269 | } 270 | }, 271 | "id": "tag_55c8c149", 272 | "name": "Robots" 273 | } 274 | ], 275 | "last_message": { 276 | "_links": { 277 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 278 | "related": { 279 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 280 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 281 | } 282 | }, 283 | "id": "msg_55c8c149", 284 | "type": "email", 285 | "is_inbound": true, 286 | "created_at": 1453770984.123, 287 | "blurb": "Anything less than immortality is a...", 288 | "author": { 289 | "_links": { 290 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 291 | "related": { 292 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 293 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 294 | } 295 | }, 296 | "id": "tea_55c8c149", 297 | "email": "leela@planet-express.com", 298 | "username": "leela", 299 | "first_name": "Leela", 300 | "last_name": "Turanga", 301 | "is_admin": true, 302 | "is_available": true 303 | }, 304 | "recipients": [ 305 | { 306 | "_links": { 307 | "related": { 308 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 309 | } 310 | }, 311 | "handle": "calculon@momsbot.com", 312 | "role": "to" 313 | } 314 | ], 315 | "body": "Anything less than immortality is a complete waste of time.", 316 | "text": "Anything less than immortality is a complete waste of time.", 317 | "attachments": [ 318 | { 319 | "filename": "attachment.jpg", 320 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 321 | "content_type": "image/jpeg", 322 | "size": 10000, 323 | "metadata": { 324 | "is_inline": true, 325 | "cid": "123456789" 326 | } 327 | } 328 | ], 329 | "metadata": {} 330 | }, 331 | "created_at": 1453770984.123 332 | } 333 | } 334 | } 335 | } 336 | 337 | it "can get all events" do 338 | stub_request(:get, "#{base_url}/events"). 339 | with( headers: headers). 340 | to_return(status: 200, body: all_events_response) 341 | frontapp.events 342 | end 343 | 344 | it "can get a specific event" do 345 | stub_request(:get, "#{base_url}/events/#{event_id}"). 346 | with( headers: headers). 347 | to_return(status: 200, body: get_event_response) 348 | frontapp.get_event(event_id) 349 | end 350 | 351 | end -------------------------------------------------------------------------------- /spec/exports_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Exports' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:export_id) { "exp_55c8c149" } 14 | let(:team_id) { "tim_55c8c149" } 15 | let(:all_exports_response) { 16 | %Q{ 17 | { 18 | "_pagination": {}, 19 | "_links": { 20 | "self": "https://api2.frontapp.com/exports" 21 | }, 22 | "_results": [ 23 | { 24 | "_links": { 25 | "self": "https://api2.frontapp.com/exports/exp_55c8c149" 26 | }, 27 | "id": "exp_55c8c149", 28 | "status": "running", 29 | "progress": 42, 30 | "url": "http://exports.frontapp.com/planet-express/export.csv", 31 | "filename": "export.csv", 32 | "size": 1000, 33 | "created_at": 1453770984.123, 34 | "query": { 35 | "inbox_id": "alt:address:team@planet-express.com", 36 | "start": 1428889003, 37 | "end": 1428889008, 38 | "timezone": "America/New_York", 39 | "should_export_events": false 40 | } 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | let(:get_export_response) { 47 | %Q{ 48 | { 49 | "_links": { 50 | "self": "https://api2.frontapp.com/exports/exp_55c8c149" 51 | }, 52 | "id": "exp_55c8c149", 53 | "status": "running", 54 | "progress": 42, 55 | "url": "http://exports.frontapp.com/planet-express/export.csv", 56 | "filename": "export.csv", 57 | "size": 1000, 58 | "created_at": 1453770984.123, 59 | "query": { 60 | "inbox_id": "alt:address:team@planet-express.com", 61 | "start": 1428889003, 62 | "end": 1428889008, 63 | "timezone": "America/New_York", 64 | "should_export_events": false 65 | } 66 | } 67 | } 68 | } 69 | let(:create_export_response) { 70 | %Q{ 71 | { 72 | "_links": { 73 | "self": "https://api2.frontapp.com/exports/exp_55c8c149" 74 | }, 75 | "id": "exp_55c8c149", 76 | "status": "running", 77 | "progress": 42, 78 | "url": "http://exports.frontapp.com/planet-express/export.csv", 79 | "filename": "export.csv", 80 | "size": 1000, 81 | "created_at": 1453770984.123, 82 | "query": { 83 | "inbox_id": "alt:address:team@planet-express.com", 84 | "start": 1428889003, 85 | "end": 1428889008, 86 | "timezone": "America/New_York", 87 | "should_export_events": false 88 | } 89 | } 90 | } 91 | } 92 | 93 | it "can get all exports" do 94 | stub_request(:get, "#{base_url}/exports"). 95 | with( headers: headers). 96 | to_return(status: 200, body: all_exports_response) 97 | frontapp.exports 98 | end 99 | 100 | it "can get a specific export" do 101 | stub_request(:get, "#{base_url}/exports/#{export_id}"). 102 | with( headers: headers). 103 | to_return(status: 200, body: get_export_response) 104 | frontapp.get_export(export_id) 105 | end 106 | 107 | it "can create a export" do 108 | data = { 109 | inbox_id: "alt:address:team@planet-express.com", 110 | start: 1428889003, 111 | end: 1428889008, 112 | timezone: "America/New_York", 113 | should_export_events: false 114 | } 115 | stub_request(:post, "#{base_url}/exports"). 116 | with( body: data.to_json, 117 | headers: headers). 118 | to_return(status: 201, body: create_export_response) 119 | frontapp.create_export!(data) 120 | end 121 | 122 | it "can create a export for a team" do 123 | data = { 124 | inbox_id: "alt:address:team@planet-express.com", 125 | start: 1428889003, 126 | end: 1428889008, 127 | timezone: "America/New_York", 128 | should_export_events: false 129 | } 130 | stub_request(:post, "#{base_url}/teams/#{team_id}/exports"). 131 | with( body: data.to_json, 132 | headers: headers). 133 | to_return(status: 201, body: create_export_response) 134 | frontapp.create_export_for_team!(team_id, data) 135 | end 136 | end -------------------------------------------------------------------------------- /spec/fixtures/sample.txt: -------------------------------------------------------------------------------- 1 | I'm a sample attachment 2 | -------------------------------------------------------------------------------- /spec/hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Hash' do 5 | 6 | it "removes unpermitted keys from a Hash" do 7 | params = { 8 | name: "My thing", 9 | foo: "bar", 10 | settings: { 11 | url: "http//example.com", 12 | random_thing: "asdnkjrb" 13 | } 14 | } 15 | 16 | cleaned = params.permit(:name, settings: [:url]) 17 | 18 | expect(cleaned).to eq( { 19 | name: "My thing", 20 | settings: { 21 | url: "http//example.com", 22 | } 23 | }) 24 | end 25 | 26 | end -------------------------------------------------------------------------------- /spec/inboxes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Inboxes' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:inbox_id) { "inb_55c8c149" } 14 | let(:all_inboxes_response){ 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/inboxes" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 24 | "related": { 25 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 26 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 27 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 28 | } 29 | }, 30 | "id": "inb_55c8c149", 31 | "address": "team@planet-express.com", 32 | "type": "smtp", 33 | "name": "Team", 34 | "send_as": "team@planet-express.com" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | let(:get_inbox_response){ 41 | %Q{ 42 | { 43 | "_links": { 44 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 45 | "related": { 46 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 47 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 48 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 49 | } 50 | }, 51 | "id": "inb_55c8c149", 52 | "address": "team@planet-express.com", 53 | "type": "smtp", 54 | "name": "Team", 55 | "send_as": "team@planet-express.com" 56 | } 57 | } 58 | } 59 | let(:create_inbox_response){ 60 | %Q{ 61 | { 62 | "id": "inb_55c8c149", 63 | "name": "Delivery support" 64 | } 65 | } 66 | } 67 | let(:get_inbox_channels_response){ 68 | %Q{ 69 | { 70 | "_pagination": {}, 71 | "_links": { 72 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 73 | }, 74 | "_results": [ 75 | { 76 | "_links": { 77 | "self": "https://api2.frontapp.com/channels/cha_55c8c149", 78 | "related": { 79 | "inbox": "https://api2.frontapp.com/channels/cha_55c8c149/inbox" 80 | } 81 | }, 82 | "id": "cha_55c8c149", 83 | "address": "team@planet-express.com", 84 | "type": "smtp", 85 | "send_as": "team@planet-express.com", 86 | "settings": {} 87 | } 88 | ] 89 | } 90 | } 91 | } 92 | let(:get_inbox_conversations_response){ 93 | %Q{ 94 | { 95 | "_pagination": {}, 96 | "_links": { 97 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations" 98 | }, 99 | "_results": [ 100 | { 101 | "_links": { 102 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 103 | "related": { 104 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 105 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 106 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 107 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 108 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 109 | } 110 | }, 111 | "id": "cnv_55c8c149", 112 | "subject": "You broke my heart, Hubert.", 113 | "status": "archived", 114 | "assignee": { 115 | "_links": { 116 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 117 | "related": { 118 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 119 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 120 | } 121 | }, 122 | "id": "tea_55c8c149", 123 | "email": "leela@planet-express.com", 124 | "username": "leela", 125 | "first_name": "Leela", 126 | "last_name": "Turanga", 127 | "is_admin": true, 128 | "is_available": true 129 | }, 130 | "recipient": { 131 | "_links": { 132 | "related": { 133 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 134 | } 135 | }, 136 | "handle": "calculon@momsbot.com", 137 | "role": "to" 138 | }, 139 | "tags": [ 140 | { 141 | "_links": { 142 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 143 | "related": { 144 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 145 | } 146 | }, 147 | "id": "tag_55c8c149", 148 | "name": "Robots" 149 | } 150 | ], 151 | "last_message": { 152 | "_links": { 153 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 154 | "related": { 155 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 156 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 157 | } 158 | }, 159 | "id": "msg_55c8c149", 160 | "type": "email", 161 | "is_inbound": true, 162 | "created_at": 1453770984.123, 163 | "blurb": "Anything less than immortality is a...", 164 | "author": { 165 | "_links": { 166 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 167 | "related": { 168 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 169 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 170 | } 171 | }, 172 | "id": "tea_55c8c149", 173 | "email": "leela@planet-express.com", 174 | "username": "leela", 175 | "first_name": "Leela", 176 | "last_name": "Turanga", 177 | "is_admin": true, 178 | "is_available": true 179 | }, 180 | "recipients": [ 181 | { 182 | "_links": { 183 | "related": { 184 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 185 | } 186 | }, 187 | "handle": "calculon@momsbot.com", 188 | "role": "to" 189 | } 190 | ], 191 | "body": "Anything less than immortality is a complete waste of time.", 192 | "text": "Anything less than immortality is a complete waste of time.", 193 | "attachments": [ 194 | { 195 | "filename": "attachment.jpg", 196 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 197 | "content_type": "image/jpeg", 198 | "size": 10000, 199 | "metadata": { 200 | "is_inline": true, 201 | "cid": "123456789" 202 | } 203 | } 204 | ], 205 | "metadata": {} 206 | }, 207 | "created_at": 1453770984.123 208 | } 209 | ] 210 | } 211 | } 212 | } 213 | let(:get_inbox_teammates_response){ 214 | %Q{ 215 | { 216 | "_links": { 217 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates" 218 | }, 219 | "_results": [ 220 | { 221 | "_links": { 222 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 223 | "related": { 224 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 225 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 226 | } 227 | }, 228 | "id": "tea_55c8c149", 229 | "email": "leela@planet-express.com", 230 | "username": "leela", 231 | "first_name": "Leela", 232 | "last_name": "Turanga", 233 | "is_admin": true, 234 | "is_available": true 235 | } 236 | ] 237 | } 238 | } 239 | } 240 | 241 | it "can get all inboxes" do 242 | stub_request(:get, "#{base_url}/inboxes"). 243 | with( headers: headers). 244 | to_return(status: 200, body: all_inboxes_response) 245 | frontapp.inboxes 246 | end 247 | 248 | it "can get a specific inbox" do 249 | stub_request(:get, "#{base_url}/inboxes/#{inbox_id}"). 250 | with( headers: headers). 251 | to_return(status: 200, body: get_inbox_response) 252 | frontapp.get_inbox(inbox_id) 253 | end 254 | 255 | it "can create an inbox" do 256 | data = { 257 | name: "Delivery support", 258 | teammate_ids: [] 259 | } 260 | stub_request(:post, "#{base_url}/inboxes"). 261 | with( body: data.to_json, 262 | headers: headers). 263 | to_return(status: 201, body: create_inbox_response) 264 | frontapp.create_inbox!(data) 265 | end 266 | 267 | it "can get all channels for an inbox" do 268 | stub_request(:get, "#{base_url}/inboxes/#{inbox_id}/channels"). 269 | with( headers: headers). 270 | to_return(status: 200, body: get_inbox_channels_response) 271 | frontapp.get_inbox_channels(inbox_id) 272 | end 273 | 274 | it "can get all conversations in an inbox" do 275 | stub_request(:get, "#{base_url}/inboxes/#{inbox_id}/conversations"). 276 | with( headers: headers). 277 | to_return(status: 200, body: get_inbox_conversations_response) 278 | frontapp.get_inbox_conversations(inbox_id) 279 | end 280 | 281 | it "can get all teammates that can access an inbox" do 282 | stub_request(:get, "#{base_url}/inboxes/#{inbox_id}/teammates"). 283 | with( headers: headers). 284 | to_return(status: 200, body: get_inbox_teammates_response) 285 | frontapp.get_inbox_teammates(inbox_id) 286 | end 287 | end -------------------------------------------------------------------------------- /spec/links_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Links' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:link_id) { "top_55c8c149" } 14 | let(:link_conversations_response) { 15 | <<~JSON 16 | { 17 | "_pagination": {}, 18 | "_links": { 19 | "self": "https://api2.frontapp.com/links/top_55c8c149/conversations" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 25 | "related": { 26 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 27 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 28 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 29 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 30 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 31 | } 32 | }, 33 | "id": "cnv_55c8c149", 34 | "subject": "You broke my heart, Hubert.", 35 | "status": "archived", 36 | "assignee": { 37 | "_links": { 38 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 39 | "related": { 40 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 41 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 42 | } 43 | }, 44 | "id": "tea_55c8c149", 45 | "email": "leela@planet-express.com", 46 | "username": "leela", 47 | "first_name": "Leela", 48 | "last_name": "Turanga", 49 | "is_admin": true, 50 | "is_available": true 51 | }, 52 | "recipient": { 53 | "_links": { 54 | "related": { 55 | "contact": "https://api2.frontapp.com/contacts/crd_55c8c149" 56 | } 57 | }, 58 | "handle": "calculon@momsbot.com", 59 | "role": "to" 60 | }, 61 | "tags": [ 62 | { 63 | "_links": { 64 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 65 | "related": { 66 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations", 67 | "owner": "https://api2.frontapp.com/teams/tim_55c8c149" 68 | } 69 | }, 70 | "id": "tag_55c8c149", 71 | "name": "Robots", 72 | "is_private": false 73 | } 74 | ], 75 | "last_message": { 76 | "_links": { 77 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 78 | "related": { 79 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 80 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 81 | } 82 | }, 83 | "id": "msg_55c8c149", 84 | "type": "email", 85 | "is_inbound": true, 86 | "is_draft": false, 87 | "created_at": 1453770984.123, 88 | "blurb": "Anything less than immortality is a...", 89 | "author": { 90 | "_links": { 91 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 92 | "related": { 93 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 94 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 95 | } 96 | }, 97 | "id": "tea_55c8c149", 98 | "email": "leela@planet-express.com", 99 | "username": "leela", 100 | "first_name": "Leela", 101 | "last_name": "Turanga", 102 | "is_admin": true, 103 | "is_available": true 104 | }, 105 | "recipients": [ 106 | { 107 | "_links": { 108 | "related": { 109 | "contact": "https://api2.frontapp.com/contacts/crd_55c8c149" 110 | } 111 | }, 112 | "handle": "calculon@momsbot.com", 113 | "role": "to" 114 | } 115 | ], 116 | "body": "Anything less than immortality is a complete waste of time.", 117 | "text": "Anything less than immortality is a complete waste of time.", 118 | "attachments": [ 119 | { 120 | "filename": "attachment.jpg", 121 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 122 | "content_type": "image/jpeg", 123 | "size": 10000, 124 | "metadata": { 125 | "is_inline": true, 126 | "cid": "123456789" 127 | } 128 | } 129 | ], 130 | "metadata": {} 131 | }, 132 | "created_at": 1453770984.123, 133 | "is_private": false 134 | } 135 | ] 136 | } 137 | JSON 138 | } 139 | let(:links_response) do 140 | <<~JSON 141 | { 142 | "_pagination": { 143 | "next": null 144 | }, 145 | "_links": { 146 | "self": "https://api2.frontapp.com/links?" 147 | }, 148 | "_results": [ 149 | { 150 | "id": "top_55c8c149", 151 | "name": "123456", 152 | "type": "web", 153 | "external_url": "https://example.com/something/123456", 154 | "link": "https://example.com/something/123456" 155 | }, 156 | { 157 | "id": "top_55ih5t", 158 | "name": "www.google.com", 159 | "type": "web", 160 | "external_url": "www.google.com", 161 | "link": "www.google.com" 162 | }, 163 | { 164 | "id": "top_55i8f5", 165 | "name": "something.io", 166 | "type": "web", 167 | "external_url": "http://something.io", 168 | "link": "http://something.io" 169 | } 170 | ] 171 | } 172 | JSON 173 | end 174 | let(:get_link_response) do 175 | <<~JSON 176 | { 177 | "_links": {"self": "https://lending-home.api.frontapp.com/links/top_55c8c149"}, 178 | "id": "top_55c8c149", 179 | "name": "123456", 180 | "type": "web", 181 | "external_url": "https://example.com/something/123456", 182 | "link": "https://example.com/something/123456" 183 | } 184 | JSON 185 | end 186 | let(:create_link_response) do 187 | <<~JSON 188 | { 189 | "_links": {"self": "https://lending-home.api.frontapp.com/links/top_3ij8h"}, 190 | "id": "top_3ij8h", 191 | "name": "Bla", 192 | "type": "web", 193 | "external_url": "bla.io", 194 | "link": "bla.io" 195 | } 196 | JSON 197 | end 198 | 199 | it "can get all link conversations" do 200 | stub_request(:get, "#{base_url}/links/#{link_id}/conversations"). 201 | with( headers: headers). 202 | to_return(status: 200, body: link_conversations_response) 203 | frontapp.get_link_conversations(link_id) 204 | end 205 | 206 | it "can list links" do 207 | stub_request(:get, "#{base_url}/links"). 208 | with( headers: headers). 209 | to_return(status: 200, body: links_response) 210 | frontapp.links 211 | end 212 | 213 | it "can get a link" do 214 | stub_request(:get, "#{base_url}/links/#{link_id}"). 215 | with( headers: headers). 216 | to_return(status: 200, body: get_link_response) 217 | frontapp.get_link(link_id) 218 | end 219 | 220 | it "can create a link" do 221 | data = { 222 | name: "New link", 223 | external_url: "my.link" 224 | } 225 | stub_request(:post, "#{base_url}/links"). 226 | with( body: data.to_json, 227 | headers: headers). 228 | to_return(status: 200, body: create_link_response) 229 | end 230 | 231 | it "can update a link name" do 232 | data = { 233 | name: "new name" 234 | } 235 | stub_request(:patch, "#{base_url}/links/#{link_id}"). 236 | with( body: data.to_json, 237 | headers: headers). 238 | to_return(status: 204) 239 | frontapp.update_link!(link_id, data) 240 | end 241 | end 242 | -------------------------------------------------------------------------------- /spec/messages_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Messages' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:message_id) { "msg_55c8c149" } 14 | let(:channel_id) { "cha_55c8c149" } 15 | let(:conversation_id) { "cnv_55c8c149" } 16 | let(:inbox_id) { "inb_55c8c149" } 17 | let(:get_message_response) { 18 | %Q{ 19 | { 20 | "_links": { 21 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 22 | "related": { 23 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 24 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 25 | } 26 | }, 27 | "id": "msg_55c8c149", 28 | "type": "email", 29 | "is_inbound": true, 30 | "created_at": 1453770984.123, 31 | "blurb": "Anything less than immortality is a...", 32 | "author": { 33 | "_links": { 34 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 35 | "related": { 36 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 37 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 38 | } 39 | }, 40 | "id": "tea_55c8c149", 41 | "email": "leela@planet-express.com", 42 | "username": "leela", 43 | "first_name": "Leela", 44 | "last_name": "Turanga", 45 | "is_admin": true, 46 | "is_available": true 47 | }, 48 | "recipients": [ 49 | { 50 | "_links": { 51 | "related": { 52 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 53 | } 54 | }, 55 | "handle": "calculon@momsbot.com", 56 | "role": "to" 57 | } 58 | ], 59 | "body": "Anything less than immortality is a complete waste of time.", 60 | "text": "Anything less than immortality is a complete waste of time.", 61 | "attachments": [ 62 | { 63 | "filename": "attachment.jpg", 64 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 65 | "content_type": "image/jpeg", 66 | "size": 10000, 67 | "metadata": { 68 | "is_inline": true, 69 | "cid": "123456789" 70 | } 71 | } 72 | ], 73 | "metadata": {} 74 | } 75 | } 76 | } 77 | let(:message_source) { "Who wants to live forever?" } 78 | let(:send_message_from_channel_response) { 79 | %Q{ 80 | { 81 | "conversation_reference": "3b1q41d8@frontapp.com" 82 | } 83 | } 84 | } 85 | let(:receive_custom_message_response) { 86 | %Q{ 87 | { 88 | "conversation_reference": "3b1q41d8@frontapp.com" 89 | } 90 | } 91 | } 92 | let(:import_message_response) { 93 | %Q{ 94 | { 95 | "conversation_reference": "3b1q41d8@frontapp.com" 96 | } 97 | } 98 | } 99 | 100 | it "can get a specific message" do 101 | stub_request(:get, "#{base_url}/messages/#{message_id}"). 102 | with( headers: headers). 103 | to_return(status: 200, body: get_message_response) 104 | frontapp.get_message(message_id) 105 | end 106 | 107 | it "can get a specific message source" do 108 | headers['Accept'] = 'text/plain' 109 | stub_request(:get, "#{base_url}/messages/#{message_id}"). 110 | with( headers: headers). 111 | to_return(status: 200, body: message_source) 112 | frontapp.get_message_source(message_id) 113 | end 114 | 115 | it "can send a message from a channel" do 116 | data = { 117 | author_id: "alt:email:leela@planet-exress.com", 118 | sender_name: 'Leela', 119 | subject: "Good news everyone!", 120 | body: "Why is Zoidberg the only one still alone?", 121 | text: "Why is Zoidberg the only one still alone?", 122 | options: { 123 | tags: [], 124 | archive: true 125 | }, 126 | to: [ "calculon@momsbot.com" ], 127 | cc: [], 128 | bcc: [] 129 | } 130 | stub_request(:post, "#{base_url}/channels/#{channel_id}/messages"). 131 | with( body: data.to_json, 132 | headers: headers). 133 | to_return(status: 202, body: send_message_from_channel_response) 134 | frontapp.send_message(channel_id, data) 135 | end 136 | 137 | it "can send a reply" do 138 | data = { 139 | author_id: "alt:email:leela@planet-exress.com", 140 | sender_name: 'Leela', 141 | subject: "Good news everyone!", 142 | body: "Why is Zoidberg the only one still alone?", 143 | text: "Why is Zoidberg the only one still alone?", 144 | options: { 145 | tags: [], 146 | archive: true 147 | }, 148 | channel_id: "cha_55c8c149", 149 | to: [], 150 | cc: [], 151 | bcc: [] 152 | } 153 | stub_request(:post, "#{base_url}/conversations/#{conversation_id}/messages"). 154 | with( body: data.to_json, 155 | headers: headers). 156 | to_return(status: 202) 157 | frontapp.send_reply(conversation_id, data) 158 | end 159 | 160 | it "can receive a custom message in a channel" do 161 | data = { 162 | sender: { 163 | name: "hermes", 164 | handle: "hermes_123" 165 | }, 166 | subject: "Question", 167 | body: "Didn't we used to be a delivery company?", 168 | metadata: {} 169 | } 170 | stub_request(:post, "#{base_url}/channels/#{channel_id}/incoming_messages"). 171 | with( body: data.to_json, 172 | headers: headers). 173 | to_return(status: 202, body: receive_custom_message_response) 174 | frontapp.receive_custom_message(channel_id, data) 175 | end 176 | 177 | it "can import a message into an inbox" do 178 | data = { 179 | sender: { 180 | handle: "calculon@momsbot.com" 181 | }, 182 | to: [], 183 | cc: [], 184 | bcc: [], 185 | body: "", 186 | external_id: "", 187 | created_at: 1453770984.123, 188 | tags: [], 189 | metadata: { 190 | is_inbound: true, 191 | is_archived: true, 192 | should_skip_rules: true 193 | } 194 | } 195 | stub_request(:post, "#{base_url}/inboxes/#{inbox_id}/imported_messages"). 196 | with( body: data.to_json, 197 | headers: headers). 198 | to_return(status: 202, body: import_message_response) 199 | frontapp.import_message(inbox_id, data) 200 | end 201 | end -------------------------------------------------------------------------------- /spec/rules_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Rules' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:rule_id) { "rul_55c8c149" } 14 | let(:all_rules_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/rules" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/rules/rul_55c8c149" 24 | }, 25 | "id": "rul_55c8c149", 26 | "name": "Important deliveries", 27 | "actions": [ 28 | "Assign to Leela Turanga" 29 | ] 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | let(:get_rule_response) { 36 | %Q{ 37 | { 38 | "_links": { 39 | "self": "https://api2.frontapp.com/rules/rul_55c8c149" 40 | }, 41 | "id": "rul_55c8c149", 42 | "name": "Important deliveries", 43 | "actions": [ 44 | "Assign to Leela Turanga" 45 | ] 46 | } 47 | } 48 | } 49 | 50 | it "can get all rules" do 51 | stub_request(:get, "#{base_url}/rules"). 52 | with( headers: headers). 53 | to_return(status: 200, body: all_rules_response) 54 | frontapp.rules 55 | end 56 | 57 | it "can get a specific rule" do 58 | stub_request(:get, "#{base_url}/rules/#{rule_id}"). 59 | with( headers: headers). 60 | to_return(status: 200, body: get_rule_response) 61 | frontapp.get_rule(rule_id) 62 | end 63 | 64 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | 3 | # This file was generated by the `rspec --init` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # The generated `.rspec` file contains `--require spec_helper` which will cause 6 | # this file to always be loaded, without a need to explicitly require it in any 7 | # files. 8 | # 9 | # Given that it is always loaded, you are encouraged to keep this file as 10 | # light-weight as possible. Requiring heavyweight dependencies from this file 11 | # will add to the boot time of your test suite on EVERY test run, even for an 12 | # individual file that may not need all of that loaded. Instead, consider making 13 | # a separate helper file that requires the additional dependencies and performs 14 | # the additional setup, and require it from the spec files that actually need 15 | # it. 16 | # 17 | # The `.rspec` file also contains a few flags that are not defaults but that 18 | # users commonly want. 19 | # 20 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 21 | RSpec.configure do |config| 22 | # rspec-expectations config goes here. You can use an alternate 23 | # assertion/expectation library such as wrong or the stdlib/minitest 24 | # assertions if you prefer. 25 | config.expect_with :rspec do |expectations| 26 | # This option will default to `true` in RSpec 4. It makes the `description` 27 | # and `failure_message` of custom matchers include text for helper methods 28 | # defined using `chain`, e.g.: 29 | # be_bigger_than(2).and_smaller_than(4).description 30 | # # => "be bigger than 2 and smaller than 4" 31 | # ...rather than: 32 | # # => "be bigger than 2" 33 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 34 | end 35 | 36 | # rspec-mocks config goes here. You can use an alternate test double 37 | # library (such as bogus or mocha) by changing the `mock_with` option here. 38 | config.mock_with :rspec do |mocks| 39 | # Prevents you from mocking or stubbing a method that does not exist on 40 | # a real object. This is generally recommended, and will default to 41 | # `true` in RSpec 4. 42 | mocks.verify_partial_doubles = true 43 | end 44 | 45 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 46 | # have no way to turn it off -- the option exists only for backwards 47 | # compatibility in RSpec 3). It causes shared context metadata to be 48 | # inherited by the metadata hash of host groups and examples, rather than 49 | # triggering implicit auto-inclusion in groups with matching metadata. 50 | config.shared_context_metadata_behavior = :apply_to_host_groups 51 | 52 | # The settings below are suggested to provide a good initial experience 53 | # with RSpec, but feel free to customize to your heart's content. 54 | =begin 55 | # This allows you to limit a spec run to individual examples or groups 56 | # you care about by tagging them with `:focus` metadata. When nothing 57 | # is tagged with `:focus`, all examples get run. RSpec also provides 58 | # aliases for `it`, `describe`, and `context` that include `:focus` 59 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 60 | config.filter_run_when_matching :focus 61 | 62 | # Allows RSpec to persist some state between runs in order to support 63 | # the `--only-failures` and `--next-failure` CLI options. We recommend 64 | # you configure your source control system to ignore this file. 65 | config.example_status_persistence_file_path = "spec/examples.txt" 66 | 67 | # Limits the available syntax to the non-monkey patched syntax that is 68 | # recommended. For more details, see: 69 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 70 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 71 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 72 | config.disable_monkey_patching! 73 | 74 | # This setting enables warnings. It's recommended, but in some cases may 75 | # be too noisy due to issues in dependencies. 76 | config.warnings = true 77 | 78 | # Many RSpec users commonly either run the entire suite or an individual 79 | # file, and it's useful to allow more verbose output when running an 80 | # individual spec file. 81 | if config.files_to_run.one? 82 | # Use the documentation formatter for detailed output, 83 | # unless a formatter has already been configured 84 | # (e.g. via a command-line flag). 85 | config.default_formatter = 'doc' 86 | end 87 | 88 | # Print the 10 slowest examples and example groups at the 89 | # end of the spec run, to help surface which specs are running 90 | # particularly slow. 91 | config.profile_examples = 10 92 | 93 | # Run specs in random order to surface order dependencies. If you find an 94 | # order dependency and want to debug it, you can fix the order by providing 95 | # the seed, which is printed after each run. 96 | # --seed 1234 97 | config.order = :random 98 | 99 | # Seed global randomization in this process using the `--seed` CLI option. 100 | # Setting this allows you to use `--seed` to deterministically reproduce 101 | # test failures related to randomization by passing the same `--seed` value 102 | # as the one that triggered the failure. 103 | Kernel.srand config.seed 104 | =end 105 | end 106 | 107 | def base_url 108 | "https://api2.frontapp.com" 109 | end 110 | 111 | def auth_token 112 | "" 113 | end 114 | 115 | def standard_headers 116 | { 117 | "Accept" => "application/json", 118 | "Authorization" => "Bearer #{auth_token}", 119 | } 120 | end 121 | -------------------------------------------------------------------------------- /spec/tags_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Tags' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:tag_id) { "tag_55c8c149" } 14 | let(:all_tags_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/tags" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 24 | "related": { 25 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 26 | } 27 | }, 28 | "id": "tag_55c8c149", 29 | "name": "Robots" 30 | } 31 | ] 32 | } 33 | } 34 | } 35 | let(:get_tag_response) { 36 | %Q{ 37 | { 38 | "_links": { 39 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 40 | "related": { 41 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 42 | } 43 | }, 44 | "id": "tag_55c8c149", 45 | "name": "Robots" 46 | } 47 | } 48 | } 49 | let(:create_tag_response) { 50 | %Q{ 51 | { 52 | "_links": { 53 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 54 | "related": { 55 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 56 | } 57 | }, 58 | "id": "tag_55c8c149", 59 | "name": "New tag name" 60 | } 61 | } 62 | } 63 | let(:tag_conversations_response) { 64 | %Q{ 65 | { 66 | "_pagination": {}, 67 | "_links": { 68 | "self": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 69 | }, 70 | "_results": [ 71 | { 72 | "_links": { 73 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 74 | "related": { 75 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 76 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 77 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 78 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 79 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 80 | } 81 | }, 82 | "id": "cnv_55c8c149", 83 | "subject": "You broke my heart, Hubert.", 84 | "status": "archived", 85 | "assignee": { 86 | "_links": { 87 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 88 | "related": { 89 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 90 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 91 | } 92 | }, 93 | "id": "tea_55c8c149", 94 | "email": "leela@planet-express.com", 95 | "username": "leela", 96 | "first_name": "Leela", 97 | "last_name": "Turanga", 98 | "is_admin": true, 99 | "is_available": true 100 | }, 101 | "recipient": { 102 | "_links": { 103 | "related": { 104 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 105 | } 106 | }, 107 | "handle": "calculon@momsbot.com", 108 | "role": "to" 109 | }, 110 | "tags": [ 111 | { 112 | "_links": { 113 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 114 | "related": { 115 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 116 | } 117 | }, 118 | "id": "tag_55c8c149", 119 | "name": "Robots" 120 | } 121 | ], 122 | "last_message": { 123 | "_links": { 124 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 125 | "related": { 126 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 127 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 128 | } 129 | }, 130 | "id": "msg_55c8c149", 131 | "type": "email", 132 | "is_inbound": true, 133 | "created_at": 1453770984.123, 134 | "blurb": "Anything less than immortality is a...", 135 | "author": { 136 | "_links": { 137 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 138 | "related": { 139 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 140 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 141 | } 142 | }, 143 | "id": "tea_55c8c149", 144 | "email": "leela@planet-express.com", 145 | "username": "leela", 146 | "first_name": "Leela", 147 | "last_name": "Turanga", 148 | "is_admin": true, 149 | "is_available": true 150 | }, 151 | "recipients": [ 152 | { 153 | "_links": { 154 | "related": { 155 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 156 | } 157 | }, 158 | "handle": "calculon@momsbot.com", 159 | "role": "to" 160 | } 161 | ], 162 | "body": "Anything less than immortality is a complete waste of time.", 163 | "text": "Anything less than immortality is a complete waste of time.", 164 | "attachments": [ 165 | { 166 | "filename": "attachment.jpg", 167 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 168 | "content_type": "image/jpeg", 169 | "size": 10000, 170 | "metadata": { 171 | "is_inline": true, 172 | "cid": "123456789" 173 | } 174 | } 175 | ], 176 | "metadata": {} 177 | }, 178 | "created_at": 1453770984.123 179 | } 180 | ] 181 | } 182 | } 183 | } 184 | 185 | it "can get all tags" do 186 | stub_request(:get, "#{base_url}/tags"). 187 | with( headers: headers). 188 | to_return(status: 200, body: all_tags_response) 189 | frontapp.tags 190 | end 191 | 192 | it "can get a specific tag" do 193 | stub_request(:get, "#{base_url}/tags/#{tag_id}"). 194 | with( headers: headers). 195 | to_return(status: 200, body: get_tag_response) 196 | frontapp.get_tag(tag_id) 197 | end 198 | 199 | it "can create a tag" do 200 | data = { name: "New tag name" } 201 | stub_request(:post, "#{base_url}/tags"). 202 | with( body: data.to_json, 203 | headers: headers). 204 | to_return(status: 201, body: create_tag_response) 205 | frontapp.create_tag!(data) 206 | end 207 | 208 | it "can delete a tag" do 209 | stub_request(:delete, "#{base_url}/tags/#{tag_id}"). 210 | with( headers: headers). 211 | to_return(status: 204, body: nil) 212 | frontapp.delete_tag!(tag_id) 213 | end 214 | 215 | 216 | it "can get all tag conversations" do 217 | stub_request(:get, "#{base_url}/tags/#{tag_id}/conversations"). 218 | with( headers: headers). 219 | to_return(status: 200, body: tag_conversations_response) 220 | frontapp.get_tag_conversations(tag_id) 221 | end 222 | end -------------------------------------------------------------------------------- /spec/teammates_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Teammates' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:teammate_id) { "tea_55c8c149" } 14 | let(:all_teammates_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/teammates" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 24 | "related": { 25 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 26 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 27 | } 28 | }, 29 | "id": "tea_55c8c149", 30 | "email": "leela@planet-express.com", 31 | "username": "leela", 32 | "first_name": "Leela", 33 | "last_name": "Turanga", 34 | "is_admin": true, 35 | "is_available": true 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | let(:get_teammate_response) { 42 | %Q{ 43 | { 44 | "_links": { 45 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 46 | "related": { 47 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 48 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 49 | } 50 | }, 51 | "id": "tea_55c8c149", 52 | "email": "leela@planet-express.com", 53 | "username": "leela", 54 | "first_name": "Leela", 55 | "last_name": "Turanga", 56 | "is_admin": true, 57 | "is_available": true 58 | } 59 | } 60 | } 61 | let(:get_teammate_conversations_response) { 62 | %Q{ 63 | { 64 | "_pagination": {}, 65 | "_links": { 66 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 67 | }, 68 | "_results": [ 69 | { 70 | "_links": { 71 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 72 | "related": { 73 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 74 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 75 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 76 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 77 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 78 | } 79 | }, 80 | "id": "cnv_55c8c149", 81 | "subject": "You broke my heart, Hubert.", 82 | "status": "archived", 83 | "assignee": { 84 | "_links": { 85 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 86 | "related": { 87 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 88 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 89 | } 90 | }, 91 | "id": "tea_55c8c149", 92 | "email": "leela@planet-express.com", 93 | "username": "leela", 94 | "first_name": "Leela", 95 | "last_name": "Turanga", 96 | "is_admin": true, 97 | "is_available": true 98 | }, 99 | "recipient": { 100 | "_links": { 101 | "related": { 102 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 103 | } 104 | }, 105 | "handle": "calculon@momsbot.com", 106 | "role": "to" 107 | }, 108 | "tags": [ 109 | { 110 | "_links": { 111 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 112 | "related": { 113 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 114 | } 115 | }, 116 | "id": "tag_55c8c149", 117 | "name": "Robots" 118 | } 119 | ], 120 | "last_message": { 121 | "_links": { 122 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 123 | "related": { 124 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 125 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 126 | } 127 | }, 128 | "id": "msg_55c8c149", 129 | "type": "email", 130 | "is_inbound": true, 131 | "created_at": 1453770984.123, 132 | "blurb": "Anything less than immortality is a...", 133 | "author": { 134 | "_links": { 135 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 136 | "related": { 137 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 138 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 139 | } 140 | }, 141 | "id": "tea_55c8c149", 142 | "email": "leela@planet-express.com", 143 | "username": "leela", 144 | "first_name": "Leela", 145 | "last_name": "Turanga", 146 | "is_admin": true, 147 | "is_available": true 148 | }, 149 | "recipients": [ 150 | { 151 | "_links": { 152 | "related": { 153 | "contact": "https://api2.frontapp.com/contacts/ctc_55c8c149" 154 | } 155 | }, 156 | "handle": "calculon@momsbot.com", 157 | "role": "to" 158 | } 159 | ], 160 | "body": "Anything less than immortality is a complete waste of time.", 161 | "text": "Anything less than immortality is a complete waste of time.", 162 | "attachments": [ 163 | { 164 | "filename": "attachment.jpg", 165 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 166 | "content_type": "image/jpeg", 167 | "size": 10000, 168 | "metadata": { 169 | "is_inline": true, 170 | "cid": "123456789" 171 | } 172 | } 173 | ], 174 | "metadata": {} 175 | }, 176 | "created_at": 1453770984.123 177 | } 178 | ] 179 | } 180 | } 181 | } 182 | let(:get_teammate_inboxes_response) { 183 | %Q{ 184 | { 185 | "_links": { 186 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes" 187 | }, 188 | "_results": [ 189 | { 190 | "_links": { 191 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 192 | "related": { 193 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 194 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 195 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels" 196 | } 197 | }, 198 | "id": "inb_55c8c149", 199 | "address": "team@planet-express.com", 200 | "type": "smtp", 201 | "name": "Team", 202 | "send_as": "team@planet-express.com" 203 | } 204 | ] 205 | } 206 | } 207 | } 208 | 209 | 210 | it "can get all teammates" do 211 | stub_request(:get, "#{base_url}/teammates"). 212 | with( headers: headers). 213 | to_return(status: 200, body: all_teammates_response) 214 | frontapp.teammates 215 | end 216 | 217 | it "can get a specific teammate" do 218 | stub_request(:get, "#{base_url}/teammates/#{teammate_id}"). 219 | with( headers: headers). 220 | to_return(status: 200, body: get_teammate_response) 221 | frontapp.get_teammate(teammate_id) 222 | end 223 | 224 | it "can update a teammate" do 225 | data = { 226 | username: "bender", 227 | first_name: "Bender", 228 | last_name: "Rodriguez", 229 | is_admin: true, 230 | is_available: false 231 | } 232 | stub_request(:patch, "#{base_url}/teammates/#{teammate_id}"). 233 | with( headers: headers). 234 | to_return(status: 204) 235 | frontapp.update_teammate!(teammate_id, data) 236 | end 237 | 238 | it "can get all conversations assigned to a teammate" do 239 | stub_request(:get, "#{base_url}/teammates/#{teammate_id}/conversations"). 240 | with( headers: headers). 241 | to_return(status: 200, body: get_teammate_conversations_response) 242 | frontapp.get_teammate_conversations(teammate_id) 243 | end 244 | 245 | it "can get all inboxes a teammate has access to" do 246 | stub_request(:get, "#{base_url}/teammates/#{teammate_id}/inboxes"). 247 | with( headers: headers). 248 | to_return(status: 200, body: get_teammate_inboxes_response) 249 | frontapp.get_teammate_inboxes(teammate_id) 250 | end 251 | end -------------------------------------------------------------------------------- /spec/teams_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Teams' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:team_id) { "tea_55c8c149" } 14 | let(:all_teams_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/teams" 19 | }, 20 | "_results": [ 21 | { 22 | "_links": { 23 | "self": "https://api2.frontapp.com/teams/tim_55c8c149" 24 | }, 25 | "id": "tim_55c8c149", 26 | "name": "Delivery" 27 | } 28 | ] 29 | } 30 | } 31 | } 32 | let(:get_team_response) { 33 | %Q{ 34 | { 35 | "_links": { 36 | "self": "https://api2.frontapp.com/teams/tim_55c8c149" 37 | }, 38 | "id": "tim_55c8c149", 39 | "name": "Delivery", 40 | "inboxes": [ 41 | { 42 | "_links": { 43 | "self": "https://api2.frontapp.com/inboxes/inb_55c8c149", 44 | "related": { 45 | "teammates": "https://api2.frontapp.com/inboxes/inb_55c8c149/teammates", 46 | "conversations": "https://api2.frontapp.com/inboxes/inb_55c8c149/conversations", 47 | "channels": "https://api2.frontapp.com/inboxes/inb_55c8c149/channels", 48 | "owner": "https://api2.frontapp.com/teams/tim_55c8c149" 49 | } 50 | }, 51 | "id": "inb_55c8c149", 52 | "name": "Team", 53 | "is_private": false 54 | } 55 | ], 56 | "members": [ 57 | { 58 | "_links": { 59 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 60 | "related": { 61 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 62 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 63 | } 64 | }, 65 | "id": "tea_55c8c149", 66 | "email": "leela@planet-express.com", 67 | "username": "leela", 68 | "first_name": "Leela", 69 | "last_name": "Turanga", 70 | "is_admin": true, 71 | "is_available": true 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | 78 | it "can get all teams" do 79 | stub_request(:get, "#{base_url}/teams"). 80 | with( headers: headers). 81 | to_return(status: 200, body: all_teams_response) 82 | frontapp.teams 83 | end 84 | 85 | it "can get a specific team" do 86 | stub_request(:get, "#{base_url}/teams/#{team_id}"). 87 | with( headers: headers). 88 | to_return(status: 200, body: get_team_response) 89 | frontapp.get_team(team_id) 90 | end 91 | 92 | end -------------------------------------------------------------------------------- /spec/topics_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'Topics' do 5 | 6 | let(:headers) { 7 | { 8 | "Accept" => "application/json", 9 | "Authorization" => "Bearer #{auth_token}", 10 | } 11 | } 12 | let(:frontapp) { Frontapp::Client.new(auth_token: auth_token) } 13 | let(:topic_id) { "top_55c8c149" } 14 | let(:topic_conversations_response) { 15 | %Q{ 16 | { 17 | "_pagination": {}, 18 | "_links": { 19 | "self": "https://api2.frontapp.com/topics/top_55c8c149/conversations" 20 | }, 21 | "_results": [ 22 | { 23 | "_links": { 24 | "self": "https://api2.frontapp.com/conversations/cnv_55c8c149", 25 | "related": { 26 | "events": "https://api2.frontapp.com/conversations/cnv_55c8c149/events", 27 | "followers": "https://api2.frontapp.com/conversations/cnv_55c8c149/followers", 28 | "messages": "https://api2.frontapp.com/conversations/cnv_55c8c149/messages", 29 | "comments": "https://api2.frontapp.com/conversations/cnv_55c8c149/comments", 30 | "inboxes": "https://api2.frontapp.com/conversations/cnv_55c8c149/inboxes" 31 | } 32 | }, 33 | "id": "cnv_55c8c149", 34 | "subject": "You broke my heart, Hubert.", 35 | "status": "archived", 36 | "assignee": { 37 | "_links": { 38 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 39 | "related": { 40 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 41 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 42 | } 43 | }, 44 | "id": "tea_55c8c149", 45 | "email": "leela@planet-express.com", 46 | "username": "leela", 47 | "first_name": "Leela", 48 | "last_name": "Turanga", 49 | "is_admin": true, 50 | "is_available": true 51 | }, 52 | "recipient": { 53 | "_links": { 54 | "related": { 55 | "contact": "https://api2.frontapp.com/contacts/crd_55c8c149" 56 | } 57 | }, 58 | "handle": "calculon@momsbot.com", 59 | "role": "to" 60 | }, 61 | "tags": [ 62 | { 63 | "_links": { 64 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 65 | "related": { 66 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations", 67 | "owner": "https://api2.frontapp.com/teams/tim_55c8c149" 68 | } 69 | }, 70 | "id": "tag_55c8c149", 71 | "name": "Robots", 72 | "is_private": false 73 | } 74 | ], 75 | "last_message": { 76 | "_links": { 77 | "self": "https://api2.frontapp.com/messages/msg_55c8c149", 78 | "related": { 79 | "conversation": "https://api2.frontapp.com/conversations/cnv_55c8c149", 80 | "message_replied_to": "https://api2.frontapp.com/messages/msg_1ab23cd4" 81 | } 82 | }, 83 | "id": "msg_55c8c149", 84 | "type": "email", 85 | "is_inbound": true, 86 | "is_draft": false, 87 | "created_at": 1453770984.123, 88 | "blurb": "Anything less than immortality is a...", 89 | "author": { 90 | "_links": { 91 | "self": "https://api2.frontapp.com/teammates/tea_55c8c149", 92 | "related": { 93 | "inboxes": "https://api2.frontapp.com/teammates/tea_55c8c149/inboxes", 94 | "conversations": "https://api2.frontapp.com/teammates/tea_55c8c149/conversations" 95 | } 96 | }, 97 | "id": "tea_55c8c149", 98 | "email": "leela@planet-express.com", 99 | "username": "leela", 100 | "first_name": "Leela", 101 | "last_name": "Turanga", 102 | "is_admin": true, 103 | "is_available": true 104 | }, 105 | "recipients": [ 106 | { 107 | "_links": { 108 | "related": { 109 | "contact": "https://api2.frontapp.com/contacts/crd_55c8c149" 110 | } 111 | }, 112 | "handle": "calculon@momsbot.com", 113 | "role": "to" 114 | } 115 | ], 116 | "body": "Anything less than immortality is a complete waste of time.", 117 | "text": "Anything less than immortality is a complete waste of time.", 118 | "attachments": [ 119 | { 120 | "filename": "attachment.jpg", 121 | "url": "https://api2.frontapp.com/download/fil_55c8c149", 122 | "content_type": "image/jpeg", 123 | "size": 10000, 124 | "metadata": { 125 | "is_inline": true, 126 | "cid": "123456789" 127 | } 128 | } 129 | ], 130 | "metadata": {} 131 | }, 132 | "created_at": 1453770984.123, 133 | "is_private": false 134 | } 135 | ] 136 | } 137 | } 138 | } 139 | end 140 | -------------------------------------------------------------------------------- /spec/user_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'frontapp' 3 | 4 | RSpec.describe 'User agent' do 5 | let(:headers) { 6 | { 7 | "Accept" => "application/json", 8 | "Authorization" => "Bearer #{auth_token}", 9 | } 10 | } 11 | let(:create_tag_data) { 12 | {name: "New tag name"} 13 | } 14 | let(:create_tag_response) { 15 | %Q{ 16 | { 17 | "_links": { 18 | "self": "https://api2.frontapp.com/tags/tag_55c8c149", 19 | "related": { 20 | "conversations": "https://api2.frontapp.com/tags/tag_55c8c149/conversations" 21 | } 22 | }, 23 | "id": "tag_55c8c149", 24 | "name": "New tag name" 25 | } 26 | } 27 | } 28 | 29 | it "has a default user agent" do 30 | stub_request(:post, "#{base_url}/tags"). 31 | with( 32 | body: create_tag_data.to_json, 33 | headers: headers.merge("User-Agent": "Frontapp Ruby Gem #{Frontapp::VERSION}")). 34 | to_return(status: 201, body: create_tag_response) 35 | frontapp = Frontapp::Client.new(auth_token: auth_token) 36 | expect do 37 | frontapp.create_tag!(create_tag_data) 38 | end.to_not raise_error 39 | end 40 | 41 | it "can have a custom user agent" do 42 | user_agent = "Eye-Phone Integration (engineering@planet-express.com)" 43 | stub_request(:post, "#{base_url}/tags"). 44 | with( 45 | body: create_tag_data.to_json, 46 | headers: headers.merge("User-Agent": user_agent)). 47 | to_return(status: 201, body: create_tag_response) 48 | frontapp = Frontapp::Client.new(auth_token: auth_token, user_agent: user_agent) 49 | expect do 50 | frontapp.create_tag!(create_tag_data) 51 | end 52 | end 53 | end 54 | --------------------------------------------------------------------------------