WIP
test / test (push) Failing after 20s Details

This commit is contained in:
Luca Bilke 2024-06-27 00:56:03 +02:00
parent 1b22ce6d22
commit f5095938b8
No known key found for this signature in database
GPG Key ID: C9E851809C1A5BDE
4 changed files with 129 additions and 72 deletions

View File

@ -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) -> {

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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