forem-ruby 0.1.0.beta1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/forem-ruby.gemspec +17 -0
  4. data/lib/forem/api_operations/create.rb +47 -0
  5. data/lib/forem/api_operations/delete.rb +88 -0
  6. data/lib/forem/api_operations/list.rb +70 -0
  7. data/lib/forem/api_operations/request.rb +83 -0
  8. data/lib/forem/api_operations/retrieve.rb +43 -0
  9. data/lib/forem/api_operations/save.rb +53 -0
  10. data/lib/forem/api_operations/update.rb +47 -0
  11. data/lib/forem/api_requestor.rb +283 -0
  12. data/lib/forem/api_resource.rb +77 -0
  13. data/lib/forem/client.rb +279 -0
  14. data/lib/forem/configuration.rb +74 -0
  15. data/lib/forem/connection_manager.rb +75 -0
  16. data/lib/forem/errors.rb +118 -0
  17. data/lib/forem/forem_object.rb +264 -0
  18. data/lib/forem/forem_response.rb +50 -0
  19. data/lib/forem/list_object.rb +171 -0
  20. data/lib/forem/resources/admin_concept.rb +169 -0
  21. data/lib/forem/resources/admin_user.rb +152 -0
  22. data/lib/forem/resources/agent_session.rb +110 -0
  23. data/lib/forem/resources/analytics.rb +151 -0
  24. data/lib/forem/resources/article.rb +256 -0
  25. data/lib/forem/resources/billboard.rb +76 -0
  26. data/lib/forem/resources/comment.rb +43 -0
  27. data/lib/forem/resources/concept.rb +192 -0
  28. data/lib/forem/resources/follow.rb +78 -0
  29. data/lib/forem/resources/follower.rb +56 -0
  30. data/lib/forem/resources/health_check.rb +70 -0
  31. data/lib/forem/resources/organization.rb +79 -0
  32. data/lib/forem/resources/page.rb +52 -0
  33. data/lib/forem/resources/podcast_episode.rb +36 -0
  34. data/lib/forem/resources/profile_image.rb +44 -0
  35. data/lib/forem/resources/reaction.rb +61 -0
  36. data/lib/forem/resources/reading_list.rb +29 -0
  37. data/lib/forem/resources/recommended_articles_list.rb +45 -0
  38. data/lib/forem/resources/request_redirect.rb +60 -0
  39. data/lib/forem/resources/segment.rb +103 -0
  40. data/lib/forem/resources/survey.rb +96 -0
  41. data/lib/forem/resources/tag.rb +27 -0
  42. data/lib/forem/resources/trend.rb +80 -0
  43. data/lib/forem/resources/user.rb +229 -0
  44. data/lib/forem/resources/video.rb +28 -0
  45. data/lib/forem/services/admin_concept_service.rb +138 -0
  46. data/lib/forem/services/admin_user_service.rb +114 -0
  47. data/lib/forem/services/agent_session_service.rb +87 -0
  48. data/lib/forem/services/analytics_service.rb +93 -0
  49. data/lib/forem/services/article_service.rb +233 -0
  50. data/lib/forem/services/base_service.rb +45 -0
  51. data/lib/forem/services/billboard_service.rb +91 -0
  52. data/lib/forem/services/comment_service.rb +51 -0
  53. data/lib/forem/services/concept_service.rb +143 -0
  54. data/lib/forem/services/follow_service.rb +61 -0
  55. data/lib/forem/services/follower_service.rb +34 -0
  56. data/lib/forem/services/health_check_service.rb +46 -0
  57. data/lib/forem/services/organization_service.rb +103 -0
  58. data/lib/forem/services/page_service.rb +107 -0
  59. data/lib/forem/services/podcast_episode_service.rb +34 -0
  60. data/lib/forem/services/profile_image_service.rb +32 -0
  61. data/lib/forem/services/reaction_service.rb +72 -0
  62. data/lib/forem/services/reading_list_service.rb +35 -0
  63. data/lib/forem/services/recommended_articles_list_service.rb +87 -0
  64. data/lib/forem/services/request_redirect_service.rb +118 -0
  65. data/lib/forem/services/segment_service.rb +83 -0
  66. data/lib/forem/services/survey_service.rb +48 -0
  67. data/lib/forem/services/tag_service.rb +32 -0
  68. data/lib/forem/services/trend_service.rb +70 -0
  69. data/lib/forem/services/user_service.rb +61 -0
  70. data/lib/forem/services/video_service.rb +33 -0
  71. data/lib/forem/util.rb +43 -0
  72. data/lib/forem/version.rb +4 -0
  73. data/lib/forem.rb +91 -0
  74. metadata +111 -0
@@ -0,0 +1,29 @@
1
+ module Forem
2
+ # Represents an item saved to the authenticated user's reading list.
3
+ #
4
+ # When a user bookmarks an article on Forem, it appears in their reading
5
+ # list. This resource supports listing those saved articles with optional
6
+ # filtering by status.
7
+ #
8
+ # Requires authentication. Returns articles saved to the user's reading
9
+ # list. Default: 30 per page.
10
+ #
11
+ # Available operations (via mixins):
12
+ # - +List+ — GET /api/readinglist
13
+ #
14
+ # @example List all bookmarked articles
15
+ # items = client.reading_list.list
16
+ # items.data.each { |item| puts item.article.title }
17
+ #
18
+ # @example List only confirmed (active) reading list items
19
+ # items = client.reading_list.list(status: "confirmed")
20
+ # items.data.each { |item| puts item.article.title }
21
+ #
22
+ # @see https://developers.forem.com/api/v1#/operations/getReadinglist
23
+ class ReadingList < APIResource
24
+ extend APIOperations::List
25
+
26
+ OBJECT_NAME = "reading_list"
27
+ RESOURCE_PATH = "/api/readinglist"
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module Forem
2
+ # Represents a curated list of recommended articles on a Forem instance.
3
+ #
4
+ # Curated lists of recommended articles. Used by admins to manage content
5
+ # recommendations.
6
+ #
7
+ # RecommendedArticlesLists are editorial curation objects that group
8
+ # articles for display in recommendation widgets or featured sections.
9
+ # Full CRUD operations are supported. Managing these lists typically
10
+ # requires an admin API key.
11
+ #
12
+ # Available operations (via mixins):
13
+ # - +List+ — GET /api/recommended_articles_lists
14
+ # - +Create+ — POST /api/recommended_articles_lists
15
+ # - +Retrieve+ — GET /api/recommended_articles_lists/:id
16
+ # - +Update+ — PUT /api/recommended_articles_lists/:id
17
+ # - +Save+ — instance-level save (create or update)
18
+ #
19
+ # @example List all recommended article lists
20
+ # lists = client.recommended_articles_lists.list
21
+ # lists.each { |l| puts l.name }
22
+ #
23
+ # @example Create a recommended articles list (flat params)
24
+ # list = client.recommended_articles_lists.create(
25
+ # name: "Top Ruby Articles",
26
+ # article_ids: [101, 202, 303]
27
+ # )
28
+ #
29
+ # @example Retrieve a list by ID and update it
30
+ # list = client.recommended_articles_lists.retrieve(1)
31
+ # list.name = "Updated Name"
32
+ # list.save
33
+ #
34
+ # @see https://developers.forem.com/api/v1
35
+ class RecommendedArticlesList < APIResource
36
+ extend APIOperations::Create
37
+ extend APIOperations::List
38
+ extend APIOperations::Retrieve
39
+ extend APIOperations::Update
40
+ include APIOperations::Save
41
+
42
+ OBJECT_NAME = "recommended_articles_list"
43
+ RESOURCE_PATH = "/api/recommended_articles_lists"
44
+ end
45
+ end
@@ -0,0 +1,60 @@
1
+ module Forem
2
+ # Represents a request redirect used to map legacy or custom-domain paths
3
+ # to a destination URL.
4
+ #
5
+ # Request redirects power organization custom domains: when a request
6
+ # comes in for +request_domain+ at path +original_url+, Forem redirects
7
+ # the visitor to +destination_url+. Managing request redirects is an
8
+ # admin-only operation and requires an admin API key.
9
+ #
10
+ # Available operations (via mixins):
11
+ # - +List+ — GET /api/admin/request_redirects
12
+ # - +Create+ — POST /api/admin/request_redirects
13
+ # - +Retrieve+ — GET /api/admin/request_redirects/:id
14
+ # - +Update+ — PUT /api/admin/request_redirects/:id
15
+ # - +Delete+ — instance-level delete (DELETE /api/admin/request_redirects/:id)
16
+ # - +Save+ — instance-level save (create or update)
17
+ #
18
+ # == RequestRedirect Fields
19
+ #
20
+ # - +original_url+ (String, required) — the incoming request path; must
21
+ # start with +/+ and is unique per +request_domain+
22
+ # - +destination_url+ (String, required) — the fully-qualified HTTP/HTTPS
23
+ # URL to redirect to
24
+ # - +request_domain+ (String, required) — the domain the redirect applies
25
+ # to; normalized to lowercase by the server
26
+ #
27
+ # The create/update endpoints require request bodies wrapped in a
28
+ # +request_redirect:+ key (the API uses strong parameters with
29
+ # +params.require(:request_redirect)+), so callers of the class methods
30
+ # here must supply that wrapper explicitly. {Forem::Services::RequestRedirectService}
31
+ # handles the wrapping automatically for the friendlier, flat-params
32
+ # service interface.
33
+ #
34
+ # @example List all request redirects
35
+ # redirects = client.request_redirects.list
36
+ # redirects.each { |r| puts "#{r.request_domain}#{r.original_url} -> #{r.destination_url}" }
37
+ #
38
+ # @example Create a request redirect (params must be wrapped in +request_redirect:+)
39
+ # redirect = Forem::RequestRedirect.create(
40
+ # { request_redirect: { original_url: "/old", destination_url: "https://example.com/new", request_domain: "example.com" } },
41
+ # requestor: client.requestor
42
+ # )
43
+ #
44
+ # @example Retrieve a request redirect by ID
45
+ # redirect = client.request_redirects.retrieve(7)
46
+ # puts redirect.destination_url
47
+ #
48
+ # @see https://developers.forem.com/api/v1
49
+ class RequestRedirect < APIResource
50
+ extend APIOperations::Create
51
+ extend APIOperations::List
52
+ extend APIOperations::Retrieve
53
+ extend APIOperations::Update
54
+ include APIOperations::Delete
55
+ include APIOperations::Save
56
+
57
+ OBJECT_NAME = "request_redirect"
58
+ RESOURCE_PATH = "/api/admin/request_redirects"
59
+ end
60
+ end
@@ -0,0 +1,103 @@
1
+ module Forem
2
+ # Represents a user segment used for targeted content delivery on Forem.
3
+ #
4
+ # Audience segments for billboard targeting. The API only permits managing
5
+ # segments you create yourself.
6
+ #
7
+ # Segments are named groups of users that can be targeted with specific
8
+ # billboards or other content. Admins can create and delete segments, list
9
+ # their members, and add or remove users from them. Managing segments
10
+ # requires an admin API key.
11
+ #
12
+ # Available operations (via mixins):
13
+ # - +List+ — GET /api/segments (getSegments)
14
+ # - +Create+ — POST /api/segments (createSegment)
15
+ # - +Retrieve+ — GET /api/segments/:id
16
+ # - +Delete+ — instance-level delete (DELETE /api/segments/:id)
17
+ #
18
+ # == Segment Fields
19
+ #
20
+ # - +id+ (Integer) — The segment ID
21
+ # - +type_of+ (String) — Marks segment as manually managed
22
+ # - +user_count+ (Integer) — Current number of users in the segment
23
+ #
24
+ # @example List all segments
25
+ # segments = client.segments.list
26
+ # segments.each { |s| puts s.id }
27
+ #
28
+ # @example Create a segment (no params accepted — the API ignores anything passed)
29
+ # segment = client.segments.create
30
+ # #=> #<Forem::Segment id=14 type_of="manual">
31
+ #
32
+ # @example List users in a segment
33
+ # segment = client.segments.retrieve(5)
34
+ # segment.users.auto_paging_each { |u| puts u.username }
35
+ #
36
+ # @see https://developers.forem.com/api/v1
37
+ class Segment < APIResource
38
+ extend APIOperations::Create
39
+ extend APIOperations::List
40
+ extend APIOperations::Retrieve
41
+ include APIOperations::Delete
42
+
43
+ OBJECT_NAME = "segment"
44
+ RESOURCE_PATH = "/api/segments"
45
+
46
+ # Return the users who belong to this segment.
47
+ #
48
+ # Returns users in this segment. Default: 30 per page, max: 1000.
49
+ #
50
+ # Sends a GET request to +/api/segments/:id/users+ (getUsersInSegment).
51
+ #
52
+ # @param params [Hash] query parameters
53
+ # @option params [Integer] :page page number (default: 1)
54
+ # @option params [Integer] :per_page number of results per page (default: 30, max: 1000)
55
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
56
+ # @return [Forem::ListObject<Forem::User>] paginated list of users
57
+ # belonging to this segment.
58
+ # @example
59
+ # segment = client.segments.retrieve(5)
60
+ # segment.users.auto_paging_each { |u| puts u.username }
61
+ # @see https://developers.forem.com/api/v1
62
+ def users(params = {}, opts = {})
63
+ opts = opts.dup
64
+ opts[:requestor] ||= @requestor
65
+ Forem::User.paginated_list("#{resource_url}/users", params, opts)
66
+ end
67
+
68
+ # Add one or more users to this segment.
69
+ #
70
+ # Add users in bulk. The response distinguishes successes (added) from
71
+ # failures (couldn't add).
72
+ #
73
+ # Sends a PUT request to +/api/segments/:id/add_users+ (addUsersToSegment).
74
+ #
75
+ # @param params [Hash] request body
76
+ # @option params [Array<Integer>] :user_ids list of user IDs to add
77
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
78
+ # @return [ForemResponse] the raw API response
79
+ # @example
80
+ # segment = client.segments.retrieve(5)
81
+ # segment.add_users(user_ids: [101, 102, 103])
82
+ # @see https://developers.forem.com/api/v1
83
+ def add_users(params = {}, opts = {})
84
+ request(:put, "#{resource_url}/add_users", params, opts)
85
+ end
86
+
87
+ # Remove one or more users from this segment.
88
+ #
89
+ # Sends a PUT request to +/api/segments/:id/remove_users+.
90
+ #
91
+ # @param params [Hash] request body
92
+ # @option params [Array<Integer>] :user_ids list of user IDs to remove
93
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
94
+ # @return [ForemResponse] the raw API response
95
+ # @example
96
+ # segment = client.segments.retrieve(5)
97
+ # segment.remove_users(user_ids: [101, 102])
98
+ # @see https://developers.forem.com/api/v1
99
+ def remove_users(params = {}, opts = {})
100
+ request(:put, "#{resource_url}/remove_users", params, opts)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,96 @@
1
+ module Forem
2
+ # Represents a survey published on a Forem instance.
3
+ #
4
+ # Surveys are structured questionnaires that can be presented to community
5
+ # members. They can be listed or retrieved individually. Survey responses
6
+ # are exposed through two endpoints — {#poll_votes} for multiple-choice
7
+ # poll selections and {#poll_text_responses} for free-text answers.
8
+ #
9
+ # The +/api/surveys/{id}/responses+ endpoint that earlier versions of
10
+ # this gem hit no longer exists in the Forem API; it was replaced by
11
+ # those two cursor-paginated endpoints.
12
+ #
13
+ # Available operations (via mixins):
14
+ # - +List+ — GET /api/surveys
15
+ # - +Retrieve+ — GET /api/surveys/:id_or_slug (getSurveyByIdOrSlug)
16
+ #
17
+ # == Survey Fields
18
+ #
19
+ # - +id+ (Integer)
20
+ # - +title+ (String)
21
+ # - +slug+ (String)
22
+ # - +survey_type_of+ (String) — Survey category
23
+ # - +active+ (Boolean) — Whether the survey is currently active
24
+ #
25
+ # == Authentication
26
+ #
27
+ # All survey endpoints require an admin API key.
28
+ #
29
+ # @example List active surveys
30
+ # surveys = client.surveys.list(active: true)
31
+ # surveys.each { |s| puts s.title }
32
+ #
33
+ # @example Retrieve a specific survey by id or slug
34
+ # survey = client.surveys.retrieve(3)
35
+ # survey = client.surveys.retrieve("my-survey-slug")
36
+ #
37
+ # @example Iterate every poll vote across all pages
38
+ # survey = client.surveys.retrieve(3)
39
+ # survey.poll_votes.auto_paging_each { |v| puts v.poll_option_id }
40
+ #
41
+ # @see https://developers.forem.com/api/v1
42
+ class Survey < APIResource
43
+ extend APIOperations::List
44
+ extend APIOperations::Retrieve
45
+
46
+ OBJECT_NAME = "survey"
47
+ RESOURCE_PATH = "/api/surveys"
48
+
49
+ # Return the multiple-choice poll selections recorded for this survey.
50
+ #
51
+ # Sends a GET request to +/api/surveys/:id_or_slug/poll_votes+.
52
+ # Pagination is cursor-based — pass the last seen vote id as +:after+
53
+ # (or use {Forem::ListObject#auto_paging_each}, which threads the
54
+ # cursor automatically).
55
+ #
56
+ # @param params [Hash] query parameters
57
+ # @option params [Integer] :after return only votes with id strictly
58
+ # greater than this value (cursor)
59
+ # @option params [Integer] :per_page page size (default 30)
60
+ # @param opts [Hash] per-request options
61
+ # @return [Forem::ListObject<Forem::ForemObject>] cursor-paginated list
62
+ # of poll-vote objects.
63
+ # @example
64
+ # survey = client.surveys.retrieve(3)
65
+ # survey.poll_votes.auto_paging_each { |v| puts v.id }
66
+ # @see https://developers.forem.com/api/v1
67
+ def poll_votes(params = {}, opts = {})
68
+ opts = opts.dup
69
+ opts[:requestor] ||= @requestor
70
+ Forem::ForemObject.cursor_list("#{resource_url}/poll_votes", params, opts)
71
+ end
72
+
73
+ # Return the free-text answers recorded for this survey.
74
+ #
75
+ # Sends a GET request to +/api/surveys/:id_or_slug/poll_text_responses+.
76
+ # Cursor-paginated by +:after+ on the last seen response id; see
77
+ # {#poll_votes} for paging details.
78
+ #
79
+ # @param params [Hash] query parameters
80
+ # @option params [Integer] :after return only responses with id
81
+ # strictly greater than this value (cursor)
82
+ # @option params [Integer] :per_page page size (default 30)
83
+ # @param opts [Hash] per-request options
84
+ # @return [Forem::ListObject<Forem::ForemObject>] cursor-paginated list
85
+ # of poll text-response objects.
86
+ # @example
87
+ # survey = client.surveys.retrieve(3)
88
+ # survey.poll_text_responses.each { |r| puts r.text_content }
89
+ # @see https://developers.forem.com/api/v1
90
+ def poll_text_responses(params = {}, opts = {})
91
+ opts = opts.dup
92
+ opts[:requestor] ||= @requestor
93
+ Forem::ForemObject.cursor_list("#{resource_url}/poll_text_responses", params, opts)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,27 @@
1
+ module Forem
2
+ # Represents a Forem tag used to categorize articles.
3
+ #
4
+ # Tags are keywords attached to articles that help readers discover related
5
+ # content. Only listing is supported through the public API; tags are created
6
+ # implicitly when articles are published with new tag names.
7
+ #
8
+ # Tags are ordered by popularity. Default: 10 per page.
9
+ #
10
+ # Available operations (via mixins):
11
+ # - +List+ — GET /api/tags
12
+ #
13
+ # @example List tags ordered by popularity
14
+ # tags = client.tags.list(per_page: 50)
15
+ # tags.data.each { |t| puts "#{t.name} (#{t.points} points)" }
16
+ #
17
+ # @example Iterate over every tag using auto-pagination
18
+ # client.tags.list.auto_paging_each { |t| puts t.name }
19
+ #
20
+ # @see https://developers.forem.com/api/v1#/operations/getTags
21
+ class Tag < APIResource
22
+ extend APIOperations::List
23
+
24
+ OBJECT_NAME = "tag"
25
+ RESOURCE_PATH = "/api/tags"
26
+ end
27
+ end
@@ -0,0 +1,80 @@
1
+ module Forem
2
+ # Represents a trend detected across a Forem instance's content.
3
+ #
4
+ # Trends are algorithmically-derived topic clusters ("hot and recent"
5
+ # subject areas) made up of related articles. They are read-only via the
6
+ # public API — there is no create/update/delete surface, only listing,
7
+ # retrieval, and a nested endpoint for the articles that make up a trend.
8
+ #
9
+ # Available operations (via mixins):
10
+ # - +List+ — GET /api/trends
11
+ # - +Retrieve+ — GET /api/trends/:id_or_slug
12
+ #
13
+ # == Trend Fields
14
+ #
15
+ # - +id+ (Integer)
16
+ # - +name+ (String)
17
+ # - +slug+ (String)
18
+ # - +description+ (String)
19
+ # - +key_questions+ (Array<String>) — Questions the trend's content clusters around
20
+ # - +score+ (Float) — Relative trend strength/ranking score
21
+ # - +articles_count+ (Integer) — Number of articles associated with the trend
22
+ # - +cover_image+ (String) — URL of the trend's cover image
23
+ # - +first_observed_at+ (String) — ISO 8601 timestamp
24
+ # - +last_observed_at+ (String) — ISO 8601 timestamp
25
+ # - +created_at+ (String) — ISO 8601 timestamp
26
+ # - +updated_at+ (String) — ISO 8601 timestamp
27
+ # - +type_of+ (String) — Always +"trend"+
28
+ #
29
+ # The +show+ response additionally includes +top_articles+, an array of up
30
+ # to three of the trend's highest-scoring published articles, each with
31
+ # +id+, +title+, +slug+, +score+, and +published_at+.
32
+ #
33
+ # @example List hot and recent trends
34
+ # trends = client.trends.list(per_page: 10)
35
+ # trends.data.each { |t| puts "#{t.name} (#{t.score})" }
36
+ #
37
+ # @example Retrieve a trend by ID or slug
38
+ # trend = client.trends.retrieve("ai-agents")
39
+ # puts trend.description
40
+ # trend.top_articles.each { |a| puts a["title"] }
41
+ #
42
+ # @example List the articles that make up a trend
43
+ # articles = client.trends.articles("ai-agents", per_page: 20, sort: "score")
44
+ # articles.each { |a| puts a.title }
45
+ #
46
+ # @see https://developers.forem.com/api/v1
47
+ class Trend < APIResource
48
+ extend APIOperations::List
49
+ extend APIOperations::Retrieve
50
+
51
+ OBJECT_NAME = "trend"
52
+ RESOURCE_PATH = "/api/trends"
53
+
54
+ # Return the articles belonging to a trend.
55
+ #
56
+ # Sends a GET request to +/api/trends/:id_or_slug/articles+. Unlike
57
+ # {.list} and {.retrieve}, this is a class method that takes the trend's
58
+ # ID or slug directly, so it can be called without first retrieving the
59
+ # trend itself.
60
+ #
61
+ # @param id_or_slug [Integer, String] the trend's numeric ID or slug
62
+ # @param params [Hash] query parameters
63
+ # @option params [Integer] :page page number (default: 1)
64
+ # @option params [Integer] :per_page number of results per page (default: 10)
65
+ # @option params [String] :sort +"score"+ to sort purely by article score;
66
+ # omit for the default ordering (trend membership distance, then score)
67
+ # @param opts [Hash] per-request options
68
+ # @option opts [String] :api_key override the API key for this request.
69
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
70
+ # @return [Array<Forem::Article>] articles associated with the trend
71
+ # @example
72
+ # Forem::Trend.articles("ai-agents", per_page: 5, requestor: requestor)
73
+ # @see https://developers.forem.com/api/v1
74
+ def self.articles(id_or_slug, params = {}, opts = {})
75
+ requestor = opts[:requestor]
76
+ resp = request(:get, "#{resource_path}/#{id_or_slug}/articles", params, opts)
77
+ (resp.parsed_body || []).map { |item| Forem::Article.construct_from(item, requestor: requestor) }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,229 @@
1
+ module Forem
2
+ # Represents a Forem user account.
3
+ #
4
+ # Users can be retrieved by ID or by looking up the currently authenticated
5
+ # user. Admin-level operations (suspend, unsuspend, flag as spam, etc.) are
6
+ # also exposed as instance methods and require an admin API key.
7
+ #
8
+ # Available operations (via mixins):
9
+ # - +Retrieve+ — GET /api/users/:id
10
+ #
11
+ # Note: the +id+ parameter passed to +retrieve+ can be a numeric ID or a
12
+ # username string.
13
+ #
14
+ # @example Retrieve the authenticated user
15
+ # me = client.users.me
16
+ # puts "Hello, #{me.name}!"
17
+ #
18
+ # @example Retrieve a user by ID
19
+ # user = client.users.retrieve(12345)
20
+ # puts user.username
21
+ #
22
+ # @example Look up a user by exact email
23
+ # results = client.users.search(email: "alice@example.com")
24
+ # results.each { |u| puts u.username }
25
+ #
26
+ # @see https://developers.forem.com/api/v1#/operations/getUser
27
+ class User < APIResource
28
+ extend APIOperations::Retrieve
29
+
30
+ OBJECT_NAME = "user"
31
+ RESOURCE_PATH = "/api/users"
32
+
33
+ # Return the currently authenticated user's profile.
34
+ #
35
+ # Returns extended user info including +email+ (if the user allows it
36
+ # on their profile).
37
+ #
38
+ # Sends a GET request to +/api/users/me+.
39
+ #
40
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
41
+ # @return [Forem::User] the authenticated user
42
+ # @example
43
+ # me = client.users.me
44
+ # puts "Logged in as #{me.username}"
45
+ # @see https://developers.forem.com/api/v1#/operations/getUserMe
46
+ def self.me(opts = {})
47
+ requestor = opts[:requestor]
48
+ resp = request(:get, "/api/users/me", {}, opts)
49
+ construct_from(resp.parsed_body, requestor: requestor)
50
+ end
51
+
52
+ # Look up a single user by exact email address.
53
+ #
54
+ # Sends a GET request to +/api/users/search+. Despite the endpoint
55
+ # name, this is an exact-match email lookup — there is no name- or
56
+ # username-prefix search. The endpoint is V1-only, requires admin
57
+ # privileges, and is not currently documented in the public swagger.
58
+ #
59
+ # @param params [Hash] query parameters
60
+ # @option params [String] :email (required) the email address to look up.
61
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
62
+ # @return [Forem::ListObject<Forem::User>] a list containing the matched
63
+ # user, or an empty list if none matches. The list is wrapped for
64
+ # API consistency with other search-style methods even though the
65
+ # endpoint returns at most one result.
66
+ # @example
67
+ # results = client.users.search(email: "alice@example.com")
68
+ # if (user = results.first)
69
+ # puts user.username
70
+ # end
71
+ # @see https://developers.forem.com/api/v1
72
+ def self.search(params = {}, opts = {})
73
+ requestor = opts[:requestor]
74
+ data = begin
75
+ resp = request(:get, "/api/users/search", params, opts)
76
+ body = resp.parsed_body
77
+ if body.is_a?(Hash) && !body.empty?
78
+ [construct_from(body, requestor: requestor)]
79
+ else
80
+ []
81
+ end
82
+ rescue NotFoundError
83
+ []
84
+ end
85
+ ListObject.new(
86
+ data: data,
87
+ per_page: [data.length, 1].max,
88
+ resource_class: self,
89
+ filters: params.reject { |k, _| [:per_page, "per_page"].include?(k) },
90
+ requestor: requestor,
91
+ fetcher: ->(*) { nil }
92
+ )
93
+ end
94
+
95
+ # Unpublish all articles and comments authored by this user.
96
+ #
97
+ # Unpublishes all articles by this user.
98
+ #
99
+ # Sends a PUT request to +/api/users/:id/unpublish+. Requires admin privileges.
100
+ #
101
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
102
+ # @return [ForemResponse] the raw API response
103
+ # @example
104
+ # user = client.users.retrieve(42)
105
+ # user.unpublish
106
+ # @see https://developers.forem.com/api/v1#/operations/unpublishUser
107
+ def unpublish(opts = {})
108
+ request(:put, "#{resource_url}/unpublish", {}, opts)
109
+ end
110
+
111
+ # Suspend this user, preventing them from logging in or posting.
112
+ #
113
+ # Prevents new posts and comments but does not delete existing content.
114
+ # The user is not notified in the UI.
115
+ #
116
+ # Sends a PUT request to +/api/users/:id/suspend+. Requires admin privileges.
117
+ #
118
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
119
+ # @return [ForemResponse] the raw API response
120
+ # @example
121
+ # user = client.users.retrieve(42)
122
+ # user.suspend
123
+ # @see https://developers.forem.com/api/v1#/operations/suspendUser
124
+ def suspend(opts = {})
125
+ request(:put, "#{resource_url}/suspend", {}, opts)
126
+ end
127
+
128
+ # Remove the suspension from this user, restoring their access.
129
+ #
130
+ # Sends a DELETE request to +/api/users/:id/suspend+. Requires admin privileges.
131
+ #
132
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
133
+ # @return [ForemResponse] the raw API response
134
+ # @example
135
+ # user = client.users.retrieve(42)
136
+ # user.unsuspend
137
+ # @see https://developers.forem.com/api/v1#/operations/suspendUser
138
+ def unsuspend(opts = {})
139
+ request(:delete, "#{resource_url}/suspend", {}, opts)
140
+ end
141
+
142
+ # Apply the "limited" role to this user, restricting their posting ability.
143
+ #
144
+ # Sends a PUT request to +/api/users/:id/limited+. Requires admin privileges.
145
+ #
146
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
147
+ # @return [ForemResponse] the raw API response
148
+ # @example
149
+ # user = client.users.retrieve(42)
150
+ # user.add_limited
151
+ # @see https://developers.forem.com/api/v1
152
+ def add_limited(opts = {})
153
+ request(:put, "#{resource_url}/limited", {}, opts)
154
+ end
155
+
156
+ # Remove the "limited" role from this user.
157
+ #
158
+ # Sends a DELETE request to +/api/users/:id/limited+. Requires admin privileges.
159
+ #
160
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
161
+ # @return [ForemResponse] the raw API response
162
+ # @example
163
+ # user = client.users.retrieve(42)
164
+ # user.remove_limited
165
+ # @see https://developers.forem.com/api/v1
166
+ def remove_limited(opts = {})
167
+ request(:delete, "#{resource_url}/limited", {}, opts)
168
+ end
169
+
170
+ # Flag this user as a spam account.
171
+ #
172
+ # Prevents new posts and comments but does not delete existing content.
173
+ # The user is not notified in the UI.
174
+ #
175
+ # Sends a PUT request to +/api/users/:id/spam+. Requires admin privileges.
176
+ #
177
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
178
+ # @return [ForemResponse] the raw API response
179
+ # @example
180
+ # user = client.users.retrieve(42)
181
+ # user.add_spam
182
+ # @see https://developers.forem.com/api/v1#/operations/spamUser
183
+ def add_spam(opts = {})
184
+ request(:put, "#{resource_url}/spam", {}, opts)
185
+ end
186
+
187
+ # Remove the spam flag from this user.
188
+ #
189
+ # Sends a DELETE request to +/api/users/:id/spam+. Requires admin privileges.
190
+ #
191
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
192
+ # @return [ForemResponse] the raw API response
193
+ # @example
194
+ # user = client.users.retrieve(42)
195
+ # user.remove_spam
196
+ # @see https://developers.forem.com/api/v1#/operations/spamUser
197
+ def remove_spam(opts = {})
198
+ request(:delete, "#{resource_url}/spam", {}, opts)
199
+ end
200
+
201
+ # Grant the "trusted" role to this user, giving them elevated moderation privileges.
202
+ #
203
+ # Sends a PUT request to +/api/users/:id/trusted+. Requires admin privileges.
204
+ #
205
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
206
+ # @return [ForemResponse] the raw API response
207
+ # @example
208
+ # user = client.users.retrieve(42)
209
+ # user.add_trusted
210
+ # @see https://developers.forem.com/api/v1
211
+ def add_trusted(opts = {})
212
+ request(:put, "#{resource_url}/trusted", {}, opts)
213
+ end
214
+
215
+ # Remove the "trusted" role from this user.
216
+ #
217
+ # Sends a DELETE request to +/api/users/:id/trusted+. Requires admin privileges.
218
+ #
219
+ # @param opts [Hash] per-request options (e.g., +:api_key+)
220
+ # @return [ForemResponse] the raw API response
221
+ # @example
222
+ # user = client.users.retrieve(42)
223
+ # user.remove_trusted
224
+ # @see https://developers.forem.com/api/v1
225
+ def remove_trusted(opts = {})
226
+ request(:delete, "#{resource_url}/trusted", {}, opts)
227
+ end
228
+ end
229
+ end