mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2024-12-22 16:26:28 +00:00
file upload
This commit is contained in:
parent
8eeb2a8de7
commit
8cee6579e2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -524,6 +524,7 @@ dependencies = [
|
|||||||
"dotenv",
|
"dotenv",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"memcache",
|
"memcache",
|
||||||
|
"mime",
|
||||||
"ring",
|
"ring",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
17
client/src/lib/stores/status.ts
Normal file
17
client/src/lib/stores/status.ts
Normal 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)
|
||||||
|
}
|
@ -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>
|
||||||
|
12
client/src/lib/ui/MaxSize.svelte
Normal file
12
client/src/lib/ui/MaxSize.svelte
Normal 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>
|
@ -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,8 +114,12 @@
|
|||||||
<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" />
|
||||||
|
<div class="tr">
|
||||||
|
<small>max: <MaxSize /> </small>
|
||||||
|
<br />
|
||||||
<Button type="submit" data-testid="button-create">create</Button>
|
<Button type="submit" data-testid="button-create">create</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="error-text">{error}</div>
|
<div class="error-text">{error}</div>
|
||||||
@ -126,7 +134,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="advanced" class:hidden={!advanced}>
|
{#if advanced}
|
||||||
|
<div transition:blur={{ duration: 250 }}>
|
||||||
<br />
|
<br />
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -148,15 +157,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
|
@ -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>
|
||||||
|
@ -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({})
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
web::scope("/notes")
|
||||||
|
.service(one)
|
||||||
.service(create)
|
.service(create)
|
||||||
.service(delete)
|
.service(delete)
|
||||||
.service(one),
|
.service(status),
|
||||||
|
)
|
||||||
|
.service(status),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
19
src/size.rs
19
src/size.rs
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user