mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2025-09-08 10:20:40 +00:00
Compare commits
29 Commits
v2.6.0
...
c3794fa2b6
Author | SHA1 | Date | |
---|---|---|---|
c3794fa2b6 | |||
f9962c76c1 | |||
c2b81bc04d | |||
a45f6a3772 | |||
2006be0434 | |||
ca72e94e3c | |||
dbcb3870aa | |||
3ea176cc1f | |||
145f9ef18f | |||
784c54236b | |||
5648c76f78 | |||
7761c795df | |||
4aadeb492a | |||
0d9f3fe9c7 | |||
f790438104 | |||
5936f4588c | |||
|
d3c04f8fda | ||
|
f8c17487bd | ||
|
ed3e5f48a0 | ||
|
e08c9d1871 | ||
6d2150b0b6 | |||
3a68693be1 | |||
|
a612eec220 | ||
98d3b0d394 | |||
|
6aed2e2756 | ||
6bb527198a | |||
|
7050389316 | ||
0725a0c6f7 | |||
|
c8efcc04fc |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ target
|
|||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
test-results
|
test-results
|
||||||
|
tmp
|
||||||
|
@@ -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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -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"
|
||||||
|
10
README.md
10
README.md
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -158,7 +158,7 @@ pnpm run dev
|
|||||||
- 无热重载的 rust 后端
|
- 无热重载的 rust 后端
|
||||||
- 可热重载的客户端
|
- 可热重载的客户端
|
||||||
|
|
||||||
你可以通过 1234 端口进入该应用,即 [localhost:1234](http://localhost:1234).
|
你可以通过 3000 端口进入该应用,即 [localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
|
@@ -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
|
||||||
@@ -16,7 +14,7 @@ services:
|
|||||||
- 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/']
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
|
1462
packages/backend/Cargo.lock
generated
1462
packages/backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,27 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "2.6.0"
|
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"
|
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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()),
|
|
||||||
);
|
|
||||||
}
|
|
@@ -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)?)
|
|
||||||
}
|
|
@@ -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,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
@@ -1,44 +1,70 @@
|
|||||||
use actix_web::{
|
use axum::{
|
||||||
middleware::{self, Logger},
|
extract::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,
|
||||||
|
limit::RequestBodyLimitLayer,
|
||||||
|
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(
|
||||||
|
CompressionLayer::new()
|
||||||
|
.br(true)
|
||||||
|
.deflate(true)
|
||||||
|
.gzip(true)
|
||||||
|
.zstd(true),
|
||||||
|
)
|
||||||
|
.layer(RequestBodyLimitLayer::new(*config::LIMIT));
|
||||||
|
|
||||||
|
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());
|
||||||
|
println!("Config {}", *config::LIMIT);
|
||||||
|
axum::serve(listener, ServiceExt::<Request>::into_make_service(app))
|
||||||
|
// axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
@@ -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,
|
||||||
|
pub theme_new_note_notice: bool,
|
||||||
|
// Theme
|
||||||
|
pub theme_image: String,
|
||||||
|
pub theme_text: String,
|
||||||
|
pub theme_page_title: String,
|
||||||
|
pub theme_favicon: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
@@ -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,
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
}
|
|
@@ -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
14
packages/cli/build.js
Normal 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,
|
||||||
|
})
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cryptgeon",
|
"name": "cryptgeon",
|
||||||
"version": "2.6.0",
|
"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": {
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
@@ -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 {
|
||||||
|
@@ -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')
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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 à l’intérieur de la note :"
|
"links_found": "liens trouvés à l’intérieur de la note :"
|
||||||
},
|
},
|
||||||
"file_upload": {
|
"file_upload": {
|
||||||
|
58
packages/frontend/locales/pl.json
Normal file
58
packages/frontend/locales/pl.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"note": "notatka",
|
||||||
|
"file": "plik",
|
||||||
|
"advanced": "zaawansowane",
|
||||||
|
"create": "utwórz",
|
||||||
|
"loading": "ładowanie",
|
||||||
|
"mode": "tryb",
|
||||||
|
"views": "{n, plural, =0 {wyświetleń} =1 {1 wyświetlenie} other {# wyświetleń}}",
|
||||||
|
"minutes": "{n, plural, =0 {minut} =1 {1 minuta} other {# minuty}}",
|
||||||
|
"max": "maks.",
|
||||||
|
"share_link": "link udostępniania",
|
||||||
|
"copy_clipboard": "kopiuj do schowka",
|
||||||
|
"copied_to_clipboard": "skopiowano do schowka",
|
||||||
|
"encrypting": "szyfrowanie",
|
||||||
|
"decrypting": "odszyfrowywanie",
|
||||||
|
"uploading": "wysyłanie",
|
||||||
|
"downloading": "pobieranie",
|
||||||
|
"qr_code": "kod QR",
|
||||||
|
"password": "hasło"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"intro": "Łatwo wysyłaj <i>w pełni zaszyfrowane</i>, bezpieczne notatki lub pliki jednym kliknięciem. Po prostu utwórz notatkę i udostępnij link.",
|
||||||
|
"explanation": "notatka wygaśnie i zostanie zniszczona po {type}.",
|
||||||
|
"new_note": "nowa notatka",
|
||||||
|
"new_note_notice": "<b>dostępność:</b><br />nie ma gwarancji, że notatka będzie przechowywana, ponieważ wszystko jest przechowywane w pamięci RAM, jeśli się zapełni, najstarsze notatki zostaną usunięte.<br />(prawdopodobnie nic się nie stanie, ale warto ostrzec.)",
|
||||||
|
"errors": {
|
||||||
|
"note_to_big": "nie można utworzyć notatki. notatka jest za duża",
|
||||||
|
"note_error": "nie można utworzyć notatki. spróbuj ponownie.",
|
||||||
|
"max": "maks .: {n}",
|
||||||
|
"empty_content": "notatka jest pusta."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"note_created": "notatka utworzona."
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"explanation": "Domyślnie dla każdej notatki używane jest bezpiecznie wygenerowane hasło. Możesz jednak wybrać własne hasło, które nie jest uwzględnione w linku.",
|
||||||
|
"custom_password": "własne hasło"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"show": {
|
||||||
|
"errors": {
|
||||||
|
"not_found": "notatka nie została znaleziona lub została już usunięta.",
|
||||||
|
"decryption_failed": "błędne hasło. nie można odszyfrować. prawdopodobnie uszkodzony link. notatka została zniszczona.",
|
||||||
|
"unsupported_type": "nieobsługiwany typ notatki."
|
||||||
|
},
|
||||||
|
"explanation": "kliknij poniżej, aby wyświetlić i usunąć notatkę, jeśli licznik osiągnie swój limit",
|
||||||
|
"show_note": "pokaż notatkę",
|
||||||
|
"warning_will_not_see_again": "<b>nie będziesz mieć</b> możliwości ponownego zobaczenia notatki.",
|
||||||
|
"download_all": "pobierz wszystko",
|
||||||
|
"links_found": "linki znalezione w notatce:"
|
||||||
|
},
|
||||||
|
"file_upload": {
|
||||||
|
"selected_files": "Wybrane pliki",
|
||||||
|
"no_files_selected": "Nie wybrano plików",
|
||||||
|
"clear": "Wyczyść"
|
||||||
|
}
|
||||||
|
}
|
@@ -23,7 +23,7 @@
|
|||||||
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
|
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
|
||||||
"explanation": "заметка истечет и будет уничтожена после {type}.",
|
"explanation": "заметка истечет и будет уничтожена после {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.)",
|
"new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)",
|
||||||
"errors": {
|
"errors": {
|
||||||
"note_to_big": "нельзя создать новую заметку. заметка слишком большая",
|
"note_to_big": "нельзя создать новую заметку. заметка слишком большая",
|
||||||
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
|
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
"svelte-intl-precompile": "^0.12.3",
|
"svelte-intl-precompile": "^0.12.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.1.4"
|
"vite": "^5.1.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cryptgeon/shared": "workspace:*",
|
"@cryptgeon/shared": "workspace:*",
|
||||||
|
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
{#if $status?.theme_new_note_notice}
|
{#if $status?.theme_new_note_notice}
|
||||||
<p>
|
<p>
|
||||||
{@html $t('home.theme_new_note_notice')}
|
{@html $t('home.new_note_notice')}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<br />
|
<br />
|
||||||
|
@@ -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')
|
||||||
|
@@ -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',
|
||||||
|
@@ -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: 10_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,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
4728
pnpm-lock.yaml
generated
4728
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ 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' })
|
const link = await createNote(page, { files, error: 'note is to big' })
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user