wip-ruby 0.2.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.
- checksums.yaml +7 -0
- data/.simplecov +36 -0
- data/CHANGELOG.md +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +846 -0
- data/Rakefile +45 -0
- data/lib/wip/client.rb +84 -0
- data/lib/wip/configuration.rb +89 -0
- data/lib/wip/error.rb +76 -0
- data/lib/wip/http_client.rb +221 -0
- data/lib/wip/models/base.rb +160 -0
- data/lib/wip/models/collection.rb +81 -0
- data/lib/wip/models/comment.rb +28 -0
- data/lib/wip/models/concerns/reactable.rb +25 -0
- data/lib/wip/models/project.rb +48 -0
- data/lib/wip/models/reaction.rb +32 -0
- data/lib/wip/models/todo.rb +57 -0
- data/lib/wip/models/user.rb +51 -0
- data/lib/wip/resources/base.rb +80 -0
- data/lib/wip/resources/comments.rb +93 -0
- data/lib/wip/resources/projects.rb +42 -0
- data/lib/wip/resources/reactions.rb +74 -0
- data/lib/wip/resources/todos.rb +47 -0
- data/lib/wip/resources/uploads.rb +111 -0
- data/lib/wip/resources/users.rb +61 -0
- data/lib/wip/resources/viewer.rb +52 -0
- data/lib/wip/version.rb +5 -0
- data/lib/wip-ruby.rb +1 -0
- data/lib/wip.rb +51 -0
- data/sig/wip/ruby.rbs +6 -0
- data/test_examples.rb +435 -0
- metadata +119 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require "digest/md5"
|
|
5
|
+
require "mime/types"
|
|
6
|
+
|
|
7
|
+
module Wip
|
|
8
|
+
module Resources
|
|
9
|
+
# Resource for handling file uploads
|
|
10
|
+
class Uploads < Base
|
|
11
|
+
# Request a pre-signed URL for direct file upload
|
|
12
|
+
# @param filename [String] Name of the file to upload
|
|
13
|
+
# @param byte_size [Integer] Size of the file in bytes
|
|
14
|
+
# @param checksum [String] MD5 checksum of the file
|
|
15
|
+
# @param content_type [String] MIME type of the file
|
|
16
|
+
# @return [Hash] Upload credentials and information including url, headers, method, and signed_id
|
|
17
|
+
def request_upload_url(filename:, byte_size:, checksum:, content_type:)
|
|
18
|
+
response = client.post("/v1/uploads", {
|
|
19
|
+
filename: filename,
|
|
20
|
+
byte_size: byte_size,
|
|
21
|
+
checksum: checksum,
|
|
22
|
+
content_type: content_type
|
|
23
|
+
})
|
|
24
|
+
response.extract_resource
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Upload a file using the pre-signed URL
|
|
28
|
+
# @param url [String] Pre-signed URL for upload
|
|
29
|
+
# @param headers [Hash] Headers required for the upload
|
|
30
|
+
# @param file_path [String] Path to the file to upload
|
|
31
|
+
# @param method [String] HTTP method to use (from API response, defaults to PUT)
|
|
32
|
+
# @return [Boolean] Whether the upload was successful
|
|
33
|
+
def upload_file(url:, headers:, file_path:, method: "PUT")
|
|
34
|
+
# Create a separate connection for file uploads
|
|
35
|
+
# This ensures we don't interfere with the main API client's connection
|
|
36
|
+
connection = Faraday.new(url: url) do |f|
|
|
37
|
+
# Don't process response as JSON
|
|
38
|
+
f.response :raise_error
|
|
39
|
+
|
|
40
|
+
# Add logging if configured
|
|
41
|
+
if client.config.logger
|
|
42
|
+
f.response :logger, client.config.logger, { headers: true }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Use default adapter
|
|
46
|
+
f.adapter Faraday.default_adapter
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Ensure we have Content-Length
|
|
50
|
+
file_size = File.size(file_path)
|
|
51
|
+
headers["Content-Length"] ||= file_size.to_s
|
|
52
|
+
|
|
53
|
+
# Use the HTTP method specified by the API
|
|
54
|
+
http_method = method.to_s.downcase.to_sym
|
|
55
|
+
|
|
56
|
+
# Open file in binary mode and stream it
|
|
57
|
+
File.open(file_path, "rb") do |file|
|
|
58
|
+
response = connection.public_send(http_method) do |req|
|
|
59
|
+
# Add required headers
|
|
60
|
+
headers.each { |key, value| req.headers[key] = value }
|
|
61
|
+
|
|
62
|
+
# Stream the file
|
|
63
|
+
req.body = file.read
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return response.success?
|
|
67
|
+
end
|
|
68
|
+
rescue Faraday::Error => e
|
|
69
|
+
raise Error::UploadError, "Upload failed: #{e.message}"
|
|
70
|
+
rescue SystemCallError => e
|
|
71
|
+
raise Error::UploadError, "File access error: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Helper method to handle the complete upload process
|
|
75
|
+
# @param file_path [String] Path to the file to upload
|
|
76
|
+
# @return [String] The signed ID for the uploaded file
|
|
77
|
+
def upload(file_path)
|
|
78
|
+
# Validate file exists and is readable
|
|
79
|
+
unless File.file?(file_path) && File.readable?(file_path)
|
|
80
|
+
raise Error::UploadError, "File not found or not readable: #{file_path}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get file information
|
|
84
|
+
file_size = File.size(file_path)
|
|
85
|
+
file_name = File.basename(file_path)
|
|
86
|
+
content_type = MIME::Types.type_for(file_path).first&.content_type || "application/octet-stream"
|
|
87
|
+
checksum = Digest::MD5.file(file_path).base64digest
|
|
88
|
+
|
|
89
|
+
# Get upload URL and credentials
|
|
90
|
+
credentials = request_upload_url(
|
|
91
|
+
filename: file_name,
|
|
92
|
+
byte_size: file_size,
|
|
93
|
+
checksum: checksum,
|
|
94
|
+
content_type: content_type
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Perform the upload using the method specified by the API
|
|
98
|
+
success = upload_file(
|
|
99
|
+
url: credentials["url"],
|
|
100
|
+
headers: credentials["headers"],
|
|
101
|
+
file_path: file_path,
|
|
102
|
+
method: credentials["method"] || "PUT"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
raise Error::UploadError, "Failed to upload file" unless success
|
|
106
|
+
|
|
107
|
+
credentials["signed_id"]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../models/user"
|
|
5
|
+
require_relative "../models/project"
|
|
6
|
+
require_relative "../models/todo"
|
|
7
|
+
require_relative "../models/collection"
|
|
8
|
+
|
|
9
|
+
module Wip
|
|
10
|
+
module Resources
|
|
11
|
+
# Resource for interacting with users
|
|
12
|
+
#
|
|
13
|
+
# @example Get a user's details
|
|
14
|
+
# client.users.find("username")
|
|
15
|
+
#
|
|
16
|
+
# @example List a user's projects
|
|
17
|
+
# client.users.projects("username")
|
|
18
|
+
#
|
|
19
|
+
# @example List a user's todos
|
|
20
|
+
# client.users.todos("username", since: "2024-01-01")
|
|
21
|
+
#
|
|
22
|
+
# @note For authenticated user operations, use the Viewer resource instead.
|
|
23
|
+
# @see Viewer
|
|
24
|
+
class Users < Base
|
|
25
|
+
# Get a single user by username
|
|
26
|
+
# @param username [String] The username
|
|
27
|
+
# @return [Models::User] The user
|
|
28
|
+
def find(username)
|
|
29
|
+
path = build_path("/v1/users/{username}", username: username)
|
|
30
|
+
response = client.get(path)
|
|
31
|
+
Models::User.from_json(response.extract_resource)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# List projects for a user
|
|
35
|
+
# @param username [String] The username
|
|
36
|
+
# @param limit [Integer] Number of items to return (default: 25)
|
|
37
|
+
# @param starting_after [String] Cursor for pagination
|
|
38
|
+
# @return [Models::Collection<Models::Project>] The paginated projects
|
|
39
|
+
def projects(username, limit: nil, starting_after: nil)
|
|
40
|
+
path = build_path("/v1/users/{username}/projects", username: username)
|
|
41
|
+
params = extract_pagination_params(limit: limit, starting_after: starting_after)
|
|
42
|
+
response = client.get(path, params)
|
|
43
|
+
Models::Collection.from_json(response.extract_resource, item_class: Models::Project)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# List todos for a user
|
|
47
|
+
# @param username [String] The username
|
|
48
|
+
# @param limit [Integer] Number of items to return (default: 25)
|
|
49
|
+
# @param starting_after [String] Cursor for pagination
|
|
50
|
+
# @param since [Time, Date, String, Integer] Filter todos created since this date
|
|
51
|
+
# @param before [Time, Date, String, Integer] Filter todos created before this date (maps to API's `until` param)
|
|
52
|
+
# @return [Models::Collection<Models::Todo>] The paginated todos
|
|
53
|
+
def todos(username, limit: nil, starting_after: nil, since: nil, before: nil)
|
|
54
|
+
path = build_path("/v1/users/{username}/todos", username: username)
|
|
55
|
+
params = extract_todo_params(limit: limit, starting_after: starting_after, since: since, before: before)
|
|
56
|
+
response = client.get(path, params)
|
|
57
|
+
Models::Collection.from_json(response.extract_resource, item_class: Models::Todo)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../models/user"
|
|
5
|
+
require_relative "../models/project"
|
|
6
|
+
require_relative "../models/todo"
|
|
7
|
+
require_relative "../models/collection"
|
|
8
|
+
|
|
9
|
+
module Wip
|
|
10
|
+
module Resources
|
|
11
|
+
# Resource for interacting with the authenticated user (viewer)
|
|
12
|
+
#
|
|
13
|
+
# @example Get the authenticated user
|
|
14
|
+
# client.viewer.me
|
|
15
|
+
#
|
|
16
|
+
# @example List your todos
|
|
17
|
+
# client.viewer.todos(limit: 10)
|
|
18
|
+
#
|
|
19
|
+
# @example List your projects
|
|
20
|
+
# client.viewer.projects
|
|
21
|
+
class Viewer < Base
|
|
22
|
+
# Get the authenticated user's details
|
|
23
|
+
# @return [Models::User] The authenticated user
|
|
24
|
+
def me
|
|
25
|
+
response = client.get("/v1/users/me")
|
|
26
|
+
Models::User.from_json(response.extract_resource)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# List todos for the authenticated user
|
|
30
|
+
# @param limit [Integer] Number of items to return (default: 25)
|
|
31
|
+
# @param starting_after [String] Cursor for pagination
|
|
32
|
+
# @param since [Time, Date, String, Integer] Filter todos created since this date
|
|
33
|
+
# @param before [Time, Date, String, Integer] Filter todos created before this date (maps to API's `until` param)
|
|
34
|
+
# @return [Models::Collection<Models::Todo>] The paginated todos
|
|
35
|
+
def todos(limit: nil, starting_after: nil, since: nil, before: nil)
|
|
36
|
+
params = extract_todo_params(limit: limit, starting_after: starting_after, since: since, before: before)
|
|
37
|
+
response = client.get("/v1/users/me/todos", params)
|
|
38
|
+
Models::Collection.from_json(response.extract_resource, item_class: Models::Todo)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# List projects for the authenticated user
|
|
42
|
+
# @param limit [Integer] Number of items to return (default: 25)
|
|
43
|
+
# @param starting_after [String] Cursor for pagination
|
|
44
|
+
# @return [Models::Collection<Models::Project>] The paginated projects
|
|
45
|
+
def projects(limit: nil, starting_after: nil)
|
|
46
|
+
params = extract_pagination_params(limit: limit, starting_after: starting_after)
|
|
47
|
+
response = client.get("/v1/users/me/projects", params)
|
|
48
|
+
Models::Collection.from_json(response.extract_resource, item_class: Models::Project)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/wip/version.rb
ADDED
data/lib/wip-ruby.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative "wip"
|
data/lib/wip.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "wip/version"
|
|
4
|
+
require_relative "wip/configuration"
|
|
5
|
+
require_relative "wip/error"
|
|
6
|
+
require_relative "wip/http_client"
|
|
7
|
+
require_relative "wip/client"
|
|
8
|
+
|
|
9
|
+
# Models
|
|
10
|
+
require_relative "wip/models/base"
|
|
11
|
+
require_relative "wip/models/collection"
|
|
12
|
+
require_relative "wip/models/concerns/reactable"
|
|
13
|
+
require_relative "wip/models/user"
|
|
14
|
+
require_relative "wip/models/project"
|
|
15
|
+
require_relative "wip/models/todo"
|
|
16
|
+
require_relative "wip/models/comment"
|
|
17
|
+
require_relative "wip/models/reaction"
|
|
18
|
+
|
|
19
|
+
module Wip
|
|
20
|
+
class << self
|
|
21
|
+
# Returns the global configuration
|
|
22
|
+
# @return [Wip::Configuration]
|
|
23
|
+
def configuration
|
|
24
|
+
@configuration ||= Configuration.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Configure the gem
|
|
28
|
+
# @yield [config] Configuration instance
|
|
29
|
+
# @example
|
|
30
|
+
# Wip.configure do |config|
|
|
31
|
+
# config.api_key = "your-api-key"
|
|
32
|
+
# config.base_url = "https://api.wip.co"
|
|
33
|
+
# end
|
|
34
|
+
def configure
|
|
35
|
+
yield(configuration)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns a new API client instance
|
|
39
|
+
# @return [Wip::Client]
|
|
40
|
+
def client
|
|
41
|
+
@client ||= Client.new(configuration)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Reset the client and configuration
|
|
45
|
+
# Useful for testing
|
|
46
|
+
def reset!
|
|
47
|
+
@configuration = nil
|
|
48
|
+
@client = nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|