WIP
test / test (push) Failing after 20s
Details
test / test (push) Failing after 20s
Details
This commit is contained in:
parent
1b22ce6d22
commit
f5095938b8
|
@ -2,6 +2,7 @@ import envoy
|
|||
import gleam/int
|
||||
import gleam/pgo
|
||||
import gleam/result.{unwrap}
|
||||
import gleam/uri
|
||||
import wisp
|
||||
|
||||
pub type Context {
|
||||
|
@ -34,6 +35,23 @@ pub fn get_string_or(var: String, default: String) -> String {
|
|||
envoy.get(var) |> unwrap(default)
|
||||
}
|
||||
|
||||
pub fn generate_url(
|
||||
nano_id: String,
|
||||
root: String,
|
||||
ctx: Context,
|
||||
) -> Result(String, Nil) {
|
||||
uri.parse(
|
||||
ctx.config.url_root
|
||||
<> ":"
|
||||
<> ctx.config.port |> int.to_string
|
||||
<> "/"
|
||||
<> root
|
||||
<> "/"
|
||||
<> nano_id,
|
||||
)
|
||||
|> result.map(uri.to_string)
|
||||
}
|
||||
|
||||
// fn get_path_or(var: String, default: List(String)) -> List(String) {
|
||||
// case envoy.get(var) {
|
||||
// Ok(path_raw) -> {
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
import app/config.{type Context}
|
||||
import gleam/bit_array
|
||||
import gleam/crypto
|
||||
import gleam/dynamic
|
||||
import gleam/http.{Get, Post}
|
||||
import gleam/list
|
||||
import gleam/pgo
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import ids/nanoid
|
||||
import wisp.{type Request, type Response}
|
||||
|
||||
const schema = "
|
||||
CREATE TABLE IF NOT EXISTS \"File\" (
|
||||
nanoid VARCHAR(21) PRIMARY KEY,
|
||||
md5_hash BYTEA
|
||||
Md5Hash VARCHAR(32)
|
||||
FileName TEXT
|
||||
);
|
||||
"
|
||||
|
||||
pub type File {
|
||||
File(md5_hash: BitArray, nanoid: String)
|
||||
File(nano_id: String, md5_hash: String, file_name: String)
|
||||
}
|
||||
|
||||
pub fn prepare_database(ctx: Context) -> Result(Nil, pgo.QueryError) {
|
||||
|
@ -23,27 +27,95 @@ pub fn prepare_database(ctx: Context) -> Result(Nil, pgo.QueryError) {
|
|||
|> result.map(fn(_) { Nil })
|
||||
}
|
||||
|
||||
pub fn hash_to_path(hash: String) -> List(String) {
|
||||
list.new()
|
||||
|> list.append([hash |> string.slice(0, 2)])
|
||||
|> list.append([hash |> string.slice(2, 2)])
|
||||
|> list.append([hash |> string.slice(4, { hash |> string.length } - 4)])
|
||||
pub fn store(file: File, ctx: Context) -> Result(String, Nil) {
|
||||
let sql =
|
||||
"INSERT INTO \"File\" (NanoID, Md5Hash) VALUES ($1, $2) RETURNING NanoID;"
|
||||
case
|
||||
pgo.execute(
|
||||
sql,
|
||||
ctx.db,
|
||||
[pgo.text(file.nano_id), pgo.text(file.md5_hash)],
|
||||
dynamic.element(0, dynamic.string),
|
||||
)
|
||||
{
|
||||
Ok(target_id) ->
|
||||
target_id.rows |> list.first |> result.map_error(fn(_) { Nil })
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_nano_id(req: Request, nano_id: String, ct: Context) -> Response {
|
||||
pub fn retrieve(nano_id: String, ctx: Context) -> Result(File, Nil) {
|
||||
let sql = "SELECT NanoID,Md5Hash,FileName FROM \"File\" WHERE NanoID = $1;"
|
||||
case
|
||||
pgo.execute(
|
||||
sql,
|
||||
ctx.db,
|
||||
[pgo.text(nano_id)],
|
||||
dynamic.tuple3(dynamic.string, dynamic.string, dynamic.string),
|
||||
)
|
||||
{
|
||||
Ok(res) ->
|
||||
case res.rows |> list.first {
|
||||
Ok(#(nano_id, md5_hash, file_name)) ->
|
||||
Ok(File(nano_id, md5_hash, file_name))
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_to_path(hash: String) -> String {
|
||||
[""]
|
||||
|> list.append([string.slice(hash, 0, 2)])
|
||||
|> list.append([string.slice(hash, 2, 2)])
|
||||
|> list.append([string.slice(hash, 4, string.length(hash) - 4)])
|
||||
|> string.join("/")
|
||||
}
|
||||
|
||||
pub fn serve_hash(req: Request, md5_hash: String) -> Response {
|
||||
use <- wisp.require_method(req, Get)
|
||||
todo as "implement serve_file"
|
||||
let path = hash_to_path(md5_hash)
|
||||
// TODO: Test mimetype
|
||||
wisp.ok()
|
||||
|> wisp.set_header("Content-Type", "application/octet-stream")
|
||||
|> wisp.file_download(named: "blob", from: path)
|
||||
}
|
||||
|
||||
fn handle_root(req: Request, ctx: Context) -> Response {
|
||||
use <- wisp.require_method(req, Post)
|
||||
todo as "implement receive_file"
|
||||
}
|
||||
|
||||
pub fn handle_request(req: Request, ctx: Context) -> Response {
|
||||
pub fn handle_get(req: Request, ctx: Context) -> Response {
|
||||
use <- wisp.require_method(req, Get)
|
||||
case wisp.path_segments(req) {
|
||||
["f"] -> handle_root(req, ctx)
|
||||
["f", nano_id] -> handle_nano_id(req, nano_id, ctx)
|
||||
["f", nano_id] ->
|
||||
case retrieve(nano_id, ctx) {
|
||||
Ok(file) -> serve_hash(req, file.md5_hash)
|
||||
Error(_) -> wisp.not_found()
|
||||
}
|
||||
_ -> wisp.not_found()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_post(req: Request, ctx: Context) -> Response {
|
||||
use <- wisp.require_method(req, Post)
|
||||
use body <- wisp.require_bit_array_body(req)
|
||||
|
||||
case wisp.path_segments(req) {
|
||||
["f", path] -> {
|
||||
let nano_id = nanoid.generate()
|
||||
let md5_hash = crypto.hash(crypto.Md5, body) |> bit_array.base16_encode
|
||||
let file = File(nano_id, md5_hash, path)
|
||||
|
||||
case store(file, ctx) |> result.try(config.generate_url(_, "f", ctx)) {
|
||||
Ok(url) -> wisp.ok() |> wisp.string_body(url)
|
||||
Error(_) -> wisp.internal_server_error()
|
||||
}
|
||||
}
|
||||
_ -> wisp.not_found()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_request(req: Request, ctx: Context) -> Response {
|
||||
case req.method {
|
||||
Get -> handle_get(req, ctx)
|
||||
Post -> handle_post(req, ctx)
|
||||
_ -> wisp.not_found()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import app/config.{type Context}
|
||||
import gleam/dynamic
|
||||
import gleam/http.{Get, Post}
|
||||
import gleam/int
|
||||
import gleam/io
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/pair
|
||||
import gleam/pgo
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import gleam/string_builder
|
||||
import gleam/uri.{type Uri}
|
||||
import ids/nanoid
|
||||
import wisp.{type Request, type Response}
|
||||
|
@ -30,7 +25,7 @@ pub fn prepare_database(ctx: Context) -> Result(Nil, pgo.QueryError) {
|
|||
|> result.map(fn(_) { Nil })
|
||||
}
|
||||
|
||||
pub fn store(link: Link, ctx: Context) -> Result(String, Option(pgo.QueryError)) {
|
||||
pub fn store(link: Link, ctx: Context) -> Result(String, Nil) {
|
||||
let sql =
|
||||
"INSERT INTO \"Link\" (NanoID, TargetURL) VALUES ($1, $2) RETURNING NanoID;"
|
||||
case
|
||||
|
@ -42,8 +37,8 @@ pub fn store(link: Link, ctx: Context) -> Result(String, Option(pgo.QueryError))
|
|||
)
|
||||
{
|
||||
Ok(target_id) ->
|
||||
target_id.rows |> list.first |> result.map_error(fn(_) { None })
|
||||
Error(e) -> Error(Some(e))
|
||||
target_id.rows |> list.first |> result.map_error(fn(_) { Nil })
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,55 +53,26 @@ pub fn retrieve(nano_id: String, ctx: Context) -> Result(Link, Nil) {
|
|||
)
|
||||
{
|
||||
Ok(res) -> {
|
||||
let target_raw = res.rows |> list.first
|
||||
let nano_id =
|
||||
target_raw
|
||||
|> result.map(pair.first)
|
||||
let sql_response = res.rows |> list.first
|
||||
let nano_id = sql_response |> result.map(pair.first)
|
||||
let target_url =
|
||||
target_raw
|
||||
|> result.map(pair.second)
|
||||
|> result.try(uri.parse)
|
||||
sql_response |> result.map(pair.second) |> result.try(uri.parse)
|
||||
|
||||
use nano_id <- result.try(nano_id)
|
||||
use target_url <- result.try(target_url)
|
||||
|
||||
Ok(Link(nano_id, target_url))
|
||||
{
|
||||
use nano_id <- result.try(nano_id)
|
||||
use target_url <- result.try(target_url)
|
||||
Ok(Link(nano_id, target_url))
|
||||
}
|
||||
}
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_url(
|
||||
nano_id: Result(String, Nil),
|
||||
ctx: Context,
|
||||
) -> Result(Uri, Nil) {
|
||||
use nano_id <- result.try(nano_id)
|
||||
uri.parse(
|
||||
ctx.config.url_root
|
||||
<> ":"
|
||||
<> ctx.config.port |> int.to_string
|
||||
<> "/l/"
|
||||
<> nano_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_uri(url: Uri, ctx: Context) -> Response {
|
||||
nanoid.generate()
|
||||
|> Link(url)
|
||||
|> store(ctx)
|
||||
|> result.map_error(fn(error) {
|
||||
error |> string.inspect |> io.debug
|
||||
Nil
|
||||
})
|
||||
|> generate_url(ctx)
|
||||
|> result.map(fn(uri) {
|
||||
uri
|
||||
|> uri.to_string
|
||||
|> string_builder.from_string
|
||||
|> wisp.Text
|
||||
|> wisp.set_body(wisp.response(200), _)
|
||||
})
|
||||
|> result.unwrap(wisp.response(404))
|
||||
store(nanoid.generate() |> Link(url), ctx)
|
||||
|> result.try(config.generate_url(_, "l", ctx))
|
||||
|> result.map(wisp.string_body(wisp.response(200), _))
|
||||
|> result.unwrap(wisp.response(500))
|
||||
}
|
||||
|
||||
pub fn handle_root(req: Request, ctx: Context) -> Response {
|
||||
|
|
|
@ -5,7 +5,8 @@ import gleam/pgo
|
|||
import mist
|
||||
import wisp
|
||||
|
||||
fn with_context(config: Config, next: fn(Context) -> Nil) -> Nil {
|
||||
fn with_context(continue: fn(Context) -> Nil) -> Nil {
|
||||
let config = config.load_config_from_env()
|
||||
let db =
|
||||
pgo.connect(
|
||||
pgo.Config(
|
||||
|
@ -17,19 +18,19 @@ fn with_context(config: Config, next: fn(Context) -> Nil) -> Nil {
|
|||
)
|
||||
let ctx = Context(db: db, config: config)
|
||||
|
||||
next(ctx)
|
||||
continue(ctx)
|
||||
|
||||
ctx.db |> pgo.disconnect
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let config = config.load_config_from_env()
|
||||
use ctx: Context <- with_context(config)
|
||||
use ctx <- with_context()
|
||||
|
||||
wisp.configure_logger()
|
||||
let entrypoint = router.handle_request(_, ctx)
|
||||
|
||||
let assert Ok(_) =
|
||||
wisp.mist_handler(entrypoint, config.secret_key_base)
|
||||
router.handle_request(_, ctx)
|
||||
|> wisp.mist_handler(config.secret_key_base)
|
||||
|> mist.new
|
||||
|> mist.port(config.port)
|
||||
|> mist.start_http
|
||||
|
|
Loading…
Reference in New Issue