mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-02 10:55:30 -04:00
- initial commit
This commit is contained in:
commit
3c2b054ec8
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.cr]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/docs/
|
||||||
|
/lib/
|
||||||
|
/bin/
|
||||||
|
/.shards/
|
||||||
|
*.dwarf
|
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
language: crystal
|
||||||
|
|
||||||
|
# Uncomment the following if you'd like Travis to run specs and check code formatting
|
||||||
|
# script:
|
||||||
|
# - crystal spec
|
||||||
|
# - crystal tool format --check
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Ling
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# mango
|
||||||
|
|
||||||
|
TODO: Write a description here
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
TODO: Write installation instructions here
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Write usage instructions here
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
TODO: Write development instructions here
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork it (<https://github.com/your-github-user/mango/fork>)
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
4. Push to the branch (`git push origin my-new-feature`)
|
||||||
|
5. Create a new Pull Request
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
- [Alex Ling](https://github.com/your-github-user) - creator and maintainer
|
30
shard.lock
Normal file
30
shard.lock
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
version: 1.0
|
||||||
|
shards:
|
||||||
|
db:
|
||||||
|
github: crystal-lang/crystal-db
|
||||||
|
version: 0.8.0
|
||||||
|
|
||||||
|
exception_page:
|
||||||
|
github: crystal-loot/exception_page
|
||||||
|
version: 0.1.2
|
||||||
|
|
||||||
|
kemal:
|
||||||
|
github: kemalcr/kemal
|
||||||
|
version: 0.26.1
|
||||||
|
|
||||||
|
kemal-basic-auth:
|
||||||
|
github: kemalcr/kemal-basic-auth
|
||||||
|
version: 0.2.0
|
||||||
|
|
||||||
|
kilt:
|
||||||
|
github: jeromegn/kilt
|
||||||
|
version: 0.4.0
|
||||||
|
|
||||||
|
radix:
|
||||||
|
github: luislavena/radix
|
||||||
|
version: 0.3.9
|
||||||
|
|
||||||
|
sqlite3:
|
||||||
|
github: crystal-lang/crystal-sqlite3
|
||||||
|
version: 0.15.0
|
||||||
|
|
21
shard.yml
Normal file
21
shard.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: mango
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
authors:
|
||||||
|
- Alex Ling <hkalexling@gmail.com>
|
||||||
|
|
||||||
|
targets:
|
||||||
|
mango:
|
||||||
|
main: src/mango.cr
|
||||||
|
|
||||||
|
crystal: 0.32.1
|
||||||
|
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
kemal:
|
||||||
|
github: kemalcr/kemal
|
||||||
|
kemal-basic-auth:
|
||||||
|
github: kemalcr/kemal-basic-auth
|
||||||
|
sqlite3:
|
||||||
|
github: crystal-lang/crystal-sqlite3
|
9
spec/mango_spec.cr
Normal file
9
spec/mango_spec.cr
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
describe Mango do
|
||||||
|
# TODO: Write tests
|
||||||
|
|
||||||
|
it "works" do
|
||||||
|
false.should eq(true)
|
||||||
|
end
|
||||||
|
end
|
2
spec/spec_helper.cr
Normal file
2
spec/spec_helper.cr
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
require "spec"
|
||||||
|
require "../src/mango"
|
15
src/auth_handler.cr
Normal file
15
src/auth_handler.cr
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
require "kemal"
|
||||||
|
|
||||||
|
class AuthHandler < Kemal::Handler
|
||||||
|
exclude ["/login"]
|
||||||
|
def call(env)
|
||||||
|
return call_next(env) if exclude_match?(env)
|
||||||
|
my_cookie = HTTP::Cookie.new(
|
||||||
|
name: "Example",
|
||||||
|
value: "KemalCR"
|
||||||
|
)
|
||||||
|
env.response.cookies << my_cookie
|
||||||
|
|
||||||
|
pp env.request.cookies
|
||||||
|
end
|
||||||
|
end
|
37
src/config.cr
Normal file
37
src/config.cr
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
require "yaml"
|
||||||
|
require "uuid"
|
||||||
|
require "base64"
|
||||||
|
|
||||||
|
class Config
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field(key: "port")]
|
||||||
|
property port = 9000
|
||||||
|
|
||||||
|
@[YAML::Field(key: "library_path")]
|
||||||
|
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
|
||||||
|
|
||||||
|
def self.load
|
||||||
|
cfg_path = File.expand_path "~/.config/mango/config.yml", home: true
|
||||||
|
if File.exists? cfg_path
|
||||||
|
return self.from_yaml File.read cfg_path
|
||||||
|
end
|
||||||
|
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"
|
||||||
|
abort "Aborting..."
|
||||||
|
end
|
||||||
|
default = self.allocate
|
||||||
|
cfg_dir = File.dirname cfg_path
|
||||||
|
unless Dir.exists? cfg_dir
|
||||||
|
Dir.mkdir_p cfg_dir
|
||||||
|
end
|
||||||
|
File.write cfg_path, default.to_yaml
|
||||||
|
puts "The config file has been created at #{cfg_path}."
|
||||||
|
default
|
||||||
|
end
|
||||||
|
end
|
44
src/library.cr
Normal file
44
src/library.cr
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
require "zip"
|
||||||
|
|
||||||
|
class Entry
|
||||||
|
property zip_path : String
|
||||||
|
property title : String
|
||||||
|
property size : String
|
||||||
|
|
||||||
|
def initialize(path : String)
|
||||||
|
@zip_path = path
|
||||||
|
@title = File.basename path, ".zip"
|
||||||
|
@size = (File.size path).humanize_bytes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Title
|
||||||
|
property dir : String
|
||||||
|
property entries : Array(Entry)
|
||||||
|
property title : String
|
||||||
|
|
||||||
|
def initialize(dir : String)
|
||||||
|
@dir = dir
|
||||||
|
@title = File.basename dir
|
||||||
|
@entries = (Dir.entries dir)
|
||||||
|
.select! { |path| (File.extname path) == ".zip" }
|
||||||
|
.map { |path| Entry.new File.join dir, path }
|
||||||
|
.sort { |a, b| a.title <=> b.title }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Library
|
||||||
|
property dir : String
|
||||||
|
property titles : Array(Title)
|
||||||
|
|
||||||
|
def initialize(dir : String)
|
||||||
|
@dir = dir
|
||||||
|
unless Dir.exists? dir
|
||||||
|
abort "ERROR: The library directory #{dir} does not exist"
|
||||||
|
end
|
||||||
|
@titles = (Dir.entries dir)
|
||||||
|
.select! { |path| File.directory? File.join dir, path }
|
||||||
|
.map { |path| Title.new File.join dir, path }
|
||||||
|
.select! { |title| !title.entries.empty? }
|
||||||
|
end
|
||||||
|
end
|
25
src/mango.cr
Normal file
25
src/mango.cr
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
require "kemal"
|
||||||
|
require "./config"
|
||||||
|
require "./library"
|
||||||
|
require "./storage"
|
||||||
|
require "./auth_handler"
|
||||||
|
|
||||||
|
config = Config.load
|
||||||
|
|
||||||
|
library = Library.new config.library_path
|
||||||
|
|
||||||
|
storage = Storage.new config.db_path
|
||||||
|
|
||||||
|
get "/" do
|
||||||
|
"Hello World!"
|
||||||
|
end
|
||||||
|
|
||||||
|
# APIs
|
||||||
|
get "/api/test" do |env|
|
||||||
|
"Hello!"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_handler AuthHandler.new
|
||||||
|
|
||||||
|
Kemal.config.port = config.port
|
||||||
|
Kemal.run
|
73
src/storage.cr
Normal file
73
src/storage.cr
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
require "sqlite3"
|
||||||
|
require "crypto/bcrypt"
|
||||||
|
require "uuid"
|
||||||
|
require "base64"
|
||||||
|
|
||||||
|
def hash_password(pw)
|
||||||
|
Crypto::Bcrypt::Password.create(pw).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_password(hash, pw)
|
||||||
|
(Crypto::Bcrypt::Password.new hash).verify pw
|
||||||
|
end
|
||||||
|
|
||||||
|
def random_str()
|
||||||
|
Base64.strict_encode UUID.random().to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
class Storage
|
||||||
|
property path : String
|
||||||
|
|
||||||
|
def initialize(path)
|
||||||
|
@path = path
|
||||||
|
DB.open "sqlite3://#{path}" do |db|
|
||||||
|
begin
|
||||||
|
db.exec "create table users" \
|
||||||
|
"(username text, password text, token text, admin integer)"
|
||||||
|
rescue e : SQLite3::Exception | DB::Error
|
||||||
|
unless e.message == "table users already exists"
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
else
|
||||||
|
db.exec "create unique index username_idx on users (username)"
|
||||||
|
db.exec "create unique index token_idx on users (token)"
|
||||||
|
random_pw = random_str
|
||||||
|
hash = hash_password random_pw
|
||||||
|
db.exec "insert into users values (?, ?, ?, ?)",
|
||||||
|
"admin", hash, "", 1
|
||||||
|
puts "Initial user created. You can log in with " \
|
||||||
|
"#{{"username" => "admin", "password" => random_pw}}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_user(username, password)
|
||||||
|
DB.open "sqlite3://#{@path}" do |db|
|
||||||
|
begin
|
||||||
|
hash = db.query_one "select password from users where " \
|
||||||
|
"username = (?)", username, as: String
|
||||||
|
unless verify_password hash, password
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
token = random_str
|
||||||
|
db.exec "update users set token = (?) where username = (?)",
|
||||||
|
token, username
|
||||||
|
return token
|
||||||
|
rescue e : SQLite3::Exception | DB::Error
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_token(token)
|
||||||
|
DB.open "sqlite3://#{@path}" do |db|
|
||||||
|
begin
|
||||||
|
username = db.query_one "select username from users where " \
|
||||||
|
"token = (?)", token, as: String
|
||||||
|
return username
|
||||||
|
rescue e : SQLite3::Exception | DB::Error
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
x
Reference in New Issue
Block a user