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.
@@ -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,7 @@
1
+ module BuntoAdmin
2
+ class PageWithoutAFile < Bunto::Page
3
+ def read_yaml(*)
4
+ @data ||= {}
5
+ end
6
+ end
7
+ 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