big dev changes
This commit is contained in:
+98
-5
@@ -53,6 +53,10 @@ pub struct CreateNoteRequest {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
password: Option<String>,
|
||||
#[serde(default)]
|
||||
protect: bool,
|
||||
#[serde(default)]
|
||||
created_by: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -84,6 +88,8 @@ pub struct NoteListItem {
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
url: String,
|
||||
protected: bool,
|
||||
created_by: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -93,6 +99,7 @@ pub struct NoteInfo {
|
||||
slug: String,
|
||||
title: String,
|
||||
protected: bool,
|
||||
note_protected: bool,
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
}
|
||||
@@ -141,6 +148,8 @@ pub async fn open_workspace(
|
||||
title: note.title,
|
||||
created_at: db::normalize_timestamp(¬e.created_at),
|
||||
updated_at: db::normalize_timestamp(¬e.updated_at),
|
||||
protected: note.protected,
|
||||
created_by: note.created_by,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -163,7 +172,8 @@ pub async fn create_note(
|
||||
}
|
||||
|
||||
let slug = unique_note_slug(&state, workspace.id, &base).await?;
|
||||
let note = db::create_note(&state.db, workspace.id, &slug, title).await?;
|
||||
let created_by = payload.created_by.as_deref().map(str::trim).filter(|v| !v.is_empty()).map(|v| v.chars().take(40).collect::<String>());
|
||||
let note = db::create_note(&state.db, workspace.id, &slug, title, payload.protect, created_by.as_deref()).await?;
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(NoteListItem {
|
||||
@@ -172,6 +182,8 @@ pub async fn create_note(
|
||||
title: note.title,
|
||||
created_at: db::normalize_timestamp(¬e.created_at),
|
||||
updated_at: db::normalize_timestamp(¬e.updated_at),
|
||||
protected: note.protected,
|
||||
created_by: note.created_by,
|
||||
}),
|
||||
))
|
||||
}
|
||||
@@ -193,6 +205,7 @@ pub async fn note_info(
|
||||
slug: note.slug,
|
||||
title: note.title,
|
||||
protected: workspace.password_hash.is_some(),
|
||||
note_protected: note.protected,
|
||||
created_at: db::normalize_timestamp(¬e.created_at),
|
||||
updated_at: db::normalize_timestamp(¬e.updated_at),
|
||||
}))
|
||||
@@ -547,8 +560,30 @@ pub async fn upload_pad_file(
|
||||
stored = format!("{stem}-{}{}", db::random_suffix(6), ext);
|
||||
path = dir.join(&stored);
|
||||
}
|
||||
tokio::fs::write(&path, bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": format!("/f/{}/{}", file_token, stored)})))
|
||||
tokio::fs::write(&path, &bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
let url = format!("/f/{}/{}", file_token, stored);
|
||||
let mime = mime_guess::from_path(&stored).first_or_octet_stream().to_string();
|
||||
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})))
|
||||
}
|
||||
|
||||
pub async fn pad_files(
|
||||
State(state): State<SharedState>,
|
||||
Path(slug): Path<String>,
|
||||
Json(payload): Json<PasswordRequest>,
|
||||
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
||||
let pad = authorized_pad(&state, &slug, payload.password.as_deref()).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);
|
||||
if attached != file.is_attached {
|
||||
db::set_pad_file_attached(&state.db, file.id, attached).await?;
|
||||
file.is_attached = attached;
|
||||
file.detached_at = if attached { None } else { Some(chrono::Utc::now().to_rfc3339()) };
|
||||
}
|
||||
file.created_at = db::normalize_timestamp(&file.created_at);
|
||||
}
|
||||
Ok(Json(files))
|
||||
}
|
||||
|
||||
pub async fn upload_note_file(
|
||||
@@ -586,11 +621,69 @@ pub async fn upload_note_file(
|
||||
stored = format!("{stem}-{}{}", db::random_suffix(6), ext);
|
||||
path = dir.join(&stored);
|
||||
}
|
||||
tokio::fs::write(&path, bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
Ok(Json(serde_json::json!({"name": stored, "url": format!("/f/{}/{}", file_token, stored)})))
|
||||
tokio::fs::write(&path, &bytes).await.map_err(|_| ApiError::internal("Failed to save the file"))?;
|
||||
let url = format!("/f/{}/{}", file_token, stored);
|
||||
let mime = mime_guess::from_path(&stored).first_or_octet_stream().to_string();
|
||||
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})))
|
||||
}
|
||||
|
||||
|
||||
pub async fn delete_note(
|
||||
State(state): State<SharedState>,
|
||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||
Json(payload): Json<PasswordRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let (_workspace, note) = authorized_note(&state, &workspace_slug, ¬e_slug, payload.password.as_deref()).await?;
|
||||
if note.protected { return Err(ApiError::bad_request("This note is protected and cannot be deleted")); }
|
||||
db::delete_note(&state.db, note.id).await?;
|
||||
Ok(Json(serde_json::json!({"ok": true})))
|
||||
}
|
||||
|
||||
pub async fn note_files(
|
||||
State(state): State<SharedState>,
|
||||
Path((workspace_slug, note_slug)): Path<(String, String)>,
|
||||
Json(payload): Json<PasswordRequest>,
|
||||
) -> Result<Json<Vec<db::NoteFile>>, ApiError> {
|
||||
let (_workspace, note) = authorized_note(&state, &workspace_slug, ¬e_slug, payload.password.as_deref()).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);
|
||||
if attached != file.is_attached {
|
||||
db::set_note_file_attached(&state.db, file.id, attached).await?;
|
||||
file.is_attached = attached;
|
||||
file.detached_at = if attached { None } else { Some(chrono::Utc::now().to_rfc3339()) };
|
||||
}
|
||||
file.created_at = db::normalize_timestamp(&file.created_at);
|
||||
}
|
||||
Ok(Json(files))
|
||||
}
|
||||
|
||||
pub async fn delete_note_file(
|
||||
State(state): State<SharedState>,
|
||||
Path((workspace_slug, note_slug, file_id)): Path<(String, String, i64)>,
|
||||
Json(payload): Json<PasswordRequest>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let (workspace, note) = authorized_note(&state, &workspace_slug, ¬e_slug, payload.password.as_deref()).await?;
|
||||
if workspace.password_hash.is_none() || payload.password.as_deref().unwrap_or_default().is_empty() {
|
||||
return Err(ApiError::unauthorized());
|
||||
}
|
||||
let file = db::find_note_file(&state.db, note.id, file_id).await?
|
||||
.ok_or_else(ApiError::not_found_file)?;
|
||||
let relative = file.url.trim_start_matches('/').split('/').collect::<Vec<_>>();
|
||||
if relative.len() == 3 && relative[0] == "f" {
|
||||
let directory = format!("{}_{}", note.id, relative[1]);
|
||||
let path = std::path::Path::new(&state.files_dir).join("notes").join(directory).join(sanitize_filename(relative[2]));
|
||||
match tokio::fs::remove_file(&path).await {
|
||||
Ok(()) => {},
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {},
|
||||
Err(_) => return Err(ApiError::internal("Failed to delete the file")),
|
||||
}
|
||||
}
|
||||
db::delete_note_file(&state.db, note.id, file_id).await?;
|
||||
Ok(Json(serde_json::json!({"ok": true})))
|
||||
}
|
||||
|
||||
pub async fn download_file(
|
||||
State(state): State<SharedState>,
|
||||
Path((token, filename)): Path<(String, String)>,
|
||||
|
||||
Reference in New Issue
Block a user