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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/forem-ruby.gemspec +17 -0
- data/lib/forem/api_operations/create.rb +47 -0
- data/lib/forem/api_operations/delete.rb +88 -0
- data/lib/forem/api_operations/list.rb +70 -0
- data/lib/forem/api_operations/request.rb +83 -0
- data/lib/forem/api_operations/retrieve.rb +43 -0
- data/lib/forem/api_operations/save.rb +53 -0
- data/lib/forem/api_operations/update.rb +47 -0
- data/lib/forem/api_requestor.rb +283 -0
- data/lib/forem/api_resource.rb +77 -0
- data/lib/forem/client.rb +279 -0
- data/lib/forem/configuration.rb +74 -0
- data/lib/forem/connection_manager.rb +75 -0
- data/lib/forem/errors.rb +118 -0
- data/lib/forem/forem_object.rb +264 -0
- data/lib/forem/forem_response.rb +50 -0
- data/lib/forem/list_object.rb +171 -0
- data/lib/forem/resources/admin_concept.rb +169 -0
- data/lib/forem/resources/admin_user.rb +152 -0
- data/lib/forem/resources/agent_session.rb +110 -0
- data/lib/forem/resources/analytics.rb +151 -0
- data/lib/forem/resources/article.rb +256 -0
- data/lib/forem/resources/billboard.rb +76 -0
- data/lib/forem/resources/comment.rb +43 -0
- data/lib/forem/resources/concept.rb +192 -0
- data/lib/forem/resources/follow.rb +78 -0
- data/lib/forem/resources/follower.rb +56 -0
- data/lib/forem/resources/health_check.rb +70 -0
- data/lib/forem/resources/organization.rb +79 -0
- data/lib/forem/resources/page.rb +52 -0
- data/lib/forem/resources/podcast_episode.rb +36 -0
- data/lib/forem/resources/profile_image.rb +44 -0
- data/lib/forem/resources/reaction.rb +61 -0
- data/lib/forem/resources/reading_list.rb +29 -0
- data/lib/forem/resources/recommended_articles_list.rb +45 -0
- data/lib/forem/resources/request_redirect.rb +60 -0
- data/lib/forem/resources/segment.rb +103 -0
- data/lib/forem/resources/survey.rb +96 -0
- data/lib/forem/resources/tag.rb +27 -0
- data/lib/forem/resources/trend.rb +80 -0
- data/lib/forem/resources/user.rb +229 -0
- data/lib/forem/resources/video.rb +28 -0
- data/lib/forem/services/admin_concept_service.rb +138 -0
- data/lib/forem/services/admin_user_service.rb +114 -0
- data/lib/forem/services/agent_session_service.rb +87 -0
- data/lib/forem/services/analytics_service.rb +93 -0
- data/lib/forem/services/article_service.rb +233 -0
- data/lib/forem/services/base_service.rb +45 -0
- data/lib/forem/services/billboard_service.rb +91 -0
- data/lib/forem/services/comment_service.rb +51 -0
- data/lib/forem/services/concept_service.rb +143 -0
- data/lib/forem/services/follow_service.rb +61 -0
- data/lib/forem/services/follower_service.rb +34 -0
- data/lib/forem/services/health_check_service.rb +46 -0
- data/lib/forem/services/organization_service.rb +103 -0
- data/lib/forem/services/page_service.rb +107 -0
- data/lib/forem/services/podcast_episode_service.rb +34 -0
- data/lib/forem/services/profile_image_service.rb +32 -0
- data/lib/forem/services/reaction_service.rb +72 -0
- data/lib/forem/services/reading_list_service.rb +35 -0
- data/lib/forem/services/recommended_articles_list_service.rb +87 -0
- data/lib/forem/services/request_redirect_service.rb +118 -0
- data/lib/forem/services/segment_service.rb +83 -0
- data/lib/forem/services/survey_service.rb +48 -0
- data/lib/forem/services/tag_service.rb +32 -0
- data/lib/forem/services/trend_service.rb +70 -0
- data/lib/forem/services/user_service.rb +61 -0
- data/lib/forem/services/video_service.rb +33 -0
- data/lib/forem/util.rb +43 -0
- data/lib/forem/version.rb +4 -0
- data/lib/forem.rb +91 -0
- metadata +111 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module Forem
|
|
2
|
+
# Provides access to analytics data for the authenticated user's content.
|
|
3
|
+
#
|
|
4
|
+
# All analytics endpoints require authentication. They return analytics for
|
|
5
|
+
# the calling user (or an organization, when +:organization_id+ is passed).
|
|
6
|
+
#
|
|
7
|
+
# The Analytics resource exposes four read-only reporting endpoints:
|
|
8
|
+
# cumulative totals, day-by-day historical data, yesterday's aggregates,
|
|
9
|
+
# and traffic referrer breakdowns.
|
|
10
|
+
#
|
|
11
|
+
# == Response shapes
|
|
12
|
+
#
|
|
13
|
+
# The Forem analytics endpoints don't all return the same kind of object,
|
|
14
|
+
# so this resource preserves each endpoint's natural shape rather than
|
|
15
|
+
# forcing them into a uniform list:
|
|
16
|
+
#
|
|
17
|
+
# * {.totals} — single nested-stats object
|
|
18
|
+
# * {.historical} — +Hash+ keyed by +"YYYY-MM-DD"+ date string
|
|
19
|
+
# * {.past_day} — +Hash+ keyed by +"YYYY-MM-DD"+ (typically 1–2 entries)
|
|
20
|
+
# * {.referrers} — +Array+ of referrer objects (extracted from +domains+)
|
|
21
|
+
#
|
|
22
|
+
# @example Lifetime totals
|
|
23
|
+
# totals = client.analytics.totals
|
|
24
|
+
# puts totals.reactions.total #=> 7
|
|
25
|
+
# puts totals.page_views.total #=> 7
|
|
26
|
+
#
|
|
27
|
+
# @example Historical data for a date range
|
|
28
|
+
# history = client.analytics.historical(start: "2026-04-01", end: "2026-04-30")
|
|
29
|
+
# history.each do |date, stats|
|
|
30
|
+
# puts "#{date}: #{stats.page_views.total} views"
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @example Referrer breakdown
|
|
34
|
+
# client.analytics.referrers.each { |r| puts "#{r.domain}: #{r.count}" }
|
|
35
|
+
#
|
|
36
|
+
# @see https://developers.forem.com/api/v1
|
|
37
|
+
class Analytics < APIResource
|
|
38
|
+
OBJECT_NAME = "analytics"
|
|
39
|
+
RESOURCE_PATH = "/api/analytics"
|
|
40
|
+
|
|
41
|
+
# Return cumulative analytics totals for the authenticated user (or org).
|
|
42
|
+
#
|
|
43
|
+
# The response groups totals into nested buckets — +comments+, +follows+,
|
|
44
|
+
# +reactions+, and +page_views+ — each with its own sub-fields. To read
|
|
45
|
+
# a count, navigate one level deeper than you might expect (e.g.
|
|
46
|
+
# +totals.reactions.total+, not +totals.reactions+).
|
|
47
|
+
#
|
|
48
|
+
# @param params [Hash] query parameters
|
|
49
|
+
# @option params [String] :article_id filter to a specific article
|
|
50
|
+
# @option params [Integer] :organization_id ID of the organization
|
|
51
|
+
# @param opts [Hash] per-request options
|
|
52
|
+
# @return [Forem::ForemObject] cumulative stats with nested sub-objects
|
|
53
|
+
# @example
|
|
54
|
+
# totals = client.analytics.totals
|
|
55
|
+
# puts totals.reactions.total #=> 7
|
|
56
|
+
# puts totals.reactions.like #=> 2
|
|
57
|
+
# puts totals.page_views.total #=> 7
|
|
58
|
+
# @see https://developers.forem.com/api/v1
|
|
59
|
+
def self.totals(params = {}, opts = {})
|
|
60
|
+
requestor = opts[:requestor]
|
|
61
|
+
resp = request(:get, "/api/analytics/totals", params, opts)
|
|
62
|
+
Forem::ForemObject.construct_from(resp.parsed_body, requestor: requestor)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Return day-by-day historical analytics for a given date range.
|
|
66
|
+
#
|
|
67
|
+
# The response is a +Hash+ keyed by the calendar date (+"YYYY-MM-DD"+).
|
|
68
|
+
# Each value is a stats object with the same nested shape as {.totals}.
|
|
69
|
+
#
|
|
70
|
+
# @param params [Hash] query parameters
|
|
71
|
+
# @option params [String] :start (required) start date in YYYY-MM-DD form.
|
|
72
|
+
# @option params [String] :end end date in YYYY-MM-DD form.
|
|
73
|
+
# @option params [String] :article_id filter to a specific article.
|
|
74
|
+
# @option params [Integer] :organization_id ID of the organization.
|
|
75
|
+
# @param opts [Hash] per-request options
|
|
76
|
+
# @return [Hash{String => Forem::ForemObject}] stats keyed by date.
|
|
77
|
+
# @example
|
|
78
|
+
# history = client.analytics.historical(start: "2026-04-01", end: "2026-04-15")
|
|
79
|
+
# history.each do |date, stats|
|
|
80
|
+
# puts "#{date}: #{stats.page_views.total} views"
|
|
81
|
+
# end
|
|
82
|
+
# @see https://developers.forem.com/api/v1
|
|
83
|
+
def self.historical(params = {}, opts = {})
|
|
84
|
+
requestor = opts[:requestor]
|
|
85
|
+
resp = request(:get, "/api/analytics/historical", params, opts)
|
|
86
|
+
grouped_by_day(resp.parsed_body, requestor)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return aggregated analytics for the previous calendar day(s).
|
|
90
|
+
#
|
|
91
|
+
# The response shape mirrors {.historical}: a +Hash+ keyed by date.
|
|
92
|
+
# Forem typically returns one or two entries (yesterday plus today's
|
|
93
|
+
# partial), so callers usually want either the most recent date or
|
|
94
|
+
# iteration with +#each+.
|
|
95
|
+
#
|
|
96
|
+
# @param params [Hash] query parameters
|
|
97
|
+
# @option params [String] :article_id filter to a specific article.
|
|
98
|
+
# @option params [Integer] :organization_id ID of the organization.
|
|
99
|
+
# @param opts [Hash] per-request options
|
|
100
|
+
# @return [Hash{String => Forem::ForemObject}] stats keyed by date.
|
|
101
|
+
# @example
|
|
102
|
+
# stats = client.analytics.past_day
|
|
103
|
+
# latest_date, latest_stats = stats.max_by { |date, _| date }
|
|
104
|
+
# puts "#{latest_date}: #{latest_stats.page_views.total} views"
|
|
105
|
+
# @see https://developers.forem.com/api/v1
|
|
106
|
+
def self.past_day(params = {}, opts = {})
|
|
107
|
+
requestor = opts[:requestor]
|
|
108
|
+
resp = request(:get, "/api/analytics/past_day", params, opts)
|
|
109
|
+
grouped_by_day(resp.parsed_body, requestor)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Return the breakdown of traffic referrers for the authenticated user's
|
|
113
|
+
# content.
|
|
114
|
+
#
|
|
115
|
+
# The endpoint wraps the data in a +{"domains" => [...]}+ envelope. This
|
|
116
|
+
# method unwraps that envelope and returns the inner array directly so
|
|
117
|
+
# callers can iterate without an extra hop.
|
|
118
|
+
#
|
|
119
|
+
# @param params [Hash] query parameters
|
|
120
|
+
# @option params [String] :start start date in YYYY-MM-DD form.
|
|
121
|
+
# @option params [String] :end end date in YYYY-MM-DD form.
|
|
122
|
+
# @option params [String] :article_id filter to a specific article.
|
|
123
|
+
# @option params [Integer] :organization_id ID of the organization.
|
|
124
|
+
# @param opts [Hash] per-request options
|
|
125
|
+
# @return [Array<Forem::ForemObject>] referrer objects with +domain+ and
|
|
126
|
+
# +count+ fields.
|
|
127
|
+
# @example
|
|
128
|
+
# client.analytics.referrers.each { |r| puts "#{r.domain}: #{r.count}" }
|
|
129
|
+
# @see https://developers.forem.com/api/v1
|
|
130
|
+
def self.referrers(params = {}, opts = {})
|
|
131
|
+
requestor = opts[:requestor]
|
|
132
|
+
resp = request(:get, "/api/analytics/referrers", params, opts)
|
|
133
|
+
body = resp.parsed_body
|
|
134
|
+
domains = body.is_a?(Hash) ? (body["domains"] || []) : []
|
|
135
|
+
domains.map { |item| Forem::ForemObject.construct_from(item, requestor: requestor) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Internal: convert the date-keyed response of historical/past_day into a
|
|
139
|
+
# Hash<String, ForemObject>. Preserves +nil+ stat values (which the API
|
|
140
|
+
# emits for some days) by converting them to an empty ForemObject.
|
|
141
|
+
#
|
|
142
|
+
# @api private
|
|
143
|
+
def self.grouped_by_day(body, requestor)
|
|
144
|
+
return {} unless body.is_a?(Hash)
|
|
145
|
+
body.each_with_object({}) do |(date, stats), out|
|
|
146
|
+
out[date] = Forem::ForemObject.construct_from(stats || {}, requestor: requestor)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
private_class_method :grouped_by_day
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
|
|
3
|
+
module Forem
|
|
4
|
+
# Represents a Forem article (blog post).
|
|
5
|
+
#
|
|
6
|
+
# Articles are the core content type on Forem. They support full CRUD
|
|
7
|
+
# operations plus several custom endpoints for filtering by auth state,
|
|
8
|
+
# latest publication order, and full-text search.
|
|
9
|
+
#
|
|
10
|
+
# Available operations (via mixins):
|
|
11
|
+
# - +List+ — GET /api/articles
|
|
12
|
+
# - +Create+ — POST /api/articles
|
|
13
|
+
# - +Retrieve+ — GET /api/articles/:id
|
|
14
|
+
# - +Update+ — PUT /api/articles/:id
|
|
15
|
+
# - +Save+ — instance-level save (create or update)
|
|
16
|
+
#
|
|
17
|
+
# == List Parameters
|
|
18
|
+
#
|
|
19
|
+
# When calling +Article.list+, the following query parameters are supported:
|
|
20
|
+
#
|
|
21
|
+
# - +tag+ (String) — Filter by tag; can combine with +top+
|
|
22
|
+
# - +tags+ (String) — Comma-separated tags; returns articles with ANY of these
|
|
23
|
+
# - +tags_exclude+ (String) — Comma-separated tags to exclude
|
|
24
|
+
# - +username+ (String) — Filter by user or organization username
|
|
25
|
+
# - +state+ (String) — One of: +"fresh"+, +"rising"+, +"all"+.
|
|
26
|
+
# Note: +state=all+ only works combined with +username+ and returns up to 1000 items
|
|
27
|
+
# - +top+ (Integer) — Most popular articles in the last N days; can combine with +tag+
|
|
28
|
+
# - +collection_id+ (Integer) — Articles in a specific collection, ordered by publication date
|
|
29
|
+
# - +page+ (Integer) — Page number (default: 1)
|
|
30
|
+
# - +per_page+ (Integer) — Items per page (default: 30, max: 1000)
|
|
31
|
+
#
|
|
32
|
+
# == Create Parameters
|
|
33
|
+
#
|
|
34
|
+
# When calling +Article.create+, wrap all fields under the +:article+ key:
|
|
35
|
+
#
|
|
36
|
+
# @option params [Hash] :article the article payload
|
|
37
|
+
# @option params [String] 'article.title' (required)
|
|
38
|
+
# @option params [String] 'article.body_markdown' (required)
|
|
39
|
+
# @option params [String] 'article.description' (required)
|
|
40
|
+
# @option params [Boolean] 'article.published' (default: false)
|
|
41
|
+
# @option params [String] 'article.tags' comma-separated tags
|
|
42
|
+
# @option params [String] 'article.series' series name (nullable)
|
|
43
|
+
# @option params [String] 'article.canonical_url' (nullable)
|
|
44
|
+
# @option params [String] 'article.main_image' (nullable)
|
|
45
|
+
# @option params [Integer] 'article.organization_id' (nullable)
|
|
46
|
+
#
|
|
47
|
+
# @example List published articles
|
|
48
|
+
# articles = client.articles.list(per_page: 10, tag: "ruby")
|
|
49
|
+
# articles.data.each { |a| puts a.title }
|
|
50
|
+
#
|
|
51
|
+
# @example Create a new article
|
|
52
|
+
# article = client.articles.create(
|
|
53
|
+
# article: { title: "Hello World", body_markdown: "# Hello", published: false }
|
|
54
|
+
# )
|
|
55
|
+
#
|
|
56
|
+
# @example Retrieve a single article by ID
|
|
57
|
+
# article = client.articles.retrieve(12345)
|
|
58
|
+
# puts article.title
|
|
59
|
+
#
|
|
60
|
+
# @see https://developers.forem.com/api/v1#/operations/getArticles
|
|
61
|
+
# @see https://developers.forem.com/api/v1#/operations/createArticle
|
|
62
|
+
# @see https://developers.forem.com/api/v1#/operations/updateArticle
|
|
63
|
+
class Article < APIResource
|
|
64
|
+
extend APIOperations::Create
|
|
65
|
+
extend APIOperations::List
|
|
66
|
+
extend APIOperations::Retrieve
|
|
67
|
+
extend APIOperations::Update
|
|
68
|
+
include APIOperations::Save
|
|
69
|
+
|
|
70
|
+
OBJECT_NAME = "article"
|
|
71
|
+
RESOURCE_PATH = "/api/articles"
|
|
72
|
+
|
|
73
|
+
# Unpublish this article, reverting it to draft status.
|
|
74
|
+
#
|
|
75
|
+
# Marks the article as draft. Keeps content, deletes notifications,
|
|
76
|
+
# preserves comments. Requires admin or moderator role.
|
|
77
|
+
#
|
|
78
|
+
# Sends a PUT request to +/api/articles/:id/unpublish+.
|
|
79
|
+
#
|
|
80
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
81
|
+
# @return [ForemResponse] the raw API response
|
|
82
|
+
# @example
|
|
83
|
+
# article = client.articles.retrieve(42)
|
|
84
|
+
# article.unpublish
|
|
85
|
+
# @see https://developers.forem.com/api/v1#/operations/unpublishArticle
|
|
86
|
+
def unpublish(opts = {})
|
|
87
|
+
request(:put, "#{resource_url}/unpublish", {}, opts)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Return articles authored by the authenticated user (all statuses).
|
|
91
|
+
#
|
|
92
|
+
# Requires authentication. Returns articles in reverse chronological order,
|
|
93
|
+
# 30 per page.
|
|
94
|
+
#
|
|
95
|
+
# Sends a GET request to +/api/articles/me+.
|
|
96
|
+
#
|
|
97
|
+
# @param params [Hash] query parameters
|
|
98
|
+
# @option params [Integer] :page page number (default: 1)
|
|
99
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
100
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
101
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of the
|
|
102
|
+
# authenticated user's articles
|
|
103
|
+
# @example
|
|
104
|
+
# my_articles = client.articles.me(per_page: 5)
|
|
105
|
+
# my_articles.each { |a| puts "#{a.id}: #{a.title}" }
|
|
106
|
+
# @see https://developers.forem.com/api/v1#/operations/getUserArticles
|
|
107
|
+
def self.me(params = {}, opts = {})
|
|
108
|
+
paginated_list("/api/articles/me", params, opts)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Return published articles authored by the authenticated user.
|
|
112
|
+
#
|
|
113
|
+
# Requires authentication. Returns articles in reverse chronological order,
|
|
114
|
+
# 30 per page.
|
|
115
|
+
#
|
|
116
|
+
# Sends a GET request to +/api/articles/me/published+.
|
|
117
|
+
#
|
|
118
|
+
# @param params [Hash] query parameters
|
|
119
|
+
# @option params [Integer] :page page number (default: 1)
|
|
120
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
121
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
122
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of the
|
|
123
|
+
# authenticated user's published articles
|
|
124
|
+
# @example
|
|
125
|
+
# published = client.articles.me_published(per_page: 20)
|
|
126
|
+
# published.each { |a| puts a.title }
|
|
127
|
+
# @see https://developers.forem.com/api/v1#/operations/getUserArticles
|
|
128
|
+
def self.me_published(params = {}, opts = {})
|
|
129
|
+
paginated_list("/api/articles/me/published", params, opts)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Return unpublished (draft) articles authored by the authenticated user.
|
|
133
|
+
#
|
|
134
|
+
# Requires authentication. Returns articles in reverse chronological order,
|
|
135
|
+
# 30 per page.
|
|
136
|
+
#
|
|
137
|
+
# Sends a GET request to +/api/articles/me/unpublished+.
|
|
138
|
+
#
|
|
139
|
+
# @param params [Hash] query parameters
|
|
140
|
+
# @option params [Integer] :page page number (default: 1)
|
|
141
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
142
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
143
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of the
|
|
144
|
+
# authenticated user's unpublished articles
|
|
145
|
+
# @example
|
|
146
|
+
# drafts = client.articles.me_unpublished
|
|
147
|
+
# drafts.each { |a| puts "Draft: #{a.title}" }
|
|
148
|
+
# @see https://developers.forem.com/api/v1#/operations/getUserArticles
|
|
149
|
+
def self.me_unpublished(params = {}, opts = {})
|
|
150
|
+
paginated_list("/api/articles/me/unpublished", params, opts)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Return all articles authored by the authenticated user regardless of status.
|
|
154
|
+
#
|
|
155
|
+
# Requires authentication. Returns articles in reverse chronological order,
|
|
156
|
+
# 30 per page.
|
|
157
|
+
#
|
|
158
|
+
# Sends a GET request to +/api/articles/me/all+.
|
|
159
|
+
#
|
|
160
|
+
# @param params [Hash] query parameters
|
|
161
|
+
# @option params [Integer] :page page number (default: 1)
|
|
162
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
163
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
164
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of all
|
|
165
|
+
# articles (published + drafts) for the authenticated user
|
|
166
|
+
# @example
|
|
167
|
+
# all = client.articles.me_all
|
|
168
|
+
# all.auto_paging_each { |a| puts a.title }
|
|
169
|
+
# @see https://developers.forem.com/api/v1#/operations/getUserArticles
|
|
170
|
+
def self.me_all(params = {}, opts = {})
|
|
171
|
+
paginated_list("/api/articles/me/all", params, opts)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Return the most recently published articles ordered by publication date.
|
|
175
|
+
#
|
|
176
|
+
# Sends a GET request to +/api/articles/latest+.
|
|
177
|
+
#
|
|
178
|
+
# @param params [Hash] query parameters
|
|
179
|
+
# @option params [Integer] :page page number (default: 1)
|
|
180
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
181
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
182
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of latest
|
|
183
|
+
# published articles
|
|
184
|
+
# @example
|
|
185
|
+
# latest = client.articles.latest(per_page: 5)
|
|
186
|
+
# latest.each { |a| puts "#{a.published_at}: #{a.title}" }
|
|
187
|
+
# @see https://developers.forem.com/api/v1#/operations/getLatestArticles
|
|
188
|
+
def self.latest(params = {}, opts = {})
|
|
189
|
+
paginated_list("/api/articles/latest", params, opts)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Search published articles by keyword.
|
|
193
|
+
#
|
|
194
|
+
# Sends a GET request to +/api/articles/search+.
|
|
195
|
+
#
|
|
196
|
+
# @param params [Hash] query parameters
|
|
197
|
+
# @option params [String] :q search query string
|
|
198
|
+
# @option params [Integer] :page page number (default: 1)
|
|
199
|
+
# @option params [Integer] :per_page number of results per page (default: 30)
|
|
200
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
201
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of articles
|
|
202
|
+
# matching the search query
|
|
203
|
+
# @example
|
|
204
|
+
# results = client.articles.search(q: "ruby on rails")
|
|
205
|
+
# results.each { |a| puts a.title }
|
|
206
|
+
# @see https://developers.forem.com/api/v1
|
|
207
|
+
def self.search(params = {}, opts = {})
|
|
208
|
+
paginated_list("/api/articles/search", params, opts)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Semantically search published articles using Forem's embeddings-based search.
|
|
212
|
+
#
|
|
213
|
+
# Unlike +search+ (keyword matching), this endpoint finds articles whose
|
|
214
|
+
# meaning is close to the query text, using vector similarity. Requires
|
|
215
|
+
# authentication.
|
|
216
|
+
#
|
|
217
|
+
# Sends a GET request to +/api/articles/semantic_search+.
|
|
218
|
+
#
|
|
219
|
+
# @param params [Hash] query parameters
|
|
220
|
+
# @option params [String] :q the search query text (required)
|
|
221
|
+
# @option params [Integer] :page page number (default: 1)
|
|
222
|
+
# @option params [Integer] :per_page number of results per page (default: 10, max: 50)
|
|
223
|
+
# @option params [Float] :threshold optional cosine distance threshold (0.0-2.0) used
|
|
224
|
+
# to filter out weakly-related results
|
|
225
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
226
|
+
# @return [Forem::ListObject<Forem::Article>] paginated list of articles
|
|
227
|
+
# matching the query, ordered by relevance. Each article additionally
|
|
228
|
+
# exposes +distance+ (cosine distance) and +similarity+ (1 - distance)
|
|
229
|
+
# attributes.
|
|
230
|
+
# @example
|
|
231
|
+
# results = client.articles.semantic_search(q: "how to deploy rails apps")
|
|
232
|
+
# results.each { |a| puts "#{a.title} (#{a.similarity})" }
|
|
233
|
+
# @see https://developers.forem.com/api/v1
|
|
234
|
+
def self.semantic_search(params = {}, opts = {})
|
|
235
|
+
paginated_list("/api/articles/semantic_search", params, opts)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Retrieve a single article by the author's username and the article's slug.
|
|
239
|
+
#
|
|
240
|
+
# Sends a GET request to +/api/articles/:username/:slug+.
|
|
241
|
+
#
|
|
242
|
+
# @param username [String] the author's Forem username
|
|
243
|
+
# @param slug [String] the article slug (the URL-friendly title segment)
|
|
244
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
245
|
+
# @return [Forem::Article] the matching article
|
|
246
|
+
# @example
|
|
247
|
+
# article = client.articles.retrieve_by_path("ben", "my-first-post")
|
|
248
|
+
# puts article.title
|
|
249
|
+
# @see https://developers.forem.com/api/v1#/operations/getArticleByPath
|
|
250
|
+
def self.retrieve_by_path(username, slug, opts = {})
|
|
251
|
+
requestor = opts[:requestor]
|
|
252
|
+
resp = request(:get, "/api/articles/#{CGI.escape(username)}/#{CGI.escape(slug)}", {}, opts)
|
|
253
|
+
construct_from(resp.parsed_body, requestor: requestor)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Forem
|
|
2
|
+
# Represents a Forem billboard (display advertisement).
|
|
3
|
+
#
|
|
4
|
+
# Billboards are configurable ad units shown to readers on a Forem instance.
|
|
5
|
+
# They support full CRUD operations plus an +unpublish+ action that reverts
|
|
6
|
+
# a live billboard back to draft status. Managing billboards requires an
|
|
7
|
+
# admin API key.
|
|
8
|
+
#
|
|
9
|
+
# Available operations (via mixins):
|
|
10
|
+
# - +List+ — GET /api/billboards
|
|
11
|
+
# - +Create+ — POST /api/billboards
|
|
12
|
+
# - +Retrieve+ — GET /api/billboards/:id
|
|
13
|
+
# - +Update+ — PUT /api/billboards/:id
|
|
14
|
+
# - +Save+ — instance-level save (create or update)
|
|
15
|
+
#
|
|
16
|
+
# == Billboard Fields
|
|
17
|
+
#
|
|
18
|
+
# - +name+ (String) — Internal name to distinguish ads
|
|
19
|
+
# - +body_markdown+ (String, required) — The ad content in markdown
|
|
20
|
+
# - +approved+ (Boolean) — Must be both published AND approved to appear in rotation
|
|
21
|
+
# - +published+ (Boolean) — Must be both published AND approved to appear in rotation
|
|
22
|
+
# - +expires_at+ (String) — ISO 8601 timestamp; automatically unapproved after this time
|
|
23
|
+
# - +placement_area+ (String) — Which area of the site layout the ad appears in
|
|
24
|
+
# - +tag_list+ (String) — Tags on which the ad can display (blank = all tags)
|
|
25
|
+
# - +exclude_article_ids+ (String) — Comma-separated Article IDs where ad should NOT appear
|
|
26
|
+
# - +audience_segment_id+ (Integer) — Target a specific audience segment
|
|
27
|
+
# - +audience_segment_type+ (String) — Must match +audience_segment_id+ if both provided
|
|
28
|
+
# - +target_geolocations+ (Array) — ISO 3166-2 country/region codes (blank = all locations)
|
|
29
|
+
# - +display_to+ (String) — Limits which visitors see the ad
|
|
30
|
+
# - +type_of+ (String) — One of: +"in_house"+ (admin-created), +"community"+ (entity content), +"external"+ (everywhere)
|
|
31
|
+
#
|
|
32
|
+
# @example List all billboards
|
|
33
|
+
# billboards = client.billboards.list
|
|
34
|
+
# billboards.data.each { |b| puts "#{b.id}: #{b.name}" }
|
|
35
|
+
#
|
|
36
|
+
# @example Create a billboard (flat params — there is no +billboard:+ wrapper)
|
|
37
|
+
# billboard = client.billboards.create(
|
|
38
|
+
# name: "Summer Sale",
|
|
39
|
+
# body_markdown: "**50% off** all plans!",
|
|
40
|
+
# placement_area: "sidebar_left"
|
|
41
|
+
# )
|
|
42
|
+
#
|
|
43
|
+
# @example Retrieve a billboard by ID
|
|
44
|
+
# billboard = client.billboards.retrieve(7)
|
|
45
|
+
# puts billboard.name
|
|
46
|
+
#
|
|
47
|
+
# @see https://developers.forem.com/api/v1
|
|
48
|
+
class Billboard < APIResource
|
|
49
|
+
extend APIOperations::Create
|
|
50
|
+
extend APIOperations::List
|
|
51
|
+
extend APIOperations::Retrieve
|
|
52
|
+
extend APIOperations::Update
|
|
53
|
+
include APIOperations::Save
|
|
54
|
+
|
|
55
|
+
OBJECT_NAME = "billboard"
|
|
56
|
+
RESOURCE_PATH = "/api/billboards"
|
|
57
|
+
|
|
58
|
+
# Unpublish this billboard, reverting it to draft/inactive status.
|
|
59
|
+
#
|
|
60
|
+
# Marks the billboard as unpublished. A billboard must be both published
|
|
61
|
+
# AND approved to appear in rotation.
|
|
62
|
+
#
|
|
63
|
+
# Sends a PUT request to +/api/billboards/:id/unpublish+.
|
|
64
|
+
# Requires admin privileges.
|
|
65
|
+
#
|
|
66
|
+
# @param opts [Hash] per-request options (e.g., +:api_key+)
|
|
67
|
+
# @return [ForemResponse] the raw API response
|
|
68
|
+
# @example
|
|
69
|
+
# billboard = client.billboards.retrieve(7)
|
|
70
|
+
# billboard.unpublish
|
|
71
|
+
# @see https://developers.forem.com/api/v1
|
|
72
|
+
def unpublish(opts = {})
|
|
73
|
+
request(:put, "#{resource_url}/unpublish", {}, opts)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Forem
|
|
2
|
+
# Represents a comment on a Forem article or podcast episode.
|
|
3
|
+
#
|
|
4
|
+
# Comments are threaded replies attached to articles or podcast episodes.
|
|
5
|
+
# They can be listed (optionally filtered by the parent content item) or
|
|
6
|
+
# retrieved individually by ID.
|
|
7
|
+
#
|
|
8
|
+
# Available operations (via mixins):
|
|
9
|
+
# - +List+ — GET /api/comments (pass +:a_id+ or +:p_id+ to filter)
|
|
10
|
+
# - +Retrieve+ — GET /api/comments/:id (returns comment tree rooted at that ID)
|
|
11
|
+
#
|
|
12
|
+
# == List Parameters
|
|
13
|
+
#
|
|
14
|
+
# Comments must be filtered by either article or podcast episode:
|
|
15
|
+
# - +a_id+ (String) — Article identifier (required if +p_id+ not provided)
|
|
16
|
+
# - +p_id+ (String) — Podcast Episode identifier (required if +a_id+ not provided)
|
|
17
|
+
# - +page+ (Integer) — Page number
|
|
18
|
+
# - +per_page+ (Integer) — Items per page (default: 50)
|
|
19
|
+
#
|
|
20
|
+
# Returns threaded conversations with nested replies.
|
|
21
|
+
#
|
|
22
|
+
# @example List all comments for an article
|
|
23
|
+
# comments = client.comments.list(a_id: "some-article-id")
|
|
24
|
+
# comments.data.each { |c| puts c.body_html }
|
|
25
|
+
#
|
|
26
|
+
# @example List comments for a podcast episode
|
|
27
|
+
# comments = client.comments.list(p_id: "some-podcast-id")
|
|
28
|
+
# comments.data.each { |c| puts c.body_html }
|
|
29
|
+
#
|
|
30
|
+
# @example Retrieve a comment tree by root comment ID
|
|
31
|
+
# comment = client.comments.retrieve("abc123")
|
|
32
|
+
# puts comment.body_html
|
|
33
|
+
#
|
|
34
|
+
# @see https://developers.forem.com/api/v1#/operations/getCommentsByArticleId
|
|
35
|
+
# @see https://developers.forem.com/api/v1#/operations/getCommentById
|
|
36
|
+
class Comment < APIResource
|
|
37
|
+
extend APIOperations::List
|
|
38
|
+
extend APIOperations::Retrieve
|
|
39
|
+
|
|
40
|
+
OBJECT_NAME = "comment"
|
|
41
|
+
RESOURCE_PATH = "/api/comments"
|
|
42
|
+
end
|
|
43
|
+
end
|