mirror of https://github.com/getmango/Mango.git
- initial commit
This commit is contained in:
commit
3c2b054ec8
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
/docs/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Mango do
|
||||
# TODO: Write tests
|
||||
|
||||
it "works" do
|
||||
false.should eq(true)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../src/mango"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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…
Reference in New Issue