WIP
test / test (push) Failing after 38s
Details
test / test (push) Failing after 38s
Details
This commit is contained in:
parent
8d25de682a
commit
1b22ce6d22
27
README.md
27
README.md
|
@ -8,27 +8,10 @@ gleam test # Run the tests
|
||||||
gleam shell # Run an Erlang shell
|
gleam shell # Run an Erlang shell
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flow
|
|
||||||
|
|
||||||
### Upload
|
|
||||||
|
|
||||||
take file upload
|
|
||||||
-> get hash of file
|
|
||||||
-> test against CSAM hash list
|
|
||||||
-> generate UUID
|
|
||||||
-> point UUID at hash of file
|
|
||||||
-> save file if hash doesn't exist
|
|
||||||
-> return UUID
|
|
||||||
|
|
||||||
### Download
|
|
||||||
|
|
||||||
take file UUID
|
|
||||||
-> search for UUID in table and get hash
|
|
||||||
-> return file if hash exists
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [] Handle file upload
|
- [X] Handle link GET
|
||||||
- [] Handle file download
|
- [X] Handle link POST
|
||||||
- [] Testing against CSAM hash list
|
- [] Handle file POST
|
||||||
- [] Logging IPs (up and down) of detected CSAM uploads
|
- [] Handle file GET
|
||||||
|
- [] Web Frontend
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import envoy
|
import envoy
|
||||||
import gleam/int
|
import gleam/int
|
||||||
|
import gleam/pgo
|
||||||
import gleam/result.{unwrap}
|
import gleam/result.{unwrap}
|
||||||
import wisp
|
import wisp
|
||||||
|
|
||||||
|
pub type Context {
|
||||||
|
Context(db: pgo.Connection, config: Config)
|
||||||
|
}
|
||||||
|
|
||||||
pub type Config {
|
pub type Config {
|
||||||
Config(
|
Config(
|
||||||
port: Int,
|
port: Int,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import app/config.{type Config}
|
import app/config.{type Context}
|
||||||
import app/web.{type Context}
|
import app/web
|
||||||
import app/web/file
|
import app/web/file
|
||||||
import app/web/link
|
import app/web/link
|
||||||
import app/web/root
|
import app/web/root
|
||||||
import wisp.{type Request, type Response}
|
import wisp.{type Request, type Response}
|
||||||
|
|
||||||
pub fn handle_request(req: Request, config: Config) -> Response {
|
pub fn handle_request(req: Request, ctx: Context) -> Response {
|
||||||
use req: Request, ctx: Context <- web.middleware(req, config)
|
use req: Request <- web.middleware(req)
|
||||||
|
|
||||||
case wisp.path_segments(req) {
|
case wisp.path_segments(req) {
|
||||||
["f", ..] -> file.handle_request(req, ctx)
|
["f", ..] -> file.handle_request(req, ctx)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import app/config.{type Config}
|
|
||||||
import gleam/pgo
|
|
||||||
import wisp.{type Request, type Response}
|
import wisp.{type Request, type Response}
|
||||||
|
|
||||||
pub const html_head = "
|
pub const html_head = "
|
||||||
|
@ -18,27 +16,13 @@ pub const html_tail = "
|
||||||
</html>
|
</html>
|
||||||
"
|
"
|
||||||
|
|
||||||
pub type Context {
|
|
||||||
Context(db: pgo.Connection, config: Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn middleware(
|
pub fn middleware(
|
||||||
req: Request,
|
req: Request,
|
||||||
config: Config,
|
handle_request: fn(Request) -> Response,
|
||||||
handle_request: fn(Request, Context) -> Response,
|
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let req = wisp.method_override(req)
|
let req = wisp.method_override(req)
|
||||||
use <- wisp.log_request(req)
|
use <- wisp.log_request(req)
|
||||||
use <- wisp.rescue_crashes
|
use <- wisp.rescue_crashes
|
||||||
use req <- wisp.handle_head(req)
|
use req <- wisp.handle_head(req)
|
||||||
let db =
|
handle_request(req)
|
||||||
pgo.connect(
|
|
||||||
pgo.Config(
|
|
||||||
..pgo.default_config(),
|
|
||||||
host: config.postgres_host,
|
|
||||||
database: config.postgres_db,
|
|
||||||
pool_size: config.postgres_pool_size,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
handle_request(req, Context(db, config))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,49 @@
|
||||||
import app/web.{type Context}
|
import app/config.{type Context}
|
||||||
|
import gleam/dynamic
|
||||||
import gleam/http.{Get, Post}
|
import gleam/http.{Get, Post}
|
||||||
import gleam/list
|
import gleam/list
|
||||||
|
import gleam/pgo
|
||||||
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import wisp.{type Request, type Response}
|
import wisp.{type Request, type Response}
|
||||||
|
|
||||||
const schema = "
|
const schema = "
|
||||||
CREATE TABLE IF NOT EXISTS `FileReference` (
|
CREATE TABLE IF NOT EXISTS \"File\" (
|
||||||
nanoid TEXT <<key>>
|
nanoid VARCHAR(21) PRIMARY KEY,
|
||||||
PathID INT
|
md5_hash BYTEA
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS `FilePath` (
|
|
||||||
PathID INT <<key>>
|
|
||||||
md5_hash BLOB
|
|
||||||
banned BOOLEAN
|
|
||||||
);
|
);
|
||||||
"
|
"
|
||||||
|
|
||||||
type File {
|
pub type File {
|
||||||
Reference(md5_hash: BitArray, nanoid: String)
|
File(md5_hash: BitArray, nanoid: String)
|
||||||
Path(md5_hash: String, banned: Bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_to_path(hash: String) -> List(String) {
|
pub fn prepare_database(ctx: Context) -> Result(Nil, pgo.QueryError) {
|
||||||
|
pgo.execute(schema, ctx.db, [], dynamic.dynamic)
|
||||||
|
|> result.map(fn(_) { Nil })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_to_path(hash: String) -> List(String) {
|
||||||
list.new()
|
list.new()
|
||||||
|> list.append([hash |> string.slice(0, 2)])
|
|> list.append([hash |> string.slice(0, 2)])
|
||||||
|> list.append([hash |> string.slice(2, 2)])
|
|> list.append([hash |> string.slice(2, 2)])
|
||||||
|> list.append([hash |> string.slice(4, { hash |> string.length } - 4)])
|
|> list.append([hash |> string.slice(4, { hash |> string.length } - 4)])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request(req: Request, ctx: Context) -> Response {
|
fn handle_nano_id(req: Request, nano_id: String, ct: Context) -> Response {
|
||||||
todo
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve_file(req, uuid: String) -> Response {
|
|
||||||
use <- wisp.require_method(req, Get)
|
use <- wisp.require_method(req, Get)
|
||||||
todo as "implement serve_file"
|
todo as "implement serve_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_file(req: Request) -> Response {
|
fn handle_root(req: Request, ctx: Context) -> Response {
|
||||||
use <- wisp.require_method(req, Post)
|
use <- wisp.require_method(req, Post)
|
||||||
todo as "implement receive_file"
|
todo as "implement receive_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_request(req: Request, ctx: Context) -> Response {
|
||||||
|
case wisp.path_segments(req) {
|
||||||
|
["f"] -> handle_root(req, ctx)
|
||||||
|
["f", nano_id] -> handle_nano_id(req, nano_id, ctx)
|
||||||
|
_ -> wisp.not_found()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import app/web.{type Context}
|
import app/config.{type Context}
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
import gleam/http.{Get, Post}
|
import gleam/http.{Get, Post}
|
||||||
import gleam/int
|
import gleam/int
|
||||||
|
|
|
@ -1,14 +1,33 @@
|
||||||
import app/config
|
import app/config.{type Config, type Context, Context}
|
||||||
import app/router
|
import app/router
|
||||||
import gleam/erlang/process
|
import gleam/erlang/process
|
||||||
|
import gleam/pgo
|
||||||
import mist
|
import mist
|
||||||
import wisp
|
import wisp
|
||||||
|
|
||||||
pub fn main() {
|
fn with_context(config: Config, next: fn(Context) -> Nil) -> Nil {
|
||||||
wisp.configure_logger()
|
let db =
|
||||||
let config = config.load_config_from_env()
|
pgo.connect(
|
||||||
|
pgo.Config(
|
||||||
|
..pgo.default_config(),
|
||||||
|
host: config.postgres_host,
|
||||||
|
database: config.postgres_db,
|
||||||
|
pool_size: config.postgres_pool_size,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
let ctx = Context(db: db, config: config)
|
||||||
|
|
||||||
let entrypoint = router.handle_request(_, config)
|
next(ctx)
|
||||||
|
|
||||||
|
ctx.db |> pgo.disconnect
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let config = config.load_config_from_env()
|
||||||
|
use ctx: Context <- with_context(config)
|
||||||
|
|
||||||
|
wisp.configure_logger()
|
||||||
|
let entrypoint = router.handle_request(_, ctx)
|
||||||
let assert Ok(_) =
|
let assert Ok(_) =
|
||||||
wisp.mist_handler(entrypoint, config.secret_key_base)
|
wisp.mist_handler(entrypoint, config.secret_key_base)
|
||||||
|> mist.new
|
|> mist.new
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import app/config.{type Context, Config, Context}
|
||||||
|
import app/web/file
|
||||||
|
import gleam/dynamic
|
||||||
|
import gleam/option.{None, Some}
|
||||||
|
import gleam/pgo
|
||||||
|
import gleam/string
|
||||||
|
import gleeunit/should
|
||||||
|
|
||||||
|
const test_config = Config(
|
||||||
|
port: 8080,
|
||||||
|
max_bytes: 52_428_800,
|
||||||
|
url_root: "http://localhost",
|
||||||
|
secret_key_base: "cDv6ikrODZKjKbwSBBwspXInc61MYpCzQ8My9gW2QpQg3Y3lT7IJ5RJlzRN5HZK0",
|
||||||
|
postgres_db: "dumptruck",
|
||||||
|
postgres_user: "dumptruck",
|
||||||
|
postgres_password: "dumptruck",
|
||||||
|
postgres_host: "localhost",
|
||||||
|
postgres_pool_size: 15,
|
||||||
|
postgres_port: 5432,
|
||||||
|
)
|
||||||
|
|
||||||
|
fn with_context(testcase: fn(Context) -> t) -> t {
|
||||||
|
let ctx =
|
||||||
|
Context(
|
||||||
|
db: pgo.Config(
|
||||||
|
..pgo.default_config(),
|
||||||
|
database: test_config.postgres_db,
|
||||||
|
password: test_config.postgres_password |> Some,
|
||||||
|
user: test_config.postgres_user,
|
||||||
|
host: test_config.postgres_host,
|
||||||
|
pool_size: test_config.postgres_pool_size,
|
||||||
|
port: 5432,
|
||||||
|
)
|
||||||
|
|> pgo.connect,
|
||||||
|
config: test_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
case file.prepare_database(ctx) {
|
||||||
|
Ok(_) -> {
|
||||||
|
let ret = testcase(ctx)
|
||||||
|
|
||||||
|
ctx.db |> pgo.disconnect
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
Error(e) -> {
|
||||||
|
e |> string.inspect |> panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn null_test() {
|
||||||
|
use ctx <- with_context()
|
||||||
|
|
||||||
|
pgo.execute(
|
||||||
|
"select $1",
|
||||||
|
ctx.db,
|
||||||
|
[pgo.null()],
|
||||||
|
dynamic.element(0, dynamic.optional(dynamic.int)),
|
||||||
|
)
|
||||||
|
|> should.equal(Ok(pgo.Returned(count: 1, rows: [None])))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_to_path_test() {
|
||||||
|
"f7b478961451483b8c251c788750d5ef"
|
||||||
|
|> file.hash_to_path
|
||||||
|
|> should.equal(["f7", "b4", "78961451483b8c251c788750d5ef"])
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
import app/config.{Config}
|
import app/config.{type Context, Config, Context}
|
||||||
import app/web.{type Context, Context}
|
|
||||||
import app/web/link.{Link}
|
import app/web/link.{Link}
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
import gleam/io
|
|
||||||
import gleam/option.{None, Some}
|
import gleam/option.{None, Some}
|
||||||
import gleam/pgo
|
import gleam/pgo
|
||||||
import gleam/result
|
import gleam/result
|
||||||
|
|
Loading…
Reference in New Issue