Compare commits

..

28 Commits

Author SHA1 Message Date
a5da51fb2c Merge dfa2401eea into 2006be0434 2024-08-25 20:22:56 +00:00
dfa2401eea aria check 2024-08-25 22:22:45 +02:00
ea58d89f98 cleanup 2024-08-25 22:22:13 +02:00
eaca1a981d cleanup 2024-08-25 22:21:45 +02:00
199755d18e timeout 2024-08-25 22:20:46 +02:00
724d0709d3 .env.dev 2024-08-25 22:20:29 +02:00
bd5acca97a use axum body limit 2024-08-25 22:19:41 +02:00
a0a99cd3cc cleanup status variable 2024-08-25 22:18:31 +02:00
c3794fa2b6 port 3000 2024-08-23 14:27:59 +02:00
f9962c76c1 enable to big 2024-08-23 14:27:52 +02:00
c2b81bc04d refactor to use axum 2024-08-23 14:27:47 +02:00
a45f6a3772 use 3000 port 2024-08-23 14:27:17 +02:00
2006be0434 Merge pull request #145 from cupcakearmy/better-programmatic-access
better programmatic access
2024-08-23 11:19:57 +02:00
ca72e94e3c update node and playwright 2024-08-23 11:02:30 +02:00
dbcb3870aa fix tests 2024-08-23 11:01:57 +02:00
3ea176cc1f add build cli 2024-08-23 09:59:06 +02:00
145f9ef18f right package stuff 2024-08-22 20:21:05 +02:00
784c54236b better programmatic access 2024-08-22 20:01:14 +02:00
5648c76f78 Merge pull request #144 from cupcakearmy/update-rust
update rust
2024-08-22 19:57:39 +02:00
7761c795df update rust 2024-08-22 18:42:44 +02:00
4aadeb492a maintenance 2024-08-22 18:40:56 +02:00
0d9f3fe9c7 Merge pull request #130 from DDd-Devops/add-redis-tls-feature
Add redis tls feature 'rediss://'
2024-08-22 18:30:58 +02:00
f790438104 add french blog post 2024-06-16 22:34:44 +02:00
5936f4588c Merge pull request #133 from DDd-Devops/improve-french-translations
improve french translations wording
2024-06-16 22:31:34 +02:00
Thomas Chrétien
d3c04f8fda improve french translations wording 2024-06-14 14:26:02 +00:00
Matthieu Guegan
f8c17487bd Support dynamically-linked and/or native musl targets
See https://github.com/rust-lang/rust/pull/40113#issuecomment-323193341
2024-05-16 09:55:04 +02:00
Matthieu Guegan
ed3e5f48a0 Fix wrong type due to updated lib
See https://github.com/redis-rs/redis-rs/pull/589
2024-05-16 09:47:01 +02:00
Matthieu Guegan
e08c9d1871 Bump redis crate to 0.25.2
This will enable TLS feature
2024-05-16 09:45:26 +02:00
44 changed files with 4383 additions and 3639 deletions

View File

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ target
# Testing # Testing
test-results test-results
tmp

2
.nvmrc
View File

@@ -1 +1 @@
v20.11.1 v22.7.0

View File

@@ -15,13 +15,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -40,13 +35,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -95,13 +85,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -170,13 +155,8 @@
}, },
"url": { "url": {
"raw": "{{BASE}}/notes/", "raw": "{{BASE}}/notes/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ""]
],
"path": [
"notes",
""
]
} }
}, },
"response": [ "response": [
@@ -196,13 +176,8 @@
}, },
"url": { "url": {
"raw": "{{BASE}}/notes/", "raw": "{{BASE}}/notes/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ""]
],
"path": [
"notes",
""
]
} }
}, },
"status": "OK", "status": "OK",
@@ -253,13 +228,8 @@
}, },
"url": { "url": {
"raw": "{{BASE}}/notes/", "raw": "{{BASE}}/notes/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ""]
],
"path": [
"notes",
""
]
} }
}, },
"status": "OK", "status": "OK",
@@ -310,13 +280,8 @@
}, },
"url": { "url": {
"raw": "{{BASE}}/notes/", "raw": "{{BASE}}/notes/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ""]
],
"path": [
"notes",
""
]
} }
}, },
"status": "OK", "status": "OK",
@@ -360,13 +325,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -384,13 +344,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -438,13 +393,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/notes/:id", "raw": "{{BASE}}/notes/:id",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["notes", ":id"],
],
"path": [
"notes",
":id"
],
"variable": [ "variable": [
{ {
"key": "id", "key": "id",
@@ -495,13 +445,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/status/", "raw": "{{BASE}}/status/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["status", ""]
],
"path": [
"status",
""
]
} }
}, },
"response": [ "response": [
@@ -512,13 +457,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/status/", "raw": "{{BASE}}/status/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["status", ""]
],
"path": [
"status",
""
]
} }
}, },
"status": "OK", "status": "OK",
@@ -562,13 +502,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/live/", "raw": "{{BASE}}/live/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["live", ""]
],
"path": [
"live",
""
]
}, },
"description": "Return `200` for healthy service. `503` if service is unavailable." "description": "Return `200` for healthy service. `503` if service is unavailable."
}, },
@@ -580,13 +515,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/live/", "raw": "{{BASE}}/live/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["live", ""]
],
"path": [
"live",
""
]
} }
}, },
"status": "OK", "status": "OK",
@@ -620,13 +550,8 @@
"header": [], "header": [],
"url": { "url": {
"raw": "{{BASE}}/live/", "raw": "{{BASE}}/live/",
"host": [ "host": ["{{BASE}}"],
"{{BASE}}" "path": ["live", ""]
],
"path": [
"live",
""
]
} }
}, },
"status": "Service Unavailable", "status": "Service Unavailable",
@@ -663,25 +588,21 @@
"listen": "prerequest", "listen": "prerequest",
"script": { "script": {
"type": "text/javascript", "type": "text/javascript",
"exec": [ "exec": [""]
""
]
} }
}, },
{ {
"listen": "test", "listen": "test",
"script": { "script": {
"type": "text/javascript", "type": "text/javascript",
"exec": [ "exec": [""]
""
]
} }
} }
], ],
"variable": [ "variable": [
{ {
"key": "BASE", "key": "BASE",
"value": "http://localhost:1234/api", "value": "http://localhost:3000/api",
"type": "default" "type": "default"
}, },
{ {

View File

@@ -1,5 +1,5 @@
# FRONTEND # FRONTEND
FROM node:20-alpine as client FROM node:22-alpine as client
ENV PNPM_HOME="/pnpm" ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH" ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable RUN corepack enable
@@ -11,17 +11,17 @@ RUN pnpm run build
# BACKEND # BACKEND
FROM rust:1.76-alpine as backend FROM rust:1.80-alpine as backend
WORKDIR /tmp WORKDIR /tmp
RUN apk add --no-cache libc-dev openssl-dev alpine-sdk RUN apk add --no-cache libc-dev openssl-dev alpine-sdk
COPY ./packages/backend ./ COPY ./packages/backend ./
RUN cargo build --release RUN RUSTFLAGS="-Ctarget-feature=-crt-static" cargo build --release
# RUNNER # RUNNER
FROM alpine:3.19 FROM alpine:3.19
WORKDIR /app WORKDIR /app
RUN apk add --no-cache curl RUN apk add --no-cache curl libgcc
COPY --from=backend /tmp/target/release/cryptgeon . COPY --from=backend /tmp/target/release/cryptgeon .
COPY --from=client /tmp/packages/frontend/build ./frontend COPY --from=client /tmp/packages/frontend/build ./frontend
ENV FRONTEND_PATH="./frontend" ENV FRONTEND_PATH="./frontend"

View File

@@ -141,12 +141,16 @@ There is a [guide](https://mariushosting.com/how-to-install-cryptgeon-on-your-sy
- English by [DB Tech](https://www.youtube.com/watch?v=S0jx7wpOfNM) [Previous Video](https://www.youtube.com/watch?v=JhpIatD06vE) - English by [DB Tech](https://www.youtube.com/watch?v=S0jx7wpOfNM) [Previous Video](https://www.youtube.com/watch?v=JhpIatD06vE)
- German by [ApfelCast](https://www.youtube.com/watch?v=84ZMbE9AkHg) - German by [ApfelCast](https://www.youtube.com/watch?v=84ZMbE9AkHg)
### Written Guides
- French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/)
## Development ## Development
**Requirements** **Requirements**
- `pnpm`: `>=6` - `pnpm`: `>=9`
- `node`: `>=18` - `node`: `>=22`
- `rust`: edition `2021` - `rust`: edition `2021`
**Install** **Install**
@@ -174,7 +178,7 @@ Running `pnpm run dev` in the root folder will start the following things:
- client - client
- cli - cli
You can see the app under [localhost:1234](http://localhost:1234). You can see the app under [localhost:3000](http://localhost:3000).
> There is a Postman collection with some example requests [available in the repo](./Cryptgeon.postman_collection.json) > There is a Postman collection with some example requests [available in the repo](./Cryptgeon.postman_collection.json)

View File

@@ -60,7 +60,7 @@ se usa para guardar y recuperar la nota. Después la nota es encriptada con la <
## Variables de entorno ## Variables de entorno
| Variable | Default | Descripción | | Variable | Default | Descripción |
| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REDIS` | `redis://redis/` | Redis URL a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) | | `REDIS` | `redis://redis/` | Redis URL a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) |
| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo. Valores aceptados según la [unidad byte](https://docs.rs/byte-unit/). <br> `512 MiB` es el máximo permitido. <br> El frontend mostrará ese número, incluyendo el ~35% de sobrecarga de codificación. | | `SIZE_LIMIT` | `1 KiB` | Tamaño máximo. Valores aceptados según la [unidad byte](https://docs.rs/byte-unit/). <br> `512 MiB` es el máximo permitido. <br> El frontend mostrará ese número, incluyendo el ~35% de sobrecarga de codificación. |
| `MAX_VIEWS` | `100` | Número máximo de vistas. | | `MAX_VIEWS` | `100` | Número máximo de vistas. |
@@ -169,7 +169,7 @@ Ejecutando `pnpm run dev` en la carpeta raíz iniciará lo siguiente:
- client - client
- cli - cli
Puedes ver la app en [localhost:1234](http://localhost:1234). Puedes ver la app en [localhost:3000](http://localhost:3000).
> Existe una colección de Postman con algunas peticiones de ejemplo [disponible en el repo](./Cryptgeon.postman_collection.json) > Existe una colección de Postman con algunas peticiones de ejemplo [disponible en el repo](./Cryptgeon.postman_collection.json)

View File

@@ -158,7 +158,7 @@ pnpm run dev
- 无热重载的 rust 后端 - 无热重载的 rust 后端
- 可热重载的客户端 - 可热重载的客户端
你可以通过 1234 端口进入该应用,即 [localhost:1234](http://localhost:1234). 你可以通过 3000 端口进入该应用,即 [localhost:3000](http://localhost:3000).
## 测试 ## 测试

View File

@@ -1,8 +1,6 @@
# DEV Compose file. # DEV Compose file.
# For a production file see: README.md # For a production file see: README.md
version: '3.8'
services: services:
redis: redis:
image: redis:7-alpine image: redis:7-alpine
@@ -11,12 +9,12 @@ services:
app: app:
build: . build: .
env_file: .dev.env env_file: .env.dev
depends_on: depends_on:
- redis - redis
restart: unless-stopped restart: unless-stopped
ports: ports:
- 1234:8000 - 3000:8000
healthcheck: healthcheck:
test: ['CMD', 'curl', '--fail', 'http://127.0.0.1:8000/api/live/'] test: ['CMD', 'curl', '--fail', 'http://127.0.0.1:8000/api/live/']

View File

@@ -1,5 +1,3 @@
version: '3.8'
services: services:
redis: redis:
image: redis:7-alpine image: redis:7-alpine

View File

@@ -1,6 +1,6 @@
{ {
"scripts": { "scripts": {
"dev:docker": "docker-compose -f docker-compose.dev.yaml up redis", "dev:docker": "docker compose -f docker-compose.dev.yaml up redis",
"dev:packages": "pnpm --parallel run dev", "dev:packages": "pnpm --parallel run dev",
"dev": "run-p dev:*", "dev": "run-p dev:*",
"docker:up": "docker compose -f docker-compose.dev.yaml up", "docker:up": "docker compose -f docker-compose.dev.yaml up",
@@ -12,10 +12,10 @@
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build" "build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.42.1", "@playwright/test": "^1.46.1",
"@types/node": "^20.11.28", "@types/node": "^22.5.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"shelljs": "^0.8.5" "shelljs": "^0.8.5"
}, },
"packageManager": "pnpm@8.15.4" "packageManager": "pnpm@9.8.0"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,27 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "2.6.1" version = "2.7.0"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021" edition = "2021"
rust-version = "1.76" rust-version = "1.80"
[[bin]] [[bin]]
name = "cryptgeon" name = "cryptgeon"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
actix-web = "4" # Core
actix-files = "0.6" axum = "0.7.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0.208", features = ["derive"] }
tokio = { version = "1.39.3", features = ["full"] }
tower = "0.5.0"
tower-http = { version = "0.5.2", features = ["full"] }
redis = { version = "0.25.2", features = ["tls-native-tls"] }
# Utility
serde_json = "1" serde_json = "1"
lazy_static = "1" lazy_static = "1"
ring = "0.16" ring = "0.16"
bs62 = "0.1" bs62 = "0.1"
byte-unit = "4" byte-unit = "4"
dotenv = "0.15" dotenv = "0.15"
mime = "0.3"
env_logger = "0.9"
log = "0.4"
redis = "0.23"

View File

@@ -4,7 +4,7 @@
"scripts": { "scripts": {
"dev": "cargo watch -x 'run --bin cryptgeon'", "dev": "cargo watch -x 'run --bin cryptgeon'",
"build": "cargo build --release", "build": "cargo build --release",
"test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run", "test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:3000 cargo run",
"test:prepare": "cargo build" "test:prepare": "cargo build"
} }
} }

View File

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

View File

@@ -1,17 +0,0 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Result};
use crate::config;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
Files::new("/", config::FRONTEND_PATH.to_string())
.index_file("index.html")
.use_etag(true),
);
}
pub async fn index() -> Result<NamedFile> {
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
Ok(NamedFile::open(index)?)
}

View File

@@ -38,10 +38,6 @@ pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
.unwrap_or("true".to_string()) .unwrap_or("true".to_string())
.parse() .parse()
.unwrap(); .unwrap();
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
.unwrap_or("true".to_string())
.parse()
.unwrap();
} }
// THEME // THEME
@@ -62,4 +58,8 @@ lazy_static! {
.unwrap_or("".to_string()) .unwrap_or("".to_string())
.parse() .parse()
.unwrap(); .unwrap();
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
.unwrap_or("true".to_string())
.parse()
.unwrap();
} }

View File

@@ -1,3 +1,10 @@
mod routes; use crate::store;
use axum::http::StatusCode;
pub use routes::*; pub async fn report_health() -> (StatusCode,) {
if store::can_reach_redis() {
return (StatusCode::OK,);
} else {
return (StatusCode::SERVICE_UNAVAILABLE,);
}
}

View File

@@ -1,16 +0,0 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::store;
#[get("/")]
async fn get_live() -> impl Responder {
if store::can_reach_redis() {
return HttpResponse::Ok();
} else {
return HttpResponse::ServiceUnavailable();
}
}
pub fn init() -> Scope {
web::scope("/live").service(get_live)
}

View File

@@ -1,44 +1,67 @@
use actix_web::{ use axum::{
middleware::{self, Logger}, extract::{DefaultBodyLimit, Request},
web::{self}, routing::{delete, get, post},
App, HttpServer, Router, ServiceExt,
}; };
use dotenv::dotenv; use dotenv::dotenv;
use log::error; use tower::Layer;
use tower_http::{
compression::CompressionLayer,
normalize_path::NormalizePathLayer,
services::{ServeDir, ServeFile},
};
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
mod api;
mod client;
mod config; mod config;
mod health; mod health;
mod note; mod note;
mod size;
mod status; mod status;
mod store; mod store;
#[actix_web::main] #[tokio::main]
async fn main() -> std::io::Result<()> { async fn main() {
dotenv().ok(); dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or(config::VERBOSITY.as_str()));
if !store::can_reach_redis() { if !store::can_reach_redis() {
error!("cannot reach redis"); println!("cannot reach redis");
panic!("canont reach redis"); panic!("canont reach redis");
} }
return HttpServer::new(|| { let notes_routes = Router::new()
App::new() .route("/", post(note::create))
.wrap(Logger::new("\"%r\" %s %b %T")) .route("/:id", delete(note::delete))
.wrap(middleware::Compress::default()) .route("/:id", get(note::preview));
.wrap(middleware::DefaultHeaders::default()) let health_routes = Router::new().route("/live", get(health::report_health));
.configure(size::init) let status_routes = Router::new().route("/status", get(status::get_status));
.configure(api::init) let api_routes = Router::new()
.configure(client::init) .nest("/notes", notes_routes)
.default_service(web::to(client::index)) .nest("/", health_routes)
}) .nest("/", status_routes);
.bind(config::LISTEN_ADDR.to_string())?
.run() let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
.await; let serve_dir =
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
let app = Router::new()
.nest("/api", api_routes)
.fallback_service(serve_dir)
.layer(DefaultBodyLimit::max(*config::LIMIT))
.layer(
CompressionLayer::new()
.br(true)
.deflate(true)
.gzip(true)
.zstd(true),
);
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
let listener = tokio::net::TcpListener::bind(config::LISTEN_ADDR.to_string())
.await
.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, ServiceExt::<Request>::into_make_service(app))
.await
.unwrap();
} }

View File

@@ -12,12 +12,12 @@ pub struct Note {
pub expiration: Option<u32>, pub expiration: Option<u32>,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize)]
pub struct NoteInfo { pub struct NoteInfo {
pub meta: String, pub meta: String,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize)]
pub struct NotePublic { pub struct NotePublic {
pub meta: String, pub meta: String,
pub contents: String, pub contents: String,

View File

@@ -1,11 +1,16 @@
use actix_web::{delete, get, post, web, HttpResponse, Responder, Scope}; use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::SystemTime; use std::time::SystemTime;
use crate::config; use crate::config;
use crate::note::{generate_id, Note, NoteInfo, NotePublic}; use crate::note::{generate_id, Note, NoteInfo};
use crate::store; use crate::store;
use super::NotePublic;
pub fn now() -> u32 { pub fn now() -> u32 {
SystemTime::now() SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
@@ -13,20 +18,18 @@ pub fn now() -> u32 {
.as_secs() as u32 .as_secs() as u32
} }
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
struct NotePath { pub struct OneNoteParams {
id: String, id: String,
} }
#[get("/{id}")] pub async fn preview(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
async fn one(path: web::Path<NotePath>) -> impl Responder { let note = store::get(&id);
let p = path.into_inner();
let note = store::get(&p.id);
match note { match note {
Ok(Some(n)) => HttpResponse::Ok().json(NoteInfo { meta: n.meta }), Ok(Some(n)) => (StatusCode::OK, Json(NoteInfo { meta: n.meta })).into_response(),
Ok(None) => HttpResponse::NotFound().finish(), Ok(None) => (StatusCode::NOT_FOUND).into_response(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
} }
} }
@@ -35,13 +38,16 @@ struct CreateResponse {
id: String, id: String,
} }
#[post("/")] pub async fn create(Json(mut n): Json<Note>) -> Response {
async fn create(note: web::Json<Note>) -> impl Responder { // let mut n = note.into_inner();
let mut n = note.into_inner();
let id = generate_id(); let id = generate_id();
let bad_req = HttpResponse::BadRequest().finish(); // let bad_req = HttpResponse::BadRequest().finish();
if n.views == None && n.expiration == None { if n.views == None && n.expiration == None {
return bad_req; return (
StatusCode::BAD_REQUEST,
"At least views or expiration must be set",
)
.into_response();
} }
if !*config::ALLOW_ADVANCED { if !*config::ALLOW_ADVANCED {
n.views = Some(1); n.views = Some(1);
@@ -50,7 +56,7 @@ async fn create(note: web::Json<Note>) -> impl Responder {
match n.views { match n.views {
Some(v) => { Some(v) => {
if v > *config::MAX_VIEWS || v < 1 { if v > *config::MAX_VIEWS || v < 1 {
return bad_req; return (StatusCode::BAD_REQUEST, "Invalid views").into_response();
} }
n.expiration = None; // views overrides expiration n.expiration = None; // views overrides expiration
} }
@@ -58,8 +64,8 @@ async fn create(note: web::Json<Note>) -> impl Responder {
} }
match n.expiration { match n.expiration {
Some(e) => { Some(e) => {
if e > *config::MAX_EXPIRATION { if e > *config::MAX_EXPIRATION || e < 1 {
return bad_req; return (StatusCode::BAD_REQUEST, "Invalid expiration").into_response();
} }
let expiration = now() + (e * 60); let expiration = now() + (e * 60);
n.expiration = Some(expiration); n.expiration = Some(expiration);
@@ -67,38 +73,40 @@ async fn create(note: web::Json<Note>) -> impl Responder {
_ => {} _ => {}
} }
match store::set(&id.clone(), &n.clone()) { match store::set(&id.clone(), &n.clone()) {
Ok(_) => return HttpResponse::Ok().json(CreateResponse { id: id }), Ok(_) => (StatusCode::OK, Json(CreateResponse { id })).into_response(),
Err(e) => return HttpResponse::InternalServerError().body(e.to_string()), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
} }
} }
#[delete("/{id}")] pub async fn delete(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
async fn delete(path: web::Path<NotePath>) -> impl Responder { let note = store::get(&id);
let p = path.into_inner();
let note = store::get(&p.id);
match note { match note {
Err(e) => HttpResponse::InternalServerError().body(e.to_string()), // Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
Ok(None) => return HttpResponse::NotFound().finish(), // Ok(None) => return HttpResponse::NotFound().finish(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
Ok(None) => (StatusCode::NOT_FOUND).into_response(),
Ok(Some(note)) => { Ok(Some(note)) => {
let mut changed = note.clone(); let mut changed = note.clone();
if changed.views == None && changed.expiration == None { if changed.views == None && changed.expiration == None {
return HttpResponse::BadRequest().finish(); return (StatusCode::BAD_REQUEST).into_response();
} }
match changed.views { match changed.views {
Some(v) => { Some(v) => {
changed.views = Some(v - 1); changed.views = Some(v - 1);
let id = p.id.clone(); let id = id.clone();
if v <= 1 { if v <= 1 {
match store::del(&id) { match store::del(&id) {
Err(e) => { Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string()) return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
.into_response();
} }
_ => {} _ => {}
} }
} else { } else {
match store::set(&id, &changed.clone()) { match store::set(&id, &changed.clone()) {
Err(e) => { Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string()) return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
.into_response();
} }
_ => {} _ => {}
} }
@@ -111,33 +119,26 @@ async fn delete(path: web::Path<NotePath>) -> impl Responder {
match changed.expiration { match changed.expiration {
Some(e) => { Some(e) => {
if e < n { if e < n {
match store::del(&p.id.clone()) { match store::del(&id.clone()) {
Ok(_) => return HttpResponse::BadRequest().finish(), Ok(_) => return (StatusCode::BAD_REQUEST).into_response(),
Err(e) => { Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string()) return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
.into_response()
} }
} }
} }
} }
_ => {} _ => {}
} }
return HttpResponse::Ok().json(NotePublic {
return (
StatusCode::OK,
Json(NotePublic {
contents: changed.contents, contents: changed.contents,
meta: changed.meta, meta: changed.meta,
}); }),
)
.into_response();
} }
} }
} }
#[derive(Serialize, Deserialize)]
struct Status {
version: String,
max_size: usize,
}
pub fn init() -> Scope {
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
}

View File

@@ -1,12 +0,0 @@
use crate::config;
use actix_web::web;
use mime;
pub fn init(cfg: &mut web::ServiceConfig) {
let json = web::JsonConfig::default().limit(*config::LIMIT);
let plain = web::PayloadConfig::default()
.limit(*config::LIMIT)
.mimetype(mime::STAR_STAR);
// cfg.app_data(plain);
cfg.app_data(json).app_data(plain);
}

View File

@@ -1,5 +1,40 @@
mod model; use crate::config;
mod routes; use axum::http::StatusCode;
use axum::Json;
use serde::Serialize;
pub use model::*; #[derive(Serialize)]
pub use routes::*; pub struct Status {
// General
pub version: String,
// Config
pub max_size: u32,
pub max_views: u32,
pub max_expiration: u32,
pub allow_advanced: bool,
pub allow_files: bool,
// Theme
pub theme_image: String,
pub theme_text: String,
pub theme_page_title: String,
pub theme_favicon: String,
pub theme_new_note_notice: bool,
}
pub async fn get_status() -> (StatusCode, Json<Status>) {
let status = Status {
version: config::VERSION.to_string(),
max_size: *config::LIMIT as u32,
max_views: *config::MAX_VIEWS,
max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED,
allow_files: *config::ALLOW_FILES,
theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE,
theme_image: config::THEME_IMAGE.to_string(),
theme_text: config::THEME_TEXT.to_string(),
theme_page_title: config::THEME_PAGE_TITLE.to_string(),
theme_favicon: config::THEME_FAVICON.to_string(),
};
(StatusCode::OK, Json(status))
}

View File

@@ -1,19 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Status {
// General
pub version: String,
// Config
pub max_size: u32,
pub max_views: u32,
pub max_expiration: u32,
pub allow_advanced: bool,
pub allow_files: bool,
pub theme_new_note_notice: bool,
// Theme
pub theme_image: String,
pub theme_text: String,
pub theme_page_title: String,
pub theme_favicon: String,
}

View File

@@ -1,25 +0,0 @@
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 as u32,
max_views: *config::MAX_VIEWS,
max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED,
allow_files: *config::ALLOW_FILES,
theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE,
theme_image: config::THEME_IMAGE.to_string(),
theme_text: config::THEME_TEXT.to_string(),
theme_page_title: config::THEME_PAGE_TITLE.to_string(),
theme_favicon: config::THEME_FAVICON.to_string(),
});
}
pub fn init() -> Scope {
web::scope("/status").service(get_status)
}

View File

@@ -36,7 +36,7 @@ pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
match note.expiration { match note.expiration {
Some(e) => { Some(e) => {
let seconds = e - now(); let seconds = e - now();
conn.expire(id, seconds as usize) conn.expire(id, seconds as i64)
.map_err(|_| "Unable to set expiration on notion")? .map_err(|_| "Unable to set expiration on notion")?
} }
None => {} None => {}

14
packages/cli/build.js Normal file
View File

@@ -0,0 +1,14 @@
import pkg from './package.json' with { type: 'json' }
import { build } from 'tsup'
const watch = process.argv.slice(2)[0] === '--watch'
await build({
entry: ['src/index.ts', 'src/cli.ts'],
dts: true,
minify: true,
format: ['esm', 'cjs'],
clean: true,
define: { VERSION: `"${pkg.version}"` },
watch,
})

View File

@@ -1,6 +1,6 @@
{ {
"name": "cryptgeon", "name": "cryptgeon",
"version": "2.6.1", "version": "2.7.0",
"homepage": "https://github.com/cupcakearmy/cryptgeon", "homepage": "https://github.com/cupcakearmy/cryptgeon",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -9,7 +9,7 @@
}, },
"type": "module", "type": "module",
"exports": { "exports": {
".": "./dist/index.mjs" ".": "./dist/index.js"
}, },
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"bin": { "bin": {
@@ -20,8 +20,8 @@
], ],
"scripts": { "scripts": {
"bin": "run-s build package", "bin": "run-s build package",
"build": "rm -rf dist && tsc && ./scripts/build.js", "build": "tsc && node build.js",
"dev": "./scripts/build.js --watch", "dev": "node build.js --watch",
"prepublishOnly": "run-s build" "prepublishOnly": "run-s build"
}, },
"devDependencies": { "devDependencies": {
@@ -31,11 +31,11 @@
"@types/mime": "^3.0.4", "@types/mime": "^3.0.4",
"@types/node": "^20.11.24", "@types/node": "^20.11.24",
"commander": "^12.0.0", "commander": "^12.0.0",
"esbuild": "^0.20.1",
"inquirer": "^9.2.15", "inquirer": "^9.2.15",
"mime": "^4.0.1", "mime": "^4.0.1",
"occulto": "^2.0.3", "occulto": "^2.0.3",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"tsup": "^8.2.4",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"engines": { "engines": {

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env node
import { build, context } from 'esbuild'
import pkg from '../package.json' assert { type: 'json' }
const common = {
bundle: true,
minify: true,
platform: 'node',
define: { VERSION: `"${pkg.version}"` },
}
const cliOptions = {
...common,
entryPoints: ['./src/cli.ts'],
format: 'cjs',
outfile: './dist/cli.cjs',
}
const indexOptions = {
...common,
entryPoints: ['./src/index.ts'],
outfile: './dist/index.mjs',
format: 'esm',
}
const watch = process.argv.slice(2)[0] === '--watch'
if (watch) {
const ctx = await context(cliOptions)
ctx.watch()
} else {
await build(cliOptions)
await build(indexOptions)
}

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Argument, Option, program } from '@commander-js/extra-typings' import { Argument, Option, program } from '@commander-js/extra-typings'
import { setBase, status } from '@cryptgeon/shared' import { setOptions, status } from '@cryptgeon/shared'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { download } from './download.js' import { download } from './download.js'
@@ -33,7 +33,7 @@ program
.description('show information about the server') .description('show information about the server')
.addOption(server) .addOption(server)
.action(async (options) => { .action(async (options) => {
setBase(options.server) setOptions({ server: options.server })
const response = await status() const response = await status()
const formatted = { const formatted = {
...response, ...response,
@@ -54,7 +54,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (files, options) => { .action(async (files, options) => {
setBase(options.server!) setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {
@@ -72,7 +72,7 @@ send
.addOption(minutes) .addOption(minutes)
.addOption(password) .addOption(password)
.action(async (text, options) => { .action(async (text, options) => {
setBase(options.server!) setOptions({ server: options.server })
await checkConstrains(options) await checkConstrains(options)
options.password ||= await getStdin() options.password ||= await getStdin()
try { try {

View File

@@ -1,4 +1,4 @@
import { Adapters, get, info, setBase } from '@cryptgeon/shared' import { Adapters, get, info, setOptions } from '@cryptgeon/shared'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import { access, constants, writeFile } from 'node:fs/promises' import { access, constants, writeFile } from 'node:fs/promises'
import { basename, resolve } from 'node:path' import { basename, resolve } from 'node:path'
@@ -6,7 +6,7 @@ import { AES, Hex } from 'occulto'
import pretty from 'pretty-bytes' import pretty from 'pretty-bytes'
export async function download(url: URL, all: boolean, suggestedPassword?: string) { export async function download(url: URL, all: boolean, suggestedPassword?: string) {
setBase(url.origin) setOptions({ server: url.origin })
const id = url.pathname.split('/')[2] const id = url.pathname.split('/')[2]
const preview = await info(id).catch(() => { const preview = await info(id).catch(() => {
throw new Error('Note does not exist or is expired') throw new Error('Note does not exist or is expired')

View File

@@ -1,7 +1,7 @@
import { readFile, stat } from 'node:fs/promises' import { readFile, stat } from 'node:fs/promises'
import { basename } from 'node:path' import { basename } from 'node:path'
import { Adapters, BASE, create, FileDTO, Note, NoteMeta } from '@cryptgeon/shared' import { Adapters, create, getOptions, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
import mime from 'mime' import mime from 'mime'
import { AES, Hex } from 'occulto' import { AES, Hex } from 'occulto'
@@ -39,7 +39,7 @@ export async function upload(input: string | string[], options: UploadOptions):
// Create the actual note and upload it. // Create the actual note and upload it.
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } } const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
const result = await create(note) const result = await create(note)
let url = `${BASE}/note/${result.id}` let url = `${getOptions().server}/note/${result.id}`
if (!derived) url += `#${Hex.encode(key)}` if (!derived) url += `#${Hex.encode(key)}`
return url return url
} }

View File

@@ -23,7 +23,7 @@
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement chiffrés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.", "intro": "Envoyez facilement des notes ou des fichiers <i>entièrement chiffré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}.", "explanation": "la note expirera et sera détruite après {type}.",
"new_note": "nouvelle note", "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 />(tout ira probablement bien, soyez juste averti.)", "new_note_notice": "<b>disponibilité :</b><br />il n'est pas garanti que la note reste stockée car tout est conservé dans la mémoire vive; si elle se remplit, les notes les plus anciennes seront supprimées.<br />(tout ira probablement bien, soyez juste averti.)",
"errors": { "errors": {
"note_to_big": "Impossible de créer une note. La note est trop grande.", "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.", "note_error": "n'a pas pu créer de note. Veuillez réessayer.",
@@ -45,9 +45,9 @@
"unsupported_type": "type de note non supporté." "unsupported_type": "type de note non supporté."
}, },
"explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.", "explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.",
"show_note": "note de présentation", "show_note": "afficher la note",
"warning_will_not_see_again": "vous <b>n'aurez pas</b> la chance de revoir la note.", "warning_will_not_see_again": "vous <b>n'aurez pas</b> la chance de revoir la note.",
"download_all": "télécharger tout", "download_all": "tout télécharger",
"links_found": "liens trouvés à lintérieur de la note :" "links_found": "liens trouvés à lintérieur de la note :"
}, },
"file_upload": { "file_upload": {

View File

@@ -18,19 +18,19 @@
export let icon: keyof typeof map export let icon: keyof typeof map
</script> </script>
<div on:click {...$$restProps}> <button on:click {...$$restProps}>
{#if map[icon]} {#if map[icon]}
<svelte:component this={map[icon]} /> <svelte:component this={map[icon]} />
{/if} {/if}
</div> </button>
<style> <style>
div { button {
display: inline-block; display: inline-block;
contain: strict; contain: strict;
box-sizing: content-box; box-sizing: content-box;
} }
div > :global(svg) { button > :global(svg) {
display: block; display: block;
fill: currentColor; fill: currentColor;
} }

View File

@@ -12,5 +12,5 @@ const server = http.createServer(function (req, res) {
const target = req.url.startsWith('/api/') ? 'http://127.0.0.1:8000' : 'http://localhost:8001' const target = req.url.startsWith('/api/') ? 'http://127.0.0.1:8000' : 'http://localhost:8001'
proxy.web(req, res, { target, proxyTimeout: 250, timeout: 250 }) proxy.web(req, res, { target, proxyTimeout: 250, timeout: 250 })
}) })
server.listen(1234) server.listen(3000)
console.log('Proxy on http://localhost:1234') console.log('Proxy on http://localhost:3000')

View File

@@ -23,6 +23,10 @@ export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
contents: string contents: string
} }
type ClientOptions = {
server: string
}
type CallOptions = { type CallOptions = {
url: string url: string
method: string method: string
@@ -31,14 +35,21 @@ type CallOptions = {
export class PayloadToLargeError extends Error {} export class PayloadToLargeError extends Error {}
export let BASE = '' export let client: ClientOptions = {
server: '',
}
export function setBase(url: string) { export function setOptions(options: Partial<ClientOptions>) {
BASE = url client = { ...client, ...options }
}
export function getOptions(): ClientOptions {
return client
} }
export async function call(options: CallOptions) { export async function call(options: CallOptions) {
const response = await fetch(BASE + '/api/' + options.url, { const url = client.server + '/api/' + options.url
const response = await fetch(url, {
method: options.method, method: options.method,
body: options.body === undefined ? undefined : JSON.stringify(options.body), body: options.body === undefined ? undefined : JSON.stringify(options.body),
mode: 'cors', mode: 'cors',
@@ -105,6 +116,7 @@ export type Status = {
theme_text: string theme_text: string
theme_favicon: string theme_favicon: string
theme_page_title: string theme_page_title: string
theme_new_note_notice: boolean
} }
export async function status() { export async function status() {

View File

@@ -3,17 +3,18 @@ import { devices, type PlaywrightTestConfig } from '@playwright/test'
const config: PlaywrightTestConfig = { const config: PlaywrightTestConfig = {
use: { use: {
video: 'retain-on-failure', video: 'retain-on-failure',
baseURL: 'http://localhost:1234', baseURL: 'http://localhost:3000',
actionTimeout: 60_000, actionTimeout: 10_000,
}, },
outputDir: './test-results', outputDir: './test-results',
testDir: './test', testDir: './test',
timeout: 60_000, timeout: 30_000,
fullyParallel: true,
webServer: { webServer: {
command: 'docker compose -f docker-compose.dev.yaml up', command: 'docker compose -f docker-compose.dev.yaml up',
port: 1234, port: 3000,
reuseExistingServer: true, reuseExistingServer: true,
}, },
@@ -21,10 +22,6 @@ const config: PlaywrightTestConfig = {
{ name: 'chrome', use: { ...devices['Desktop Chrome'] } }, { name: 'chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'safari', use: { ...devices['Desktop Safari'] } }, { name: 'safari', use: { ...devices['Desktop Safari'] } },
{ name: 'cli', use: { ...devices['Desktop Chrome'] }, grep: [/@cli/] },
{ name: 'web', use: { ...devices['Desktop Chrome'] }, grep: [/@web/] },
{ name: 'cross', use: { ...devices['Desktop Chrome'] }, grep: [/@cross/] },
], ],
} }

4726
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
import { test } from '@playwright/test' import { test } from '@playwright/test'
import { basename } from 'node:path' import { basename } from 'node:path'
import { Files, getFileChecksum, rm, tmpFile } from '../../files' import { rm } from 'node:fs/promises'
import { Files, getFileChecksum, tmpFile } from '../../files'
import { CLI, getLinkFromCLI } from '../../utils' import { CLI, getLinkFromCLI } from '../../utils'
test.describe('file @cli', () => { test.describe('file @cli', () => {
test('simple', async ({ page }) => { test('simple', async () => {
const file = await tmpFile(Files.Image) const file = await tmpFile(Files.Image)
const checksum = await getFileChecksum(file) const checksum = await getFileChecksum(file)
const note = await CLI('send', 'file', file) const note = await CLI('send', 'file', file)
@@ -17,7 +18,7 @@ test.describe('file @cli', () => {
test.expect(checksum).toBe(c) test.expect(checksum).toBe(c)
}) })
test('simple with password', async ({ page }) => { test('simple with password', async () => {
const file = await tmpFile(Files.Image) const file = await tmpFile(Files.Image)
const password = 'password' const password = 'password'
const checksum = await getFileChecksum(file) const checksum = await getFileChecksum(file)

View File

@@ -2,7 +2,7 @@ import { test } from '@playwright/test'
import { CLI, getLinkFromCLI } from '../../utils' import { CLI, getLinkFromCLI } from '../../utils'
test.describe('text @cli', () => { test.describe('text @cli', () => {
test('simple', async ({ page }) => { test('simple', async () => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.` const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const note = await CLI('send', 'text', text) const note = await CLI('send', 'text', text)
const link = getLinkFromCLI(note.stdout) const link = getLinkFromCLI(note.stdout)
@@ -11,7 +11,7 @@ test.describe('text @cli', () => {
test.expect(retrieved.stdout.trim()).toBe(text) test.expect(retrieved.stdout.trim()).toBe(text)
}) })
test('simple with password', async ({ page }) => { test('simple with password', async () => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying.` const text = `Endless prejudice endless play derive joy eternal-return selfish burying.`
const password = 'password' const password = 'password'
const note = await CLI('send', 'text', text, '--password', password) const note = await CLI('send', 'text', text, '--password', password)

View File

@@ -1,10 +1,5 @@
import { createHash } from 'crypto' import { createHash } from 'node:crypto'
import { cp as cpFN, rm as rmFN } from 'fs' import { cp, readFile } from 'node:fs/promises'
import { readFile } from 'fs/promises'
import { promisify } from 'util'
export const cp = promisify(cpFN)
export const rm = promisify(rmFN)
export const Files = { export const Files = {
PDF: 'test/assets/AES.pdf', PDF: 'test/assets/AES.pdf',

View File

@@ -91,7 +91,7 @@ export async function CLI(...args: string[]) {
return await exec('./packages/cli/dist/cli.cjs', args, { return await exec('./packages/cli/dist/cli.cjs', args, {
env: { env: {
...process.env, ...process.env,
CRYPTGEON_SERVER: 'http://localhost:1234', CRYPTGEON_SERVER: 'http://localhost:3000',
}, },
}) })
} }

View File

@@ -3,8 +3,8 @@ import { createNote } from '../../utils'
import { Files } from '../../files' import { Files } from '../../files'
test.describe('@web', () => { test.describe('@web', () => {
test.skip('to big zip', async ({ page }) => { test('to big zip', async ({ page }) => {
const files = [Files.Zip] const files = [Files.Zip]
const link = await createNote(page, { files, error: 'note is to big' }) await createNote(page, { files, error: 'note is to big' })
}) })
}) })