diff --git a/.env.example b/.env.example
index 153521d..3fdffb1 100644
--- a/.env.example
+++ b/.env.example
@@ -20,10 +20,10 @@ DATABASE_MAX_CONNECTIONS=8
# Session lifetime in days
# Anonymous pad/workspace access tokens
-ANONYMOUS_ACCESS_TOKEN_TTL_DAYS=7
+ANONYMOUS_ACCESS_TOKEN_TTL_DAYS=3
# Logged-in user sessions
-USER_SESSION_TTL_DAYS=30
-
+USER_SESSION_TTL_DAYS=3
+UNCONFIRMED_ACCOUNT_TTL_DAYS=3
# Logging
# available: warn, debug, info
diff --git a/Cargo.lock b/Cargo.lock
index 1824907..44187e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2581,7 +2581,7 @@ dependencies = [
[[package]]
name = "rustpad"
-version = "0.0.42"
+version = "0.1.1"
dependencies = [
"argon2",
"aws-config",
diff --git a/Cargo.toml b/Cargo.toml
index 7be527f..ef20dd7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustpad"
-version = "0.0.42"
+version = "0.1.2"
edition = "2024"
rust-version = "1.94"
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
diff --git a/README.md b/README.md
index 5313158..4b92502 100644
--- a/README.md
+++ b/README.md
@@ -242,9 +242,24 @@ docker compose up -d
Test users:
-- `mateusz` / `test1234`
-- `anna` / `test1234`
+- `admin` / `test1234`
+- `user` / `test1234`
-phpLDAPadmin: `http://10.87.2.6:8088`
+phpLDAPadmin: `http://10.0.0.1:8088`
Administrator: `cn=admin,dc=example,dc=org` / `admin`
+
+
+## CLI and YAML configuration
+
+RustPad reads `.env` as before and can additionally load a YAML file. Environment variables have higher priority than YAML values.
+
+```bash
+rustpad --version
+rustpad --help
+rustpad --config /etc/rustpad/rustpad.yaml check-config
+rustpad --config /etc/rustpad/rustpad.yaml migrate
+rustpad --config /etc/rustpad/rustpad.yaml
+```
+
+`check-config` validates YAML syntax, supported keys, value types, required LDAP/S3/SMTP fields, database URL scheme, paths and dependent settings. It does not connect to the database or LDAP server. Example deployment files are in `systemd/`.
diff --git a/migrations/mysql/0014_account_profile.sql b/migrations/mysql/0014_account_profile.sql
new file mode 100644
index 0000000..99064ca
--- /dev/null
+++ b/migrations/mysql/0014_account_profile.sql
@@ -0,0 +1,11 @@
+CREATE TABLE account_action_tokens (
+ token VARCHAR(64) PRIMARY KEY,
+ user_id BIGINT NOT NULL,
+ action VARCHAR(32) NOT NULL,
+ payload TEXT,
+ expires_at VARCHAR(64) NOT NULL,
+ used_at VARCHAR(64),
+ created_at VARCHAR(64) NOT NULL,
+ CONSTRAINT fk_account_action_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
+CREATE INDEX idx_account_action_user ON account_action_tokens(user_id);
diff --git a/migrations/mysql/0015_directory_display_name.sql b/migrations/mysql/0015_directory_display_name.sql
new file mode 100644
index 0000000..3c0aaba
--- /dev/null
+++ b/migrations/mysql/0015_directory_display_name.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN directory_display_name VARCHAR(255);
diff --git a/migrations/mysql/0016_editor_colors.sql b/migrations/mysql/0016_editor_colors.sql
new file mode 100644
index 0000000..84ba26c
--- /dev/null
+++ b/migrations/mysql/0016_editor_colors.sql
@@ -0,0 +1,11 @@
+ALTER TABLE users ADD COLUMN editor_color VARCHAR(7) NULL;
+
+CREATE TABLE user_resource_colors (
+ user_id BIGINT NOT NULL,
+ resource_kind VARCHAR(32) NOT NULL,
+ resource_slug VARCHAR(255) NOT NULL,
+ color VARCHAR(7) NOT NULL,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (user_id, resource_kind, resource_slug),
+ CONSTRAINT fk_user_resource_colors_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
diff --git a/migrations/postgres/0014_account_profile.sql b/migrations/postgres/0014_account_profile.sql
new file mode 100644
index 0000000..f1fed9b
--- /dev/null
+++ b/migrations/postgres/0014_account_profile.sql
@@ -0,0 +1,10 @@
+CREATE TABLE account_action_tokens (
+ token TEXT PRIMARY KEY,
+ user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ action TEXT NOT NULL,
+ payload TEXT,
+ expires_at TEXT NOT NULL,
+ used_at TEXT,
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+CREATE INDEX idx_account_action_user ON account_action_tokens(user_id);
diff --git a/migrations/postgres/0015_directory_display_name.sql b/migrations/postgres/0015_directory_display_name.sql
new file mode 100644
index 0000000..b983e56
--- /dev/null
+++ b/migrations/postgres/0015_directory_display_name.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN directory_display_name TEXT;
diff --git a/migrations/postgres/0016_editor_colors.sql b/migrations/postgres/0016_editor_colors.sql
new file mode 100644
index 0000000..8cd35b2
--- /dev/null
+++ b/migrations/postgres/0016_editor_colors.sql
@@ -0,0 +1,10 @@
+ALTER TABLE users ADD COLUMN editor_color TEXT;
+
+CREATE TABLE user_resource_colors (
+ user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ resource_kind TEXT NOT NULL,
+ resource_slug TEXT NOT NULL,
+ color TEXT NOT NULL,
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (user_id, resource_kind, resource_slug)
+);
diff --git a/migrations/sqlite/0014_account_profile.sql b/migrations/sqlite/0014_account_profile.sql
new file mode 100644
index 0000000..3e3e629
--- /dev/null
+++ b/migrations/sqlite/0014_account_profile.sql
@@ -0,0 +1,10 @@
+CREATE TABLE account_action_tokens (
+ token TEXT PRIMARY KEY,
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ action TEXT NOT NULL,
+ payload TEXT,
+ expires_at TEXT NOT NULL,
+ used_at TEXT,
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+CREATE INDEX idx_account_action_user ON account_action_tokens(user_id);
diff --git a/migrations/sqlite/0015_directory_display_name.sql b/migrations/sqlite/0015_directory_display_name.sql
new file mode 100644
index 0000000..b983e56
--- /dev/null
+++ b/migrations/sqlite/0015_directory_display_name.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN directory_display_name TEXT;
diff --git a/migrations/sqlite/0016_editor_colors.sql b/migrations/sqlite/0016_editor_colors.sql
new file mode 100644
index 0000000..5e8f7f9
--- /dev/null
+++ b/migrations/sqlite/0016_editor_colors.sql
@@ -0,0 +1,11 @@
+ALTER TABLE users ADD COLUMN editor_color TEXT;
+
+CREATE TABLE user_resource_colors (
+ user_id INTEGER NOT NULL,
+ resource_kind TEXT NOT NULL,
+ resource_slug TEXT NOT NULL,
+ color TEXT NOT NULL,
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (user_id, resource_kind, resource_slug),
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
diff --git a/src/api.rs b/src/api.rs
index ff5b8c3..d413222 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -130,6 +130,13 @@ pub struct NoteInfo {
created_at: String,
updated_at: String,
can_delete_files: bool,
+ global_color: Option,
+ note_color: Option,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct EditorColorRequest {
+ color: Option,
}
pub async fn create_workspace(
@@ -234,8 +241,17 @@ pub async fn create_note(
.await?;
let level = if db::verify_workspace_password(&workspace, payload.password.as_deref())
|| (workspace.is_private == 0 && workspace.password_hash.is_none())
- { AccessLevel::Write } else {
- combined_token_access_level(&state, "workspace", &workspace_slug, payload.access_token.as_deref(), bearer_token(&headers)).await?
+ {
+ AccessLevel::Write
+ } else {
+ combined_token_access_level(
+ &state,
+ "workspace",
+ &workspace_slug,
+ payload.access_token.as_deref(),
+ bearer_token(&headers),
+ )
+ .await?
};
require_write(level)?;
let title = validate_name(&payload.name, "Note name")?;
@@ -276,6 +292,90 @@ pub async fn create_note(
))
}
+fn clean_editor_color(value: Option<&str>) -> Result
+
+
+
+