wikiwiki 0.5.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +36 -0
- data/README.ja.md +100 -3
- data/README.md +100 -3
- data/exe/wikiwiki +8 -0
- data/lib/wikiwiki/api.rb +42 -6
- data/lib/wikiwiki/auth/token.rb +23 -0
- data/lib/wikiwiki/auth.rb +6 -0
- data/lib/wikiwiki/cli/commands/attachment/delete.rb +34 -0
- data/lib/wikiwiki/cli/commands/attachment/get.rb +45 -0
- data/lib/wikiwiki/cli/commands/attachment/list.rb +37 -0
- data/lib/wikiwiki/cli/commands/attachment/put.rb +55 -0
- data/lib/wikiwiki/cli/commands/attachment/show.rb +44 -0
- data/lib/wikiwiki/cli/commands/auth.rb +32 -0
- data/lib/wikiwiki/cli/commands/base.rb +64 -0
- data/lib/wikiwiki/cli/commands/page/delete.rb +32 -0
- data/lib/wikiwiki/cli/commands/page/get.rb +46 -0
- data/lib/wikiwiki/cli/commands/page/list.rb +36 -0
- data/lib/wikiwiki/cli/commands/page/put.rb +44 -0
- data/lib/wikiwiki/cli/commands/page/show.rb +44 -0
- data/lib/wikiwiki/cli/formatter/json.rb +18 -0
- data/lib/wikiwiki/cli.rb +49 -0
- data/lib/wikiwiki/rate_limiter.rb +4 -12
- data/lib/wikiwiki/sliding_window.rb +1 -3
- data/lib/wikiwiki/version.rb +1 -1
- data/lib/wikiwiki/wiki.rb +22 -3
- data/lib/wikiwiki.rb +5 -1
- data/sig/dry/cli.rbs +9 -0
- data/sig/wikiwiki/api.rbs +5 -3
- data/sig/wikiwiki/auth/token.rbs +9 -0
- data/sig/wikiwiki/auth.rbs +2 -0
- data/sig/wikiwiki/cli/commands/attachment.rbs +87 -0
- data/sig/wikiwiki/cli/commands/auth.rbs +9 -0
- data/sig/wikiwiki/cli/commands/base.rbs +27 -0
- data/sig/wikiwiki/cli/commands/page.rbs +82 -0
- data/sig/wikiwiki/cli/formatter/json.rbs +9 -0
- data/sig/wikiwiki/cli.rbs +11 -0
- data/sig/wikiwiki/wiki.rbs +5 -1
- data/sig/wikiwiki.rbs +3 -0
- metadata +60 -8
- data/LICENSE.txt +0 -21
- data/mise.toml +0 -6
- data/rbs_collection.yaml +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cd510fe9ee02e6521a325c51ed5e586a41a904ea0758fc78b16ad66159c40d9
|
|
4
|
+
data.tar.gz: e3e57832169db7d9644cdcbd2d8c3495576e8ff221338f107b99cb7fde161991
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3e67b565e5d9414a111dea276ea16f23bffe307c5c02b9de97062ce24a2e926304929bf975d5144cc359190680748cf224aae6f0819c96a21924b076152ca99
|
|
7
|
+
data.tar.gz: 3bfa6e34164386c86b19912170bb450da3dd50b2e610e8d722a3568d433d2877185fec64011cc7f68e283c4f1523286311e832b4c64ffa2102473b37083d2243
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2025-11-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Page deletion support
|
|
8
|
+
- `Wiki#delete_page` method to delete pages
|
|
9
|
+
- `wikiwiki page delete` command in CLI
|
|
10
|
+
- Validation in `Wiki#update_page` and `wikiwiki page put` to prevent accidental deletion with empty content
|
|
11
|
+
- `ConflictError` exception class for HTTP 409 Conflict responses
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `attachment put --force` behavior: attempts upload first, then deletes and retries on conflict (409 error)
|
|
16
|
+
- Previously: checked existence first, then deleted and uploaded
|
|
17
|
+
- Now: optimistic upload pattern eliminates Time-of-Check-Time-of-Use (TOCTOU) race conditions
|
|
18
|
+
|
|
19
|
+
## [0.6.0] - 2025-11-02
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Pre-obtained token reuse support
|
|
24
|
+
- `Auth.token(token:)` for authentication with pre-obtained JWT tokens
|
|
25
|
+
- `wikiwiki auth` command to obtain authentication tokens
|
|
26
|
+
- `Wiki#token` method to retrieve the current authentication token
|
|
27
|
+
- `--token` option and `WIKIWIKI_TOKEN` environment variable support for all commands
|
|
28
|
+
- JWT token expiration validation with AuthenticationError for expired tokens
|
|
29
|
+
- Command-line interface (`wikiwiki` command) for all API operations
|
|
30
|
+
- Page commands: `list`, `show`, `get`, `put`
|
|
31
|
+
- Attachment commands: `list`, `show`, `get`, `put`, `delete`
|
|
32
|
+
- Authentication command: `auth`
|
|
33
|
+
- Environment variable support for credentials (WIKIWIKI_WIKI_ID, WIKIWIKI_TOKEN, WIKIWIKI_PASSWORD, WIKIWIKI_API_KEY_ID, WIKIWIKI_SECRET)
|
|
34
|
+
- JSON output option (`--json`) for automation
|
|
35
|
+
- Verbose (`--verbose`) and debug (`--debug`) modes
|
|
36
|
+
- File overwrite protection with `--force` flag
|
|
37
|
+
- Attachment size limit validation (512 KiB) for uploads
|
|
38
|
+
|
|
3
39
|
## [0.5.0] - 2025-10-31
|
|
4
40
|
|
|
5
41
|
### Added
|
data/README.ja.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
{file:README.md English version}
|
|
4
4
|
|
|
5
|
-
[Wikiwiki](https://wikiwiki.jp/) REST API用のRuby
|
|
5
|
+
[Wikiwiki](https://wikiwiki.jp/) REST API用のRubyクライアントライブラリおよびコマンドラインツールです。
|
|
6
6
|
|
|
7
7
|
## 概要
|
|
8
8
|
|
|
9
|
-
このgemは、Wikiwikiのwiki
|
|
9
|
+
このgemは、Wikiwikiのwikiを操作するためのRubyライブラリとCLIツールの両方を提供します。
|
|
10
10
|
|
|
11
11
|
## インストール
|
|
12
12
|
|
|
@@ -44,9 +44,95 @@ auth = Wikiwiki::Auth.password(password: "your_admin_password")
|
|
|
44
44
|
auth = Wikiwiki::Auth.api_key(api_key_id: "your_api_key_id", secret: "your_secret")
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### トークン認証
|
|
48
|
+
|
|
49
|
+
事前に認証して得られたJWTトークンは、有効期限以内なら再利用できます:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
auth = Wikiwiki::Auth.token(token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
|
53
|
+
```
|
|
54
|
+
|
|
47
55
|
## 使い方
|
|
48
56
|
|
|
49
|
-
###
|
|
57
|
+
### コマンドラインインターフェース
|
|
58
|
+
|
|
59
|
+
`wikiwiki`コマンドで、すべてのAPI操作にアクセスできます:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 環境変数で認証情報を設定(オプション)
|
|
63
|
+
export WIKIWIKI_WIKI_ID=your-wiki-id
|
|
64
|
+
export WIKIWIKI_PASSWORD=your-password
|
|
65
|
+
# または
|
|
66
|
+
export WIKIWIKI_API_KEY_ID=your-api-key-id
|
|
67
|
+
export WIKIWIKI_SECRET=your-secret
|
|
68
|
+
# または事前に取得したトークンを使用
|
|
69
|
+
export WIKIWIKI_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
70
|
+
|
|
71
|
+
# 後で使用するためにトークンを取得
|
|
72
|
+
wikiwiki auth --wiki-id=your-wiki-id --password=your-password
|
|
73
|
+
# またはexportと組み合わせて使用
|
|
74
|
+
export WIKIWIKI_TOKEN=$(wikiwiki auth --wiki-id=your-wiki-id --password=your-password)
|
|
75
|
+
|
|
76
|
+
# ページ一覧
|
|
77
|
+
wikiwiki page list
|
|
78
|
+
|
|
79
|
+
# ページのメタデータ表示
|
|
80
|
+
wikiwiki page show FrontPage
|
|
81
|
+
|
|
82
|
+
# ページのコンテンツをダウンロード
|
|
83
|
+
wikiwiki page get FrontPage
|
|
84
|
+
wikiwiki page get FrontPage frontpage.txt
|
|
85
|
+
|
|
86
|
+
# ページをアップロード/更新(標準入力またはファイルから)
|
|
87
|
+
wikiwiki page put TestPage < content.txt
|
|
88
|
+
wikiwiki page put TestPage content.txt
|
|
89
|
+
|
|
90
|
+
# ページを削除
|
|
91
|
+
wikiwiki page delete TestPage
|
|
92
|
+
|
|
93
|
+
# 添付ファイル一覧
|
|
94
|
+
wikiwiki attachment list FrontPage
|
|
95
|
+
|
|
96
|
+
# 添付ファイルのメタデータ表示
|
|
97
|
+
wikiwiki attachment show FrontPage logo.png
|
|
98
|
+
|
|
99
|
+
# 添付ファイルをダウンロード
|
|
100
|
+
wikiwiki attachment get FrontPage logo.png
|
|
101
|
+
wikiwiki attachment get FrontPage logo.png --directory downloads/
|
|
102
|
+
|
|
103
|
+
# 添付ファイルをアップロード(最大512 KiB)
|
|
104
|
+
wikiwiki attachment put FrontPage image.png
|
|
105
|
+
wikiwiki attachment put FrontPage local.png --name remote.png
|
|
106
|
+
|
|
107
|
+
# 添付ファイルを削除
|
|
108
|
+
wikiwiki attachment delete FrontPage logo.png
|
|
109
|
+
|
|
110
|
+
# 既存のファイル/添付ファイルを上書きするには --force を使用
|
|
111
|
+
wikiwiki page get FrontPage existing.txt --force
|
|
112
|
+
wikiwiki attachment put FrontPage logo.png --force
|
|
113
|
+
|
|
114
|
+
# 注意: --force による添付ファイルの上書きはアトミックではありません。
|
|
115
|
+
# まずアップロードを試み、競合エラー(ファイルが既に存在)が発生した場合、
|
|
116
|
+
# 既存ファイルを削除してアップロードをリトライします。
|
|
117
|
+
# リトライに失敗した場合、添付ファイルは失われます。
|
|
118
|
+
|
|
119
|
+
# コマンドラインで認証情報を指定(環境変数より優先)
|
|
120
|
+
wikiwiki --wiki-id=your-wiki-id --password=your-password page list
|
|
121
|
+
wikiwiki --wiki-id=your-wiki-id --api-key-id=id --secret=secret page list
|
|
122
|
+
wikiwiki --wiki-id=your-wiki-id --token=eyJ... page list
|
|
123
|
+
|
|
124
|
+
# 自動化のためのJSON出力
|
|
125
|
+
wikiwiki page list --json
|
|
126
|
+
wikiwiki attachment show FrontPage logo.png --json
|
|
127
|
+
|
|
128
|
+
# 詳細モードとデバッグモード
|
|
129
|
+
wikiwiki page list --verbose
|
|
130
|
+
wikiwiki page list --debug
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Rubyライブラリ
|
|
134
|
+
|
|
135
|
+
ライブラリを使用する基本的な例:
|
|
50
136
|
|
|
51
137
|
```ruby
|
|
52
138
|
require "wikiwiki"
|
|
@@ -55,6 +141,14 @@ require "wikiwiki"
|
|
|
55
141
|
auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
56
142
|
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
57
143
|
|
|
144
|
+
# 認証トークンを取得して再利用
|
|
145
|
+
token = wiki.token
|
|
146
|
+
# トークンを保存して後で使用...
|
|
147
|
+
|
|
148
|
+
# 後で保存したトークンを使用
|
|
149
|
+
auth = Wikiwiki::Auth.token(token: token)
|
|
150
|
+
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
151
|
+
|
|
58
152
|
# すべてのページ名の一覧を取得
|
|
59
153
|
page_names = wiki.page_names
|
|
60
154
|
# => ["FrontPage", "SideBar", ...]
|
|
@@ -70,6 +164,9 @@ wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
|
70
164
|
# Hello World
|
|
71
165
|
SOURCE
|
|
72
166
|
|
|
167
|
+
# ページを削除
|
|
168
|
+
wiki.delete_page(page_name: "TestPage")
|
|
169
|
+
|
|
73
170
|
# 添付ファイル名の一覧を取得
|
|
74
171
|
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
75
172
|
|
data/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
{file:README.ja.md 日本語版}
|
|
4
4
|
|
|
5
|
-
A Ruby client library for the [Wikiwiki](https://wikiwiki.jp/) REST API.
|
|
5
|
+
A Ruby client library and command-line interface for the [Wikiwiki](https://wikiwiki.jp/) REST API.
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
This gem provides a
|
|
9
|
+
This gem provides both a Ruby library and CLI tool to interact with Wikiwiki wikis.
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -44,9 +44,95 @@ auth = Wikiwiki::Auth.password(password: "your_admin_password")
|
|
|
44
44
|
auth = Wikiwiki::Auth.api_key(api_key_id: "your_api_key_id", secret: "your_secret")
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Token Authentication
|
|
48
|
+
|
|
49
|
+
A JWT token obtained from a previous authentication can be reused within its validity period:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
auth = Wikiwiki::Auth.token(token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
|
53
|
+
```
|
|
54
|
+
|
|
47
55
|
## Usage
|
|
48
56
|
|
|
49
|
-
###
|
|
57
|
+
### Command Line Interface
|
|
58
|
+
|
|
59
|
+
The `wikiwiki` command provides access to all API operations:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Set credentials via environment variables (optional)
|
|
63
|
+
export WIKIWIKI_WIKI_ID=your-wiki-id
|
|
64
|
+
export WIKIWIKI_PASSWORD=your-password
|
|
65
|
+
# or
|
|
66
|
+
export WIKIWIKI_API_KEY_ID=your-api-key-id
|
|
67
|
+
export WIKIWIKI_SECRET=your-secret
|
|
68
|
+
# or use a pre-obtained token
|
|
69
|
+
export WIKIWIKI_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
70
|
+
|
|
71
|
+
# Obtain a token for later use
|
|
72
|
+
wikiwiki auth --wiki-id=your-wiki-id --password=your-password
|
|
73
|
+
# or combine with export
|
|
74
|
+
export WIKIWIKI_TOKEN=$(wikiwiki auth --wiki-id=your-wiki-id --password=your-password)
|
|
75
|
+
|
|
76
|
+
# List pages
|
|
77
|
+
wikiwiki page list
|
|
78
|
+
|
|
79
|
+
# Show page metadata
|
|
80
|
+
wikiwiki page show FrontPage
|
|
81
|
+
|
|
82
|
+
# Download page content
|
|
83
|
+
wikiwiki page get FrontPage
|
|
84
|
+
wikiwiki page get FrontPage frontpage.txt
|
|
85
|
+
|
|
86
|
+
# Upload/update page (from stdin or file)
|
|
87
|
+
wikiwiki page put TestPage < content.txt
|
|
88
|
+
wikiwiki page put TestPage content.txt
|
|
89
|
+
|
|
90
|
+
# Delete page
|
|
91
|
+
wikiwiki page delete TestPage
|
|
92
|
+
|
|
93
|
+
# List attachments
|
|
94
|
+
wikiwiki attachment list FrontPage
|
|
95
|
+
|
|
96
|
+
# Show attachment metadata
|
|
97
|
+
wikiwiki attachment show FrontPage logo.png
|
|
98
|
+
|
|
99
|
+
# Download attachment
|
|
100
|
+
wikiwiki attachment get FrontPage logo.png
|
|
101
|
+
wikiwiki attachment get FrontPage logo.png --directory downloads/
|
|
102
|
+
|
|
103
|
+
# Upload attachment (max 512 KiB)
|
|
104
|
+
wikiwiki attachment put FrontPage image.png
|
|
105
|
+
wikiwiki attachment put FrontPage local.png --name remote.png
|
|
106
|
+
|
|
107
|
+
# Delete attachment
|
|
108
|
+
wikiwiki attachment delete FrontPage logo.png
|
|
109
|
+
|
|
110
|
+
# Use --force to overwrite existing files/attachments
|
|
111
|
+
wikiwiki page get FrontPage existing.txt --force
|
|
112
|
+
wikiwiki attachment put FrontPage logo.png --force
|
|
113
|
+
|
|
114
|
+
# Note: Attachment overwrite with --force is not atomic.
|
|
115
|
+
# First attempts to upload; if it fails due to conflict (file exists),
|
|
116
|
+
# deletes the existing file and retries the upload.
|
|
117
|
+
# If retry fails, the attachment will be lost.
|
|
118
|
+
|
|
119
|
+
# Authentication via command line (overrides environment variables)
|
|
120
|
+
wikiwiki --wiki-id=your-wiki-id --password=your-password page list
|
|
121
|
+
wikiwiki --wiki-id=your-wiki-id --api-key-id=id --secret=secret page list
|
|
122
|
+
wikiwiki --wiki-id=your-wiki-id --token=eyJ... page list
|
|
123
|
+
|
|
124
|
+
# JSON output for automation
|
|
125
|
+
wikiwiki page list --json
|
|
126
|
+
wikiwiki attachment show FrontPage logo.png --json
|
|
127
|
+
|
|
128
|
+
# Verbose and debug modes
|
|
129
|
+
wikiwiki page list --verbose
|
|
130
|
+
wikiwiki page list --debug
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Ruby Library
|
|
134
|
+
|
|
135
|
+
Basic example using the library:
|
|
50
136
|
|
|
51
137
|
```ruby
|
|
52
138
|
require "wikiwiki"
|
|
@@ -55,6 +141,14 @@ require "wikiwiki"
|
|
|
55
141
|
auth = Wikiwiki::Auth.password(password: "admin_password")
|
|
56
142
|
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
57
143
|
|
|
144
|
+
# Obtain and reuse authentication token
|
|
145
|
+
token = wiki.token
|
|
146
|
+
# Save token for later use...
|
|
147
|
+
|
|
148
|
+
# Later, use the saved token
|
|
149
|
+
auth = Wikiwiki::Auth.token(token: token)
|
|
150
|
+
wiki = Wikiwiki::Wiki.new(wiki_id: "your-wiki-id", auth:)
|
|
151
|
+
|
|
58
152
|
# List all page names
|
|
59
153
|
page_names = wiki.page_names
|
|
60
154
|
# => ["FrontPage", "SideBar", ...]
|
|
@@ -70,6 +164,9 @@ wiki.update_page(page_name: "TestPage", source: <<~SOURCE)
|
|
|
70
164
|
# Hello World
|
|
71
165
|
SOURCE
|
|
72
166
|
|
|
167
|
+
# Delete a page
|
|
168
|
+
wiki.delete_page(page_name: "TestPage")
|
|
169
|
+
|
|
73
170
|
# List attachment names
|
|
74
171
|
attachment_names = wiki.attachment_names(page_name: "FrontPage")
|
|
75
172
|
|
data/exe/wikiwiki
ADDED
data/lib/wikiwiki/api.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "jwt"
|
|
4
5
|
require "net/http"
|
|
5
6
|
require "uri"
|
|
6
7
|
|
|
@@ -13,6 +14,7 @@ module Wikiwiki
|
|
|
13
14
|
# pages = api.get_pages
|
|
14
15
|
class API
|
|
15
16
|
attr_reader :logger
|
|
17
|
+
attr_reader :token
|
|
16
18
|
|
|
17
19
|
BASE_URL = URI.parse("https://api.wikiwiki.jp").freeze
|
|
18
20
|
private_constant :BASE_URL
|
|
@@ -60,6 +62,7 @@ module Wikiwiki
|
|
|
60
62
|
# @param source [String] the page source content
|
|
61
63
|
# @return [void]
|
|
62
64
|
# @raise [Error] if request fails
|
|
65
|
+
# @note Passing an empty string as source will delete the page
|
|
63
66
|
def put_page(encoded_page_name:, source:)
|
|
64
67
|
uri = BASE_URL + "/#{wiki_id}/page/#{encoded_page_name}"
|
|
65
68
|
response = request(:put, uri, body: {"source" => source})
|
|
@@ -123,14 +126,44 @@ module Wikiwiki
|
|
|
123
126
|
|
|
124
127
|
# Authenticate with the Wikiwiki API
|
|
125
128
|
#
|
|
126
|
-
# @param auth [Auth::Password, Auth::ApiKey] authentication credentials
|
|
129
|
+
# @param auth [Auth::Password, Auth::ApiKey, Auth::Token] authentication credentials
|
|
127
130
|
# @return [String] JWT token
|
|
128
131
|
# @raise [Error] if authentication fails
|
|
129
132
|
private def authenticate(auth)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
token = if auth.is_a?(Auth::Token)
|
|
134
|
+
auth.token
|
|
135
|
+
else
|
|
136
|
+
uri = BASE_URL + "/#{wiki_id}/auth"
|
|
137
|
+
response = request(:post, uri, body: auth.to_h, authenticate: false)
|
|
138
|
+
data = parse_json_response(response)
|
|
139
|
+
data["token"]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
validate_token_expiry(token) if auth.is_a?(Auth::Token)
|
|
143
|
+
token
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Validate JWT token expiry
|
|
147
|
+
#
|
|
148
|
+
# @param token [String] JWT token
|
|
149
|
+
# @return [void]
|
|
150
|
+
# @raise [AuthenticationError] if token has expired
|
|
151
|
+
private def validate_token_expiry(token)
|
|
152
|
+
payload, = JWT.decode(token, nil, false)
|
|
153
|
+
exp = payload["exp"]
|
|
154
|
+
|
|
155
|
+
if exp
|
|
156
|
+
exp_time = Time.at(exp)
|
|
157
|
+
if Time.now >= exp_time
|
|
158
|
+
raise AuthenticationError, "Token has expired at #{exp_time.iso8601}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
logger.debug("[#{wiki_id}] Token expires at: #{exp_time.iso8601}")
|
|
162
|
+
else
|
|
163
|
+
logger.debug("[#{wiki_id}] Token has no expiration")
|
|
164
|
+
end
|
|
165
|
+
rescue JWT::DecodeError => e
|
|
166
|
+
logger.debug("[#{wiki_id}] Failed to decode token: #{e.message}")
|
|
134
167
|
end
|
|
135
168
|
|
|
136
169
|
# Parse JSON response
|
|
@@ -139,6 +172,7 @@ module Wikiwiki
|
|
|
139
172
|
# @return [Hash] parsed response body
|
|
140
173
|
# @raise [AuthenticationError] if authentication fails (401)
|
|
141
174
|
# @raise [ResourceNotFoundError] if resource not found (404)
|
|
175
|
+
# @raise [ConflictError] if conflict occurs (409)
|
|
142
176
|
# @raise [ServerError] if server error (5xx)
|
|
143
177
|
# @raise [APIError] if other API request fails
|
|
144
178
|
private def parse_json_response(response)
|
|
@@ -149,6 +183,8 @@ module Wikiwiki
|
|
|
149
183
|
raise AuthenticationError, message
|
|
150
184
|
when 404
|
|
151
185
|
raise ResourceNotFoundError, message
|
|
186
|
+
when 409
|
|
187
|
+
raise ConflictError, message
|
|
152
188
|
when 500..599
|
|
153
189
|
raise ServerError, message
|
|
154
190
|
else
|
|
@@ -217,6 +253,6 @@ module Wikiwiki
|
|
|
217
253
|
end
|
|
218
254
|
end
|
|
219
255
|
|
|
220
|
-
private attr_reader :wiki_id
|
|
256
|
+
private attr_reader :wiki_id
|
|
221
257
|
end
|
|
222
258
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
module Auth
|
|
5
|
+
# Token-based authentication using a pre-obtained JWT token
|
|
6
|
+
#
|
|
7
|
+
# This allows using a previously obtained authentication token without
|
|
8
|
+
# re-authenticating with password or API key credentials.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
12
|
+
# auth = Wikiwiki::Auth.token(token: token)
|
|
13
|
+
# wiki = Wikiwiki::Wiki.new(wiki_id: "my-wiki", auth: auth)
|
|
14
|
+
Token = Data.define(:token)
|
|
15
|
+
|
|
16
|
+
class Token
|
|
17
|
+
# Reopen the class to add YARD documentation for attributes
|
|
18
|
+
|
|
19
|
+
# @!attribute [r] token
|
|
20
|
+
# @return [String] the JWT token
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/wikiwiki/auth.rb
CHANGED
|
@@ -17,5 +17,11 @@ module Wikiwiki
|
|
|
17
17
|
# @param secret [String] secret key
|
|
18
18
|
# @return [ApiKey] API key authentication object
|
|
19
19
|
def self.api_key(api_key_id:, secret:) = ApiKey.new(api_key_id:, secret:)
|
|
20
|
+
|
|
21
|
+
# Create token-based authentication credentials
|
|
22
|
+
#
|
|
23
|
+
# @param token [String] JWT authentication token
|
|
24
|
+
# @return [Token] token authentication object
|
|
25
|
+
def self.token(token:) = Token.new(token:)
|
|
20
26
|
end
|
|
21
27
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Attachment
|
|
7
|
+
# Delete attachment from a page
|
|
8
|
+
class Delete < Base
|
|
9
|
+
desc "Delete attachment from a page"
|
|
10
|
+
|
|
11
|
+
argument :page_name, required: true, desc: "Page name"
|
|
12
|
+
argument :file_name, required: true, desc: "Attachment file name"
|
|
13
|
+
|
|
14
|
+
# Execute the delete command
|
|
15
|
+
#
|
|
16
|
+
# @param page_name [String] name of the page
|
|
17
|
+
# @param file_name [String] name of the attachment file to delete
|
|
18
|
+
# @param out [IO] output stream
|
|
19
|
+
# @param err [IO] error stream
|
|
20
|
+
# @return [void]
|
|
21
|
+
def call(page_name:, file_name:, out: $stdout, err: $stderr, **)
|
|
22
|
+
wiki = create_wiki(out:, err:, **)
|
|
23
|
+
|
|
24
|
+
wiki.delete_attachment(page_name:, attachment_name: file_name)
|
|
25
|
+
|
|
26
|
+
say("Attachment '#{file_name}' deleted from page '#{page_name}'", out:, **)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
register "attachment delete", Attachment::Delete
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Attachment
|
|
7
|
+
# Download attachment from a page
|
|
8
|
+
class Get < Base
|
|
9
|
+
desc "Download attachment from a page"
|
|
10
|
+
|
|
11
|
+
argument :page_name, required: true, desc: "Page name"
|
|
12
|
+
argument :file_name, required: true, desc: "Attachment file name"
|
|
13
|
+
option :directory, aliases: ["-d"], desc: "Download directory (current directory if omitted)"
|
|
14
|
+
option :force, aliases: ["-f"], type: :boolean, default: false, desc: "Overwrite existing file"
|
|
15
|
+
|
|
16
|
+
# Execute the get command
|
|
17
|
+
#
|
|
18
|
+
# @param page_name [String] name of the page
|
|
19
|
+
# @param file_name [String] name of the attachment file
|
|
20
|
+
# @param directory [String, nil] optional download directory
|
|
21
|
+
# @param force [Boolean] whether to overwrite existing file
|
|
22
|
+
# @param out [IO] output stream
|
|
23
|
+
# @param err [IO] error stream
|
|
24
|
+
# @return [void]
|
|
25
|
+
def call(page_name:, file_name:, directory: nil, force: false, out: $stdout, err: $stderr, **)
|
|
26
|
+
wiki = create_wiki(out:, err:, **)
|
|
27
|
+
|
|
28
|
+
output_path = directory ? File.join(directory, file_name) : file_name
|
|
29
|
+
|
|
30
|
+
if File.exist?(output_path) && !force
|
|
31
|
+
raise ArgumentError, "File '#{output_path}' already exists. Use --force to overwrite."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attachment = wiki.attachment(page_name:, attachment_name: file_name)
|
|
35
|
+
File.binwrite(output_path, attachment.content)
|
|
36
|
+
|
|
37
|
+
say("Attachment '#{file_name}' (#{attachment.content.bytesize} bytes) saved to #{output_path}", out:, **)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
register "attachment get", Attachment::Get
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
# Attachment-related commands
|
|
7
|
+
module Attachment
|
|
8
|
+
# List attachments on a page
|
|
9
|
+
class List < Base
|
|
10
|
+
desc "List attachments on a page"
|
|
11
|
+
|
|
12
|
+
argument :page_name, required: true, desc: "Page name"
|
|
13
|
+
option :json, aliases: ["-j"], type: :boolean, default: false, desc: "Output as JSON"
|
|
14
|
+
|
|
15
|
+
# Execute the list command
|
|
16
|
+
#
|
|
17
|
+
# @param page_name [String] name of the page
|
|
18
|
+
# @param options [Hash] command options including wiki_id, auth, json, verbose, out, err
|
|
19
|
+
# @return [void]
|
|
20
|
+
def call(page_name:, out: $stdout, err: $stderr, **options)
|
|
21
|
+
wiki = create_wiki(out:, err:, **options)
|
|
22
|
+
attachment_names = wiki.attachment_names(page_name:)
|
|
23
|
+
|
|
24
|
+
if options[:json]
|
|
25
|
+
out.puts Formatter::JSON.new.format(attachment_names)
|
|
26
|
+
else
|
|
27
|
+
attachment_names.each {|name| out.puts name }
|
|
28
|
+
say("#{attachment_names.size} attachments found", out:, **options)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
register "attachment list", Attachment::List
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Attachment
|
|
7
|
+
# Upload attachment to a page
|
|
8
|
+
class Put < Base
|
|
9
|
+
desc "Upload attachment to a page"
|
|
10
|
+
|
|
11
|
+
MAX_ATTACHMENT_SIZE = 512 * 1024 # 512 KiB
|
|
12
|
+
private_constant :MAX_ATTACHMENT_SIZE
|
|
13
|
+
|
|
14
|
+
argument :page_name, required: true, desc: "Page name"
|
|
15
|
+
argument :file_path, required: true, desc: "Local file path"
|
|
16
|
+
option :name, aliases: ["-n"], desc: "Attachment name (inferred from file path if omitted)"
|
|
17
|
+
option :force, aliases: ["-f"], type: :boolean, default: false, desc: "Overwrite existing attachment (non-atomic: deletes then uploads)"
|
|
18
|
+
|
|
19
|
+
# Execute the put command
|
|
20
|
+
#
|
|
21
|
+
# @param page_name [String] name of the page
|
|
22
|
+
# @param file_path [String] local file path to upload
|
|
23
|
+
# @param name [String, nil] optional attachment name (inferred from file_path if nil)
|
|
24
|
+
# @param force [Boolean] whether to overwrite existing attachment
|
|
25
|
+
# @param out [IO] output stream
|
|
26
|
+
# @param err [IO] error stream
|
|
27
|
+
# @return [void]
|
|
28
|
+
def call(page_name:, file_path:, name: nil, force: false, out: $stdout, err: $stderr, **)
|
|
29
|
+
wiki = create_wiki(out:, err:, **)
|
|
30
|
+
|
|
31
|
+
attachment_name = name || File.basename(file_path)
|
|
32
|
+
content = File.binread(file_path)
|
|
33
|
+
|
|
34
|
+
if content.bytesize > MAX_ATTACHMENT_SIZE
|
|
35
|
+
raise ArgumentError, "File size (#{content.bytesize} bytes) exceeds maximum allowed size (#{MAX_ATTACHMENT_SIZE} bytes / 512 KiB)"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
wiki.add_attachment(page_name:, attachment_name:, content:)
|
|
40
|
+
rescue ConflictError
|
|
41
|
+
raise ArgumentError, "Attachment '#{attachment_name}' already exists. Use --force to overwrite." unless force
|
|
42
|
+
|
|
43
|
+
wiki.delete_attachment(page_name:, attachment_name:)
|
|
44
|
+
retry
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
say("Attachment '#{attachment_name}' uploaded to page '#{page_name}'", out:, **)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
register "attachment put", Attachment::Put
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wikiwiki
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module Attachment
|
|
7
|
+
# Show attachment metadata
|
|
8
|
+
class Show < Base
|
|
9
|
+
desc "Show attachment metadata"
|
|
10
|
+
|
|
11
|
+
argument :page_name, required: true, desc: "Page name"
|
|
12
|
+
argument :file_name, required: true, desc: "Attachment file name"
|
|
13
|
+
option :json, aliases: ["-j"], type: :boolean, default: false, desc: "Output as JSON"
|
|
14
|
+
|
|
15
|
+
# Execute the show command
|
|
16
|
+
#
|
|
17
|
+
# @param page_name [String] name of the page
|
|
18
|
+
# @param file_name [String] name of the attachment file
|
|
19
|
+
# @param options [Hash] command options including wiki_id, auth, json, verbose, out, err
|
|
20
|
+
# @return [void]
|
|
21
|
+
def call(page_name:, file_name:, out: $stdout, err: $stderr, **options)
|
|
22
|
+
wiki = create_wiki(out:, err:, **options)
|
|
23
|
+
attachment = wiki.attachment(page_name:, attachment_name: file_name)
|
|
24
|
+
|
|
25
|
+
metadata = attachment.to_h.except(:content).transform_values {|v| v.is_a?(Time) ? v.iso8601 : v }
|
|
26
|
+
|
|
27
|
+
if options[:json]
|
|
28
|
+
out.puts Formatter::JSON.new.format(metadata)
|
|
29
|
+
else
|
|
30
|
+
out.puts "Page: #{metadata[:page_name]}"
|
|
31
|
+
out.puts "Name: #{metadata[:name]}"
|
|
32
|
+
out.puts "Size: #{metadata[:size]} bytes"
|
|
33
|
+
out.puts "Time: #{metadata[:time]}"
|
|
34
|
+
out.puts "Type: #{metadata[:type]}"
|
|
35
|
+
say("Attachment metadata retrieved", out:, **options)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
register "attachment show", Attachment::Show
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|