mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-02 10:55:30 -04:00
-
This commit is contained in:
parent
83f6fc25f0
commit
042df2bf1f
@ -1,6 +1,15 @@
|
||||
require "kemal"
|
||||
require "./storage"
|
||||
|
||||
def request_path_startswith(env, ary)
|
||||
ary.each do |prefix|
|
||||
if env.request.path.starts_with? prefix
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
class AuthHandler < Kemal::Handler
|
||||
exclude ["/login"]
|
||||
exclude ["/login"], "POST"
|
||||
@ -18,9 +27,9 @@ class AuthHandler < Kemal::Handler
|
||||
return env.redirect "/login"
|
||||
end
|
||||
|
||||
if env.request.path.starts_with? "/admin"
|
||||
if request_path_startswith env, ["/admin", "/api/admin"]
|
||||
unless storage.verify_admin cookie.value
|
||||
env.response.status_code = 401
|
||||
return env.response.status_code = 401
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -9,10 +9,10 @@ class Config
|
||||
property port = 9000
|
||||
|
||||
@[YAML::Field(key: "library_path")]
|
||||
property library_path = File.expand_path "~/mango-library", home: true
|
||||
property library_path = File.expand_path "~/mango/library", home: true
|
||||
|
||||
@[YAML::Field(key: "db_path")]
|
||||
property db_path = File.expand_path "~/mango-library/mango.db", home: true
|
||||
property db_path = File.expand_path "~/mango/mango.db", home: true
|
||||
|
||||
def self.load
|
||||
cfg_path = File.expand_path "~/.config/mango/config.yml", home: true
|
||||
@ -22,7 +22,7 @@ class Config
|
||||
puts "The config file #{cfg_path} does not exist." \
|
||||
"Do you want mango to dump the default config there? [Y/n]"
|
||||
input = gets
|
||||
if !input.nil? && input.downcase == "n"
|
||||
if input && input.downcase == "n"
|
||||
abort "Aborting..."
|
||||
end
|
||||
default = self.allocate
|
||||
|
@ -79,7 +79,7 @@ class Library
|
||||
def initialize(dir : String)
|
||||
@dir = dir
|
||||
unless Dir.exists? dir
|
||||
abort "ERROR: The library directory #{dir} does not exist"
|
||||
Dir.mkdir_p dir
|
||||
end
|
||||
@titles = (Dir.entries dir)
|
||||
.select! { |path| File.directory? File.join dir, path }
|
||||
|
76
src/mango.cr
76
src/mango.cr
@ -8,7 +8,6 @@ config = Config.load
|
||||
library = Library.new config.library_path
|
||||
storage = Storage.new config.db_path
|
||||
|
||||
|
||||
macro layout(name)
|
||||
render "src/views/#{{{name}}}.ecr", "src/views/layout.ecr"
|
||||
end
|
||||
@ -23,11 +22,14 @@ macro get_username(env)
|
||||
storage.verify_token cookie.value
|
||||
end
|
||||
|
||||
def hash_to_query(hash)
|
||||
hash.map { |k, v| "#{k}=#{v}" }
|
||||
.join("&")
|
||||
macro send_json(env, json)
|
||||
{{env}}.response.content_type = "application/json"
|
||||
{{json}}
|
||||
end
|
||||
|
||||
def hash_to_query(hash)
|
||||
hash.map { |k, v| "#{k}=#{v}" }.join("&")
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
titles = library.titles
|
||||
@ -53,6 +55,7 @@ get "/admin/user" do |env|
|
||||
layout "user"
|
||||
end
|
||||
|
||||
|
||||
get "/admin/user/edit" do |env|
|
||||
username = env.params.query["username"]?
|
||||
admin = env.params.query["admin"]?
|
||||
@ -76,9 +79,16 @@ post "/admin/user/edit" do |env|
|
||||
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
|
||||
|
||||
@ -104,8 +114,18 @@ post "/admin/user/edit/:original_username" do |env|
|
||||
if username.size < 3
|
||||
raise "Username should contain at least 3 characters"
|
||||
end
|
||||
if password.size != 0 && password.size < 6
|
||||
raise "Password should contain at least 6 characters"
|
||||
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
|
||||
@ -121,14 +141,13 @@ post "/admin/user/edit/:original_username" do |env|
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
get "/reader/:title/:entry" do |env|
|
||||
# We should save the reading progress, and ask the user if she wants to
|
||||
# start over or resume. For now we just start from page 0
|
||||
begin
|
||||
title = library.get_title env.params.url["title"]
|
||||
raise "" if title.nil?
|
||||
entry = title.get_entry env.params.url["entry"]
|
||||
raise "" if entry.nil?
|
||||
title = (library.get_title env.params.url["title"]).not_nil!
|
||||
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
||||
env.redirect "/reader/#{title.title}/#{entry.title}/0"
|
||||
rescue
|
||||
env.response.status_code = 404
|
||||
@ -139,10 +158,8 @@ get "/reader/:title/:entry/:page" do |env|
|
||||
imgs_each_page = 5
|
||||
# here each :page contains `imgs_each_page` images
|
||||
begin
|
||||
title = library.get_title env.params.url["title"]
|
||||
raise "" if title.nil?
|
||||
entry = title.get_entry env.params.url["entry"]
|
||||
raise "" if entry.nil?
|
||||
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 * imgs_each_page >= entry.pages
|
||||
|
||||
@ -163,8 +180,7 @@ end
|
||||
|
||||
get "/logout" do |env|
|
||||
begin
|
||||
cookie = env.request.cookies.find { |c| c.name == "token" }
|
||||
raise "" if cookie.nil?
|
||||
cookie = env.request.cookies.find { |c| c.name == "token" }.not_nil!
|
||||
storage.logout cookie.value
|
||||
rescue
|
||||
ensure
|
||||
@ -176,8 +192,7 @@ post "/login" do |env|
|
||||
begin
|
||||
username = env.params.body["username"]
|
||||
password = env.params.body["password"]
|
||||
token = storage.verify_user username, password
|
||||
raise "" if token.nil?
|
||||
token = storage.verify_user(username, password).not_nil!
|
||||
|
||||
cookie = HTTP::Cookie.new "token", token
|
||||
env.response.cookies << cookie
|
||||
@ -215,8 +230,7 @@ get "/api/book/:title" do |env|
|
||||
t = library.get_title title
|
||||
raise "Title `#{title}` not found" if t.nil?
|
||||
|
||||
env.response.content_type = "application/json"
|
||||
t.to_json
|
||||
send_json env, t.to_json
|
||||
rescue e
|
||||
STDERR.puts e
|
||||
env.response.status_code = 500
|
||||
@ -225,8 +239,26 @@ get "/api/book/:title" do |env|
|
||||
end
|
||||
|
||||
get "/api/book" do |env|
|
||||
env.response.content_type = "application/json"
|
||||
library.to_json
|
||||
send_json env, library.to_json
|
||||
end
|
||||
|
||||
post "/api/admin/scan" do |env|
|
||||
start = Time.utc
|
||||
library = Library.new config.library_path
|
||||
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
|
||||
|
||||
add_handler AuthHandler.new storage
|
||||
|
@ -20,6 +20,10 @@ class Storage
|
||||
|
||||
def initialize(path)
|
||||
@path = path
|
||||
dir = File.dirname path
|
||||
unless Dir.exists? dir
|
||||
Dir.mkdir_p dir
|
||||
end
|
||||
DB.open "sqlite3://#{path}" do |db|
|
||||
begin
|
||||
db.exec "create table users" \
|
||||
@ -34,7 +38,7 @@ class Storage
|
||||
random_pw = random_str
|
||||
hash = hash_password random_pw
|
||||
db.exec "insert into users values (?, ?, ?, ?)",
|
||||
"admin", hash, "", 1
|
||||
"admin", hash, nil, 1
|
||||
puts "Initial user created. You can log in with " \
|
||||
"#{{"username" => "admin", "password" => random_pw}}"
|
||||
end
|
||||
@ -99,7 +103,7 @@ class Storage
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
hash = hash_password password
|
||||
db.exec "insert into users values (?, ?, ?, ?)",
|
||||
username, hash, "", admin
|
||||
username, hash, nil, admin
|
||||
end
|
||||
end
|
||||
|
||||
@ -119,11 +123,17 @@ class Storage
|
||||
end
|
||||
end
|
||||
|
||||
def delete_user(username)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
db.exec "delete from users where username = (?)", username
|
||||
end
|
||||
end
|
||||
|
||||
def logout(token)
|
||||
DB.open "sqlite3://#{@path}" do |db|
|
||||
begin
|
||||
db.exec "update users set token = (?) where token = (?)", \
|
||||
"", token
|
||||
nil, token
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,13 @@
|
||||
<ul class="uk-list uk-list-large uk-list-divider">
|
||||
<li data-url="/admin/info">Server Info</li>
|
||||
<li data-url="/admin/user">User Managerment</li>
|
||||
<li onclick="scan()">Scan Library Files</li>
|
||||
<li onclick="if(!scanning){scan()}">
|
||||
<span id="scan">Scan Library Files</span>
|
||||
<span id="scan-status" style="float:right;">
|
||||
<div uk-spinner hidden></div>
|
||||
<span hidden style="cursor:auto;"></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr class="uk-divider-icon">
|
||||
@ -9,8 +15,22 @@
|
||||
|
||||
<% content_for "script" do %>
|
||||
<script>
|
||||
var scanning = false
|
||||
function scan() {
|
||||
alert("scan");
|
||||
scanning = true
|
||||
$('#scan-status > div').removeAttr('hidden');
|
||||
$('#scan-status > span').attr('hidden', '');
|
||||
var color = $('#scan').css('color');
|
||||
$('#scan').css('color', 'gray');
|
||||
$.post('/api/admin/scan', function (data) {
|
||||
var ms = data.milliseconds;
|
||||
var titles = data.titles;
|
||||
$('#scan-status > span').text('Scanned ' + titles + ' titles in ' + ms + 'ms');
|
||||
$('#scan-status > span').removeAttr('hidden');
|
||||
$('#scan').css('color', color);
|
||||
$('#scan-status > div').attr('hidden', '');
|
||||
scanning = false;
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
$('li').click(function() {
|
||||
|
@ -1,3 +1,5 @@
|
||||
<h2 class=uk-title>Library</h2>
|
||||
<p class="uk-text-meta"><%= titles.size %> titles found</p>
|
||||
<div class="uk-child-width-1-4@m" uk-grid>
|
||||
<%- titles.each do |t| -%>
|
||||
<div>
|
||||
|
@ -11,13 +11,44 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="uk-section uk-section-primary uk-section-small">
|
||||
<p class="uk-align-right uk-margin-right">Hello !</p>
|
||||
<div class="uk-offcanvas-content">
|
||||
<div class="uk-navbar-container uk-navbar-transparent" uk-navbar="uk-navbar">
|
||||
<div id="mobile-nav" uk-offcanvas="overlay: true">
|
||||
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
|
||||
<ul class="uk-nav uk-nav-primary uk-nav-center uk-margin-auto-vertical">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/admin">Admin</a></li>
|
||||
<hr uk-divider>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-position-top">
|
||||
<div class="uk-navbar-container uk-navbar-transparent" uk-navbar="uk-navbar">
|
||||
<div class="uk-navbar-left uk-hidden@s">
|
||||
<div class="uk-navbar-toggle" uk-navbar-toggle-icon="uk-navbar-toggle-icon" uk-toggle="target: #mobile-nav"></div>
|
||||
</div>
|
||||
<div class="uk-navbar-left uk-visible@s">
|
||||
<ul class="uk-navbar-nav">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/admin">Admin</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="uk-navbar-right uk-visible@s">
|
||||
<ul class="uk-navbar-nav">
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-section uk-section-default uk-section-small">
|
||||
<div class="uk-container uk-container-small">
|
||||
<%= content %>
|
||||
</div>
|
||||
<div class="uk-section uk-section-default uk-section-small">
|
||||
<div class="uk-container uk-container-small">
|
||||
<%= content %>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<h2 class=uk-title><%= title.title %></h2>
|
||||
<p class="uk-text-meta"><%= title.entries.size %> entries found</p>
|
||||
<div class="uk-child-width-1-4@m" uk-grid>
|
||||
<%- title.entries.each do |e| -%>
|
||||
<div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
<div id="alert"></div>
|
||||
<table class="uk-table uk-table-divider">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -27,8 +28,24 @@
|
||||
|
||||
<% content_for "script" do %>
|
||||
<script>
|
||||
function remove(username) {
|
||||
alert(username);
|
||||
}
|
||||
function alert(level, text) {
|
||||
hideAlert();
|
||||
var html = '<div class="uk-alert-' + level + '" uk-alert><a class="uk-alert-close" uk-close></a><p>' + text + '</p></div>';
|
||||
$('#alert').append(html);
|
||||
}
|
||||
function hideAlert() {
|
||||
$('#alert').empty();
|
||||
}
|
||||
function remove(username) {
|
||||
$.post('/api/admin/user/delete/' + username, function(data) {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
}
|
||||
else {
|
||||
error = data.error;
|
||||
alert('danger', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
|
Loading…
x
Reference in New Issue
Block a user