Compare commits

..

40 Commits

Author SHA1 Message Date
35ba25ba9e changelog 2022-06-07 12:29:58 +02:00
724dca0e69 update deps 2022-06-07 12:29:54 +02:00
9029f72a02 improve docker build and wait for memcached 2022-06-07 12:29:40 +02:00
1d55d7f2d2 update deps 2022-06-07 12:29:16 +02:00
d09bb4e0c6 update deps 2022-06-07 12:29:11 +02:00
53c7c9d9e2 1.5.1 2022-05-15 12:13:03 +02:00
df9c60c29e 1.5.0 2022-05-14 16:57:28 +02:00
f29b6b23f0 Update README.md 2022-04-12 09:34:38 +02:00
cc88fa6763 readme 2022-04-12 09:12:40 +02:00
19022e7cb5 formatting 2022-03-12 14:07:33 +01:00
44f43dbc2c scratch docs 2022-03-06 12:27:08 +01:00
45f6f3af32 readme from scratch 2022-03-06 12:16:19 +01:00
9bd544f0d5 add env vars to readme 2022-03-06 12:01:54 +01:00
a315e58284 Merge pull request #26 from cupcakearmy/1.4.1
fallback route & dep updates
2022-03-05 12:47:55 +01:00
d576b71bc5 fallback route & dep updates 2022-03-05 12:47:11 +01:00
e02f7f59c6 changelog 2022-03-02 16:59:25 +01:00
e8c6467faa dont' allow empty notes 2022-03-02 16:55:10 +01:00
43f67c795d don't remove already selected files 2022-03-02 16:55:04 +01:00
83f0902291 lokalise 2022-03-02 16:32:36 +01:00
11a6621bd7 script to download locales 2022-03-02 16:15:25 +01:00
36fa451249 enforce limits 2022-03-01 16:16:02 +01:00
d112eba8fe add config for max views, expiration and advanced 2022-03-01 15:24:17 +01:00
ef39f9ec0b cleanup 2022-03-01 02:04:43 +01:00
8517c20e6c remove unused code 2022-03-01 02:01:57 +01:00
728ad56b33 enforce strict typescript 2022-03-01 02:00:01 +01:00
f185ccee03 add svelte check 2022-03-01 01:52:20 +01:00
284bbcbae2 better typings 2022-03-01 01:52:09 +01:00
7eba454f1b visual improvements 2022-03-01 01:52:04 +01:00
dcd9efaeba use native icons 2022-03-01 01:51:43 +01:00
f13bcbaf3f update robots 2022-03-01 01:51:21 +01:00
8e7e0414a6 add copy to clipboard note 2022-03-01 01:16:31 +01:00
229c8d8368 update actix to version 4 2022-03-01 00:53:47 +01:00
1adf87b884 set memcached size 2022-03-01 00:53:34 +01:00
a061b540b1 make top margin smaller 2022-01-21 17:46:13 +01:00
824603ff4a add locales 2022-01-16 14:39:45 +01:00
539d99d35f version bump 2022-01-16 14:04:49 +01:00
716034660c translate the app 2022-01-16 14:02:53 +01:00
bab65bcdad bug due to dep update 2022-01-03 18:16:54 +01:00
a0732a4593 move folder 2022-01-02 23:46:08 +01:00
835f7df0f6 update dep 2022-01-02 18:43:31 +01:00
94 changed files with 4605 additions and 3426 deletions

View File

@@ -1,2 +1,15 @@
target *
node_modules !/entry.sh
!/backend/src
!/backend/Cargo.lock
!/backend/Cargo.toml
!/frontend/locales
!/frontend/src
!/frontend/static
!/frontend/.npmrc
!/frontend/package.json
!/frontend/pnpm-lock.yaml
!/frontend/svelte.config.js
!/frontend/tsconfig.json

BIN
.github/lokalise.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
# Backend # Backend
/target target
# Client # Client
.DS_Store .DS_Store

10
.vscode/settings.json vendored
View File

@@ -1,6 +1,6 @@
{ {
"cSpell.words": [ "cSpell.words": ["ciphertext", "cryptgeon"],
"ciphertext", "i18n-ally.localesPaths": ["frontend/locales"],
"cryptgeon" "i18n-ally.enabledFrameworks": ["svelte"],
] "i18n-ally.keystyle": "nested"
} }

View File

@@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.2] - 2022-06-07
### Added
- Wait for script for memecached.
### Security
- Updated dependencies.
## [1.5.1] - 2022-05-15
### Fixed
- Remove double note content.
## [1.5.0] - 2022-05-14
### Added
- Links in notes are not highlighted and can be directly clicked #30.
## [1.4.1] - 2022-03-05
### Fixed
- Router in prod build.
## [1.4.0] - 2022-03-02
### Added
- Support for multiple languages.
- Select multiple files without removing already selected ones.
- Tooltip for copy action.
- Configure maximum views, expiration and advanced options for the server.
### Changed
- Use native SVGs instead of images.
- Update robots.txt file to allow only root.
- Stronger frontend types.
## [1.3.3] - 2022-01-03
### Fixed
- Bug fix due to dependency update.
## [1.3.2] - 2022-01-02
### Changed
- Dependencies updates.
- Folder structure.
## [1.3.1] - 2021-12-30 ## [1.3.1] - 2021-12-30
### Added ### Added

2126
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,26 @@
FROM node:16-alpine as CLIENT # FRONTEND
FROM node:16-alpine as client
WORKDIR /tmp WORKDIR /tmp
COPY ./client ./
RUN npm install -g pnpm RUN npm install -g pnpm
COPY ./frontend ./
RUN pnpm install RUN pnpm install
RUN pnpm run build RUN pnpm run build
FROM rust:1.51-alpine as RUST
# BACKEND
FROM rust:1.61-alpine as backend
WORKDIR /tmp WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./Cargo* ./ COPY ./backend ./
COPY ./src ./src
RUN cargo build --release RUN cargo build --release
FROM scratch
# RUNNER
FROM alpine
WORKDIR /app WORKDIR /app
COPY --from=RUST /tmp/target/release/cryptgeon . COPY ./entry.sh .
COPY --from=CLIENT /tmp/build ./client/build COPY --from=backend /tmp/target/release/cryptgeon .
COPY --from=client /tmp/build ./frontend/build
ENV MEMCACHE=memcached:11211 ENV MEMCACHE=memcached:11211
EXPOSE 5000 EXPOSE 5000
ENTRYPOINT [ "/app/entry.sh" ]
ENTRYPOINT [ "/app/cryptgeon" ]

View File

@@ -10,13 +10,18 @@
</a> </a>
<br/> <br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50">
<br/> <br/>
## About? ## About?
_cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com) _cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com)
> 🌍 If you want to translate the project feel free to reach out to me.
>
> Thanks to [Lokalise](https://lokalise.com/) for providing free access to their platform.
## Demo ## Demo
Check out the demo and see for yourself https://cryptgeon.nicco.io. Check out the demo and see for yourself https://cryptgeon.nicco.io.
@@ -43,10 +48,13 @@ of the notes even if it tried to.
## Environment Variables ## Environment Variables
| Variable | Default | Description | | Variable | Default | Description |
| ------------ | ----------------- | --------------------------------------------------------------------------------------- | | ---------------- | ----------------- | --------------------------------------------------------------------------------------- |
| `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. | | `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. |
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) | | `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) |
| `MAX_VIEWS` | `100` | Maximal number of views. |
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
## Deployment ## Deployment
@@ -119,11 +127,35 @@ services:
## Development ## Development
1. Clone **Requirements**
2. run `pnpm i` in the root and and client `client/` folders.
3. Run `pnpm run dev` to start development.
Running `npm run dev` in the root folder will start the following things - `pnpm`: `>=6`
- `node`: `>=14`
- `rust`: edition `2021`
**Install**
```bash
pnpm install
pnpm --prefix frontend install
# Also you need cargo watch if you don't already have it installed.
# https://lib.rs/crates/cargo-watch
cargo install cargo-watch
```
**Run**
Make sure you have docker running.
> If you are on `macOS` you might need to disable AirPlay Receiver as it uses port 5000 (So stupid...)
> https://developer.apple.com/forums/thread/682332
```bash
pnpm run dev
```
Running `pnpm run dev` in the root folder will start the following things:
- a memcache docker container - a memcache docker container
- rust backend with hot reload - rust backend with hot reload

1551
backend/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "1.3.0" version = "1.5.2"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2018" edition = "2021"
[[bin]] [[bin]]
name = "cryptgeon" name = "cryptgeon"
@@ -11,9 +11,9 @@ path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
actix-web = "3" actix-web = "4"
actix-files = "0.5" actix-files = "0.6"
serde = "1" serde = { version = "1.0", features = ["derive"] }
serde_json = "1" serde_json = "1"
lazy_static = "1" lazy_static = "1"
ring = "0.16" ring = "0.16"

12
backend/src/api.rs Normal file
View File

@@ -0,0 +1,12 @@
use actix_web::web;
use crate::note;
use crate::status;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(note::init())
.service(status::init()),
);
}

14
backend/src/client.rs Normal file
View File

@@ -0,0 +1,14 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Result};
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
Files::new("/", "./frontend/build")
.index_file("index.html")
.use_etag(true),
);
}
pub async fn index() -> Result<NamedFile> {
Ok(NamedFile::open("./frontend/build/index.html")?)
}

23
backend/src/config.rs Normal file
View File

@@ -0,0 +1,23 @@
use byte_unit::Byte;
lazy_static! {
pub static ref VERSION: String = option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string();
pub static ref LIMIT: u32 =
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
.unwrap()
.get_bytes() as u32;
pub static ref MAX_VIEWS: u32 = std::env::var("MAX_VIEWS")
.unwrap_or("100".to_string())
.parse()
.unwrap();
pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION")
.unwrap_or("360".to_string()) // 6 hours in minutes
.parse()
.unwrap();
pub static ref ALLOW_ADVANCED: bool = std::env::var("ALLOW_ADVANCED")
.unwrap_or("true".to_string())
.parse()
.unwrap();
}

View File

@@ -4,9 +4,12 @@ use dotenv::dotenv;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
mod api;
mod client; mod client;
mod config;
mod note; mod note;
mod size; mod size;
mod status;
mod store; mod store;
#[actix_web::main] #[actix_web::main]
@@ -17,9 +20,9 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::DefaultHeaders::default()) .wrap(middleware::DefaultHeaders::default())
.configure(size::init) .configure(size::init)
.configure(note::init) .configure(api::init)
.configure(client::init) .configure(client::init)
.default_service(web::resource("").route(web::get().to(client::fallback_fn))) .default_service(web::to(client::index))
}) })
.bind("0.0.0.0:5000")? .bind("0.0.0.0:5000")?
.run() .run()

View File

@@ -4,10 +4,10 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct Note { pub struct Note {
pub meta: String, pub meta: String,
pub contents: String, pub contents: String,
pub views: Option<u8>, pub views: Option<u32>,
pub expiration: Option<u32>, pub expiration: Option<u32>,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
@@ -15,13 +15,13 @@ pub struct NoteInfo {}
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct NotePublic { pub struct NotePublic {
pub meta: String, pub meta: String,
pub contents: String, pub contents: String,
} }
pub fn generate_id() -> String { pub fn generate_id() -> String {
let mut id: [u8; 32] = [0; 32]; let mut id: [u8; 32] = [0; 32];
let sr = ring::rand::SystemRandom::new(); let sr = ring::rand::SystemRandom::new();
let _ = sr.fill(&mut id); let _ = sr.fill(&mut id);
return bs62::encode_data(&id); return bs62::encode_data(&id);
} }

124
backend/src/note/routes.rs Normal file
View File

@@ -0,0 +1,124 @@
use actix_web::{delete, get, post, web, HttpResponse, Responder, Scope};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use crate::config;
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
use crate::store;
pub fn now() -> u32 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32
}
#[derive(Serialize, Deserialize)]
struct NotePath {
id: String,
}
#[get("/{id}")]
async fn one(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
None => return HttpResponse::NotFound().finish(),
Some(_) => return HttpResponse::Ok().json(NoteInfo {}),
}
}
#[derive(Serialize, Deserialize)]
struct CreateResponse {
id: String,
}
#[post("/")]
async fn create(note: web::Json<Note>) -> impl Responder {
let mut n = note.into_inner();
let id = generate_id();
let bad_req = HttpResponse::BadRequest().finish();
if n.views == None && n.expiration == None {
return bad_req;
}
if !*config::ALLOW_ADVANCED {
n.views = Some(1);
n.expiration = None;
}
match n.views {
Some(v) => {
if v > *config::MAX_VIEWS {
return bad_req;
}
n.expiration = None; // views overrides expiration
}
_ => {}
}
match n.expiration {
Some(e) => {
if e > *config::MAX_EXPIRATION {
return bad_req;
}
let expiration = now() + (e * 60);
n.expiration = Some(expiration);
}
_ => {}
}
store::set(&id.clone(), &n.clone());
return HttpResponse::Ok().json(CreateResponse { id: id });
}
#[delete("/{id}")]
async fn delete(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
None => return HttpResponse::NotFound().finish(),
Some(note) => {
let mut changed = note.clone();
if changed.views == None && changed.expiration == None {
return HttpResponse::BadRequest().finish();
}
match changed.views {
Some(v) => {
changed.views = Some(v - 1);
let id = p.id.clone();
if v <= 1 {
store::del(&id);
} else {
store::set(&id, &changed.clone());
}
}
_ => {}
}
let n = now();
match changed.expiration {
Some(e) => {
if e < n {
store::del(&p.id.clone());
return HttpResponse::BadRequest().finish();
}
}
_ => {}
}
return HttpResponse::Ok().json(NotePublic {
contents: changed.contents,
meta: changed.meta,
});
}
}
}
#[derive(Serialize, Deserialize)]
struct Status {
version: String,
max_size: usize,
}
pub fn init() -> Scope {
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
}

18
backend/src/size.rs Normal file
View File

@@ -0,0 +1,18 @@
use actix_web::web;
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) {
let json = web::JsonConfig::default().limit(*LIMIT);
let plain = web::PayloadConfig::default()
.limit(*LIMIT)
.mimetype(mime::STAR_STAR);
cfg.app_data(json).app_data(plain);
}

View File

@@ -0,0 +1,5 @@
mod model;
mod routes;
pub use model::*;
pub use routes::*;

View File

@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Status {
pub version: String,
pub max_size: u32,
pub max_views: u32,
pub max_expiration: u32,
pub allow_advanced: bool,
}

View File

@@ -0,0 +1,19 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::config;
use crate::status::Status;
#[get("/")]
async fn get_status() -> impl Responder {
return HttpResponse::Ok().json(Status {
version: config::VERSION.to_string(),
max_size: *config::LIMIT,
max_views: *config::MAX_VIEWS,
max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED,
});
}
pub fn init() -> Scope {
web::scope("/status").service(get_status)
}

36
backend/src/store.rs Normal file
View File

@@ -0,0 +1,36 @@
use memcache;
use crate::note::now;
use crate::note::Note;
lazy_static! {
static ref CLIENT: memcache::Client = memcache::connect(format!(
"memcache://{}?timeout=10&tcp_nodelay=true",
std::env::var("MEMCACHE").unwrap_or("127.0.0.1:11211".to_string())
))
.unwrap();
}
pub fn set(id: &String, note: &Note) {
let serialized = serde_json::to_string(&note.clone()).unwrap();
let expiration: u32 = match note.expiration {
Some(e) => e - now(),
None => 0,
};
CLIENT.set(id, serialized, expiration).unwrap();
}
pub fn get(id: &String) -> Option<Note> {
let value: Option<String> = CLIENT.get(&id).unwrap();
match value {
None => return None,
Some(s) => {
let deserialize: Note = serde_json::from_str(&s).unwrap();
return Some(deserialize);
}
}
}
pub fn del(id: &String) {
CLIENT.delete(id).unwrap();
}

View File

@@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

View File

@@ -1,25 +0,0 @@
{
"private": true,
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"licenses": "license-checker --summary > licenses.csv"
},
"type": "module",
"devDependencies": {
"@sveltejs/adapter-static": "^1.0.0-next.22",
"@sveltejs/kit": "^1.0.0-next.202",
"svelte": "^3.44.3",
"svelte-preprocess": "^4.10.1",
"tslib": "^2.3.1",
"typescript": "^4.5.4",
"vite": "^2.7.4"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.0",
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
}

638
client/pnpm-lock.yaml generated
View File

@@ -1,638 +0,0 @@
lockfileVersion: 5.3
specifiers:
'@fontsource/fira-mono': ^4.5.0
'@sveltejs/adapter-static': ^1.0.0-next.22
'@sveltejs/kit': ^1.0.0-next.202
copy-to-clipboard: ^3.3.1
file-saver: ^2.0.5
pretty-bytes: ^5.6.0
svelte: ^3.44.3
svelte-preprocess: ^4.10.1
tslib: ^2.3.1
typescript: ^4.5.4
vite: ^2.7.4
dependencies:
'@fontsource/fira-mono': 4.5.0
copy-to-clipboard: 3.3.1
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies:
'@sveltejs/adapter-static': 1.0.0-next.22
'@sveltejs/kit': 1.0.0-next.202_svelte@3.44.3
svelte: 3.44.3
svelte-preprocess: 4.10.1_svelte@3.44.3+typescript@4.5.4
tslib: 2.3.1
typescript: 4.5.4
vite: 2.7.4
packages:
/@fontsource/fira-mono/4.5.0:
resolution: {integrity: sha512-KE+d3wmgq/YKM0BqgUF7p2yeBNi805Nfof1lC1wJ7E9i2EWoC363sGdKG+MQBVm+ei3GYZu+Bo8Xha1w1pkB7g==}
dev: false
/@rollup/pluginutils/4.1.2:
resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
engines: {node: '>= 8.0.0'}
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
dev: true
/@sveltejs/adapter-static/1.0.0-next.22:
resolution: {integrity: sha512-Dc1V9Z72dA7caVwNxxzl9Jhcq4uN9N1udA2GKNTLMu3aWX3Cq+v6C2CddY9Aazr+F9h6J0vi9AienuH+ySRXzQ==}
dev: true
/@sveltejs/kit/1.0.0-next.202_svelte@3.44.3:
resolution: {integrity: sha512-rXmJ0FplkWvD1CaeCfejRYhOJYrlmeUm5Fkw7gIKDdWPQev5rqOhd9B9ZvRpq35oMqCAwaOfK+e5S6k+83feEQ==}
engines: {node: '>=14.13'}
hasBin: true
peerDependencies:
svelte: ^3.44.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.3+vite@2.7.4
cheap-watch: 1.0.4
sade: 1.7.4
svelte: 3.44.3
vite: 2.7.4
transitivePeerDependencies:
- diff-match-patch
- less
- sass
- stylus
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.32_svelte@3.44.3+vite@2.7.4:
resolution: {integrity: sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g==}
engines: {node: ^14.13.1 || >= 16}
peerDependencies:
diff-match-patch: ^1.0.5
svelte: ^3.44.0
vite: ^2.7.0
peerDependenciesMeta:
diff-match-patch:
optional: true
dependencies:
'@rollup/pluginutils': 4.1.2
debug: 4.3.3
kleur: 4.1.4
magic-string: 0.25.7
require-relative: 0.8.7
svelte: 3.44.3
svelte-hmr: 0.14.7_svelte@3.44.3
vite: 2.7.4
transitivePeerDependencies:
- supports-color
dev: true
/@types/node/17.0.1:
resolution: {integrity: sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ==}
dev: true
/@types/pug/2.0.5:
resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}
dev: true
/@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies:
'@types/node': 17.0.1
dev: true
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/buffer-crc32/0.2.13:
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
dev: true
/cheap-watch/1.0.4:
resolution: {integrity: sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==}
engines: {node: '>=8'}
dev: true
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
/copy-to-clipboard/3.3.1:
resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==}
dependencies:
toggle-selection: 1.0.6
dev: false
/debug/4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/detect-indent/6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
dev: true
/es6-promise/3.3.1:
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
dev: true
/esbuild-android-arm64/0.13.15:
resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-64/0.13.15:
resolution: {integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-arm64/0.13.15:
resolution: {integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-64/0.13.15:
resolution: {integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-arm64/0.13.15:
resolution: {integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-32/0.13.15:
resolution: {integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-64/0.13.15:
resolution: {integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm/0.13.15:
resolution: {integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm64/0.13.15:
resolution: {integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-mips64le/0.13.15:
resolution: {integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-ppc64le/0.13.15:
resolution: {integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-netbsd-64/0.13.15:
resolution: {integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-openbsd-64/0.13.15:
resolution: {integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-sunos-64/0.13.15:
resolution: {integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-32/0.13.15:
resolution: {integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-64/0.13.15:
resolution: {integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-arm64/0.13.15:
resolution: {integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild/0.13.15:
resolution: {integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==}
hasBin: true
requiresBuild: true
optionalDependencies:
esbuild-android-arm64: 0.13.15
esbuild-darwin-64: 0.13.15
esbuild-darwin-arm64: 0.13.15
esbuild-freebsd-64: 0.13.15
esbuild-freebsd-arm64: 0.13.15
esbuild-linux-32: 0.13.15
esbuild-linux-64: 0.13.15
esbuild-linux-arm: 0.13.15
esbuild-linux-arm64: 0.13.15
esbuild-linux-mips64le: 0.13.15
esbuild-linux-ppc64le: 0.13.15
esbuild-netbsd-64: 0.13.15
esbuild-openbsd-64: 0.13.15
esbuild-sunos-64: 0.13.15
esbuild-windows-32: 0.13.15
esbuild-windows-64: 0.13.15
esbuild-windows-arm64: 0.13.15
dev: true
/estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/file-saver/2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/graceful-fs/4.2.8:
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-core-module/2.8.0:
resolution: {integrity: sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==}
dependencies:
has: 1.0.3
dev: true
/kleur/4.1.4:
resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
engines: {node: '>=6'}
dev: true
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
sourcemap-codec: 1.4.8
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
dev: true
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist/1.2.5:
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
dev: true
/mkdirp/0.5.5:
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
hasBin: true
dependencies:
minimist: 1.2.5
dev: true
/mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
dev: true
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid/3.1.30:
resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
engines: {node: '>=8.6'}
dev: true
/postcss/8.4.5:
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.1.30
picocolors: 1.0.0
source-map-js: 1.0.1
dev: true
/pretty-bytes/5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: false
/require-relative/0.8.7:
resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
is-core-module: 2.8.0
path-parse: 1.0.7
dev: true
/rimraf/2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.0
dev: true
/rollup/2.61.1:
resolution: {integrity: sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/sade/1.7.4:
resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}
engines: {node: '>= 6'}
dependencies:
mri: 1.2.0
dev: true
/sander/0.5.1:
resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
dependencies:
es6-promise: 3.3.1
graceful-fs: 4.2.8
mkdirp: 0.5.5
rimraf: 2.7.1
dev: true
/sorcery/0.10.0:
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
hasBin: true
dependencies:
buffer-crc32: 0.2.13
minimist: 1.2.5
sander: 0.5.1
sourcemap-codec: 1.4.8
dev: true
/source-map-js/1.0.1:
resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
engines: {node: '>=0.10.0'}
dev: true
/sourcemap-codec/1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
dev: true
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
dependencies:
min-indent: 1.0.1
dev: true
/svelte-hmr/0.14.7_svelte@3.44.3:
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.44.3
dev: true
/svelte-preprocess/4.10.1_svelte@3.44.3+typescript@4.5.4:
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
postcss: ^7 || ^8
postcss-load-config: ^2.1.0 || ^3.0.0
pug: ^3.0.0
sass: ^1.26.8
stylus: ^0.54.7
sugarss: ^2.0.0
svelte: ^3.23.0
typescript: ^4.5.2
peerDependenciesMeta:
'@babel/core':
optional: true
coffeescript:
optional: true
less:
optional: true
node-sass:
optional: true
postcss:
optional: true
postcss-load-config:
optional: true
pug:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
typescript:
optional: true
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.43.1
detect-indent: 6.1.0
magic-string: 0.25.7
sorcery: 0.10.0
strip-indent: 3.0.0
svelte: 3.44.3
typescript: 4.5.4
dev: true
/svelte/3.44.3:
resolution: {integrity: sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==}
engines: {node: '>= 8'}
dev: true
/toggle-selection/1.0.6:
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
dev: false
/tslib/2.3.1:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
dev: true
/typescript/4.5.4:
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/vite/2.7.4:
resolution: {integrity: sha512-f+0426k9R/roz5mRNwJlQ+6UOnhCwIypJSbfgCmsVzVJe9jTTM5iRX2GWYUean+iqPBWaU/dYLryx9AoH2pmrw==}
engines: {node: '>=12.2.0'}
hasBin: true
peerDependencies:
less: '*'
sass: '*'
stylus: '*'
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
dependencies:
esbuild: 0.13.15
postcss: 8.4.5
resolve: 1.20.0
rollup: 2.61.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@@ -1,34 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte'
export let icon: string = ''
export let href: string = ''
$: src = href || `/icons/${icon}.svg`
let html = null
onMount(async () => {
html = await fetch(src).then((res) => res.text())
})
</script>
{#if html === null}
<img on:click {...$$restProps} {src} alt={icon} />
{:else}
<div on:click {...$$restProps}>
{@html html}
</div>
{/if}
<style>
img,
div {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
display: block;
fill: currentColor;
}
</style>

View File

@@ -1,32 +0,0 @@
<script lang="ts">
import { init } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
init()
})
</script>
<svelte:head>
<title>cryptgeon</title>
</svelte:head>
<main>
<Header />
<slot />
</main>
<Footer />
<style>
main {
padding: 1rem;
padding-bottom: 4rem;
width: 100%;
max-width: 35rem;
margin: 0 auto;
}
</style>

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Contrast</title><path d='M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z'/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Copy</title><path d='M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z'/><path d='M112 80h288V56a24 24 0 00-24-24H60a28 28 0 00-28 28v316a24 24 0 0024 24h24V112a32 32 0 0132-32z'/></svg>

Before

Width:  |  Height:  |  Size: 313 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Dice</title><path d='M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z'/></svg>

Before

Width:  |  Height:  |  Size: 728 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye Off</title><path d='M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z'/><path d='M256 352a96 96 0 01-92.6-121.34l-69.07-69.08C66.12 187.42 39.24 221.14 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416A233.47 233.47 0 00335 402.2l-53.61-53.6A95.84 95.84 0 01256 352zM256 160a96 96 0 0192.6 121.34L419.26 352c29.15-26.25 56.07-61.56 76.74-96-26.38-43.43-62.9-88.56-101.18-114.82C351.1 111.2 304.31 96 255.76 96a222.92 222.92 0 00-78.21 14.29l53.11 53.11A95.84 95.84 0 01256 160z'/></svg>

Before

Width:  |  Height:  |  Size: 720 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye</title><circle cx='256' cy='256' r='64'/><path d='M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z'/></svg>

Before

Width:  |  Height:  |  Size: 474 B

View File

@@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Lock Closed</title><path d='M420 192h-68v-80a96 96 0 10-192 0v80H92a12 12 0 00-12 12v280a12 12 0 0012 12h328a12 12 0 0012-12V204a12 12 0 00-12-12zm-106 0H198v-80.75a58 58 0 11116 0z'/></svg>

Before

Width:  |  Height:  |  Size: 275 B

View File

@@ -1,30 +0,0 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020"],
"target": "es2019",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}

View File

@@ -1,5 +0,0 @@
{
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false
}

View File

@@ -7,7 +7,7 @@ services:
memcached: memcached:
image: memcached:1-alpine image: memcached:1-alpine
restart: unless-stopped restart: unless-stopped
entrypoint: memcached -m 128M -I 4M entrypoint: memcached -m 256M -I 128M
ports: ports:
- 11211:11211 - 11211:11211
@@ -16,6 +16,6 @@ services:
depends_on: depends_on:
- memcached - memcached
environment: environment:
SIZE_LIMIT: 4M SIZE_LIMIT: 128M
ports: ports:
- 80:5000 - 80:5000

11
entry.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/ash
echo "Waiting for memcached"
while ! nc -z -w 1 memcached 11211; do
sleep 1
echo "..."
done
echo "Starting server"
/app/cryptgeon

View File

@@ -3,7 +3,7 @@ version: '3.8'
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 256 -I 128 # Limit to 128 MB Ram, customize at free will. -m must be at least double than -I.
app: app:
image: cupcakearmy/cryptgeon:latest image: cupcakearmy/cryptgeon:latest

161
examples/scratch/README.md Normal file
View File

@@ -0,0 +1,161 @@
# Install from scratch.
This is a tiny guide to install cryptgeon on (probably) any unix system (and maybe windows?) from scratch using traefik as the proxy, which will manage certificates and handle https for us.
1. Install Docker & Docker Compose.
2. Install Traefik.
3. Run the cryptgeon.
4. [Optional] install watchtower to keep up to date.
## Install Docker & DOcker Compose
- [Docker](https://docs.docker.com/engine/install/)
- [Compose](https://docs.docker.com/compose/install/)
## Install Traefik 2.0
[Traefik](https://doc.traefik.io/traefik/) is a router & proxy that makes deployment of containers incredibly easy. It will manage all the https certificates, routing, etc.
```sh
/foo/bar/traefik/
├── docker-compose.yaml
└── traefik.yaml
```
```yaml
# docker-compose.yaml
version: '3.8'
services:
traefik:
image: traefik:2.6
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.yaml:/etc/traefik/traefik.yaml:ro
- ./data:/data
labels:
- 'traefik.enable=true'
# HTTP to HTTPS redirection
- 'traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)'
- 'traefik.http.routers.http_catchall.entrypoints=insecure'
- 'traefik.http.routers.http_catchall.middlewares=https_redirect'
- 'traefik.http.middlewares.https_redirect.redirectscheme.scheme=https'
- 'traefik.http.middlewares.https_redirect.redirectscheme.permanent=true'
networks:
default:
external: true
name: proxy
```
```yaml
# traefik.yaml
api:
dashboard: true
# Define HTTP and HTTPS entrypoint
entryPoints:
insecure:
address: ':80'
secure:
address: ':443'
# Dynamic configuration will come from docker labels
providers:
docker:
endpoint: 'unix:///var/run/docker.sock'
network: 'proxy'
exposedByDefault: false
# Enable acme with http file challenge
certificatesResolvers:
le:
acme:
email: me@example.org
storage: /data/acme.json
httpChallenge:
entryPoint: insecure
```
**Run**
```sh
docker network create proxy
docker-compose up -d
```
## Cryptgeon
Create another docker-compose.yaml file in another folder. We will assume that the domain is `cryptgeon.example.org`.
```sh
/foo/bar/cryptgeon/
└── docker-compose.yaml
```
```yaml
version: '3.8'
networks:
proxy:
external: true
services:
memcached:
image: memcached:1-alpine
restart: unless-stopped
entrypoint: memcached -m 256M -I 4M # Limit to 128 MB Ram, customize at free will.
app:
image: cupcakearmy/cryptgeon:latest
restart: unless-stopped
depends_on:
- memcached
environment:
SIZE_LIMIT: 4 MiB
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.cryptgeon.rule=Host(`cryptgeon.example.org`)
- traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le
```
**Run**
```sh
docker-compose up -d
```
## Watchtower
> A container-based solution for automating Docker container base image updates.
[Watchtower](https://containrrr.dev/watchtower/) will keep our containers up to date. The interval is set to once a day and also configured to delete old images to prevent cluttering.
```sh
/foo/bar/watchtower/
└── docker-compose.yaml
```
```yaml
# docker-compose.yaml
version: '3.8'
services:
watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --cleanup --interval 86400
```

18
frontend/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Cryptgeon Frontend
## Locale
Download with these settings:
```json
{
"format": "json",
"indentation": "tab",
"json_unescaped_slashes": true,
"export_sort": "first_added",
"original_filenames": false,
"export_empty_as": "skip",
"add_newline_eof": true,
"replace_breaks": false
}
```

42
frontend/locales/de.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "Hinweis",
"file": "Datei",
"advanced": "erweitert",
"create": "erstellen",
"loading": "Läd...",
"mode": "Modus",
"views": "{n, plural, =0 {Ansichten} =1 {1 Ansicht} other {# Ansichten}}",
"minutes": "{n, plural, =0 {Minuten} =1 {1 Minute} other {# Minuten}}",
"max": "max",
"share_link": "Link teilen",
"copy_clipboard": "in die Zwischenablage kopieren"
},
"home": {
"intro": "Senden Sie ganz einfach <i>vollständig verschlüsselte</i>, sichere Notizen oder Dateien mit einem Klick. Erstellen Sie einfach eine Notiz und teilen Sie den Link.",
"explanation": "die Notiz verfällt und wird nach {type} zerstört.",
"new_note": "neue Note",
"new_note_notice": "<b>Verfügbarkeit:</b><br />es ist nicht garantiert, dass die Notiz gespeichert wird, da alles im Speicher gehalten wird. Wenn dieser voll ist, werden die ältesten Notizen entfernt.<br />(Sie werden wahrscheinlich keine Probleme haben, seien Sie nur gewarnt).",
"errors": {
"note_to_big": "Notiz konnte nicht erstellt werden. Notiz ist zu groß",
"note_error": "konnte keine Notiz erstellen. Bitte versuchen Sie es erneut.",
"max": "max: {n}",
"empty_content": "Notiz ist leer."
},
"copied_to_clipboard": "in die Zwischenablage kopiert 🔗"
},
"show": {
"errors": {
"not_found": "wurde nicht gefunden oder wurde bereits gelöscht.",
"decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört."
},
"explanation": "Klicken Sie unten, um die Notiz anzuzeigen und zu löschen, wenn der Zähler sein Limit erreicht hat",
"show_note": "Notiz anzeigen",
"warning_will_not_see_again": "haben Sie <b>keine</b> Gelegenheit, die Notiz noch einmal zu sehen.",
"download_all": "alle herunterladen"
},
"file_upload": {
"selected_files": "Ausgewählte Dateien",
"no_files_selected": "Keine Dateien ausgewählt"
}
}

42
frontend/locales/en.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "note",
"file": "file",
"advanced": "advanced",
"create": "create",
"loading": "loading...",
"mode": "mode",
"views": "{n, plural, =0 {views} =1 {1 view} other {# views}}",
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
"max": "max",
"share_link": "share link",
"copy_clipboard": "copy to clipboard"
},
"home": {
"intro": "Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and share the link.",
"explanation": "the note will expire and be destroyed after {type}.",
"new_note": "new note",
"new_note_notice": "<b>availability:</b><br />the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.<br />(you probably will be fine, just be warned.)",
"errors": {
"note_to_big": "could not create note. note is to big",
"note_error": "could not create note. please try again.",
"max": "max: {n}",
"empty_content": "note is empty."
},
"copied_to_clipboard": "copied to clipboard 🔗"
},
"show": {
"errors": {
"not_found": "note was not found or was already deleted.",
"decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed."
},
"explanation": "click below to show and delete the note if the counter has reached it's limit",
"show_note": "show note",
"warning_will_not_see_again": "you will <b>not</b> get the chance to see the note again.",
"download_all": "download all"
},
"file_upload": {
"selected_files": "Selected Files",
"no_files_selected": "No Files Selected"
}
}

42
frontend/locales/es.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "nota",
"file": "archivo",
"advanced": "avanzado",
"create": "crear",
"loading": "cargando...",
"mode": "modo",
"views": "{n, plural, =0 {vistas} =1 {1 vista} other {# vistas}}",
"minutes": "{n, plural, =0 {minutos} =1 {1 minuto} other {# minutos}}",
"max": "max",
"share_link": "compartir enlace",
"copy_clipboard": "copiar al portapapeles"
},
"home": {
"intro": "Envía fácilmente notas o archivos <i>totalmente encriptados</i> y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.",
"explanation": "la nota expirará y se destruirá después de {type}.",
"new_note": "nueva nota",
"new_note_notice": "<b>disponibilidad:</b><br />no se garantiza que la nota se almacene, ya que todo se guarda en la memoria RAM, si se llena se eliminarán las notas más antiguas.<br />(probablemente estará bien, sólo está advertido.)",
"errors": {
"note_to_big": "no se pudo crear la nota. la nota es demasiado grande",
"note_error": "No se ha podido crear la nota. Por favor, inténtelo de nuevo.",
"max": "max: {n}",
"empty_content": "la nota está vacía."
},
"copied_to_clipboard": "copiado al portapapeles 🔗"
},
"show": {
"errors": {
"not_found": "la nota no se encontró o ya fue borrada.",
"decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida."
},
"explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite",
"show_note": "mostrar nota",
"warning_will_not_see_again": " <b>no</b> tendrás la oportunidad de volver a ver la nota.",
"download_all": "descargar todo"
},
"file_upload": {
"selected_files": "Archivos seleccionados",
"no_files_selected": "No hay archivos seleccionados"
}
}

42
frontend/locales/fr.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "note",
"file": "fichier",
"advanced": "avancé",
"create": "créer",
"loading": "chargement...",
"mode": "mode",
"views": "{n, plural, =0 {vues} =1 {1 vue} other {# vues}}",
"minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}",
"max": "max",
"share_link": "partager le lien",
"copy_clipboard": "copier dans le presse-papiers"
},
"home": {
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement cryptés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.",
"explanation": "la note expirera et sera détruite après {type}.",
"new_note": "nouvelle note",
"new_note_notice": "<b>disponibilité :</b><br />la note n'est pas garantie d'être stockée car tout est conservé dans la mémoire vive, si elle se remplit les notes les plus anciennes seront supprimées.<br />(vous serez probablement bien, soyez juste averti.)",
"errors": {
"note_to_big": "Impossible de créer une note. La note est trop grande",
"note_error": "n'a pas pu créer de note. Veuillez réessayer.",
"max": "max: {n}",
"empty_content": "La note est vide."
},
"copied_to_clipboard": "copié dans le presse-papiers 🔗"
},
"show": {
"errors": {
"not_found": "La note n'a pas été trouvée ou a déjà été supprimée.",
"decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite."
},
"explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.",
"show_note": "note de présentation",
"warning_will_not_see_again": "vous <b>n'aurez pas</b> la chance de revoir la note.",
"download_all": "télécharger tout"
},
"file_upload": {
"selected_files": "Fichiers sélectionnés",
"no_files_selected": "Aucun fichier sélectionné"
}
}

42
frontend/locales/it.json Normal file
View File

@@ -0,0 +1,42 @@
{
"common": {
"note": "nota",
"file": "file",
"advanced": "avanzato",
"create": "crea",
"loading": "carica...",
"mode": "modalita",
"views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}",
"minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}",
"max": "max",
"share_link": "condividi link",
"copy_clipboard": "copia negli appunti"
},
"home": {
"intro": "Invia facilmente note o file <i>completamente criptati</i> e sicuri con un solo clic. Basta creare una nota e condividere il link.",
"explanation": "la nota scadrà e sarà distrutta dopo {type}.",
"new_note": "nuova nota",
"new_note_notice": "<b>disponibilità:</b><br />la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.<br />(probabilmente andrà bene, basta essere avvertiti).",
"errors": {
"note_to_big": "impossibile creare una nota. la nota è troppo grande",
"note_error": "Impossibile creare la nota. Riprova.",
"max": "max: {n}",
"empty_content": "la nota è vuota."
},
"copied_to_clipboard": "copiato negli appunti 🔗"
},
"show": {
"errors": {
"not_found": "non è stata trovata o è stata già cancellata.",
"decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta."
},
"explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite",
"show_note": "mostra la nota",
"warning_will_not_see_again": " <b>non</b> avrete la possibilità di rivedere la nota.",
"download_all": "scarica tutti"
},
"file_upload": {
"selected_files": "File selezionati",
"no_files_selected": "Nessun file selezionato"
}
}

35
frontend/package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"private": true,
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"check": "svelte-check --tsconfig tsconfig.json",
"licenses": "license-checker --summary > licenses.csv",
"locale:download": "node scripts/locale.js"
},
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^7.2.0",
"@sveltejs/adapter-static": "^1.0.0-next.34",
"@sveltejs/kit": "^1.0.0-next.348",
"@types/file-saver": "^2.0.5",
"@types/sanitize-html": "^2.6.2",
"adm-zip": "^0.5.9",
"dotenv": "^16.0.1",
"svelte": "^3.48.0",
"svelte-check": "^2.7.2",
"svelte-intl-precompile": "^0.10.1",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.0",
"typescript": "^4.7.3",
"vite": "^2.9.10"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.8",
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0",
"sanitize-html": "^2.7.0"
}
}

1644
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
import dotenv from 'dotenv'
import { LokaliseApi } from '@lokalise/node-api'
import https from 'https'
import AdmZip from 'adm-zip'
dotenv.config()
const apiKey = process.env.LOKALISE_API_KEY
const project_id = process.env.LOKALISE_PROJECT
if (!apiKey) throw new Error('No API Key set for Lokalize! Set with "LOKALISE_API_KEY"')
if (!project_id) throw new Error('No project id set for Lokalize! Set with "LOKALISE_PROJECT"')
const client = new LokaliseApi({ apiKey })
const WGet = (url) =>
new Promise((done) => {
https
.get(url, (res) => {
const data = []
res
.on('data', (chunk) => {
data.push(chunk)
})
.on('end', () => {
let buffer = Buffer.concat(data)
done(buffer)
})
})
.on('error', (err) => {
console.log('download error:', err)
})
})
async function download() {
// For details see: https://app.lokalise.com/api2docs/curl/#transition-download-files-post
const download = await client.files().download(project_id, {
format: 'json',
indentation: 'tab',
json_unescaped_slashes: true,
original_filenames: false,
bundle_structure: '%LANG_ISO%.%FORMAT%',
export_sort: 'first_added',
export_empty_as: 'skip',
add_newline_eof: true,
replace_breaks: false,
})
const buffered = await WGet(download.bundle_url)
const zip = new AdmZip(buffered)
zip.extractAllTo('./locales', true)
}
download().catch((e) => {
console.error(e)
process.exit(1)
})

View File

@@ -5,9 +5,9 @@
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head% %sveltekit.head%
</head> </head>
<body> <body>
<div id="svelte">%svelte.body%</div> <div id="svelte">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@@ -7,7 +7,7 @@ export class Files {
}) })
} }
static fromString(s: string): Promise<Blob> { static async fromString(s: string): Promise<Blob> {
return fetch(s).then((r) => r.blob()) return fetch(s).then((r) => r.blob())
} }
} }

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Contrast</title><path
d="M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z"
/></svg
>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Copy</title><path
d="M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z"
/><path
d="M112 80h288V56a24 24 0 00-24-24H60a28 28 0 00-28 28v316a24 24 0 0024 24h24V112a32 32 0 0132-32z"
/></svg
>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Dice</title><path
d="M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z"
/></svg
>

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Eye</title><circle cx="256" cy="256" r="64" /><path
d="M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z"
/></svg
>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"
><title>Eye Off</title><path
d="M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z"
/><path
d="M256 352a96 96 0 01-92.6-121.34l-69.07-69.08C66.12 187.42 39.24 221.14 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416A233.47 233.47 0 00335 402.2l-53.61-53.6A95.84 95.84 0 01256 352zM256 160a96 96 0 0192.6 121.34L419.26 352c29.15-26.25 56.07-61.56 76.74-96-26.38-43.43-62.9-88.56-101.18-114.82C351.1 111.2 304.31 96 255.76 96a222.92 222.92 0 00-78.21 14.29l53.11 53.11A95.84 95.84 0 01256 160z"
/></svg
>

After

Width:  |  Height:  |  Size: 732 B

View File

@@ -4,13 +4,16 @@ import { writable } from 'svelte/store'
export type Status = { export type Status = {
version: string version: string
max_size: number max_size: number
max_views: number
max_expiration: number
allow_advanced: boolean
} }
export const status = writable<null | Status>(null) export const status = writable<null | Status>(null)
export async function init() { export async function init() {
const data = await call({ const data = await call({
url: 'status', url: 'status/',
method: 'get', method: 'get',
}) })
status.set(data) status.set(data)

View File

@@ -2,6 +2,8 @@
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 { t } from 'svelte-intl-precompile'
import Button from './Button.svelte'
import MaxSize from './MaxSize.svelte' import MaxSize from './MaxSize.svelte'
export let label: string = '' export let label: string = ''
@@ -11,8 +13,8 @@
async function onInput(e: Event) { async function onInput(e: Event) {
const input = e.target as HTMLInputElement const input = e.target as HTMLInputElement
if (input.files.length) { if (input?.files?.length) {
files = Array.from(input.files) files = [...files, ...Array.from(input.files)]
const data: FileDTO[] = await Promise.all( const data: FileDTO[] = await Promise.all(
files.map(async (file) => ({ files.map(async (file) => ({
name: file.name, name: file.name,
@@ -21,15 +23,17 @@
contents: await Files.toString(file), contents: await Files.toString(file),
})) }))
) )
console.debug(
'files',
data.map((d) => d.contents.length)
)
dispatch('file', JSON.stringify(data)) dispatch('file', JSON.stringify(data))
} else { } else {
dispatch('file', '') dispatch('file', '')
} }
} }
function clear(e: Event) {
e.preventDefault()
files = []
dispatch('file', '')
}
</script> </script>
<label> <label>
@@ -40,18 +44,20 @@
<div class="box"> <div class="box">
{#if files.length} {#if files.length}
<div> <div>
<b>Selected Files</b> <b>{$t('file_upload.selected_files')}</b>
{#each files as file} {#each files as file}
<div class="file"> <div class="file">
{file.name} {file.name}
</div> </div>
{/each} {/each}
<div class="spacer" />
<Button on:click={clear}>Clear</Button>
</div> </div>
{:else} {:else}
<div> <div>
<b>No Files Selected</b> <b>{$t('file_upload.no_files_selected')}</b>
<br /> <br />
<small>max: <MaxSize /></small> <small>{$t('common.max')}: <MaxSize /></small>
</div> </div>
{/if} {/if}
</div> </div>
@@ -69,4 +75,8 @@
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
} }
.spacer {
margin-top: 1rem;
}
</style> </style>

View File

@@ -0,0 +1,37 @@
<script lang="ts" context="module">
import IconContrast from '$lib/icons/IconContrast.svelte'
import IconCopy from '$lib/icons/IconCopy.svelte'
import IconDice from '$lib/icons/IconDice.svelte'
import IconEye from '$lib/icons/IconEye.svelte'
import IconEyeOff from '$lib/icons/IconEyeOff.svelte'
const map = {
contrast: IconContrast,
copy: IconCopy,
dice: IconDice,
eye: IconEye,
'eye-off': IconEyeOff,
}
</script>
<script lang="ts">
export let icon: keyof typeof map
</script>
<div on:click {...$$restProps}>
{#if map[icon]}
<svelte:component this={map[icon]} />
{/if}
</div>
<style>
div {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
display: block;
fill: currentColor;
}
</style>

View File

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

View File

@@ -4,10 +4,13 @@
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import sanitize from 'sanitize-html'
import { t } from 'svelte-intl-precompile'
import Button from './Button.svelte' import Button from './Button.svelte'
export let note: NotePublic export let note: NotePublic
const RE_URL = /[A-Za-z]+:\/\/([A-Z a-z0-9\-._~:\/?#\[\]@!$&'()*+,;%=])+/g
let files: FileDTO[] = [] let files: FileDTO[] = []
$: if (note.meta.type === 'file') { $: if (note.meta.type === 'file') {
@@ -26,22 +29,30 @@
}) })
saveAs(f) saveAs(f)
} }
function contentWithLinks(content: string): string {
const replaced = note.contents.replace(
RE_URL,
(url) => `<a href="${url}" rel="noreferrer">${url}</a>`
)
return sanitize(replaced, { allowedTags: ['a'], allowedAttributes: { a: ['href', 'rel'] } })
}
</script> </script>
<p class="error-text">you will <b>not</b> get the chance to see the note again.</p> <p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
{#if note.meta.type === 'text'} {#if note.meta.type === 'text'}
<div class="note" data-testid="note-result"> <div class="note">
{note.contents} {@html contentWithLinks(note.contents)}
</div> </div>
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button> <Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
{:else} {:else}
{#each files as file} {#each files as file}
<div class="note file" data-testid="note-result"> <div class="note file">
<b on:click={() => downloadFile(file)}> {file.name}</b> <b on:click={() => downloadFile(file)}> {file.name}</b>
<small> {file.type} {prettyBytes(file.size)}</small> <small> {file.type} {prettyBytes(file.size)}</small>
</div> </div>
{/each} {/each}
<Button on:click={download}>download all</Button> <Button on:click={download}>{$t('show.download_all')}</Button>
{/if} {/if}
<style> <style>
@@ -59,6 +70,9 @@
.note b { .note b {
cursor: pointer; cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.note.file { .note.file {
@@ -66,4 +80,8 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.note.file small {
padding-left: 1rem;
}
</style> </style>

View File

@@ -49,7 +49,7 @@
height: 2rem; height: 2rem;
width: 1.25rem; width: 1.25rem;
left: 0.125rem; left: 0.125rem;
bottom: 0.1rem; bottom: 0.125rem;
background-color: var(--ui-bg-1); background-color: var(--ui-bg-1);
-webkit-transition: 0.4s; -webkit-transition: 0.4s;
transition: var(--ui-anim); transition: var(--ui-anim);

View File

@@ -1,19 +1,23 @@
<script lang="ts"> <script lang="ts">
import { getRandomBytes, Hex } from '$lib/crypto' import { getRandomBytes, Hex } from '$lib/crypto'
import copyToClipboard from 'copy-to-clipboard' import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile'
import { fade } from 'svelte/transition'
import Icon from './Icon.svelte' import Icon from './Icon.svelte'
export let label: string = '' export let label: string = ''
export let value export let value: any
export let validate: (value: any) => boolean | string = () => true
export let copy: boolean = false export let copy: boolean = false
export let random: boolean = false export let random: boolean = false
const initialType = $$restProps.type const initialType = $$restProps.type
const isPassword = initialType === 'password' const isPassword = initialType === 'password'
let hidden = true let hidden = true
let notification: string | null = null
let notificationTimeout: NodeJS.Timeout | null = null
$: valid = validate(value)
$: if (isPassword) { $: if (isPassword) {
value value
@@ -24,29 +28,46 @@
hidden = !hidden hidden = !hidden
} }
function copyFN() { function copyFN() {
copyToClipboard(value) copyToClipboard(value.toString())
notify($t('home.copied_to_clipboard'))
} }
function randomFN() { function randomFN() {
value = Hex.encode(getRandomBytes(20)) value = Hex.encode(getRandomBytes(20))
} }
function notify(msg: string, delay: number = 2000) {
if (notificationTimeout) {
clearTimeout(notificationTimeout)
}
notificationTimeout = setTimeout(() => {
notification = null
}, delay)
notification = msg
}
</script> </script>
<label> <label>
<small disabled={$$restProps.disabled}> <small disabled={$$restProps.disabled}>
{label} {label}
{#if valid !== true}
<span class="error-text">{valid}</span>
{/if}
</small> </small>
<input bind:value {...$$restProps} /> <input bind:value {...$$restProps} class:valid={valid === true} />
<div class="icons"> <div class="icons">
{#if isPassword} {#if isPassword}
<Icon class="icon" icon={hidden ? 'eye-sharp' : 'eye-off-sharp'} on:click={toggle} /> <Icon class="icon" icon={hidden ? 'eye' : 'eye-off'} on:click={toggle} />
{/if} {/if}
{#if random} {#if random}
<Icon class="icon" icon="dice-sharp" on:click={randomFN} /> <Icon class="icon" icon="dice" on:click={randomFN} />
{/if} {/if}
{#if copy} {#if copy}
<Icon class="icon" icon="copy-sharp" on:click={copyFN} /> <Icon class="icon" icon="copy" on:click={copyFN} />
{/if} {/if}
</div> </div>
{#if notification}
<div class="notification" transition:fade><small>{notification}</small></div>
{/if}
</label> </label>
<style> <style>
@@ -55,6 +76,10 @@
display: block; display: block;
} }
label > small {
display: block;
}
input { input {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -68,6 +93,10 @@
border-color: var(--ui-clr-primary); border-color: var(--ui-clr-primary);
} }
input:not(.valid) {
border-color: var(--ui-clr-error);
}
.icons { .icons {
border: 1px red; border: 1px red;
position: absolute; position: absolute;
@@ -88,4 +117,11 @@
.icons > :global(.icon:hover) { .icons > :global(.icon:hover) {
border-color: var(--ui-clr-primary); border-color: var(--ui-clr-primary);
} }
.notification {
text-align: right;
position: absolute;
right: 0;
bottom: -1.5em;
}
</style> </style>

View File

@@ -41,7 +41,7 @@
</script> </script>
<div on:click={change}> <div on:click={change}>
<Icon class="icon" icon="contrast-sharp" /> <Icon class="icon" icon="contrast" />
{$theme} {$theme}
</div> </div>

View File

@@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
import { create, Note, PayloadToLargeError } from '$lib/api' import { create, Note, PayloadToLargeError } from '$lib/api'
import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto' import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto'
import { status } from '$lib/stores/status'
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 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 { t } from 'svelte-intl-precompile'
import { blur } from 'svelte/transition' import { blur } from 'svelte/transition'
let note: Note = { let note: Note = {
@@ -18,20 +20,24 @@
let result: { password: string; id: string } | null = null let result: { password: string; id: string } | null = null
let advanced = false let advanced = false
let file = false let file = false
let type = false let timeExpiration = false
let message = '' let message = ''
let loading = false let loading = false
let error: string | null = null let error: string | null = null
$: if (!advanced) { $: if (!advanced) {
note.views = 1 note.views = 1
type = false timeExpiration = false
} }
$: { $: {
let fraction: string message = $t('home.explanation', {
fraction = type ? `${note.expiration} minutes` : `${note.views} views` values: {
message = 'the note will expire and be destroyed after ' + fraction type: $t(timeExpiration ? 'common.minutes' : 'common.views', {
values: { n: (timeExpiration ? note.expiration : note.views) ?? '?' },
}),
},
})
} }
$: note.meta.type = file ? 'file' : 'text' $: note.meta.type = file ? 'file' : 'text'
@@ -40,20 +46,21 @@
note.contents = '' note.contents = ''
} }
class EmptyContentError extends Error {}
async function submit() { async function submit() {
try { try {
error = null error = null
loading = true loading = true
const password = Hex.encode(getRandomBytes(32)) const password = Hex.encode(getRandomBytes(32))
const key = await getKeyFromString(password) const key = await getKeyFromString(password)
if (note.contents === '') throw new EmptyContentError()
const data: Note = { const data: Note = {
contents: await encrypt(note.contents, key), contents: await encrypt(note.contents, key),
meta: note.meta, meta: note.meta,
} }
// @ts-ignore if (timeExpiration) data.expiration = parseInt(note.expiration as any)
if (type) data.expiration = parseInt(note.expiration) else data.views = parseInt(note.views as any)
// @ts-ignore
else data.views = parseInt(note.views)
const response = await create(data) const response = await create(data)
result = { result = {
@@ -62,9 +69,11 @@
} }
} catch (e) { } catch (e) {
if (e instanceof PayloadToLargeError) { if (e instanceof PayloadToLargeError) {
error = 'could not create not. note is to big' error = $t('home.errors.note_to_big')
} else if (e instanceof EmptyContentError) {
error = $t('home.errors.empty_content')
} else { } else {
error = 'could not create note. please try again.' error = $t('home.errors.note_error')
} }
} finally { } finally {
loading = false loading = false
@@ -80,48 +89,38 @@
<TextInput <TextInput
type="text" type="text"
readonly readonly
label="share link" label={$t('common.share_link')}
value="{window.location.origin}/note/{result.id}#{result.password}" value="{window.location.origin}/note/{result.id}#{result.password}"
copy copy
data-testid="note-share-link"
/> />
<br /> <br />
<p> <p>
<b>availability:</b> {@html $t('home.new_note_notice')}
<br />
the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes
will be removed.
<br />
(you probably will be fine, just be warned.)
</p> </p>
<br /> <br />
<Button on:click={reset}>new note</Button> <Button on:click={reset}>{$t('home.new_note')}</Button>
{:else} {:else}
<p> <p>
Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and {@html $t('home.intro')}
share the link.
</p> </p>
<form on:submit|preventDefault={submit}> <form on:submit|preventDefault={submit}>
<fieldset disabled={loading}> <fieldset disabled={loading}>
{#if file} {#if file}
<FileUpload label="file" on:file={(f) => (note.contents = f.detail)} /> <FileUpload label={$t('common.file')} on:file={(f) => (note.contents = f.detail)} />
{:else} {:else}
<TextArea <TextArea label={$t('common.note')} bind:value={note.contents} placeholder="..." />
label="note"
bind:value={note.contents}
placeholder="..."
data-testid="input-note"
/>
{/if} {/if}
<div class="bottom"> <div class="bottom">
<Switch class="file" label="file" bind:value={file} /> <Switch class="file" label={$t('common.file')} bind:value={file} />
<Switch label="advanced" bind:value={advanced} /> {#if $status?.allow_advanced}
<Switch label={$t('common.advanced')} bind:value={advanced} />
{/if}
<div class="grow" /> <div class="grow" />
<div class="tr"> <div class="tr">
<small>max: <MaxSize /> </small> <small>{$t('common.max')}: <MaxSize /> </small>
<br /> <br />
<Button type="submit" data-testid="button-create">create</Button> <Button type="submit">{$t('common.create')}</Button>
</div> </div>
</div> </div>
@@ -132,7 +131,7 @@
<p> <p>
<br /> <br />
{#if loading} {#if loading}
loading... {$t('common.loading')}
{:else} {:else}
{message} {message}
{/if} {/if}
@@ -144,20 +143,26 @@
<div class="fields"> <div class="fields">
<TextInput <TextInput
type="number" type="number"
label="views" label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views} bind:value={note.views}
disabled={type} disabled={timeExpiration}
max={100} max={$status?.max_views}
validate={(v) =>
($status && v < $status?.max_views) ||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/> />
<div class="middle-switch"> <div class="middle-switch">
<Switch label="mode" bind:value={type} color={false} /> <Switch label={$t('common.mode')} bind:value={timeExpiration} color={false} />
</div> </div>
<TextInput <TextInput
type="number" type="number"
label="minutes" label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration} bind:value={note.expiration}
disabled={!type} disabled={!timeExpiration}
max={360} max={$status?.max_expiration}
validate={(v) =>
($status && v < $status?.max_expiration) ||
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
/> />
</div> </div>
</div> </div>

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import Icon from '$lib/ui/Icon.svelte'
import ThemeToggle from '$lib/ui/ThemeToggle.svelte' import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
</script> </script>

View File

@@ -83,7 +83,7 @@
header { header {
text-align: center; text-align: center;
margin-top: 4rem; margin-top: 3rem;
margin-bottom: 2rem; margin-bottom: 2rem;
} }

View File

@@ -0,0 +1,42 @@
<script lang="ts" context="module">
import { init, waitLocale, getLocaleFromNavigator } from 'svelte-intl-precompile'
// @ts-ignore
import { registerAll } from '$locales'
registerAll()
init({ initialLocale: getLocaleFromNavigator() ?? undefined, fallbackLocale: 'en' })
</script>
<script lang="ts">
import { init as initStores } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
initStores()
})
</script>
<svelte:head>
<title>cryptgeon</title>
</svelte:head>
{#await waitLocale() then _}
<main>
<Header />
<slot />
</main>
<Footer />
{/await}
<style>
main {
padding: 1rem;
padding-bottom: 4rem;
width: 100%;
max-width: 35rem;
margin: 0 auto;
}
</style>

View File

@@ -5,7 +5,6 @@
export const hydrate = dev export const hydrate = dev
export const router = browser export const router = browser
export const prerender = true
</script> </script>
<svelte:head> <svelte:head>
@@ -50,6 +49,14 @@
</span> </span>
</AboutParagraph> </AboutParagraph>
<AboutParagraph title="translations">
<span
>translations are managed on <a href="https://lokalise.com/" target="_blank">Lokalise</a>,
which granted an open source license to use the paid version. If you are interested in helping
translating don't hesitate to contact me!
</span>
</AboutParagraph>
<AboutParagraph title="attribution"> <AboutParagraph title="attribution">
<span> <span>
icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from

View File

@@ -1,7 +1,3 @@
<script context="module" lang="ts">
export const prerender = true
</script>
<script lang="ts"> <script lang="ts">
import Create from '$lib/views/Create.svelte' import Create from '$lib/views/Create.svelte'
</script> </script>

View File

@@ -1,18 +1,22 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
export async function load({ page }) { import type { Load } from '@sveltejs/kit'
export const load: Load = async ({ params }) => {
return { return {
props: page.params, props: params,
} }
} }
</script> </script>
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile'
import type { NotePublic } from '$lib/api' import type { NotePublic } from '$lib/api'
import { get, info } from '$lib/api' import { get, info } from '$lib/api'
import { decrypt, getKeyFromString } from '$lib/crypto' import { decrypt, getKeyFromString } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import ShowNote from '$lib/ui/ShowNote.svelte' import ShowNote from '$lib/ui/ShowNote.svelte'
import { onMount } from 'svelte'
export let id: string export let id: string
@@ -26,7 +30,7 @@
onMount(async () => { onMount(async () => {
try { try {
loading = true loading = true
error = null error = false
password = window.location.hash.slice(1) password = window.location.hash.slice(1)
await info(id) await info(id)
exists = true exists = true
@@ -55,20 +59,18 @@
{#if !loading} {#if !loading}
{#if !exists} {#if !exists}
<p class="error-text" data-testid="note-not-found"> <p class="error-text">{$t('show.errors.not_found')}</p>
note was not found or was already deleted.
</p>
{:else if note && !error} {:else if note && !error}
<ShowNote {note} /> <ShowNote {note} />
{:else} {:else}
<form on:submit|preventDefault={show}> <form on:submit|preventDefault={show}>
<fieldset> <fieldset>
<p>click below to show and delete the note if the counter has reached it's limit</p> <p>{$t('show.explanation')}</p>
<Button type="submit" data-testid="button-show">show note</Button> <Button type="submit">{$t('show.show_note')}</Button>
{#if error} {#if error}
<br /> <br />
<p class="error-text"> <p class="error-text">
wrong password. could not decipher. probably a broken link. note was destroyed. {$t('show.errors.decryption_failed')}
<br /> <br />
</p> </p>
{/if} {/if}
@@ -77,5 +79,5 @@
{/if} {/if}
{/if} {/if}
{#if loading} {#if loading}
<p>loading...</p> <p>{$t('common.loading')}</p>
{/if} {/if}

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,3 +1,4 @@
# https://www.robotstxt.org/robotstxt.html # https://www.robotstxt.org/robotstxt.html
User-agent: * User-agent: *
Disallow: Allow: /$
Disallow: /

View File

@@ -1,5 +1,6 @@
import preprocess from 'svelte-preprocess' import preprocess from 'svelte-preprocess'
import adapter from '@sveltejs/adapter-static' import adapter from '@sveltejs/adapter-static'
import precompileIntl from 'svelte-intl-precompile/sveltekit-plugin'
export default { export default {
preprocess: preprocess(), preprocess: preprocess(),
@@ -8,6 +9,10 @@ export default {
adapter: adapter({ adapter: adapter({
fallback: 'index.html', fallback: 'index.html',
}), }),
target: '#svelte', vite: {
plugins: [
precompileIntl('locales'), // if your translations are defined in /locales/[lang].json
],
},
}, },
} }

3
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "./.svelte-kit/tsconfig.json"
}

View File

@@ -1,17 +1,13 @@
{ {
"scripts": { "scripts": {
"dev:docker": "docker-compose up memcached", "dev:docker": "docker-compose up memcached",
"dev:backend": "cargo watch -x 'run --bin cryptgeon'", "dev:backend": "cd backend && cargo watch -x 'run --bin cryptgeon'",
"dev:front": "pnpm --prefix client run dev", "dev:front": "pnpm --prefix frontend run dev",
"dev:proxy": "node proxy.mjs", "dev:proxy": "node proxy.mjs",
"dev": "run-p dev:*" "dev": "run-p dev:*"
}, },
"devDependencies": { "devDependencies": {
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5" "npm-run-all": "^4.1.5"
},
"dependencies": {
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
} }
} }

332
pnpm-lock.yaml generated
View File

@@ -1,14 +1,8 @@
lockfileVersion: 5.3 lockfileVersion: 5.4
specifiers: specifiers:
file-saver: ^2.0.5
http-proxy: ^1.18.1 http-proxy: ^1.18.1
npm-run-all: ^4.1.5 npm-run-all: ^4.1.5
pretty-bytes: ^5.6.0
dependencies:
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies: devDependencies:
http-proxy: 1.18.1 http-proxy: 1.18.1
@@ -57,7 +51,7 @@ packages:
dev: true dev: true
/color-name/1.1.3: /color-name/1.1.3:
resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: true dev: true
/concat-map/0.0.1: /concat-map/0.0.1:
@@ -75,10 +69,11 @@ packages:
which: 1.3.1 which: 1.3.1
dev: true dev: true
/define-properties/1.1.3: /define-properties/1.1.4:
resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
has-property-descriptors: 1.0.0
object-keys: 1.1.1 object-keys: 1.1.1
dev: true dev: true
@@ -88,39 +83,46 @@ packages:
is-arrayish: 0.2.1 is-arrayish: 0.2.1
dev: true dev: true
/es-abstract/1.18.0: /es-abstract/1.20.1:
resolution: {integrity: sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==} resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
es-to-primitive: 1.2.1 es-to-primitive: 1.2.1
function-bind: 1.1.1 function-bind: 1.1.1
function.prototype.name: 1.1.5
get-intrinsic: 1.1.1 get-intrinsic: 1.1.1
get-symbol-description: 1.0.0
has: 1.0.3 has: 1.0.3
has-symbols: 1.0.2 has-property-descriptors: 1.0.0
is-callable: 1.2.3 has-symbols: 1.0.3
is-negative-zero: 2.0.1 internal-slot: 1.0.3
is-regex: 1.1.2 is-callable: 1.2.4
is-string: 1.0.5 is-negative-zero: 2.0.2
object-inspect: 1.10.2 is-regex: 1.1.4
is-shared-array-buffer: 1.0.2
is-string: 1.0.7
is-weakref: 1.0.2
object-inspect: 1.12.2
object-keys: 1.1.1 object-keys: 1.1.1
object.assign: 4.1.2 object.assign: 4.1.2
string.prototype.trimend: 1.0.4 regexp.prototype.flags: 1.4.3
string.prototype.trimstart: 1.0.4 string.prototype.trimend: 1.0.5
unbox-primitive: 1.0.1 string.prototype.trimstart: 1.0.5
unbox-primitive: 1.0.2
dev: true dev: true
/es-to-primitive/1.2.1: /es-to-primitive/1.2.1:
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
is-callable: 1.2.3 is-callable: 1.2.4
is-date-object: 1.0.2 is-date-object: 1.0.5
is-symbol: 1.0.3 is-symbol: 1.0.4
dev: true dev: true
/escape-string-regexp/1.0.5: /escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
dev: true dev: true
@@ -128,12 +130,8 @@ packages:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: true dev: true
/file-saver/2.0.5: /follow-redirects/1.15.1:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
dev: false
/follow-redirects/1.14.6:
resolution: {integrity: sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
peerDependencies: peerDependencies:
debug: '*' debug: '*'
@@ -146,32 +144,67 @@ packages:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true dev: true
/function.prototype.name/1.1.5:
resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.4
es-abstract: 1.20.1
functions-have-names: 1.2.3
dev: true
/functions-have-names/1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: true
/get-intrinsic/1.1.1: /get-intrinsic/1.1.1:
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
dependencies: dependencies:
function-bind: 1.1.1 function-bind: 1.1.1
has: 1.0.3 has: 1.0.3
has-symbols: 1.0.2 has-symbols: 1.0.3
dev: true dev: true
/graceful-fs/4.2.6: /get-symbol-description/1.0.0:
resolution: {integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==} resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.1
dev: true dev: true
/has-bigints/1.0.1: /graceful-fs/4.2.10:
resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
dev: true
/has-bigints/1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true dev: true
/has-flag/3.0.0: /has-flag/3.0.0:
resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/has-symbols/1.0.2: /has-property-descriptors/1.0.0:
resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
dependencies:
get-intrinsic: 1.1.1
dev: true
/has-symbols/1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true dev: true
/has-tostringtag/1.0.0:
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.3
dev: true
/has/1.0.3: /has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@@ -188,75 +221,105 @@ packages:
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
dependencies: dependencies:
eventemitter3: 4.0.7 eventemitter3: 4.0.7
follow-redirects: 1.14.6 follow-redirects: 1.15.1
requires-port: 1.0.0 requires-port: 1.0.0
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: true dev: true
/internal-slot/1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.1.1
has: 1.0.3
side-channel: 1.0.4
dev: true
/is-arrayish/0.2.1: /is-arrayish/0.2.1:
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
dev: true dev: true
/is-bigint/1.0.1: /is-bigint/1.0.4:
resolution: {integrity: sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==} resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies:
has-bigints: 1.0.2
dev: true dev: true
/is-boolean-object/1.1.0: /is-boolean-object/1.1.2:
resolution: {integrity: sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==} resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
has-tostringtag: 1.0.0
dev: true dev: true
/is-callable/1.2.3: /is-callable/1.2.4:
resolution: {integrity: sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==} resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true dev: true
/is-core-module/2.3.0: /is-core-module/2.9.0:
resolution: {integrity: sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==} resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
dependencies: dependencies:
has: 1.0.3 has: 1.0.3
dev: true dev: true
/is-date-object/1.0.2: /is-date-object/1.0.5:
resolution: {integrity: sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==} resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: true
/is-negative-zero/2.0.2:
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true dev: true
/is-negative-zero/2.0.1: /is-number-object/1.0.7:
resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==} resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: true dev: true
/is-number-object/1.0.4: /is-regex/1.1.4:
resolution: {integrity: sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==} resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'}
dev: true
/is-regex/1.1.2:
resolution: {integrity: sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
has-symbols: 1.0.2 has-tostringtag: 1.0.0
dev: true dev: true
/is-string/1.0.5: /is-shared-array-buffer/1.0.2:
resolution: {integrity: sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==} resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
engines: {node: '>= 0.4'} dependencies:
call-bind: 1.0.2
dev: true dev: true
/is-symbol/1.0.3: /is-string/1.0.7:
resolution: {integrity: sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==} resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
has-symbols: 1.0.2 has-tostringtag: 1.0.0
dev: true
/is-symbol/1.0.4:
resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.3
dev: true
/is-weakref/1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
dependencies:
call-bind: 1.0.2
dev: true dev: true
/isexe/2.0.0: /isexe/2.0.0:
resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true dev: true
/json-parse-better-errors/1.0.2: /json-parse-better-errors/1.0.2:
@@ -264,22 +327,22 @@ packages:
dev: true dev: true
/load-json-file/4.0.0: /load-json-file/4.0.0:
resolution: {integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs=} resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
graceful-fs: 4.2.6 graceful-fs: 4.2.10
parse-json: 4.0.0 parse-json: 4.0.0
pify: 3.0.0 pify: 3.0.0
strip-bom: 3.0.0 strip-bom: 3.0.0
dev: true dev: true
/memorystream/0.3.1: /memorystream/0.3.1:
resolution: {integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI=} resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
dev: true dev: true
/minimatch/3.0.4: /minimatch/3.1.2:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies: dependencies:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
dev: true dev: true
@@ -292,7 +355,7 @@ packages:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies: dependencies:
hosted-git-info: 2.8.9 hosted-git-info: 2.8.9
resolve: 1.20.0 resolve: 1.22.0
semver: 5.7.1 semver: 5.7.1
validate-npm-package-license: 3.0.4 validate-npm-package-license: 3.0.4
dev: true dev: true
@@ -306,15 +369,15 @@ packages:
chalk: 2.4.2 chalk: 2.4.2
cross-spawn: 6.0.5 cross-spawn: 6.0.5
memorystream: 0.3.1 memorystream: 0.3.1
minimatch: 3.0.4 minimatch: 3.1.2
pidtree: 0.3.1 pidtree: 0.3.1
read-pkg: 3.0.0 read-pkg: 3.0.0
shell-quote: 1.7.2 shell-quote: 1.7.3
string.prototype.padend: 3.1.2 string.prototype.padend: 3.1.3
dev: true dev: true
/object-inspect/1.10.2: /object-inspect/1.12.2:
resolution: {integrity: sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==} resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
dev: true dev: true
/object-keys/1.1.1: /object-keys/1.1.1:
@@ -327,13 +390,13 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
define-properties: 1.1.3 define-properties: 1.1.4
has-symbols: 1.0.2 has-symbols: 1.0.3
object-keys: 1.1.1 object-keys: 1.1.1
dev: true dev: true
/parse-json/4.0.0: /parse-json/4.0.0:
resolution: {integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=} resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
error-ex: 1.3.2 error-ex: 1.3.2
@@ -341,12 +404,12 @@ packages:
dev: true dev: true
/path-key/2.0.1: /path-key/2.0.1:
resolution: {integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=} resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/path-parse/1.0.6: /path-parse/1.0.7:
resolution: {integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true dev: true
/path-type/3.0.0: /path-type/3.0.0:
@@ -363,17 +426,12 @@ packages:
dev: true dev: true
/pify/3.0.0: /pify/3.0.0:
resolution: {integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=} resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/pretty-bytes/5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: false
/read-pkg/3.0.0: /read-pkg/3.0.0:
resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=} resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
engines: {node: '>=4'} engines: {node: '>=4'}
dependencies: dependencies:
load-json-file: 4.0.0 load-json-file: 4.0.0
@@ -381,15 +439,26 @@ packages:
path-type: 3.0.0 path-type: 3.0.0
dev: true dev: true
/requires-port/1.0.0: /regexp.prototype.flags/1.4.3:
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.4
functions-have-names: 1.2.3
dev: true dev: true
/resolve/1.20.0: /requires-port/1.0.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==} resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true
/resolve/1.22.0:
resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
hasBin: true
dependencies: dependencies:
is-core-module: 2.3.0 is-core-module: 2.9.0
path-parse: 1.0.6 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true dev: true
/semver/5.7.1: /semver/5.7.1:
@@ -409,15 +478,23 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/shell-quote/1.7.2: /shell-quote/1.7.3:
resolution: {integrity: sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==} resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
dev: true
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.1
object-inspect: 1.12.2
dev: true dev: true
/spdx-correct/3.1.1: /spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies: dependencies:
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
spdx-license-ids: 3.0.7 spdx-license-ids: 3.0.11
dev: true dev: true
/spdx-exceptions/2.3.0: /spdx-exceptions/2.3.0:
@@ -428,34 +505,36 @@ packages:
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
dependencies: dependencies:
spdx-exceptions: 2.3.0 spdx-exceptions: 2.3.0
spdx-license-ids: 3.0.7 spdx-license-ids: 3.0.11
dev: true dev: true
/spdx-license-ids/3.0.7: /spdx-license-ids/3.0.11:
resolution: {integrity: sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==} resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==}
dev: true dev: true
/string.prototype.padend/3.1.2: /string.prototype.padend/3.1.3:
resolution: {integrity: sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==} resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
define-properties: 1.1.3 define-properties: 1.1.4
es-abstract: 1.18.0 es-abstract: 1.20.1
dev: true dev: true
/string.prototype.trimend/1.0.4: /string.prototype.trimend/1.0.5:
resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
define-properties: 1.1.3 define-properties: 1.1.4
es-abstract: 1.20.1
dev: true dev: true
/string.prototype.trimstart/1.0.4: /string.prototype.trimstart/1.0.5:
resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==}
dependencies: dependencies:
call-bind: 1.0.2 call-bind: 1.0.2
define-properties: 1.1.3 define-properties: 1.1.4
es-abstract: 1.20.1
dev: true dev: true
/strip-bom/3.0.0: /strip-bom/3.0.0:
@@ -470,12 +549,17 @@ packages:
has-flag: 3.0.0 has-flag: 3.0.0
dev: true dev: true
/unbox-primitive/1.0.1: /supports-preserve-symlinks-flag/1.0.0:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/unbox-primitive/1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies: dependencies:
function-bind: 1.1.1 call-bind: 1.0.2
has-bigints: 1.0.1 has-bigints: 1.0.2
has-symbols: 1.0.2 has-symbols: 1.0.3
which-boxed-primitive: 1.0.2 which-boxed-primitive: 1.0.2
dev: true dev: true
@@ -489,11 +573,11 @@ packages:
/which-boxed-primitive/1.0.2: /which-boxed-primitive/1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies: dependencies:
is-bigint: 1.0.1 is-bigint: 1.0.4
is-boolean-object: 1.1.0 is-boolean-object: 1.1.2
is-number-object: 1.0.4 is-number-object: 1.0.7
is-string: 1.0.5 is-string: 1.0.7
is-symbol: 1.0.3 is-symbol: 1.0.4
dev: true dev: true
/which/1.3.1: /which/1.3.1:

View File

@@ -1,19 +1,15 @@
import http from 'http' import http from 'http'
import httpProxy from 'http-proxy' import httpProxy from 'http-proxy'
function start() { const proxy = httpProxy.createProxyServer()
try { proxy.on('error', function (err, req, res) {
const proxy = httpProxy.createProxyServer({}) res.writeHead(500, { 'Content-Type': 'text/plain' })
const server = http.createServer(function (req, res) { res.end('500 Internal Server Error')
const target = req.url.startsWith('/api/') ? 'http://localhost:5000' : 'http://localhost:3000' })
proxy.web(req, res, { target })
})
server.listen(1234)
server.on('error', () => start()) const server = http.createServer(function (req, res) {
} catch (e) { const target = req.url.startsWith('/api/') ? 'http://localhost:5000' : 'http://localhost:3000'
start() proxy.web(req, res, { target })
} })
} server.listen(1234)
console.log('Proxy on http://localhost:1234')
start()

View File

@@ -1,10 +0,0 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Responder};
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(Files::new("/", "./client/build").index_file("index.html"));
}
pub async fn fallback_fn() -> impl Responder {
NamedFile::open("./client/build/index.html")
}

View File

@@ -1,137 +0,0 @@
use actix_web::{delete, get, post, web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
use crate::size::LIMIT;
use crate::store;
pub fn now() -> u32 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32
}
#[derive(Serialize, Deserialize)]
struct NotePath {
id: String,
}
#[get("/{id}")]
async fn one(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
None => return HttpResponse::NotFound().finish(),
Some(_) => return HttpResponse::Ok().json(NoteInfo {}),
}
}
#[derive(Serialize, Deserialize)]
struct CreateResponse {
id: String,
}
#[post("/")]
async fn create(note: web::Json<Note>) -> impl Responder {
let mut n = note.into_inner();
let id = generate_id();
let bad_req = HttpResponse::BadRequest().finish();
if n.views == None && n.expiration == None {
return bad_req;
}
match n.views {
Some(v) => {
if v > 100 {
return bad_req;
}
}
_ => {}
}
match n.expiration {
Some(e) => {
if e > 360 {
return bad_req;
}
let expiration = now() + (e * 60);
n.expiration = Some(expiration);
}
_ => {}
}
store::set(&id.clone(), &n.clone());
return HttpResponse::Ok().json(CreateResponse { id: id });
}
#[delete("/{id}")]
async fn delete(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
None => return HttpResponse::NotFound().finish(),
Some(note) => {
let mut changed = note.clone();
if changed.views == None && changed.expiration == None {
return HttpResponse::BadRequest().finish();
}
match changed.views {
Some(v) => {
changed.views = Some(v - 1);
let id = p.id.clone();
if v <= 1 {
store::del(&id);
} else {
store::set(&id, &changed.clone());
}
}
_ => {}
}
let n = now();
match changed.expiration {
Some(e) => {
if e < n {
store::del(&p.id.clone());
return HttpResponse::BadRequest().finish();
}
}
_ => {}
}
return HttpResponse::Ok().json(NotePublic {
contents: changed.contents,
meta: changed.meta,
});
}
}
}
#[derive(Serialize, Deserialize)]
struct Status {
version: String,
max_size: usize,
}
#[get("/status")]
async fn status() -> impl Responder {
println!("Limit: {}", *LIMIT);
return HttpResponse::Ok().json(Status {
version: option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string(),
max_size: *LIMIT,
});
}
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
.service(status),
)
.service(status),
);
}

View File

@@ -1,20 +0,0 @@
use actix_web::web;
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) {
println!("Limit: {}", *LIMIT);
let json = web::JsonConfig::default().limit(*LIMIT);
let plain = web::PayloadConfig::default()
.limit(*LIMIT)
.mimetype(mime::STAR_STAR);
cfg.data(json);
cfg.data(plain);
}

View File

@@ -1,36 +0,0 @@
use memcache;
use crate::note::now;
use crate::note::Note;
lazy_static! {
static ref CLIENT: memcache::Client = memcache::connect(format!(
"memcache://{}?timeout=10&tcp_nodelay=true",
std::env::var("MEMCACHE").unwrap_or("127.0.0.1:11211".to_string())
))
.unwrap();
}
pub fn set(id: &String, note: &Note) {
let serialized = serde_json::to_string(&note.clone()).unwrap();
let expiration: u32 = match note.expiration {
Some(e) => e - now(),
None => 0,
};
CLIENT.set(id, serialized, expiration).unwrap();
}
pub fn get(id: &String) -> Option<Note> {
let value: Option<String> = CLIENT.get(&id).unwrap();
match value {
None => return None,
Some(s) => {
let deserialize: Note = serde_json::from_str(&s).unwrap();
return Some(deserialize);
}
}
}
pub fn del(id: &String) {
CLIENT.delete(id).unwrap();
}