require "kemal" require "./config" require "./library" require "./storage" require "./auth_handler" require "./static" require "./util" class Server property config : Config property library : Library property storage : Storage def initialize @config = Config.load @library = Library.new @config.@library_path @storage = Storage.new @config.db_path error 403 do |env| message = "You are not authorized to visit #{env.request.path}" layout "message" end get "/" do |env| begin titles = @library.titles username = get_username env percentage = titles.map &.load_percetage username layout "index" rescue env.response.status_code = 500 end end get "/book/:title" do |env| begin title = (@library.get_title env.params.url["title"]).not_nil! username = get_username env percentage = title.entries.map { |e| title.load_percetage username, e.title } layout "title" rescue env.response.status_code = 404 end end get "/admin" do |env| layout "admin" end get "/admin/user" do |env| users = @storage.list_users username = get_username env layout "user" end get "/admin/user/edit" do |env| username = env.params.query["username"]? admin = env.params.query["admin"]? if admin admin = admin == "true" end error = env.params.query["error"]? current_user = get_username env new_user = username.nil? && admin.nil? layout "user-edit" end post "/admin/user/edit" do |env| # creating new user begin username = env.params.body["username"] password = env.params.body["password"] # if `admin` is unchecked, the body hash # would not contain `admin` admin = !env.params.body["admin"]?.nil? if username.size < 3 raise "Username should contain at least 3 characters" end if (username =~ /^[A-Za-z0-9_]+$/).nil? raise "Username should contain alphanumeric characters "\ "and underscores only" end if password.size < 6 raise "Password should contain at least 6 characters" end if (password =~ /^[[:ascii:]]+$/).nil? raise "password should contain ASCII characters only" end @storage.new_user username, password, admin env.redirect "/admin/user" rescue e puts e.message redirect_url = URI.new \ path: "/admin/user/edit",\ query: hash_to_query({"error" => e.message}) env.redirect redirect_url.to_s end end post "/admin/user/edit/:original_username" do |env| # editing existing user begin username = env.params.body["username"] password = env.params.body["password"] # if `admin` is unchecked, the body # hash would not contain `admin` admin = !env.params.body["admin"]?.nil? original_username = env.params.url["original_username"] if username.size < 3 raise "Username should contain at least 3 characters" end if (username =~ /^[A-Za-z0-9_]+$/).nil? raise "Username should contain alphanumeric characters "\ "and underscores only" end if password.size != 0 if password.size < 6 raise "Password should contain at least 6 characters" end if (password =~ /^[[:ascii:]]+$/).nil? raise "password should contain ASCII characters only" end end @storage.update_user \ original_username, username, password, admin env.redirect "/admin/user" rescue e puts e.message redirect_url = URI.new \ path: "/admin/user/edit",\ query: hash_to_query({"username" => original_username, \ "admin" => admin, "error" => e.message}) env.redirect redirect_url.to_s end end get "/reader/:title/:entry" do |env| begin title = (@library.get_title env.params.url["title"]).not_nil! entry = (title.get_entry env.params.url["entry"]).not_nil! # load progress username = get_username env page = title.load_progress username, entry.title # we go back 2 * `IMGS_PER_PAGE` pages. the infinite scroll # library perloads a few pages in advance, and the user # might not have actually read them page = [page - 2 * IMGS_PER_PAGE, 1].max env.redirect "/reader/#{title.title}/#{entry.title}/#{page}" rescue env.response.status_code = 404 end end get "/reader/:title/:entry/:page" do |env| begin title = (@library.get_title env.params.url["title"]).not_nil! entry = (title.get_entry env.params.url["entry"]).not_nil! page = env.params.url["page"].to_i raise "" if page > entry.pages || page <= 0 # save progress username = get_username env title.save_progress username, entry.title, page pages = (page...[entry.pages + 1, page + IMGS_PER_PAGE].min) urls = pages.map { |idx| "/api/page/#{title.title}/#{entry.title}/#{idx}" } reader_urls = pages.map { |idx| "/reader/#{title.title}/#{entry.title}/#{idx}" } next_page = page + IMGS_PER_PAGE next_url = next_page > entry.pages ? nil : "/reader/#{title.title}/#{entry.title}/#{next_page}" exit_url = "/book/#{title.title}" next_entry = title.next_entry entry next_entry_url = next_entry.nil? ? nil : \ "/reader/#{title.title}/#{next_entry.title}" render "src/views/reader.ecr" rescue env.response.status_code = 404 end end get "/login" do |env| render "src/views/login.ecr" end get "/logout" do |env| begin cookie = env.request.cookies .find { |c| c.name == "token" }.not_nil! @storage.logout cookie.value rescue ensure env.redirect "/login" end end post "/login" do |env| begin username = env.params.body["username"] password = env.params.body["password"] token = @storage.verify_user(username, password).not_nil! cookie = HTTP::Cookie.new "token", token env.response.cookies << cookie env.redirect "/" rescue env.redirect "/login" end end get "/api/page/:title/:entry/:page" do |env| begin title = env.params.url["title"] entry = env.params.url["entry"] page = env.params.url["page"].to_i t = @library.get_title title raise "Title `#{title}` not found" if t.nil? e = t.get_entry entry raise "Entry `#{entry}` of `#{title}` not found" if e.nil? img = e.read_page page raise "Failed to load page #{page} of `#{title}/#{entry}`"\ if img.nil? send_img env, img rescue e STDERR.puts e env.response.status_code = 500 e.message end end get "/api/book/:title" do |env| begin title = env.params.url["title"] t = @library.get_title title raise "Title `#{title}` not found" if t.nil? send_json env, t.to_json rescue e STDERR.puts e env.response.status_code = 500 e.message end end get "/api/book" do |env| send_json env, @library.to_json end post "/api/admin/scan" do |env| start = Time.utc @library.scan ms = (Time.utc - start).total_milliseconds send_json env, { "milliseconds" => ms, "titles" => @library.titles.size }.to_json end post "/api/admin/user/delete/:username" do |env| begin username = env.params.url["username"] @storage.delete_user username rescue e send_json env, { "success" => false, "error" => e.message }.to_json else send_json env, {"success" => true}.to_json end end post "/api/progress/:title/:entry/:page" do |env| begin username = get_username env title = (@library.get_title env.params.url["title"]).not_nil! entry = (title.get_entry env.params.url["entry"]).not_nil! page = env.params.url["page"].to_i raise "incorrect page value" if page < 0 || page > entry.pages title.save_progress username, entry.title, page rescue e send_json env, { "success" => false, "error" => e.message }.to_json else send_json env, {"success" => true}.to_json end end add_handler AuthHandler.new @storage {% if flag?(:release) %} # when building for relase, embed the static files in binary serve_static false add_handler StaticHandler.new {% end %} end def start {% if flag?(:release) %} Kemal.config.env = "production" {% end %} Kemal.config.port = @config.port Kemal.run end end server = Server.new server.start