discourse_api 0.44.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -17
  3. data/.gitignore +0 -19
  4. data/CHANGELOG.md +38 -7
  5. data/discourse_api.gemspec +5 -5
  6. data/examples/bookmark_topic.rb +15 -0
  7. data/examples/category.rb +3 -0
  8. data/examples/invite_users.rb +20 -2
  9. data/examples/manage_api_keys.rb +42 -0
  10. data/examples/notifications.rb +18 -0
  11. data/examples/topic_lists.rb +3 -0
  12. data/lib/discourse_api/api/api_key.rb +15 -9
  13. data/lib/discourse_api/api/categories.rb +41 -13
  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/topics.rb +37 -2
  19. data/lib/discourse_api/api/users.rb +1 -1
  20. data/lib/discourse_api/client.rb +13 -1
  21. data/lib/discourse_api/error.rb +3 -0
  22. data/lib/discourse_api/version.rb +1 -1
  23. data/spec/discourse_api/api/api_key_spec.rb +59 -35
  24. data/spec/discourse_api/api/categories_spec.rb +90 -0
  25. data/spec/discourse_api/api/invite_spec.rb +123 -0
  26. data/spec/discourse_api/api/private_messages_spec.rb +4 -4
  27. data/spec/discourse_api/api/search_spec.rb +2 -2
  28. data/spec/discourse_api/api/topics_spec.rb +71 -0
  29. data/spec/discourse_api/client_spec.rb +15 -0
  30. data/spec/fixtures/api_key.json +12 -0
  31. data/spec/fixtures/list_api_keys.json +14 -0
  32. data/spec/fixtures/notification_success.json +3 -0
  33. data/spec/fixtures/retrieve_invite.json +116 -0
  34. data/spec/fixtures/top.json +108 -0
  35. data/spec/fixtures/topic_posts.json +1 -0
  36. metadata +24 -13
  37. data/spec/fixtures/api.json +0 -12
  38. data/spec/fixtures/generate_master_key.json +0 -7
@@ -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
@@ -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,15 +74,45 @@ 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
95
+
96
+ def change_owner(topic_id, params = {})
97
+ params = API.params(params)
98
+ .required(:username, :post_ids)
99
+
100
+ post("/t/#{topic_id}/change-owner.json", params)
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
81
116
  end
82
117
  end
83
118
  end
@@ -52,7 +52,7 @@ module DiscourseApi
52
52
  def create_user(args)
53
53
  args = API.params(args)
54
54
  .required(:name, :email, :password, :username)
55
- .optional(:active, :staged, :user_fields)
55
+ .optional(:active, :approved, :staged, :user_fields)
56
56
  .to_h
57
57
  post("/users", args)
58
58
  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 < DiscourseError
38
+ end
36
39
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module DiscourseApi
3
- VERSION = "0.44.0"
3
+ VERSION = "0.47.0"
4
4
  end
@@ -10,77 +10,101 @@ describe DiscourseApi::API::ApiKey do
10
10
  )
11
11
  }
12
12
 
13
- describe "#api" do
13
+ describe "#list_api_keys" do
14
14
  before do
15
- url = "#{host}/admin/api.json"
16
- stub_get(url).to_return(body: fixture("api.json"),
15
+ url = "#{host}/admin/api/keys"
16
+ stub_get(url).to_return(body: fixture("list_api_keys.json"),
17
17
  headers: { content_type: "application/json" })
18
18
  end
19
19
 
20
20
  it "requests the correct resource" do
21
- subject.api
22
- url = "#{host}/admin/api.json"
21
+ subject.list_api_keys
22
+ url = "#{host}/admin/api/keys"
23
23
  expect(a_get(url)).to have_been_made
24
24
  end
25
25
 
26
26
  it "returns the requested api keys" do
27
- api = subject.api
28
- expect(api).to be_an Array
29
- expect(api.first).to be_a Hash
30
- expect(api.first).to have_key('key')
27
+ keys = subject.list_api_keys
28
+ expect(keys["keys"]).to be_an Array
29
+ expect(keys["keys"].first).to be_a Hash
30
+ expect(keys["keys"].first).to have_key('key')
31
31
  end
32
32
  end
33
33
 
34
- describe "#generate_master_key" do
34
+ describe "#create_api_key" do
35
35
  before do
36
- url = "#{host}/admin/api/key"
37
- stub_post(url).to_return(body: fixture("generate_master_key.json"),
36
+ url = "#{host}/admin/api/keys"
37
+ stub_post(url).to_return(body: fixture("api_key.json"),
38
38
  headers: { content_type: "application/json" })
39
39
  end
40
40
 
41
- it "returns the generated master key" do
42
- master_key = subject.generate_master_key
43
- expect(master_key).to be_a Hash
44
- expect(master_key['api_key']).to have_key('key')
45
- expect(master_key['api_key']['user']).to eq(nil)
41
+ it "requests the correct resource" do
42
+ subject.create_api_key(key: { username: 'robin' })
43
+ url = "#{host}/admin/api/keys"
44
+ expect(a_post(url)).to have_been_made
45
+ end
46
+
47
+ it "returns the generated api key" do
48
+ api_key = subject.create_api_key(key: { username: 'robin' })
49
+ expect(api_key).to be_a Hash
50
+ expect(api_key['key']).to have_key('key')
46
51
  end
47
52
  end
48
53
 
49
54
  describe "#revoke_api_key" do
50
55
  before do
51
- url = "#{host}/admin/api/key?id=10"
52
- stub_delete(url).to_return(body: "",
53
- headers: { content_type: "application/json" })
56
+ url = "#{host}/admin/api/keys/10/revoke"
57
+ stub_post(url).to_return(body: fixture("api_key.json"),
58
+ headers: { content_type: "application/json" })
54
59
  end
55
60
 
56
61
  it "requests the correct resource" do
57
62
  subject.revoke_api_key(10)
58
- url = "#{host}/admin/api/key?id=10"
59
- expect(a_delete(url)).to have_been_made
63
+ url = "#{host}/admin/api/keys/10/revoke"
64
+ expect(a_post(url)).to have_been_made
60
65
  end
61
66
 
62
- it "returns 200" do
63
- response = subject.revoke_api_key(10)
64
- expect(response['status']).to eq(200)
67
+ it "returns the api key" do
68
+ api_key = subject.revoke_api_key(10)
69
+ expect(api_key['key']).to have_key('key')
65
70
  end
66
71
  end
67
72
 
68
- describe "#regenerate_api_key" do
73
+ describe "#undo_revoke_api_key" do
69
74
  before do
70
- url = "#{host}/admin/api/key"
71
- stub_put(url).to_return(body: fixture("regenerate_api_key.json"),
72
- headers: { content_type: "application/json" })
75
+ url = "#{host}/admin/api/keys/10/undo-revoke"
76
+ stub_post(url).to_return(body: fixture("api_key.json"),
77
+ headers: { content_type: "application/json" })
73
78
  end
74
79
 
75
80
  it "requests the correct resource" do
76
- subject.regenerate_api_key(10)
77
- url = "#{host}/admin/api/key"
78
- expect(a_put(url)).to have_been_made
81
+ subject.undo_revoke_api_key(10)
82
+ url = "#{host}/admin/api/keys/10/undo-revoke"
83
+ expect(a_post(url)).to have_been_made
79
84
  end
80
85
 
81
- it "returns the regenerated api key" do
82
- key = subject.regenerate_api_key(10)
83
- expect(key['api_key']).to have_key('key')
86
+ it "returns the api key" do
87
+ api_key = subject.undo_revoke_api_key(10)
88
+ expect(api_key['key']).to have_key('key')
89
+ end
90
+ end
91
+
92
+ describe "#delete_api_key" do
93
+ before do
94
+ url = "#{host}/admin/api/keys/10"
95
+ stub_delete(url).to_return(body: '{"success": "OK"}',
96
+ headers: { content_type: "application/json" })
97
+ end
98
+
99
+ it "requests the correct resource" do
100
+ subject.delete_api_key(10)
101
+ url = "#{host}/admin/api/keys/10"
102
+ expect(a_delete(url)).to have_been_made
103
+ end
104
+
105
+ it "returns 200" do
106
+ response = subject.delete_api_key(10)
107
+ expect(response['status']).to eq(200)
84
108
  end
85
109
  end
86
110
  end
@@ -28,6 +28,25 @@ describe DiscourseApi::API::Categories do
28
28
  end
29
29
  end
30
30
 
31
+ describe "#categories_full" do
32
+ before do
33
+ stub_get("#{host}/categories.json")
34
+ .to_return(body: fixture("categories.json"), headers: { content_type: "application/json" })
35
+ end
36
+
37
+ it "requests the correct resource" do
38
+ subject.categories
39
+ expect(a_get("#{host}/categories.json")).to have_been_made
40
+ end
41
+
42
+ it "returns the entire categories response" do
43
+ categories = subject.categories_full
44
+ expect(categories).to be_a Hash
45
+ expect(categories).to have_key 'category_list'
46
+ expect(categories).to have_key 'featured_users'
47
+ end
48
+ end
49
+
31
50
  describe '#category_latest_topics' do
32
51
  before do
33
52
  stub_get("#{host}/c/category-slug/l/latest.json")
@@ -40,6 +59,20 @@ describe DiscourseApi::API::Categories do
40
59
  end
41
60
  end
42
61
 
62
+ describe '#category_latest_topics_full' do
63
+ before do
64
+ stub_get("#{host}/c/category-slug/l/latest.json")
65
+ .to_return(body: fixture("category_latest_topics.json"), headers: { content_type: "application/json" })
66
+ end
67
+
68
+ it "returns the entire latest topics in a category response" do
69
+ latest_topics = subject.category_latest_topics_full(category_slug: 'category-slug')
70
+ expect(latest_topics).to be_a Hash
71
+ expect(latest_topics).to have_key 'topic_list'
72
+ expect(latest_topics).to have_key 'users'
73
+ end
74
+ end
75
+
43
76
  describe '#category_top_topics' do
44
77
  before do
45
78
  stub_get("#{host}/c/category-slug/l/top.json")
@@ -55,6 +88,23 @@ describe DiscourseApi::API::Categories do
55
88
  end
56
89
  end
57
90
 
91
+ describe '#category_top_topics_full' do
92
+ before do
93
+ stub_get("#{host}/c/category-slug/l/top.json")
94
+ .to_return(
95
+ body: fixture("category_topics.json"),
96
+ headers: { content_type: "application/json" }
97
+ )
98
+ end
99
+
100
+ it "returns the entire top topics in a category response" do
101
+ topics = subject.category_top_topics_full('category-slug')
102
+ expect(topics).to be_a Hash
103
+ expect(topics).to have_key 'topic_list'
104
+ expect(topics).to have_key 'users'
105
+ end
106
+ end
107
+
58
108
  describe '#category_new_topics' do
59
109
  before do
60
110
  stub_get("#{host}/c/category-slug/l/new.json")
@@ -70,6 +120,23 @@ describe DiscourseApi::API::Categories do
70
120
  end
71
121
  end
72
122
 
123
+ describe '#category_new_topics_full' do
124
+ before do
125
+ stub_get("#{host}/c/category-slug/l/new.json")
126
+ .to_return(
127
+ body: fixture("category_topics.json"),
128
+ headers: { content_type: "application/json" }
129
+ )
130
+ end
131
+
132
+ it "returns the new topics in a category" do
133
+ topics = subject.category_new_topics_full('category-slug')
134
+ expect(topics).to be_a Hash
135
+ expect(topics).to have_key 'topic_list'
136
+ expect(topics).to have_key 'users'
137
+ end
138
+ end
139
+
73
140
  describe '#category_new_category' do
74
141
  before do
75
142
  stub_post("#{host}/categories")
@@ -85,4 +152,27 @@ describe DiscourseApi::API::Categories do
85
152
  end
86
153
  end
87
154
 
155
+ describe "#category_set_user_notification" do
156
+ before do
157
+ stub_post("#{host}/category/1/notifications").to_return(body: fixture("notification_success.json"), headers: { content_type: "application/json" })
158
+ end
159
+
160
+ it "makes the post request" do
161
+ response = subject.category_set_user_notification(id: 1, notification_level: 3)
162
+ expect(a_post("#{host}/category/1/notifications")).to have_been_made
163
+ expect(response['success']).to eq('OK')
164
+ end
165
+ end
166
+
167
+ describe "#category_set_user_notification_level" do
168
+ before do
169
+ stub_post("#{host}/category/1/notifications").to_return(body: fixture("notification_success.json"), headers: { content_type: "application/json" })
170
+ end
171
+
172
+ it "makes the post request" do
173
+ response = subject.category_set_user_notification_level(1, notification_level: 3)
174
+ expect(a_post("#{host}/category/1/notifications").with(body: "notification_level=3")).to have_been_made
175
+ expect(response['success']).to eq('OK')
176
+ end
177
+ end
88
178
  end