- initial commit

This commit is contained in:
Alex Ling 2020-02-11 22:06:17 +00:00
commit 3c2b054ec8
15 changed files with 324 additions and 0 deletions

9
.editorconfig Normal file
View 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
View File

@ -0,0 +1,5 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf

6
.travis.yml Normal file
View 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
View 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
View 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

BIN
mango Executable file

Binary file not shown.

30
shard.lock Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,2 @@
require "spec"
require "../src/mango"

15
src/auth_handler.cr Normal file
View 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
View 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
View 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
View 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
View 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