mirror of
https://github.com/hkalexling/Mango.git
synced 2026-04-25 00:00:52 -04:00
Compare commits
33 Commits
v0.3.1-alpha
...
v0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3039031924 | |||
| 8665616c2e | |||
| 4453b0ee9f | |||
| 487154e68c | |||
| 60609263ab | |||
| 4a245d2504 | |||
| 48c3a82078 | |||
| 4a59459773 | |||
| eefa8c3982 | |||
| 8fe2f3b4cc | |||
| 60d4cee0a9 | |||
| 8658cb8306 | |||
| d4e523c337 | |||
| d49c0092c2 | |||
| d75009f088 | |||
| d416dc6618 | |||
| 7233e6e5c3 | |||
| bd8ae9497f | |||
| 34b11dc2c7 | |||
| 30dea57346 | |||
| 7448592216 | |||
| 049bd3ab2c | |||
| c3608c101b | |||
| 1bec9f0108 | |||
| 09b297cd8e | |||
| b7cd55e692 | |||
| 986939ecb6 | |||
| a5e97af3a3 | |||
| 4cee5faecd | |||
| 711add74ef | |||
| f6f09c54bc | |||
| 0f58ebb87b | |||
| 46347a8fe4 |
@@ -8,9 +8,9 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: "!contains(github.event.head_commit.message, 'skip ci')"
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: crystallang/crystal:0.34.0-alpine
|
image: crystallang/crystal:0.34.0-alpine
|
||||||
|
|
||||||
@@ -19,8 +19,13 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: apk add --no-cache yarn yaml sqlite-static
|
run: apk add --no-cache yarn yaml sqlite-static
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make
|
run: make static
|
||||||
- name: Linter
|
- name: Linter
|
||||||
run: make check
|
run: make check
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: make test
|
run: make test
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: mango
|
||||||
|
path: mango
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ yarn.lock
|
|||||||
dist
|
dist
|
||||||
mango
|
mango
|
||||||
.env
|
.env
|
||||||
|
*.md
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ test:
|
|||||||
check:
|
check:
|
||||||
crystal tool format --check
|
crystal tool format --check
|
||||||
./bin/ameba
|
./bin/ameba
|
||||||
|
./dev/linewidth.sh
|
||||||
|
|
||||||
install:
|
install:
|
||||||
cp mango $(INSTALL_DIR)/mango
|
cp mango $(INSTALL_DIR)/mango
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r
|
|||||||
### CLI
|
### CLI
|
||||||
|
|
||||||
```
|
```
|
||||||
Mango e-manga server/reader. Version 0.3.0
|
Mango e-manga server/reader. Version 0.4.0
|
||||||
|
|
||||||
-v, --version Show version
|
-v, --version Show version
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
@@ -64,17 +64,19 @@ The default config file location is `~/.config/mango/config.yml`. It might be di
|
|||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
port: 9000
|
port: 9000
|
||||||
library_path: /home/alex_ling/mango/library
|
base_url: /
|
||||||
upload_path: /home/alex_ling/mango/uploads
|
db_path: ~/mango/mango.db
|
||||||
db_path: /home/alex_ling/mango/mango.db
|
|
||||||
scan_interval_minutes: 5
|
scan_interval_minutes: 5
|
||||||
log_level: info
|
log_level: info
|
||||||
|
upload_path: ~/mango/uploads
|
||||||
mangadex:
|
mangadex:
|
||||||
base_url: https://mangadex.org
|
base_url: https://mangadex.org
|
||||||
api_url: https://mangadex.org/api
|
api_url: https://mangadex.org/api
|
||||||
download_wait_seconds: 5
|
download_wait_seconds: 5
|
||||||
download_retries: 4
|
download_retries: 4
|
||||||
download_queue_db_path: /home/alex_ling/mango/queue.db
|
download_queue_db_path: ~/mango/queue.db
|
||||||
|
chapter_rename_rule: '[Vol.{volume} ][Ch.{chapter} ]{title|id}'
|
||||||
|
manga_rename_rule: '{title}'
|
||||||
```
|
```
|
||||||
|
|
||||||
- `scan_interval_minutes` can be any non-negative integer. Setting it to `0` disables the periodic scan
|
- `scan_interval_minutes` can be any non-negative integer. Setting it to `0` disables the periodic scan
|
||||||
@@ -127,4 +129,6 @@ Mobile UI:
|
|||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
|
Please check the [development guideline](https://github.com/hkalexling/Mango/wiki/Development) if you are interest in code contributions.
|
||||||
|
|
||||||
[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/0)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/1)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/2)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/3)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/4)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/5)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/6)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/7)
|
[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/0)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/1)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/2)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/3)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/4)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/5)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/6)[](https://sourcerer.io/fame/hkalexling/hkalexling/Mango/links/7)
|
||||||
|
|||||||
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ ! -z "$(grep '.\{80\}' --exclude-dir=lib --include="*.cr" -nr --color=always . | tee /dev/tty)" ] \
|
||||||
|
&& echo "The above lines exceed the 80 characters limit" \
|
||||||
|
|| exit 0
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
.uk-logo > img {
|
.uk-logo > img {
|
||||||
max-height: 90px;
|
height: 90px;
|
||||||
|
width: 90px;
|
||||||
}
|
}
|
||||||
.uk-search {
|
.uk-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ function scan() {
|
|||||||
$('#scan-status > span').attr('hidden', '');
|
$('#scan-status > span').attr('hidden', '');
|
||||||
var color = $('#scan').css('color');
|
var color = $('#scan').css('color');
|
||||||
$('#scan').css('color', 'gray');
|
$('#scan').css('color', 'gray');
|
||||||
$.post('/api/admin/scan', function (data) {
|
$.post(base_url + 'api/admin/scan', function (data) {
|
||||||
var ms = data.milliseconds;
|
var ms = data.milliseconds;
|
||||||
var titles = data.titles;
|
var titles = data.titles;
|
||||||
$('#scan-status > span').text('Scanned ' + titles + ' titles in ' + ms + 'ms');
|
$('#scan-status > span').text('Scanned ' + titles + ' titles in ' + ms + 'ms');
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const loadConfig = () => {
|
|||||||
globalConfig.autoRefresh = $('#auto-refresh').prop('checked');
|
globalConfig.autoRefresh = $('#auto-refresh').prop('checked');
|
||||||
};
|
};
|
||||||
const remove = (id) => {
|
const remove = (id) => {
|
||||||
var url = '/api/admin/mangadex/queue/delete';
|
var url = base_url + 'api/admin/mangadex/queue/delete';
|
||||||
if (id !== undefined)
|
if (id !== undefined)
|
||||||
url += '?' + $.param({id: id});
|
url += '?' + $.param({id: id});
|
||||||
console.log(url);
|
console.log(url);
|
||||||
@@ -43,7 +43,7 @@ const remove = (id) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const refresh = (id) => {
|
const refresh = (id) => {
|
||||||
var url = '/api/admin/mangadex/queue/retry';
|
var url = base_url + 'api/admin/mangadex/queue/retry';
|
||||||
if (id !== undefined)
|
if (id !== undefined)
|
||||||
url += '?' + $.param({id: id});
|
url += '?' + $.param({id: id});
|
||||||
console.log(url);
|
console.log(url);
|
||||||
@@ -67,7 +67,7 @@ const toggle = () => {
|
|||||||
$('#pause-resume-btn').attr('disabled', '');
|
$('#pause-resume-btn').attr('disabled', '');
|
||||||
const paused = $('#pause-resume-btn').text() === 'Resume download';
|
const paused = $('#pause-resume-btn').text() === 'Resume download';
|
||||||
const action = paused ? 'resume' : 'pause';
|
const action = paused ? 'resume' : 'pause';
|
||||||
const url = `/api/admin/mangadex/queue/${action}`;
|
const url = `${base_url}api/admin/mangadex/queue/${action}`;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: url,
|
url: url,
|
||||||
@@ -87,7 +87,7 @@ const load = () => {
|
|||||||
console.log('fetching');
|
console.log('fetching');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: '/api/admin/mangadex/queue',
|
url: base_url + 'api/admin/mangadex/queue',
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done(data => {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const download = () => {
|
|||||||
console.log(ids);
|
console.log(ids);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/api/admin/mangadex/download',
|
url: base_url + 'api/admin/mangadex/download',
|
||||||
data: JSON.stringify({chapters: chapters}),
|
data: JSON.stringify({chapters: chapters}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
@@ -47,7 +47,7 @@ const download = () => {
|
|||||||
const successCount = parseInt(data.success);
|
const successCount = parseInt(data.success);
|
||||||
const failCount = parseInt(data.fail);
|
const failCount = parseInt(data.fail);
|
||||||
UIkit.modal.confirm(`${successCount} of ${successCount + failCount} chapters added to the download queue. Proceed to the download manager?`).then(() => {
|
UIkit.modal.confirm(`${successCount} of ${successCount + failCount} chapters added to the download queue. Proceed to the download manager?`).then(() => {
|
||||||
window.location.href = '/admin/downloads';
|
window.location.href = base_url + 'admin/downloads';
|
||||||
});
|
});
|
||||||
styleModal();
|
styleModal();
|
||||||
})
|
})
|
||||||
@@ -109,7 +109,7 @@ const search = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.getJSON("/api/admin/mangadex/manga/" + int_id)
|
$.getJSON(`${base_url}api/admin/mangadex/manga/${int_id}`)
|
||||||
.done((data) => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', 'Failed to get manga info. Error: ' + data.error);
|
alert('danger', 'Failed to get manga info. Error: ' + data.error);
|
||||||
|
|||||||
+5
-5
@@ -22,8 +22,8 @@ function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTi
|
|||||||
$('#path-text').text(zipPath);
|
$('#path-text').text(zipPath);
|
||||||
$('#pages-text').text(pages + ' pages');
|
$('#pages-text').text(pages + ' pages');
|
||||||
|
|
||||||
$('#beginning-btn').attr('href', '/reader/' + titleID + '/' + entryID + '/1');
|
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
|
||||||
$('#continue-btn').attr('href', '/reader/' + titleID + '/' + entryID);
|
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
|
||||||
|
|
||||||
$('#read-btn').click(function(){
|
$('#read-btn').click(function(){
|
||||||
updateProgress(titleID, entryID, pages);
|
updateProgress(titleID, entryID, pages);
|
||||||
@@ -39,7 +39,7 @@ function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateProgress = (tid, eid, page) => {
|
const updateProgress = (tid, eid, page) => {
|
||||||
let url = `/api/progress/${tid}/${page}`
|
let url = `${base_url}api/progress/${tid}/${page}`
|
||||||
const query = $.param({entry: eid});
|
const query = $.param({entry: eid});
|
||||||
if (eid)
|
if (eid)
|
||||||
url += `?${query}`;
|
url += `?${query}`;
|
||||||
@@ -66,7 +66,7 @@ const renameSubmit = (name, eid) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const query = $.param({ entry: eid });
|
const query = $.param({ entry: eid });
|
||||||
let url = `/api/admin/display_name/${titleId}/${name}`;
|
let url = `${base_url}api/admin/display_name/${titleId}/${name}`;
|
||||||
if (eid)
|
if (eid)
|
||||||
url += `?${query}`;
|
url += `?${query}`;
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ const setupUpload = (eid) => {
|
|||||||
if (eid)
|
if (eid)
|
||||||
queryObj['entry'] = eid;
|
queryObj['entry'] = eid;
|
||||||
const query = $.param(queryObj);
|
const query = $.param(queryObj);
|
||||||
const url = `/api/admin/upload/cover?${query}`;
|
const url = `${base_url}api/admin/upload/cover?${query}`;
|
||||||
console.log(url);
|
console.log(url);
|
||||||
UIkit.upload('.upload-field', {
|
UIkit.upload('.upload-field', {
|
||||||
url: url,
|
url: url,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
var target = '/admin/user/edit';
|
var target = base_url + 'admin/user/edit';
|
||||||
if (username) target += username;
|
if (username) target += username;
|
||||||
$('form').attr('action', target);
|
$('form').attr('action', target);
|
||||||
if (error) alert('danger', error);
|
if (error) alert('danger', error);
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
function remove(username) {
|
function remove(username) {
|
||||||
$.post('/api/admin/user/delete/' + username, function(data) {
|
$.post(base_url + 'api/admin/user/delete/' + username, function(data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: mango
|
name: mango
|
||||||
version: 0.3.0
|
version: 0.4.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Alex Ling <hkalexling@gmail.com>
|
- Alex Ling <hkalexling@gmail.com>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ require "./spec_helper"
|
|||||||
|
|
||||||
describe Config do
|
describe Config do
|
||||||
it "creates config if it does not exist" do
|
it "creates config if it does not exist" do
|
||||||
with_default_config do |_, _, path|
|
with_default_config do |_, path|
|
||||||
File.exists?(path).should be_true
|
File.exists?(path).should be_true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
require "./spec_helper"
|
||||||
|
require "../src/rename"
|
||||||
|
|
||||||
|
include Rename
|
||||||
|
|
||||||
|
describe Rule do
|
||||||
|
it "raises on nested brackets" do
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "[[]]"
|
||||||
|
end
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "{{}}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises on unclosed brackets" do
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "["
|
||||||
|
end
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "{"
|
||||||
|
end
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "[{]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises when closing unopened brackets" do
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "]"
|
||||||
|
end
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "[}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles `|` in patterns" do
|
||||||
|
rule = Rule.new "{a|b|c}"
|
||||||
|
rule.render({"b" => "b"}).should eq "b"
|
||||||
|
rule.render({"a" => "a", "b" => "b"}).should eq "a"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows `|` outside of patterns" do
|
||||||
|
rule = Rule.new "hello|world"
|
||||||
|
rule.render({} of String => String).should eq "hello|world"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises on escaped characters" do
|
||||||
|
expect_raises Exception do
|
||||||
|
Rule.new "hello/world"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles spaces in patterns" do
|
||||||
|
rule = Rule.new "{ a }"
|
||||||
|
rule.render({"a" => "a"}).should eq "a"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "strips leading and tailing spaces" do
|
||||||
|
rule = Rule.new " hello "
|
||||||
|
rule.render({"a" => "a"}).should eq "hello"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "renders a few examples correctly" do
|
||||||
|
rule = Rule.new "[Ch. {chapter }] {title | id} testing"
|
||||||
|
rule.render({"id" => "ID"}).should eq "ID testing"
|
||||||
|
rule.render({"chapter" => "CH", "id" => "ID"})
|
||||||
|
.should eq "Ch. CH ID testing"
|
||||||
|
rule.render({} of String => String).should eq "testing"
|
||||||
|
end
|
||||||
|
end
|
||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
require "spec"
|
require "spec"
|
||||||
require "../src/context"
|
|
||||||
require "../src/server"
|
require "../src/server"
|
||||||
|
require "../src/config"
|
||||||
|
|
||||||
class State
|
class State
|
||||||
@@hash = {} of String => String
|
@@hash = {} of String => String
|
||||||
@@ -37,15 +37,15 @@ end
|
|||||||
def with_default_config
|
def with_default_config
|
||||||
temp_config = get_tempfile "mango-test-config"
|
temp_config = get_tempfile "mango-test-config"
|
||||||
config = Config.load temp_config.path
|
config = Config.load temp_config.path
|
||||||
logger = Logger.new config.log_level
|
config.set_current
|
||||||
yield config, logger, temp_config.path
|
yield config, temp_config.path
|
||||||
temp_config.delete
|
temp_config.delete
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_storage
|
def with_storage
|
||||||
with_default_config do |_, logger|
|
with_default_config do
|
||||||
temp_db = get_tempfile "mango-test-db"
|
temp_db = get_tempfile "mango-test-db"
|
||||||
storage = Storage.new temp_db.path, logger
|
storage = Storage.new temp_db.path
|
||||||
clear = yield storage, temp_db.path
|
clear = yield storage, temp_db.path
|
||||||
if clear == true
|
if clear == true
|
||||||
temp_db.delete
|
temp_db.delete
|
||||||
@@ -54,9 +54,9 @@ def with_storage
|
|||||||
end
|
end
|
||||||
|
|
||||||
def with_queue
|
def with_queue
|
||||||
with_default_config do |_, logger|
|
with_default_config do
|
||||||
temp_queue_db = get_tempfile "mango-test-queue-db"
|
temp_queue_db = get_tempfile "mango-test-queue-db"
|
||||||
queue = MangaDex::Queue.new temp_queue_db.path, logger
|
queue = MangaDex::Queue.new temp_queue_db.path
|
||||||
clear = yield queue, temp_queue_db.path
|
clear = yield queue, temp_queue_db.path
|
||||||
if clear == true
|
if clear == true
|
||||||
temp_queue_db.delete
|
temp_queue_db.delete
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class Config
|
|||||||
include YAML::Serializable
|
include YAML::Serializable
|
||||||
|
|
||||||
property port : Int32 = 9000
|
property port : Int32 = 9000
|
||||||
|
property base_url : String = "/"
|
||||||
property library_path : String = File.expand_path "~/mango/library",
|
property library_path : String = File.expand_path "~/mango/library",
|
||||||
home: true
|
home: true
|
||||||
property db_path : String = File.expand_path "~/mango/mango.db", home: true
|
property db_path : String = File.expand_path "~/mango/mango.db", home: true
|
||||||
@@ -22,13 +23,26 @@ class Config
|
|||||||
"download_retries" => 4,
|
"download_retries" => 4,
|
||||||
"download_queue_db_path" => File.expand_path("~/mango/queue.db",
|
"download_queue_db_path" => File.expand_path("~/mango/queue.db",
|
||||||
home: true),
|
home: true),
|
||||||
|
"chapter_rename_rule" => "[Vol.{volume} ][Ch.{chapter} ]{title|id}",
|
||||||
|
"manga_rename_rule" => "{title}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@singlet : Config?
|
||||||
|
|
||||||
|
def self.current
|
||||||
|
@@singlet.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_current
|
||||||
|
@@singlet = self
|
||||||
|
end
|
||||||
|
|
||||||
def self.load(path : String?)
|
def self.load(path : String?)
|
||||||
path = "~/.config/mango/config.yml" if path.nil?
|
path = "~/.config/mango/config.yml" if path.nil?
|
||||||
cfg_path = File.expand_path path, home: true
|
cfg_path = File.expand_path path, home: true
|
||||||
if File.exists? cfg_path
|
if File.exists? cfg_path
|
||||||
config = self.from_yaml File.read cfg_path
|
config = self.from_yaml File.read cfg_path
|
||||||
|
config.preprocess
|
||||||
config.fill_defaults
|
config.fill_defaults
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
@@ -58,4 +72,13 @@ class Config
|
|||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preprocess
|
||||||
|
unless base_url.starts_with? "/"
|
||||||
|
raise "base url (#{base_url}) should start with `/`"
|
||||||
|
end
|
||||||
|
unless base_url.ends_with? "/"
|
||||||
|
@base_url += "/"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
require "./config"
|
|
||||||
require "./library"
|
|
||||||
require "./storage"
|
|
||||||
require "./logger"
|
|
||||||
|
|
||||||
class Context
|
|
||||||
property config : Config
|
|
||||||
property library : Library
|
|
||||||
property storage : Storage
|
|
||||||
property logger : Logger
|
|
||||||
property queue : MangaDex::Queue
|
|
||||||
|
|
||||||
def initialize(@config, @logger, @library, @storage, @queue)
|
|
||||||
end
|
|
||||||
|
|
||||||
{% for lvl in Logger::LEVELS %}
|
|
||||||
def {{lvl.id}}(msg)
|
|
||||||
@logger.{{lvl.id}} msg
|
|
||||||
end
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
@@ -11,7 +11,7 @@ class AuthHandler < Kemal::Handler
|
|||||||
|
|
||||||
cookie = env.request.cookies.find { |c| c.name == "token" }
|
cookie = env.request.cookies.find { |c| c.name == "token" }
|
||||||
if cookie.nil? || !@storage.verify_token cookie.value
|
if cookie.nil? || !@storage.verify_token cookie.value
|
||||||
return env.redirect "/login"
|
return redirect env, "/login"
|
||||||
end
|
end
|
||||||
|
|
||||||
if request_path_startswith env, ["/admin", "/api/admin", "/download"]
|
if request_path_startswith env, ["/admin", "/api/admin", "/download"]
|
||||||
|
|||||||
@@ -2,20 +2,17 @@ require "kemal"
|
|||||||
require "../logger"
|
require "../logger"
|
||||||
|
|
||||||
class LogHandler < Kemal::BaseLogHandler
|
class LogHandler < Kemal::BaseLogHandler
|
||||||
def initialize(@logger : Logger)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
elapsed_time = Time.measure { call_next env }
|
elapsed_time = Time.measure { call_next env }
|
||||||
elapsed_text = elapsed_text elapsed_time
|
elapsed_text = elapsed_text elapsed_time
|
||||||
msg = "#{env.response.status_code} #{env.request.method}" \
|
msg = "#{env.response.status_code} #{env.request.method}" \
|
||||||
" #{env.request.resource} #{elapsed_text}"
|
" #{env.request.resource} #{elapsed_text}"
|
||||||
@logger.debug msg
|
Logger.debug msg
|
||||||
env
|
env
|
||||||
end
|
end
|
||||||
|
|
||||||
def write(msg)
|
def write(msg)
|
||||||
@logger.debug msg
|
Logger.debug msg
|
||||||
end
|
end
|
||||||
|
|
||||||
private def elapsed_text(elapsed)
|
private def elapsed_text(elapsed)
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ class UploadHandler < Kemal::Handler
|
|||||||
return call_next env
|
return call_next env
|
||||||
end
|
end
|
||||||
|
|
||||||
ary = env.request.path.split(File::SEPARATOR).select { |part| !part.empty? }
|
ary = env.request.path.split(File::SEPARATOR).select do |part|
|
||||||
|
!part.empty?
|
||||||
|
end
|
||||||
ary[0] = @upload_dir
|
ary[0] = @upload_dir
|
||||||
path = File.join ary
|
path = File.join ary
|
||||||
|
|
||||||
|
|||||||
+21
-11
@@ -57,7 +57,7 @@ class Entry
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cover_url
|
def cover_url
|
||||||
url = "/api/page/#{@title_id}/#{@id}/1"
|
url = "#{Config.current.base_url}api/page/#{@title_id}/#{@id}/1"
|
||||||
TitleInfo.new @book.dir do |info|
|
TitleInfo.new @book.dir do |info|
|
||||||
info_url = info.entry_cover_url[@title]?
|
info_url = info.entry_cover_url[@title]?
|
||||||
unless info_url.nil? || info_url.empty?
|
unless info_url.nil? || info_url.empty?
|
||||||
@@ -97,7 +97,7 @@ class Title
|
|||||||
encoded_title : String, mtime : Time
|
encoded_title : String, mtime : Time
|
||||||
|
|
||||||
def initialize(@dir : String, @parent_id, storage,
|
def initialize(@dir : String, @parent_id, storage,
|
||||||
@logger : Logger, @library : Library)
|
@library : Library)
|
||||||
@id = storage.get_id @dir, true
|
@id = storage.get_id @dir, true
|
||||||
@title = File.basename dir
|
@title = File.basename dir
|
||||||
@encoded_title = URI.encode @title
|
@encoded_title = URI.encode @title
|
||||||
@@ -109,7 +109,7 @@ class Title
|
|||||||
next if fn.starts_with? "."
|
next if fn.starts_with? "."
|
||||||
path = File.join dir, fn
|
path = File.join dir, fn
|
||||||
if File.directory? path
|
if File.directory? path
|
||||||
title = Title.new path, @id, storage, @logger, library
|
title = Title.new path, @id, storage, library
|
||||||
next if title.entries.size == 0 && title.titles.size == 0
|
next if title.entries.size == 0 && title.titles.size == 0
|
||||||
@library.title_hash[title.id] = title
|
@library.title_hash[title.id] = title
|
||||||
@title_ids << title.id
|
@title_ids << title.id
|
||||||
@@ -118,9 +118,9 @@ class Title
|
|||||||
if [".zip", ".cbz"].includes? File.extname path
|
if [".zip", ".cbz"].includes? File.extname path
|
||||||
zip_exception = validate_zip path
|
zip_exception = validate_zip path
|
||||||
unless zip_exception.nil?
|
unless zip_exception.nil?
|
||||||
@logger.warn "File #{path} is corrupted or is not a valid zip " \
|
Logger.warn "File #{path} is corrupted or is not a valid zip " \
|
||||||
"archive. Ignoring it."
|
"archive. Ignoring it."
|
||||||
@logger.debug "Zip error: #{zip_exception}"
|
Logger.debug "Zip error: #{zip_exception}"
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
entry = Entry.new path, self, @id, storage
|
entry = Entry.new path, self, @id, storage
|
||||||
@@ -367,9 +367,19 @@ end
|
|||||||
|
|
||||||
class Library
|
class Library
|
||||||
property dir : String, title_ids : Array(String), scan_interval : Int32,
|
property dir : String, title_ids : Array(String), scan_interval : Int32,
|
||||||
logger : Logger, storage : Storage, title_hash : Hash(String, Title)
|
storage : Storage, title_hash : Hash(String, Title)
|
||||||
|
|
||||||
def initialize(@dir, @scan_interval, @logger, @storage)
|
def self.default : self
|
||||||
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@storage = Storage.default
|
||||||
|
@dir = Config.current.library_path
|
||||||
|
@scan_interval = Config.current.scan_interval
|
||||||
# explicitly initialize @titles to bypass the compiler check. it will
|
# explicitly initialize @titles to bypass the compiler check. it will
|
||||||
# be filled with actual Titles in the `scan` call below
|
# be filled with actual Titles in the `scan` call below
|
||||||
@title_ids = [] of String
|
@title_ids = [] of String
|
||||||
@@ -381,7 +391,7 @@ class Library
|
|||||||
start = Time.local
|
start = Time.local
|
||||||
scan
|
scan
|
||||||
ms = (Time.local - start).total_milliseconds
|
ms = (Time.local - start).total_milliseconds
|
||||||
@logger.info "Scanned #{@title_ids.size} titles in #{ms}ms"
|
Logger.info "Scanned #{@title_ids.size} titles in #{ms}ms"
|
||||||
sleep @scan_interval * 60
|
sleep @scan_interval * 60
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -410,7 +420,7 @@ class Library
|
|||||||
|
|
||||||
def scan
|
def scan
|
||||||
unless Dir.exists? @dir
|
unless Dir.exists? @dir
|
||||||
@logger.info "The library directory #{@dir} does not exist. " \
|
Logger.info "The library directory #{@dir} does not exist. " \
|
||||||
"Attempting to create it"
|
"Attempting to create it"
|
||||||
Dir.mkdir_p @dir
|
Dir.mkdir_p @dir
|
||||||
end
|
end
|
||||||
@@ -419,13 +429,13 @@ class Library
|
|||||||
.select { |fn| !fn.starts_with? "." }
|
.select { |fn| !fn.starts_with? "." }
|
||||||
.map { |fn| File.join @dir, fn }
|
.map { |fn| File.join @dir, fn }
|
||||||
.select { |path| File.directory? path }
|
.select { |path| File.directory? path }
|
||||||
.map { |path| Title.new path, "", @storage, @logger, self }
|
.map { |path| Title.new path, "", @storage, self }
|
||||||
.select { |title| !(title.entries.empty? && title.titles.empty?) }
|
.select { |title| !(title.entries.empty? && title.titles.empty?) }
|
||||||
.sort { |a, b| a.title <=> b.title }
|
.sort { |a, b| a.title <=> b.title }
|
||||||
.each do |title|
|
.each do |title|
|
||||||
@title_hash[title.id] = title
|
@title_hash[title.id] = title
|
||||||
@title_ids << title.id
|
@title_ids << title.id
|
||||||
end
|
end
|
||||||
@logger.debug "Scan completed"
|
Logger.debug "Scan completed"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+16
-1
@@ -8,7 +8,15 @@ class Logger
|
|||||||
|
|
||||||
@@severity : Log::Severity = :info
|
@@severity : Log::Severity = :info
|
||||||
|
|
||||||
def initialize(level : String)
|
def self.default : self
|
||||||
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
level = Config.current.log_level
|
||||||
{% begin %}
|
{% begin %}
|
||||||
case level.downcase
|
case level.downcase
|
||||||
when "off"
|
when "off"
|
||||||
@@ -50,9 +58,16 @@ class Logger
|
|||||||
@backend.write Log::Entry.new "", Log::Severity::None, msg, nil
|
@backend.write Log::Entry.new "", Log::Severity::None, msg, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.log(msg)
|
||||||
|
default.log msg
|
||||||
|
end
|
||||||
|
|
||||||
{% for lvl in LEVELS %}
|
{% for lvl in LEVELS %}
|
||||||
def {{lvl.id}}(msg)
|
def {{lvl.id}}(msg)
|
||||||
@log.{{lvl.id}} { msg }
|
@log.{{lvl.id}} { msg }
|
||||||
end
|
end
|
||||||
|
def self.{{lvl.id}}(msg)
|
||||||
|
default.not_nil!.{{lvl.id}} msg
|
||||||
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|||||||
+34
-8
@@ -1,6 +1,7 @@
|
|||||||
require "http/client"
|
require "http/client"
|
||||||
require "json"
|
require "json"
|
||||||
require "csv"
|
require "csv"
|
||||||
|
require "../rename"
|
||||||
|
|
||||||
macro string_properties(names)
|
macro string_properties(names)
|
||||||
{% for name in names %}
|
{% for name in names %}
|
||||||
@@ -14,6 +15,14 @@ macro parse_strings_from_json(names)
|
|||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
macro properties_to_hash(names)
|
||||||
|
{
|
||||||
|
{% for name in names %}
|
||||||
|
"{{name.id}}" => @{{name.id}}.to_s,
|
||||||
|
{% end %}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
module MangaDex
|
module MangaDex
|
||||||
class Chapter
|
class Chapter
|
||||||
string_properties ["lang_code", "title", "volume", "chapter"]
|
string_properties ["lang_code", "title", "volume", "chapter"]
|
||||||
@@ -64,16 +73,20 @@ module MangaDex
|
|||||||
gname = obj["group_name#{s}"].as_s
|
gname = obj["group_name#{s}"].as_s
|
||||||
@groups << {gid, gname}
|
@groups << {gid, gname}
|
||||||
end
|
end
|
||||||
@full_title = @title
|
|
||||||
unless @chapter.empty?
|
rename_rule = Rename::Rule.new \
|
||||||
@full_title = "Ch.#{@chapter} " + @full_title
|
Config.current.mangadex["chapter_rename_rule"].to_s
|
||||||
end
|
@full_title = rename rename_rule
|
||||||
unless @volume.empty?
|
|
||||||
@full_title = "Vol.#{@volume} " + @full_title
|
|
||||||
end
|
|
||||||
rescue e
|
rescue e
|
||||||
raise "failed to parse json: #{e}"
|
raise "failed to parse json: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rename(rule : Rename::Rule)
|
||||||
|
hash = properties_to_hash ["id", "title", "volume", "chapter",
|
||||||
|
"lang_code", "language", "pages"]
|
||||||
|
hash["groups"] = @groups.map { |g| g[1] }.join ","
|
||||||
|
rule.render hash
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Manga
|
class Manga
|
||||||
@@ -111,10 +124,23 @@ module MangaDex
|
|||||||
rescue e
|
rescue e
|
||||||
raise "failed to parse json: #{e}"
|
raise "failed to parse json: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rename(rule : Rename::Rule)
|
||||||
|
rule.render properties_to_hash ["id", "title", "author", "artist"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class API
|
class API
|
||||||
def initialize(@base_url = "https://mangadex.org/api/")
|
def self.default : self
|
||||||
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@base_url = Config.current.mangadex["api_url"].to_s ||
|
||||||
|
"https://mangadex.org/api/"
|
||||||
@lang = {} of String => String
|
@lang = {} of String => String
|
||||||
CSV.each_row {{read_file "src/assets/lang_codes.csv"}} do |row|
|
CSV.each_row {{read_file "src/assets/lang_codes.csv"}} do |row|
|
||||||
@lang[row[1]] = row[0]
|
@lang[row[1]] = row[0]
|
||||||
|
|||||||
+41
-18
@@ -1,5 +1,6 @@
|
|||||||
require "./api"
|
require "./api"
|
||||||
require "sqlite3"
|
require "sqlite3"
|
||||||
|
require "zip"
|
||||||
|
|
||||||
module MangaDex
|
module MangaDex
|
||||||
class PageJob
|
class PageJob
|
||||||
@@ -79,11 +80,20 @@ module MangaDex
|
|||||||
|
|
||||||
class Queue
|
class Queue
|
||||||
property downloader : Downloader?
|
property downloader : Downloader?
|
||||||
|
@path : String
|
||||||
|
|
||||||
def initialize(@path : String, @logger : Logger)
|
def self.default : self
|
||||||
dir = File.dirname path
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(db_path : String? = nil)
|
||||||
|
@path = db_path || Config.current.mangadex["download_queue_db_path"].to_s
|
||||||
|
dir = File.dirname @path
|
||||||
unless Dir.exists? dir
|
unless Dir.exists? dir
|
||||||
@logger.info "The queue DB directory #{dir} does not exist. " \
|
Logger.info "The queue DB directory #{dir} does not exist. " \
|
||||||
"Attepmting to create it"
|
"Attepmting to create it"
|
||||||
Dir.mkdir_p dir
|
Dir.mkdir_p dir
|
||||||
end
|
end
|
||||||
@@ -101,7 +111,7 @@ module MangaDex
|
|||||||
db.exec "create index if not exists status_idx " \
|
db.exec "create index if not exists status_idx " \
|
||||||
"on queue (status)"
|
"on queue (status)"
|
||||||
rescue e
|
rescue e
|
||||||
@logger.error "Error when checking tables in DB: #{e}"
|
Logger.error "Error when checking tables in DB: #{e}"
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -254,11 +264,22 @@ module MangaDex
|
|||||||
|
|
||||||
class Downloader
|
class Downloader
|
||||||
property stopped = false
|
property stopped = false
|
||||||
|
@wait_seconds : Int32 = Config.current.mangadex["download_wait_seconds"]
|
||||||
|
.to_i32
|
||||||
|
@retries : Int32 = Config.current.mangadex["download_retries"].to_i32
|
||||||
|
@library_path : String = Config.current.library_path
|
||||||
@downloading = false
|
@downloading = false
|
||||||
|
|
||||||
def initialize(@queue : Queue, @api : API, @library_path : String,
|
def self.default : self
|
||||||
@wait_seconds : Int32, @retries : Int32,
|
unless @@default
|
||||||
@logger : Logger)
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@queue = Queue.default
|
||||||
|
@api = API.default
|
||||||
@queue.downloader = self
|
@queue.downloader = self
|
||||||
|
|
||||||
spawn do
|
spawn do
|
||||||
@@ -270,7 +291,7 @@ module MangaDex
|
|||||||
next if job.nil?
|
next if job.nil?
|
||||||
download job
|
download job
|
||||||
rescue e
|
rescue e
|
||||||
@logger.error e
|
Logger.error e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -282,7 +303,7 @@ module MangaDex
|
|||||||
begin
|
begin
|
||||||
chapter = @api.get_chapter(job.id)
|
chapter = @api.get_chapter(job.id)
|
||||||
rescue e
|
rescue e
|
||||||
@logger.error e
|
Logger.error e
|
||||||
@queue.set_status JobStatus::Error, job
|
@queue.set_status JobStatus::Error, job
|
||||||
unless e.message.nil?
|
unless e.message.nil?
|
||||||
@queue.add_message e.message.not_nil!, job
|
@queue.add_message e.message.not_nil!, job
|
||||||
@@ -292,7 +313,9 @@ module MangaDex
|
|||||||
end
|
end
|
||||||
@queue.set_pages chapter.pages.size, job
|
@queue.set_pages chapter.pages.size, job
|
||||||
lib_dir = @library_path
|
lib_dir = @library_path
|
||||||
manga_dir = File.join lib_dir, chapter.manga.title
|
rename_rule = Rename::Rule.new \
|
||||||
|
Config.current.mangadex["manga_rename_rule"].to_s
|
||||||
|
manga_dir = File.join lib_dir, chapter.manga.rename rename_rule
|
||||||
unless File.exists? manga_dir
|
unless File.exists? manga_dir
|
||||||
Dir.mkdir_p manga_dir
|
Dir.mkdir_p manga_dir
|
||||||
end
|
end
|
||||||
@@ -310,14 +333,14 @@ module MangaDex
|
|||||||
ext = File.extname fn
|
ext = File.extname fn
|
||||||
fn = "#{i.to_s.rjust len, '0'}#{ext}"
|
fn = "#{i.to_s.rjust len, '0'}#{ext}"
|
||||||
page_job = PageJob.new url, fn, writer, @retries
|
page_job = PageJob.new url, fn, writer, @retries
|
||||||
@logger.debug "Downloading #{url}"
|
Logger.debug "Downloading #{url}"
|
||||||
loop do
|
loop do
|
||||||
sleep @wait_seconds.seconds
|
sleep @wait_seconds.seconds
|
||||||
download_page page_job
|
download_page page_job
|
||||||
break if page_job.success ||
|
break if page_job.success ||
|
||||||
page_job.tries_remaning <= 0
|
page_job.tries_remaning <= 0
|
||||||
page_job.tries_remaning -= 1
|
page_job.tries_remaning -= 1
|
||||||
@logger.warn "Failed to download page #{url}. " \
|
Logger.warn "Failed to download page #{url}. " \
|
||||||
"Retrying... Remaining retries: " \
|
"Retrying... Remaining retries: " \
|
||||||
"#{page_job.tries_remaning}"
|
"#{page_job.tries_remaning}"
|
||||||
end
|
end
|
||||||
@@ -330,7 +353,7 @@ module MangaDex
|
|||||||
page_jobs = [] of PageJob
|
page_jobs = [] of PageJob
|
||||||
chapter.pages.size.times do
|
chapter.pages.size.times do
|
||||||
page_job = channel.receive
|
page_job = channel.receive
|
||||||
@logger.debug "[#{page_job.success ? "success" : "failed"}] " \
|
Logger.debug "[#{page_job.success ? "success" : "failed"}] " \
|
||||||
"#{page_job.url}"
|
"#{page_job.url}"
|
||||||
page_jobs << page_job
|
page_jobs << page_job
|
||||||
if page_job.success
|
if page_job.success
|
||||||
@@ -339,14 +362,14 @@ module MangaDex
|
|||||||
@queue.add_fail job
|
@queue.add_fail job
|
||||||
msg = "Failed to download page #{page_job.url}"
|
msg = "Failed to download page #{page_job.url}"
|
||||||
@queue.add_message msg, job
|
@queue.add_message msg, job
|
||||||
@logger.error msg
|
Logger.error msg
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
fail_count = page_jobs.count { |j| !j.success }
|
fail_count = page_jobs.count { |j| !j.success }
|
||||||
@logger.debug "Download completed. " \
|
Logger.debug "Download completed. " \
|
||||||
"#{fail_count}/#{page_jobs.size} failed"
|
"#{fail_count}/#{page_jobs.size} failed"
|
||||||
writer.close
|
writer.close
|
||||||
@logger.debug "cbz File created at #{zip_path}"
|
Logger.debug "cbz File created at #{zip_path}"
|
||||||
|
|
||||||
zip_exception = validate_zip zip_path
|
zip_exception = validate_zip zip_path
|
||||||
if !zip_exception.nil?
|
if !zip_exception.nil?
|
||||||
@@ -363,7 +386,7 @@ module MangaDex
|
|||||||
end
|
end
|
||||||
|
|
||||||
private def download_page(job : PageJob)
|
private def download_page(job : PageJob)
|
||||||
@logger.debug "downloading #{job.url}"
|
Logger.debug "downloading #{job.url}"
|
||||||
headers = HTTP::Headers{
|
headers = HTTP::Headers{
|
||||||
"User-agent" => "Mangadex.cr",
|
"User-agent" => "Mangadex.cr",
|
||||||
}
|
}
|
||||||
@@ -377,7 +400,7 @@ module MangaDex
|
|||||||
end
|
end
|
||||||
job.success = true
|
job.success = true
|
||||||
rescue e
|
rescue e
|
||||||
@logger.error e
|
Logger.error e
|
||||||
job.success = false
|
job.success = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+7
-16
@@ -1,9 +1,9 @@
|
|||||||
|
require "./config"
|
||||||
require "./server"
|
require "./server"
|
||||||
require "./context"
|
|
||||||
require "./mangadex/*"
|
require "./mangadex/*"
|
||||||
require "option_parser"
|
require "option_parser"
|
||||||
|
|
||||||
VERSION = "0.3.0"
|
VERSION = "0.4.0"
|
||||||
|
|
||||||
config_path = nil
|
config_path = nil
|
||||||
|
|
||||||
@@ -19,23 +19,14 @@ OptionParser.parse do |parser|
|
|||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
parser.on "-c PATH", "--config=PATH",
|
parser.on "-c PATH", "--config=PATH",
|
||||||
"Path to the config file. Default is `~/.config/mango/config.yml`" do |path|
|
"Path to the config file. " \
|
||||||
|
"Default is `~/.config/mango/config.yml`" do |path|
|
||||||
config_path = path
|
config_path = path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
config = Config.load config_path
|
Config.load(config_path).set_current
|
||||||
logger = Logger.new config.log_level
|
MangaDex::Downloader.default
|
||||||
storage = Storage.new config.db_path, logger
|
|
||||||
library = Library.new config.library_path, config.scan_interval, logger, storage
|
|
||||||
queue = MangaDex::Queue.new config.mangadex["download_queue_db_path"].to_s,
|
|
||||||
logger
|
|
||||||
api = MangaDex::API.new config.mangadex["api_url"].to_s
|
|
||||||
MangaDex::Downloader.new queue, api, config.library_path,
|
|
||||||
config.mangadex["download_wait_seconds"].to_i,
|
|
||||||
config.mangadex["download_retries"].to_i, logger
|
|
||||||
|
|
||||||
context = Context.new config, logger, library, storage, queue
|
server = Server.new
|
||||||
|
|
||||||
server = Server.new context
|
|
||||||
server.start
|
server.start
|
||||||
|
|||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
module Rename
|
||||||
|
alias VHash = Hash(String, String)
|
||||||
|
|
||||||
|
abstract class Base(T)
|
||||||
|
@ary = [] of T
|
||||||
|
|
||||||
|
def push(var)
|
||||||
|
@ary.push var
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract def render(hash : VHash)
|
||||||
|
end
|
||||||
|
|
||||||
|
class Variable < Base(String)
|
||||||
|
property id : String
|
||||||
|
|
||||||
|
def initialize(@id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(hash : VHash)
|
||||||
|
hash[@id]? || ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Pattern < Base(Variable)
|
||||||
|
def render(hash : VHash)
|
||||||
|
@ary.each do |v|
|
||||||
|
if hash.has_key? v.id
|
||||||
|
return v.render hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Group < Base(Pattern | String)
|
||||||
|
def render(hash : VHash)
|
||||||
|
return "" if @ary.select(&.is_a? Pattern)
|
||||||
|
.any? &.as(Pattern).render(hash).empty?
|
||||||
|
@ary.map do |e|
|
||||||
|
if e.is_a? Pattern
|
||||||
|
e.render hash
|
||||||
|
else
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end.join
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Rule < Base(Group | String | Pattern)
|
||||||
|
ESCAPE = ['/']
|
||||||
|
|
||||||
|
def initialize(str : String)
|
||||||
|
parse! str
|
||||||
|
rescue e
|
||||||
|
raise "Failed to parse rename rule #{str}. Error: #{e}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private def parse!(str : String)
|
||||||
|
chars = [] of Char
|
||||||
|
pattern : Pattern? = nil
|
||||||
|
group : Group? = nil
|
||||||
|
|
||||||
|
str.each_char_with_index do |char, i|
|
||||||
|
if ['[', ']', '{', '}', '|'].includes?(char) && !chars.empty?
|
||||||
|
string = chars.join
|
||||||
|
if !pattern.nil?
|
||||||
|
pattern.push Variable.new string.strip
|
||||||
|
elsif !group.nil?
|
||||||
|
group.push string
|
||||||
|
else
|
||||||
|
@ary.push string
|
||||||
|
end
|
||||||
|
chars = [] of Char
|
||||||
|
end
|
||||||
|
|
||||||
|
case char
|
||||||
|
when '['
|
||||||
|
if !group.nil? || !pattern.nil?
|
||||||
|
raise "nested groups are not allowed"
|
||||||
|
end
|
||||||
|
group = Group.new
|
||||||
|
when ']'
|
||||||
|
if group.nil?
|
||||||
|
raise "unmatched ] at position #{i}"
|
||||||
|
end
|
||||||
|
if !pattern.nil?
|
||||||
|
raise "patterns (`{}`) should be closed before closing the " \
|
||||||
|
"group (`[]`)"
|
||||||
|
end
|
||||||
|
@ary.push group
|
||||||
|
group = nil
|
||||||
|
when '{'
|
||||||
|
if !pattern.nil?
|
||||||
|
raise "nested patterns are not allowed"
|
||||||
|
end
|
||||||
|
pattern = Pattern.new
|
||||||
|
when '}'
|
||||||
|
if pattern.nil?
|
||||||
|
raise "unmatched } at position #{i}"
|
||||||
|
end
|
||||||
|
if !group.nil?
|
||||||
|
group.push pattern
|
||||||
|
else
|
||||||
|
@ary.push pattern
|
||||||
|
end
|
||||||
|
pattern = nil
|
||||||
|
when '|'
|
||||||
|
if pattern.nil?
|
||||||
|
chars.push char
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if ESCAPE.includes? char
|
||||||
|
raise "the character #{char} at position #{i} is not allowed"
|
||||||
|
end
|
||||||
|
chars.push char
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless chars.empty?
|
||||||
|
@ary.push chars.join
|
||||||
|
end
|
||||||
|
if !pattern.nil?
|
||||||
|
raise "unclosed pattern {"
|
||||||
|
end
|
||||||
|
if !group.nil?
|
||||||
|
raise "unclosed group ["
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(hash : VHash)
|
||||||
|
@ary.map do |e|
|
||||||
|
if e.is_a? String
|
||||||
|
e
|
||||||
|
else
|
||||||
|
e.render hash
|
||||||
|
end
|
||||||
|
end.join.strip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
require "./router"
|
require "./router"
|
||||||
|
|
||||||
class AdminRouter < Router
|
class AdminRouter < Router
|
||||||
def setup
|
def initialize
|
||||||
get "/admin" do |env|
|
get "/admin" do |env|
|
||||||
layout "admin"
|
layout "admin"
|
||||||
end
|
end
|
||||||
@@ -48,13 +48,13 @@ class AdminRouter < Router
|
|||||||
|
|
||||||
@context.storage.new_user username, password, admin
|
@context.storage.new_user username, password, admin
|
||||||
|
|
||||||
env.redirect "/admin/user"
|
redirect env, "/admin/user"
|
||||||
rescue e
|
rescue e
|
||||||
@context.error e
|
@context.error e
|
||||||
redirect_url = URI.new \
|
redirect_url = URI.new \
|
||||||
path: "/admin/user/edit",
|
path: "/admin/user/edit",
|
||||||
query: hash_to_query({"error" => e.message})
|
query: hash_to_query({"error" => e.message})
|
||||||
env.redirect redirect_url.to_s
|
redirect env, redirect_url.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
post "/admin/user/edit/:original_username" do |env|
|
post "/admin/user/edit/:original_username" do |env|
|
||||||
@@ -85,18 +85,18 @@ class AdminRouter < Router
|
|||||||
@context.storage.update_user \
|
@context.storage.update_user \
|
||||||
original_username, username, password, admin
|
original_username, username, password, admin
|
||||||
|
|
||||||
env.redirect "/admin/user"
|
redirect env, "/admin/user"
|
||||||
rescue e
|
rescue e
|
||||||
@context.error e
|
@context.error e
|
||||||
redirect_url = URI.new \
|
redirect_url = URI.new \
|
||||||
path: "/admin/user/edit",
|
path: "/admin/user/edit",
|
||||||
query: hash_to_query({"username" => original_username, \
|
query: hash_to_query({"username" => original_username, \
|
||||||
"admin" => admin, "error" => e.message})
|
"admin" => admin, "error" => e.message})
|
||||||
env.redirect redirect_url.to_s
|
redirect env, redirect_url.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/admin/downloads" do |env|
|
get "/admin/downloads" do |env|
|
||||||
base_url = @context.config.mangadex["base_url"]
|
mangadex_base_url = Config.current.mangadex["base_url"]
|
||||||
layout "download-manager"
|
layout "download-manager"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@ require "../mangadex/*"
|
|||||||
require "../upload"
|
require "../upload"
|
||||||
|
|
||||||
class APIRouter < Router
|
class APIRouter < Router
|
||||||
def setup
|
def initialize
|
||||||
get "/api/page/:tid/:eid/:page" do |env|
|
get "/api/page/:tid/:eid/:page" do |env|
|
||||||
begin
|
begin
|
||||||
tid = env.params.url["tid"]
|
tid = env.params.url["tid"]
|
||||||
@@ -123,7 +123,7 @@ class APIRouter < Router
|
|||||||
get "/api/admin/mangadex/manga/:id" do |env|
|
get "/api/admin/mangadex/manga/:id" do |env|
|
||||||
begin
|
begin
|
||||||
id = env.params.url["id"]
|
id = env.params.url["id"]
|
||||||
api = MangaDex::API.new @context.config.mangadex["api_url"].to_s
|
api = MangaDex::API.default
|
||||||
manga = api.get_manga id
|
manga = api.get_manga id
|
||||||
send_json env, manga.to_info_json
|
send_json env, manga.to_info_json
|
||||||
rescue e
|
rescue e
|
||||||
@@ -230,7 +230,7 @@ class APIRouter < Router
|
|||||||
end
|
end
|
||||||
|
|
||||||
ext = File.extname filename
|
ext = File.extname filename
|
||||||
upload = Upload.new @context.config.upload_path, @context.logger
|
upload = Upload.new Config.current.upload_path
|
||||||
url = upload.path_to_url upload.save "img", ext, part.body
|
url = upload.path_to_url upload.save "img", ext, part.body
|
||||||
|
|
||||||
if url.nil?
|
if url.nil?
|
||||||
|
|||||||
+6
-5
@@ -1,8 +1,9 @@
|
|||||||
require "./router"
|
require "./router"
|
||||||
|
|
||||||
class MainRouter < Router
|
class MainRouter < Router
|
||||||
def setup
|
def initialize
|
||||||
get "/login" do |env|
|
get "/login" do |env|
|
||||||
|
base_url = Config.current.base_url
|
||||||
render "src/views/login.ecr"
|
render "src/views/login.ecr"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ class MainRouter < Router
|
|||||||
rescue e
|
rescue e
|
||||||
@context.error "Error when attempting to log out: #{e}"
|
@context.error "Error when attempting to log out: #{e}"
|
||||||
ensure
|
ensure
|
||||||
env.redirect "/login"
|
redirect env, "/login"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -26,9 +27,9 @@ class MainRouter < Router
|
|||||||
cookie = HTTP::Cookie.new "token", token
|
cookie = HTTP::Cookie.new "token", token
|
||||||
cookie.expires = Time.local.shift years: 1
|
cookie.expires = Time.local.shift years: 1
|
||||||
env.response.cookies << cookie
|
env.response.cookies << cookie
|
||||||
env.redirect "/"
|
redirect env, "/"
|
||||||
rescue
|
rescue
|
||||||
env.redirect "/login"
|
redirect env, "/login"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ class MainRouter < Router
|
|||||||
end
|
end
|
||||||
|
|
||||||
get "/download" do |env|
|
get "/download" do |env|
|
||||||
base_url = @context.config.mangadex["base_url"]
|
mangadex_base_url = Config.current.mangadex["base_url"]
|
||||||
layout "download"
|
layout "download"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
require "./router"
|
require "./router"
|
||||||
|
|
||||||
class ReaderRouter < Router
|
class ReaderRouter < Router
|
||||||
def setup
|
def initialize
|
||||||
get "/reader/:title/:entry" do |env|
|
get "/reader/:title/:entry" do |env|
|
||||||
begin
|
begin
|
||||||
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
||||||
@@ -15,7 +15,7 @@ class ReaderRouter < Router
|
|||||||
# might not have actually read them
|
# might not have actually read them
|
||||||
page = [page - 2 * IMGS_PER_PAGE, 1].max
|
page = [page - 2 * IMGS_PER_PAGE, 1].max
|
||||||
|
|
||||||
env.redirect "/reader/#{title.id}/#{entry.id}/#{page}"
|
redirect env, "/reader/#{title.id}/#{entry.id}/#{page}"
|
||||||
rescue e
|
rescue e
|
||||||
@context.error e
|
@context.error e
|
||||||
env.response.status_code = 404
|
env.response.status_code = 404
|
||||||
@@ -24,6 +24,8 @@ class ReaderRouter < Router
|
|||||||
|
|
||||||
get "/reader/:title/:entry/:page" do |env|
|
get "/reader/:title/:entry/:page" do |env|
|
||||||
begin
|
begin
|
||||||
|
base_url = Config.current.base_url
|
||||||
|
|
||||||
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
||||||
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
||||||
page = env.params.url["page"].to_i
|
page = env.params.url["page"].to_i
|
||||||
@@ -35,20 +37,20 @@ class ReaderRouter < Router
|
|||||||
|
|
||||||
pages = (page...[entry.pages + 1, page + IMGS_PER_PAGE].min)
|
pages = (page...[entry.pages + 1, page + IMGS_PER_PAGE].min)
|
||||||
urls = pages.map { |idx|
|
urls = pages.map { |idx|
|
||||||
"/api/page/#{title.id}/#{entry.id}/#{idx}"
|
"#{base_url}api/page/#{title.id}/#{entry.id}/#{idx}"
|
||||||
}
|
}
|
||||||
reader_urls = pages.map { |idx|
|
reader_urls = pages.map { |idx|
|
||||||
"/reader/#{title.id}/#{entry.id}/#{idx}"
|
"#{base_url}reader/#{title.id}/#{entry.id}/#{idx}"
|
||||||
}
|
}
|
||||||
next_page = page + IMGS_PER_PAGE
|
next_page = page + IMGS_PER_PAGE
|
||||||
next_url = next_entry_url = nil
|
next_url = next_entry_url = nil
|
||||||
exit_url = "/book/#{title.id}"
|
exit_url = "#{base_url}book/#{title.id}"
|
||||||
next_entry = title.next_entry entry
|
next_entry = title.next_entry entry
|
||||||
unless next_page > entry.pages
|
unless next_page > entry.pages
|
||||||
next_url = "/reader/#{title.id}/#{entry.id}/#{next_page}"
|
next_url = "#{base_url}reader/#{title.id}/#{entry.id}/#{next_page}"
|
||||||
end
|
end
|
||||||
unless next_entry.nil?
|
unless next_entry.nil?
|
||||||
next_entry_url = "/reader/#{title.id}/#{next_entry.id}"
|
next_entry_url = "#{base_url}reader/#{title.id}/#{next_entry.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
render "src/views/reader.ecr"
|
render "src/views/reader.ecr"
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
require "../context"
|
|
||||||
|
|
||||||
class Router
|
class Router
|
||||||
def initialize(@context : Context)
|
@context : Context = Context.default
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
+39
-9
@@ -1,11 +1,38 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
require "./context"
|
require "./library"
|
||||||
require "./handlers/*"
|
require "./handlers/*"
|
||||||
require "./util"
|
require "./util"
|
||||||
require "./routes/*"
|
require "./routes/*"
|
||||||
|
|
||||||
|
class Context
|
||||||
|
property library : Library
|
||||||
|
property storage : Storage
|
||||||
|
property queue : MangaDex::Queue
|
||||||
|
|
||||||
|
def self.default : self
|
||||||
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@storage = Storage.default
|
||||||
|
@library = Library.default
|
||||||
|
@queue = MangaDex::Queue.default
|
||||||
|
end
|
||||||
|
|
||||||
|
{% for lvl in Logger::LEVELS %}
|
||||||
|
def {{lvl.id}}(msg)
|
||||||
|
Logger.{{lvl.id}} msg
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
|
||||||
class Server
|
class Server
|
||||||
def initialize(@context : Context)
|
@context : Context = Context.default
|
||||||
|
|
||||||
|
def initialize
|
||||||
error 403 do |env|
|
error 403 do |env|
|
||||||
message = "HTTP 403: You are not authorized to visit #{env.request.path}"
|
message = "HTTP 403: You are not authorized to visit #{env.request.path}"
|
||||||
layout "message"
|
layout "message"
|
||||||
@@ -14,20 +41,23 @@ class Server
|
|||||||
message = "HTTP 404: Mango cannot find the page #{env.request.path}"
|
message = "HTTP 404: Mango cannot find the page #{env.request.path}"
|
||||||
layout "message"
|
layout "message"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
{% if flag?(:release) %}
|
||||||
error 500 do |env|
|
error 500 do |env|
|
||||||
message = "HTTP 500: Internal server error. Please try again later."
|
message = "HTTP 500: Internal server error. Please try again later."
|
||||||
layout "message"
|
layout "message"
|
||||||
end
|
end
|
||||||
|
{% end %}
|
||||||
|
|
||||||
MainRouter.new(@context).setup
|
MainRouter.new
|
||||||
AdminRouter.new(@context).setup
|
AdminRouter.new
|
||||||
ReaderRouter.new(@context).setup
|
ReaderRouter.new
|
||||||
APIRouter.new(@context).setup
|
APIRouter.new
|
||||||
|
|
||||||
Kemal.config.logging = false
|
Kemal.config.logging = false
|
||||||
add_handler LogHandler.new @context.logger
|
add_handler LogHandler.new
|
||||||
add_handler AuthHandler.new @context.storage
|
add_handler AuthHandler.new @context.storage
|
||||||
add_handler UploadHandler.new @context.config.upload_path
|
add_handler UploadHandler.new Config.current.upload_path
|
||||||
{% if flag?(:release) %}
|
{% if flag?(:release) %}
|
||||||
# when building for relase, embed the static files in binary
|
# when building for relase, embed the static files in binary
|
||||||
@context.debug "We are in release mode. Using embedded static files."
|
@context.debug "We are in release mode. Using embedded static files."
|
||||||
@@ -41,7 +71,7 @@ class Server
|
|||||||
{% if flag?(:release) %}
|
{% if flag?(:release) %}
|
||||||
Kemal.config.env = "production"
|
Kemal.config.env = "production"
|
||||||
{% end %}
|
{% end %}
|
||||||
Kemal.config.port = @context.config.port
|
Kemal.config.port = Config.current.port
|
||||||
Kemal.run
|
Kemal.run
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+23
-13
@@ -13,14 +13,24 @@ def verify_password(hash, pw)
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Storage
|
class Storage
|
||||||
def initialize(@path : String, @logger : Logger)
|
@path : String
|
||||||
dir = File.dirname path
|
|
||||||
|
def self.default : self
|
||||||
|
unless @@default
|
||||||
|
@@default = new
|
||||||
|
end
|
||||||
|
@@default.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(db_path : String? = nil)
|
||||||
|
@path = db_path || Config.current.db_path
|
||||||
|
dir = File.dirname @path
|
||||||
unless Dir.exists? dir
|
unless Dir.exists? dir
|
||||||
@logger.info "The DB directory #{dir} does not exist. " \
|
Logger.info "The DB directory #{dir} does not exist. " \
|
||||||
"Attepmting to create it"
|
"Attepmting to create it"
|
||||||
Dir.mkdir_p dir
|
Dir.mkdir_p dir
|
||||||
end
|
end
|
||||||
DB.open "sqlite3://#{path}" do |db|
|
DB.open "sqlite3://#{@path}" do |db|
|
||||||
begin
|
begin
|
||||||
# We create the `ids` table first. even if the uses has an
|
# We create the `ids` table first. even if the uses has an
|
||||||
# early version installed and has the `user` table only,
|
# early version installed and has the `user` table only,
|
||||||
@@ -34,18 +44,18 @@ class Storage
|
|||||||
"(username text, password text, token text, admin integer)"
|
"(username text, password text, token text, admin integer)"
|
||||||
rescue e
|
rescue e
|
||||||
unless e.message.not_nil!.ends_with? "already exists"
|
unless e.message.not_nil!.ends_with? "already exists"
|
||||||
@logger.fatal "Error when checking tables in DB: #{e}"
|
Logger.fatal "Error when checking tables in DB: #{e}"
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@logger.debug "Creating DB file at #{@path}"
|
Logger.debug "Creating DB file at #{@path}"
|
||||||
db.exec "create unique index username_idx on users (username)"
|
db.exec "create unique index username_idx on users (username)"
|
||||||
db.exec "create unique index token_idx on users (token)"
|
db.exec "create unique index token_idx on users (token)"
|
||||||
random_pw = random_str
|
random_pw = random_str
|
||||||
hash = hash_password random_pw
|
hash = hash_password random_pw
|
||||||
db.exec "insert into users values (?, ?, ?, ?)",
|
db.exec "insert into users values (?, ?, ?, ?)",
|
||||||
"admin", hash, nil, 1
|
"admin", hash, nil, 1
|
||||||
@logger.log "Initial user created. You can log in with " \
|
Logger.log "Initial user created. You can log in with " \
|
||||||
"#{{"username" => "admin", "password" => random_pw}}"
|
"#{{"username" => "admin", "password" => random_pw}}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -58,18 +68,18 @@ class Storage
|
|||||||
"users where username = (?)",
|
"users where username = (?)",
|
||||||
username, as: {String, String?}
|
username, as: {String, String?}
|
||||||
unless verify_password hash, password
|
unless verify_password hash, password
|
||||||
@logger.debug "Password does not match the hash"
|
Logger.debug "Password does not match the hash"
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@logger.debug "User #{username} verified"
|
Logger.debug "User #{username} verified"
|
||||||
return token if token
|
return token if token
|
||||||
token = random_str
|
token = random_str
|
||||||
@logger.debug "Updating token for #{username}"
|
Logger.debug "Updating token for #{username}"
|
||||||
db.exec "update users set token = (?) where username = (?)",
|
db.exec "update users set token = (?) where username = (?)",
|
||||||
token, username
|
token, username
|
||||||
return token
|
return token
|
||||||
rescue e
|
rescue e
|
||||||
@logger.error "Error when verifying user #{username}: #{e}"
|
Logger.error "Error when verifying user #{username}: #{e}"
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -82,7 +92,7 @@ class Storage
|
|||||||
username = db.query_one "select username from users where " \
|
username = db.query_one "select username from users where " \
|
||||||
"token = (?)", token, as: String
|
"token = (?)", token, as: String
|
||||||
rescue e
|
rescue e
|
||||||
@logger.debug "Unable to verify token"
|
Logger.debug "Unable to verify token"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
username
|
username
|
||||||
@@ -95,7 +105,7 @@ class Storage
|
|||||||
is_admin = db.query_one "select admin from users where " \
|
is_admin = db.query_one "select admin from users where " \
|
||||||
"token = (?)", token, as: Bool
|
"token = (?)", token, as: Bool
|
||||||
rescue e
|
rescue e
|
||||||
@logger.debug "Unable to verify user as admin"
|
Logger.debug "Unable to verify user as admin"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
is_admin
|
is_admin
|
||||||
|
|||||||
+4
-4
@@ -1,9 +1,9 @@
|
|||||||
require "./util"
|
require "./util"
|
||||||
|
|
||||||
class Upload
|
class Upload
|
||||||
def initialize(@dir : String, @logger : Logger)
|
def initialize(@dir : String)
|
||||||
unless Dir.exists? @dir
|
unless Dir.exists? @dir
|
||||||
@logger.info "The uploads directory #{@dir} does not exist. " \
|
Logger.info "The uploads directory #{@dir} does not exist. " \
|
||||||
"Attempting to create it"
|
"Attempting to create it"
|
||||||
Dir.mkdir_p @dir
|
Dir.mkdir_p @dir
|
||||||
end
|
end
|
||||||
@@ -19,7 +19,7 @@ class Upload
|
|||||||
file_path = File.join full_dir, filename
|
file_path = File.join full_dir, filename
|
||||||
|
|
||||||
unless Dir.exists? full_dir
|
unless Dir.exists? full_dir
|
||||||
@logger.debug "creating directory #{full_dir}"
|
Logger.debug "creating directory #{full_dir}"
|
||||||
Dir.mkdir_p full_dir
|
Dir.mkdir_p full_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class Upload
|
|||||||
end
|
end
|
||||||
|
|
||||||
if ary.empty?
|
if ary.empty?
|
||||||
@logger.warn "File #{path} is not in the upload directory #{@dir}"
|
Logger.warn "File #{path} is not in the upload directory #{@dir}"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ IMGS_PER_PAGE = 5
|
|||||||
UPLOAD_URL_PREFIX = "/uploads"
|
UPLOAD_URL_PREFIX = "/uploads"
|
||||||
|
|
||||||
macro layout(name)
|
macro layout(name)
|
||||||
|
base_url = Config.current.base_url
|
||||||
begin
|
begin
|
||||||
cookie = env.request.cookies.find { |c| c.name == "token" }
|
cookie = env.request.cookies.find { |c| c.name == "token" }
|
||||||
is_admin = false
|
is_admin = false
|
||||||
@@ -99,3 +100,8 @@ end
|
|||||||
def random_str
|
def random_str
|
||||||
UUID.random.to_s.gsub "-", ""
|
UUID.random.to_s.gsub "-", ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def redirect(env, path)
|
||||||
|
base = Config.current.base_url
|
||||||
|
env.redirect File.join base, path
|
||||||
|
end
|
||||||
|
|||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
<ul class="uk-list uk-list-large uk-list-divider">
|
<ul class="uk-list uk-list-large uk-list-divider">
|
||||||
<li data-url="/admin/user">User Managerment</li>
|
<li data-url="<%= base_url %>admin/user">User Managerment</li>
|
||||||
<li onclick="if(!scanning){scan()}">
|
<li onclick="if(!scanning){scan()}">
|
||||||
<span id="scan">Scan Library Files</span>
|
<span id="scan">Scan Library Files</span>
|
||||||
<span id="scan-status" class="uk-align-right">
|
<span id="scan-status" class="uk-align-right">
|
||||||
@@ -7,12 +7,12 @@
|
|||||||
<span hidden></span>
|
<span hidden></span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li data-url="/admin/downloads">Download Manager</li>
|
<li data-url="<%= base_url %>admin/downloads">Download Manager</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<hr class="uk-divider-icon">
|
<hr class="uk-divider-icon">
|
||||||
<a class="uk-button uk-button-danger" href="/logout">Log Out</a>
|
<a class="uk-button uk-button-danger" href="<%= base_url %>logout">Log Out</a>
|
||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="/js/admin.js"></script>
|
<script src="<%= base_url %>js/admin.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script>
|
<script>
|
||||||
var baseURL = "<%= base_url %>".replace(/\/$/, "");
|
var baseURL = "<%= mangadex_base_url %>".replace(/\/$/, "");
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
||||||
<script src="/js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="/js/download-manager.js"></script>
|
<script src="<%= base_url %>js/download-manager.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -74,10 +74,10 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script>
|
<script>
|
||||||
var baseURL = "<%= base_url %>".replace(/\/$/, "");
|
var baseURL = "<%= mangadex_base_url %>".replace(/\/$/, "");
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
|
||||||
<script src="/js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="/js/download.js"></script>
|
<script src="<%= base_url %>js/download.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
+4
-4
@@ -23,7 +23,7 @@
|
|||||||
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||||
<%- titles.each_with_index do |t, i| -%>
|
<%- titles.each_with_index do |t, i| -%>
|
||||||
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
||||||
<a class="acard" href="/book/<%= t.id %>">
|
<a class="acard" href="<%= base_url %>book/<%= t.id %>">
|
||||||
<div class="uk-card uk-card-default">
|
<div class="uk-card uk-card-default">
|
||||||
<div class="uk-card-media-top">
|
<div class="uk-card-media-top">
|
||||||
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
|
||||||
<script src="/js/dots.js"></script>
|
<script src="<%= base_url %>js/dots.js"></script>
|
||||||
<script src="/js/search.js"></script>
|
<script src="<%= base_url %>js/search.js"></script>
|
||||||
<script src="/js/sort-items.js"></script>
|
<script src="<%= base_url %>js/sort-items.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
+14
-13
@@ -7,11 +7,11 @@
|
|||||||
<meta name="description" content="Mango Manga Server">
|
<meta name="description" content="Mango Manga Server">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||||
<link rel="stylesheet" href="/css/mango.css" />
|
<link rel="stylesheet" href="<%= base_url %>css/mango.css" />
|
||||||
<script defer src="/js/fontawesome.min.js"></script>
|
<script defer src="<%= base_url %>js/fontawesome.min.js"></script>
|
||||||
<script defer src="/js/solid.min.js"></script>
|
<script defer src="<%= base_url %>js/solid.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||||
<script src="/js/theme.js"></script>
|
<script src="<%= base_url %>js/theme.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -20,14 +20,14 @@
|
|||||||
<div id="mobile-nav" uk-offcanvas="overlay: true">
|
<div id="mobile-nav" uk-offcanvas="overlay: true">
|
||||||
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
|
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
|
||||||
<ul class="uk-nav uk-nav-primary uk-nav-center uk-margin-auto-vertical">
|
<ul class="uk-nav uk-nav-primary uk-nav-center uk-margin-auto-vertical">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="<%= base_url %>">Home</a></li>
|
||||||
<% if is_admin %>
|
<% if is_admin %>
|
||||||
<li><a href="/admin">Admin</a></li>
|
<li><a href="<%= base_url %>admin">Admin</a></li>
|
||||||
<li><a href="/download">Download</a></li>
|
<li><a href="<%= base_url %>download">Download</a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<hr uk-divider>
|
<hr uk-divider>
|
||||||
<li><a onclick="toggleTheme()"><i class="fas fa-adjust"></i></a></li>
|
<li><a onclick="toggleTheme()"><i class="fas fa-adjust"></i></a></li>
|
||||||
<li><a href="/logout">Logout</a></li>
|
<li><a href="<%= base_url %>logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,19 +39,19 @@
|
|||||||
<div class="uk-navbar-toggle" uk-navbar-toggle-icon="uk-navbar-toggle-icon" uk-toggle="target: #mobile-nav"></div>
|
<div class="uk-navbar-toggle" uk-navbar-toggle-icon="uk-navbar-toggle-icon" uk-toggle="target: #mobile-nav"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-left uk-visible@s">
|
<div class="uk-navbar-left uk-visible@s">
|
||||||
<a class="uk-navbar-item uk-logo" href="/"><img src="/img/icon.png"></a>
|
<a class="uk-navbar-item uk-logo" href="<%= base_url %>"><img src="<%= base_url %>img/icon.png"></a>
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="uk-navbar-nav">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="<%= base_url %>">Home</a></li>
|
||||||
<% if is_admin %>
|
<% if is_admin %>
|
||||||
<li><a href="/admin">Admin</a></li>
|
<li><a href="<%= base_url %>admin">Admin</a></li>
|
||||||
<li><a href="/download">Download</a></li>
|
<li><a href="<%= base_url %>download">Download</a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-right uk-visible@s">
|
<div class="uk-navbar-right uk-visible@s">
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="uk-navbar-nav">
|
||||||
<li><a onclick="toggleTheme()"><i class="fas fa-adjust"></i></a></li>
|
<li><a onclick="toggleTheme()"><i class="fas fa-adjust"></i></a></li>
|
||||||
<li><a href="/logout">Logout</a></li>
|
<li><a href="<%= base_url %>logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
setTheme(getTheme());
|
setTheme(getTheme());
|
||||||
|
const base_url = "<%= base_url %>";
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||||
<script src="/js/theme.js"></script>
|
<script src="<%= base_url %>js/theme.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="uk-section uk-flex uk-flex-middle uk-animation-fade" uk-height-viewport="">
|
<div class="uk-section uk-flex uk-flex-middle uk-animation-fade" uk-height-viewport="">
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="uk-width-1-1@m">
|
<div class="uk-width-1-1@m">
|
||||||
<div class="uk-margin uk-width-large uk-margin-auto uk-card uk-card-default uk-card-body uk-box-shadow-large">
|
<div class="uk-margin uk-width-large uk-margin-auto uk-card uk-card-default uk-card-body uk-box-shadow-large">
|
||||||
<h3 class="uk-card-title uk-text-center">Log In</h3>
|
<h3 class="uk-card-title uk-text-center">Log In</h3>
|
||||||
<form action="/login" method="post">
|
<form action="<%= base_url %>login" method="post">
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<div class="uk-inline uk-width-1-1"><span class="uk-form-icon" uk-icon="icon:user"></span><input class="uk-input uk-form-large" type="text" name="username"></div>
|
<div class="uk-inline uk-width-1-1"><span class="uk-form-icon" uk-icon="icon:user"></span><input class="uk-input uk-form-large" type="text" name="username"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
<meta name="description" content="Mango Manga Server">
|
<meta name="description" content="Mango Manga Server">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||||
<link rel="stylesheet" href="/css/mango.css" />
|
<link rel="stylesheet" href="<%= base_url %>css/mango.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script src="/js/theme.js"></script>
|
<script src="<%= base_url %>js/theme.js"></script>
|
||||||
<div class="uk-section uk-section-default uk-section-small reader-bg">
|
<div class="uk-section uk-section-default uk-section-small reader-bg">
|
||||||
<div class="uk-container uk-container-small">
|
<div class="uk-container uk-container-small">
|
||||||
<%- urls.each_with_index do |url, i| -%>
|
<%- urls.each_with_index do |url, i| -%>
|
||||||
@@ -56,10 +56,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
const base_url = "<%= base_url %>"
|
||||||
|
</script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
<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>
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
|
||||||
<script src="/js/reader.js"></script>
|
<script src="<%= base_url %>js/reader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+8
-8
@@ -7,9 +7,9 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<ul class="uk-breadcrumb">
|
<ul class="uk-breadcrumb">
|
||||||
<li><a href="/">Library</a></li>
|
<li><a href="<%= base_url %>">Library</a></li>
|
||||||
<%- title.parents.each do |t| -%>
|
<%- title.parents.each do |t| -%>
|
||||||
<li><a href="/book/<%= t.id %>"><%= t.display_name %></a></li>
|
<li><a href="<%= base_url %>book/<%= t.id %>"><%= t.display_name %></a></li>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
<li class="uk-disabled"><a><%= title.display_name %></a></li>
|
<li class="uk-disabled"><a><%= title.display_name %></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||||
<%- title.titles.each_with_index do |t, i| -%>
|
<%- title.titles.each_with_index do |t, i| -%>
|
||||||
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="0.0">
|
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="0.0">
|
||||||
<a class="acard" href="/book/<%= t.id %>">
|
<a class="acard" href="<%= base_url %>book/<%= t.id %>">
|
||||||
<div class="uk-card uk-card-default">
|
<div class="uk-card uk-card-default">
|
||||||
<div class="uk-card-media-top">
|
<div class="uk-card-media-top">
|
||||||
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
||||||
@@ -151,9 +151,9 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
|
||||||
<script src="/js/dots.js"></script>
|
<script src="<%= base_url %>js/dots.js"></script>
|
||||||
<script src="/js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="/js/title.js"></script>
|
<script src="<%= base_url %>js/title.js"></script>
|
||||||
<script src="/js/search.js"></script>
|
<script src="<%= base_url %>js/search.js"></script>
|
||||||
<script src="/js/sort-items.js"></script>
|
<script src="<%= base_url %>js/sort-items.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<form action="/admin/user/edit" method="post" accept-charset="utf-8">
|
<form action="<%= base_url %>admin/user/edit" method="post" accept-charset="utf-8">
|
||||||
|
|
||||||
<div class="uk-margin">
|
<div class="uk-margin">
|
||||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
||||||
@@ -49,6 +49,6 @@
|
|||||||
error = '<%= error %>';
|
error = '<%= error %>';
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="/js/user-edit.js"></script>
|
<script src="<%= base_url %>js/user-edit.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
+4
-4
@@ -12,7 +12,7 @@
|
|||||||
<td><%= u[0] %></td>
|
<td><%= u[0] %></td>
|
||||||
<td><%= u[1] %></td>
|
<td><%= u[1] %></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/user/edit?username=<%= u[0] %>&admin=<%= u[1] %>" uk-icon="file-edit"></a>
|
<a href="<%= base_url %>admin/user/edit?username=<%= u[0] %>&admin=<%= u[1] %>" uk-icon="file-edit"></a>
|
||||||
<%- if u[0] != username %>
|
<%- if u[0] != username %>
|
||||||
<a href="#" onclick="remove('<%= u[0] %>');return false;" uk-icon="trash"></a>
|
<a href="#" onclick="remove('<%= u[0] %>');return false;" uk-icon="trash"></a>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a href="/admin/user/edit" class="uk-button uk-button-primary">New User</a>
|
<a href="<%= base_url %>admin/user/edit" class="uk-button uk-button-primary">New User</a>
|
||||||
|
|
||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="/js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="/js/user.js"></script>
|
<script src="<%= base_url %>js/user.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
Reference in New Issue
Block a user