v0.2.84
This commit is contained in:
1 parent
6329ac33d1
commit
90e3c399ea
22 files changed
+729
-46
No files matched your search
+6
-1
@@ -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
@@ -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(¬e.created_at),
|
||||
updated_at: db::normalize_timestamp(¬e.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 = ?"#,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user