file upload

This commit is contained in:
cupcakearmy 2021-12-22 13:10:08 +01:00
parent 8eeb2a8de7
commit 8cee6579e2
No known key found for this signature in database
GPG Key ID: 3235314B4D31232F
14 changed files with 126 additions and 68 deletions

1
Cargo.lock generated
View File

@ -524,6 +524,7 @@ dependencies = [
"dotenv", "dotenv",
"lazy_static", "lazy_static",
"memcache", "memcache",
"mime",
"ring", "ring",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -21,3 +21,4 @@ bs62 = "0.1"
memcache = "0.16" memcache = "0.16"
byte-unit = "4" byte-unit = "4"
dotenv = "0.15" dotenv = "0.15"
mime = "0.3"

View File

@ -59,7 +59,7 @@ version: '3.7'
services: services:
memcached: memcached:
image: memcached:1-alpine image: memcached:1-alpine
entrypoint: memcached -m 128 # Limit to 128 MB Ram, customize at free will. entrypoint: memcached -m 128M -I 4M # Limit to 128 MB Ram, 4M per entry, customize at free will.
app: app:
image: cupcakearmy/cryptgeon:latest image: cupcakearmy/cryptgeon:latest

View File

@ -100,7 +100,7 @@ fieldset {
.box { .box {
width: 100%; width: 100%;
min-height: min(calc(100vh - 30rem), 30rem); min-height: min(calc(100vh - 30rem), 20rem);
margin: 0; margin: 0;
border: 2px solid var(--ui-bg-1); border: 2px solid var(--ui-bg-1);
resize: vertical; resize: vertical;
@ -118,3 +118,7 @@ fieldset {
.box:focus { .box:focus {
border-color: var(--ui-clr-primary); border-color: var(--ui-clr-primary);
} }
.tr {
text-align: right;
}

View File

@ -22,7 +22,7 @@ type CallOptions = {
export class PayloadToLargeError extends Error {} export class PayloadToLargeError extends Error {}
async function call(options: CallOptions) { export async function call(options: CallOptions) {
const response = await fetch('/api/' + options.url, { const response = await fetch('/api/' + options.url, {
method: options.method, method: options.method,
body: options.body === undefined ? undefined : JSON.stringify(options.body), body: options.body === undefined ? undefined : JSON.stringify(options.body),
@ -46,7 +46,7 @@ export async function create(note: Note) {
meta: JSON.stringify(meta), meta: JSON.stringify(meta),
} }
const data = await call({ const data = await call({
url: 'notes', url: 'notes/',
method: 'post', method: 'post',
body, body,
}) })

View File

@ -0,0 +1,17 @@
import { call } from '$lib/api'
import { onMount } from 'svelte'
import { writable } from 'svelte/store'
export type Status = {
max_size: number
}
export const status = writable<null | Status>(null)
export async function init() {
const data = await call({
url: 'status',
method: 'get',
})
status.set(data)
}

View File

@ -2,6 +2,7 @@
import type { FileDTO } from '$lib/api' import type { FileDTO } from '$lib/api'
import { Files } from '$lib/files' import { Files } from '$lib/files'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import MaxSize from './MaxSize.svelte'
export let label: string = '' export let label: string = ''
let files: File[] = [] let files: File[] = []
@ -49,6 +50,8 @@
{:else} {:else}
<div> <div>
<b>No Files Selected</b> <b>No Files Selected</b>
<br />
<small>max: <MaxSize /></small>
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -0,0 +1,12 @@
<script lang="ts">
import { status } from '$lib/stores/status'
import prettyBytes from 'pretty-bytes'
</script>
<span>
{#if $status !== null}
{prettyBytes($status.max_size, { binary: true })}
{:else}
loading...
{/if}
</span>

View File

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { Note, PayloadToLargeError } from '$lib/api' import { create, Note, PayloadToLargeError } from '$lib/api'
import { create } from '$lib/api' import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto'
import { getKeyFromString, encrypt, Hex, getRandomBytes } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import FileUpload from '$lib/ui/FileUpload.svelte' import FileUpload from '$lib/ui/FileUpload.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte'
import Switch from '$lib/ui/Switch.svelte' import Switch from '$lib/ui/Switch.svelte'
import TextArea from '$lib/ui/TextArea.svelte' import TextArea from '$lib/ui/TextArea.svelte'
import TextInput from '$lib/ui/TextInput.svelte' import TextInput from '$lib/ui/TextInput.svelte'
import { blur } from 'svelte/transition'
let note: Note = { let note: Note = {
contents: '', contents: '',
@ -36,6 +36,10 @@
$: note.meta.type = file ? 'file' : 'text' $: note.meta.type = file ? 'file' : 'text'
$: if (!file) {
note.contents = ''
}
async function submit() { async function submit() {
try { try {
error = null error = null
@ -110,7 +114,11 @@
<Switch class="file" label="file" bind:value={file} /> <Switch class="file" label="file" bind:value={file} />
<Switch label="advanced" bind:value={advanced} /> <Switch label="advanced" bind:value={advanced} />
<div class="grow" /> <div class="grow" />
<Button type="submit" data-testid="button-create">create</Button> <div class="tr">
<small>max: <MaxSize /> </small>
<br />
<Button type="submit" data-testid="button-create">create</Button>
</div>
</div> </div>
{#if error} {#if error}
@ -126,37 +134,30 @@
{/if} {/if}
</p> </p>
<div class="advanced" class:hidden={!advanced}> {#if advanced}
<br /> <div transition:blur={{ duration: 250 }}>
<div class="fields"> <br />
<TextInput <div class="fields">
type="number" <TextInput
label="views" type="number"
bind:value={note.views} label="views"
disabled={type} bind:value={note.views}
max={100} disabled={type}
/> max={100}
<div class="middle-switch"> />
<Switch label="mode" bind:value={type} color={false} /> <div class="middle-switch">
<Switch label="mode" bind:value={type} color={false} />
</div>
<TextInput
type="number"
label="minutes"
bind:value={note.expiration}
disabled={!type}
max={360}
/>
</div> </div>
<TextInput
type="number"
label="minutes"
bind:value={note.expiration}
disabled={!type}
max={360}
/>
</div> </div>
</div> {/if}
<style>
.fields {
display: flex;
}
.spacer {
width: 3rem;
}
</style>
</fieldset> </fieldset>
</form> </form>
{/if} {/if}
@ -164,7 +165,6 @@
<style> <style>
.bottom { .bottom {
display: flex; display: flex;
/* justify-content: space-between; */
align-items: flex-end; align-items: flex-end;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
@ -181,17 +181,11 @@
margin: 0 1rem; margin: 0 1rem;
} }
.advanced {
max-height: 14em;
overflow: hidden;
transition: var(--ui-anim);
}
.advanced.hidden {
max-height: 0;
}
.error-text { .error-text {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.fields {
display: flex;
}
</style> </style>

View File

@ -1,8 +1,13 @@
<script lang="ts"> <script lang="ts">
import { init } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte' import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte' import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css' import '../app.css'
onMount(() => {
init()
})
</script> </script>
<svelte:head> <svelte:head>

View File

@ -1,5 +1,5 @@
import httpProxy from 'http-proxy'
import http from 'http' import http from 'http'
import httpProxy from 'http-proxy'
const proxy = httpProxy.createProxyServer({}) const proxy = httpProxy.createProxyServer({})

View File

@ -14,13 +14,6 @@ async fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
return HttpServer::new(|| { return HttpServer::new(|| {
App::new() App::new()
// .configure(|cfg: &mut web::ServiceConfig| {
// let limit_string = std::env::var("SIZE_LIMIT").unwrap_or("0.1 KiB".to_string());
// let limit = Byte::from_str(limit_string.clone()).unwrap().get_bytes() as usize;
// println!("SIZE_LIMIT: {}, {}", limit_string, limit);
// let config = web::JsonConfig::default().limit(limit);
// cfg.data(config);
// })
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::DefaultHeaders::default()) .wrap(middleware::DefaultHeaders::default())
.configure(size::init) .configure(size::init)

View File

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use std::time::SystemTime; use std::time::SystemTime;
use crate::note::{generate_id, Note, NoteInfo, NotePublic}; use crate::note::{generate_id, Note, NoteInfo, NotePublic};
use crate::size::LIMIT;
use crate::store; use crate::store;
fn now() -> u64 { fn now() -> u64 {
@ -17,7 +18,7 @@ struct NotePath {
id: String, id: String,
} }
#[get("/notes/{id}")] #[get("/{id}")]
async fn one(path: web::Path<NotePath>) -> impl Responder { async fn one(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner(); let p = path.into_inner();
let note = store::get(&p.id); let note = store::get(&p.id);
@ -32,7 +33,7 @@ struct CreateResponse {
id: String, id: String,
} }
#[post("/notes")] #[post("/")]
async fn create(note: web::Json<Note>) -> impl Responder { async fn create(note: web::Json<Note>) -> impl Responder {
let mut n = note.into_inner(); let mut n = note.into_inner();
let id = generate_id(); let id = generate_id();
@ -62,7 +63,7 @@ async fn create(note: web::Json<Note>) -> impl Responder {
return HttpResponse::Ok().json(CreateResponse { id: id }); return HttpResponse::Ok().json(CreateResponse { id: id });
} }
#[delete("/notes/{id}")] #[delete("/{id}")]
async fn delete(path: web::Path<NotePath>) -> impl Responder { async fn delete(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner(); let p = path.into_inner();
let note = store::get(&p.id); let note = store::get(&p.id);
@ -102,11 +103,27 @@ async fn delete(path: web::Path<NotePath>) -> impl Responder {
} }
} }
#[derive(Serialize, Deserialize)]
struct Status {
max_size: usize,
}
#[get("/status")]
async fn status() -> impl Responder {
println!("Limit: {}", *LIMIT);
return HttpResponse::Ok().json(Status { max_size: *LIMIT });
}
pub fn init(cfg: &mut web::ServiceConfig) { pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::scope("/api") web::scope("/api")
.service(create) .service(
.service(delete) web::scope("/notes")
.service(one), .service(one)
.service(create)
.service(delete)
.service(status),
)
.service(status),
); );
} }

View File

@ -1,9 +1,20 @@
use actix_web::web; use actix_web::web;
use byte_unit::Byte; use byte_unit::Byte;
use mime;
lazy_static! {
pub static ref LIMIT: usize =
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
.unwrap()
.get_bytes() as usize;
}
pub fn init(cfg: &mut web::ServiceConfig) { pub fn init(cfg: &mut web::ServiceConfig) {
let limit_string = std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()); println!("Limit: {}", *LIMIT);
let limit = Byte::from_str(limit_string).unwrap().get_bytes() as usize; let json = web::JsonConfig::default().limit(*LIMIT);
let config = web::JsonConfig::default().limit(limit); let plain = web::PayloadConfig::default()
cfg.data(config); .limit(*LIMIT)
.mimetype(mime::STAR_STAR);
cfg.data(json);
cfg.data(plain);
} }