bunto-admin 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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