v0.2.87
This commit is contained in:
1 parent
acfe926066
commit
df403c0aa7
26 files changed
+1502
-144
No files matched your search
+12
-33
@@ -288,13 +288,8 @@ pub async fn list_favorites(
|
||||
|
||||
for note in notes {
|
||||
if (note.private_resource != 0 || note.protected_resource != 0)
|
||||
&& !has_header_resource_access(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
¬e.workspace_slug,
|
||||
)
|
||||
.await?
|
||||
&& !has_header_resource_access(&state, &headers, "workspace", ¬e.workspace_slug)
|
||||
.await?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -389,11 +384,7 @@ mod tests {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::COOKIE,
|
||||
HeaderValue::from_str(&format!(
|
||||
"{}={token}",
|
||||
crate::security::SESSION_COOKIE
|
||||
))
|
||||
.unwrap(),
|
||||
HeaderValue::from_str(&format!("{}={token}", crate::security::SESSION_COOKIE)).unwrap(),
|
||||
);
|
||||
(headers, user_id)
|
||||
}
|
||||
@@ -412,13 +403,9 @@ mod tests {
|
||||
};
|
||||
|
||||
for _ in 0..2 {
|
||||
let result = add_favorite(
|
||||
State(state.clone()),
|
||||
headers.clone(),
|
||||
Json(target()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = add_favorite(State(state.clone()), headers.clone(), Json(target()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.0.favorite);
|
||||
}
|
||||
|
||||
@@ -828,13 +815,9 @@ mod tests {
|
||||
workspace_slug: None,
|
||||
};
|
||||
|
||||
let anonymous = add_favorite(
|
||||
State(state.clone()),
|
||||
HeaderMap::new(),
|
||||
Json(target()),
|
||||
)
|
||||
.await
|
||||
.expect_err("anonymous favorite must fail");
|
||||
let anonymous = add_favorite(State(state.clone()), HeaderMap::new(), Json(target()))
|
||||
.await
|
||||
.expect_err("anonymous favorite must fail");
|
||||
assert_eq!(anonymous.into_response().status(), StatusCode::FORBIDDEN);
|
||||
|
||||
let (inactive_headers, user_id) = logged_in_headers(&state, "inactive").await;
|
||||
@@ -843,13 +826,9 @@ mod tests {
|
||||
.execute(state.db.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
let inactive = add_favorite(
|
||||
State(state),
|
||||
inactive_headers,
|
||||
Json(target()),
|
||||
)
|
||||
.await
|
||||
.expect_err("inactive account favorite must fail");
|
||||
let inactive = add_favorite(State(state), inactive_headers, Json(target()))
|
||||
.await
|
||||
.expect_err("inactive account favorite must fail");
|
||||
assert_eq!(inactive.into_response().status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -125,6 +125,7 @@ pub fn router(
|
||||
"/api/auth/resources/privacy",
|
||||
post(auth::set_resource_privacy),
|
||||
)
|
||||
.route("/api/auth/resources/mentions", get(auth::resource_mentions))
|
||||
.route(
|
||||
"/api/auth/resources/sharing",
|
||||
get(auth::resource_sharing)
|
||||
@@ -261,7 +262,6 @@ pub fn router(
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
async fn language_catalog() -> Response {
|
||||
let mut response = Json(crate::i18n::metadata()).into_response();
|
||||
response.headers_mut().insert(
|
||||
@@ -375,7 +375,10 @@ fn is_file_path(path: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_asset_path(path: &str) -> bool {
|
||||
path == "/assets" || path.starts_with("/assets/") || path == "/lang" || path.starts_with("/lang/")
|
||||
path == "/assets"
|
||||
|| path.starts_with("/assets/")
|
||||
|| path == "/lang"
|
||||
|| path.starts_with("/lang/")
|
||||
}
|
||||
|
||||
fn is_icon_path(path: &str) -> bool {
|
||||
|
||||
+32
-27
@@ -81,27 +81,21 @@ async fn share_session_redirect(
|
||||
) -> Response {
|
||||
let client_key = crate::security::client_key(headers);
|
||||
let cookie = match share.map(str::trim).filter(|value| !value.is_empty()) {
|
||||
Some(share) => match auth::create_share_session(
|
||||
state,
|
||||
kind,
|
||||
slug,
|
||||
share,
|
||||
&client_key,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(session)) => Some(crate::security::share_session_cookie(
|
||||
kind,
|
||||
slug,
|
||||
&session.token,
|
||||
session.max_age_seconds,
|
||||
)),
|
||||
Ok(None) => None,
|
||||
Err(error) => {
|
||||
tracing::warn!(error = %error.message, kind, slug, "failed to exchange share link for guest session");
|
||||
None
|
||||
Some(share) => {
|
||||
match auth::create_share_session(state, kind, slug, share, &client_key).await {
|
||||
Ok(Some(session)) => Some(crate::security::share_session_cookie(
|
||||
kind,
|
||||
slug,
|
||||
&session.token,
|
||||
session.max_age_seconds,
|
||||
)),
|
||||
Ok(None) => None,
|
||||
Err(error) => {
|
||||
tracing::warn!(error = %error.message, kind, slug, "failed to exchange share link for guest session");
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
@@ -118,10 +112,9 @@ async fn share_session_redirect(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("no-store, max-age=0"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
"referrer-policy",
|
||||
HeaderValue::from_static("no-referrer"),
|
||||
);
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("referrer-policy", HeaderValue::from_static("no-referrer"));
|
||||
response
|
||||
}
|
||||
|
||||
@@ -383,7 +376,11 @@ pub(super) async fn workspace(
|
||||
)
|
||||
.replace(
|
||||
"__WORKSPACE_TITLE_I18N__",
|
||||
if workspace.is_private != 0 { r#"data-i18n="common.workspace""# } else { "" },
|
||||
if workspace.is_private != 0 {
|
||||
r#"data-i18n="common.workspace""#
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
assets::render_html(
|
||||
&html,
|
||||
@@ -460,7 +457,11 @@ pub(super) async fn note(
|
||||
} else {
|
||||
¬e.title
|
||||
},
|
||||
if workspace.is_private != 0 { Some("common.note") } else { None },
|
||||
if workspace.is_private != 0 {
|
||||
Some("common.note")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
if workspace.is_private != 0 {
|
||||
"Workspace"
|
||||
} else {
|
||||
@@ -548,7 +549,11 @@ pub(super) fn error_response(
|
||||
let html = include_str!("../../static/error.html")
|
||||
.replace(
|
||||
"__APP_THEME_BOOTSTRAP__",
|
||||
&format!("{}{}", assets::theme_bootstrap(), assets::language_bootstrap()),
|
||||
&format!(
|
||||
"{}{}",
|
||||
assets::theme_bootstrap(),
|
||||
assets::language_bootstrap()
|
||||
),
|
||||
)
|
||||
.replace(
|
||||
"__APP_STYLESHEET__",
|
||||
|
||||
@@ -31,6 +31,8 @@ const MODULES: &[&str] = &[
|
||||
"logger",
|
||||
"line-links",
|
||||
"markdown",
|
||||
"mention-autocomplete",
|
||||
"mention-navigation",
|
||||
"modal",
|
||||
"note-api",
|
||||
"note-editor",
|
||||
|
||||
+64
-6
@@ -1791,6 +1791,69 @@ pub async fn remove_resource_user(
|
||||
Ok(Json(serde_json::json!({"ok":true})))
|
||||
}
|
||||
|
||||
/// Return only explicitly authorized account names; never expose e-mail addresses.
|
||||
/// The requester must themselves own the resource or have an accepted direct share.
|
||||
pub async fn resource_mentions(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Query(params): Query<std::collections::HashMap<String, String>>,
|
||||
) -> Result<Json<serde_json::Value>, AuthError> {
|
||||
let user = require_user(&state, &headers).await?;
|
||||
let kind = params.get("kind").map(String::as_str).unwrap_or("");
|
||||
let slug = params.get("slug").map(String::as_str).unwrap_or("").trim();
|
||||
let (query, owner_query) = match kind {
|
||||
"workspace" => (
|
||||
queries::RESOURCE_MENTION_USERS_WORKSPACE,
|
||||
queries::USER_OWNS_WORKSPACE,
|
||||
),
|
||||
"pad" => (queries::RESOURCE_MENTION_USERS_PAD, queries::USER_OWNS_PAD),
|
||||
_ => return Err(AuthError::bad_request("Unknown resource type.")),
|
||||
};
|
||||
if slug.is_empty() {
|
||||
return Err(AuthError::bad_request("Missing slug."));
|
||||
}
|
||||
let owned: i64 = sqlx::query_scalar(queries::get(state.db.kind(), owner_query))
|
||||
.bind(user.id)
|
||||
.bind(slug)
|
||||
.fetch_one(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
if owned == 0 {
|
||||
let access: Option<String> = sqlx::query_scalar(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_PERMISSION_BY_USER,
|
||||
))
|
||||
.bind(kind)
|
||||
.bind(slug)
|
||||
.bind(user.id)
|
||||
.fetch_optional(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
if access.is_none() {
|
||||
return Err(AuthError::forbidden(
|
||||
"This resource was not shared with your account.",
|
||||
));
|
||||
}
|
||||
}
|
||||
let rows = sqlx::query(queries::get(state.db.kind(), query))
|
||||
.bind(slug)
|
||||
.bind(kind)
|
||||
.bind(slug)
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
let users: Vec<_> = rows
|
||||
.iter()
|
||||
.map(|row| -> Result<serde_json::Value, AuthError> {
|
||||
Ok(serde_json::json!({
|
||||
"nickname": crate::row_decode::text(row, 0).map_err(AuthError::database)?,
|
||||
"color": crate::row_decode::optional_text(row, 1).map_err(AuthError::database)?,
|
||||
}))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(Json(serde_json::json!({ "users": users })))
|
||||
}
|
||||
|
||||
pub async fn resource_sharing(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
@@ -2262,12 +2325,7 @@ pub async fn logout(
|
||||
let mut revoked_access_tokens = 0_u64;
|
||||
|
||||
if session_token.is_some() || has_access_tokens {
|
||||
let mut tx = state
|
||||
.db
|
||||
.pool()
|
||||
.begin()
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
let mut tx = state.db.pool().begin().await.map_err(AuthError::database)?;
|
||||
|
||||
if let Some(token) = session_token.as_deref() {
|
||||
let result = sqlx::query(queries::get(
|
||||
|
||||
+3
-6
@@ -76,8 +76,6 @@ pub struct Note {
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ArchivedNote {
|
||||
#[serde(skip_serializing)]
|
||||
pub id: i64,
|
||||
pub slug: String,
|
||||
pub title: String,
|
||||
pub created_at: String,
|
||||
@@ -345,20 +343,20 @@ pub async fn list_archived_notes(
|
||||
) -> Result<Vec<ArchivedNote>, sqlx::Error> {
|
||||
let query = match pool.kind() {
|
||||
DatabaseKind::Sqlite => {
|
||||
r#"SELECT id, slug, title, created_at, updated_at, deleted_at, protected, created_by
|
||||
r#"SELECT slug, title, created_at, updated_at, deleted_at, protected, created_by
|
||||
FROM notes
|
||||
WHERE workspace_id = ? AND deleted_at IS NOT NULL
|
||||
ORDER BY deleted_at DESC, id DESC"#
|
||||
}
|
||||
DatabaseKind::Postgres => {
|
||||
r#"SELECT id, slug, title, created_at, updated_at, deleted_at,
|
||||
r#"SELECT slug, title, created_at, updated_at, deleted_at,
|
||||
CAST(CASE WHEN protected THEN 1 ELSE 0 END AS BIGINT) AS protected, created_by
|
||||
FROM notes
|
||||
WHERE workspace_id = $1 AND deleted_at IS NOT NULL
|
||||
ORDER BY deleted_at DESC, id DESC"#
|
||||
}
|
||||
DatabaseKind::MySql => {
|
||||
r#"SELECT id, CAST(slug AS CHAR CHARACTER SET utf8mb4) AS slug,
|
||||
r#"SELECT CAST(slug AS CHAR CHARACTER SET utf8mb4) AS slug,
|
||||
CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, created_at, updated_at, deleted_at,
|
||||
CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected,
|
||||
CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by
|
||||
@@ -374,7 +372,6 @@ pub async fn list_archived_notes(
|
||||
rows.into_iter()
|
||||
.map(|row| {
|
||||
Ok(ArchivedNote {
|
||||
id: row.try_get("id")?,
|
||||
slug: crate::row_decode::text(&row, "slug")?,
|
||||
title: crate::row_decode::text(&row, "title")?,
|
||||
created_at: crate::row_decode::text(&row, "created_at")?,
|
||||
|
||||
@@ -114,6 +114,8 @@ pub enum Query {
|
||||
SHARE_INVITATION_FIND_TOKEN,
|
||||
SHARE_INVITATION_ACCEPT,
|
||||
RESOURCE_SHARING_USERS,
|
||||
RESOURCE_MENTION_USERS_WORKSPACE,
|
||||
RESOURCE_MENTION_USERS_PAD,
|
||||
RESOURCE_SHARING_LINKS,
|
||||
RESOURCE_SHARING_PENDING,
|
||||
SHARE_LINK_INSERT,
|
||||
@@ -296,6 +298,8 @@ pub const SHARE_INVITATION_DELETE_TOKEN: Query = Query::SHARE_INVITATION_DELETE_
|
||||
pub const SHARE_INVITATION_FIND_TOKEN: Query = Query::SHARE_INVITATION_FIND_TOKEN;
|
||||
pub const SHARE_INVITATION_ACCEPT: Query = Query::SHARE_INVITATION_ACCEPT;
|
||||
pub const RESOURCE_SHARING_USERS: Query = Query::RESOURCE_SHARING_USERS;
|
||||
pub const RESOURCE_MENTION_USERS_WORKSPACE: Query = Query::RESOURCE_MENTION_USERS_WORKSPACE;
|
||||
pub const RESOURCE_MENTION_USERS_PAD: Query = Query::RESOURCE_MENTION_USERS_PAD;
|
||||
pub const RESOURCE_SHARING_LINKS: Query = Query::RESOURCE_SHARING_LINKS;
|
||||
pub const RESOURCE_SHARING_PENDING: Query = Query::RESOURCE_SHARING_PENDING;
|
||||
pub const SHARE_LINK_INSERT: Query = Query::SHARE_LINK_INSERT;
|
||||
|
||||
+12
-2
@@ -32,7 +32,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"UPDATE users SET editor_color = ?, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_UPDATE_THEME => r#"UPDATE users SET theme = ?, updated_at = ? WHERE id = ?"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => r#"UPDATE users SET language = ?, updated_at = ? WHERE id = ?"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => {
|
||||
r#"UPDATE users SET language = ?, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_EDITOR_COLOR_BY_USER => r#"SELECT editor_color FROM users WHERE id = ?"#,
|
||||
Query::RESOURCE_COLOR_BY_USER => {
|
||||
r#"SELECT color FROM user_resource_colors WHERE user_id = ? AND resource_kind = ? AND resource_slug = ?"#
|
||||
@@ -276,6 +278,12 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_SHARING_USERS => {
|
||||
r#"SELECT u.email, u.nickname, rp.permission FROM resource_permissions rp JOIN users u ON u.id = rp.user_id WHERE rp.resource_kind = ? AND rp.resource_slug = ? ORDER BY u.email"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_WORKSPACE => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = 1 AND (EXISTS (SELECT 1 FROM user_workspaces own JOIN workspaces r ON r.id = own.workspace_id WHERE r.slug = ? AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = ? AND rp.resource_slug = ? AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_PAD => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = 1 AND (EXISTS (SELECT 1 FROM user_pads own JOIN pads r ON r.id = own.pad_id WHERE r.slug = ? AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = ? AND rp.resource_slug = ? AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_SHARING_LINKS => {
|
||||
r#"SELECT token_hash, CAST(label AS CHAR CHARACTER SET utf8mb4) AS label, permission, CAST(expires_at AS CHAR CHARACTER SET utf8mb4) AS expires_at, CAST(created_at AS CHAR CHARACTER SET utf8mb4) AS created_at FROM resource_share_links WHERE resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL ORDER BY created_at DESC"#
|
||||
}
|
||||
@@ -322,7 +330,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
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(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::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 = ? AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
|
||||
}
|
||||
|
||||
+12
-2
@@ -32,7 +32,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"UPDATE users SET editor_color = $1, updated_at = $2 WHERE id = $3"#
|
||||
}
|
||||
Query::AUTH_UPDATE_THEME => r#"UPDATE users SET theme = $1, updated_at = $2 WHERE id = $3"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => r#"UPDATE users SET language = $1, updated_at = $2 WHERE id = $3"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => {
|
||||
r#"UPDATE users SET language = $1, updated_at = $2 WHERE id = $3"#
|
||||
}
|
||||
Query::AUTH_EDITOR_COLOR_BY_USER => r#"SELECT editor_color FROM users WHERE id = $1"#,
|
||||
Query::RESOURCE_COLOR_BY_USER => {
|
||||
r#"SELECT color FROM user_resource_colors WHERE user_id = $1 AND resource_kind = $2 AND resource_slug = $3"#
|
||||
@@ -278,6 +280,12 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_SHARING_USERS => {
|
||||
r#"SELECT u.email, u.nickname, rp.permission FROM resource_permissions rp JOIN users u ON u.id = rp.user_id WHERE rp.resource_kind = $1 AND rp.resource_slug = $2 ORDER BY u.email"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_WORKSPACE => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = TRUE AND (EXISTS (SELECT 1 FROM user_workspaces own JOIN workspaces r ON r.id = own.workspace_id WHERE r.slug = $1 AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = $2 AND rp.resource_slug = $3 AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_PAD => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = TRUE AND (EXISTS (SELECT 1 FROM user_pads own JOIN pads r ON r.id = own.pad_id WHERE r.slug = $1 AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = $2 AND rp.resource_slug = $3 AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_SHARING_LINKS => {
|
||||
r#"SELECT token_hash, label, permission, expires_at, created_at FROM resource_share_links WHERE resource_kind = $1 AND resource_slug = $2 AND revoked_at IS NULL ORDER BY created_at DESC"#
|
||||
}
|
||||
@@ -322,7 +330,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
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, 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::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 AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
|
||||
}
|
||||
|
||||
+12
-2
@@ -32,7 +32,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
r#"UPDATE users SET editor_color = ?, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_UPDATE_THEME => r#"UPDATE users SET theme = ?, updated_at = ? WHERE id = ?"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => r#"UPDATE users SET language = ?, updated_at = ? WHERE id = ?"#,
|
||||
Query::AUTH_UPDATE_LANGUAGE => {
|
||||
r#"UPDATE users SET language = ?, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_EDITOR_COLOR_BY_USER => r#"SELECT editor_color FROM users WHERE id = ?"#,
|
||||
Query::RESOURCE_COLOR_BY_USER => {
|
||||
r#"SELECT color FROM user_resource_colors WHERE user_id = ? AND resource_kind = ? AND resource_slug = ?"#
|
||||
@@ -276,6 +278,12 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_SHARING_USERS => {
|
||||
r#"SELECT u.email, u.nickname, rp.permission FROM resource_permissions rp JOIN users u ON u.id = rp.user_id WHERE rp.resource_kind = ? AND rp.resource_slug = ? ORDER BY u.email"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_WORKSPACE => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = 1 AND (EXISTS (SELECT 1 FROM user_workspaces own JOIN workspaces r ON r.id = own.workspace_id WHERE r.slug = ? AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = ? AND rp.resource_slug = ? AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_MENTION_USERS_PAD => {
|
||||
r#"SELECT u.nickname, u.editor_color FROM users u WHERE u.is_active = 1 AND (EXISTS (SELECT 1 FROM user_pads own JOIN pads r ON r.id = own.pad_id WHERE r.slug = ? AND own.user_id = u.id) OR EXISTS (SELECT 1 FROM resource_permissions rp WHERE rp.resource_kind = ? AND rp.resource_slug = ? AND rp.user_id = u.id)) ORDER BY LOWER(u.nickname), u.nickname"#
|
||||
}
|
||||
Query::RESOURCE_SHARING_LINKS => {
|
||||
r#"SELECT token_hash, label, permission, expires_at, created_at FROM resource_share_links WHERE resource_kind = ? AND resource_slug = ? AND revoked_at IS NULL ORDER BY created_at DESC"#
|
||||
}
|
||||
@@ -322,7 +330,9 @@ pub fn get(query: Query) -> &'static str {
|
||||
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, 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::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 = ? AND deleted_at IS NULL ORDER BY updated_at DESC, id DESC"#
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user