app/server.rb in sqlui-0.1.71 vs app/server.rb in sqlui-0.1.72
- old
+ new
@@ -5,33 +5,42 @@
require 'digest/md5'
require 'erb'
require 'json'
require 'prometheus/middleware/collector'
require 'prometheus/middleware/exporter'
+require 'securerandom'
require 'sinatra/base'
require 'uri'
require 'webrick'
require_relative 'database_metadata'
require_relative 'github/cache'
require_relative 'github/caching_client'
require_relative 'github/client'
+require_relative 'github/paths'
require_relative 'github/tree'
require_relative 'github/tree_client'
require_relative 'mysql_types'
+require_relative 'pigs'
require_relative 'sql_parser'
require_relative 'sqlui'
require_relative 'version'
# SQLUI Sinatra server.
class Server < Sinatra::Base
- def self.logger
- @logger ||= WEBrick::Log.new
+ # An error message and status code.
+ class ClientError < StandardError
+ attr_reader :status
+
+ def initialize(message, status: 400)
+ super(message)
+ @status = status
+ end
end
- def self.init_and_run(config, resources_dir, github_cache = Github::Cache.new({}, logger: Server.logger))
- logger.info("Starting SQLUI v#{Version::SQLUI}")
- logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
+ def self.init_and_run(config, resources_dir, github_cache)
+ Sqlui.logger.info("Starting SQLUI v#{Version::SQLUI}")
+ Sqlui.logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
WEBrick::HTTPRequest.const_set('MAX_URI_LENGTH', 2 * 1024 * 1024)
if config.airbrake[:server]&.[](:enabled)
require 'airbrake'
@@ -108,37 +117,58 @@
get "#{config.base_url_path}/#{database.url_path}/?" do
redirect "#{database.url_path}/query", 301
end
post "#{config.base_url_path}/#{database.url_path}/metadata" do
- tree = Github::Tree.new(files: [], truncated: false)
+ tree = nil
if (saved_config = database.saved_config)
- tree_client = Github::TreeClient.new(access_token: database.saved_config.token, cache: github_cache,
- logger: Server.logger)
- tree = tree_client.get_tree(owner: saved_config.owner, repo: saved_config.repo, branch: saved_config.branch,
- regex: saved_config.regex)
+ tree_client = Github::TreeClient.new(
+ access_token: database.saved_config.token,
+ cache: github_cache,
+ logger: Sqlui.logger
+ )
+ tree = tree_client.get_tree(
+ owner: saved_config.owner,
+ repo: saved_config.repo,
+ ref: saved_config.branch,
+ regex: saved_config.regex
+ )
+ if params[:file]
+ owner, repo, ref, path = Github::Paths.parse_file_path(params[:file])
+ raise ClientError, "invalid owner: #{owner}" unless tree.owner == owner
+ raise ClientError, "invalid repo: #{repo}" unless tree.repo == repo
+
+ requested_tree = tree_client.get_tree(
+ owner: owner,
+ repo: repo,
+ ref: ref,
+ regex: /#{Regexp.escape(path)}/,
+ cache: ref == tree.ref
+ )
+ raise ClientError.new("file not found: #{params[:file]}", status: 404) unless requested_tree[path]
+
+ tree << requested_tree[path]
+ end
end
+
metadata = database.with_client do |client|
{
server: "#{config.name} - #{database.display_name}",
base_url_path: config.base_url_path,
schemas: DatabaseMetadata.lookup(client, database),
tables: database.tables,
columns: database.columns,
joins: database.joins,
- saved: tree.to_h do |file|
- comment_lines = file.content.split("\n").take_while do |l|
- l.start_with?('--')
- end
- description = comment_lines.map { |l| l.sub(/^-- */, '') }.join("\n")
+ saved: (tree || {}).to_h do |file|
[
- file.display_path,
+ file.full_path,
{
- filename: file.display_path,
+ filename: file.full_path,
+ path: file.path,
github_url: file.github_url,
- description: description,
- contents: file.content
+ contents: file.content,
+ tree_sha: file.tree_sha
}
]
end
}
end
@@ -149,11 +179,11 @@
post "#{config.base_url_path}/#{database.url_path}/query" do
data = request.body.read
request.body.rewind # since Airbrake will read the body on error
params.merge!(JSON.parse(data, symbolize_names: true))
- break client_error('missing sql') unless params[:sql]
+ raise ClientError, 'ERROR: missing sql' unless params[:sql]
variables = params[:variables] || {}
queries = find_selected_queries(params[:sql], params[:selection])
status 200
@@ -230,11 +260,11 @@
end
end
end
get "#{config.base_url_path}/#{database.url_path}/download_csv" do
- break client_error('missing sql') unless params[:sql]
+ raise ClientError, 'missing sql' unless params[:sql]
sql = Base64.decode64(params[:sql]).force_encoding('UTF-8')
variables = params.map { |k, v| k[0] == '_' ? [k, v] : nil }.compact.to_h
queries = find_selected_queries(sql, params[:selection])
@@ -276,40 +306,63 @@
airbrake_project_id: client_config[:project_id] || '',
airbrake_project_key: client_config[:project_key] || '',
resource_path_map: resource_path_map
}
end
+
+ post("#{config.base_url_path}/#{database.url_path}/save-file") do
+ raise ClientError, 'saved files disabled' unless (saved_config = database.saved_config)
+ raise ClientError, 'missing base_sha' if (params[:base_sha] || '').strip.empty?
+ raise ClientError, 'missing path' if (params[:path] || '').strip.empty?
+ raise ClientError, 'missing content' if params[:path].nil?
+
+ branch = "sqlui-#{Pigs.generate_phrase}"
+ tree_client = Github::TreeClient.new(
+ access_token: database.saved_config.token,
+ cache: github_cache,
+ logger: Sqlui.logger
+ )
+ tree_client.create_commit_with_file(
+ owner: saved_config.owner,
+ repo: saved_config.repo,
+ base_sha: params[:base_sha],
+ branch: branch,
+ path: params[:path],
+ content: params[:content].gsub("\r\n", "\n"),
+ author_name: database.saved_config.author_name,
+ author_email: database.saved_config.author_email
+ )
+ status 201
+ erb :redirect, locals: {
+ resource_path_map: resource_path_map,
+ location: "https://github.com/#{saved_config.owner}/#{saved_config.repo}/compare/#{branch}"
+ }
+ end
end
- error 400..510 do
- exception = env['sinatra.error']
- stacktrace = exception&.full_message(highlight: false)
+ error do |exception|
+ status exception.is_a?(ClientError) ? exception.status : 500
+ stacktrace = exception.full_message(highlight: false)
if request.env['HTTP_ACCEPT'] == 'application/json'
headers 'Content-Type' => 'application/json; charset=utf-8'
- message = "error: #{exception&.message&.lines&.first&.strip || 'unexpected error'}"
+ message = exception.message.to_s
json = { error: message, stacktrace: stacktrace }.compact.to_json
body json
else
- message = "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
erb :error, locals: {
resource_path_map: resource_path_map,
title: "SQLUI #{message}",
- message: message,
+ heading: "#{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}",
+ message: exception.message,
stacktrace: stacktrace
}
end
end
run!
end
private
-
- def client_error(message, stacktrace: nil)
- status(400)
- headers 'Content-Type' => 'application/json; charset=utf-8'
- body({ error: message, stacktrace: stacktrace }.compact.to_json)
- end
def find_selected_queries(full_sql, selection)
if selection
if selection.include?('-')
# sort because the selection could be in either direction