wikiwiki 0.5.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/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.ja.md +96 -0
- data/README.md +96 -0
- data/lib/wikiwiki/api.rb +222 -0
- data/lib/wikiwiki/attachment.rb +39 -0
- data/lib/wikiwiki/auth/api_key.rb +22 -0
- data/lib/wikiwiki/auth/password.rb +19 -0
- data/lib/wikiwiki/auth.rb +21 -0
- data/lib/wikiwiki/page.rb +27 -0
- data/lib/wikiwiki/rate_limiter.rb +164 -0
- data/lib/wikiwiki/sliding_window.rb +49 -0
- data/lib/wikiwiki/version.rb +7 -0
- data/lib/wikiwiki/wiki.rb +153 -0
- data/lib/wikiwiki.rb +43 -0
- data/mise.toml +6 -0
- data/rbs_collection.yaml +12 -0
- data/sig/wikiwiki/api.rbs +36 -0
- data/sig/wikiwiki/attachment.rbs +12 -0
- data/sig/wikiwiki/auth/api_key.rbs +10 -0
- data/sig/wikiwiki/auth/password.rbs +9 -0
- data/sig/wikiwiki/auth.rbs +7 -0
- data/sig/wikiwiki/page.rbs +9 -0
- data/sig/wikiwiki/rate_limiter.rbs +41 -0
- data/sig/wikiwiki/sliding_window.rbs +19 -0
- data/sig/wikiwiki/wiki.rbs +28 -0
- data/sig/wikiwiki.rbs +24 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c94061d746d2242a2a78e735c43fa5fba37888231ba3a92e959aeafa5afaec3a
|
|
4
|
+
data.tar.gz: c9c3f4c74090aa044e10e48b72b03ad2a7b1b58713fec6e0447a871439153c05
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9bae7372d114df3f32f4f60d3ae1b4664ff3581c30ba0215e5d705de733063b2d12faaa92c557439f29b1e9b9707e9c3993400e738b672125365b7be24d401c1
|
|
7
|
+
data.tar.gz: f6e397e07040ba97328609f437a30a29be059c2df7c7051d78def34c204f6d5bf6005e618014893ffddcb36e20cc41db414eb1187435776a60217b2177c960ee
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.5.0] - 2025-10-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release of Wikiwiki REST API client library
|
|
8
|
+
- Support for password and API key authentication
|
|
9
|
+
- Page operations: list, read, and update
|
|
10
|
+
- Attachment operations: list, download, upload, and delete
|
|
11
|
+
- Automatic rate limiting with configurable strategies (raise, wait, or no limit)
|
|
12
|
+
- Full RBS type signatures for type safety
|
|
13
|
+
- `Wiki#url` method to get the wiki URL as a frozen `URI::HTTPS` instance
|
|
14
|
+
- Logging support for API requests and responses with configurable logger (default: Logger.new($stdout))
|
|
15
|
+
- Request/response URLs and status codes logged at INFO level
|
|
16
|
+
- HTTP headers logged at DEBUG level (with Authorization masked as "Bearer ***")
|
|
17
|
+
- Each log entry is prefixed with `[wiki-id]` for easy filtering
|
|
18
|
+
- Logger accessible via `Wiki#logger` and `API#logger` reader methods
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OZAWA Sakuro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.ja.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Wikiwiki
|
|
2
|
+
|
|
3
|
+
{file:README.md English version}
|
|
4
|
+
|
|
5
|
+
[Wikiwiki](https://wikiwiki.jp/) REST API用のRubyクライアントライブラリです。
|
|
6
|
+
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
9
|
+
このgemは、Wikiwikiのwikiをプログラムから操作するためのシンプルなインターフェースを提供します。ページ操作(一覧取得、読み取り、書き込み)と添付ファイル管理(一覧取得、アップロード、ダウンロード、削除)をサポートしています。
|
|
10
|
+
|
|
11
|
+
## インストール
|
|
12
|
+
|
|
13
|
+
アプリケーションのGemfileに以下の行を追加してください:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "wikiwiki"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
その後、以下を実行してください:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
または、直接インストールすることもできます:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install wikiwiki
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 認証
|
|
32
|
+
|
|
33
|
+
APIを使用する前に、wikiの管理画面でREST APIアクセスを有効にしてください。
|
|
34
|
+
|
|
35
|
+
### パスワード認証
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
auth = Wikiwiki::Auth.password(password: "your_admin_password")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### APIキー認証
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
auth = Wikiwiki::Auth.api_key(api_key_id: "your_api_key_id", secret: "your_secret")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 使い方
|
|
48
|
+
|
|
49
|
+
### 基本的な使用例
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require "wikiwiki"
|
|
53
|
+
|
|
54
|
+
# 認証情報を使って初期化
|
|
55
|
+
auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
56
|
+
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
57
|
+
|
|
58
|
+
# すべてのページ名の一覧を取得
|
|
59
|
+
page_names = wiki.page_names
|
|
60
|
+
# => ["FrontPage", "SideBar", ...]
|
|
61
|
+
|
|
62
|
+
# ページを取得
|
|
63
|
+
page = wiki.page(page_name: "FrontPage")
|
|
64
|
+
puts page.source
|
|
65
|
+
puts page.timestamp
|
|
66
|
+
|
|
67
|
+
# ページを更新
|
|
68
|
+
wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
69
|
+
TITLE:Test
|
|
70
|
+
# Hello World
|
|
71
|
+
SOURCE
|
|
72
|
+
|
|
73
|
+
# 添付ファイル名の一覧を取得
|
|
74
|
+
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
75
|
+
|
|
76
|
+
# 添付ファイルをダウンロード
|
|
77
|
+
attachment = wiki.attachment(page_name: "FrontPage", attachment_name: "logo.png")
|
|
78
|
+
File.binwrite("logo.png", attachment.content)
|
|
79
|
+
# 注意: attachment.nameをファイル名として使用する場合は、ディレクトリトラバーサル攻撃を防ぐために安全性を検証してください
|
|
80
|
+
|
|
81
|
+
# 添付ファイルをアップロード
|
|
82
|
+
content = File.binread("image.png")
|
|
83
|
+
wiki.add_attachment(page_name: "FrontPage", attachment_name: "image.png", content:)
|
|
84
|
+
|
|
85
|
+
# 添付ファイルを削除
|
|
86
|
+
wiki.delete_attachment(page_name: "FrontPage", attachment_name: "image.png")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## リファレンス
|
|
90
|
+
|
|
91
|
+
- [ページ操作API](https://z.wikiwiki.jp/wikiwiki-rest-api/topic/1)
|
|
92
|
+
- [ファイル操作API](https://z.wikiwiki.jp/wikiwiki-rest-api/topic/3)
|
|
93
|
+
|
|
94
|
+
## ライセンス
|
|
95
|
+
|
|
96
|
+
このgemは[MITライセンス](https://opensource.org/licenses/MIT)の条件の下でオープンソースとして利用可能です。
|
data/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Wikiwiki
|
|
2
|
+
|
|
3
|
+
{file:README.ja.md 日本語版}
|
|
4
|
+
|
|
5
|
+
A Ruby client library for the [Wikiwiki](https://wikiwiki.jp/) REST API.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This gem provides a simple interface to interact with Wikiwiki wikis programmatically. It supports page operations (list, read, write) and attachment management (list, upload, download, delete).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Add this line to your application's Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "wikiwiki"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
And then execute:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install it yourself as:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install wikiwiki
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Before using the API, enable REST API access in your wiki's admin panel.
|
|
34
|
+
|
|
35
|
+
### Password Authentication
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
auth = Wikiwiki::Auth.password(password: "your_admin_password")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### API Key Authentication
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
auth = Wikiwiki::Auth.api_key(api_key_id: "your_api_key_id", secret: "your_secret")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Basic Example
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require "wikiwiki"
|
|
53
|
+
|
|
54
|
+
# Initialize with authentication
|
|
55
|
+
auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
56
|
+
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
57
|
+
|
|
58
|
+
# List all page names
|
|
59
|
+
page_names = wiki.page_names
|
|
60
|
+
# => ["FrontPage", "SideBar", ...]
|
|
61
|
+
|
|
62
|
+
# Read a page
|
|
63
|
+
page = wiki.page(page_name: "FrontPage")
|
|
64
|
+
puts page.source
|
|
65
|
+
puts page.timestamp
|
|
66
|
+
|
|
67
|
+
# Update a page
|
|
68
|
+
wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
69
|
+
TITLE:Test
|
|
70
|
+
# Hello World
|
|
71
|
+
SOURCE
|
|
72
|
+
|
|
73
|
+
# List attachment names
|
|
74
|
+
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
75
|
+
|
|
76
|
+
# Download an attachment
|
|
77
|
+
attachment = wiki.attachment(page_name: "FrontPage", attachment_name: "logo.png")
|
|
78
|
+
File.binwrite("logo.png", attachment.content)
|
|
79
|
+
# Note: If using attachment.name as filename, validate it first to prevent directory traversal attacks
|
|
80
|
+
|
|
81
|
+
# Upload an attachment
|
|
82
|
+
content = File.binread("image.png")
|
|
83
|
+
wiki.add_attachment(page_name: "FrontPage", attachment_name: "image.png", content:)
|
|
84
|
+
|
|
85
|
+
# Delete an attachment
|
|
86
|
+
wiki.delete_attachment(page_name: "FrontPage", attachment_name: "image.png")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Reference
|
|
90
|
+
|
|
91
|
+
- [Page Operations API](https://z.wikiwiki.jp/wikiwiki-rest-api/topic/1) (Japanese)
|
|
92
|
+
- [File Operations API](https://z.wikiwiki.jp/wikiwiki-rest-api/topic/3) (Japanese)
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/wikiwiki/api.rb
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Wikiwiki
|
|
8
|
+
# Handles HTTP communication with the Wikiwiki REST API
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# auth = Wikiwiki::Auth.password(password: "secret")
|
|
12
|
+
# api = Wikiwiki::API.new(wiki_id: "my-wiki", auth:)
|
|
13
|
+
# pages = api.get_pages
|
|
14
|
+
class API
|
|
15
|
+
attr_reader :logger
|
|
16
|
+
|
|
17
|
+
BASE_URL = URI.parse("https://api.wikiwiki.jp").freeze
|
|
18
|
+
private_constant :BASE_URL
|
|
19
|
+
|
|
20
|
+
# Initialize a new API client and authenticate
|
|
21
|
+
#
|
|
22
|
+
# @param wiki_id [String] the wiki identifier
|
|
23
|
+
# @param auth [Auth::Password, Auth::ApiKey] authentication credentials
|
|
24
|
+
# @param logger [Logger] logger instance for request/response logging
|
|
25
|
+
# @param rate_limiter [RateLimiter] rate limiter instance (default: RateLimiter.default)
|
|
26
|
+
# @raise [Error] if authentication fails
|
|
27
|
+
def initialize(wiki_id:, auth:, logger:, rate_limiter: RateLimiter.default)
|
|
28
|
+
@wiki_id = wiki_id
|
|
29
|
+
@rate_limiter = rate_limiter
|
|
30
|
+
@logger = logger
|
|
31
|
+
@token = authenticate(auth)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get list of all pages
|
|
35
|
+
#
|
|
36
|
+
# @return [Hash] response with "pages" key containing array of page info
|
|
37
|
+
# @raise [Error] if request fails
|
|
38
|
+
def get_pages
|
|
39
|
+
uri = BASE_URL + "/#{wiki_id}/pages"
|
|
40
|
+
response = request(:get, uri)
|
|
41
|
+
|
|
42
|
+
parse_json_response(response)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get a specific page
|
|
46
|
+
#
|
|
47
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
48
|
+
# @return [Hash] response with "page", "source", and "timestamp" keys
|
|
49
|
+
# @raise [Error] if request fails
|
|
50
|
+
def get_page(encoded_page_name:)
|
|
51
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}"
|
|
52
|
+
response = request(:get, uri)
|
|
53
|
+
|
|
54
|
+
parse_json_response(response)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Update a page
|
|
58
|
+
#
|
|
59
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
60
|
+
# @param source [String] the page source content
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @raise [Error] if request fails
|
|
63
|
+
def put_page(encoded_page_name:, source:)
|
|
64
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}"
|
|
65
|
+
response = request(:put, uri, body: {"source" => source})
|
|
66
|
+
|
|
67
|
+
parse_json_response(response)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get list of attachments on a page
|
|
71
|
+
#
|
|
72
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
73
|
+
# @return [Hash] response with "attachments" key containing hash of file info
|
|
74
|
+
# @raise [Error] if request fails
|
|
75
|
+
def get_attachments(encoded_page_name:)
|
|
76
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}/attachments"
|
|
77
|
+
response = request(:get, uri)
|
|
78
|
+
|
|
79
|
+
parse_json_response(response)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get a specific attachment
|
|
83
|
+
#
|
|
84
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
85
|
+
# @param encoded_attachment_name [String] the URL-encoded attachment file name
|
|
86
|
+
# @param rev [String, nil] optional MD5 hash for specific revision
|
|
87
|
+
# @return [Hash] file info with Base64-encoded "src" data
|
|
88
|
+
# @raise [Error] if request fails
|
|
89
|
+
def get_attachment(encoded_page_name:, encoded_attachment_name:, rev: nil)
|
|
90
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}/attachment/#{encoded_attachment_name}"
|
|
91
|
+
uri.query = "rev=#{rev}" if rev
|
|
92
|
+
response = request(:get, uri)
|
|
93
|
+
|
|
94
|
+
parse_json_response(response)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Upload an attachment to a page
|
|
98
|
+
#
|
|
99
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
100
|
+
# @param attachment_name [String] the attachment file name (not URL-encoded; sent in JSON body)
|
|
101
|
+
# @param encoded_content [String] Base64-encoded file data
|
|
102
|
+
# @return [void]
|
|
103
|
+
# @raise [Error] if request fails
|
|
104
|
+
def put_attachment(encoded_page_name:, attachment_name:, encoded_content:)
|
|
105
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}/attachment"
|
|
106
|
+
response = request(:put, uri, body: {"filename" => attachment_name, "data" => encoded_content})
|
|
107
|
+
|
|
108
|
+
parse_json_response(response)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Delete an attachment from a page
|
|
112
|
+
#
|
|
113
|
+
# @param encoded_page_name [String] the URL-encoded page name
|
|
114
|
+
# @param encoded_attachment_name [String] the URL-encoded attachment file name
|
|
115
|
+
# @return [void]
|
|
116
|
+
# @raise [Error] if request fails
|
|
117
|
+
def delete_attachment(encoded_page_name:, encoded_attachment_name:)
|
|
118
|
+
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}/attachment/#{encoded_attachment_name}"
|
|
119
|
+
response = request(:delete, uri)
|
|
120
|
+
|
|
121
|
+
parse_json_response(response)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Authenticate with the Wikiwiki API
|
|
125
|
+
#
|
|
126
|
+
# @param auth [Auth::Password, Auth::ApiKey] authentication credentials
|
|
127
|
+
# @return [String] JWT token
|
|
128
|
+
# @raise [Error] if authentication fails
|
|
129
|
+
private def authenticate(auth)
|
|
130
|
+
uri = BASE_URL + "/#{wiki_id}/auth"
|
|
131
|
+
response = request(:post, uri, body: auth.to_h, authenticate: false)
|
|
132
|
+
data = parse_json_response(response)
|
|
133
|
+
data["token"]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Parse JSON response
|
|
137
|
+
#
|
|
138
|
+
# @param response [Net::HTTPResponse] HTTP response
|
|
139
|
+
# @return [Hash] parsed response body
|
|
140
|
+
# @raise [AuthenticationError] if authentication fails (401)
|
|
141
|
+
# @raise [ResourceNotFoundError] if resource not found (404)
|
|
142
|
+
# @raise [ServerError] if server error (5xx)
|
|
143
|
+
# @raise [APIError] if other API request fails
|
|
144
|
+
private def parse_json_response(response)
|
|
145
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
146
|
+
message = "API request failed: #{response.code} #{response.message}"
|
|
147
|
+
case Integer(response.code, 10)
|
|
148
|
+
when 401
|
|
149
|
+
raise AuthenticationError, message
|
|
150
|
+
when 404
|
|
151
|
+
raise ResourceNotFoundError, message
|
|
152
|
+
when 500..599
|
|
153
|
+
raise ServerError, message
|
|
154
|
+
else
|
|
155
|
+
raise APIError, message
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
JSON.parse(response.body)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Send HTTP request
|
|
163
|
+
#
|
|
164
|
+
# @param method [Symbol] HTTP method (:get, :post, :put, :delete)
|
|
165
|
+
# @param uri [URI::HTTPS] request URI
|
|
166
|
+
# @param body [Hash, nil] request body (only for methods that permit body)
|
|
167
|
+
# @param authenticate [Boolean] whether to include authentication header
|
|
168
|
+
# @return [Net::HTTPResponse] HTTP response
|
|
169
|
+
private def request(method, uri, body: nil, authenticate: true)
|
|
170
|
+
@rate_limiter.acquire!
|
|
171
|
+
|
|
172
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
173
|
+
http.use_ssl = true
|
|
174
|
+
|
|
175
|
+
request_class = Net::HTTP.const_get(method.capitalize)
|
|
176
|
+
request = request_class.new(uri.request_uri)
|
|
177
|
+
|
|
178
|
+
if request.request_body_permitted? && body
|
|
179
|
+
request["Content-Type"] = "application/json"
|
|
180
|
+
request.body = JSON.generate(body)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
request["Authorization"] = "Bearer #{token}" if authenticate
|
|
184
|
+
|
|
185
|
+
log_request(method, uri, request)
|
|
186
|
+
response = http.request(request)
|
|
187
|
+
log_response(response)
|
|
188
|
+
|
|
189
|
+
response
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Log HTTP request details
|
|
193
|
+
#
|
|
194
|
+
# @param method [Symbol] HTTP method
|
|
195
|
+
# @param uri [URI::HTTPS] request URI
|
|
196
|
+
# @param request [Net::HTTPRequest] HTTP request object
|
|
197
|
+
private def log_request(method, uri, request)
|
|
198
|
+
logger.info("[#{wiki_id}] API Request: #{method.upcase} #{uri}")
|
|
199
|
+
|
|
200
|
+
request.each_header do |key, value|
|
|
201
|
+
if key.casecmp("authorization").zero?
|
|
202
|
+
logger.debug("[#{wiki_id}] #{key}: Bearer ***")
|
|
203
|
+
else
|
|
204
|
+
logger.debug("[#{wiki_id}] #{key}: #{value}")
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Log HTTP response details
|
|
210
|
+
#
|
|
211
|
+
# @param response [Net::HTTPResponse] HTTP response object
|
|
212
|
+
private def log_response(response)
|
|
213
|
+
logger.info("[#{wiki_id}] API Response: #{response.code} #{response.message}")
|
|
214
|
+
|
|
215
|
+
response.each_header do |key, value|
|
|
216
|
+
logger.debug("[#{wiki_id}] #{key}: #{value}")
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private attr_reader :wiki_id, :token
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
# Represents a file attachment on a wiki page
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
8
|
+
# wiki = Wikiwiki::Wiki.new(wiki_id: "my-wiki", auth:)
|
|
9
|
+
# page = wiki.page(page_name: "FrontPage")
|
|
10
|
+
# attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
11
|
+
# attachment = wiki.attachment(page_name: "FrontPage", attachment_name: attachment_names.first)
|
|
12
|
+
# attachment.name # => "logo.png"
|
|
13
|
+
# attachment.size # => 12345
|
|
14
|
+
# attachment.time # => 2022-01-01 00:00:00 +0900
|
|
15
|
+
# attachment.content # => binary data (decoded)
|
|
16
|
+
Attachment = Data.define(:page_name, :name, :size, :time, :type, :content)
|
|
17
|
+
|
|
18
|
+
class Attachment
|
|
19
|
+
# Reopen the class to add YARD documentation for attributes
|
|
20
|
+
|
|
21
|
+
# @!attribute [r] page_name
|
|
22
|
+
# @return [String] the page name this attachment belongs to
|
|
23
|
+
|
|
24
|
+
# @!attribute [r] name
|
|
25
|
+
# @return [String] the attachment file name
|
|
26
|
+
|
|
27
|
+
# @!attribute [r] size
|
|
28
|
+
# @return [Integer] the file size in bytes
|
|
29
|
+
|
|
30
|
+
# @!attribute [r] time
|
|
31
|
+
# @return [Time] the upload time
|
|
32
|
+
|
|
33
|
+
# @!attribute [r] type
|
|
34
|
+
# @return [String] the MIME type
|
|
35
|
+
|
|
36
|
+
# @!attribute [r] content
|
|
37
|
+
# @return [String] the binary file content (decoded from Base64)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
module Auth
|
|
5
|
+
# API key-based authentication credentials
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# auth = Wikiwiki::Auth::ApiKey.new(api_key_id: "key_id", secret: "secret")
|
|
9
|
+
# auth.to_h # => {api_key_id: "key_id", secret: "secret"}
|
|
10
|
+
ApiKey = Data.define(:api_key_id, :secret)
|
|
11
|
+
|
|
12
|
+
class ApiKey
|
|
13
|
+
# Reopen the class to add YARD documentation for attributes
|
|
14
|
+
|
|
15
|
+
# @!attribute [r] api_key_id
|
|
16
|
+
# @return [String] the API key ID
|
|
17
|
+
|
|
18
|
+
# @!attribute [r] secret
|
|
19
|
+
# @return [String] the secret key
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
module Auth
|
|
5
|
+
# Password-based authentication credentials
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# auth = Wikiwiki::Auth::Password.new(password: "admin_password")
|
|
9
|
+
# auth.to_h # => {password: "admin_password"}
|
|
10
|
+
Password = Data.define(:password)
|
|
11
|
+
|
|
12
|
+
class Password
|
|
13
|
+
# Reopen the class to add YARD documentation for attributes
|
|
14
|
+
|
|
15
|
+
# @!attribute [r] password
|
|
16
|
+
# @return [String] the admin password
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
# Authentication credentials module
|
|
5
|
+
#
|
|
6
|
+
# Provides factory methods for creating authentication objects
|
|
7
|
+
module Auth
|
|
8
|
+
# Create password-based authentication credentials
|
|
9
|
+
#
|
|
10
|
+
# @param password [String] admin password
|
|
11
|
+
# @return [Password] password authentication object
|
|
12
|
+
def self.password(password:) = Password.new(password:)
|
|
13
|
+
|
|
14
|
+
# Create API key-based authentication credentials
|
|
15
|
+
#
|
|
16
|
+
# @param api_key_id [String] API key ID
|
|
17
|
+
# @param secret [String] secret key
|
|
18
|
+
# @return [ApiKey] API key authentication object
|
|
19
|
+
def self.api_key(api_key_id:, secret:) = ApiKey.new(api_key_id:, secret:)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
# Represents a wiki page
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
8
|
+
# wiki = Wikiwiki::Wiki.new(wiki_id: "my-wiki", auth:)
|
|
9
|
+
# page = wiki.page(page_name: "FrontPage")
|
|
10
|
+
# page.name # => "FrontPage"
|
|
11
|
+
# page.source # => "TITLE:FrontPage\n..."
|
|
12
|
+
# page.timestamp # => 2022-01-01 00:00:00 +0900
|
|
13
|
+
Page = Data.define(:name, :source, :timestamp)
|
|
14
|
+
|
|
15
|
+
class Page
|
|
16
|
+
# Reopen the class to add YARD documentation for attributes
|
|
17
|
+
|
|
18
|
+
# @!attribute [r] name
|
|
19
|
+
# @return [String] the page name
|
|
20
|
+
|
|
21
|
+
# @!attribute [r] source
|
|
22
|
+
# @return [String] the page source content
|
|
23
|
+
|
|
24
|
+
# @!attribute [r] timestamp
|
|
25
|
+
# @return [Time] the last update timestamp
|
|
26
|
+
end
|
|
27
|
+
end
|