discourse_api 0.45.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -17
  3. data/.gitignore +0 -19
  4. data/CHANGELOG.md +39 -0
  5. data/discourse_api.gemspec +4 -4
  6. data/examples/bookmark_topic.rb +15 -0
  7. data/examples/category.rb +3 -0
  8. data/examples/invite_users.rb +21 -3
  9. data/examples/notifications.rb +18 -0
  10. data/examples/topic_lists.rb +3 -0
  11. data/lib/discourse_api/api/categories.rb +37 -11
  12. data/lib/discourse_api/api/dashboard.rb +2 -2
  13. data/lib/discourse_api/api/groups.rb +15 -4
  14. data/lib/discourse_api/api/invite.rb +67 -2
  15. data/lib/discourse_api/api/notifications.rb +5 -2
  16. data/lib/discourse_api/api/private_messages.rb +10 -3
  17. data/lib/discourse_api/api/search.rb +1 -1
  18. data/lib/discourse_api/api/site_settings.rb +3 -3
  19. data/lib/discourse_api/api/topics.rb +30 -2
  20. data/lib/discourse_api/api/users.rb +4 -0
  21. data/lib/discourse_api/client.rb +13 -1
  22. data/lib/discourse_api/error.rb +3 -0
  23. data/lib/discourse_api/single_sign_on.rb +6 -6
  24. data/lib/discourse_api/version.rb +1 -1
  25. data/spec/discourse_api/api/categories_spec.rb +90 -0
  26. data/spec/discourse_api/api/groups_spec.rb +29 -0
  27. data/spec/discourse_api/api/invite_spec.rb +123 -0
  28. data/spec/discourse_api/api/private_messages_spec.rb +4 -4
  29. data/spec/discourse_api/api/search_spec.rb +2 -2
  30. data/spec/discourse_api/api/topics_spec.rb +69 -3
  31. data/spec/discourse_api/api/users_spec.rb +14 -0
  32. data/spec/discourse_api/client_spec.rb +21 -0
  33. data/spec/fixtures/notification_success.json +3 -0
  34. data/spec/fixtures/retrieve_invite.json +116 -0
  35. data/spec/fixtures/top.json +108 -0
  36. data/spec/fixtures/topic_posts.json +1 -0
  37. metadata +17 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82cd748487d5d986f8327b702088ea4ea22c43cae311c2634dfa5d9cf4c4059f
4
- data.tar.gz: a4fd68101134895f5071443f221a2327e9fe9e4a0a79df0d53a620c7e08d685d
3
+ metadata.gz: '047378d84854b5dae025d57550c071bf9944257a4048bd1be5b51b389e995dc3'
4
+ data.tar.gz: af75df86361c631aba91dc685c26e2f0d392597fbd561ef93b628f319f907457
5
5
  SHA512:
6
- metadata.gz: 42546f17e134cd1e4ac6be81c8fdb53cf82461d60a73859dd757c2ec69e1128127f33a131e45dfec3d910012702256a173a7a1afd2a9873dc3d8a3e220c4af1a
7
- data.tar.gz: 990fc89fd2a4935704e1512b858872b41ed7a6b7ba3ef28f583ad272e0675daff74f61b9a441632a8dc92c76892b8c507e53afce356317403c7c67f31a4fab18
6
+ metadata.gz: 7a435c702c930231c817d8192a20ddd859c3102cb3a9928ad4402dc1c019330d4dca79bd407f00ffa9e9038cebe7a0c138c081a019f4373711bfea67104c1a00
7
+ data.tar.gz: 8664b7d0efa3982e223a61af2a7d28ea2744100fdf1f3dcef99c9c1c3f7c59481c264ac04092c9b0c4c6a5f7559bd1fa99c4010d69fb1451f1a7875678a06d19
@@ -5,8 +5,7 @@ on:
5
5
  push:
6
6
  branches:
7
7
  - master
8
- tags:
9
- - v*
8
+ - main
10
9
 
11
10
  jobs:
12
11
  build:
@@ -18,30 +17,25 @@ jobs:
18
17
  - 2.5
19
18
  - 2.6
20
19
  - 2.7
20
+ - 3.0
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v1
23
+ - uses: actions/checkout@v2
24
24
 
25
25
  - name: Setup ruby
26
- uses: actions/setup-ruby@v1
26
+ uses: ruby/setup-ruby@v1
27
27
  with:
28
28
  ruby-version: ${{ matrix.ruby }}
29
- architecture: 'x64'
30
-
31
- - name: Setup bundler
32
- run: gem install bundler
33
-
34
- - name: Setup gems
35
- run: bundle install
29
+ bundler-cache: true
36
30
 
37
- - name: Rubocop
31
+ - name: Lint
38
32
  run: bundle exec rubocop
39
33
 
40
- - name: RSpec
41
- run: bundle exec rspec
34
+ - name: Tests
35
+ run: bundle exec rake test
42
36
 
43
37
  publish:
44
- if: contains(github.ref, 'refs/tags/v')
38
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
45
39
  needs: build
46
40
  runs-on: ubuntu-latest
47
41
 
@@ -49,6 +43,8 @@ jobs:
49
43
  - uses: actions/checkout@v2
50
44
 
51
45
  - name: Release Gem
52
- uses: CvX/publish-rubygems-action@master
46
+ uses: discourse/publish-rubygems-action@v2-beta
53
47
  env:
54
- RUBYGEMS_API_KEY: ${{secrets.RUBYGEMS_API_KEY}}
48
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
49
+ GIT_EMAIL: team@discourse.org
50
+ GIT_NAME: discoursebot
data/.gitignore CHANGED
@@ -1,22 +1,3 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
1
  Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
2
  coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- bin/
19
- .ruby-gemset
20
- .ruby-version
21
- .env
22
3
  /config.yml
data/CHANGELOG.md CHANGED
@@ -6,6 +6,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.48.0] - 2022-01-28
10
+ ### Added
11
+ - `group_add_owners` method (#239)
12
+ - `group_remove_owners` method (#239)
13
+ - `anonymize` method (#241)
14
+
15
+ ### Changed
16
+ - `DiscourseApi::Timeout` error now inherits from `DiscourseApi::Error` (#240)
17
+ - `DiscourseApi::SingleSignOn#groups` now returns an array of strings where each string is a group name, rather than an array with a single string that contains all the groups comma-concatenated (#243)
18
+
19
+ ## [0.47.0] - 2021-07-19
20
+ ### Added
21
+ - Update invite method
22
+ - Retrieve invite method
23
+ - Destroy all expired invites method
24
+ - Destroy invite method
25
+ - Resend all invites method
26
+ - Resend invite method
27
+ - Pass params to get notifications API
28
+
29
+ ### Deprecated
30
+ - `invite_user_to_topic` has been deprecated, use `invite_to_topic` instead.
31
+ - `create_private_message` has been deprecated, use `create_pm` instead.
32
+
33
+ ## [0.46.0] - 2021-04-12
34
+ ### Added
35
+ - Allow bookmarking topics
36
+ - Add timeout to requests
37
+ - Add params to get_topic_posts
38
+
39
+ ## [0.45.1] - 2021-03-11
40
+ ### Added
41
+ - Fetch global top topics
42
+ - Allow setting topic notifications
43
+ - Return full category response
44
+
45
+ ### Changed
46
+ - Use new search endpoint
47
+
9
48
  ## [0.45.0] - 2021-01-15
10
49
  ### Added
11
50
  - Tag configuration in create_category/update_category
@@ -18,9 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'faraday', '~> 1.0'
22
- spec.add_dependency 'faraday_middleware', '~> 1.0'
23
- spec.add_dependency 'rack', '>= 1.6'
21
+ spec.add_runtime_dependency 'faraday', '~> 1.0'
22
+ spec.add_runtime_dependency 'faraday_middleware', '~> 1.0'
23
+ spec.add_runtime_dependency 'rack', '>= 1.6'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '~> 2.0'
26
26
  spec.add_development_dependency 'guard', '~> 2.14'
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'rspec', '~> 3.4'
31
31
  spec.add_development_dependency 'simplecov', '~> 0.11'
32
32
  spec.add_development_dependency 'webmock', '~> 3.0'
33
- spec.add_development_dependency 'rubocop-discourse'
33
+ spec.add_development_dependency 'rubocop-discourse', '~> 2.4.1'
34
34
 
35
35
  spec.required_ruby_version = '>= 2.5.0'
36
36
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require File.expand_path('../../lib/discourse_api', __FILE__)
4
+
5
+ config = DiscourseApi::ExampleHelper.load_yml
6
+
7
+ client = DiscourseApi::Client.new(config['host'] || 'http://localhost:3000')
8
+ client.api_key = config['api_key'] || "YOUR_API_KEY"
9
+ client.api_username = config['api_username'] || "YOUR_USERNAME"
10
+
11
+ # Bookmark topic
12
+ puts client.bookmark_topic(1418)
13
+
14
+ # Remove bookmark from topic
15
+ puts client.remove_topic_bookmark(1418)
data/examples/category.rb CHANGED
@@ -14,6 +14,9 @@ puts client.categories()
14
14
  # get sub categories for parent category with id 2
15
15
  puts client.categories(parent_category_id: 2)
16
16
 
17
+ # get the full categories response
18
+ puts client.categories_full()
19
+
17
20
  # List topics in a category
18
21
  category_topics = client.category_latest_topics(category_slug: "test-category")
19
22
  puts category_topics
@@ -9,10 +9,28 @@ client.api_key = config['api_key'] || "YOUR_API_KEY"
9
9
  client.api_username = config['api_username'] || "YOUR_USERNAME"
10
10
 
11
11
  # invite user
12
- client.invite_user(email: "name@example.com", group_ids: "41,42")
12
+ invite = client.invite_user(email: "name@example.com", group_ids: "41,42")
13
+
14
+ #update invite
15
+ client.update_invite(invite["id"], email: "namee@example.com")
16
+
17
+ # resend invite
18
+ client.resend_invite("namee@example.com")
13
19
 
14
20
  # invite to a topic
15
- client.invite_user_to_topic(email: "foo@bar.com", topic_id: 1)
21
+ client.invite_to_topic(1, email: "foo@bar.com")
16
22
 
17
23
  # if the user is an admin you may invite to a group as well
18
- client.invite_user_to_topic(email: "foo@bar.com", topic_id: 1, group_ids: "1,2,3")
24
+ client.invite_to_topic(1, email: "foo@bar.com", group_ids: "1,2,3")
25
+
26
+ # retrieve invite
27
+ puts client.retrieve_invite(email: "foo@bar.com")
28
+
29
+ # resend all invites
30
+ client.resend_all_invites
31
+
32
+ # destroy invite
33
+ client.destroy_invite(invite["id"])
34
+
35
+ # destroy all expired invites
36
+ client.destroy_all_expired_invites
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require File.expand_path('../../lib/discourse_api', __FILE__)
4
+
5
+ config = DiscourseApi::ExampleHelper.load_yml
6
+
7
+ client = DiscourseApi::Client.new(config['host'] || 'http://localhost:3000')
8
+ client.api_key = config['api_key'] || "YOUR_API_KEY"
9
+ client.api_username = config['api_username'] || "YOUR_USERNAME"
10
+
11
+ # watch an entire category
12
+ client.category_set_user_notification_level(1, notification_level: 3)
13
+
14
+ # mute a topic
15
+ client.topic_set_user_notification_level(1, notification_level: 0)
16
+
17
+ # get user notifications
18
+ client.notifications(username: 'discourse')
@@ -11,6 +11,9 @@ client.api_username = config['api_username'] || "YOUR_USERNAME"
11
11
  # get latest topics
12
12
  puts client.latest_topics({})
13
13
 
14
+ # get top topics
15
+ puts client.top_topics
16
+
14
17
  # recategorize topic
15
18
  puts client.recategorize_topic(topic_id: 108, category_id: 5)
16
19
 
@@ -36,11 +36,24 @@ module DiscourseApi
36
36
  end
37
37
 
38
38
  def categories(params = {})
39
+ categories_full(params)['category_list']['categories']
40
+ end
41
+
42
+ def categories_full(params = {})
39
43
  response = get('/categories.json', params)
40
- response[:body]['category_list']['categories']
44
+ response[:body]
41
45
  end
42
46
 
43
47
  def category_latest_topics(args = {})
48
+ response = category_latest_topics_full(args)
49
+ if response['errors']
50
+ response['errors']
51
+ else
52
+ response['topic_list']['topics']
53
+ end
54
+ end
55
+
56
+ def category_latest_topics_full(args = {})
44
57
  params = API.params(args)
45
58
  .required(:category_slug)
46
59
  .optional(:page).to_h
@@ -49,25 +62,31 @@ module DiscourseApi
49
62
  url = "#{url}?page=#{params[:page]}"
50
63
  end
51
64
  response = get(url)
52
- if response[:body]['errors']
53
- response[:body]['errors']
54
- else
55
- response[:body]['topic_list']['topics']
56
- end
65
+ response[:body]
57
66
  end
58
67
 
59
68
  def category_top_topics(category_slug)
60
- response = get("/c/#{category_slug}/l/top.json")
61
- if response[:body]['errors']
62
- response[:body]['errors']
69
+ response = category_top_topics_full(category_slug)
70
+ if response['errors']
71
+ response['errors']
63
72
  else
64
- response[:body]['topic_list']['topics']
73
+ response['topic_list']['topics']
65
74
  end
66
75
  end
67
76
 
77
+ def category_top_topics_full(category_slug)
78
+ response = get("/c/#{category_slug}/l/top.json")
79
+ response[:body]
80
+ end
81
+
68
82
  def category_new_topics(category_slug)
83
+ response = category_new_topics_full(category_slug)
84
+ response['topic_list']['topics']
85
+ end
86
+
87
+ def category_new_topics_full(category_slug)
69
88
  response = get("/c/#{category_slug}/l/new.json")
70
- response[:body]['topic_list']['topics']
89
+ response[:body]
71
90
  end
72
91
 
73
92
  def category(id)
@@ -75,12 +94,19 @@ module DiscourseApi
75
94
  response[:body]['category']
76
95
  end
77
96
 
97
+ # TODO: Deprecated. Remove after 20210727
78
98
  def category_set_user_notification(args = {})
79
99
  category_id = args[:id]
80
100
  args = API.params(args)
81
101
  .required(:notification_level)
82
102
  post("/category/#{category_id}/notifications", args)
83
103
  end
104
+
105
+ def category_set_user_notification_level(category_id, params)
106
+ params = API.params(params)
107
+ .required(:notification_level)
108
+ post("/category/#{category_id}/notifications", params)
109
+ end
84
110
  end
85
111
  end
86
112
  end
@@ -14,10 +14,10 @@ module DiscourseApi
14
14
  topics = global_reports[3]
15
15
  posts = global_reports[4]
16
16
 
17
- totals = {
17
+ {
18
18
  'users' => users['total'],
19
19
  'topics' => topics['total'],
20
- 'posts' => posts['total']
20
+ 'posts' => posts['total'],
21
21
  }
22
22
  end
23
23
  end
@@ -38,7 +38,6 @@ module DiscourseApi
38
38
  :messageable_level,
39
39
  :name,
40
40
  :automatic_membership_email_domains,
41
- :automatic_membership_retroactive,
42
41
  :title,
43
42
  :primary_group,
44
43
  :grant_trust_level,
@@ -47,19 +46,31 @@ module DiscourseApi
47
46
  :flair_bg_color,
48
47
  :flair_color,
49
48
  :bio_raw,
50
- :members_visibility_level,
49
+ :visibility_level,
51
50
  :public_admission,
52
51
  :public_exit,
53
52
  :allow_membership_requests,
54
53
  :full_name,
55
54
  :default_notification_level,
56
- :usernames,
57
- :owner_usernames,
58
55
  :membership_request_template)
59
56
  .to_h
60
57
  put("/groups/#{group_id}", group: args)
61
58
  end
62
59
 
60
+ def group_add_owners(group_id, args)
61
+ args = API.params(args)
62
+ .required(:usernames)
63
+ .to_h
64
+ put("/admin/groups/#{group_id}/owners.json", group: args)
65
+ end
66
+
67
+ def group_remove_owners(group_id, args)
68
+ args = API.params(args)
69
+ .required(:usernames)
70
+ .to_h
71
+ delete("/admin/groups/#{group_id}/owners.json", group: args)
72
+ end
73
+
63
74
  def groups(args = {})
64
75
  params = API.params(args)
65
76
  .optional(:page)
@@ -3,17 +3,82 @@ module DiscourseApi
3
3
  module API
4
4
  module Invite
5
5
  def invite_user(params = {})
6
- post("/invites", params)
6
+ args = API.params(params)
7
+ .optional(
8
+ :email,
9
+ :skip_email,
10
+ :custom_message,
11
+ :max_redemptions_allowed,
12
+ :topic_id,
13
+ :group_ids,
14
+ :expires_at
15
+ ).to_h
16
+
17
+ post("/invites", args)
7
18
  end
8
19
 
20
+ # TODO: Deprecated. Remove after 20220506
9
21
  def invite_user_to_topic(params = {})
10
- post("/t/#{params[:topic_id]}/invite", params)
22
+ deprecated(__method__, 'invite_to_topic')
23
+ invite_to_topic(params[:topic_id], params)
24
+ end
25
+
26
+ def invite_to_topic(topic_id, params = {})
27
+ args = API.params(params)
28
+ .optional(
29
+ :email,
30
+ :user,
31
+ :group_ids,
32
+ :custom_message
33
+ ).to_h
34
+
35
+ post("/t/#{topic_id}/invite", args)
36
+ end
37
+
38
+ def retrieve_invite(params = {})
39
+ args = API.params(params).required(:email).to_h
40
+
41
+ response = get("invites/retrieve.json", args)
42
+
43
+ response.body
11
44
  end
12
45
 
13
46
  # requires this plugin => https://github.com/discourse/discourse-invite-tokens
14
47
  def disposable_tokens(params = {})
15
48
  post("/invite-token/generate", params)
16
49
  end
50
+
51
+ def update_invite(invite_id, params = {})
52
+ args = API.params(params)
53
+ .optional(
54
+ :topic_id,
55
+ :group_ids,
56
+ :group_names,
57
+ :email,
58
+ :send_email,
59
+ :custom_message,
60
+ :max_redemptions_allowed,
61
+ :expires_at
62
+ ).to_h
63
+
64
+ put("invites/#{invite_id}", args)
65
+ end
66
+
67
+ def destroy_all_expired_invites
68
+ post("invites/destroy-all-expired")
69
+ end
70
+
71
+ def resend_all_invites
72
+ post("invites/reinvite-all")
73
+ end
74
+
75
+ def resend_invite(email)
76
+ post("invites/reinvite", { "email": email })
77
+ end
78
+
79
+ def destroy_invite(invite_id)
80
+ delete("/invites", { id: invite_id })
81
+ end
17
82
  end
18
83
  end
19
84
  end
@@ -2,8 +2,11 @@
2
2
  module DiscourseApi
3
3
  module API
4
4
  module Notifications
5
- def notifications
6
- response = get('/notifications.json')
5
+ def notifications(params = {})
6
+ params = API.params(params)
7
+ .optional(:username, :recent, :limit, :offset, :filter)
8
+
9
+ response = get('/notifications.json', params)
7
10
  response[:body]
8
11
  end
9
12
  end
@@ -3,13 +3,20 @@ module DiscourseApi
3
3
  module API
4
4
  module PrivateMessages
5
5
 
6
- # :target_usernames REQUIRED comma separated list of usernames
6
+ # TODO: Deprecated. Remove after 20220628
7
+ def create_private_message(args = {})
8
+ deprecated(__method__, 'create_pm')
9
+ args[:target_recipients] = args.delete :target_usernames
10
+ create_pm(args.to_h)
11
+ end
12
+
13
+ # :target_recipients REQUIRED comma separated list of usernames
7
14
  # :category OPTIONAL name of category, not ID
8
15
  # :created_at OPTIONAL seconds since epoch.
9
- def create_private_message(args = {})
16
+ def create_pm(args = {})
10
17
  args[:archetype] = 'private_message'
11
18
  args = API.params(args)
12
- .required(:title, :raw, :target_usernames, :archetype)
19
+ .required(:title, :raw, :target_recipients, :archetype)
13
20
  .optional(:category, :created_at, :api_username)
14
21
  post("/posts", args.to_h)
15
22
  end
@@ -12,7 +12,7 @@ module DiscourseApi
12
12
  raise ArgumentError.new("#{term} is required but not specified") unless term
13
13
  raise ArgumentError.new("#{term} is required but not specified") unless !term.empty?
14
14
 
15
- response = get('/search/query', options.merge(term: term))
15
+ response = get('/search', options.merge(q: term))
16
16
  response[:body]
17
17
  end
18
18
  end
@@ -3,10 +3,10 @@ module DiscourseApi
3
3
  module API
4
4
  module SiteSettings
5
5
  def site_setting_update(args = {})
6
- params = API.params(args)
7
- .required(:name, :value).to_h
6
+ params = API.params(args).required(:name, :value).to_h
8
7
  new_site_setting = { params[:name] => params[:value] }
9
- response = put("/admin/site_settings/#{params[:name]}", new_site_setting)
8
+
9
+ put("/admin/site_settings/#{params[:name]}", new_site_setting)
10
10
  end
11
11
  end
12
12
  end
@@ -29,6 +29,11 @@ module DiscourseApi
29
29
  response[:body]['topic_list']['topics']
30
30
  end
31
31
 
32
+ def top_topics(params = {})
33
+ response = get("/top.json", params)
34
+ response[:body]['topic_list']['topics']
35
+ end
36
+
32
37
  def new_topics(params = {})
33
38
  response = get("/new.json", params)
34
39
  response[:body]['topic_list']['topics']
@@ -69,13 +74,22 @@ module DiscourseApi
69
74
  delete("/t/#{id}.json")
70
75
  end
71
76
 
72
- def topic_posts(topic_id, post_ids = [])
77
+ def topic_posts(topic_id, post_ids = [], params = {})
78
+ params = API.params(params)
79
+ .optional(:asc,
80
+ :filter,
81
+ :include_raw,
82
+ :include_suggested,
83
+ :post_number,
84
+ :username_filters,
85
+ )
86
+
73
87
  url = ["/t/#{topic_id}/posts.json"]
74
88
  if post_ids.count > 0
75
89
  url.push('?')
76
90
  url.push(post_ids.map { |id| "post_ids[]=#{id}" }.join('&'))
77
91
  end
78
- response = get(url.join)
92
+ response = get(url.join, params)
79
93
  response[:body]
80
94
  end
81
95
 
@@ -85,6 +99,20 @@ module DiscourseApi
85
99
 
86
100
  post("/t/#{topic_id}/change-owner.json", params)
87
101
  end
102
+
103
+ def topic_set_user_notification_level(topic_id, params)
104
+ params = API.params(params)
105
+ .required(:notification_level)
106
+ post("/t/#{topic_id}/notifications", params)
107
+ end
108
+
109
+ def bookmark_topic(topic_id)
110
+ put("/t/#{topic_id}/bookmark.json")
111
+ end
112
+
113
+ def remove_topic_bookmark(topic_id)
114
+ put("/t/#{topic_id}/remove_bookmarks.json")
115
+ end
88
116
  end
89
117
  end
90
118
  end
@@ -102,6 +102,10 @@ module DiscourseApi
102
102
  put("/admin/users/#{user_id}/unsuspend")
103
103
  end
104
104
 
105
+ def anonymize(user_id)
106
+ put("/admin/users/#{user_id}/anonymize")
107
+ end
108
+
105
109
  def delete_user(user_id, delete_posts = false)
106
110
  delete("/admin/users/#{user_id}.json?delete_posts=#{delete_posts}")
107
111
  end
@@ -29,7 +29,9 @@ module DiscourseApi
29
29
  class Client
30
30
  attr_accessor :api_key
31
31
  attr_accessor :basic_auth
32
- attr_reader :host, :api_username
32
+ attr_reader :host, :api_username, :timeout
33
+
34
+ DEFAULT_TIMEOUT = 30
33
35
 
34
36
  include DiscourseApi::API::Categories
35
37
  include DiscourseApi::API::Search
@@ -60,6 +62,11 @@ module DiscourseApi
60
62
  @use_relative = check_subdirectory(host)
61
63
  end
62
64
 
65
+ def timeout=(timeout)
66
+ @timeout = timeout
67
+ @connection.options.timeout = timeout if @connection
68
+ end
69
+
63
70
  def api_username=(api_username)
64
71
  @api_username = api_username
65
72
  @connection.headers['Api-Username'] = api_username unless @connection.nil?
@@ -68,6 +75,9 @@ module DiscourseApi
68
75
  def connection_options
69
76
  @connection_options ||= {
70
77
  url: @host,
78
+ request: {
79
+ timeout: @timeout || DEFAULT_TIMEOUT
80
+ },
71
81
  headers: {
72
82
  accept: 'application/json',
73
83
  user_agent: user_agent,
@@ -158,6 +168,8 @@ module DiscourseApi
158
168
  response.env
159
169
  rescue Faraday::ClientError, JSON::ParserError
160
170
  raise DiscourseApi::Error
171
+ rescue Faraday::ConnectionFailed
172
+ raise DiscourseApi::Timeout
161
173
  end
162
174
 
163
175
  def handle_error(response)
@@ -33,4 +33,7 @@ module DiscourseApi
33
33
 
34
34
  class TooManyRequests < DiscourseError
35
35
  end
36
+
37
+ class Timeout < Error
38
+ end
36
39
  end