2022-12-30 17:04:27 +01:00
|
|
|
require "http/server"
|
2023-01-02 10:31:55 +01:00
|
|
|
require "http/server/handler"
|
2022-12-30 17:04:27 +01:00
|
|
|
require "uri"
|
|
|
|
require "sqlite3"
|
|
|
|
require "migrate"
|
|
|
|
require "dotenv"
|
2022-12-31 09:16:43 +01:00
|
|
|
require "toml"
|
2023-01-02 11:59:37 +01:00
|
|
|
require "colorize"
|
2023-01-03 06:07:15 +01:00
|
|
|
require "option_parser"
|
|
|
|
require "migrate"
|
2022-12-30 17:04:27 +01:00
|
|
|
|
|
|
|
require "./enums"
|
2022-12-31 02:08:09 +01:00
|
|
|
require "./lib/hash"
|
|
|
|
require "./lib/format"
|
2023-01-05 13:57:26 +01:00
|
|
|
require "./lib/xor"
|
2022-12-31 02:08:09 +01:00
|
|
|
require "./lib/accounts"
|
|
|
|
require "./lib/gjp"
|
2022-12-31 03:08:02 +01:00
|
|
|
require "./lib/clean"
|
2022-12-31 14:25:43 +01:00
|
|
|
require "./lib/songs"
|
2023-01-02 14:32:31 +01:00
|
|
|
require "./lib/ids"
|
2023-01-03 11:38:23 +01:00
|
|
|
require "./lib/level"
|
2023-01-04 00:20:45 +01:00
|
|
|
require "./lib/dailies"
|
2022-12-30 17:04:27 +01:00
|
|
|
|
2023-01-03 15:50:05 +01:00
|
|
|
if File.exists?(".env")
|
|
|
|
Dotenv.load
|
|
|
|
end
|
2022-12-30 17:04:27 +01:00
|
|
|
|
|
|
|
module CrystalGauntlet
|
|
|
|
VERSION = "0.1.0"
|
|
|
|
|
2023-01-03 15:50:05 +01:00
|
|
|
CONFIG = File.exists?("./config.toml") ? TOML.parse(File.read("./config.toml")) : TOML.parse("") # todo: log warning?
|
2023-01-02 11:59:37 +01:00
|
|
|
LOG = ::Log.for("crystal-gauntlet")
|
2022-12-31 09:16:43 +01:00
|
|
|
|
|
|
|
def config_get(key : String)
|
|
|
|
this = CONFIG
|
|
|
|
key.split(".").each do |val|
|
|
|
|
next_val = this.as(Hash)[val]?
|
|
|
|
if next_val == nil
|
|
|
|
return nil
|
|
|
|
else
|
|
|
|
this = next_val
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return this
|
|
|
|
end
|
|
|
|
|
2023-01-03 15:50:05 +01:00
|
|
|
DATABASE = DB.open(ENV["DATABASE_URL"]? || "sqlite3://./crystal-gauntlet.db")
|
2022-12-30 17:04:27 +01:00
|
|
|
|
2023-01-05 15:08:57 +01:00
|
|
|
# todo: unhardcore
|
|
|
|
DATA_FOLDER = Path.new("data")
|
|
|
|
|
2023-01-03 08:02:50 +01:00
|
|
|
@@endpoints = Hash(String, (HTTP::Server::Context -> String)).new
|
2023-01-04 10:20:45 +01:00
|
|
|
@@template_endpoints = Hash(String, (HTTP::Server::Context -> Nil)).new
|
2022-12-30 17:04:27 +01:00
|
|
|
|
2023-01-04 10:07:22 +01:00
|
|
|
@@up_at = nil
|
|
|
|
|
|
|
|
def self.uptime
|
|
|
|
if !@@up_at
|
|
|
|
return Time::Span::ZERO
|
|
|
|
else
|
|
|
|
return Time.utc - @@up_at.not_nil!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.uptime_s
|
|
|
|
span = uptime
|
|
|
|
Format.fmt_timespan_long(span)
|
|
|
|
end
|
|
|
|
|
2022-12-30 17:04:27 +01:00
|
|
|
def self.endpoints
|
|
|
|
@@endpoints
|
|
|
|
end
|
|
|
|
|
2023-01-03 18:02:01 +01:00
|
|
|
def self.template_endpoints
|
|
|
|
@@template_endpoints
|
|
|
|
end
|
|
|
|
|
2023-01-02 11:59:37 +01:00
|
|
|
def severity_color(severity : Log::Severity) : Colorize::Object
|
|
|
|
case severity
|
|
|
|
when .trace?
|
|
|
|
Colorize.with.dark_gray
|
|
|
|
when .debug?
|
|
|
|
Colorize.with.dark_gray
|
|
|
|
when .info?
|
|
|
|
Colorize.with.cyan
|
|
|
|
when .notice?
|
|
|
|
Colorize.with.cyan
|
|
|
|
when .warn?
|
|
|
|
Colorize.with.yellow
|
|
|
|
when .error?
|
|
|
|
Colorize.with.red
|
|
|
|
when .fatal?
|
|
|
|
Colorize.with.light_red
|
|
|
|
else
|
|
|
|
Colorize.with.white
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
struct CrystalGauntletFormat < Log::StaticFormatter
|
|
|
|
def run
|
|
|
|
Colorize.with.light_gray.dim.surround(@io) do
|
|
|
|
timestamp
|
|
|
|
end
|
2023-01-02 15:00:04 +01:00
|
|
|
string " "
|
2023-01-02 11:59:37 +01:00
|
|
|
severity_color(@entry.severity).surround(@io) do
|
|
|
|
@entry.severity.label.rjust(@io, 6)
|
|
|
|
end
|
2023-01-02 15:00:04 +01:00
|
|
|
string " "
|
2023-01-02 11:59:37 +01:00
|
|
|
Colorize.with.white.surround(@io) do
|
|
|
|
source
|
|
|
|
end
|
2023-01-02 15:00:04 +01:00
|
|
|
string " "
|
2023-01-02 11:59:37 +01:00
|
|
|
message
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-02 10:31:55 +01:00
|
|
|
class GDHandler
|
|
|
|
include HTTP::Handler
|
|
|
|
|
2023-01-03 08:02:50 +01:00
|
|
|
def call(context : HTTP::Server::Context)
|
2022-12-30 17:04:27 +01:00
|
|
|
# expunge trailing slashes
|
|
|
|
path = context.request.path.chomp("/")
|
|
|
|
|
2023-01-02 10:31:55 +01:00
|
|
|
path = path.sub(config_get("general.append_path").as(String | Nil) || "", "")
|
|
|
|
|
|
|
|
body = context.request.body
|
|
|
|
|
2023-01-02 14:32:31 +01:00
|
|
|
if CrystalGauntlet.endpoints.has_key?(path) && context.request.method == "POST" && body
|
2023-01-02 10:31:55 +01:00
|
|
|
func = CrystalGauntlet.endpoints[path]
|
2023-01-02 15:00:04 +01:00
|
|
|
begin
|
2023-01-03 08:02:50 +01:00
|
|
|
value = func.call(context)
|
2023-01-02 15:00:04 +01:00
|
|
|
rescue err
|
2023-01-03 08:02:50 +01:00
|
|
|
LOG.error { "error while handling #{path.colorize(:white)}:" }
|
2023-01-02 15:00:04 +01:00
|
|
|
LOG.error { err.to_s }
|
|
|
|
is_relevant = true
|
|
|
|
err.backtrace.each do |str|
|
|
|
|
# this is a hack. Oh well
|
|
|
|
if str.starts_with?("src/crystal-gauntlet.cr") || (!is_relevant)
|
|
|
|
is_relevant = false
|
|
|
|
else
|
|
|
|
LOG.error {" #{str}"}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
context.response.content_type = "text/plain"
|
|
|
|
context.response.respond_with_status(500, "-1")
|
|
|
|
else
|
2023-01-04 13:10:55 +01:00
|
|
|
max_size = 2048
|
|
|
|
|
|
|
|
value_displayed = value
|
|
|
|
if value.size > max_size
|
|
|
|
value_displayed = value[0..max_size] + ("…".colorize(:dark_gray).to_s)
|
|
|
|
end
|
|
|
|
LOG.debug { "-> ".colorize(:green).to_s + value_displayed }
|
|
|
|
|
2023-01-02 15:00:04 +01:00
|
|
|
context.response.content_type = "text/plain"
|
|
|
|
context.response.print value
|
|
|
|
end
|
2022-12-30 17:04:27 +01:00
|
|
|
else
|
2023-01-02 10:31:55 +01:00
|
|
|
call_next(context)
|
2022-12-30 17:04:27 +01:00
|
|
|
end
|
|
|
|
end
|
2023-01-02 10:31:55 +01:00
|
|
|
end
|
|
|
|
|
2023-01-03 18:02:01 +01:00
|
|
|
class TemplateHandler
|
|
|
|
include HTTP::Handler
|
|
|
|
|
|
|
|
def call(context : HTTP::Server::Context)
|
|
|
|
# expunge trailing slashes
|
|
|
|
path = context.request.path.chomp("/")
|
|
|
|
|
|
|
|
body = context.request.body
|
|
|
|
|
|
|
|
if CrystalGauntlet.template_endpoints.has_key?(path)
|
|
|
|
func = CrystalGauntlet.template_endpoints[path]
|
|
|
|
begin
|
2023-01-04 10:20:45 +01:00
|
|
|
func.call(context)
|
2023-01-03 18:02:01 +01:00
|
|
|
rescue err
|
|
|
|
LOG.error { "error while handling #{path.colorize(:white)}:" }
|
|
|
|
LOG.error { err.to_s }
|
|
|
|
is_relevant = true
|
|
|
|
err.backtrace.each do |str|
|
|
|
|
# this is a hack. Oh well
|
|
|
|
if str.starts_with?("src/crystal-gauntlet.cr") || (!is_relevant)
|
|
|
|
is_relevant = false
|
|
|
|
else
|
|
|
|
LOG.error {" #{str}"}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
context.response.content_type = "text/html"
|
|
|
|
context.response.respond_with_status(500, "Internal server error occurred, sorry about that")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
call_next(context)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-02 10:31:55 +01:00
|
|
|
def self.run()
|
2023-01-03 06:07:15 +01:00
|
|
|
Log.setup_from_env(backend: Log::IOBackend.new(formatter: CrystalGauntletFormat))
|
|
|
|
|
|
|
|
migrate = false
|
|
|
|
|
|
|
|
parser = OptionParser.new do |parser|
|
|
|
|
parser.banner = "Usage: crystal-gauntlet [command] [arguments]"
|
|
|
|
|
|
|
|
parser.on("migrate", "Migrate the database") do
|
|
|
|
migrate = true
|
|
|
|
parser.banner = "Usage: crystal-gauntlet migrate [arguments]"
|
|
|
|
end
|
|
|
|
parser.on("-h", "--help", "Show this help") do
|
|
|
|
puts parser
|
|
|
|
exit
|
|
|
|
end
|
2022-12-31 09:45:06 +01:00
|
|
|
end
|
|
|
|
|
2023-01-03 06:07:15 +01:00
|
|
|
parser.parse
|
|
|
|
|
2023-01-04 00:39:14 +01:00
|
|
|
migrator = Migrate::Migrator.new(
|
|
|
|
DATABASE
|
|
|
|
)
|
|
|
|
|
2023-01-03 06:07:15 +01:00
|
|
|
if migrate
|
|
|
|
LOG.info { "Migrating #{ENV["DATABASE_URL"].colorize(:white)}..." }
|
|
|
|
migrator.to_latest
|
|
|
|
else
|
2023-01-04 00:39:14 +01:00
|
|
|
if !migrator.latest?
|
|
|
|
LOG.fatal { "Database hasn\'t been migrated!! Please run #{"crystal-gauntlet migrate".colorize(:white)}" }
|
2023-01-05 13:57:26 +01:00
|
|
|
return
|
2023-01-04 00:39:14 +01:00
|
|
|
end
|
|
|
|
|
2023-01-05 15:08:57 +01:00
|
|
|
["songs", "levels"].each() { |v|
|
|
|
|
Dir.mkdir_p(DATA_FOLDER / v)
|
|
|
|
}
|
|
|
|
|
2023-01-03 06:07:15 +01:00
|
|
|
server = HTTP::Server.new([
|
|
|
|
HTTP::LogHandler.new,
|
2023-01-03 18:47:12 +01:00
|
|
|
HTTP::StaticFileHandler.new("public/", fallthrough: true, directory_listing: false),
|
2023-01-05 15:08:57 +01:00
|
|
|
HTTP::StaticFileHandler.new((DATA_FOLDER / "songs").to_s, fallthrough: true, directory_listing: false),
|
2023-01-03 18:02:01 +01:00
|
|
|
CrystalGauntlet::GDHandler.new,
|
|
|
|
CrystalGauntlet::TemplateHandler.new
|
2023-01-03 06:07:15 +01:00
|
|
|
])
|
|
|
|
|
|
|
|
listen_on = URI.parse(ENV["LISTEN_ON"]? || "http://localhost:8080").normalize
|
|
|
|
|
|
|
|
case listen_on.scheme
|
|
|
|
when "http"
|
|
|
|
server.bind_tcp(listen_on.hostname.not_nil!, listen_on.port.not_nil!)
|
|
|
|
when "unix"
|
|
|
|
server.bind_unix(listen_on.to_s.sub("unix://",""))
|
|
|
|
end
|
2023-01-01 06:42:34 +01:00
|
|
|
|
2023-01-03 18:30:14 +01:00
|
|
|
full_server_path = (config_get("general.hostname").as?(String) || "") + "/" + (config_get("general.append_path").as?(String) || "")
|
|
|
|
robtop_server_path = "www.boomlings.com/database/"
|
|
|
|
if full_server_path.size != robtop_server_path.size
|
|
|
|
LOG.warn { "i think you made a mistake? length of full server path and default .exe location do not match" }
|
|
|
|
LOG.warn { " #{full_server_path}" }
|
|
|
|
LOG.warn { " #{robtop_server_path}" }
|
|
|
|
min_length = Math.min(full_server_path.size, robtop_server_path.size)
|
|
|
|
max_length = Math.max(full_server_path.size, robtop_server_path.size)
|
|
|
|
LOG.warn { " #{" " * min_length}#{"^" * (max_length - min_length)}"}
|
|
|
|
end
|
|
|
|
|
2023-01-04 10:07:22 +01:00
|
|
|
@@up_at = Time.utc
|
2023-01-03 06:07:15 +01:00
|
|
|
LOG.notice { "Listening on #{listen_on.to_s.colorize(:white)}" }
|
|
|
|
server.listen
|
|
|
|
end
|
2022-12-30 17:04:27 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
require "./endpoints/**"
|
2023-01-03 18:02:01 +01:00
|
|
|
require "./template_endpoints/**"
|
2022-12-30 17:04:27 +01:00
|
|
|
|
|
|
|
CrystalGauntlet.run()
|