mirror of
https://github.com/captn3m0/outliner.git
synced 2024-09-28 22:23:03 +00:00
Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@ -1,3 +0,0 @@
|
|||||||
ko_fi: captn3m0
|
|
||||||
liberapay: captn3m0
|
|
||||||
github: captn3m0
|
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -7,19 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## 1.0.2 - 2024-03-07
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Dependency updates
|
|
||||||
- Fixes for new Outline API
|
|
||||||
|
|
||||||
## 1.0.1 - 2020-04-26
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Dependency Updates
|
|
||||||
|
|
||||||
## 1.0.0 - 2020-04-26
|
## 1.0.0 - 2020-04-26
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
16
Dockerfile
16
Dockerfile
@ -1,18 +1,12 @@
|
|||||||
FROM ruby:3.3-alpine3.19
|
FROM ruby:2.7-alpine
|
||||||
|
|
||||||
RUN apk add --no-cache git openssh-client rsync && \
|
|
||||||
echo -e "StrictHostKeyChecking no" >> /etc/ssh/ssh_config && \
|
|
||||||
mkdir /root/.ssh
|
|
||||||
|
|
||||||
WORKDIR /outliner
|
WORKDIR /outliner
|
||||||
COPY . /outliner/
|
COPY . /outliner/
|
||||||
|
|
||||||
RUN echo "gem: --no-ri --no-rdoc" > ~/.gemrc && \
|
RUN gem install bundler && \
|
||||||
apk add --no-cache alpine-sdk && \
|
|
||||||
gem update --system && \
|
|
||||||
gem install bundler && \
|
|
||||||
bundle install && \
|
bundle install && \
|
||||||
apk del --no-cache alpine-sdk && \
|
apk add --no-cache git openssh-client rsync && \
|
||||||
rm ~/.gemrc
|
echo -e "StrictHostKeyChecking no" >> /etc/ssh/ssh_config && \
|
||||||
|
mkdir /root/.ssh
|
||||||
|
|
||||||
ENTRYPOINT ["/outliner/entrypoint.sh"]
|
ENTRYPOINT ["/outliner/entrypoint.sh"]
|
||||||
|
@ -28,13 +28,6 @@ require 'outliner'
|
|||||||
client = Outliner.new('https://knowledge.example.com')
|
client = Outliner.new('https://knowledge.example.com')
|
||||||
pp client.auth_info
|
pp client.auth_info
|
||||||
pp client.collections_list(offset: 0, limit: 10)
|
pp client.collections_list(offset: 0, limit: 10)
|
||||||
# This works around a 302 redirect bug in httparty
|
|
||||||
begin
|
|
||||||
r = @client.fileOperations__redirect({id: FILE_OPERATION_ID}, format: nil, no_follow: true)
|
|
||||||
rescue HTTParty::RedirectionTooDeep => e
|
|
||||||
# Download this using response = HTTParty.get e.response.header['location'] if needed
|
|
||||||
pp e.response.header['location']
|
|
||||||
end
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Import
|
### Import
|
||||||
|
@ -20,30 +20,13 @@ local_directory = ARGV[0]
|
|||||||
CLIENT = Outliner::Client.new ENV['OUTLINE_BASE_URI']
|
CLIENT = Outliner::Client.new ENV['OUTLINE_BASE_URI']
|
||||||
|
|
||||||
# Download the complete zip
|
# Download the complete zip
|
||||||
response = CLIENT.collections__export_all(format: "outline-markdown")
|
response = CLIENT.collections_exportAll(download: true)
|
||||||
|
|
||||||
raise 'Failed to trigger export_all action' if not response['success']
|
# Extract it to a tempfle
|
||||||
|
file = Tempfile.new('download.zip')
|
||||||
file_operation_id = response['data']['fileOperation']['id']
|
File.open(file.path, 'w') { |f| f.write(response.body) }
|
||||||
fop_info_response = nil
|
|
||||||
i = 0
|
|
||||||
loop do
|
|
||||||
i += 1
|
|
||||||
raise 'Timed out waiting for the file export operation to complete' if i > 20
|
|
||||||
sleep(2*i)
|
|
||||||
fop_info_response = CLIENT.fileOperations__info(id: file_operation_id)
|
|
||||||
raise 'Failed to query export file operation info' if not fop_info_response['ok']
|
|
||||||
break if fop_info_response['data']['state'] == 'complete'
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
fop_redirect_response = CLIENT.fileOperations__redirect({id: file_operation_id}, {no_follow: true})
|
|
||||||
rescue HTTParty::RedirectionTooDeep => e
|
|
||||||
response = HTTParty.get e.response.header['location']
|
|
||||||
file = Tempfile.new('download.zip')
|
|
||||||
File.open(file.path, 'w') { |f| f.write(response.body) }
|
|
||||||
`unzip -o "#{file.path}" -d "#{local_directory}"`
|
|
||||||
file.unlink
|
|
||||||
end
|
|
||||||
|
|
||||||
|
`unzip -o "#{file.path}" -d "#{local_directory}"`
|
||||||
|
|
||||||
|
# Delete tempfile
|
||||||
|
file.unlink
|
||||||
|
@ -28,7 +28,7 @@ def create_documents_recursively(directory, collection_id, parent_document_id =
|
|||||||
}
|
}
|
||||||
|
|
||||||
params[:parentDocumentId] = parent_document_id if parent_document_id
|
params[:parentDocumentId] = parent_document_id if parent_document_id
|
||||||
CLIENT.documents__create(params)
|
CLIENT.documents_create(params)
|
||||||
puts "[-] #{file}"
|
puts "[-] #{file}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ def create_documents_recursively(directory, collection_id, parent_document_id =
|
|||||||
publish: true,
|
publish: true,
|
||||||
parentDocumentId: parent_document_id
|
parentDocumentId: parent_document_id
|
||||||
}
|
}
|
||||||
response = CLIENT.documents__create(params)
|
response = CLIENT.documents_create(params)
|
||||||
create_documents_recursively(dir, collection_id, response['data']['id'])
|
create_documents_recursively(dir, collection_id, response['data']['id'])
|
||||||
end
|
end
|
||||||
Dir.chdir cwd
|
Dir.chdir cwd
|
||||||
@ -65,7 +65,7 @@ begin
|
|||||||
rescue StandardError? => e
|
rescue StandardError? => e
|
||||||
# If we fail, print an error, and delete the collection
|
# If we fail, print an error, and delete the collection
|
||||||
puts "[E] Import failed with error: #{e.message}"
|
puts "[E] Import failed with error: #{e.message}"
|
||||||
CLIENT.collections__delete(id: root_collection_id)
|
CLIENT.collections_delete(id: root_collection_id)
|
||||||
puts '[E] Deleted collection, please report the issue or retry'
|
puts '[E] Deleted collection, please report the issue or retry'
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
@ -11,28 +11,27 @@ module Outliner
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_collection(name)
|
def find_or_create_collection(name)
|
||||||
collections = self.collections__list(limit: 100)['data']
|
collections = self.collections_list(limit: 100)['data']
|
||||||
collections.filter!{|c|c['name'] == name}
|
collections.filter!{|c|c['name'] == name}
|
||||||
if collections.size >= 1
|
if collections.size >= 1
|
||||||
collections[0]['id']
|
collections[0]['id']
|
||||||
else
|
else
|
||||||
self.collections__create(name: name, description: 'Imported Collection')['data']['id']
|
self.collections_create(name: name, description: 'Imported Collection')['data']['id']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(method_name, params = {}, options = {})
|
def method_missing(method_name, params = {})
|
||||||
method_name = "/#{method_name.to_s.sub('__', '.')}"
|
method_name = '/' + method_name.to_s.sub('_', '.')
|
||||||
|
body = {token: @token}.merge(params).to_json
|
||||||
options = {
|
options = {
|
||||||
body: params.to_json,
|
body: body,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept' => 'application/json',
|
'Accept'=>'application/json',
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'User-Agent' => "Outliner/#{Outliner::VERSION}",
|
'User-Agent': "Outliner/#{Outliner::VERSION}"
|
||||||
'Authorization' => "Bearer #{@token}"
|
|
||||||
},
|
},
|
||||||
format: :json,
|
format: :json
|
||||||
}.merge!(options)
|
}
|
||||||
|
|
||||||
self.class.post(method_name, options)
|
self.class.post(method_name, options)
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
module Outliner
|
module Outliner
|
||||||
VERSION = "1.0.2"
|
VERSION = "1.0.0"
|
||||||
end
|
end
|
||||||
|
@ -23,11 +23,10 @@ Gem::Specification.new do |spec|
|
|||||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
spec.require_paths = ["lib"]
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
spec.add_dependency "httparty", "~> 0.21"
|
spec.add_dependency "httparty", "~> 0.17"
|
||||||
spec.add_dependency "json", "~> 2.7"
|
|
||||||
|
|
||||||
spec.add_development_dependency "bundler", "~> 2.1"
|
spec.add_development_dependency "bundler", "~> 2.0"
|
||||||
spec.add_dependency "rake", ">= 13.1"
|
spec.add_development_dependency "rake", "~> 10.0"
|
||||||
spec.add_development_dependency "webmock", "~> 3.23"
|
spec.add_development_dependency "webmock", "~> 3.6.0"
|
||||||
spec.add_development_dependency "minitest", "~> 5.22"
|
spec.add_development_dependency "minitest", "~> 5.8.4"
|
||||||
end
|
end
|
||||||
|
31
test/fixtures/collections.export_all.200.json
vendored
31
test/fixtures/collections.export_all.200.json
vendored
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"fileOperation": {
|
|
||||||
"id": "08d5db26-bf43-4ec9-ac62-8769fd828e94",
|
|
||||||
"type": "export",
|
|
||||||
"format": "outline-markdown",
|
|
||||||
"name": "Acme-export.zip",
|
|
||||||
"state": "creating",
|
|
||||||
"error": null,
|
|
||||||
"size": "0",
|
|
||||||
"collectionId": null,
|
|
||||||
"user": {
|
|
||||||
"id": "817fb131-4a9b-4981-9002-38c2503adc3e",
|
|
||||||
"name": "Acme Admin",
|
|
||||||
"avatarUrl": "https://fake-avatar-url.com",
|
|
||||||
"color": "#2BC2FF",
|
|
||||||
"isAdmin": true,
|
|
||||||
"isSuspended": false,
|
|
||||||
"isViewer": false,
|
|
||||||
"createdAt": "2024-03-07T04:03:45.204Z",
|
|
||||||
"updatedAt": "2024-03-07T06:51:26.023Z",
|
|
||||||
"lastActiveAt": "2024-03-07T06:51:26.023Z"
|
|
||||||
},
|
|
||||||
"createdAt": "2024-03-07T06:51:26.031Z",
|
|
||||||
"updatedAt": "2024-03-07T06:51:26.031Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": 200,
|
|
||||||
"ok": true
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
Redirecting to https://fake.s3-accelerate.amazonaws.com/uploads/3e11b7f9-f1c0-44d0-a21b-4d6e0561e9c9/a5b6985a-cff6-4d03-be60-20c517bee63e/Acme-export.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXXXXXXXXXXXX%2F20240307%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240307T055812Z&X-Amz-Expires=60&X-Amz-Signature=ff759b27ddfd5c7401c1715411a8ceba886f9f462c9b52fc0c4a5906e99ecd22&X-Amz-SignedHeaders=host&response-content-disposition=attachment.
|
|
@ -6,7 +6,6 @@ require 'json'
|
|||||||
class ClientTest < Minitest::Test
|
class ClientTest < Minitest::Test
|
||||||
TOKEN = "c4302eFAKE_TOKEN9b6e27bccb7"
|
TOKEN = "c4302eFAKE_TOKEN9b6e27bccb7"
|
||||||
BASE_URI='https://kb.example.com'
|
BASE_URI='https://kb.example.com'
|
||||||
FILE_OPERATION_ID = "08d5db26-bf43-4ec9-ac62-8769fd828e94"
|
|
||||||
def setup
|
def setup
|
||||||
ENV['OUTLINE_TOKEN'] = TOKEN
|
ENV['OUTLINE_TOKEN'] = TOKEN
|
||||||
@client = Outliner::Client.new BASE_URI
|
@client = Outliner::Client.new BASE_URI
|
||||||
@ -17,36 +16,9 @@ class ClientTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_auth_info_api
|
def test_auth_info_api
|
||||||
mock('auth.info')
|
mock('auth.info', 'auth.info.200')
|
||||||
r = @client.auth__info
|
auth_info = @client.auth_info
|
||||||
assert_equal "https://kb.example.com", r['data']['team']['url']
|
assert_equal "https://kb.example.com", auth_info['data']['team']['url']
|
||||||
end
|
|
||||||
|
|
||||||
def test_export
|
|
||||||
mock('collections.export_all')
|
|
||||||
r = @client.collections__export_all
|
|
||||||
assert_equal FILE_OPERATION_ID, r['data']['fileOperation']['id']
|
|
||||||
assert_equal 200, r['status']
|
|
||||||
assert_equal true, r['ok']
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_retrieve_file_operation
|
|
||||||
mock("fileOperations.redirect", {
|
|
||||||
id: FILE_OPERATION_ID
|
|
||||||
}, {
|
|
||||||
"X-Download-Options" => "noopen",
|
|
||||||
"X-Content-Type-Options" => "nosniff",
|
|
||||||
"Content-Type" => "text/plain; charset=utf-8",
|
|
||||||
"Content-Length" => "459",
|
|
||||||
"Location" => "https://s3.example.com/#{FILE_OPERATION_ID}"
|
|
||||||
}, 302)
|
|
||||||
begin
|
|
||||||
r = @client.fileOperations__redirect({id: FILE_OPERATION_ID}, format: nil, no_follow: true)
|
|
||||||
rescue HTTParty::RedirectionTooDeep => e
|
|
||||||
assert_equal "302", e.response.code
|
|
||||||
assert_equal "https://s3.example.com/#{FILE_OPERATION_ID}", e.response.header['location']
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -55,16 +27,15 @@ class ClientTest < Minitest::Test
|
|||||||
File.read "test/fixtures/#{file}.json"
|
File.read "test/fixtures/#{file}.json"
|
||||||
end
|
end
|
||||||
|
|
||||||
def mock(method_name, params = {}, response_headers = {}, status = 200)
|
def mock(method_name, fixture_file, params = {})
|
||||||
stub_request(:post, BASE_URI + "/api/" + method_name)
|
stub_request(:post, BASE_URI + "/api/" + method_name)
|
||||||
.with(
|
.with(
|
||||||
body: params.to_json,
|
body: params.merge({token: TOKEN}).to_json,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept'=>'application/json',
|
'Accept'=>'application/json',
|
||||||
'User-Agent'=>"Outliner/#{Outliner::VERSION}",
|
'User-Agent'=>"Outliner/#{Outliner::VERSION}",
|
||||||
'Content-Type'=> 'application/json',
|
'Content-Type'=> 'application/json'
|
||||||
"Authorization"=> "Bearer #{TOKEN}"
|
|
||||||
}
|
}
|
||||||
).to_return(body: read_fixture(method_name + ".#{status}"), headers: response_headers, status: 302)
|
).to_return(body: read_fixture(fixture_file))
|
||||||
end
|
end
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user