Skip to content

Commit

Permalink
F2: Integrate Freefeed API (#593)
Browse files Browse the repository at this point in the history
* Remove shadows

* Merge Freefeed API wrapper

* Use HTTP client directly

* Test coverage

* Fix method calls

* Clean up

* Rubocop
  • Loading branch information
dreikanter authored Oct 19, 2024
1 parent 27e0e65 commit 0a2effe
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 2 deletions.
2 changes: 1 addition & 1 deletion app/views/passwords/edit.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card">
<div class="card-header bg-transparent py-3">
<h1 class="card-title text-center mb-0 fs-3">Update your password</h1>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/passwords/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card">
<div class="card-header bg-transparent py-3">
<h1 class="card-title text-center mb-0 fs-3">Forgot your password?</h1>
</div>
Expand Down
3 changes: 3 additions & 0 deletions lib/freefeed.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Freefeed
BASE_URL = "https://freefeed.net"
end
9 changes: 9 additions & 0 deletions lib/freefeed/authenticated_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Freefeed
class AuthenticatedRequest < Request
private

def headers
super.merge(authorization: "Bearer #{client.token}")
end
end
end
18 changes: 18 additions & 0 deletions lib/freefeed/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Freefeed
class Client
include Freefeed::V1::Attachments
include Freefeed::V1::Comments
include Freefeed::V1::Posts
include Freefeed::V2::Notifications
include Freefeed::V2::Posts
include Freefeed::V2::Timelines
include Freefeed::V2::Users

attr_reader :token, :base_url

def initialize(token:, base_url: Freefeed::BASE_URL)
@token = token
@base_url = base_url
end
end
end
40 changes: 40 additions & 0 deletions lib/freefeed/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Freefeed
class Error < StandardError
ClientError = Class.new(self)
ServerError = Class.new(self)

BadRequest = Class.new(ClientError)
Forbidden = Class.new(ClientError)
NotAcceptable = Class.new(ClientError)
NotFound = Class.new(ClientError)
RequestEntityTooLarge = Class.new(ClientError)
TooManyRequests = Class.new(ClientError)
Unauthorized = Class.new(ClientError)
UnprocessableEntity = Class.new(ClientError)

BadGateway = Class.new(ServerError)
GatewayTimeout = Class.new(ServerError)
InternalServerError = Class.new(ServerError)
ServiceUnavailable = Class.new(ServerError)

ERRORS = {
400 => Freefeed::Error::BadRequest,
401 => Freefeed::Error::Unauthorized,
403 => Freefeed::Error::Forbidden,
404 => Freefeed::Error::NotFound,
406 => Freefeed::Error::NotAcceptable,
413 => Freefeed::Error::RequestEntityTooLarge,
422 => Freefeed::Error::UnprocessableEntity,
429 => Freefeed::Error::TooManyRequests,
500 => Freefeed::Error::InternalServerError,
502 => Freefeed::Error::BadGateway,
503 => Freefeed::Error::ServiceUnavailable,
504 => Freefeed::Error::GatewayTimeout
}.freeze

# TODO: Populate error object with details
def self.for(response)
ERRORS[response.code]
end
end
end
55 changes: 55 additions & 0 deletions lib/freefeed/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Freefeed
class Request
DEFAULT_OPTIONS = {
http_max_hops: 3,
http_timeout_seconds: 5
}.freeze

attr_reader :client, :request_method, :path, :options

def initialize(client:, request_method:, path:, options: {})
@client = client
@request_method = request_method
@path = path
@options = options
end

def call
response = http.headers(headers).public_send(request_method, uri, **request_params)
ensure_successful_response(response)
response
end

private

def ensure_successful_response(response)
error = Freefeed::Error.for(response)
return unless error
Honeybadger.context(failed_request_params: request_params)
raise(error)
end

def uri
@uri ||= URI.parse(client.base_url + path).to_s
end

def request_params
@request_params ||= options.slice(:json, :form, :params, :body)
end

def headers
{
accept: "*/*",
user_agent: "feeder"
}
end

def http
HTTP.follow(max_hops: option(:http_max_hops)).timeout(option(:http_timeout_seconds))
end

def option(option_name)
options.fetch(option_name) { DEFAULT_OPTIONS.fetch(option_name) }
end
end
end
21 changes: 21 additions & 0 deletions lib/freefeed/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Freefeed
module Utils
def authenticated_request(request_method, path, params = {})
AuthenticatedRequest.new(
client: self,
request_method: request_method,
path: path,
options: params
).call
end

def request(request_method, path, params = {})
Request.new(
client: self,
request_method: request_method,
path: path,
options: params
).call
end
end
end
24 changes: 24 additions & 0 deletions lib/freefeed/v1/attachments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Freefeed
module V1
module Attachments
# @param [String, Pathname, IO] source could by a file path
# or an IO object
def create_attachment(source, content_type: nil)
options = {form: {file: file(source, content_type)}}
authenticated_request(:post, "/v1/attachments", options)
end

private

def file(source, content_type)
content_type ||= detect_content_type(source)
HTTP::FormData::File.new(source, content_type: content_type)
end

def detect_content_type(source)
return MimeMagic.by_magic(source) if source.is_a?(IO)
MimeMagic.by_path(source.to_s)
end
end
end
end
17 changes: 17 additions & 0 deletions lib/freefeed/v1/comments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Freefeed
module V1
module Comments
def create_comment(comment)
authenticated_request(:post, "/v1/comments", json: comment)
end

def update_comment(id, comment)
authenticated_request(:put, "/v1/comments/#{id}", json: comment)
end

def delete_comment(id)
authenticated_request(:delete, "/v1/comments/#{id}")
end
end
end
end
51 changes: 51 additions & 0 deletions lib/freefeed/v1/posts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module Freefeed
module V1
module Posts
include Freefeed::Utils

def create_post(post)
authenticated_request(:post, "/v1/posts", json: post)
end

def update_post(id, post)
authenticated_request(:put, "/v1/posts/#{id}", json: post)
end

def delete_post(id)
authenticated_request(:delete, "/v1/posts/#{id}")
end

def like(id)
authenticated_request(:post, "/v1/posts/#{id}/like")
end

def unlike(id)
authenticated_request(:post, "/v1/posts/#{id}/unlike")
end

def hide(id)
authenticated_request(:post, "/v1/posts/#{id}/hide")
end

def unhide(id)
authenticated_request(:post, "/v1/posts/#{id}/unhide")
end

def save(id)
authenticated_request(:post, "/v1/posts/#{id}/save")
end

def unsave(id)
authenticated_request(:delete, "/v1/posts/#{id}/save")
end

def disable_comments(id)
authenticated_request(:post, "/v1/posts/#{id}/disableComments")
end

def enable_comments(id)
authenticated_request(:post, "/v1/posts/#{id}/enableComments")
end
end
end
end
11 changes: 11 additions & 0 deletions lib/freefeed/v2/notifications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Freefeed
module V2
module Notifications
include Freefeed::Utils

def notifications
authenticated_request(:get, "/v2/notifications")
end
end
end
end
15 changes: 15 additions & 0 deletions lib/freefeed/v2/posts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Freefeed
module V2
module Posts
include Freefeed::Utils

def post(id)
authenticated_request(:get, "/v2/posts/#{id}")
end

def post_open_graph(id)
authenticated_request(:get, "/v2/posts-opengraph/#{id}")
end
end
end
end
39 changes: 39 additions & 0 deletions lib/freefeed/v2/timelines.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# :reek:DataClump
module Freefeed
module V2
module Timelines
include Freefeed::Utils

def best_of
authenticated_request(:get, "/v2/bestof")
end

def everything
request(:get, "/v2/everything")
end

def own_timeline(filter: nil, offset: 0)
request_timeline(filter ? "filter/#{filter}" : "home", offset)
end

def timeline(username, offset: 0)
request_timeline(username, offset)
end

def comments_timeline(username, offset: 0)
request_timeline("#{username}/comments", offset)
end

def likes_timeline(username, offset: 0)
request_timeline("#{username}/likes", offset)
end

private

def request_timeline(path, offset)
params = offset.positive? ? {json: {offset: offset}} : {}
authenticated_request(:get, "/v2/timelines/#{path}", params)
end
end
end
end
29 changes: 29 additions & 0 deletions lib/freefeed/v2/users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Freefeed
module V2
module Users
def whoami
authenticated_request(:get, "/v2/users/whoami")
end

def blocked_by_me
authenticated_request(:get, "/v2/users/blockedByMe")
end

def unread_directs_number
authenticated_request(:get, "/v2/users/getUnreadDirectsNumber")
end

def unread_notifications_number
authenticated_request(:get, "/v2/users/getUnreadNotificationsNumber")
end

def mark_all_directs_as_read
authenticated_request(:get, "/v2/users/markAllDirectsAsRead")
end

def mark_all_notifications_as_read
authenticated_request(:post, "/v2/users/markAllNotificationsAsRead")
end
end
end
end
12 changes: 12 additions & 0 deletions spec/lib/freefeed/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "rails_helper"

RSpec.describe Freefeed::Client do
let(:token) { "TEST_TOKEN" }
let(:base_url) { "https://example.com" }
let(:client) { described_class.new(token: token, base_url: base_url) }

it "initializes with token and base_url" do
expect(client.token).to eq(token)
expect(client.base_url).to eq(base_url)
end
end
9 changes: 9 additions & 0 deletions spec/lib/freefeed/error_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "rails_helper"

RSpec.describe Freefeed::Error do
it "returns the correct error class for a response code" do
response = HTTP::Response.new(status: 404, version: "1.1", body: "", request: nil)

expect(described_class.for(response)).to eq(Freefeed::Error::NotFound)
end
end
Loading

0 comments on commit 0a2effe

Please sign in to comment.