bunto-admin 0.6.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/LICENSE +21 -0
- data/README.md +52 -0
- data/lib/bunto-admin.rb +47 -0
- data/lib/bunto-admin/apiable.rb +161 -0
- data/lib/bunto-admin/data_file.rb +106 -0
- data/lib/bunto-admin/directory.rb +70 -0
- data/lib/bunto-admin/file_helper.rb +66 -0
- data/lib/bunto-admin/page_without_a_file.rb +7 -0
- data/lib/bunto-admin/path_helper.rb +74 -0
- data/lib/bunto-admin/server.rb +100 -0
- data/lib/bunto-admin/server/collection.rb +82 -0
- data/lib/bunto-admin/server/configuration.rb +57 -0
- data/lib/bunto-admin/server/data.rb +82 -0
- data/lib/bunto-admin/server/page.rb +90 -0
- data/lib/bunto-admin/server/static_file.rb +61 -0
- data/lib/bunto-admin/static_server.rb +24 -0
- data/lib/bunto-admin/urlable.rb +65 -0
- data/lib/bunto-admin/version.rb +3 -0
- data/lib/bunto/commands/build.rb +14 -0
- data/lib/bunto/commands/serve.rb +26 -0
- metadata +209 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
module FileHelper
|
3
|
+
|
4
|
+
# The file the user requested in the URL
|
5
|
+
def requested_file
|
6
|
+
find_by_path(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
# The file ultimately written to disk
|
10
|
+
# This may be the requested file, or in the case of a rename will be read
|
11
|
+
# from the new path that was actually written to disk
|
12
|
+
def written_file
|
13
|
+
find_by_path(write_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Write a file to disk with the given content
|
17
|
+
def write_file(path, content)
|
18
|
+
Bunto.logger.debug "WRITING:", path
|
19
|
+
path = sanitized_path(path)
|
20
|
+
FileUtils.mkdir_p File.dirname(path)
|
21
|
+
File.open(path, "wb") do |file|
|
22
|
+
file.write(content)
|
23
|
+
end
|
24
|
+
site.process
|
25
|
+
end
|
26
|
+
|
27
|
+
# Delete the file at the given path
|
28
|
+
def delete_file(path)
|
29
|
+
Bunto.logger.debug "DELETING:", path
|
30
|
+
FileUtils.rm_f sanitized_path(path)
|
31
|
+
site.process
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def ensure_requested_file
|
37
|
+
ensure_file(requested_file)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ensure_written_file
|
41
|
+
ensure_file(written_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_by_path(path)
|
45
|
+
files = case namespace
|
46
|
+
when "collections"
|
47
|
+
collection.docs
|
48
|
+
when "data"
|
49
|
+
DataFile.all
|
50
|
+
when "pages", "static_files"
|
51
|
+
site.public_send(namespace.to_sym)
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
files.find { |f| sanitized_path(f.path) == path }
|
56
|
+
end
|
57
|
+
|
58
|
+
def ensure_file(file)
|
59
|
+
render_404 if file.nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def ensure_directory
|
63
|
+
render_404 unless Dir.exist?(directory_path)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
module PathHelper
|
3
|
+
def sanitized_path(path)
|
4
|
+
path = path_without_site_source(path)
|
5
|
+
Bunto.sanitized_path BuntoAdmin.site.source, path
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the basename + extension for the requested file
|
9
|
+
def filename
|
10
|
+
params["ext"] ||= "yml" if namespace == "data"
|
11
|
+
"#{params["path"]}.#{params["ext"]}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the sanitized path relative to the site source
|
15
|
+
def sanitized_relative_path(path)
|
16
|
+
path_without_site_source sanitized_path(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the sanitized absolute path to the requested object
|
20
|
+
def absolute_path
|
21
|
+
sanitized_path File.join(directory_path, filename)
|
22
|
+
end
|
23
|
+
alias_method :path, :absolute_path
|
24
|
+
|
25
|
+
# Returns the sanitized relative path to the requested object
|
26
|
+
def relative_path
|
27
|
+
sanitized_relative_path absolute_path
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the sanitized absolute path to write the requested object
|
31
|
+
def write_path
|
32
|
+
if renamed?
|
33
|
+
sanitized_path request_payload["path"]
|
34
|
+
else
|
35
|
+
path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
alias_method :request_path, :write_path
|
39
|
+
|
40
|
+
# Returns the sanitized relative path to write the requested object
|
41
|
+
def relative_write_path
|
42
|
+
sanitized_relative_path write_path
|
43
|
+
end
|
44
|
+
|
45
|
+
# Is this request renaming a file?
|
46
|
+
def renamed?
|
47
|
+
return false unless request_payload["path"]
|
48
|
+
ensure_leading_slash(request_payload["path"]) != relative_path
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Returns the path to the requested file's containing directory
|
54
|
+
def directory_path
|
55
|
+
case namespace
|
56
|
+
when "collections"
|
57
|
+
sanitized_path File.join(collection.relative_directory, params["splat"].first)
|
58
|
+
when "data"
|
59
|
+
sanitized_path File.join(DataFile.data_dir, params["splat"].first)
|
60
|
+
else
|
61
|
+
sanitized_path params["splat"].first
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def ensure_leading_slash(input)
|
66
|
+
return input if input.nil? || input.empty? || input.start_with?("/")
|
67
|
+
"/#{input}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def path_without_site_source(path)
|
71
|
+
path.to_s.gsub(%r!\A#{Regexp.escape(BuntoAdmin.site.source)}!, "")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
class Server < Sinatra::Base
|
3
|
+
ROUTES = %w(collections configuration data pages static_files).freeze
|
4
|
+
include BuntoAdmin::PathHelper
|
5
|
+
include BuntoAdmin::FileHelper
|
6
|
+
|
7
|
+
register Sinatra::Namespace
|
8
|
+
|
9
|
+
configure :development do
|
10
|
+
register Sinatra::Reloader
|
11
|
+
enable :logging
|
12
|
+
end
|
13
|
+
|
14
|
+
configure :development, :test do
|
15
|
+
require "sinatra/cross_origin"
|
16
|
+
register Sinatra::CrossOrigin
|
17
|
+
enable :cross_origin
|
18
|
+
disable :allow_credentials
|
19
|
+
set :allow_methods, %i[delete get options post put]
|
20
|
+
end
|
21
|
+
|
22
|
+
get "/" do
|
23
|
+
json ROUTES.map { |r| ["#{r}_api", URI.join(base_url, "/_api/", r)] }.to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
# CORS preflight
|
27
|
+
options "*" do
|
28
|
+
render_404 unless settings.development? || settings.test?
|
29
|
+
status 204
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def site
|
35
|
+
BuntoAdmin.site
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_404
|
39
|
+
status 404
|
40
|
+
content_type :json
|
41
|
+
halt
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_payload
|
45
|
+
@request_payload ||= if request_body.to_s.empty?
|
46
|
+
{}
|
47
|
+
else
|
48
|
+
JSON.parse(request_body)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def base_url
|
53
|
+
"#{request.scheme}://#{request.host_with_port}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def front_matter
|
57
|
+
request_payload["front_matter"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def document_body
|
61
|
+
body = if front_matter && !front_matter.empty?
|
62
|
+
YAML.dump(restored_front_matter).strip
|
63
|
+
.gsub(": 'null'", ": null") # restore null values
|
64
|
+
else
|
65
|
+
"---"
|
66
|
+
end
|
67
|
+
body << "\n---\n\n"
|
68
|
+
body << request_payload["raw_content"].to_s
|
69
|
+
end
|
70
|
+
alias page_body document_body
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def request_body
|
75
|
+
@request_body ||= begin
|
76
|
+
request.body.rewind
|
77
|
+
request.body.read
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def namespace
|
82
|
+
namespace = request.path_info.split("/")[1].to_s.downcase
|
83
|
+
namespace if ROUTES.include?(namespace)
|
84
|
+
end
|
85
|
+
|
86
|
+
# verbose 'null' values in front matter
|
87
|
+
def restored_front_matter
|
88
|
+
front_matter.map do |key, value|
|
89
|
+
value = "null" if value.nil?
|
90
|
+
[key, value]
|
91
|
+
end.to_h
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
require "bunto-admin/server/collection"
|
97
|
+
require "bunto-admin/server/configuration"
|
98
|
+
require "bunto-admin/server/data"
|
99
|
+
require "bunto-admin/server/page"
|
100
|
+
require "bunto-admin/server/static_file"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
class Server < Sinatra::Base
|
3
|
+
namespace "/collections" do
|
4
|
+
get do
|
5
|
+
json(site.collections.map { |c| c[1].to_api })
|
6
|
+
end
|
7
|
+
|
8
|
+
get "/:collection_id" do
|
9
|
+
ensure_collection
|
10
|
+
json collection.to_api
|
11
|
+
end
|
12
|
+
|
13
|
+
get "/:collection_id/*?/?:path.:ext" do
|
14
|
+
ensure_requested_file
|
15
|
+
json requested_file.to_api(:include_content => true)
|
16
|
+
end
|
17
|
+
|
18
|
+
get "/:collection_id/entries/?*" do
|
19
|
+
ensure_directory
|
20
|
+
json entries.map(&:to_api)
|
21
|
+
end
|
22
|
+
|
23
|
+
put "/:collection_id/*?/?:path.:ext" do
|
24
|
+
ensure_collection
|
25
|
+
|
26
|
+
if renamed?
|
27
|
+
ensure_requested_file
|
28
|
+
delete_file path
|
29
|
+
end
|
30
|
+
|
31
|
+
write_file write_path, document_body
|
32
|
+
json written_file.to_api(:include_content => true)
|
33
|
+
end
|
34
|
+
|
35
|
+
delete "/:collection_id/*?/?:path.:ext" do
|
36
|
+
ensure_requested_file
|
37
|
+
delete_file path
|
38
|
+
content_type :json
|
39
|
+
status 200
|
40
|
+
halt
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def collection
|
46
|
+
collection = site.collections.find { |l, _c| l == params["collection_id"] }
|
47
|
+
collection[1] if collection
|
48
|
+
end
|
49
|
+
|
50
|
+
def document_id
|
51
|
+
path = "#{params["splat"].first}/#{filename}"
|
52
|
+
path.gsub(%r!(\d{4})/(\d{2})/(\d{2})/(.*)!, '\1-\2-\3-\4')
|
53
|
+
end
|
54
|
+
|
55
|
+
def directory_docs
|
56
|
+
collection.docs.find_all { |d| File.dirname(d.path) == directory_path }
|
57
|
+
end
|
58
|
+
|
59
|
+
def ensure_collection
|
60
|
+
render_404 if collection.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_directory
|
64
|
+
ensure_collection
|
65
|
+
render_404 unless Dir.exist?(directory_path)
|
66
|
+
end
|
67
|
+
|
68
|
+
def entries
|
69
|
+
args = {
|
70
|
+
:base => site.source,
|
71
|
+
:content_type => params["collection_id"],
|
72
|
+
:splat => params["splat"].first,
|
73
|
+
}
|
74
|
+
# get the directories inside the requested directory
|
75
|
+
directory = BuntoAdmin::Directory.new(directory_path, args)
|
76
|
+
directories = directory.directories
|
77
|
+
# merge directories with the documents at the same level
|
78
|
+
directories.concat(directory_docs.sort_by(&:date).reverse)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
class Server < Sinatra::Base
|
3
|
+
namespace "/configuration" do
|
4
|
+
get do
|
5
|
+
json({
|
6
|
+
:content => parsed_configuration,
|
7
|
+
:raw_content => raw_configuration,
|
8
|
+
})
|
9
|
+
end
|
10
|
+
|
11
|
+
put do
|
12
|
+
write_file(configuration_path, configuration_body)
|
13
|
+
json request_payload
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def overrides
|
19
|
+
{
|
20
|
+
"source" => sanitized_path("/"),
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Computed configuration, with updates and defaults
|
25
|
+
def configuration
|
26
|
+
@configuration ||= Bunto.configuration(overrides)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Configuration data, as read by Bunto
|
30
|
+
def parsed_configuration
|
31
|
+
configuration.read_config_file(configuration_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Raw configuration content, as it sits on disk
|
35
|
+
def raw_configuration
|
36
|
+
File.read(
|
37
|
+
configuration_path,
|
38
|
+
Bunto::Utils.merged_file_read_opts(site, {})
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the path to the *first* config file discovered
|
43
|
+
def configuration_path
|
44
|
+
sanitized_path configuration.config_files(overrides).first
|
45
|
+
end
|
46
|
+
|
47
|
+
# The user's uploaded configuration for updates
|
48
|
+
# Instead of extracting `raw_content` directly from the `request_payload`,
|
49
|
+
# assign the data to a new variable and then extract the `raw_content`
|
50
|
+
# from it to circumvent CORS violation in `development` mode.
|
51
|
+
def configuration_body
|
52
|
+
payload = request_payload
|
53
|
+
payload["raw_content"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module BuntoAdmin
|
2
|
+
class Server < Sinatra::Base
|
3
|
+
# supported extensions, in order of preference, for now, no .csv
|
4
|
+
EXTENSIONS = %w(yml json).freeze
|
5
|
+
|
6
|
+
namespace "/data" do
|
7
|
+
get "/*?/?:path.:ext" do
|
8
|
+
ensure_requested_file
|
9
|
+
json requested_file.to_api(:include_content => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
get "/?*" do
|
13
|
+
ensure_directory
|
14
|
+
json entries.map(&:to_api)
|
15
|
+
end
|
16
|
+
|
17
|
+
put "/*?/?:path.:ext" do
|
18
|
+
if renamed?
|
19
|
+
ensure_requested_file
|
20
|
+
delete_file path
|
21
|
+
end
|
22
|
+
|
23
|
+
write_file write_path, data_file_body
|
24
|
+
json written_file.to_api(:include_content => true)
|
25
|
+
end
|
26
|
+
|
27
|
+
delete "/*?/?:path.:ext" do
|
28
|
+
ensure_requested_file
|
29
|
+
delete_file path
|
30
|
+
content_type :json
|
31
|
+
status 200
|
32
|
+
halt
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# returns relative path of root level directories that contain data files
|
38
|
+
def directory_paths
|
39
|
+
DataFile.all.map { |p| File.dirname(p.relative_path).split("/")[0] }.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def directory_pages
|
43
|
+
DataFile.all.find_all do |p|
|
44
|
+
sanitized_path(File.dirname(p.path)) == directory_path
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def entries
|
49
|
+
args = {
|
50
|
+
:base => sanitized_path(DataFile.data_dir),
|
51
|
+
:content_type => "data",
|
52
|
+
:splat => splats.first,
|
53
|
+
}
|
54
|
+
# get all directories inside the requested directory
|
55
|
+
directory = BuntoAdmin::Directory.new(directory_path, args)
|
56
|
+
directories = directory.directories
|
57
|
+
|
58
|
+
# exclude root level directories which do not have data files
|
59
|
+
if splats.first.empty?
|
60
|
+
directories = directories.select do |d|
|
61
|
+
directory_paths.include? d.name.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# merge directories with the pages at the same level
|
66
|
+
directories.concat(directory_pages)
|
67
|
+
end
|
68
|
+
|
69
|
+
def data_file_body
|
70
|
+
if !request_payload["raw_content"].to_s.empty?
|
71
|
+
request_payload["raw_content"]
|
72
|
+
elsif !request_payload["content"].to_s.empty?
|
73
|
+
YAML.dump(request_payload["content"]).sub(%r!\A---\n!, "")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def splats
|
78
|
+
params["splat"] || ["/"]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|