mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2025-09-08 18:30:40 +00:00
Compare commits
1 Commits
v2.8.3
...
8583ce2d0f
Author | SHA1 | Date | |
---|---|---|---|
|
8583ce2d0f |
13
.github/workflows/test.yaml
vendored
13
.github/workflows/test.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
# Node
|
# Node
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v3
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -27,16 +27,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
pnpm install
|
pnpm install --frozen-lockfile
|
||||||
pnpm exec playwright install --with-deps
|
pnpm exec playwright install --with-deps
|
||||||
pnpm run test:prepare
|
pnpm run test:prepare
|
||||||
|
|
||||||
- name: Run your tests
|
- name: Run your tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: test-results
|
||||||
path: test-results/
|
path: test-results
|
||||||
retention-days: 7
|
|
||||||
|
@@ -15,8 +15,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -35,8 +40,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -85,8 +95,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -155,8 +170,13 @@
|
|||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/",
|
"raw": "{{BASE}}/notes/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": [
|
"response": [
|
||||||
@@ -176,8 +196,13 @@
|
|||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/",
|
"raw": "{{BASE}}/notes/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
@@ -228,8 +253,13 @@
|
|||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/",
|
"raw": "{{BASE}}/notes/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
@@ -280,8 +310,13 @@
|
|||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/",
|
"raw": "{{BASE}}/notes/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
@@ -325,8 +360,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -344,8 +384,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -393,8 +438,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/notes/:id",
|
"raw": "{{BASE}}/notes/:id",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["notes", ":id"],
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"notes",
|
||||||
|
":id"
|
||||||
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
@@ -445,8 +495,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/status/",
|
"raw": "{{BASE}}/status/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["status", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"status",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": [
|
"response": [
|
||||||
@@ -457,8 +512,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/status/",
|
"raw": "{{BASE}}/status/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["status", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"status",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
@@ -502,8 +562,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/live/",
|
"raw": "{{BASE}}/live/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["live", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"live",
|
||||||
|
""
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"description": "Return `200` for healthy service. `503` if service is unavailable."
|
"description": "Return `200` for healthy service. `503` if service is unavailable."
|
||||||
},
|
},
|
||||||
@@ -515,8 +580,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/live/",
|
"raw": "{{BASE}}/live/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["live", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"live",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
@@ -550,8 +620,13 @@
|
|||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{BASE}}/live/",
|
"raw": "{{BASE}}/live/",
|
||||||
"host": ["{{BASE}}"],
|
"host": [
|
||||||
"path": ["live", ""]
|
"{{BASE}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"live",
|
||||||
|
""
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "Service Unavailable",
|
"status": "Service Unavailable",
|
||||||
@@ -588,21 +663,25 @@
|
|||||||
"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:3000/api",
|
"value": "http://localhost:1234/api",
|
||||||
"type": "default"
|
"type": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
16
README.md
16
README.md
@@ -12,7 +12,6 @@
|
|||||||
<br/><br/>
|
<br/><br/>
|
||||||
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
|
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
|
||||||
<a href=""><img src="./.github/lokalise.png" height="50">
|
<a href=""><img src="./.github/lokalise.png" height="50">
|
||||||
<a title="Install cryptgeon Raycast Extension" href="https://www.raycast.com/cupcakearmy/cryptgeon"><img src="https://www.raycast.com/cupcakearmy/cryptgeon/install_button@2x.png?v=1.1" height="64" alt="" style="height: 64px;"></a>
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
EN | [简体中文](README_zh-CN.md) | [ES](README_ES.md)
|
EN | [简体中文](README_zh-CN.md) | [ES](README_ES.md)
|
||||||
@@ -40,12 +39,6 @@ npx cryptgeon send text "This is a secret note"
|
|||||||
|
|
||||||
For more documentation about the CLI see the [readme](./packages/cli/README.md).
|
For more documentation about the CLI see the [readme](./packages/cli/README.md).
|
||||||
|
|
||||||
### Raycast Extension
|
|
||||||
|
|
||||||
There is an [official Raycast extension](https://www.raycast.com/cupcakearmy/cryptgeon).
|
|
||||||
|
|
||||||
<a title="Install cryptgeon Raycast Extension" href="https://www.raycast.com/cupcakearmy/cryptgeon"><img src="https://www.raycast.com/cupcakearmy/cryptgeon/install_button@2x.png?v=1.1" height="64" alt="" style="height: 64px;"></a>
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- send text or files
|
- send text or files
|
||||||
@@ -77,15 +70,14 @@ of the notes even if it tried to.
|
|||||||
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
|
| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. |
|
||||||
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
|
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
|
||||||
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
|
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
|
||||||
|
| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. |
|
||||||
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
|
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
|
||||||
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
|
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
|
||||||
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
|
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
|
||||||
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
|
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
|
||||||
| `THEME_PAGE_TITLE` | `""` | Custom text the page title |
|
| `THEME_PAGE_TITLE` | `""` | Custom text the page title |
|
||||||
| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable |
|
| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable |
|
||||||
| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. |
|
|
||||||
| `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. |
|
|
||||||
| `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both.|
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
> ℹ️ `https` is required otherwise browsers will not support the cryptographic functions.
|
> ℹ️ `https` is required otherwise browsers will not support the cryptographic functions.
|
||||||
@@ -152,8 +144,6 @@ There is a [guide](https://mariushosting.com/how-to-install-cryptgeon-on-your-sy
|
|||||||
### Written Guides
|
### Written Guides
|
||||||
|
|
||||||
- French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/)
|
- French by [zarevskaya](https://belginux.com/installer-cryptgeon-avec-docker/)
|
||||||
- Italian by [@nicfab](https://notes.nicfab.eu/it/posts/cryptgeon/)
|
|
||||||
- English by [@nicfab](https://notes.nicfab.eu/en/posts/cryptgeon/)
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -188,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:3000](http://localhost:3000).
|
You can see the app under [localhost:1234](http://localhost:1234).
|
||||||
|
|
||||||
> 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:3000](http://localhost:3000).
|
Puedes ver la app en [localhost:1234](http://localhost:1234).
|
||||||
|
|
||||||
> 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 后端
|
||||||
- 可热重载的客户端
|
- 可热重载的客户端
|
||||||
|
|
||||||
你可以通过 3000 端口进入该应用,即 [localhost:3000](http://localhost:3000).
|
你可以通过 1234 端口进入该应用,即 [localhost:1234](http://localhost:1234).
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "packages/cli"
|
"path": "packages/cli"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "packages/shared"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@@ -9,12 +9,12 @@ services:
|
|||||||
|
|
||||||
app:
|
app:
|
||||||
build: .
|
build: .
|
||||||
env_file: .env.dev
|
env_file: .dev.env
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 3000:8000
|
- 1234: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/']
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
"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",
|
||||||
"docker:build": "docker compose -f docker-compose.dev.yaml build",
|
"docker:build": "docker compose -f docker-compose.dev.yaml build",
|
||||||
"test": "playwright test --project=chrome --project=firefox --project=safari",
|
"test": "playwright test --project chrome firefox safari",
|
||||||
"test:local": "playwright test --project=chrome",
|
"test:local": "playwright test --project chrome",
|
||||||
"test:server": "run-s docker:up",
|
"test:server": "run-s docker:up",
|
||||||
"test:prepare": "run-p build docker:build",
|
"test:prepare": "run-p build docker:build",
|
||||||
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
|
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"shelljs": "^0.8.5"
|
"shelljs": "^0.8.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.11.0"
|
"packageManager": "pnpm@9.8.0"
|
||||||
}
|
}
|
||||||
|
957
packages/backend/Cargo.lock
generated
957
packages/backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
version = "2.8.3"
|
version = "2.7.0"
|
||||||
authors = ["cupcakearmy <hi@nicco.io>"]
|
authors = ["cupcakearmy <hi@nicco.io>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
@@ -10,18 +10,16 @@ name = "cryptgeon"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
actix-web = "4"
|
||||||
axum = "0.7.5"
|
actix-files = "0.6"
|
||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { version = "1.0", 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 = { version = "0.25.2", features = ["tls-native-tls"] }
|
||||||
|
@@ -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:3000 cargo run",
|
"test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run",
|
||||||
"test:prepare": "cargo build"
|
"test:prepare": "cargo build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
packages/backend/src/api.rs
Normal file
14
packages/backend/src/api.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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()),
|
||||||
|
);
|
||||||
|
}
|
17
packages/backend/src/client.rs
Normal file
17
packages/backend/src/client.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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)?)
|
||||||
|
}
|
@@ -38,12 +38,8 @@ 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 IMPRINT_URL: String = std::env::var("IMPRINT_URL")
|
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
|
||||||
.unwrap_or("".to_string())
|
.unwrap_or("true".to_string())
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML")
|
|
||||||
.unwrap_or("".to_string())
|
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -66,8 +62,4 @@ 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();
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,3 @@
|
|||||||
use crate::store;
|
mod routes;
|
||||||
use axum::http::StatusCode;
|
|
||||||
|
|
||||||
pub async fn report_health() -> (StatusCode,) {
|
pub use routes::*;
|
||||||
if store::can_reach_redis() {
|
|
||||||
return (StatusCode::OK,);
|
|
||||||
} else {
|
|
||||||
return (StatusCode::SERVICE_UNAVAILABLE,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
16
packages/backend/src/health/routes.rs
Normal file
16
packages/backend/src/health/routes.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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,67 +1,44 @@
|
|||||||
use axum::{
|
use actix_web::{
|
||||||
extract::{DefaultBodyLimit, Request},
|
middleware::{self, Logger},
|
||||||
routing::{delete, get, post},
|
web::{self},
|
||||||
Router, ServiceExt,
|
App, HttpServer,
|
||||||
};
|
};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use tower::Layer;
|
use log::error;
|
||||||
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;
|
||||||
|
|
||||||
#[tokio::main]
|
#[actix_web::main]
|
||||||
async fn main() {
|
async fn main() -> std::io::Result<()> {
|
||||||
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() {
|
||||||
println!("cannot reach redis");
|
error!("cannot reach redis");
|
||||||
panic!("canont reach redis");
|
panic!("canont reach redis");
|
||||||
}
|
}
|
||||||
|
|
||||||
let notes_routes = Router::new()
|
return HttpServer::new(|| {
|
||||||
.route("/", post(note::create))
|
App::new()
|
||||||
.route("/:id", delete(note::delete))
|
.wrap(Logger::new("\"%r\" %s %b %T"))
|
||||||
.route("/:id", get(note::preview));
|
.wrap(middleware::Compress::default())
|
||||||
let health_routes = Router::new().route("/live", get(health::report_health));
|
.wrap(middleware::DefaultHeaders::default())
|
||||||
let status_routes = Router::new().route("/status", get(status::get_status));
|
.configure(size::init)
|
||||||
let api_routes = Router::new()
|
.configure(api::init)
|
||||||
.nest("/notes", notes_routes)
|
.configure(client::init)
|
||||||
.nest("/", health_routes)
|
.default_service(web::to(client::index))
|
||||||
.nest("/", status_routes);
|
})
|
||||||
|
.bind(config::LISTEN_ADDR.to_string())?
|
||||||
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
|
.run()
|
||||||
let serve_dir =
|
.await;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
@@ -12,12 +12,12 @@ pub struct Note {
|
|||||||
pub expiration: Option<u32>,
|
pub expiration: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NoteInfo {
|
pub struct NoteInfo {
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct NotePublic {
|
pub struct NotePublic {
|
||||||
pub meta: String,
|
pub meta: String,
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
|
@@ -1,18 +1,11 @@
|
|||||||
use axum::{
|
use actix_web::{delete, get, post, web, HttpResponse, Responder, Scope};
|
||||||
extract::Path,
|
|
||||||
http::StatusCode,
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
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};
|
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
|
||||||
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)
|
||||||
@@ -20,18 +13,20 @@ pub fn now() -> u32 {
|
|||||||
.as_secs() as u32
|
.as_secs() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct OneNoteParams {
|
struct NotePath {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn preview(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
|
#[get("/{id}")]
|
||||||
let note = store::get(&id);
|
async fn one(path: web::Path<NotePath>) -> impl Responder {
|
||||||
|
let p = path.into_inner();
|
||||||
|
let note = store::get(&p.id);
|
||||||
|
|
||||||
match note {
|
match note {
|
||||||
Ok(Some(n)) => (StatusCode::OK, Json(NoteInfo { meta: n.meta })).into_response(),
|
Ok(Some(n)) => HttpResponse::Ok().json(NoteInfo { meta: n.meta }),
|
||||||
Ok(None) => (StatusCode::NOT_FOUND).into_response(),
|
Ok(None) => HttpResponse::NotFound().finish(),
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,16 +35,13 @@ struct CreateResponse {
|
|||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(Json(mut n): Json<Note>) -> Response {
|
#[post("/")]
|
||||||
// let mut n = note.into_inner();
|
async fn create(note: web::Json<Note>) -> impl Responder {
|
||||||
|
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 (
|
return bad_req;
|
||||||
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);
|
||||||
@@ -58,7 +50,7 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
|
|||||||
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 (StatusCode::BAD_REQUEST, "Invalid views").into_response();
|
return bad_req;
|
||||||
}
|
}
|
||||||
n.expiration = None; // views overrides expiration
|
n.expiration = None; // views overrides expiration
|
||||||
}
|
}
|
||||||
@@ -66,8 +58,8 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
|
|||||||
}
|
}
|
||||||
match n.expiration {
|
match n.expiration {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
if e > *config::MAX_EXPIRATION || e < 1 {
|
if e > *config::MAX_EXPIRATION {
|
||||||
return (StatusCode::BAD_REQUEST, "Invalid expiration").into_response();
|
return bad_req;
|
||||||
}
|
}
|
||||||
let expiration = now() + (e * 60);
|
let expiration = now() + (e * 60);
|
||||||
n.expiration = Some(expiration);
|
n.expiration = Some(expiration);
|
||||||
@@ -75,40 +67,38 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
match store::set(&id.clone(), &n.clone()) {
|
match store::set(&id.clone(), &n.clone()) {
|
||||||
Ok(_) => (StatusCode::OK, Json(CreateResponse { id })).into_response(),
|
Ok(_) => return HttpResponse::Ok().json(CreateResponse { id: id }),
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
Err(e) => return HttpResponse::InternalServerError().body(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
|
#[delete("/{id}")]
|
||||||
let note = store::get(&id);
|
async fn delete(path: web::Path<NotePath>) -> impl Responder {
|
||||||
|
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 (StatusCode::BAD_REQUEST).into_response();
|
return HttpResponse::BadRequest().finish();
|
||||||
}
|
}
|
||||||
match changed.views {
|
match changed.views {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
changed.views = Some(v - 1);
|
changed.views = Some(v - 1);
|
||||||
let id = id.clone();
|
let id = p.id.clone();
|
||||||
if v <= 1 {
|
if v <= 1 {
|
||||||
match store::del(&id) {
|
match store::del(&id) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
return HttpResponse::InternalServerError().body(e.to_string())
|
||||||
.into_response();
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match store::set(&id, &changed.clone()) {
|
match store::set(&id, &changed.clone()) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
return HttpResponse::InternalServerError().body(e.to_string())
|
||||||
.into_response();
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -121,26 +111,33 @@ pub async fn delete(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response
|
|||||||
match changed.expiration {
|
match changed.expiration {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
if e < n {
|
if e < n {
|
||||||
match store::del(&id.clone()) {
|
match store::del(&p.id.clone()) {
|
||||||
Ok(_) => return (StatusCode::BAD_REQUEST).into_response(),
|
Ok(_) => return HttpResponse::BadRequest().finish(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
return HttpResponse::InternalServerError().body(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)
|
||||||
|
}
|
||||||
|
12
packages/backend/src/size.rs
Normal file
12
packages/backend/src/size.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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,43 +1,5 @@
|
|||||||
use crate::config;
|
mod model;
|
||||||
use axum::{http::StatusCode, Json};
|
mod routes;
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
pub use model::*;
|
||||||
pub struct Status {
|
pub use routes::*;
|
||||||
// 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 imprint_url: String,
|
|
||||||
pub imprint_html: String,
|
|
||||||
// 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,
|
|
||||||
imprint_url: config::IMPRINT_URL.to_string(),
|
|
||||||
imprint_html: config::IMPRINT_HTML.to_string(),
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
19
packages/backend/src/status/model.rs
Normal file
19
packages/backend/src/status/model.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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,
|
||||||
|
}
|
25
packages/backend/src/status/routes.rs
Normal file
25
packages/backend/src/status/routes.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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)
|
||||||
|
}
|
@@ -1,14 +1,13 @@
|
|||||||
import { build } from 'tsup'
|
|
||||||
import pkg from './package.json' with { type: 'json' }
|
import pkg from './package.json' with { type: 'json' }
|
||||||
|
import { build } from 'tsup'
|
||||||
|
|
||||||
const watch = process.argv.slice(2)[0] === '--watch'
|
const watch = process.argv.slice(2)[0] === '--watch'
|
||||||
|
|
||||||
await build({
|
await build({
|
||||||
entry: ['src/index.ts', 'src/cli.ts', 'src/shared/shared.ts'],
|
entry: ['src/index.ts', 'src/cli.ts'],
|
||||||
dts: true,
|
dts: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
format: ['esm', 'cjs'],
|
format: ['esm', 'cjs'],
|
||||||
target: 'es2020',
|
|
||||||
clean: true,
|
clean: true,
|
||||||
define: { VERSION: `"${pkg.version}"` },
|
define: { VERSION: `"${pkg.version}"` },
|
||||||
watch,
|
watch,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cryptgeon",
|
"name": "cryptgeon",
|
||||||
"version": "2.8.3",
|
"version": "2.7.0",
|
||||||
"homepage": "https://github.com/cupcakearmy/cryptgeon",
|
"homepage": "https://github.com/cupcakearmy/cryptgeon",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -9,11 +9,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js"
|
||||||
"./shared": {
|
|
||||||
"import": "./dist/shared/shared.js",
|
|
||||||
"types": "./dist/shared/shared.d.ts"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -29,14 +25,15 @@
|
|||||||
"prepublishOnly": "run-s build"
|
"prepublishOnly": "run-s build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commander-js/extra-typings": "^12.1.0",
|
"@commander-js/extra-typings": "^12.0.1",
|
||||||
|
"@cryptgeon/shared": "workspace:*",
|
||||||
"@types/inquirer": "^9.0.7",
|
"@types/inquirer": "^9.0.7",
|
||||||
"@types/mime": "^4.0.0",
|
"@types/mime": "^3.0.4",
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.11.24",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.0.0",
|
||||||
"inquirer": "^9.2.15",
|
"inquirer": "^9.2.15",
|
||||||
"mime": "^4.0.1",
|
"mime": "^4.0.1",
|
||||||
"occulto": "^2.0.6",
|
"occulto": "^2.0.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"tsup": "^8.2.4",
|
"tsup": "^8.2.4",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
#!/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 { setOptions, status } from '@cryptgeon/shared'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
|
||||||
import { download } from './actions/download.js'
|
import { download } from './download.js'
|
||||||
import { upload } from './actions/upload.js'
|
import { parseFile, parseNumber } from './parsers.js'
|
||||||
import { API } from './shared/api.js'
|
import { getStdin } from './stdin.js'
|
||||||
import { parseFile, parseNumber } from './utils/parsers.js'
|
import { upload } from './upload.js'
|
||||||
import { getStdin } from './utils/stdin.js'
|
import { checkConstrains, exit } from './utils.js'
|
||||||
import { checkConstrains, exit } from './utils/utils.js'
|
|
||||||
|
|
||||||
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
||||||
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
|
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
|
||||||
@@ -33,8 +33,8 @@ program
|
|||||||
.description('show information about the server')
|
.description('show information about the server')
|
||||||
.addOption(server)
|
.addOption(server)
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
API.setOptions({ server: options.server })
|
setOptions({ server: options.server })
|
||||||
const response = await API.status()
|
const response = await status()
|
||||||
const formatted = {
|
const formatted = {
|
||||||
...response,
|
...response,
|
||||||
max_size: prettyBytes(response.max_size),
|
max_size: prettyBytes(response.max_size),
|
||||||
@@ -54,7 +54,7 @@ send
|
|||||||
.addOption(minutes)
|
.addOption(minutes)
|
||||||
.addOption(password)
|
.addOption(password)
|
||||||
.action(async (files, options) => {
|
.action(async (files, options) => {
|
||||||
API.setOptions({ server: 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) => {
|
||||||
API.setOptions({ server: options.server })
|
setOptions({ server: options.server })
|
||||||
await checkConstrains(options)
|
await checkConstrains(options)
|
||||||
options.password ||= await getStdin()
|
options.password ||= await getStdin()
|
||||||
try {
|
try {
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
|
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'
|
||||||
import { AES, Hex } from 'occulto'
|
import { AES, Hex } from 'occulto'
|
||||||
import pretty from 'pretty-bytes'
|
import pretty from 'pretty-bytes'
|
||||||
import { Adapters } from '../shared/adapters.js'
|
|
||||||
import { API } from '../shared/api.js'
|
|
||||||
|
|
||||||
export async function download(url: URL, all: boolean, suggestedPassword?: string) {
|
export async function download(url: URL, all: boolean, suggestedPassword?: string) {
|
||||||
API.setOptions({ server: url.origin })
|
setOptions({ server: url.origin })
|
||||||
const id = url.pathname.split('/')[2]
|
const id = url.pathname.split('/')[2]
|
||||||
const preview = await API.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')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ export async function download(url: URL, all: boolean, suggestedPassword?: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
|
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
|
||||||
const note = await API.get(id)
|
const note = await get(id)
|
||||||
|
|
||||||
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
|
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
|
||||||
switch (note.meta.type) {
|
switch (note.meta.type) {
|
@@ -1,4 +1,4 @@
|
|||||||
export * from './actions/download.js'
|
export * from '@cryptgeon/shared'
|
||||||
export * from './actions/upload.js'
|
export * from './download.js'
|
||||||
export * from './shared/adapters.js'
|
export * from './upload.js'
|
||||||
export * from './shared/api.js'
|
export * from './utils.js'
|
||||||
|
@@ -21,7 +21,7 @@ export function parseURL(value: string, _: URL): URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseNumber(value: string, _: number): number {
|
export function parseNumber(value: string, _: number): number {
|
||||||
const n = Number.parseInt(value, 10)
|
const n = parseInt(value, 10)
|
||||||
if (Number.isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
|
if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
|
||||||
return n
|
return n
|
||||||
}
|
}
|
@@ -18,7 +18,6 @@ export function getStdin(timeout: number = 10): Promise<string> {
|
|||||||
resolve('')
|
resolve('')
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
process.stdin.on('error', reject)
|
|
||||||
process.stdin.on('data', dataHandler)
|
process.stdin.on('data', dataHandler)
|
||||||
process.stdin.on('end', endHandler)
|
process.stdin.on('end', endHandler)
|
||||||
})
|
})
|
@@ -1,10 +1,9 @@
|
|||||||
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, 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'
|
||||||
import { Adapters } from '../shared/adapters.js'
|
|
||||||
import { API, FileDTO, Note, NoteMeta } from '../shared/api.js'
|
|
||||||
|
|
||||||
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
|
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
|
||||||
|
|
||||||
@@ -39,8 +38,8 @@ 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 API.create(note)
|
const result = await create(note)
|
||||||
let url = `${API.getOptions().server}/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
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
|
import { status } from '@cryptgeon/shared'
|
||||||
import { exit as exitNode } from 'node:process'
|
import { exit as exitNode } from 'node:process'
|
||||||
import { API } from '../shared/api.js'
|
|
||||||
|
|
||||||
export function exit(message: string) {
|
export function exit(message: string) {
|
||||||
console.error(message)
|
console.error(message)
|
||||||
@@ -11,7 +11,7 @@ export async function checkConstrains(constrains: { views?: number; minutes?: nu
|
|||||||
if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
|
if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
|
||||||
if (!views && !minutes) constrains.views = 1
|
if (!views && !minutes) constrains.views = 1
|
||||||
|
|
||||||
const response = await API.status()
|
const response = await status()
|
||||||
if (views && views > response.max_views)
|
if (views && views > response.max_views)
|
||||||
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
|
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
|
||||||
if (minutes && minutes > response.max_expiration)
|
if (minutes && minutes > response.max_expiration)
|
@@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"module": "es2022",
|
"module": "es2022",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "node",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/kit": "^2.5.2",
|
"@sveltejs/kit": "^2.5.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@zerodevx/svelte-toast": "^0.9.5",
|
"@zerodevx/svelte-toast": "^0.9.5",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
@@ -28,9 +29,11 @@
|
|||||||
"vite": "^5.1.7"
|
"vite": "^5.1.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cryptgeon": "workspace:*",
|
"@cryptgeon/shared": "workspace:*",
|
||||||
"@fontsource/fira-mono": "^5.0.8",
|
"@fontsource/fira-mono": "^5.0.8",
|
||||||
"occulto": "^2.0.6",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"occulto": "^2.0.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"qrious": "^4.0.2"
|
"qrious": "^4.0.2"
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { API, type Status } from 'cryptgeon/shared'
|
import { status as getStatus, type Status } from '@cryptgeon/shared'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
export const status = writable<null | Status>(null)
|
export const status = writable<null | Status>(null)
|
||||||
|
|
||||||
export async function init() {
|
export async function init() {
|
||||||
status.set(await API.status())
|
status.set(await getStatus())
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { toast } from '@zerodevx/svelte-toast'
|
import { toast, type SvelteToastOptions } from '@zerodevx/svelte-toast'
|
||||||
|
|
||||||
export enum NotifyType {
|
export enum NotifyType {
|
||||||
Success = 'success',
|
Success = 'success',
|
||||||
Error = 'error',
|
Error = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeMapping: Record<NotifyType, Record<string, string>> = {
|
const themeMapping: Record<NotifyType, SvelteToastOptions['theme']> = {
|
||||||
[NotifyType.Success]: {
|
[NotifyType.Success]: {
|
||||||
'--toastBackground': 'var(--ui-clr-primary)',
|
'--toastBackground': 'var(--ui-clr-primary)',
|
||||||
'--toastBarBackground': 'var(--ui-clr-primary-alt)',
|
'--toastBarBackground': 'var(--ui-clr-primary-alt)',
|
||||||
@@ -17,7 +17,7 @@ const themeMapping: Record<NotifyType, Record<string, string>> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function notifyFN(message: string, type: NotifyType = NotifyType.Success) {
|
function notifyFN(message: string, type: NotifyType = NotifyType.Success) {
|
||||||
const options = {
|
const options: SvelteToastOptions = {
|
||||||
duration: 5_000,
|
duration: 5_000,
|
||||||
theme: {
|
theme: {
|
||||||
...themeMapping[type],
|
...themeMapping[type],
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
import { status } from '$lib/stores/status'
|
import { status } from '$lib/stores/status'
|
||||||
import Switch from '$lib/ui/Switch.svelte'
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
import type { Note } from 'cryptgeon/shared'
|
import type { Note } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let note: Note
|
export let note: Note
|
||||||
export let timeExpiration = false
|
export let timeExpiration = false
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import MaxSize from '$lib/ui/MaxSize.svelte'
|
import MaxSize from '$lib/ui/MaxSize.svelte'
|
||||||
import type { FileDTO } from 'cryptgeon/shared'
|
import type { FileDTO } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let label: string = ''
|
export let label: string = ''
|
||||||
export let files: FileDTO[] = []
|
export let files: FileDTO[] = []
|
||||||
|
@@ -18,19 +18,19 @@
|
|||||||
export let icon: keyof typeof map
|
export let icon: keyof typeof map
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button type="button" on:click {...$$restProps}>
|
<div on:click {...$$restProps}>
|
||||||
{#if map[icon]}
|
{#if map[icon]}
|
||||||
<svelte:component this={map[icon]} />
|
<svelte:component this={map[icon]} />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
}
|
}
|
||||||
button > :global(svg) {
|
div > :global(svg) {
|
||||||
display: block;
|
display: block;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +1,16 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
|
export type DecryptedNote = Omit<NotePublic, 'contents'> & { contents: any }
|
||||||
|
|
||||||
function saveAs(file: File) {
|
|
||||||
const url = window.URL.createObjectURL(file)
|
|
||||||
const a = document.createElement('a')
|
|
||||||
a.style.display = 'none'
|
|
||||||
a.href = url
|
|
||||||
a.download = file.name
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
window.URL.revokeObjectURL(url)
|
|
||||||
a.remove()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import pkg from 'file-saver'
|
||||||
|
const { saveAs } = pkg
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import { copy } from '$lib/utils'
|
import { copy } from '$lib/utils'
|
||||||
import type { FileDTO, NotePublic } from 'cryptgeon/shared'
|
import type { FileDTO, NotePublic } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let note: DecryptedNote
|
export let note: DecryptedNote
|
||||||
|
|
||||||
|
@@ -39,23 +39,13 @@
|
|||||||
<input bind:value {...$$restProps} class:valid={valid === true} />
|
<input bind:value {...$$restProps} class:valid={valid === true} />
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
{#if isPassword}
|
{#if isPassword}
|
||||||
<Icon
|
<Icon class="icon" icon={hidden ? 'eye' : 'eye-off'} on:click={toggle} />
|
||||||
disabled={$$restProps.disabled}
|
|
||||||
class="icon"
|
|
||||||
icon={hidden ? 'eye' : 'eye-off'}
|
|
||||||
on:click={toggle}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if random}
|
{#if random}
|
||||||
<Icon disabled={$$restProps.disabled} class="icon" icon="dice" on:click={randomFN} />
|
<Icon class="icon" icon="dice" on:click={randomFN} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if copy}
|
{#if copy}
|
||||||
<Icon
|
<Icon class="icon" icon="copy" on:click={() => copyFN(value.toString())} />
|
||||||
disabled={$$restProps.disabled}
|
|
||||||
class="icon"
|
|
||||||
icon="copy"
|
|
||||||
on:click={() => copyFN(value.toString())}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
import { notify } from './toast'
|
import { notify } from './toast'
|
||||||
|
|
||||||
export function copy(value: string) {
|
export function copy(value: string) {
|
||||||
window.navigator.clipboard.writeText(value)
|
copyToClipboard(value)
|
||||||
const msg = get(t)('common.copied_to_clipboard')
|
const msg = get(t)('common.copied_to_clipboard')
|
||||||
notify.success(msg)
|
notify.success(msg)
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,8 @@
|
|||||||
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
|
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
|
||||||
import Switch from '$lib/ui/Switch.svelte'
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
import TextArea from '$lib/ui/TextArea.svelte'
|
import TextArea from '$lib/ui/TextArea.svelte'
|
||||||
import { Adapters, API, PayloadToLargeError, type FileDTO, type Note } from 'cryptgeon/shared'
|
import type { FileDTO, Note } from '@cryptgeon/shared'
|
||||||
|
import { Adapters, PayloadToLargeError, create } from '@cryptgeon/shared'
|
||||||
|
|
||||||
let note: Note = {
|
let note: Note = {
|
||||||
contents: '',
|
contents: '',
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
else data.views = parseInt(note.views as any)
|
else data.views = parseInt(note.views as any)
|
||||||
|
|
||||||
loading = $t('common.uploading')
|
loading = $t('common.uploading')
|
||||||
const response = await API.create(data)
|
const response = await create(data)
|
||||||
result = {
|
result = {
|
||||||
id: response.id,
|
id: response.id,
|
||||||
password: customPassword ? undefined : Hex.encode(key),
|
password: customPassword ? undefined : Hex.encode(key),
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
|
import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
|
||||||
import { status } from '$lib/stores/status'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
@@ -8,11 +7,6 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<a href="/">/home</a>
|
<a href="/">/home</a>
|
||||||
<a href="/about">/about</a>
|
<a href="/about">/about</a>
|
||||||
{#if $status?.imprint_url}
|
|
||||||
<a href={$status.imprint_url} target="_blank" rel="noopener noreferrer">/imprint</a>
|
|
||||||
{:else if $status?.imprint_html}
|
|
||||||
<a href="/imprint">/imprint</a>
|
|
||||||
{/if}
|
|
||||||
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener noreferrer">
|
||||||
code
|
code
|
||||||
</a>
|
</a>
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { status } from '$lib/stores/status'
|
|
||||||
|
|
||||||
status.subscribe((config) => {
|
|
||||||
if (config != null) {
|
|
||||||
if (config.imprint_url) {
|
|
||||||
window.location = config.imprint_url;
|
|
||||||
}
|
|
||||||
else if (config.imprint_html == "") {
|
|
||||||
goto("/about");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>Imprint</title>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section class="content">
|
|
||||||
{#if $status?.imprint_html}
|
|
||||||
{@html $status.imprint_html}
|
|
||||||
{/if}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
section {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -7,7 +7,7 @@
|
|||||||
import Loader from '$lib/ui/Loader.svelte'
|
import Loader from '$lib/ui/Loader.svelte'
|
||||||
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
|
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
|
||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
import { Adapters, API, type NoteMeta } from 'cryptgeon/shared'
|
import { Adapters, get, info, type NoteMeta } from '@cryptgeon/shared'
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
||||||
export let data: PageData
|
export let data: PageData
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
try {
|
try {
|
||||||
loading = $t('common.loading')
|
loading = $t('common.loading')
|
||||||
password = window.location.hash.slice(1)
|
password = window.location.hash.slice(1)
|
||||||
const note = await API.info(id)
|
const note = await info(id)
|
||||||
meta = note.meta
|
meta = note.meta
|
||||||
exists = true
|
exists = true
|
||||||
} catch {
|
} catch {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
// Load note
|
// Load note
|
||||||
error = null
|
error = null
|
||||||
loading = $t('common.downloading')
|
loading = $t('common.downloading')
|
||||||
const data = await API.get(id)
|
const data = await get(id)
|
||||||
loading = $t('common.decrypting')
|
loading = $t('common.decrypting')
|
||||||
const derived = meta?.derivation && (await AES.derive(password!, meta.derivation))
|
const derived = meta?.derivation && (await AES.derive(password!, meta.derivation))
|
||||||
const key = derived ? derived[0] : Hex.decode(password!)
|
const key = derived ? derived[0] : Hex.decode(password!)
|
||||||
|
@@ -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(3000)
|
server.listen(1234)
|
||||||
console.log('Proxy on http://localhost:3000')
|
console.log('Proxy on http://localhost:1234')
|
||||||
|
22
packages/shared/package.json
Normal file
22
packages/shared/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@cryptgeon/shared",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc -w",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"occulto": "^2.0.3"
|
||||||
|
}
|
||||||
|
}
|
@@ -39,15 +39,15 @@ export let client: ClientOptions = {
|
|||||||
server: '',
|
server: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOptions(options: Partial<ClientOptions>) {
|
export function setOptions(options: Partial<ClientOptions>) {
|
||||||
client = { ...client, ...options }
|
client = { ...client, ...options }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptions(): ClientOptions {
|
export function getOptions(): ClientOptions {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
async function call(options: CallOptions) {
|
export async function call(options: CallOptions) {
|
||||||
const url = client.server + '/api/' + options.url
|
const url = client.server + '/api/' + options.url
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: options.method,
|
method: options.method,
|
||||||
@@ -65,7 +65,7 @@ async function call(options: CallOptions) {
|
|||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create(note: Note) {
|
export async function create(note: Note) {
|
||||||
const { meta, ...rest } = note
|
const { meta, ...rest } = note
|
||||||
const body: NoteCreate = {
|
const body: NoteCreate = {
|
||||||
...rest,
|
...rest,
|
||||||
@@ -79,7 +79,7 @@ async function create(note: Note) {
|
|||||||
return data as { id: string }
|
return data as { id: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(id: string): Promise<NotePublic> {
|
export async function get(id: string): Promise<NotePublic> {
|
||||||
const data = await call({
|
const data = await call({
|
||||||
url: `notes/${id}`,
|
url: `notes/${id}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
@@ -93,7 +93,7 @@ async function get(id: string): Promise<NotePublic> {
|
|||||||
return note
|
return note
|
||||||
}
|
}
|
||||||
|
|
||||||
async function info(id: string): Promise<NoteInfo> {
|
export async function info(id: string): Promise<NoteInfo> {
|
||||||
const data = await call({
|
const data = await call({
|
||||||
url: `notes/${id}`,
|
url: `notes/${id}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@@ -112,29 +112,16 @@ export type Status = {
|
|||||||
max_views: number
|
max_views: number
|
||||||
max_expiration: number
|
max_expiration: number
|
||||||
allow_advanced: boolean
|
allow_advanced: boolean
|
||||||
allow_files: boolean
|
|
||||||
imprint_url: string
|
|
||||||
imprint_html: string
|
|
||||||
theme_image: string
|
theme_image: string
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function status() {
|
export async function status() {
|
||||||
const data = await call({
|
const data = await call({
|
||||||
url: 'status/',
|
url: 'status/',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
return data as Status
|
return data as Status
|
||||||
}
|
}
|
||||||
|
|
||||||
export const API = {
|
|
||||||
setOptions,
|
|
||||||
getOptions,
|
|
||||||
create,
|
|
||||||
get,
|
|
||||||
info,
|
|
||||||
status,
|
|
||||||
}
|
|
12
packages/shared/tsconfig.json
Normal file
12
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"incremental": true,
|
||||||
|
"composite": true,
|
||||||
|
"target": "es2022",
|
||||||
|
"module": "es2022",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
@@ -3,19 +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:3000',
|
baseURL: 'http://localhost:1234',
|
||||||
actionTimeout: 30_000,
|
actionTimeout: 10_000,
|
||||||
},
|
},
|
||||||
|
|
||||||
outputDir: './test-results',
|
outputDir: './test-results',
|
||||||
testDir: './test',
|
testDir: './test',
|
||||||
timeout: 30_000,
|
timeout: 10_000,
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
retries: 2,
|
|
||||||
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'pnpm run docker:up',
|
command: 'docker compose -f docker-compose.dev.yaml up',
|
||||||
port: 3000,
|
port: 1234,
|
||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -23,6 +22,10 @@ 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/] },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
103
pnpm-lock.yaml
generated
103
pnpm-lock.yaml
generated
@@ -26,20 +26,23 @@ importers:
|
|||||||
packages/cli:
|
packages/cli:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@commander-js/extra-typings':
|
'@commander-js/extra-typings':
|
||||||
specifier: ^12.1.0
|
specifier: ^12.0.1
|
||||||
version: 12.1.0(commander@12.1.0)
|
version: 12.0.1(commander@12.0.0)
|
||||||
|
'@cryptgeon/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../shared
|
||||||
'@types/inquirer':
|
'@types/inquirer':
|
||||||
specifier: ^9.0.7
|
specifier: ^9.0.7
|
||||||
version: 9.0.7
|
version: 9.0.7
|
||||||
'@types/mime':
|
'@types/mime':
|
||||||
specifier: ^4.0.0
|
specifier: ^3.0.4
|
||||||
version: 4.0.0
|
version: 3.0.4
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.11.24
|
specifier: ^20.11.24
|
||||||
version: 20.11.24
|
version: 20.11.24
|
||||||
commander:
|
commander:
|
||||||
specifier: ^12.1.0
|
specifier: ^12.0.0
|
||||||
version: 12.1.0
|
version: 12.0.0
|
||||||
inquirer:
|
inquirer:
|
||||||
specifier: ^9.2.15
|
specifier: ^9.2.15
|
||||||
version: 9.2.15
|
version: 9.2.15
|
||||||
@@ -47,8 +50,8 @@ importers:
|
|||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
occulto:
|
occulto:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.3
|
||||||
version: 2.0.6
|
version: 2.0.3
|
||||||
pretty-bytes:
|
pretty-bytes:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@@ -61,15 +64,21 @@ importers:
|
|||||||
|
|
||||||
packages/frontend:
|
packages/frontend:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@cryptgeon/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../shared
|
||||||
'@fontsource/fira-mono':
|
'@fontsource/fira-mono':
|
||||||
specifier: ^5.0.8
|
specifier: ^5.0.8
|
||||||
version: 5.0.8
|
version: 5.0.8
|
||||||
cryptgeon:
|
copy-to-clipboard:
|
||||||
specifier: workspace:*
|
specifier: ^3.3.3
|
||||||
version: link:../cli
|
version: 3.3.3
|
||||||
|
file-saver:
|
||||||
|
specifier: ^2.0.5
|
||||||
|
version: 2.0.5
|
||||||
occulto:
|
occulto:
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.3
|
||||||
version: 2.0.6
|
version: 2.0.3
|
||||||
pretty-bytes:
|
pretty-bytes:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@@ -89,6 +98,9 @@ importers:
|
|||||||
'@sveltejs/vite-plugin-svelte':
|
'@sveltejs/vite-plugin-svelte':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2(svelte@4.2.12)(vite@5.1.7(@types/node@22.5.0))
|
version: 3.0.2(svelte@4.2.12)(vite@5.1.7(@types/node@22.5.0))
|
||||||
|
'@types/file-saver':
|
||||||
|
specifier: ^2.0.7
|
||||||
|
version: 2.0.7
|
||||||
'@zerodevx/svelte-toast':
|
'@zerodevx/svelte-toast':
|
||||||
specifier: ^0.9.5
|
specifier: ^0.9.5
|
||||||
version: 0.9.5(svelte@4.2.12)
|
version: 0.9.5(svelte@4.2.12)
|
||||||
@@ -123,6 +135,16 @@ importers:
|
|||||||
specifier: ^1.18.1
|
specifier: ^1.18.1
|
||||||
version: 1.18.1
|
version: 1.18.1
|
||||||
|
|
||||||
|
packages/shared:
|
||||||
|
dependencies:
|
||||||
|
occulto:
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.3.3
|
||||||
|
version: 5.3.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
@@ -220,10 +242,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@commander-js/extra-typings@12.1.0':
|
'@commander-js/extra-typings@12.0.1':
|
||||||
resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==}
|
resolution: {integrity: sha512-OvkMobb1eMqOCuJdbuSin/KJkkZr7n24/UNV+Lcz/0Dhepf3r2p9PaGwpRpAWej7A+gQnny4h8mGhpFl4giKkg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
commander: ~12.1.0
|
commander: ~12.0.0
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.19.12':
|
'@esbuild/aix-ppc64@0.19.12':
|
||||||
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
||||||
@@ -756,12 +778,14 @@ packages:
|
|||||||
'@types/estree@1.0.5':
|
'@types/estree@1.0.5':
|
||||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||||
|
|
||||||
|
'@types/file-saver@2.0.7':
|
||||||
|
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
|
||||||
|
|
||||||
'@types/inquirer@9.0.7':
|
'@types/inquirer@9.0.7':
|
||||||
resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==}
|
resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==}
|
||||||
|
|
||||||
'@types/mime@4.0.0':
|
'@types/mime@3.0.4':
|
||||||
resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==}
|
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
|
||||||
deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed.
|
|
||||||
|
|
||||||
'@types/node@20.11.24':
|
'@types/node@20.11.24':
|
||||||
resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==}
|
resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==}
|
||||||
@@ -956,8 +980,8 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
commander@12.1.0:
|
commander@12.0.0:
|
||||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
commander@4.1.1:
|
commander@4.1.1:
|
||||||
@@ -978,6 +1002,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
copy-to-clipboard@3.3.3:
|
||||||
|
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
|
||||||
|
|
||||||
cross-spawn@6.0.5:
|
cross-spawn@6.0.5:
|
||||||
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
|
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
|
||||||
engines: {node: '>=4.8'}
|
engines: {node: '>=4.8'}
|
||||||
@@ -1126,6 +1153,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
|
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
file-saver@2.0.5:
|
||||||
|
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
|
||||||
|
|
||||||
fill-range@7.0.1:
|
fill-range@7.0.1:
|
||||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1588,9 +1618,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
|
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
occulto@2.0.6:
|
occulto@2.0.3:
|
||||||
resolution: {integrity: sha512-oaCwtnQjr+fTfFVfSPEDC5rh+L13OcEJ6uQzhmG8PlLxxYn2MxTDPmoCUVHKD7rUxwxCpH7/N4hbTu5U4mqZag==}
|
resolution: {integrity: sha512-rLXDNqsxM3Gusp4cn8QsveeKsmX36SbnIUcUc3mSeM88pGsNY5Tn6VnWItw/7zST01z9gtaZWQFk0F1L53jMCQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=16', npm: please-use-pnpm, pnpm: '>=8', yarn: please-use-pnpm}
|
||||||
|
|
||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
@@ -2049,6 +2079,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
|
toggle-selection@1.0.6:
|
||||||
|
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||||
|
|
||||||
totalist@3.0.1:
|
totalist@3.0.1:
|
||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2344,9 +2377,9 @@ snapshots:
|
|||||||
'@babel/helper-validator-identifier': 7.22.20
|
'@babel/helper-validator-identifier': 7.22.20
|
||||||
to-fast-properties: 2.0.0
|
to-fast-properties: 2.0.0
|
||||||
|
|
||||||
'@commander-js/extra-typings@12.1.0(commander@12.1.0)':
|
'@commander-js/extra-typings@12.0.1(commander@12.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
commander: 12.1.0
|
commander: 12.0.0
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.19.12':
|
'@esbuild/aix-ppc64@0.19.12':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -2700,14 +2733,14 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.5': {}
|
'@types/estree@1.0.5': {}
|
||||||
|
|
||||||
|
'@types/file-saver@2.0.7': {}
|
||||||
|
|
||||||
'@types/inquirer@9.0.7':
|
'@types/inquirer@9.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/through': 0.0.33
|
'@types/through': 0.0.33
|
||||||
rxjs: 7.8.1
|
rxjs: 7.8.1
|
||||||
|
|
||||||
'@types/mime@4.0.0':
|
'@types/mime@3.0.4': {}
|
||||||
dependencies:
|
|
||||||
mime: 4.0.1
|
|
||||||
|
|
||||||
'@types/node@20.11.24':
|
'@types/node@20.11.24':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2909,7 +2942,7 @@ snapshots:
|
|||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
commander@12.1.0: {}
|
commander@12.0.0: {}
|
||||||
|
|
||||||
commander@4.1.1: {}
|
commander@4.1.1: {}
|
||||||
|
|
||||||
@@ -2921,6 +2954,10 @@ snapshots:
|
|||||||
|
|
||||||
cookie@0.6.0: {}
|
cookie@0.6.0: {}
|
||||||
|
|
||||||
|
copy-to-clipboard@3.3.3:
|
||||||
|
dependencies:
|
||||||
|
toggle-selection: 1.0.6
|
||||||
|
|
||||||
cross-spawn@6.0.5:
|
cross-spawn@6.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
nice-try: 1.0.5
|
nice-try: 1.0.5
|
||||||
@@ -3153,6 +3190,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 1.0.5
|
escape-string-regexp: 1.0.5
|
||||||
|
|
||||||
|
file-saver@2.0.5: {}
|
||||||
|
|
||||||
fill-range@7.0.1:
|
fill-range@7.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
@@ -3584,7 +3623,7 @@ snapshots:
|
|||||||
has-symbols: 1.0.3
|
has-symbols: 1.0.3
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
|
|
||||||
occulto@2.0.6: {}
|
occulto@2.0.3: {}
|
||||||
|
|
||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4078,6 +4117,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number: 7.0.0
|
is-number: 7.0.0
|
||||||
|
|
||||||
|
toggle-selection@1.0.6: {}
|
||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
tr46@1.0.1:
|
tr46@1.0.1:
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { basename } from 'node:path'
|
import { basename } from 'node:path'
|
||||||
import { rm } from 'node:fs/promises'
|
import { Files, getFileChecksum, rm, tmpFile } from '../../files'
|
||||||
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 () => {
|
test('simple', async ({ page }) => {
|
||||||
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)
|
||||||
@@ -18,7 +17,7 @@ test.describe('file @cli', () => {
|
|||||||
test.expect(checksum).toBe(c)
|
test.expect(checksum).toBe(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('simple with password', async () => {
|
test('simple with password', async ({ page }) => {
|
||||||
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)
|
||||||
|
@@ -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 () => {
|
test('simple', async ({ page }) => {
|
||||||
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 () => {
|
test('simple with password', async ({ page }) => {
|
||||||
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)
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { rm } from 'node:fs/promises'
|
import { CLI, checkLinkForDownload, checkLinkForText, createNote, getLinkFromCLI } from '../../utils'
|
||||||
import { basename } from 'node:path'
|
import { Files, getFileChecksum, rm, tmpFile } from '../../files'
|
||||||
import { Files, getFileChecksum, tmpFile } from '../../files'
|
import { basename } from 'path'
|
||||||
import { CLI, checkLinkForDownload, createNoteSuccessfully, getLinkFromCLI } from '../../utils'
|
|
||||||
|
|
||||||
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 password = 'password'
|
const password = 'password'
|
||||||
@@ -31,7 +30,7 @@ test.describe('text @cross', () => {
|
|||||||
test('web to cli', async ({ page }) => {
|
test('web to cli', async ({ page }) => {
|
||||||
const files = [Files.Image]
|
const files = [Files.Image]
|
||||||
const checksum = await getFileChecksum(files[0])
|
const checksum = await getFileChecksum(files[0])
|
||||||
const link = await createNoteSuccessfully(page, { files })
|
const link = await createNote(page, { files })
|
||||||
|
|
||||||
const filename = basename(files[0])
|
const filename = basename(files[0])
|
||||||
await CLI('open', link, '--all')
|
await CLI('open', link, '--all')
|
||||||
@@ -43,7 +42,7 @@ test.describe('text @cross', () => {
|
|||||||
test('web to cli with password', async ({ page }) => {
|
test('web to cli with password', async ({ page }) => {
|
||||||
const files = [Files.Image]
|
const files = [Files.Image]
|
||||||
const checksum = await getFileChecksum(files[0])
|
const checksum = await getFileChecksum(files[0])
|
||||||
const link = await createNoteSuccessfully(page, { files, password })
|
const link = await createNote(page, { files, password })
|
||||||
|
|
||||||
const filename = basename(files[0])
|
const filename = basename(files[0])
|
||||||
await CLI('open', link, '--all', '--password', password)
|
await CLI('open', link, '--all', '--password', password)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { CLI, checkLinkForText, createNoteSuccessfully, getLinkFromCLI } from '../../utils'
|
import { CLI, checkLinkForText, createNote, getLinkFromCLI } from '../../utils'
|
||||||
|
|
||||||
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 password = 'password'
|
const password = 'password'
|
||||||
@@ -13,7 +13,7 @@ test.describe('text @cross', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('web to cli', async ({ page }) => {
|
test('web to cli', async ({ page }) => {
|
||||||
const link = await createNoteSuccessfully(page, { text })
|
const link = await createNote(page, { text })
|
||||||
const retrieved = await CLI('open', link)
|
const retrieved = await CLI('open', link)
|
||||||
test.expect(retrieved.stdout.trim()).toBe(text)
|
test.expect(retrieved.stdout.trim()).toBe(text)
|
||||||
})
|
})
|
||||||
@@ -25,7 +25,7 @@ test.describe('text @cross', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('web to cli with password', async ({ page }) => {
|
test('web to cli with password', async ({ page }) => {
|
||||||
const link = await createNoteSuccessfully(page, { text, password })
|
const link = await createNote(page, { text, password })
|
||||||
const retrieved = await CLI('open', link, '--password', password)
|
const retrieved = await CLI('open', link, '--password', password)
|
||||||
test.expect(retrieved.stdout.trim()).toBe(text)
|
test.expect(retrieved.stdout.trim()).toBe(text)
|
||||||
})
|
})
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'crypto'
|
||||||
import { cp, readFile } from 'node:fs/promises'
|
import { cp as cpFN, rm as rmFN } from 'fs'
|
||||||
|
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',
|
||||||
|
@@ -6,22 +6,17 @@ import { getFileChecksum } from './files'
|
|||||||
const exec = promisify(execFile)
|
const exec = promisify(execFile)
|
||||||
|
|
||||||
type CreatePage = {
|
type CreatePage = {
|
||||||
|
text?: string
|
||||||
|
files?: string[]
|
||||||
views?: number
|
views?: number
|
||||||
expiration?: number
|
expiration?: number
|
||||||
error?: string
|
error?: string
|
||||||
password?: string
|
password?: string
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
text: string
|
|
||||||
}
|
}
|
||||||
| {
|
export async function createNote(page: Page, options: CreatePage): Promise<string> {
|
||||||
files: string[]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
async function createNote(page: Page, options: CreatePage): Promise<void> {
|
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
|
|
||||||
if ('text' in options) {
|
if (options.text) {
|
||||||
await page.getByTestId('text-field').fill(options.text)
|
await page.getByTestId('text-field').fill(options.text)
|
||||||
} else if (options.files) {
|
} else if (options.files) {
|
||||||
await page.getByTestId('switch-file').click()
|
await page.getByTestId('switch-file').click()
|
||||||
@@ -36,8 +31,7 @@ async function createNote(page: Page, options: CreatePage): Promise<void> {
|
|||||||
if (options.views || options.expiration || options.password) await page.getByTestId('switch-advanced').click()
|
if (options.views || options.expiration || options.password) await page.getByTestId('switch-advanced').click()
|
||||||
if (options.views) {
|
if (options.views) {
|
||||||
await page.getByTestId('field-views').fill(options.views.toString())
|
await page.getByTestId('field-views').fill(options.views.toString())
|
||||||
}
|
} else if (options.expiration) {
|
||||||
if (options.expiration) {
|
|
||||||
await page.getByTestId('switch-advanced-toggle').click()
|
await page.getByTestId('switch-advanced-toggle').click()
|
||||||
await page.getByTestId('field-expiration').fill(options.expiration.toString())
|
await page.getByTestId('field-expiration').fill(options.expiration.toString())
|
||||||
}
|
}
|
||||||
@@ -47,18 +41,15 @@ async function createNote(page: Page, options: CreatePage): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await page.locator('button:has-text("create")').click()
|
await page.locator('button:has-text("create")').click()
|
||||||
|
|
||||||
|
if (options.error) {
|
||||||
|
await expect(page.locator('.error-text')).toContainText(options.error, { timeout: 60_000 })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNoteSuccessfully(page: Page, options: CreatePage): Promise<string> {
|
// Return share link
|
||||||
await createNote(page, options)
|
|
||||||
return await page.getByTestId('share-link').inputValue()
|
return await page.getByTestId('share-link').inputValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNoteError(page: Page, options: CreatePage, error: string): Promise<void> {
|
|
||||||
await createNote(page, options)
|
|
||||||
await expect(page.locator('._toastContainer')).toContainText(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckLinkBase = {
|
type CheckLinkBase = {
|
||||||
link: string
|
link: string
|
||||||
text: string
|
text: string
|
||||||
@@ -78,7 +69,7 @@ export async function checkLinkForDownload(page: Page, options: CheckLinkBase &
|
|||||||
const path = await download.path()
|
const path = await download.path()
|
||||||
if (!path) throw new Error('Download failed')
|
if (!path) throw new Error('Download failed')
|
||||||
const cs = await getFileChecksum(path)
|
const cs = await getFileChecksum(path)
|
||||||
expect(cs).toBe(options.checksum)
|
await expect(cs).toBe(options.checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkLinkForText(page: Page, options: CheckLinkBase) {
|
export async function checkLinkForText(page: Page, options: CheckLinkBase) {
|
||||||
@@ -87,7 +78,7 @@ export async function checkLinkForText(page: Page, options: CheckLinkBase) {
|
|||||||
if (options.password) await page.getByTestId('show-note-password').fill(options.password)
|
if (options.password) await page.getByTestId('show-note-password').fill(options.password)
|
||||||
await page.getByTestId('show-note-button').click()
|
await page.getByTestId('show-note-button').click()
|
||||||
const text = await page.getByTestId('result').locator('.note').innerText()
|
const text = await page.getByTestId('result').locator('.note').innerText()
|
||||||
expect(text).toContain(options.text)
|
await expect(text).toContain(options.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkLinkDoesNotExist(page: Page, link: string) {
|
export async function checkLinkDoesNotExist(page: Page, link: string) {
|
||||||
@@ -100,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:3000',
|
CRYPTGEON_SERVER: 'http://localhost:1234',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { Files, getFileChecksum } from '../../files'
|
import { Files, getFileChecksum } from '../../files'
|
||||||
import { checkLinkForDownload, createNoteSuccessfully } from '../../utils'
|
import { checkLinkForDownload, createNote } from '../../utils'
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test('multiple', async ({ page }) => {
|
test('multiple', async ({ page }) => {
|
||||||
const files = [Files.PDF, Files.Image]
|
const files = [Files.PDF, Files.Image]
|
||||||
const checksums = await Promise.all(files.map(getFileChecksum))
|
const checksums = await Promise.all(files.map(getFileChecksum))
|
||||||
const link = await createNoteSuccessfully(page, { files, views: 2 })
|
const link = await createNote(page, { files, views: 2 })
|
||||||
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum: checksums[1] })
|
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum: checksums[1] })
|
||||||
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum: checksums[0] })
|
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum: checksums[0] })
|
||||||
})
|
})
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { Files, getFileChecksum } from '../../files'
|
import { Files, getFileChecksum } from '../../files'
|
||||||
import { checkLinkDoesNotExist, checkLinkForDownload, checkLinkForText, createNoteSuccessfully } from '../../utils'
|
import { checkLinkDoesNotExist, checkLinkForDownload, checkLinkForText, createNote } from '../../utils'
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test('simple pdf', async ({ page }) => {
|
test('simple pdf', async ({ page }) => {
|
||||||
const files = [Files.PDF]
|
const files = [Files.PDF]
|
||||||
const link = await createNoteSuccessfully(page, { files })
|
const link = await createNote(page, { files })
|
||||||
await checkLinkForText(page, { link, text: 'AES.pdf' })
|
await checkLinkForText(page, { link, text: 'AES.pdf' })
|
||||||
await checkLinkDoesNotExist(page, link)
|
await checkLinkDoesNotExist(page, link)
|
||||||
})
|
})
|
||||||
@@ -13,21 +13,21 @@ test.describe('@web', () => {
|
|||||||
test('pdf content', async ({ page }) => {
|
test('pdf content', async ({ page }) => {
|
||||||
const files = [Files.PDF]
|
const files = [Files.PDF]
|
||||||
const checksum = await getFileChecksum(files[0])
|
const checksum = await getFileChecksum(files[0])
|
||||||
const link = await createNoteSuccessfully(page, { files })
|
const link = await createNote(page, { files })
|
||||||
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum })
|
await checkLinkForDownload(page, { link, text: 'AES.pdf', checksum })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('image content', async ({ page }) => {
|
test('image content', async ({ page }) => {
|
||||||
const files = [Files.Image]
|
const files = [Files.Image]
|
||||||
const checksum = await getFileChecksum(files[0])
|
const checksum = await getFileChecksum(files[0])
|
||||||
const link = await createNoteSuccessfully(page, { files })
|
const link = await createNote(page, { files })
|
||||||
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum })
|
await checkLinkForDownload(page, { link, text: 'image.jpg', checksum })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('simple pdf with password', async ({ page }) => {
|
test('simple pdf with password', async ({ page }) => {
|
||||||
const files = [Files.PDF]
|
const files = [Files.PDF]
|
||||||
const password = 'password'
|
const password = 'password'
|
||||||
const link = await createNoteSuccessfully(page, { files, password })
|
const link = await createNote(page, { files, password })
|
||||||
await checkLinkForText(page, { link, text: 'AES.pdf', password })
|
await checkLinkForText(page, { link, text: 'AES.pdf', password })
|
||||||
await checkLinkDoesNotExist(page, link)
|
await checkLinkDoesNotExist(page, link)
|
||||||
})
|
})
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
|
import { createNote } from '../../utils'
|
||||||
import { Files } from '../../files'
|
import { Files } from '../../files'
|
||||||
import { createNoteError } from '../../utils'
|
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test.skip('to big zip', async ({ page }) => {
|
test.skip('to big zip', async ({ page }) => {
|
||||||
const files = [Files.Zip]
|
const files = [Files.Zip]
|
||||||
await createNoteError(page, { files }, 'could not create note. note is too big')
|
const link = await createNote(page, { files, error: 'note is to big' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils'
|
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../../utils'
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test('1 minute', async ({ page, browserName }) => {
|
test('1 minute', async ({ page }) => {
|
||||||
test.skip(browserName === 'webkit')
|
|
||||||
const text = `Virtues value ascetic revaluation sea dead strong burying.`
|
const text = `Virtues value ascetic revaluation sea dead strong burying.`
|
||||||
const minutes = 1
|
const minutes = 1
|
||||||
const timeout = minutes * 60_000
|
const timeout = minutes * 60_000
|
||||||
test.setTimeout(timeout * 2)
|
test.setTimeout(timeout * 2)
|
||||||
const link = await createNoteSuccessfully(page, { text, expiration: minutes })
|
const link = await createNote(page, { text, expiration: minutes })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
await page.waitForTimeout(timeout)
|
await page.waitForTimeout(timeout)
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { checkLinkForText, createNoteSuccessfully } from '../../utils'
|
import { checkLinkForText, createNote } from '../../utils'
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test('simple', async ({ page }) => {
|
test('simple', async ({ page }) => {
|
||||||
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of deceive 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 deceive 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 link = await createNoteSuccessfully(page, { text })
|
const link = await createNote(page, { text })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('simple with password', async ({ page }) => {
|
test('simple with password', async ({ page }) => {
|
||||||
const text = 'Foo bar'
|
const text = 'Foo bar'
|
||||||
const password = '123'
|
const password = '123'
|
||||||
const shareLink = await createNoteSuccessfully(page, { text, password })
|
const shareLink = await createNote(page, { text, password })
|
||||||
await checkLinkForText(page, { link: shareLink, text, password })
|
await checkLinkForText(page, { link: shareLink, text, password })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test } from '@playwright/test'
|
||||||
import { checkLinkDoesNotExist, checkLinkForText, createNoteSuccessfully } from '../../utils'
|
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../../utils'
|
||||||
|
|
||||||
test.describe('@web', () => {
|
test.describe('@web', () => {
|
||||||
test('only shown once', async ({ page }) => {
|
test('only shown once', async ({ page }) => {
|
||||||
const text = `Christian victorious reason suicide dead. Right ultimate gains god hope truth burying selfish society joy. Ultimate.`
|
const text = `Christian victorious reason suicide dead. Right ultimate gains god hope truth burying selfish society joy. Ultimate.`
|
||||||
const link = await createNoteSuccessfully(page, { text })
|
const link = await createNote(page, { text })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
await checkLinkDoesNotExist(page, link)
|
await checkLinkDoesNotExist(page, link)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('view 3 times', async ({ page }) => {
|
test('view 3 times', async ({ page }) => {
|
||||||
const text = `Justice holiest overcome fearful strong ultimate holiest christianity.`
|
const text = `Justice holiest overcome fearful strong ultimate holiest christianity.`
|
||||||
const link = await createNoteSuccessfully(page, { text, views: 3 })
|
const link = await createNote(page, { text, views: 3 })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
await checkLinkForText(page, { link, text })
|
await checkLinkForText(page, { link, text })
|
||||||
|
Reference in New Issue
Block a user