new finctions and fixes
This commit is contained in:
+37
-9
@@ -101,14 +101,37 @@ pub async fn upload_pad_file(
|
||||
let mime = mime_guess::from_path(&stored)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
let cache_control = format!("public, max-age={}", state.file_cache_max_age_seconds);
|
||||
let cache_control = crate::cache::cache_control(state.file_cache_max_age_seconds);
|
||||
state
|
||||
.storage
|
||||
.put(&key, bytes.clone().into(), &mime, &cache_control)
|
||||
.await
|
||||
.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
db::register_pad_file(&state.db, pad.id, &stored, &url, &mime, bytes.len() as i64).await?;
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": url})))
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": url, "mime_type": mime})))
|
||||
}
|
||||
|
||||
pub(super) fn content_references_file(content: &str, filename: &str, url: &str) -> bool {
|
||||
if content.contains(url) {
|
||||
return true;
|
||||
}
|
||||
for marker in ["[file=", "[image=", "[img="] {
|
||||
let mut remaining = content;
|
||||
while let Some(index) = remaining.find(marker) {
|
||||
let after = &remaining[index + marker.len()..];
|
||||
let end = after
|
||||
.find(|character| character == ',' || character == ']')
|
||||
.unwrap_or(after.len());
|
||||
if after[..end].trim() == filename {
|
||||
return true;
|
||||
}
|
||||
if end >= after.len() {
|
||||
break;
|
||||
}
|
||||
remaining = &after[end + 1..];
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn pad_files(
|
||||
@@ -127,7 +150,7 @@ pub async fn pad_files(
|
||||
.await?;
|
||||
let mut files = db::list_pad_files(&state.db, pad.id).await?;
|
||||
for file in &mut files {
|
||||
let attached = pad.content.contains(&file.url);
|
||||
let attached = content_references_file(&pad.content, &file.filename, &file.url);
|
||||
if attached != file.is_attached {
|
||||
db::set_pad_file_attached(&state.db, file.id, attached).await?;
|
||||
file.is_attached = attached;
|
||||
@@ -265,14 +288,14 @@ pub async fn upload_note_file(
|
||||
let mime = mime_guess::from_path(&stored)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
let cache_control = format!("public, max-age={}", state.file_cache_max_age_seconds);
|
||||
let cache_control = crate::cache::cache_control(state.file_cache_max_age_seconds);
|
||||
state
|
||||
.storage
|
||||
.put(&key, bytes.clone().into(), &mime, &cache_control)
|
||||
.await
|
||||
.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
db::register_note_file(&state.db, note.id, &stored, &url, &mime, bytes.len() as i64).await?;
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": url})))
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": url, "mime_type": mime})))
|
||||
}
|
||||
|
||||
pub async fn delete_note(
|
||||
@@ -316,6 +339,12 @@ pub async fn delete_note(
|
||||
.await
|
||||
.map_err(|_| ApiError::internal("Failed to delete note files"))?;
|
||||
}
|
||||
db::delete_resource_editor_state(
|
||||
&state.db,
|
||||
"note",
|
||||
&format!("{workspace_slug}/{note_slug}"),
|
||||
)
|
||||
.await?;
|
||||
db::delete_note(&state.db, note.id).await?;
|
||||
Ok(Json(serde_json::json!({"ok": true})))
|
||||
}
|
||||
@@ -337,7 +366,7 @@ pub async fn note_files(
|
||||
.await?;
|
||||
let mut files = db::list_note_files(&state.db, note.id).await?;
|
||||
for file in &mut files {
|
||||
let attached = note.content.contains(&file.url);
|
||||
let attached = content_references_file(¬e.content, &file.filename, &file.url);
|
||||
if attached != file.is_attached {
|
||||
db::set_note_file_attached(&state.db, file.id, attached).await?;
|
||||
file.is_attached = attached;
|
||||
@@ -464,9 +493,8 @@ async fn serve_token_file(
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_str(&format!(
|
||||
"public, max-age={}",
|
||||
state.file_cache_max_age_seconds
|
||||
HeaderValue::from_str(&crate::cache::cache_control(
|
||||
state.file_cache_max_age_seconds,
|
||||
))
|
||||
.expect("valid file cache-control header"),
|
||||
);
|
||||
|
||||
+261
-85
@@ -48,18 +48,99 @@ fn bearer_token(headers: &HeaderMap) -> Option<&str> {
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn user_session_token(headers: &HeaderMap) -> Option<&str> {
|
||||
headers
|
||||
.get("x-rustpad-user-token")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.or_else(|| bearer_token(headers))
|
||||
}
|
||||
|
||||
async fn session_user(
|
||||
state: &SharedState,
|
||||
headers: &HeaderMap,
|
||||
) -> Result<Option<crate::auth::User>, ApiError> {
|
||||
let Some(token) = user_session_token(headers) else {
|
||||
return Ok(None);
|
||||
};
|
||||
crate::auth::user_from_token(state, token)
|
||||
.await
|
||||
.map_err(|error| ApiError::forbidden(&error.message))
|
||||
}
|
||||
|
||||
async fn has_write_permission(
|
||||
state: &SharedState,
|
||||
headers: &HeaderMap,
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
) -> Result<bool, ApiError> {
|
||||
let bearer = bearer_token(headers);
|
||||
if token_access_level(state, kind, slug, bearer).await? >= AccessLevel::Write {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let session = user_session_token(headers);
|
||||
if session.is_some() && session != bearer {
|
||||
return Ok(token_access_level(state, kind, slug, session).await? >= AccessLevel::Write);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PublishResponse {
|
||||
url: Option<String>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct MarkdownFileReference {
|
||||
filename: String,
|
||||
url: String,
|
||||
mime_type: String,
|
||||
}
|
||||
|
||||
impl From<db::NoteFile> for MarkdownFileReference {
|
||||
fn from(file: db::NoteFile) -> Self {
|
||||
Self {
|
||||
filename: file.filename,
|
||||
url: file.url,
|
||||
mime_type: file.mime_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn markdown_file_references(
|
||||
state: &SharedState,
|
||||
pad_id: Option<i64>,
|
||||
note_id: Option<i64>,
|
||||
content: Option<&str>,
|
||||
) -> Result<Vec<MarkdownFileReference>, ApiError> {
|
||||
let file_rows = if let Some(id) = pad_id {
|
||||
db::list_pad_files(&state.db, id).await?
|
||||
} else if let Some(id) = note_id {
|
||||
db::list_note_files(&state.db, id).await?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
Ok(file_rows
|
||||
.into_iter()
|
||||
.filter(|file| {
|
||||
content
|
||||
.map(|value| files::content_references_file(value, &file.filename, &file.url))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PublicPageResponse {
|
||||
title: String,
|
||||
content: String,
|
||||
updated_at: String,
|
||||
allow_task_updates: bool,
|
||||
files: Vec<MarkdownFileReference>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -173,7 +254,16 @@ pub struct NoteInfo {
|
||||
note_color: Option<String>,
|
||||
authorship_mode: String,
|
||||
colors_enabled: bool,
|
||||
compact_view: bool,
|
||||
editor_line_numbers: bool,
|
||||
preview_line_numbers: bool,
|
||||
line_links: bool,
|
||||
font_family: String,
|
||||
font_size: i64,
|
||||
personal_editor_settings: bool,
|
||||
can_save_editor_settings: bool,
|
||||
can_manage_authorship: bool,
|
||||
files: Vec<MarkdownFileReference>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -183,35 +273,38 @@ pub struct EditorColorRequest {
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EditorSettingsRequest {
|
||||
authorship_mode: String,
|
||||
colors_enabled: bool,
|
||||
#[serde(default)]
|
||||
authorship_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
colors_enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
compact_view: Option<bool>,
|
||||
#[serde(default)]
|
||||
editor_line_numbers: Option<bool>,
|
||||
#[serde(default)]
|
||||
preview_line_numbers: Option<bool>,
|
||||
#[serde(default)]
|
||||
line_links: Option<bool>,
|
||||
#[serde(default)]
|
||||
font_family: Option<String>,
|
||||
#[serde(default)]
|
||||
font_size: Option<i64>,
|
||||
}
|
||||
|
||||
async fn editor_settings(
|
||||
async fn user_editor_preferences(
|
||||
state: &SharedState,
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
) -> Result<(String, bool), ApiError> {
|
||||
let row: Option<(String, i64)> = sqlx::query_as(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_SELECT,
|
||||
headers: &HeaderMap,
|
||||
resource: db::EditorPreferenceResource,
|
||||
) -> Result<(db::EditorPreferences, bool), ApiError> {
|
||||
let Some(user) = session_user(state, headers).await? else {
|
||||
return Ok((db::EditorPreferences::default(), false));
|
||||
};
|
||||
Ok((
|
||||
db::load_editor_preferences(&state.db, user.id, resource)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
true,
|
||||
))
|
||||
.bind(kind)
|
||||
.bind(slug)
|
||||
.fetch_optional(state.db.pool())
|
||||
.await?;
|
||||
Ok(row
|
||||
.map(|(mode, colors)| {
|
||||
(
|
||||
if mode == "full" {
|
||||
"full".into()
|
||||
} else {
|
||||
"simple".into()
|
||||
},
|
||||
colors != 0,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| ("simple".into(), true)))
|
||||
}
|
||||
|
||||
async fn save_editor_settings(
|
||||
@@ -221,49 +314,123 @@ async fn save_editor_settings(
|
||||
permission_slug: &str,
|
||||
settings_kind: &str,
|
||||
settings_slug: &str,
|
||||
resource: db::EditorPreferenceResource,
|
||||
payload: EditorSettingsRequest,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let permission = crate::auth::resource_permission(
|
||||
if !has_write_permission(
|
||||
state,
|
||||
headers,
|
||||
permission_kind,
|
||||
permission_slug,
|
||||
bearer_token(headers),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ApiError::forbidden(&e.message))?;
|
||||
if permission.as_deref() != Some("rw") {
|
||||
.await?
|
||||
{
|
||||
return Err(ApiError::forbidden(
|
||||
"Read and write access is required to save editor settings",
|
||||
"Read and write access is required to save editor preferences",
|
||||
));
|
||||
}
|
||||
let mode = match payload.authorship_mode.as_str() {
|
||||
"simple" => "simple",
|
||||
"full" | "advanced" => "full",
|
||||
_ => return Err(ApiError::bad_request("Invalid authorship mode")),
|
||||
let user = session_user(state, headers)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::forbidden("Log in to save personal editor preferences"))?;
|
||||
|
||||
let wants_personal_update = payload.compact_view.is_some()
|
||||
|| payload.editor_line_numbers.is_some()
|
||||
|| payload.preview_line_numbers.is_some()
|
||||
|| payload.line_links.is_some()
|
||||
|| payload.font_family.is_some()
|
||||
|| payload.font_size.is_some();
|
||||
let wants_global_update = payload.authorship_mode.is_some() || payload.colors_enabled.is_some();
|
||||
if !wants_personal_update && !wants_global_update {
|
||||
return Err(ApiError::bad_request("No editor settings were provided"));
|
||||
}
|
||||
let can_manage_authorship = if wants_global_update {
|
||||
crate::auth::is_resource_owner(
|
||||
state,
|
||||
permission_kind,
|
||||
permission_slug,
|
||||
user_session_token(headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let mut tx = state.db.pool().begin().await?;
|
||||
sqlx::query(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_DELETE,
|
||||
))
|
||||
.bind(settings_kind)
|
||||
.bind(settings_slug)
|
||||
.execute(&mut *tx)
|
||||
if wants_global_update && !can_manage_authorship {
|
||||
return Err(ApiError::forbidden(
|
||||
"Only the resource owner can change authorship settings",
|
||||
));
|
||||
}
|
||||
|
||||
let preferences = if wants_personal_update {
|
||||
let mut preferences = db::load_editor_preferences(&state.db, user.id, resource)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
if let Some(value) = payload.compact_view {
|
||||
preferences.compact_view = value;
|
||||
}
|
||||
if let Some(value) = payload.editor_line_numbers {
|
||||
preferences.editor_line_numbers = value;
|
||||
}
|
||||
if let Some(value) = payload.preview_line_numbers {
|
||||
preferences.preview_line_numbers = value;
|
||||
}
|
||||
if let Some(value) = payload.line_links {
|
||||
preferences.line_links = value;
|
||||
}
|
||||
if let Some(value) = payload.font_family {
|
||||
preferences.font_family = match value.as_str() {
|
||||
"mono" | "system" | "serif" | "arial" | "georgia" => value,
|
||||
_ => return Err(ApiError::bad_request("Invalid editor font")),
|
||||
};
|
||||
}
|
||||
if let Some(value) = payload.font_size {
|
||||
if !matches!(value, 14 | 16 | 18 | 20 | 22) {
|
||||
return Err(ApiError::bad_request("Invalid editor font size"));
|
||||
}
|
||||
preferences.font_size = value;
|
||||
}
|
||||
Some(preferences)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let resource_settings = if wants_global_update {
|
||||
let mut settings = db::load_resource_editor_settings(
|
||||
&state.db,
|
||||
settings_kind,
|
||||
settings_slug,
|
||||
)
|
||||
.await?;
|
||||
if let Some(mode) = payload.authorship_mode {
|
||||
settings.authorship_mode = match mode.as_str() {
|
||||
"simple" => "simple".into(),
|
||||
"full" | "advanced" => "full".into(),
|
||||
_ => return Err(ApiError::bad_request("Invalid authorship mode")),
|
||||
};
|
||||
}
|
||||
if let Some(value) = payload.colors_enabled {
|
||||
settings.colors_enabled = value;
|
||||
}
|
||||
Some(settings)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
db::save_editor_configuration(
|
||||
&state.db,
|
||||
user.id,
|
||||
resource,
|
||||
preferences.as_ref(),
|
||||
resource_settings
|
||||
.as_ref()
|
||||
.map(|settings| (settings_kind, settings_slug, settings)),
|
||||
)
|
||||
.await?;
|
||||
sqlx::query(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_INSERT,
|
||||
))
|
||||
.bind(settings_kind)
|
||||
.bind(settings_slug)
|
||||
.bind(mode)
|
||||
.bind(payload.colors_enabled)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(Json(
|
||||
serde_json::json!({"authorship_mode": mode, "colors_enabled": payload.colors_enabled}),
|
||||
))
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"preferences": preferences,
|
||||
"resource_settings": resource_settings,
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn create_workspace(
|
||||
@@ -456,10 +623,7 @@ async fn editor_colors(
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
) -> Result<(Option<String>, Option<String>), ApiError> {
|
||||
let Some(user) = crate::auth::optional_user(state, headers)
|
||||
.await
|
||||
.map_err(|e| ApiError::forbidden(&e.message))?
|
||||
else {
|
||||
let Some(user) = session_user(state, headers).await? else {
|
||||
return Ok((None, None));
|
||||
};
|
||||
let global: Option<String> = sqlx::query_scalar(queries::get(
|
||||
@@ -488,9 +652,8 @@ async fn save_editor_color(
|
||||
slug: &str,
|
||||
color: Option<&str>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let user = crate::auth::optional_user(state, headers)
|
||||
.await
|
||||
.map_err(|e| ApiError::forbidden(&e.message))?
|
||||
let user = session_user(state, headers)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::forbidden("Log in to save note colors"))?;
|
||||
let color = clean_editor_color(color)?;
|
||||
let mut tx = state.db.pool().begin().await?;
|
||||
@@ -540,18 +703,24 @@ pub async fn note_info(
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
let color_slug = format!("{}/{}", workspace_slug, note_slug);
|
||||
let (global_color, note_color) = editor_colors(&state, &headers, "note", &color_slug).await?;
|
||||
let (authorship_mode, colors_enabled) = editor_settings(&state, "note", &color_slug).await?;
|
||||
let can_save_editor_settings = crate::auth::resource_permission(
|
||||
let (editor_preferences, personal_editor_settings) = user_editor_preferences(
|
||||
&state,
|
||||
&headers,
|
||||
db::EditorPreferenceResource::Note(note.id),
|
||||
)
|
||||
.await?;
|
||||
let resource_editor_settings =
|
||||
db::load_resource_editor_settings(&state.db, "note", &color_slug).await?;
|
||||
let can_save_editor_settings = personal_editor_settings
|
||||
&& has_write_permission(&state, &headers, "workspace", &workspace_slug).await?;
|
||||
let can_manage_authorship = crate::auth::is_resource_owner(
|
||||
&state,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
bearer_token(&headers),
|
||||
user_session_token(&headers),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
== Some("rw");
|
||||
.unwrap_or(false);
|
||||
|
||||
if workspace.is_private == 0
|
||||
&& !db::note_public_page_disabled(&state.db, note.id).await?
|
||||
@@ -573,15 +742,7 @@ pub async fn note_info(
|
||||
created_at: db::normalize_timestamp(¬e.created_at),
|
||||
updated_at: db::normalize_timestamp(¬e.updated_at),
|
||||
can_delete_files: {
|
||||
let workspace_owner = crate::auth::is_resource_owner(
|
||||
&state,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
bearer_token(&headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
let note_owner = crate::auth::optional_user(&state, &headers)
|
||||
let note_owner = session_user(&state, &headers)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
@@ -591,13 +752,22 @@ pub async fn note_info(
|
||||
.map(|creator| creator == user.nickname)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
workspace_owner || note_owner
|
||||
can_manage_authorship || note_owner
|
||||
},
|
||||
global_color,
|
||||
note_color,
|
||||
authorship_mode,
|
||||
colors_enabled,
|
||||
authorship_mode: resource_editor_settings.authorship_mode,
|
||||
colors_enabled: resource_editor_settings.colors_enabled,
|
||||
compact_view: editor_preferences.compact_view,
|
||||
editor_line_numbers: editor_preferences.editor_line_numbers,
|
||||
preview_line_numbers: editor_preferences.preview_line_numbers,
|
||||
line_links: editor_preferences.line_links,
|
||||
font_family: editor_preferences.font_family,
|
||||
font_size: editor_preferences.font_size,
|
||||
personal_editor_settings,
|
||||
can_save_editor_settings,
|
||||
can_manage_authorship,
|
||||
files: markdown_file_references(&state, None, Some(note.id), None).await?,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -607,14 +777,20 @@ pub async fn set_note_editor_settings(
|
||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||
Json(payload): Json<EditorSettingsRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let settings_slug = format!("{}/{}", workspace_slug, note_slug);
|
||||
let workspace = db::find_workspace(&state.db, &workspace_slug)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_workspace)?;
|
||||
let note = db::find_note(&state.db, workspace.id, ¬e_slug)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
save_editor_settings(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
"note",
|
||||
&settings_slug,
|
||||
&format!("{workspace_slug}/{note_slug}"),
|
||||
db::EditorPreferenceResource::Note(note.id),
|
||||
payload,
|
||||
)
|
||||
.await
|
||||
|
||||
+69
-19
@@ -38,7 +38,16 @@ pub struct PadInfo {
|
||||
note_color: Option<String>,
|
||||
authorship_mode: String,
|
||||
colors_enabled: bool,
|
||||
compact_view: bool,
|
||||
editor_line_numbers: bool,
|
||||
preview_line_numbers: bool,
|
||||
line_links: bool,
|
||||
font_family: String,
|
||||
font_size: i64,
|
||||
personal_editor_settings: bool,
|
||||
can_save_editor_settings: bool,
|
||||
can_manage_authorship: bool,
|
||||
files: Vec<MarkdownFileReference>,
|
||||
}
|
||||
|
||||
pub async fn create_pad(
|
||||
@@ -92,14 +101,24 @@ pub async fn pad_info(
|
||||
)
|
||||
.await?;
|
||||
let (global_color, note_color) = editor_colors(&state, &headers, "pad", &slug).await?;
|
||||
let (authorship_mode, colors_enabled) = editor_settings(&state, "pad", &slug).await?;
|
||||
let can_save_editor_settings =
|
||||
crate::auth::resource_permission(&state, "pad", &slug, bearer_token(&headers))
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.as_deref()
|
||||
== Some("rw");
|
||||
let (editor_preferences, personal_editor_settings) = user_editor_preferences(
|
||||
&state,
|
||||
&headers,
|
||||
db::EditorPreferenceResource::Pad(pad.id),
|
||||
)
|
||||
.await?;
|
||||
let resource_editor_settings =
|
||||
db::load_resource_editor_settings(&state.db, "pad", &slug).await?;
|
||||
let can_manage_authorship = crate::auth::is_resource_owner(
|
||||
&state,
|
||||
"pad",
|
||||
&slug,
|
||||
user_session_token(&headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
let can_save_editor_settings = personal_editor_settings
|
||||
&& has_write_permission(&state, &headers, "pad", &slug).await?;
|
||||
if pad.is_private == 0
|
||||
&& !db::pad_public_page_disabled(&state.db, pad.id).await?
|
||||
&& !db::pad_public_page_enabled(&state.db, pad.id).await?
|
||||
@@ -116,19 +135,21 @@ pub async fn pad_info(
|
||||
private: pad.is_private != 0,
|
||||
created_at: db::normalize_timestamp(&pad.created_at),
|
||||
updated_at: db::normalize_timestamp(&pad.updated_at),
|
||||
can_delete_files: crate::auth::is_resource_owner(
|
||||
&state,
|
||||
"pad",
|
||||
&slug,
|
||||
bearer_token(&headers),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false),
|
||||
can_delete_files: can_manage_authorship,
|
||||
global_color,
|
||||
note_color,
|
||||
authorship_mode,
|
||||
colors_enabled,
|
||||
authorship_mode: resource_editor_settings.authorship_mode,
|
||||
colors_enabled: resource_editor_settings.colors_enabled,
|
||||
compact_view: editor_preferences.compact_view,
|
||||
editor_line_numbers: editor_preferences.editor_line_numbers,
|
||||
preview_line_numbers: editor_preferences.preview_line_numbers,
|
||||
line_links: editor_preferences.line_links,
|
||||
font_family: editor_preferences.font_family,
|
||||
font_size: editor_preferences.font_size,
|
||||
personal_editor_settings,
|
||||
can_save_editor_settings,
|
||||
can_manage_authorship,
|
||||
files: markdown_file_references(&state, Some(pad.id), None, None).await?,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -138,7 +159,20 @@ pub async fn set_pad_editor_settings(
|
||||
Path(slug): Path<String>,
|
||||
Json(payload): Json<EditorSettingsRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
save_editor_settings(&state, &headers, "pad", &slug, "pad", &slug, payload).await
|
||||
let pad = db::find_pad(&state.db, &slug)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
save_editor_settings(
|
||||
&state,
|
||||
&headers,
|
||||
"pad",
|
||||
&slug,
|
||||
"pad",
|
||||
&slug,
|
||||
db::EditorPreferenceResource::Pad(pad.id),
|
||||
payload,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn pad_editor_color(
|
||||
@@ -382,11 +416,19 @@ pub async fn public_page(
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
ensure_public_page_access(&state, &headers, &page).await?;
|
||||
let files = markdown_file_references(
|
||||
&state,
|
||||
page.pad_id,
|
||||
page.note_id,
|
||||
Some(&page.content),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(PublicPageResponse {
|
||||
title: page.title,
|
||||
content: page.content,
|
||||
updated_at: db::normalize_timestamp(&page.updated_at),
|
||||
allow_task_updates: page.allow_task_updates,
|
||||
files,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -408,11 +450,19 @@ pub async fn update_public_task(
|
||||
let page = db::update_public_task(&state.db, &token, payload.source_line, payload.checked)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
let files = markdown_file_references(
|
||||
&state,
|
||||
page.pad_id,
|
||||
page.note_id,
|
||||
Some(&page.content),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(PublicPageResponse {
|
||||
title: page.title,
|
||||
content: page.content,
|
||||
updated_at: db::normalize_timestamp(&page.updated_at),
|
||||
allow_task_updates: page.allow_task_updates,
|
||||
files,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -46,9 +46,10 @@ pub fn router(
|
||||
}
|
||||
});
|
||||
|
||||
let asset_cache_control =
|
||||
HeaderValue::from_str(&format!("public, max-age={asset_cache_max_age_seconds}"))
|
||||
.expect("valid asset cache-control header");
|
||||
let asset_cache_control = HeaderValue::from_str(&crate::cache::cache_control(
|
||||
asset_cache_max_age_seconds,
|
||||
))
|
||||
.expect("valid asset cache-control header");
|
||||
|
||||
Router::new()
|
||||
.route("/", get(home))
|
||||
|
||||
+52
-42
@@ -15,6 +15,36 @@ use axum::{
|
||||
|
||||
use crate::{assets, db, state::SharedState};
|
||||
|
||||
fn render_editor_page(
|
||||
state: &SharedState,
|
||||
entrypoint: &str,
|
||||
resource_kind: &str,
|
||||
document_title: &str,
|
||||
parent_title: &str,
|
||||
parent_url: &str,
|
||||
parent_class: &str,
|
||||
protected_resource_label: &str,
|
||||
extra_shortcuts: &str,
|
||||
) -> Response {
|
||||
let html = include_str!("../../static/editor.html")
|
||||
.replace("__RESOURCE_KIND__", resource_kind)
|
||||
.replace("__DOCUMENT_TITLE__", &escape_html(document_title))
|
||||
.replace("__PARENT_TITLE__", &escape_html(parent_title))
|
||||
.replace("__PARENT_URL__", &escape_html(parent_url))
|
||||
.replace("__PARENT_CLASS__", parent_class)
|
||||
.replace("__PROTECTED_RESOURCE_LABEL__", protected_resource_label)
|
||||
.replace("__EXTRA_SHORTCUTS__", extra_shortcuts);
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
state.ldap.is_some(),
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
entrypoint,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) async fn health() -> &'static str {
|
||||
"ok"
|
||||
}
|
||||
@@ -46,19 +76,17 @@ pub(super) async fn home(State(state): State<SharedState>) -> Response {
|
||||
|
||||
pub(super) async fn pad(State(state): State<SharedState>, Path(slug): Path<String>) -> Response {
|
||||
match db::find_pad(&state.db, &slug).await {
|
||||
Ok(Some(pad)) => {
|
||||
let html = include_str!("../../static/pad.html")
|
||||
.replace("__PAD_TITLE__", &escape_html(&pad.title));
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
state.ldap.is_some(),
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"pad",
|
||||
)
|
||||
}
|
||||
Ok(Some(pad)) => render_editor_page(
|
||||
&state,
|
||||
"pad",
|
||||
"pad",
|
||||
&pad.title,
|
||||
"RustPad",
|
||||
"/",
|
||||
"home-brand",
|
||||
"note",
|
||||
"<kbd>Alt+Enter</kbd><span>New line while editing Preview</span><kbd>Esc</kbd><span>Edit raw Markdown of current Preview line</span>",
|
||||
),
|
||||
Ok(None) => error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"404",
|
||||
@@ -169,35 +197,17 @@ pub(super) async fn note(
|
||||
};
|
||||
|
||||
match db::find_note(&state.db, workspace.id, ¬e_slug).await {
|
||||
Ok(Some(note)) => {
|
||||
let html = include_str!("../../static/note.html")
|
||||
.replace(
|
||||
"__NOTE_TITLE__",
|
||||
&escape_html(if workspace.is_private != 0 {
|
||||
"Note"
|
||||
} else {
|
||||
¬e.title
|
||||
}),
|
||||
)
|
||||
.replace(
|
||||
"__WORKSPACE_TITLE__",
|
||||
&escape_html(if workspace.is_private != 0 {
|
||||
"Workspace"
|
||||
} else {
|
||||
&workspace.title
|
||||
}),
|
||||
)
|
||||
.replace("__WORKSPACE_SLUG__", &escape_html(&workspace_slug));
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
state.registration_enabled,
|
||||
state.ldap.is_some(),
|
||||
&state.frontend_log_level,
|
||||
state.upload_max_size_bytes,
|
||||
"note",
|
||||
)
|
||||
}
|
||||
Ok(Some(note)) => render_editor_page(
|
||||
&state,
|
||||
"note",
|
||||
"note",
|
||||
if workspace.is_private != 0 { "Note" } else { ¬e.title },
|
||||
if workspace.is_private != 0 { "Workspace" } else { &workspace.title },
|
||||
&format!("/w/{workspace_slug}"),
|
||||
"",
|
||||
"workspace",
|
||||
"",
|
||||
),
|
||||
Ok(None) => error_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
"404",
|
||||
|
||||
@@ -944,6 +944,22 @@ pub async fn confirm_account_action(
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
sqlx::query(queries::get(
|
||||
state.db.kind(),
|
||||
queries::RESOURCE_COLORS_DELETE_BY_USER,
|
||||
))
|
||||
.bind(user_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
sqlx::query(queries::get(
|
||||
state.db.kind(),
|
||||
queries::EDITOR_PREFERENCES_DELETE_BY_USER,
|
||||
))
|
||||
.bind(user_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
sqlx::query(queries::get(state.db.kind(), queries::AUTH_ANONYMIZE_USER))
|
||||
.bind(&deleted_nickname)
|
||||
.bind(normalize(&deleted_nickname))
|
||||
@@ -1143,6 +1159,13 @@ pub async fn delete_resource(
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
for note in notes {
|
||||
crate::db::delete_resource_editor_state(
|
||||
&state.db,
|
||||
"note",
|
||||
&format!("{}/{}", req.slug.trim(), note.slug),
|
||||
)
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
let files = crate::db::list_note_files(&state.db, note.id)
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
@@ -1161,6 +1184,9 @@ pub async fn delete_resource(
|
||||
queries::USER_DELETE_WORKSPACE
|
||||
}
|
||||
"pad" => {
|
||||
crate::db::delete_resource_editor_state(&state.db, "pad", req.slug.trim())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
if let Some(pad) = crate::db::find_pad(&state.db, req.slug.trim())
|
||||
.await
|
||||
.map_err(AuthError::database)?
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
||||
* Source-Available Code / Dual-Licensed.
|
||||
*
|
||||
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
||||
* Commercial or production use requires a valid paid license.
|
||||
* See LICENSE file in repository root for details.
|
||||
*/
|
||||
|
||||
pub const DISABLED_CACHE_CONTROL: &str = "no-cache, no-store, must-revalidate";
|
||||
|
||||
pub fn cache_control(max_age_seconds: u64) -> String {
|
||||
if max_age_seconds == 0 {
|
||||
DISABLED_CACHE_CONTROL.into()
|
||||
} else {
|
||||
format!("public, max-age={max_age_seconds}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
||||
* Source-Available Code / Dual-Licensed.
|
||||
*
|
||||
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
||||
* Commercial or production use requires a valid paid license.
|
||||
* See LICENSE file in repository root for details.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct EditorPreferences {
|
||||
pub compact_view: bool,
|
||||
pub editor_line_numbers: bool,
|
||||
pub preview_line_numbers: bool,
|
||||
pub line_links: bool,
|
||||
pub font_family: String,
|
||||
pub font_size: i64,
|
||||
}
|
||||
|
||||
impl Default for EditorPreferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
compact_view: true,
|
||||
editor_line_numbers: true,
|
||||
preview_line_numbers: false,
|
||||
line_links: false,
|
||||
font_family: "mono".into(),
|
||||
font_size: 14,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ResourceEditorSettings {
|
||||
pub authorship_mode: String,
|
||||
pub colors_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for ResourceEditorSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
authorship_mode: "simple".into(),
|
||||
colors_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum EditorPreferenceResource {
|
||||
Pad(i64),
|
||||
Note(i64),
|
||||
}
|
||||
|
||||
fn preference_select_query(resource: EditorPreferenceResource) -> queries::Query {
|
||||
match resource {
|
||||
EditorPreferenceResource::Pad(_) => queries::EDITOR_PREFERENCES_SELECT_PAD,
|
||||
EditorPreferenceResource::Note(_) => queries::EDITOR_PREFERENCES_SELECT_NOTE,
|
||||
}
|
||||
}
|
||||
|
||||
fn preference_upsert_query(resource: EditorPreferenceResource) -> queries::Query {
|
||||
match resource {
|
||||
EditorPreferenceResource::Pad(_) => queries::EDITOR_PREFERENCES_UPSERT_PAD,
|
||||
EditorPreferenceResource::Note(_) => queries::EDITOR_PREFERENCES_UPSERT_NOTE,
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_id(resource: EditorPreferenceResource) -> i64 {
|
||||
match resource {
|
||||
EditorPreferenceResource::Pad(id) | EditorPreferenceResource::Note(id) => id,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_editor_preferences(
|
||||
pool: &Database,
|
||||
user_id: i64,
|
||||
resource: EditorPreferenceResource,
|
||||
) -> Result<Option<EditorPreferences>, sqlx::Error> {
|
||||
let Some(row) = sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
preference_select_query(resource),
|
||||
))
|
||||
.bind(user_id)
|
||||
.bind(resource_id(resource))
|
||||
.fetch_optional(pool.pool())
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(EditorPreferences {
|
||||
compact_view: row.try_get::<i64, _>(0)? != 0,
|
||||
editor_line_numbers: row.try_get::<i64, _>(1)? != 0,
|
||||
preview_line_numbers: row.try_get::<i64, _>(2)? != 0,
|
||||
line_links: row.try_get::<i64, _>(3)? != 0,
|
||||
font_family: crate::row_decode::text(&row, 4)?,
|
||||
font_size: row.try_get(5)?,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn save_editor_configuration(
|
||||
pool: &Database,
|
||||
user_id: i64,
|
||||
resource: EditorPreferenceResource,
|
||||
preferences: Option<&EditorPreferences>,
|
||||
resource_settings: Option<(&str, &str, &ResourceEditorSettings)>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let mut tx = pool.pool().begin().await?;
|
||||
|
||||
if let Some(preferences) = preferences {
|
||||
sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
preference_upsert_query(resource),
|
||||
))
|
||||
.bind(user_id)
|
||||
.bind(resource_id(resource))
|
||||
.bind(preferences.compact_view)
|
||||
.bind(preferences.editor_line_numbers)
|
||||
.bind(preferences.preview_line_numbers)
|
||||
.bind(preferences.line_links)
|
||||
.bind(&preferences.font_family)
|
||||
.bind(preferences.font_size)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some((resource_kind, resource_slug, settings)) = resource_settings {
|
||||
sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_UPSERT,
|
||||
))
|
||||
.bind(resource_kind)
|
||||
.bind(resource_slug)
|
||||
.bind(&settings.authorship_mode)
|
||||
.bind(settings.colors_enabled)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_resource_editor_settings(
|
||||
pool: &Database,
|
||||
resource_kind: &str,
|
||||
resource_slug: &str,
|
||||
) -> Result<ResourceEditorSettings, sqlx::Error> {
|
||||
let Some(row) = sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_SELECT,
|
||||
))
|
||||
.bind(resource_kind)
|
||||
.bind(resource_slug)
|
||||
.fetch_optional(pool.pool())
|
||||
.await?
|
||||
else {
|
||||
return Ok(ResourceEditorSettings::default());
|
||||
};
|
||||
|
||||
Ok(ResourceEditorSettings {
|
||||
authorship_mode: crate::row_decode::text(&row, 0)?,
|
||||
colors_enabled: row.try_get::<i64, _>(1)? != 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_resource_editor_state(
|
||||
pool: &Database,
|
||||
resource_kind: &str,
|
||||
resource_slug: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let mut tx = pool.pool().begin().await?;
|
||||
sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
queries::RESOURCE_COLORS_DELETE_BY_RESOURCE,
|
||||
))
|
||||
.bind(resource_kind)
|
||||
.bind(resource_slug)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query(queries::get(
|
||||
pool.kind(),
|
||||
queries::RESOURCE_EDITOR_SETTINGS_DELETE,
|
||||
))
|
||||
.bind(resource_kind)
|
||||
.bind(resource_slug)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -18,9 +18,11 @@ use serde::Serialize;
|
||||
use sqlx::FromRow;
|
||||
use sqlx::{Any, Row, Transaction, any::AnyRow};
|
||||
|
||||
mod editor_preferences;
|
||||
mod files;
|
||||
mod public_pages;
|
||||
|
||||
pub use editor_preferences::*;
|
||||
pub use files::*;
|
||||
pub use public_pages::*;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ mod api;
|
||||
mod app;
|
||||
mod assets;
|
||||
mod auth;
|
||||
mod cache;
|
||||
mod config;
|
||||
mod database;
|
||||
mod db;
|
||||
|
||||
+16
-2
@@ -30,9 +30,16 @@ pub enum Query {
|
||||
RESOURCE_COLOR_BY_USER,
|
||||
RESOURCE_COLOR_DELETE,
|
||||
RESOURCE_COLOR_INSERT,
|
||||
RESOURCE_COLORS_DELETE_BY_RESOURCE,
|
||||
RESOURCE_COLORS_DELETE_BY_USER,
|
||||
RESOURCE_EDITOR_SETTINGS_SELECT,
|
||||
RESOURCE_EDITOR_SETTINGS_UPSERT,
|
||||
RESOURCE_EDITOR_SETTINGS_DELETE,
|
||||
RESOURCE_EDITOR_SETTINGS_INSERT,
|
||||
EDITOR_PREFERENCES_SELECT_PAD,
|
||||
EDITOR_PREFERENCES_SELECT_NOTE,
|
||||
EDITOR_PREFERENCES_UPSERT_PAD,
|
||||
EDITOR_PREFERENCES_UPSERT_NOTE,
|
||||
EDITOR_PREFERENCES_DELETE_BY_USER,
|
||||
AUTH_ACCOUNT_ACTION_BY_TOKEN,
|
||||
AUTH_CONSUME_ACCOUNT_ACTION,
|
||||
AUTH_UPDATE_EMAIL,
|
||||
@@ -173,9 +180,16 @@ pub const AUTH_EDITOR_COLOR_BY_USER: Query = Query::AUTH_EDITOR_COLOR_BY_USER;
|
||||
pub const RESOURCE_COLOR_BY_USER: Query = Query::RESOURCE_COLOR_BY_USER;
|
||||
pub const RESOURCE_COLOR_DELETE: Query = Query::RESOURCE_COLOR_DELETE;
|
||||
pub const RESOURCE_COLOR_INSERT: Query = Query::RESOURCE_COLOR_INSERT;
|
||||
pub const RESOURCE_COLORS_DELETE_BY_RESOURCE: Query = Query::RESOURCE_COLORS_DELETE_BY_RESOURCE;
|
||||
pub const RESOURCE_COLORS_DELETE_BY_USER: Query = Query::RESOURCE_COLORS_DELETE_BY_USER;
|
||||
pub const RESOURCE_EDITOR_SETTINGS_SELECT: Query = Query::RESOURCE_EDITOR_SETTINGS_SELECT;
|
||||
pub const RESOURCE_EDITOR_SETTINGS_UPSERT: Query = Query::RESOURCE_EDITOR_SETTINGS_UPSERT;
|
||||
pub const RESOURCE_EDITOR_SETTINGS_DELETE: Query = Query::RESOURCE_EDITOR_SETTINGS_DELETE;
|
||||
pub const RESOURCE_EDITOR_SETTINGS_INSERT: Query = Query::RESOURCE_EDITOR_SETTINGS_INSERT;
|
||||
pub const EDITOR_PREFERENCES_SELECT_PAD: Query = Query::EDITOR_PREFERENCES_SELECT_PAD;
|
||||
pub const EDITOR_PREFERENCES_SELECT_NOTE: Query = Query::EDITOR_PREFERENCES_SELECT_NOTE;
|
||||
pub const EDITOR_PREFERENCES_UPSERT_PAD: Query = Query::EDITOR_PREFERENCES_UPSERT_PAD;
|
||||
pub const EDITOR_PREFERENCES_UPSERT_NOTE: Query = Query::EDITOR_PREFERENCES_UPSERT_NOTE;
|
||||
pub const EDITOR_PREFERENCES_DELETE_BY_USER: Query = Query::EDITOR_PREFERENCES_DELETE_BY_USER;
|
||||
pub const AUTH_ACCOUNT_ACTION_BY_TOKEN: Query = Query::AUTH_ACCOUNT_ACTION_BY_TOKEN;
|
||||
pub const AUTH_CONSUME_ACCOUNT_ACTION: Query = Query::AUTH_CONSUME_ACCOUNT_ACTION;
|
||||
pub const AUTH_UPDATE_EMAIL: Query = Query::AUTH_UPDATE_EMAIL;
|
||||
|
||||
+28
-6
@@ -38,17 +38,39 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_COLOR_DELETE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = ? AND resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES (?, ?, ?, ?)"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_RESOURCE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_SELECT => {
|
||||
r#"SELECT authorship_mode, CASE WHEN colors_enabled THEN 1 ELSE 0 END FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
r#"SELECT CAST(authorship_mode AS CHAR CHARACTER SET utf8mb4), CAST(CASE WHEN colors_enabled THEN 1 ELSE 0 END AS SIGNED) FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_UPSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE authorship_mode = VALUES(authorship_mode), colors_enabled = VALUES(colors_enabled), updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_DELETE => {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_INSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled) VALUES (?, ?, ?, ?)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
}
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES (?, ?, ?, ?)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT CAST(CASE WHEN compact_view THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN editor_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN preview_line_numbers THEN 1 ELSE 0 END AS SIGNED), CAST(CASE WHEN line_links THEN 1 ELSE 0 END AS SIGNED), CAST(font_family AS CHAR CHARACTER SET utf8mb4), font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE compact_view = VALUES(compact_view), editor_line_numbers = VALUES(editor_line_numbers), preview_line_numbers = VALUES(preview_line_numbers), line_links = VALUES(line_links), font_family = VALUES(font_family), font_size = VALUES(font_size), updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = ?"#
|
||||
}
|
||||
Query::AUTH_ACCOUNT_ACTION_BY_TOKEN => {
|
||||
r#"SELECT user_id, action, CAST(payload AS CHAR CHARACTER SET utf8mb4) AS payload, expires_at, used_at FROM account_action_tokens WHERE token = ?"#
|
||||
@@ -85,7 +107,7 @@ pub fn get(query: Query) -> &'static str {
|
||||
}
|
||||
Query::AUTH_DELETE_USER => r#"DELETE FROM users WHERE id = ?"#,
|
||||
Query::AUTH_ANONYMIZE_USER => {
|
||||
r#"UPDATE users SET nickname = ?, nickname_key = ?, email = ?, email_key = ?, password_hash = ?, is_active = 0, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, updated_at = ? WHERE id = ?"#
|
||||
r#"UPDATE users SET nickname = ?, nickname_key = ?, email = ?, email_key = ?, password_hash = ?, is_active = 0, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, editor_color = NULL, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_NICKNAME_BY_ID => r#"SELECT nickname FROM users WHERE id = ?"#,
|
||||
Query::AUTH_ANONYMIZE_NOTE_CREATORS => {
|
||||
|
||||
+27
-5
@@ -38,17 +38,39 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_COLOR_DELETE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = $1 AND resource_kind = $2 AND resource_slug = $3"#
|
||||
}
|
||||
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES ($1, $2, $3, $4)"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_RESOURCE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE resource_kind = $1 AND resource_slug = $2"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = $1"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_SELECT => {
|
||||
r#"SELECT authorship_mode, (CASE WHEN colors_enabled THEN 1 ELSE 0 END)::BIGINT FROM resource_editor_settings WHERE resource_kind = $1 AND resource_slug = $2"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_UPSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled, updated_at) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) ON CONFLICT (resource_kind, resource_slug) DO UPDATE SET authorship_mode = EXCLUDED.authorship_mode, colors_enabled = EXCLUDED.colors_enabled, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_DELETE => {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = $1 AND resource_slug = $2"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_INSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled) VALUES ($1, $2, $3, $4)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND pad_id = $2"#
|
||||
}
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES ($1, $2, $3, $4)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT (CASE WHEN compact_view THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN editor_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN preview_line_numbers THEN 1 ELSE 0 END)::BIGINT, (CASE WHEN line_links THEN 1 ELSE 0 END)::BIGINT, font_family, font_size FROM user_editor_preferences WHERE user_id = $1 AND note_id = $2"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES ($1, $2, NULL, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES ($1, NULL, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP::text) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = EXCLUDED.compact_view, editor_line_numbers = EXCLUDED.editor_line_numbers, preview_line_numbers = EXCLUDED.preview_line_numbers, line_links = EXCLUDED.line_links, font_family = EXCLUDED.font_family, font_size = EXCLUDED.font_size, updated_at = CURRENT_TIMESTAMP::text"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = $1"#
|
||||
}
|
||||
Query::AUTH_ACCOUNT_ACTION_BY_TOKEN => {
|
||||
r#"SELECT user_id, action, payload, expires_at, used_at FROM account_action_tokens WHERE token = $1"#
|
||||
@@ -85,7 +107,7 @@ pub fn get(query: Query) -> &'static str {
|
||||
}
|
||||
Query::AUTH_DELETE_USER => r#"DELETE FROM users WHERE id = $1"#,
|
||||
Query::AUTH_ANONYMIZE_USER => {
|
||||
r#"UPDATE users SET nickname = $1, nickname_key = $2, email = $3, email_key = $4, password_hash = $5, is_active = FALSE, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, updated_at = $6 WHERE id = $7"#
|
||||
r#"UPDATE users SET nickname = $1, nickname_key = $2, email = $3, email_key = $4, password_hash = $5, is_active = FALSE, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, editor_color = NULL, updated_at = $6 WHERE id = $7"#
|
||||
}
|
||||
Query::AUTH_NICKNAME_BY_ID => r#"SELECT nickname FROM users WHERE id = $1"#,
|
||||
Query::AUTH_ANONYMIZE_NOTE_CREATORS => {
|
||||
|
||||
+27
-5
@@ -38,17 +38,39 @@ pub fn get(query: Query) -> &'static str {
|
||||
Query::RESOURCE_COLOR_DELETE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = ? AND resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES (?, ?, ?, ?)"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_RESOURCE => {
|
||||
r#"DELETE FROM user_resource_colors WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_COLORS_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_resource_colors WHERE user_id = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_SELECT => {
|
||||
r#"SELECT authorship_mode, CASE WHEN colors_enabled THEN 1 ELSE 0 END FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_UPSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (resource_kind, resource_slug) DO UPDATE SET authorship_mode = excluded.authorship_mode, colors_enabled = excluded.colors_enabled, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_DELETE => {
|
||||
r#"DELETE FROM resource_editor_settings WHERE resource_kind = ? AND resource_slug = ?"#
|
||||
}
|
||||
Query::RESOURCE_EDITOR_SETTINGS_INSERT => {
|
||||
r#"INSERT INTO resource_editor_settings (resource_kind, resource_slug, authorship_mode, colors_enabled) VALUES (?, ?, ?, ?)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_PAD => {
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND pad_id = ?"#
|
||||
}
|
||||
Query::RESOURCE_COLOR_INSERT => {
|
||||
r#"INSERT INTO user_resource_colors (user_id, resource_kind, resource_slug, color) VALUES (?, ?, ?, ?)"#
|
||||
Query::EDITOR_PREFERENCES_SELECT_NOTE => {
|
||||
r#"SELECT CASE WHEN compact_view THEN 1 ELSE 0 END, CASE WHEN editor_line_numbers THEN 1 ELSE 0 END, CASE WHEN preview_line_numbers THEN 1 ELSE 0 END, CASE WHEN line_links THEN 1 ELSE 0 END, font_family, font_size FROM user_editor_preferences WHERE user_id = ? AND note_id = ?"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_PAD => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, pad_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_UPSERT_NOTE => {
|
||||
r#"INSERT INTO user_editor_preferences (user_id, pad_id, note_id, compact_view, editor_line_numbers, preview_line_numbers, line_links, font_family, font_size, updated_at) VALUES (?, NULL, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, note_id) DO UPDATE SET compact_view = excluded.compact_view, editor_line_numbers = excluded.editor_line_numbers, preview_line_numbers = excluded.preview_line_numbers, line_links = excluded.line_links, font_family = excluded.font_family, font_size = excluded.font_size, updated_at = CURRENT_TIMESTAMP"#
|
||||
}
|
||||
Query::EDITOR_PREFERENCES_DELETE_BY_USER => {
|
||||
r#"DELETE FROM user_editor_preferences WHERE user_id = ?"#
|
||||
}
|
||||
Query::AUTH_ACCOUNT_ACTION_BY_TOKEN => {
|
||||
r#"SELECT user_id, action, payload, expires_at, used_at FROM account_action_tokens WHERE token = ?"#
|
||||
@@ -85,7 +107,7 @@ pub fn get(query: Query) -> &'static str {
|
||||
}
|
||||
Query::AUTH_DELETE_USER => r#"DELETE FROM users WHERE id = ?"#,
|
||||
Query::AUTH_ANONYMIZE_USER => {
|
||||
r#"UPDATE users SET nickname = ?, nickname_key = ?, email = ?, email_key = ?, password_hash = ?, is_active = 0, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, updated_at = ? WHERE id = ?"#
|
||||
r#"UPDATE users SET nickname = ?, nickname_key = ?, email = ?, email_key = ?, password_hash = ?, is_active = 0, auth_provider = 'deleted', external_id = NULL, external_dn = NULL, directory_display_name = NULL, directory_username = NULL, editor_color = NULL, updated_at = ? WHERE id = ?"#
|
||||
}
|
||||
Query::AUTH_NICKNAME_BY_ID => r#"SELECT nickname FROM users WHERE id = ?"#,
|
||||
Query::AUTH_ANONYMIZE_NOTE_CREATORS => {
|
||||
|
||||
Reference in New Issue
Block a user