Skip to content

Commit a0c139f

Browse files
authored
Prototype (#1)
1 parent 28a7270 commit a0c139f

10 files changed

+272
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
config.yml
2+
users.yml
3+
tmp

Gemfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
source "https://rubygems.org"
3+
4+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
5+
6+
gem 'docbase'
7+
gem 'esa'
8+
gem 'pry'

Gemfile.lock

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
coderay (1.1.2)
5+
docbase (0.1.2)
6+
faraday (~> 0.9.2)
7+
faraday_middleware (~> 0.10.0)
8+
esa (1.13.0)
9+
faraday (~> 0.9)
10+
faraday_middleware
11+
mime-types (~> 2.6)
12+
multi_xml (~> 0.5.5)
13+
faraday (0.9.2)
14+
multipart-post (>= 1.2, < 3)
15+
faraday_middleware (0.10.1)
16+
faraday (>= 0.7.4, < 1.0)
17+
method_source (0.9.0)
18+
mime-types (2.99.3)
19+
multi_xml (0.5.5)
20+
multipart-post (2.0.0)
21+
pry (0.11.2)
22+
coderay (~> 1.1.0)
23+
method_source (~> 0.9.0)
24+
25+
PLATFORMS
26+
ruby
27+
28+
DEPENDENCIES
29+
docbase
30+
esa
31+
pry
32+
33+
BUNDLED WITH
34+
1.15.4

README.md

+45
Original file line numberDiff line numberDiff line change
@@ -1 +1,46 @@
11
# d2e
2+
3+
DocBaseからesa.ioへのインポートスクリプトのサンプルです
4+
5+
# スクリプトを使った移行の流れ
6+
7+
## メンバーの紐付けの準備
8+
9+
- Docbase上で、memmber全員の「名前」を「ユーザーID (半角小英数) 」と同一に揃えてください
10+
- docbase上のユーザーをesaに招待し、「ScreenName」をdocbase上の「ユーザーID (半角小英数) 」と同じにして下さい
11+
12+
これらは、メンバーの紐付けのために必要です。この作業を行わなくても移行は行なえますが、システムが投稿した記事やコメントの扱いになります。
13+
14+
## スクリプトの用意
15+
16+
```
17+
$ git clone https://github.com/fukayatsu/d2e.git
18+
$ bundle install
19+
$ cp config.sample.yml config.yml
20+
```
21+
22+
次に、
23+
24+
- JSON形式でダウンロードした3つのzipを全てzipディレクトリに配置します
25+
- esaでは同じチーム内での記事の閲覧権限は全員同じなので、全員が閲覧できてまずいドキュメントはこの段階で削除して下さい
26+
- `config.yml` にAPIトークンや設定を記述します
27+
- `users.yml` にdocbase上のユーザー名とesa上のユーザー名の紐付けを記述します
28+
- 必要に応じてimporter.rbの内容を変更します。
29+
30+
## スクリプトの動作確認(Dry Run)
31+
32+
```
33+
$ bundle exec ruby app.rb
34+
```
35+
36+
ここでは、まだ実際には移行が行われません。
37+
移行された場合にどうなるか出力されます
38+
39+
## インポート実行
40+
41+
Dry Runの出力が問題なければ、実際にインポートを実行します。
42+
※ スクリプトの実行前に、esa運営チームにご連絡頂ければAPI制限の一時引き上げなどが可能です。
43+
44+
```
45+
$ bundle exec ruby app.rb --run
46+
```

app.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'optparse'
2+
require 'yaml'
3+
require_relative 'lib/importer'
4+
5+
config = YAML.load_file('config.yml')
6+
importer = Importer.new(config)
7+
8+
params = ARGV.getopts('', 'run', 'start:0')
9+
importer.import(dry_run: !params['run'], start_index: params['start'].to_i)

config.sample.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 移行先のesa.ioのチーム名
2+
esa_team_name: foo-test
3+
4+
# /user/tokens/new から read/write の両方が有効なtokenを作成
5+
esa_access_token: xxx
6+
7+
# docbaseのデータをJSON形式でダウンロードし、展開したフォルダのpath
8+
file_dir: tmp/corp2_file001_xxx
9+
image_dir: tmp/corp2_image001_xxx
10+
json_dir: tmp/corp2_json001_xxx

lib/base_converter.rb

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
class BaseConverter
2+
def initialize(dry_run:, client:, config:, screen_names: [])
3+
@dry_run = dry_run
4+
@client = client
5+
@screen_names = screen_names
6+
@config = config
7+
8+
@files = Dir.glob File.expand_path(File.join(config['file_dir'], '*'))
9+
@images = Dir.glob File.expand_path(File.join(config['image_dir'], '*'))
10+
end
11+
attr_reader :dry_run, :client, :screen_names, :config, :files, :images
12+
13+
def convert(content)
14+
{
15+
post: post_params(content),
16+
comments: content['comments'].map { |c| comments_params(c) }
17+
}
18+
end
19+
20+
private
21+
22+
def post_params(content)
23+
raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
24+
end
25+
26+
def comment_params(comment_content)
27+
raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
28+
end
29+
30+
def screen_name_for(name)
31+
return name if screen_names.include?(name)
32+
'esa_bot'
33+
end
34+
35+
def reupload(text)
36+
return text unless text
37+
38+
text.gsub(%r{https://docbase.io/file_attachments/[a-z0-9\-\.]+}) do |match|
39+
basename = File.basename(match)
40+
local_file = files.find { |file| File.basename(file).end_with?(basename) }
41+
new_url_for_attachment(match, local_file, dry_run)
42+
end.gsub(%r{https://image.docbase.io/uploads/[a-z0-9\-\.]+}) do |match|
43+
basename = File.basename(match)
44+
local_file = images.find { |file| File.basename(file).end_with?(basename) }
45+
new_url_for_attachment(match, local_file, dry_run)
46+
end.gsub('](/images/file_icons/', '](https://docbase.io/images/file_icons/')
47+
end
48+
49+
def new_url_for_attachment(match, local_file, dry_run)
50+
return match if !local_file
51+
'**will be re-uploaded to esa.io**' if dry_run
52+
53+
# 再アップロード
54+
resp = client.upload_attachment(local_file)
55+
return local_file unless resp.status == 200
56+
57+
resp.body['attachment']['url']
58+
end
59+
end

lib/converter.rb

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require_relative './base_converter'
2+
3+
class Converter < BaseConverter
4+
private
5+
6+
def post_params(content)
7+
group_category = content['groups'].map{ |g| g['name'] }.join('-')
8+
category = "DocBase"
9+
category += "/#{group_category}" if group_category.length > 0
10+
11+
body_md = <<~EOT
12+
url: #{content['url']}
13+
created_at: #{content['created_at']}
14+
user: #{content['user']['name']}
15+
16+
17+
#{reupload content['body']}
18+
EOT
19+
20+
{
21+
name: content['title'],
22+
category: category,
23+
tags: content['tags'].map{ |tag| tag['name'] },
24+
body_md: body_md,
25+
wip: content['draft'],
26+
message: '[skip notice] Imported from DocBase',
27+
user: screen_name_for([content['user']['name']])
28+
}
29+
end
30+
31+
def comments_params(comment_content)
32+
body_md = <<~EOT
33+
created_at: #{comment_content['created_at']}
34+
user_id: #{comment_content['user_id']}
35+
36+
37+
#{reupload comment_content['encrypted_comment']}
38+
EOT
39+
40+
{
41+
body_md: body_md,
42+
# user: screen_name_for([comment)content['user']['name']])
43+
}
44+
end
45+
end

lib/importer.rb

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require 'pp'
2+
require 'json'
3+
require 'esa'
4+
require_relative './converter'
5+
6+
class Importer
7+
def initialize(config)
8+
@config = config
9+
@json_files = Dir.glob File.expand_path(File.join(config['json_dir'], '*.json'))
10+
end
11+
attr_reader :config, :json_files
12+
13+
def import(dry_run: true, start_index: 0)
14+
converter = Converter.new(dry_run: dry_run, client: client, config: config, screen_names: screen_names)
15+
16+
json_files.sort_by { |file| File.basename(file, '.*').to_i }.each.with_index do |file, index|
17+
next unless index >= start_index
18+
19+
content = JSON.parse(File.read(file))
20+
params = converter.convert(content)
21+
22+
if dry_run
23+
puts "***** index: #{index} *****"
24+
pp params
25+
puts
26+
next
27+
end
28+
29+
print "[#{Time.now}] index[#{index}] #{params['name']} => "
30+
response = client.create_post(params[:post])
31+
case response.status
32+
when 201
33+
puts "created: #{response.body["full_name"]}"
34+
else
35+
puts "failure with status: #{response.status}"
36+
exit 1
37+
end
38+
39+
post_number = response.body['number']
40+
params[:comments].each do |comment_param|
41+
client.create_comment(post_number, comment_param)
42+
end
43+
end
44+
end
45+
46+
private
47+
48+
def client
49+
@client ||= Esa::Client.new(
50+
access_token: config['esa_access_token'],
51+
current_team: config['esa_team_name'],
52+
api_endpoint: config['esa_api_endpoint']
53+
)
54+
end
55+
56+
def screen_names
57+
@screen_names ||= client.members(per_page: 100).body['members'].map{ |m| m['screen_name'] }
58+
end
59+
end

tmp/.keep

Whitespace-only changes.

0 commit comments

Comments
 (0)