This commit is contained in:
Mateusz Gruszczyński committed 2026-09-25 15:07:20 +02:00
1 parent 6329ac33d1
commit 90e3c399ea
22 files changed
+729 -46

No files matched your search

+6 -1
View File
@@ -405,6 +405,11 @@ pub async fn delete_note(
"This note is protected. Only its owner can delete it.",
));
}
if workspace.archive_deleted_notes != 0 {
db::archive_note(&state.db, note.id).await?;
return Ok(Json(serde_json::json!({"ok": true, "archived": true})));
}
for file in db::list_note_files(&state.db, note.id).await? {
crate::storage::delete_url_file(&state.storage, "notes", note.id, &file.url)
.await
@@ -417,7 +422,7 @@ pub async fn delete_note(
)
.await?;
db::delete_note(&state.db, note.id).await?;
Ok(Json(serde_json::json!({"ok": true})))
Ok(Json(serde_json::json!({"ok": true, "archived": false})))
}
pub async fn note_files(
+80 -9
View File
@@ -177,6 +177,22 @@ async fn can_set_workspace_password(
)
}
async fn can_manage_workspace_settings(
state: &SharedState,
headers: &HeaderMap,
workspace: &db::Workspace,
) -> bool {
let account_owner = crate::auth::is_resource_owner(
state,
"workspace",
&workspace.slug,
user_session_token(headers),
)
.await
.unwrap_or(false);
account_owner || workspace_creator_is_requester(headers, workspace)
}
async fn has_write_permission(
state: &SharedState,
headers: &HeaderMap,
@@ -354,6 +370,11 @@ pub struct SetWorkspacePasswordRequest {
client_id: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct SetWorkspaceSettingsRequest {
archive_deleted_notes: bool,
}
#[derive(Debug, Deserialize)]
pub struct RestoreRequest {
#[serde(default)]
@@ -370,6 +391,8 @@ pub struct WorkspaceInfo {
protected: bool,
access_level: String,
can_set_password: bool,
can_manage_settings: bool,
archive_deleted_notes: bool,
created_at: String,
updated_at: String,
}
@@ -442,6 +465,7 @@ pub struct NoteInfo {
public_page_unprotected: bool,
public_page_enabled: bool,
private: bool,
archive_deleted_notes: bool,
created_at: String,
updated_at: String,
can_delete: bool,
@@ -714,10 +738,53 @@ pub async fn workspace_info(
)
.await?;
let can_set_password = can_set_workspace_password(&state, &headers, &workspace).await;
let can_manage_settings = can_manage_workspace_settings(&state, &headers, &workspace).await;
Ok(Json(workspace_info_from(
&workspace,
access_level,
can_set_password,
can_manage_settings,
)))
}
pub async fn set_workspace_settings(
State(state): State<SharedState>,
headers: HeaderMap,
Path(workspace_slug): Path<String>,
Json(payload): Json<SetWorkspaceSettingsRequest>,
) -> Result<Json<WorkspaceInfo>, ApiError> {
let workspace = db::find_workspace(&state.db, &workspace_slug)
.await?
.ok_or_else(ApiError::not_found_workspace)?;
if !can_manage_workspace_settings(&state, &headers, &workspace).await {
return Err(ApiError::forbidden(
"Only the workspace owner can change workspace settings.",
));
}
db::set_workspace_archive_deleted_notes(
&state.db,
workspace.id,
payload.archive_deleted_notes,
)
.await?;
let workspace = db::find_workspace(&state.db, &workspace_slug)
.await?
.ok_or_else(ApiError::not_found_workspace)?;
let access_level = effective_header_access_level(
&state,
&headers,
"workspace",
&workspace.slug,
workspace.is_private,
workspace.password_hash.is_some(),
)
.await?;
let can_set_password = can_set_workspace_password(&state, &headers, &workspace).await;
Ok(Json(workspace_info_from(
&workspace,
access_level,
can_set_password,
true,
)))
}
@@ -875,8 +942,14 @@ pub async fn open_workspace(
let notes = notes.into_iter().skip(start).take(per_page).collect();
let can_set_password = can_set_workspace_password(&state, &headers, &workspace).await;
let can_manage_settings = can_manage_workspace_settings(&state, &headers, &workspace).await;
Ok(Json(WorkspaceOpenResponse {
workspace: workspace_info_from(&workspace, access_level, can_set_password),
workspace: workspace_info_from(
&workspace,
access_level,
can_set_password,
can_manage_settings,
),
notes,
pagination: ListPaginationMeta {
page,
@@ -1159,6 +1232,7 @@ pub async fn note_info(
public_page_unprotected: db::note_public_page_unprotected(&state.db, note.id).await?,
public_page_enabled: db::note_public_page_enabled(&state.db, note.id).await?,
private: workspace.is_private != 0,
archive_deleted_notes: workspace.archive_deleted_notes != 0,
created_at: db::normalize_timestamp(&note.created_at),
updated_at: db::normalize_timestamp(&note.updated_at),
can_delete: can_delete_files,
@@ -1628,6 +1702,7 @@ fn workspace_info_from(
workspace: &db::Workspace,
access_level: AccessLevel,
can_set_password: bool,
can_manage_settings: bool,
) -> WorkspaceInfo {
WorkspaceInfo {
slug: workspace.slug.clone(),
@@ -1635,6 +1710,8 @@ fn workspace_info_from(
protected: workspace.password_hash.is_some(),
access_level: access_level_name(access_level).into(),
can_set_password,
can_manage_settings,
archive_deleted_notes: workspace.archive_deleted_notes != 0,
created_at: db::normalize_timestamp(&workspace.created_at),
updated_at: db::normalize_timestamp(&workspace.updated_at),
}
@@ -1701,18 +1778,12 @@ async fn unique_note_slug(
workspace_id: i64,
base: &str,
) -> Result<String, ApiError> {
if db::find_note(&state.db, workspace_id, base)
.await?
.is_none()
{
if !db::note_slug_exists_any(&state.db, workspace_id, base).await? {
return Ok(base.to_owned());
}
for _ in 0..8 {
let candidate = format!("{base}-{}", db::random_suffix(6));
if db::find_note(&state.db, workspace_id, &candidate)
.await?
.is_none()
{
if !db::note_slug_exists_any(&state.db, workspace_id, &candidate).await? {
return Ok(candidate);
}
}
+4
View File
@@ -183,6 +183,10 @@ pub fn router(
"/api/workspaces/{workspace_slug}/password",
post(api::set_workspace_password),
)
.route(
"/api/workspaces/{workspace_slug}/settings",
post(api::set_workspace_settings),
)
.route(
"/api/workspaces/{workspace_slug}/open",
post(api::open_workspace),
+43 -1
View File
@@ -52,6 +52,7 @@ pub struct Workspace {
pub created_at: String,
pub updated_at: String,
pub is_private: i64,
pub archive_deleted_notes: i64,
pub created_by_guest_id: Option<String>,
}
@@ -179,6 +180,22 @@ pub async fn set_workspace_password(
Ok(())
}
pub async fn set_workspace_archive_deleted_notes(
pool: &Database,
workspace_id: i64,
enabled: bool,
) -> Result<(), sqlx::Error> {
sqlx::query(queries::get(
pool.kind(),
queries::WORKSPACE_SET_ARCHIVE_DELETED_NOTES,
))
.bind(enabled)
.bind(workspace_id)
.execute(pool.pool())
.await?;
Ok(())
}
pub fn verify_workspace_password(workspace: &Workspace, password: Option<&str>) -> bool {
match (
&workspace.password_hash,
@@ -241,7 +258,7 @@ pub async fn list_note_stats(
(SELECT COUNT(*) FROM note_files f WHERE f.note_id = n.id) AS file_count, \
(SELECT {file_size_sum} FROM note_files f WHERE f.note_id = n.id) AS file_size_bytes, \
(SELECT COUNT(*) FROM note_revisions r WHERE r.note_id = n.id) AS revision_count \
FROM notes n WHERE n.workspace_id = {placeholder}"
FROM notes n WHERE n.workspace_id = {placeholder} AND n.deleted_at IS NULL"
);
let rows = sqlx::query(&query)
@@ -284,6 +301,30 @@ pub async fn find_note(
.await
}
pub async fn note_slug_exists_any(
pool: &Database,
workspace_id: i64,
slug: &str,
) -> Result<bool, sqlx::Error> {
let count: i64 = sqlx::query_scalar(queries::get(
pool.kind(),
queries::NOTE_SLUG_EXISTS_ANY,
))
.bind(workspace_id)
.bind(slug)
.fetch_one(pool.pool())
.await?;
Ok(count > 0)
}
pub async fn archive_note(pool: &Database, note_id: i64) -> Result<(), sqlx::Error> {
sqlx::query(queries::get(pool.kind(), queries::NOTE_ARCHIVE))
.bind(note_id)
.execute(pool.pool())
.await?;
Ok(())
}
pub async fn create_note(
pool: &Database,
workspace_id: i64,
@@ -665,6 +706,7 @@ impl<'r> sqlx::FromRow<'r, AnyRow> for Workspace {
created_at: crate::row_decode::text(row, "created_at")?,
updated_at: crate::row_decode::text(row, "updated_at")?,
is_private: row.try_get("is_private")?,
archive_deleted_notes: row.try_get("archive_deleted_notes")?,
created_by_guest_id: crate::row_decode::optional_text(row, "created_by_guest_id")?,
})
}
+6
View File
@@ -99,6 +99,9 @@ pub enum Query {
USER_SET_PAD_PASSWORD,
USER_SET_WORKSPACE_PRIVACY,
USER_SET_PAD_PRIVACY,
WORKSPACE_SET_ARCHIVE_DELETED_NOTES,
NOTE_ARCHIVE,
NOTE_SLUG_EXISTS_ANY,
RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE,
RESOURCE_ACCESS_TOKENS_DELETE_BY_TOKEN_HASH,
RESOURCE_ACCESS_TOKENS_INSERT,
@@ -276,6 +279,9 @@ pub const USER_SET_WORKSPACE_PASSWORD: Query = Query::USER_SET_WORKSPACE_PASSWOR
pub const USER_SET_PAD_PASSWORD: Query = Query::USER_SET_PAD_PASSWORD;
pub const USER_SET_WORKSPACE_PRIVACY: Query = Query::USER_SET_WORKSPACE_PRIVACY;
pub const USER_SET_PAD_PRIVACY: Query = Query::USER_SET_PAD_PRIVACY;
pub const WORKSPACE_SET_ARCHIVE_DELETED_NOTES: Query = Query::WORKSPACE_SET_ARCHIVE_DELETED_NOTES;
pub const NOTE_ARCHIVE: Query = Query::NOTE_ARCHIVE;
pub const NOTE_SLUG_EXISTS_ANY: Query = Query::NOTE_SLUG_EXISTS_ANY;
pub const RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE: Query =
Query::RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE;
pub const RESOURCE_ACCESS_TOKENS_DELETE_BY_TOKEN_HASH: Query =
+16 -7
View File
@@ -200,10 +200,10 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT COUNT(*) FROM user_note_favorites WHERE user_id = ? AND note_id = ?"#
}
Query::USER_FAVORITE_NOTE_LIST => {
r#"SELECT n.id, n.slug, CAST(n.title AS CHAR CHARACTER SET utf8mb4) AS title, n.updated_at, w.slug AS workspace_slug, CAST(w.title AS CHAR CHARACTER SET utf8mb4) AS workspace_title, CAST(CASE WHEN w.is_private THEN 1 ELSE 0 END AS SIGNED) AS private, CAST(CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS SIGNED) AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = ? ORDER BY f.created_at DESC, n.id DESC"#
r#"SELECT n.id, n.slug, CAST(n.title AS CHAR CHARACTER SET utf8mb4) AS title, n.updated_at, w.slug AS workspace_slug, CAST(w.title AS CHAR CHARACTER SET utf8mb4) AS workspace_title, CAST(CASE WHEN w.is_private THEN 1 ELSE 0 END AS SIGNED) AS private, CAST(CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS SIGNED) AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = ? AND n.deleted_at IS NULL ORDER BY f.created_at DESC, n.id DESC"#
}
Query::USER_FAVORITE_NOTE_IDS_BY_WORKSPACE => {
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = ? AND n.workspace_id = ?"#
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = ? AND n.workspace_id = ? AND n.deleted_at IS NULL"#
}
Query::USER_FAVORITE_PADS_DELETE_BY_USER => {
r#"DELETE FROM user_pad_favorites WHERE user_id = ?"#
@@ -231,6 +231,15 @@ pub fn get(query: Query) -> &'static str {
Query::USER_SET_PAD_PRIVACY => {
r#"UPDATE pads SET is_private = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?"#
}
Query::WORKSPACE_SET_ARCHIVE_DELETED_NOTES => {
r#"UPDATE workspaces SET archive_deleted_notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"#
}
Query::NOTE_ARCHIVE => {
r#"UPDATE notes SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL"#
}
Query::NOTE_SLUG_EXISTS_ANY => {
r#"SELECT COUNT(*) FROM notes WHERE workspace_id = ? AND slug = ?"#
}
Query::RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE => {
r#"DELETE FROM resource_access_tokens WHERE resource_kind = ? AND resource_slug = ?"#
}
@@ -311,14 +320,14 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT CAST(CASE WHEN public_page_disabled THEN 1 ELSE 0 END AS SIGNED) FROM notes WHERE id = ?"#
}
Query::Q001 => {
r#"SELECT id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(password_hash AS CHAR CHARACTER SET utf8mb4) AS password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS SIGNED) AS is_private, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM workspaces WHERE slug = ?"#
r#"SELECT id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(password_hash AS CHAR CHARACTER SET utf8mb4) AS password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS SIGNED) AS is_private, CAST(CASE WHEN archive_deleted_notes THEN 1 ELSE 0 END AS SIGNED) AS archive_deleted_notes, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM workspaces WHERE slug = ?"#
}
Query::Q002 => r#"INSERT INTO workspaces (slug, title, password_hash, created_by_guest_id) VALUES (?, ?, ?, ?)"#,
Query::Q003 => {
r#"SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC"#
r#"SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM notes WHERE workspace_id = ? AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
}
Query::Q004 => {
r#"SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM notes WHERE workspace_id = ? AND slug = ?"#
r#"SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by, CAST(created_by_guest_id AS CHAR CHARACTER SET utf8mb4) AS created_by_guest_id FROM notes WHERE workspace_id = ? AND slug = ? AND deleted_at IS NULL"#
}
Query::Q005 => {
r#"INSERT INTO notes (workspace_id, slug, title, protected, created_by, created_by_guest_id) VALUES (?, ?, ?, ?, ?, ?)"#
@@ -355,14 +364,14 @@ pub fn get(query: Query) -> &'static str {
Query::Q019 => r#"SELECT token FROM published_pages WHERE note_id = ?"#,
Query::Q020 => r#"INSERT INTO published_pages (token, note_id) VALUES (?, ?)"#,
Query::Q021 => {
r#"SELECT pp.token, pp.pad_id, pp.note_id, CAST(CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS SIGNED) AS allow_task_updates, CAST(COALESCE(p.slug, n.slug) AS CHAR CHARACTER SET utf8mb4) AS resource_slug, n.workspace_id AS workspace_id, CAST(w.slug AS CHAR CHARACTER SET utf8mb4) AS workspace_slug, CAST(COALESCE(p.owner_map, n.owner_map, '[]') AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(COALESCE(p.title, n.title) AS CHAR CHARACTER SET utf8mb4) AS title, CAST(COALESCE(p.content, n.content) AS CHAR CHARACTER SET utf8mb4) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = ?"#
r#"SELECT pp.token, pp.pad_id, pp.note_id, CAST(CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS SIGNED) AS allow_task_updates, CAST(COALESCE(p.slug, n.slug) AS CHAR CHARACTER SET utf8mb4) AS resource_slug, n.workspace_id AS workspace_id, CAST(w.slug AS CHAR CHARACTER SET utf8mb4) AS workspace_slug, CAST(COALESCE(p.owner_map, n.owner_map, '[]') AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(COALESCE(p.title, n.title) AS CHAR CHARACTER SET utf8mb4) AS title, CAST(COALESCE(p.content, n.content) AS CHAR CHARACTER SET utf8mb4) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = ? AND (pp.note_id IS NULL OR n.deleted_at IS NULL)"#
}
Query::Q022 => r#"SELECT file_token FROM pads WHERE id = ?"#,
Query::Q023 => r#"UPDATE pads SET file_token = ? WHERE id = ? AND file_token IS NULL"#,
Query::Q024 => r#"SELECT file_token FROM notes WHERE id = ?"#,
Query::Q025 => r#"UPDATE notes SET file_token = ? WHERE id = ? AND file_token IS NULL"#,
Query::Q026 => r#"SELECT id FROM pads WHERE file_token = ?"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = ?"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = ? AND deleted_at IS NULL"#,
Query::Q028 => {
r#"SELECT CAST(content AS CHAR CHARACTER SET utf8mb4) FROM note_revisions WHERE id = ? AND note_id = ?"#
}
+16 -7
View File
@@ -202,10 +202,10 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT COUNT(*) FROM user_note_favorites WHERE user_id = $1 AND note_id = $2"#
}
Query::USER_FAVORITE_NOTE_LIST => {
r#"SELECT n.id, n.slug, n.title, n.updated_at, w.slug AS workspace_slug, w.title AS workspace_title, CAST(CASE WHEN w.is_private THEN 1 ELSE 0 END AS BIGINT) AS private, CAST(CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS BIGINT) AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = $1 ORDER BY f.created_at DESC, n.id DESC"#
r#"SELECT n.id, n.slug, n.title, n.updated_at, w.slug AS workspace_slug, w.title AS workspace_title, CAST(CASE WHEN w.is_private THEN 1 ELSE 0 END AS BIGINT) AS private, CAST(CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS BIGINT) AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = $1 AND n.deleted_at IS NULL ORDER BY f.created_at DESC, n.id DESC"#
}
Query::USER_FAVORITE_NOTE_IDS_BY_WORKSPACE => {
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = $1 AND n.workspace_id = $2"#
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = $1 AND n.workspace_id = $2 AND n.deleted_at IS NULL"#
}
Query::USER_FAVORITE_PADS_DELETE_BY_USER => {
r#"DELETE FROM user_pad_favorites WHERE user_id = $1"#
@@ -233,6 +233,15 @@ pub fn get(query: Query) -> &'static str {
Query::USER_SET_PAD_PRIVACY => {
r#"UPDATE pads SET is_private = $1, updated_at = (CURRENT_TIMESTAMP::text) WHERE slug = $2"#
}
Query::WORKSPACE_SET_ARCHIVE_DELETED_NOTES => {
r#"UPDATE workspaces SET archive_deleted_notes = $1, updated_at = (CURRENT_TIMESTAMP::text) WHERE id = $2"#
}
Query::NOTE_ARCHIVE => {
r#"UPDATE notes SET deleted_at = (CURRENT_TIMESTAMP::text), updated_at = (CURRENT_TIMESTAMP::text) WHERE id = $1 AND deleted_at IS NULL"#
}
Query::NOTE_SLUG_EXISTS_ANY => {
r#"SELECT COUNT(*) FROM notes WHERE workspace_id = $1 AND slug = $2"#
}
Query::RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE => {
r#"DELETE FROM resource_access_tokens WHERE resource_kind = $1 AND resource_slug = $2"#
}
@@ -311,14 +320,14 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT public_page_disabled FROM notes WHERE id = $1"#
}
Query::Q001 => {
r#"SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS BIGINT) AS is_private, created_by_guest_id FROM workspaces WHERE slug = $1"#
r#"SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS BIGINT) AS is_private, CAST(CASE WHEN archive_deleted_notes THEN 1 ELSE 0 END AS BIGINT) AS archive_deleted_notes, created_by_guest_id FROM workspaces WHERE slug = $1"#
}
Query::Q002 => r#"INSERT INTO workspaces (slug, title, password_hash, created_by_guest_id) VALUES ($1, $2, $3, $4)"#,
Query::Q003 => {
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS BIGINT) AS protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = $1 ORDER BY updated_at DESC, id DESC"#
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS BIGINT) AS protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = $1 AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
}
Query::Q004 => {
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS BIGINT) AS protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = $1 AND slug = $2"#
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS BIGINT) AS protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = $1 AND slug = $2 AND deleted_at IS NULL"#
}
Query::Q005 => {
r#"INSERT INTO notes (workspace_id, slug, title, protected, created_by, created_by_guest_id) VALUES ($1, $2, $3, $4, $5, $6)"#
@@ -357,14 +366,14 @@ pub fn get(query: Query) -> &'static str {
Query::Q019 => r#"SELECT token FROM published_pages WHERE note_id = $1"#,
Query::Q020 => r#"INSERT INTO published_pages (token, note_id) VALUES ($1, $2)"#,
Query::Q021 => {
r#"SELECT pp.token, pp.pad_id, pp.note_id, pp.allow_task_updates, COALESCE(p.slug, n.slug) AS resource_slug, n.workspace_id AS workspace_id, w.slug AS workspace_slug, COALESCE(p.owner_map, n.owner_map, '[]') AS owner_map, COALESCE(p.title, n.title) AS title, COALESCE(p.content, n.content) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = $1"#
r#"SELECT pp.token, pp.pad_id, pp.note_id, pp.allow_task_updates, COALESCE(p.slug, n.slug) AS resource_slug, n.workspace_id AS workspace_id, w.slug AS workspace_slug, COALESCE(p.owner_map, n.owner_map, '[]') AS owner_map, COALESCE(p.title, n.title) AS title, COALESCE(p.content, n.content) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = $1 AND (pp.note_id IS NULL OR n.deleted_at IS NULL)"#
}
Query::Q022 => r#"SELECT file_token FROM pads WHERE id = $1"#,
Query::Q023 => r#"UPDATE pads SET file_token = $1 WHERE id = $2 AND file_token IS NULL"#,
Query::Q024 => r#"SELECT file_token FROM notes WHERE id = $1"#,
Query::Q025 => r#"UPDATE notes SET file_token = $1 WHERE id = $2 AND file_token IS NULL"#,
Query::Q026 => r#"SELECT id FROM pads WHERE file_token = $1"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = $1"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = $1 AND deleted_at IS NULL"#,
Query::Q028 => r#"SELECT content FROM note_revisions WHERE id = $1 AND note_id = $2"#,
Query::Q029 => r#"SELECT content FROM revisions WHERE id = $1 AND pad_id = $2"#,
Query::Q030 => r#"SELECT owner_map FROM revisions WHERE id = $1 AND pad_id = $2"#,
+16 -7
View File
@@ -200,10 +200,10 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT COUNT(*) FROM user_note_favorites WHERE user_id = ? AND note_id = ?"#
}
Query::USER_FAVORITE_NOTE_LIST => {
r#"SELECT n.id, n.slug, n.title, n.updated_at, w.slug AS workspace_slug, w.title AS workspace_title, CASE WHEN w.is_private THEN 1 ELSE 0 END AS private, CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = ? ORDER BY f.created_at DESC, n.id DESC"#
r#"SELECT n.id, n.slug, n.title, n.updated_at, w.slug AS workspace_slug, w.title AS workspace_title, CASE WHEN w.is_private THEN 1 ELSE 0 END AS private, CASE WHEN w.password_hash IS NULL THEN 0 ELSE 1 END AS protected, f.created_at AS favorited_at FROM user_note_favorites f JOIN notes n ON n.id = f.note_id JOIN workspaces w ON w.id = n.workspace_id WHERE f.user_id = ? AND n.deleted_at IS NULL ORDER BY f.created_at DESC, n.id DESC"#
}
Query::USER_FAVORITE_NOTE_IDS_BY_WORKSPACE => {
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = ? AND n.workspace_id = ?"#
r#"SELECT f.note_id FROM user_note_favorites f JOIN notes n ON n.id = f.note_id WHERE f.user_id = ? AND n.workspace_id = ? AND n.deleted_at IS NULL"#
}
Query::USER_FAVORITE_PADS_DELETE_BY_USER => {
r#"DELETE FROM user_pad_favorites WHERE user_id = ?"#
@@ -231,6 +231,15 @@ pub fn get(query: Query) -> &'static str {
Query::USER_SET_PAD_PRIVACY => {
r#"UPDATE pads SET is_private = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?"#
}
Query::WORKSPACE_SET_ARCHIVE_DELETED_NOTES => {
r#"UPDATE workspaces SET archive_deleted_notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"#
}
Query::NOTE_ARCHIVE => {
r#"UPDATE notes SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL"#
}
Query::NOTE_SLUG_EXISTS_ANY => {
r#"SELECT COUNT(*) FROM notes WHERE workspace_id = ? AND slug = ?"#
}
Query::RESOURCE_ACCESS_TOKENS_DELETE_BY_RESOURCE => {
r#"DELETE FROM resource_access_tokens WHERE resource_kind = ? AND resource_slug = ?"#
}
@@ -311,14 +320,14 @@ pub fn get(query: Query) -> &'static str {
r#"SELECT CASE WHEN public_page_disabled THEN 1 ELSE 0 END FROM notes WHERE id = ?"#
}
Query::Q001 => {
r#"SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS INTEGER) AS is_private, created_by_guest_id FROM workspaces WHERE slug = ?"#
r#"SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS INTEGER) AS is_private, archive_deleted_notes, created_by_guest_id FROM workspaces WHERE slug = ?"#
}
Query::Q002 => r#"INSERT INTO workspaces (slug, title, password_hash, created_by_guest_id) VALUES (?, ?, ?, ?)"#,
Query::Q003 => {
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC"#
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = ? AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
}
Query::Q004 => {
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = ? AND slug = ?"#
r#"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by, created_by_guest_id FROM notes WHERE workspace_id = ? AND slug = ? AND deleted_at IS NULL"#
}
Query::Q005 => {
r#"INSERT INTO notes (workspace_id, slug, title, protected, created_by, created_by_guest_id) VALUES (?, ?, ?, ?, ?, ?)"#
@@ -355,14 +364,14 @@ pub fn get(query: Query) -> &'static str {
Query::Q019 => r#"SELECT token FROM published_pages WHERE note_id = ?"#,
Query::Q020 => r#"INSERT INTO published_pages (token, note_id) VALUES (?, ?)"#,
Query::Q021 => {
r#"SELECT pp.token, pp.pad_id, pp.note_id, CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS allow_task_updates, COALESCE(p.slug, n.slug) AS resource_slug, n.workspace_id AS workspace_id, w.slug AS workspace_slug, COALESCE(p.owner_map, n.owner_map, '[]') AS owner_map, COALESCE(p.title, n.title) AS title, COALESCE(p.content, n.content) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = ?"#
r#"SELECT pp.token, pp.pad_id, pp.note_id, CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS allow_task_updates, COALESCE(p.slug, n.slug) AS resource_slug, n.workspace_id AS workspace_id, w.slug AS workspace_slug, COALESCE(p.owner_map, n.owner_map, '[]') AS owner_map, COALESCE(p.title, n.title) AS title, COALESCE(p.content, n.content) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id LEFT JOIN workspaces w ON w.id = n.workspace_id WHERE pp.token = ? AND (pp.note_id IS NULL OR n.deleted_at IS NULL)"#
}
Query::Q022 => r#"SELECT file_token FROM pads WHERE id = ?"#,
Query::Q023 => r#"UPDATE pads SET file_token = ? WHERE id = ? AND file_token IS NULL"#,
Query::Q024 => r#"SELECT file_token FROM notes WHERE id = ?"#,
Query::Q025 => r#"UPDATE notes SET file_token = ? WHERE id = ? AND file_token IS NULL"#,
Query::Q026 => r#"SELECT id FROM pads WHERE file_token = ?"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = ?"#,
Query::Q027 => r#"SELECT id FROM notes WHERE file_token = ? AND deleted_at IS NULL"#,
Query::Q028 => r#"SELECT content FROM note_revisions WHERE id = ? AND note_id = ?"#,
Query::Q029 => r#"SELECT content FROM revisions WHERE id = ? AND pad_id = ?"#,
Query::Q030 => r#"SELECT owner_map FROM revisions WHERE id = ? AND pad_id = ?"#,
+1
View File
@@ -18,6 +18,7 @@ fn workspace(password_hash: Option<String>) -> Workspace {
created_at: String::new(),
updated_at: String::new(),
is_private: 1,
archive_deleted_notes: 0,
created_by_guest_id: None,
}
}
+3
View File
@@ -30,6 +30,9 @@ fn every_backend_has_explicit_queries() {
USER_FAVORITE_NOTE_IDS_BY_WORKSPACE,
USER_FAVORITE_PADS_DELETE_BY_USER,
USER_FAVORITE_NOTES_DELETE_BY_USER,
WORKSPACE_SET_ARCHIVE_DELETED_NOTES,
NOTE_ARCHIVE,
NOTE_SLUG_EXISTS_ANY,
RESOURCE_ACCESS_TOKENS_DELETE_BY_TOKEN_HASH,
SHARE_LINK_SESSION_SOURCE,
SHARE_SESSION_INSERT,