Compare commits

...
Sign in to create a new pull request.

9 commits

Author SHA1 Message Date
6a4fbf5afc Update readme 2024-12-05 18:30:28 -03:00
1885d43fa6 Added more endpoints and modals
- Added endpoints to create folders, upload files and unlink files and
  folders;
- Added modals to show loading indicators, confirmation and warnings.
2024-12-05 18:25:20 -03:00
5072a69333 Improving error handling 2024-12-03 13:24:57 -03:00
bf41d27353 Building file tree, added modal
- Building file tree (ongoing)
- Added modal to handle error messages and similar interactions.
2024-12-03 01:23:29 -03:00
463e18015f API
API with first two endpoint to fetch information from Tahoe to
SvelteKit.
2024-12-02 01:48:51 -03:00
22e8576cab Replaced TOTP with cap key
- Replaced TOTP with an implementation of a cap key to better align with
  Tahoe's way of handling user / data.
2024-12-01 00:23:21 -03:00
70a92cb0e6 QR code generator, update readme
Added a QR code code generator that might be used for auth.

Updated README with better instructions for setting up Tahoe grid.
2024-11-29 17:22:29 -03:00
ec1ed9e487 One time password
TOTP implementation (incomplete. Token is generated, but not verified.)
2024-11-24 01:23:54 -03:00
c8a02fb468 Navigation bar and empty login page
- Added nav bar;
- Added an empty login page
- Fixed .gitignore, which had been overwritten on previous commit.
2024-11-19 01:46:59 -03:00
28 changed files with 893 additions and 1351 deletions

9
.gitignore vendored
View file

@ -1,9 +1,15 @@
node_modules
gm0
introducer
storage0
storage1
webapp
# Output
.output
.vercel
/.svelte-kit
.svelte-kit
packages/.svelte-kit
/build
# OS
@ -11,6 +17,7 @@ node_modules
Thumbs.db
# Env
.venv
.env
.env.*
!.env.example

View file

@ -18,6 +18,66 @@ The project intends to be a demonstration of how to use [Tahoe-lafs]() "provide
git clone https://github.com/blaisep/private_facts.git && cd private_facts
```
Install dependencies.
```sh
uv venv
source .venv/bin/activate
uv pip install -r pyproject.toml
```
Setup a grid:
```sh
grid-manager --config ./gm0 create
```
Initiate Tahoe-LAFS servers (one introducer, two storage servers and a client server, each in a separate terminal window):
Introducer server:
```sh
.venv/bin/tahoe create-introducer --listen=tcp --port=6001 --location=tcp:localhost:6001 ./introducer
.venv/bin/tahoe -d introducer run
```
Two storage servers:
```sh
.venv/bin/tahoe create-node --introducer $(cat introducer/private/introducer.furl) --nickname storage0 --webport 6101 --location tcp:localhost:6102 --port 6102 ./storage0
.venv/bin/tahoe create-node --introducer $(cat introducer/private/introducer.furl) --nickname storage1 --webport 6201 --location tcp:localhost:6202 --port 6202 ./storage1
.venv/bin/tahoe -d storage0 run
.venv/bin/tahoe -d storage1 run
```
Add storage servers to grid and create certificates:
```sh
grid-manager --config ./gm0 add storage0 $(cat storage0/node.pubkey)
grid-manager --config ./gm0 add storage1 $(cat storage1/node.pubkey)
grid-manager --config ./gm0 sign storage0 > ./storage0/gridmanager.cert 30
grid-manager --config ./gm0 sign storage1 > ./storage1/gridmanager.cert 30
```
Edit storage servers to make them announce their certificates to the grid. Edit the `tahoe.cfg` file in `storage0` and `storage1`:
```sh
[storage]
grid_management = true
[grid_manager_certificates]
default = gridmanager.cert
```
Re-start storage servers.
Client:
```sh
.venv/bin/tahoe create-client --introducer $(cat introducer/private/introducer.furl) --nickname webapp --webport 6301 --shares-total=3 --shares-needed=2 --shares-happy=3 ./webapp
.venv/bin/tahoe -d webapp run
```
### Install from (Docker) Image
TBA
@ -60,39 +120,6 @@ The project issue tracker is getting migrated. For now, feel free to open an iss
Pull requests are welcome. For major changes, please open an issue first
to discuss what you would like to change.
### Set up the developer environment
Initiate Tahoe-LAFS servers (one introducer, two storage servers and a client server):
Introducer server:
```sh
.venv/bin/tahoe create-introducer --listen=tcp --port=5555 --location=tcp:localhost:5555 ./introducer
.venv/bin/tahoe -d introducer run &>/dev/null &
```
Two storage servers:
```sh
.venv/bin/tahoe create-node --introducer $(cat introducer/private/introducer.furl) --nickname storage0 --webport 6001 --location tcp:localhost:6003 --port 6003 ./storage0
.venv/bin/tahoe create-node --introducer $(cat introducer/private/introducer.furl) --nickname storage1 --webport 6101 --location tcp:localhost:6103 --port 6103 ./storage1
.venv/bin/tahoe -d storage0 run &>/dev/null &
.venv/bin/tahoe -d storage1 run &>/dev/null &
```
Client:
```sh
.venv/bin/tahoe create-client --introducer $(cat introducer/private/introducer.furl) --nickname webapp --webport 6401 --shares-total=3 --shares-needed=2 --shares-happy=3 ./webapp
.venv/bin/tahoe -d webapp run &>/dev/null &
```
The commands should return four PIDs. Note them down to kill them later, when finished, with:
```sh
kill -9 <PID>
```
### Getting ready for your first pull request
Please make sure to update tests as appropriate.

1
packages/.env.example Normal file
View file

@ -0,0 +1 @@
TAHOE_API=http://x.x.x.x:xxxx

View file

@ -1,263 +0,0 @@
// this file is generated — do not edit it
/// <reference types="@sveltejs/kit" />
/**
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* _Unlike_ [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
*
* ```ts
* import { API_KEY } from '$env/static/private';
* ```
*
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
*
* ```
* MY_FEATURE_FLAG=""
* ```
*
* You can override `.env` values from the command line like so:
*
* ```bash
* MY_FEATURE_FLAG="enabled" npm run dev
* ```
*/
declare module '$env/static/private' {
export const NVM_INC: string;
export const COREPACK_ROOT: string;
export const npm_package_devDependencies_prettier: string;
export const TERM_PROGRAM: string;
export const npm_package_devDependencies_eslint_plugin_svelte: string;
export const NODE: string;
export const NVM_CD_FLAGS: string;
export const npm_package_devDependencies_prettier_plugin_svelte: string;
export const INIT_CWD: string;
export const SHELL: string;
export const TERM: string;
export const npm_package_devDependencies_vite: string;
export const TMPDIR: string;
export const HOMEBREW_REPOSITORY: string;
export const RIPGREP_CONFIG_PATH: string;
export const npm_package_scripts_lint: string;
export const TERM_PROGRAM_VERSION: string;
export const npm_package_scripts_dev: string;
export const TERM_SESSION_ID: string;
export const npm_package_devDependencies__sveltejs_kit: string;
export const npm_config_registry: string;
export const ZSH: string;
export const npm_package_devDependencies_globals: string;
export const USER: string;
export const NVM_DIR: string;
export const LS_COLORS: string;
export const COMMAND_MODE: string;
export const npm_package_devDependencies_mdsvex: string;
export const PNPM_SCRIPT_SRC_DIR: string;
export const SSH_AUTH_SOCK: string;
export const __CF_USER_TEXT_ENCODING: string;
export const npm_package_devDependencies_eslint: string;
export const TERM_FEATURES: string;
export const npm_execpath: string;
export const PAGER: string;
export const FZF_DEFAULT_OPTS: string;
export const npm_package_devDependencies_svelte: string;
export const LSCOLORS: string;
export const npm_config_frozen_lockfile: string;
export const PATH: string;
export const TERMINFO_DIRS: string;
export const FZF_COMPLETION_TRIGGER: string;
export const npm_config_engine_strict: string;
export const __CFBundleIdentifier: string;
export const PWD: string;
export const npm_command: string;
export const JAVA_HOME: string;
export const npm_package_scripts_preview: string;
export const EDITOR: string;
export const npm_lifecycle_event: string;
export const LANG: string;
export const npm_package_name: string;
export const npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
export const ITERM_PROFILE: string;
export const NODE_PATH: string;
export const npm_package_scripts_build: string;
export const XPC_FLAGS: string;
export const npm_package_devDependencies_vitest: string;
export const FZF_COMPLETION_OPTS: string;
export const NVM_LAZY_LOAD: string;
export const npm_package_devDependencies_eslint_config_prettier: string;
export const npm_config_node_gyp: string;
export const XPC_SERVICE_NAME: string;
export const npm_package_version: string;
export const npm_package_devDependencies__sveltejs_adapter_auto: string;
export const COLORFGBG: string;
export const HOME: string;
export const SHLVL: string;
export const PYENV_SHELL: string;
export const npm_package_type: string;
export const npm_package_scripts_test: string;
export const LC_TERMINAL_VERSION: string;
export const HOMEBREW_PREFIX: string;
export const ITERM_SESSION_ID: string;
export const LOGNAME: string;
export const LESS: string;
export const npm_package_scripts_format: string;
export const VISUAL: string;
export const npm_lifecycle_script: string;
export const FZF_DEFAULT_COMMAND: string;
export const NVM_BIN: string;
export const npm_config_user_agent: string;
export const HOMEBREW_CELLAR: string;
export const INFOPATH: string;
export const DISPLAY: string;
export const LC_TERMINAL: string;
export const npm_package_devDependencies__types_eslint: string;
export const COLORTERM: string;
export const npm_package_scripts_test_unit: string;
export const npm_node_execpath: string;
export const NODE_ENV: string;
}
/**
* Similar to [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Values are replaced statically at build time.
*
* ```ts
* import { PUBLIC_BASE_URL } from '$env/static/public';
* ```
*/
declare module '$env/static/public' {
}
/**
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* This module cannot be imported into client-side code.
*
* Dynamic environment variables cannot be used during prerendering.
*
* ```ts
* import { env } from '$env/dynamic/private';
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*
* > In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
*/
declare module '$env/dynamic/private' {
export const env: {
NVM_INC: string;
COREPACK_ROOT: string;
npm_package_devDependencies_prettier: string;
TERM_PROGRAM: string;
npm_package_devDependencies_eslint_plugin_svelte: string;
NODE: string;
NVM_CD_FLAGS: string;
npm_package_devDependencies_prettier_plugin_svelte: string;
INIT_CWD: string;
SHELL: string;
TERM: string;
npm_package_devDependencies_vite: string;
TMPDIR: string;
HOMEBREW_REPOSITORY: string;
RIPGREP_CONFIG_PATH: string;
npm_package_scripts_lint: string;
TERM_PROGRAM_VERSION: string;
npm_package_scripts_dev: string;
TERM_SESSION_ID: string;
npm_package_devDependencies__sveltejs_kit: string;
npm_config_registry: string;
ZSH: string;
npm_package_devDependencies_globals: string;
USER: string;
NVM_DIR: string;
LS_COLORS: string;
COMMAND_MODE: string;
npm_package_devDependencies_mdsvex: string;
PNPM_SCRIPT_SRC_DIR: string;
SSH_AUTH_SOCK: string;
__CF_USER_TEXT_ENCODING: string;
npm_package_devDependencies_eslint: string;
TERM_FEATURES: string;
npm_execpath: string;
PAGER: string;
FZF_DEFAULT_OPTS: string;
npm_package_devDependencies_svelte: string;
LSCOLORS: string;
npm_config_frozen_lockfile: string;
PATH: string;
TERMINFO_DIRS: string;
FZF_COMPLETION_TRIGGER: string;
npm_config_engine_strict: string;
__CFBundleIdentifier: string;
PWD: string;
npm_command: string;
JAVA_HOME: string;
npm_package_scripts_preview: string;
EDITOR: string;
npm_lifecycle_event: string;
LANG: string;
npm_package_name: string;
npm_package_devDependencies__sveltejs_vite_plugin_svelte: string;
ITERM_PROFILE: string;
NODE_PATH: string;
npm_package_scripts_build: string;
XPC_FLAGS: string;
npm_package_devDependencies_vitest: string;
FZF_COMPLETION_OPTS: string;
NVM_LAZY_LOAD: string;
npm_package_devDependencies_eslint_config_prettier: string;
npm_config_node_gyp: string;
XPC_SERVICE_NAME: string;
npm_package_version: string;
npm_package_devDependencies__sveltejs_adapter_auto: string;
COLORFGBG: string;
HOME: string;
SHLVL: string;
PYENV_SHELL: string;
npm_package_type: string;
npm_package_scripts_test: string;
LC_TERMINAL_VERSION: string;
HOMEBREW_PREFIX: string;
ITERM_SESSION_ID: string;
LOGNAME: string;
LESS: string;
npm_package_scripts_format: string;
VISUAL: string;
npm_lifecycle_script: string;
FZF_DEFAULT_COMMAND: string;
NVM_BIN: string;
npm_config_user_agent: string;
HOMEBREW_CELLAR: string;
INFOPATH: string;
DISPLAY: string;
LC_TERMINAL: string;
npm_package_devDependencies__types_eslint: string;
COLORTERM: string;
npm_package_scripts_test_unit: string;
npm_node_execpath: string;
NODE_ENV: string;
[key: `PUBLIC_${string}`]: undefined;
[key: `${string}`]: string | undefined;
}
}
/**
* Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests when possible, use `$env/static/public` instead.
*
* Dynamic environment variables cannot be used during prerendering.
*
* ```ts
* import { env } from '$env/dynamic/public';
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*/
declare module '$env/dynamic/public' {
export const env: {
[key: `PUBLIC_${string}`]: string | undefined;
}
}

View file

@ -1,21 +0,0 @@
export { matchers } from './matchers.js';
export const nodes = [
() => import('./nodes/0'),
() => import('./nodes/1'),
() => import('./nodes/2')
];
export const server_loads = [];
export const dictionary = {
"/": [2]
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
reroute: (() => {})
};
export { default as root } from '../root.js';

View file

@ -1 +0,0 @@
export const matchers = {};

View file

@ -1 +0,0 @@
export { default as component } from "../../../../node_modules/.pnpm/@sveltejs+kit@2.8.0_@sveltejs+vite-plugin-svelte@4.0.0_svelte@5.1.15_vite@5.4.11__svelte@5.1.15_vite@5.4.11/node_modules/@sveltejs/kit/src/runtime/components/svelte-5/layout.svelte";

View file

@ -1 +0,0 @@
export { default as component } from "../../../../node_modules/.pnpm/@sveltejs+kit@2.8.0_@sveltejs+vite-plugin-svelte@4.0.0_svelte@5.1.15_vite@5.4.11__svelte@5.1.15_vite@5.4.11/node_modules/@sveltejs/kit/src/runtime/components/svelte-5/error.svelte";

View file

@ -1 +0,0 @@
export { default as component } from "../../../../src/routes/+page.svelte";

View file

@ -1,3 +0,0 @@
import { asClassComponent } from 'svelte/legacy';
import Root from './root.svelte';
export default asClassComponent(Root);

View file

@ -1,66 +0,0 @@
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
<svelte:options runes={true} />
<script>
import { setContext, onMount, tick } from 'svelte';
import { browser } from '$app/environment';
// stores
let { stores, page, constructors, components = [], form, data_0 = null, data_1 = null } = $props();
if (!browser) {
setContext('__svelte__', stores);
}
if (browser) {
$effect.pre(() => stores.page.set(page));
} else {
stores.page.set(page);
}
$effect(() => {
stores;page;constructors;components;form;data_0;data_1;
stores.page.notify();
});
let mounted = $state(false);
let navigated = $state(false);
let title = $state(null);
onMount(() => {
const unsubscribe = stores.page.subscribe(() => {
if (mounted) {
navigated = true;
tick().then(() => {
title = document.title || 'untitled page';
});
}
});
mounted = true;
return unsubscribe;
});
const Pyramid_1=$derived(constructors[1])
</script>
{#if constructors[1]}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form}>
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_1 bind:this={components[1]} data={data_1} {form} />
</Pyramid_0>
{:else}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form} />
{/if}
{#if mounted}
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
{#if navigated}
{title}
{/if}
</div>
{/if}

View file

@ -1,34 +0,0 @@
import root from '../root.js';
import { set_building, set_prerendering } from '__sveltekit/environment';
import { set_assets } from '__sveltekit/paths';
import { set_manifest, set_read_implementation } from '__sveltekit/server';
import { set_private_env, set_public_env, set_safe_public_env } from '../../../node_modules/.pnpm/@sveltejs+kit@2.8.0_@sveltejs+vite-plugin-svelte@4.0.0_svelte@5.1.15_vite@5.4.11__svelte@5.1.15_vite@5.4.11/node_modules/@sveltejs/kit/src/runtime/shared-server.js';
export const options = {
app_dir: "_app",
app_template_contains_nonce: false,
csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}},
csrf_check_origin: true,
embedded: false,
env_public_prefix: 'PUBLIC_',
env_private_prefix: '',
hooks: null, // added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
templates: {
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "1vvcu4z"
};
export async function get_hooks() {
return {
};
}
export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation, set_safe_public_env };

View file

@ -1,25 +0,0 @@
// this file is generated — do not edit it
declare module "svelte/elements" {
export interface HTMLAttributes<T> {
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
}
}
export {};

View file

@ -1,49 +0,0 @@
{
"compilerOptions": {
"paths": {
"$lib": [
"../src/lib"
],
"$lib/*": [
"../src/lib/*"
]
},
"rootDirs": [
"..",
"./types"
],
"verbatimModuleSyntax": true,
"isolatedModules": true,
"lib": [
"esnext",
"DOM",
"DOM.Iterable"
],
"moduleResolution": "bundler",
"module": "esnext",
"noEmit": true,
"target": "esnext"
},
"include": [
"ambient.d.ts",
"non-ambient.d.ts",
"./types/**/$types.d.ts",
"../vite.config.js",
"../vite.config.ts",
"../src/**/*.js",
"../src/**/*.ts",
"../src/**/*.svelte",
"../tests/**/*.js",
"../tests/**/*.ts",
"../tests/**/*.svelte"
],
"exclude": [
"../node_modules/**",
"../src/service-worker.js",
"../src/service-worker/**/*.js",
"../src/service-worker.ts",
"../src/service-worker/**/*.ts",
"../src/service-worker.d.ts",
"../src/service-worker/**/*.d.ts"
]
}

View file

@ -1,23 +0,0 @@
import prettier from 'eslint-config-prettier';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
/** @type {import('eslint').Linter.Config[]} */
export default [
js.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
{
ignores: ['build/', '.svelte-kit/', 'dist/']
}
];

View file

@ -7,7 +7,7 @@
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint": "prettier --check .",
"test:unit": "vitest",
"test": "npm run test:unit -- --run"
},
@ -15,10 +15,6 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/eslint": "^9.6.0",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.0.0",
"mdsvex": "^0.11.2",
"prettier": "^3.3.2",
@ -26,5 +22,8 @@
"svelte": "^5.0.0",
"vite": "^5.0.3",
"vitest": "^2.0.4"
},
"dependencies": {
"dotenv": "^16.4.5"
}
}

827
packages/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
<div class='overlay'></div>
<div class="wrap">
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
<rect width="10" height="10">
<animate
attributeName="rx"
values="0;5;0"
dur="1s"
repeatCount="indefinite" />
</rect>
</svg>
</div>
<style>
.wrap {
position: absolute;
z-index: 51;
opacity: 0.6;
width: 3em;
left: calc(50% - 3em);
top: calc(50% - 2em);
}
.overlay {
position: fixed;
z-index: 50;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: var(--shade-loading);
}
</style>

View file

@ -0,0 +1,111 @@
<script>
import { onMount } from 'svelte'
let {
showModal = false, wide = true, hideSidebar = false,
enter, escape, children
} = $props()
const handleKeydown = (e) => {
if (showModal) {
if (e.keyCode === 27) {
escape()
} else if (e.keyCode === 13) {
enter()
}
}
}
onMount(() => {
if (showModal) {
window.addEventListener("keydown", handleKeydown)
} else {
window.removeEventListener("keydown", handleKeydown)
}
})
</script>
{#if showModal}
<div class='modal'>
<div class="overlay" role='button' tabindex='0' onclick={() => escape()} onkeydown={() => escape()}>
<div
class="dark"
role='button'
tabindex='0'
class:narrow={!wide}
class:hidden={hideSidebar}
onclick={() => escape()}
onkeydown={() => escape()}
></div>
</div>
<div class="wrap">
{@render children?.()}
</div>
</div>
{/if}
<style>
.modal {
display: flex;
position: fixed;
height: 100%;
width: 100%;
left: 0;
top: 0;
}
.wrap {
z-index: 100;
margin: auto 1em;
background-color: var(--theme-background);
border: solid thin var(--theme-color);
border-radius: 10px;
padding: 1em;
text-align: center;
width: 100%;
}
.overlay {
position: fixed;
z-index: 50;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.overlay .dark {
margin-top: 0;
width: 100%;
height: 100%;
background-color: var(--shade-background);
}
.overlay .dark::before {
content: "";
width: 40px;
height: 20px;
position: absolute;
top: -20px;
left: 0;
background-color: var(--shade-background);
border-radius: 40px 40px 0 0;
}
.overlay .dark.narrow::before {
left: 0;
}
.overlay .dark.hidden::before {
display: none;
}
@media (min-width: 45em) {
.wrap {
margin: auto;
padding: 1em 3em;
width: unset;
}
}
</style>

View file

@ -0,0 +1,65 @@
<script>
import { goto } from '$app/navigation'
</script>
<nav class='nav-bar'>
<div class='cluster nav-left'>
<ul>
<li><button onclick={() => goto('/')}>Home</button></li>
</ul>
</div>
<div class='cluster nav-right'>
<ul>
<li><button onclick={() => goto('/dashboard')}>Dashboard</button></li>
</ul>
</div>
</nav>
<style>
.nav-bar {
height: var(--navbar-height);
display: flex;
background: var(--theme-background);
border-bottom: solid thin var(--border-color);
position: sticky;
top: 0;
}
ul {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
.cluster ul {
display: flex;
height: 100%;
}
.cluster li {
display: flex;
}
.nav-left {
margin: 0 auto 0 0;
}
.nav-right {
margin: 0 0 0 auto;
}
button {
margin: 0;
background: transparent;
color: var(--theme-color);
border: none;
width: 100%;
height: 100%;
padding: 0 1em;
text-align: left;
}
</style>

View file

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,79 @@
import { env } from '$env/dynamic/private'
export const createAlias = () => {
return new Promise(async (resolve, reject) => {
try {
const url = `${env.TAHOE_API}/uri?t=mkdir`
const response = await fetch(url, { method: 'POST' })
const capKey = await response.json()
return resolve({ capKey })
} catch (error) {
return reject(error)
}
})
}
export const listDirectories = (capKey) => {
return new Promise(async (resolve, reject) => {
try {
const url = `${env.TAHOE_API}/uri/${capKey}?t=json`
const response = await fetch(url, { method: 'GET' })
const list = await response.json()
return resolve({ list })
} catch (error) {
return reject(error)
}
})
}
export const createDirectory = (path, dirName) => {
return new Promise(async (resolve, reject) => {
try {
const url = `${env.TAHOE_API}/uri/${path}?t=mkdir&name=${dirName}`
const response = await fetch(url, { method: 'POST' })
if (response.status !== 200) {
return json({ success: false, status: response.status, message: response.statusText })
}
const cap = await response.json()
return resolve({ cap })
} catch (error) {
return reject(error)
}
})
}
export const uploadFile = (path, file) => {
return new Promise(async (resolve, reject) => {
const url = `${env.TAHOE_API}/uri/${path}`
try {
const response = await fetch(url, {
method: 'PUT',
body: file
})
return resolve()
} catch (error) {
console.log({ error })
return reject(error)
}
})
}
export const unlink = (path) => {
return new Promise(async (resolve, reject) => {
try {
const url = `${env.TAHOE_API}/uri/${path}`
const response = await fetch(url, { method: 'DELETE' })
return resolve()
} catch (error) {
return reject(error)
}
})
}

View file

@ -0,0 +1,25 @@
<script>
import { page } from '$app/stores'
</script>
<div class='error-page'>
<h1>Private facts</h1>
<h2>Oops... Something went wrong!</h2>
{#if $page.status === 401}
<h3>Error {$page.status}: {$page.error?.message}</h3>
<p>
Go to <a href="/auth/login">log in</a> page.
</p>
{:else}
<h3>{$page.status}: {$page.error?.message}</h3>
<p>We are looking into it.</p>
{/if}
</div>
<style>
.error-page {
text-align: center;
}
</style>

View file

@ -0,0 +1,73 @@
<script>
import NavBar from '$lib/components/nav-bar.svelte'
</script>
<div class='content'>
<NavBar />
<slot></slot>
</div>
<style>
:root {
--border-color: #aaa;
--button-color: #f6f6f6;
--font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
--navbar-height: 3em;
--shade-background: rgba(0, 0, 0, 0.2);
--shade-color: #eee;
--shade-loading: rgba(0, 0, 0, 0.3);
--theme-background: white;
--theme-color: #444;
}
.content {
text-align: center;
margin: auto;
height: calc(100vh - 6em);
max-width: 60em;
overflow: clip;
overflow-y: auto;
}
:global(body) {
margin: 0;
font-family: var(--font-family);
font-weight: 400;
background: var(--theme-background);
color: var(--theme-color);
overflow: clip;
}
:global(button) {
margin: 1em;
width: 10em;
height: 2.5em;
font-family: var(--font-family-text);
font-weight: 200;
font-size: 16px;
background: var(--button-color);
border: solid thin;
border-radius: 5px;
}
:global(button:hover) {
cursor: pointer;
}
:global(button:disabled) {
background-color: light-dark(rgba(239, 239, 239, 0.3), rgba(19, 1, 1, 0.3));
color: light-dark(rgba(16, 16, 16, 0.3), rgba(255, 255, 255, 0.3));
border-color: light-dark(rgba(118, 118, 118, 0.3), rgba(195, 195, 195, 0.3));
}
:global(input) {
font-size: 0.9em;
font-weight: 400;
padding: 0 1em;
height: 3em;
border: none;
border-radius: 2px;
outline: solid thin var(--border-color);
}
</style>

View file

@ -1,2 +1,14 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<script>
import { goto } from '$app/navigation'
</script>
<svelte:head>
<title>Home page</title>
<meta name='description' content='Home page'/>
</svelte:head>
<div class='landing-page'>
<h1>Private facts</h1>
<button onclick={() => goto('/dashboard')}>Dashboard</button>
</div>

View file

@ -0,0 +1,102 @@
import { fail } from '@sveltejs/kit'
import { createAlias, createDirectory, listDirectories, unlink, uploadFile } from '$lib/utils/tahoe'
export const actions = {
createCapKey: async ({ request, fetch }) => {
try {
const { capKey } = await createAlias()
return { endpoint: 'createCapKey', capKey }
} catch (error) {
console.log({ error })
return fail(500, { endpoint: 'createCapKey', error })
}
},
listDirectories: async ({ request, fetch }) => {
const formData = await request.formData()
const capKey = formData.get('capKeyInput')
const encoded = encodeURIComponent(capKey)
try {
const { list } = await listDirectories(capKey)
return { endpoint: 'listDirectories', list, capKey }
} catch (error) {
if (error.cause.message.includes('ECONNREFUSED')) {
return { error: 'Tahoe server may be offline.' }
}
console.log({ error })
return fail(500, { endpoint: 'listDirectories', error })
}
},
createDirectory: async ({ request, fetch }) => {
const formData = await request.formData()
const capKey = formData.get('capKey')
const encoded = encodeURIComponent(capKey)
const dirName= formData.get('dirName')
let path = formData.get('path')
if (path.length === 0) path = '/'
if (path.slice(0, 1) !== '/') path = '/' + path
if (path.slice(-1) !== '/') path = path + '/'
path = encoded + encodeURI(path)
try {
const { cap } = await createDirectory(path, dirName)
return { success: true, endpoint: 'uploadFile' }
} catch (error) {
console.log({ error })
return fail(500, { endpoint: 'uploadFile', error})
}
},
uploadFile: async ({ request, fetch }) => {
const formData = await request.formData()
const capKey = formData.get('capKey')
const encoded = encodeURIComponent(capKey)
const file = formData.get('file')
let path = formData.get('path')
if (path.length === 0) path = '/'
if (path.slice(0, 1) !== '/') path = '/' + path
if (path.slice(-1) !== '/') path = path + '/'
path = encoded + encodeURI(path + file.name)
try {
await uploadFile(path, file.name, file)
return { success: true, endpoint: 'uploadFile' }
} catch (error) {
console.log({ error })
return fail(500, { endpoint: 'uploadFile', error})
}
},
deletePath: async ({ request, fetch }) => {
const formData = await request.formData()
const capKey = formData.get('capKey')
const encoded = encodeURIComponent(capKey)
const partialPath = formData.get('path')
const slash = partialPath.slice(0, 1) === '/' ? '' : '/'
const path = encoded + slash + partialPath
if (partialPath.length === 0) {
return { success: false, endpoint: 'deletePath', error: 'Path cannot be empty.'}
}
try {
await unlink(path)
return { success: true, endpoint: 'deletePath' }
} catch (error) {
console.log({ error })
return json({ success: false, code: 500, error })
}
}
}

View file

@ -0,0 +1,293 @@
<script>
import { enhance } from '$app/forms'
import { beforeNavigate } from '$app/navigation'
import Loading from '$lib/components/loading.svelte'
import Modal from '$lib/components/modal.svelte'
import { fade } from 'svelte/transition'
let { form } = $props()
let capKey = $state()
let capKeyInput = $state()
let list = $state()
let listForm = $state()
let modalTitle = $state()
let modalText = $state()
let newCapKey = $state()
let checked = $state(false)
let loading = $state(false)
let showModal = $state(false)
let showDelModal = $state(false)
let showUploadModal = $state(false)
let showAddFolderModal = $state(false)
const submitKey = () => {
capKey = capKeyInput
}
const persistCapKey = () => {
capKey = newCapKey
newCapKey = undefined
}
const enhanceForm = () => {
showDelModal = false
showUploadModal = false
showAddFolderModal = false
loading = true
return async ({ result, update }) => {
await update()
loading = false
if (result.status === 200) {
switch (result.data.endpoint) {
case 'createCapKey': {
newCapKey = result.data.capKey
break
}
case 'listDirectories': {
capKey = result.data.capKey
list = result.data.list
break
}
case 'uploadFile': {
listForm.requestSubmit()
break
}
case 'deletePath': {
listForm.requestsubmit()
break
}
}
}
}
}
beforeNavigate(({ cancel }) => {
if (newCapKey && !capKey) {
cancel()
modalTitle = 'Warning'
modalText = 'Please confirm you copied and saved the cap key.'
showModal = true
}
})
// Store cap key in a variable for use while current page
// is on screen
$effect(() => {
if (form?.capKey) capKey = form.capKey
})
// Show errors
$effect(() => {
if (form?.error) {
modalTitle = 'Error'
modalText = form.error
showModal = true
}
})
</script>
<svelte:head>
<title>Private Facts dashboard</title>
<meta name='description' content='Private Facts dashboard.'>
</svelte:head>
{#if loading}
<div class='loader' transition:fade|local>
<Loading />
</div>
{/if}
<h1>Dashboard</h1>
{#if !capKey && !newCapKey}
<div class='cap-key-div add'>
<form action='?/listDirectories' method='post' enctype='form-data' use:enhance={enhanceForm} bind:this={listForm}>
<label for='capKeyInput'>Cap key:</label>
<input type='text' name='capKeyInput' value={capKey} />
<button type='submit'>Submit</button>
</form>
<form action='?/createCapKey' method='post' enctype='form-data' use:enhance={enhanceForm}>
<button type='submit'>New cap key</button>
</form>
</div>
{:else if !capKey && newCapKey}
<div class='cap-key-div get'>
<label for='newCapKey'><h2>New cap key</h2></label>
<input type='text' id='newCapKey' name='newCapKey' value={newCapKey} />
<div class='warning'>
<h3>Attention!</h3>
<p>This is your new cap key. You need it to access your folders and files. It will not be saved nor ever shown again.</p>
<p><strong>Copy it now and save it somewhere safe.</strong></p>
<p>If you lose your cap key you will not be able to access your files anymore. You will have to create a new cap key and upload new files and folders.</p>
<div class='confirm-copy'>
<input type='checkbox' id='checkbox' name='checkbox' bind:checked />
<label for='copyCheckbox'>Copied and saved.</label>
</div>
<button disabled={!checked} onclick={persistCapKey}>Ok</button>
</div>
</div>
{:else}
<h2>File tree</h2>
{#if list}
<ul>
{#each Object.keys(list[1].children) as item}
<li>{item}</li>
{/each}
</ul>
{/if}
<button onclick={() => showAddFolderModal = true}>Add folder</button>
<button onclick={() => showUploadModal = true}>Upload file</button>
<button onclick={() => showDelModal = true}>Unlink file / folder</button>
<button onclick={() => capKey = undefined}>Change cap key</button>
{/if}
<!-- Modals -->
<Modal {showModal} escape={() => showModal = false}>
<h2>{modalTitle}</h2>
<p>{modalText}</p>
<button onclick={() => showModal = false}>Ok</button>
</Modal>
<Modal showModal={showDelModal} escape={() => showDelModal = false}>
<h2>Unlink</h2>
<p>Specify path of file or folder to unlink.</p>
<p>Ex.: /Documents/File-1.txt</p>
<form action='?/deletePath' method='post' enctype='form-data' use:enhance={enhanceForm}>
<input type='hidden' name='capKey' value={capKey} />
<label for='path'>Path:</label>
<input type='text' name='path' class='path-input' />
<div class='form-actions'>
<button onclick={() => showDelModal = false}>Cancel</button>
<button type='submit'>Confirm</button>
</div>
</form>
</Modal>
<Modal showModal={showUploadModal} escape={() => showUploadModal = false}>
<h2>Upload file</h2>
<form action='?/uploadFile' method='post' enctype='multipart/form-data' use:enhance={enhanceForm}>
<input type='hidden' name='capKey' value={capKey} />
<div class='file-input'>
<label for='file'>File:</label>
<input type='file' name='file' placeholder='file' required />
</div>
<label for='path'>Path:</label>
<input type='text' name='path' class='path-input' />
<div class='form-actions'>
<button onclick={() => showUploadModal = false}>Cancel</button>
<button type='submit'>Upload</button>
</div>
</form>
</Modal>
<Modal showModal={showAddFolderModal} escape={() => showAddFolderModal = false}>
<h2>Add folder</h2>
<form action='?/createDirectory' method='post' enctype='form-data' use:enhance={enhanceForm}>
<input type='hidden' name='capKey' value={capKey} />
<div class='new-folder-input'>
<label for='dirName'>New folder name:</label>
<input type='text' name='dirName' />
<label for='path'>Path:</label>
<input type='text' name='path' />
</div>
<div class='form-actions'>
<button onclick={() => showAddFolderModal = false}>Cancel</button>
<button type='submit'>Confirm</button>
</div>
</form>
</Modal>
<style>
.cap-key-div form {
display: flex;
flex-direction: column;
text-align: left;
}
.cap-key-div form button {
margin: 0 0 1em auto;
}
form input {
margin: 1em 0;
}
.cap-key-div {
max-width: 45em;
margin: 0 auto;
}
.cap-key-div.get {
display: flex;
flex-direction: column;
}
.confirm-copy {
display: flex;
justify-content: center;
align-items: center;
}
.confirm-copy input {
height: unset;
margin-right: 1em;
border: solid thin var(--border-color);
outline: none;
}
.warning {
border: solid thin var(--border-color);
border-radius: 10px;
background-color: var(--shade-color);
padding: 0.5em;
margin: 2em auto;
}
ul {
list-style: none;
text-align: left;
background: var(--shade-color);
padding: 1em;
border: solid thin var(--border-color);
}
.form-actions {
display: flex;
justify-content: center;
}
.path-input {
width: 30em;
}
.new-folder-input {
text-align: left;
display: flex;
flex-direction: column;
}
</style>

View file

@ -2,9 +2,19 @@ import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';
export default defineConfig({
plugins: [sveltekit()],
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
server:{
port:5555,
strictPort:false,
},
preview:{
port:5554,
strictPort:false,
},
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});