Merge branch 'main' into main

This commit is contained in:
2026-05-31 23:58:09 +02:00
committed by GitHub
28 changed files with 2786 additions and 2265 deletions
+649 -422
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -2,8 +2,8 @@
name = "cryptgeon"
version = "2.9.1"
authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021"
rust-version = "1.85"
edition = "2024"
rust-version = "1.95"
[[bin]]
name = "cryptgeon"
@@ -11,12 +11,12 @@ path = "src/main.rs"
[dependencies]
# Core
axum = "0.7.5"
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"] }
axum = "0.8"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["full"] }
redis = { version = "1", features = ["tls-native-tls"] }
# Utility
serde_json = "1"
+1 -1
View File
@@ -2,7 +2,7 @@
"private": true,
"name": "@cryptgeon/backend",
"scripts": {
"dev": "cargo watch -x 'run --bin cryptgeon'",
"dev": "watchexec -r -e rs cargo run",
"build": "cargo build --release",
"test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:3000 cargo run",
"test:prepare": "cargo build"
+5 -5
View File
@@ -1,9 +1,9 @@
use std::{collections::HashMap, sync::Arc};
use axum::{
Router, ServiceExt,
extract::{DefaultBodyLimit, Request},
routing::{delete, get, post},
Router, ServiceExt,
};
use dotenv::dotenv;
use lock::SharedState;
@@ -41,14 +41,14 @@ async fn main() {
let notes_routes = Router::new()
.route("/", post(note::create))
.route("/:id", delete(note::delete))
.route("/:id", get(note::preview));
.route("/{id}", delete(note::delete))
.route("/{id}", get(note::preview));
let health_routes = Router::new().route("/live", get(health::report_health));
let status_routes = Router::new().route("/status", get(status::get_status));
let api_routes = Router::new()
.nest("/notes", notes_routes)
.nest("/", health_routes)
.nest("/", status_routes);
.merge(health_routes)
.merge(status_routes);
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
let serve_dir =
+6 -6
View File
@@ -30,16 +30,16 @@
},
"devDependencies": {
"@commander-js/extra-typings": "^12.1.0",
"@types/inquirer": "^9.0.7",
"@types/inquirer": "^9.0.9",
"@types/mime": "^4.0.0",
"@types/node": "^20.11.24",
"@types/node": "^20.19.41",
"commander": "^12.1.0",
"inquirer": "^9.2.15",
"mime": "^4.0.1",
"inquirer": "^9.3.8",
"mime": "^4.1.0",
"occulto": "^2.0.6",
"pretty-bytes": "^6.1.1",
"tsup": "^8.2.4",
"typescript": "^5.3.3"
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=18"
+1 -1
View File
@@ -5,7 +5,7 @@
"advanced": "avanzato",
"create": "crea",
"loading": "carica",
"mode": "modalita",
"mode": "modalità",
"views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}",
"minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}",
"max": "max",
+2 -2
View File
@@ -17,7 +17,7 @@
"uploading": "アップロード中",
"downloading": "ダウンロード中",
"qr_code": "QRコード",
"password": "暗号"
"password": "パスワード"
},
"home": {
"intro": "<i>完全に暗号化された</i> 、安全なメモやファイルをワンクリックで簡単に送信できます。メモを作成してリンクを共有するだけです。",
@@ -46,7 +46,7 @@
},
"explanation": "カウンターが上限に達した場合、ノートの表示と削除を行うには、以下をクリックします。",
"show_note": "メモを表示",
"warning_will_not_see_again": "あなた <b>できません</b> このノートをもう一度見る",
"warning_will_not_see_again": "このノートを再度表示することは<b>できません</b>",
"download_all": "すべてダウンロード",
"links_found": "メモ内にあるリンク:"
},
+1 -1
View File
@@ -27,7 +27,7 @@
"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}",
"max": "maks.: {n}",
"empty_content": "notatka jest pusta."
},
"messages": {
+58 -58
View File
@@ -1,58 +1,58 @@
{
"common": {
"note": "заметка",
"file": "файл",
"advanced": "расширенные",
"create": "создать",
"loading": "загрузка",
"mode": "режим",
"views": "{n, plural, =0 {просмотры} =1 {1 просмотр} other {# просмотры}}",
"minutes": "{n, plural, =0 {минут} =1 {1 минута} other {# минуты}}",
"max": "макс",
"share_link": "поделиться ссылкой",
"copy_clipboard": "скопировать в буфер обмена",
"copied_to_clipboard": "скопировано в буфер обмена",
"encrypting": "шифрование",
"decrypting": "расшифровка",
"uploading": "загрузка",
"downloading": "скачивание",
"qr_code": "qr код",
"password": "пароль"
},
"home": {
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
"explanation": "заметка истечет и будет уничтожена после {type}.",
"new_note": "новая заметка",
"new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)",
"errors": {
"note_to_big": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
"max": "макс: {n}",
"empty_content": "пустая заметка."
},
"messages": {
"note_created": "заметка создана."
},
"advanced": {
"explanation": "По умолчанию для каждой заметки используется безопасно сгенерированный пароль. Однако вы также можете выбрать свой собственный пароль, который не включен в ссылку.",
"custom_password": "пользовательский пароль"
}
},
"show": {
"errors": {
"not_found": "заметка не найдена или была удалена.",
"decryption_failed": "неправильный пароль. не смог расшифровать. возможно ссылка битая. записка уничтожена.",
"unsupported_type": "неподдерживаемый тип заметки."
},
"explanation": "щелкните ниже, чтобы показать и удалить примечание, если счетчик достиг предела",
"show_note": "показать заметку",
"warning_will_not_see_again": "вы <b>не сможете</b> больше просмотреть заметку.",
"download_all": "скачать всё",
"links_found": "ссылки внутри заметки:"
},
"file_upload": {
"selected_files": "Выбранные файлы",
"no_files_selected": "Файлы не выбраны",
"clear": "Сброс"
}
}
{
"common": {
"note": "заметка",
"file": "файл",
"advanced": "расширенные",
"create": "создать",
"loading": "загрузка",
"mode": "режим",
"views": "{n, plural, =0 {просмотры} =1 {1 просмотр} other {# просмотры}}",
"minutes": "{n, plural, =0 {минут} =1 {1 минута} other {# минуты}}",
"max": "макс",
"share_link": "поделиться ссылкой",
"copy_clipboard": "скопировать в буфер обмена",
"copied_to_clipboard": "скопировано в буфер обмена",
"encrypting": "шифрование",
"decrypting": "расшифровка",
"uploading": "загрузка",
"downloading": "скачивание",
"qr_code": "qr код",
"password": "пароль"
},
"home": {
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
"explanation": "заметка истечет и будет уничтожена после {type}.",
"new_note": "новая заметка",
"new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)",
"errors": {
"note_to_big": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалуйста попробуйте позже.",
"max": "макс: {n}",
"empty_content": "пустая заметка."
},
"messages": {
"note_created": "заметка создана."
},
"advanced": {
"explanation": "По умолчанию для каждой заметки используется безопасно сгенерированный пароль. Однако вы также можете выбрать свой собственный пароль, который не включен в ссылку.",
"custom_password": "пользовательский пароль"
}
},
"show": {
"errors": {
"not_found": "заметка не найдена или была удалена.",
"decryption_failed": "неправильный пароль. не смог расшифровать. возможно ссылка битая. заметка уничтожена.",
"unsupported_type": "неподдерживаемый тип заметки."
},
"explanation": "щелкните ниже, чтобы показать и удалить заметку, если счетчик достиг предела",
"show_note": "показать заметку",
"warning_will_not_see_again": "вы <b>не сможете</b> больше просмотреть заметку.",
"download_all": "скачать всё",
"links_found": "ссылки внутри заметки:"
},
"file_upload": {
"selected_files": "Выбранные файлы",
"no_files_selected": "Файлы не выбраны",
"clear": "Сброс"
}
}
+12 -12
View File
@@ -14,24 +14,24 @@
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^13.2.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.17.3",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.61.1",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@zerodevx/svelte-toast": "^0.9.6",
"adm-zip": "^0.5.16",
"dotenv": "^16.4.7",
"svelte": "^5.20.5",
"svelte-check": "^4.1.4",
"adm-zip": "^0.5.17",
"dotenv": "^17.4.2",
"svelte": "^5.55.9",
"svelte-check": "^4.4.8",
"svelte-intl-precompile": "^0.12.3",
"tslib": "^2.8.1",
"typescript": "^5.7.3",
"vite": "^6.2.0"
"typescript": "^6.0.3",
"vite": "^8.0.14"
},
"dependencies": {
"@fontsource/fira-mono": "^5.1.1",
"@fontsource/fira-mono": "^5.2.7",
"cryptgeon": "workspace:*",
"occulto": "^2.0.6",
"pretty-bytes": "^6.1.1",
"qrious": "^4.0.2"
"pretty-bytes": "^7.1.0",
"uqr": "^0.1.3"
}
}
@@ -10,7 +10,7 @@
import { status } from '$lib/stores/status'
import Button from '$lib/ui/Button.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import Canvas from './Canvas.svelte'
import QR from './QR.svelte'
interface Props {
result: NoteResult
@@ -18,8 +18,11 @@
let { result }: Props = $props()
let url = $state(`${window.location.origin}/note/${result.id}`)
if (result.password) url += `#${result.password}`
let url = $derived.by(() => {
let url = `${window.location.origin}/note/${result.id}`
if (result.password) url += `#${result.password}`
return url
})
function reset() {
window.location.reload()
@@ -36,7 +39,7 @@
/>
<div>
<Canvas value={url} />
<QR value={url} />
</div>
{#if $status?.theme_new_note_notice}
@@ -1,9 +1,7 @@
<script lang="ts">
// @ts-ignore
import QR from 'qrious'
import { t } from 'svelte-intl-precompile'
import { getCSSVariable } from '$lib/utils'
import { t } from 'svelte-intl-precompile'
import { renderSVG } from 'uqr'
interface Props {
value: string
@@ -11,35 +9,36 @@
let { value }: Props = $props()
let canvas: HTMLCanvasElement | null = $state(null)
let qr: string | null = $state(null)
$effect(() => {
new QR({
value,
level: 'Q',
size: 800,
background: getCSSVariable('--ui-bg-0'),
foreground: getCSSVariable('--ui-text-0'),
element: canvas,
qr = renderSVG(value, {
ecc: 'Q',
blackColor: getCSSVariable('--ui-bg-0'),
whiteColor: getCSSVariable('--ui-text-0'),
})
})
</script>
<small>{$t('common.qr_code')}</small>
<div>
<canvas bind:this={canvas}></canvas>
{#if qr}
{@html qr}
{/if}
</div>
<style>
div {
padding: 0.5rem;
padding: 0.25rem;
width: fit-content;
border: 2px solid var(--ui-bg-1);
background-color: var(--ui-bg-0);
margin-top: 0.125rem;
overflow: hidden;
aspect-ratio: 1;
}
canvas {
div :global(svg) {
width: 100%;
height: auto;
}
@@ -32,6 +32,7 @@
let files: FileDTO[] = $state([])
async function downloadFile(file: FileDTO) {
// @ts-ignore
const f = new File([file.contents], file.name, {
type: file.type,
})
@@ -21,6 +21,7 @@
...rest
}: HTMLInputAttributes & Props = $props()
// svelte-ignore state_referenced_locally
const initialType = $state(rest.type)
const isPassword = initialType === 'password'
let hidden = $state(true)
@@ -16,7 +16,7 @@
let { data }: Props = $props()
let id = data.id
let id = $derived(data.id)
let password: string | null = $state<string | null>(null)
let note: DecryptedNote | null = $state(null)
let exists = $state(false)
+3 -2
View File
@@ -2,6 +2,7 @@
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"strict": true,
"allowSyntheticDefaultImports": true
}
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
},
}