Fixed Sharing Issue and added Shared Drive Folder
This commit is contained in:
+78
-2
@@ -8,7 +8,7 @@ use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
models::{Collaborator, Comment, CreateCommentRequest, Invitation, InviteRequest, UpdateCommentRequest},
|
||||
models::{Collaborator, CollaboratorView, Comment, CreateCommentRequest, Invitation, InviteRequest, UpdateCommentRequest},
|
||||
AppState,
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ pub async fn invite_collaborator(
|
||||
return Err((StatusCode::FORBIDDEN, "Only the owner can invite collaborators".to_string()));
|
||||
}
|
||||
|
||||
let invited_user = sqlx::query_as::<_, crate::models::User>("SELECT id, username, email, password_hash FROM users WHERE email = ?")
|
||||
let invited_user = sqlx::query_as::<_, crate::models::User>("SELECT id, username, email, password_hash, is_admin FROM users WHERE email = ?")
|
||||
.bind(&payload.email)
|
||||
.fetch_optional(&state.db)
|
||||
.await
|
||||
@@ -103,6 +103,82 @@ pub async fn accept_invite(
|
||||
Ok(Json(collab))
|
||||
}
|
||||
|
||||
pub async fn list_collaborators(
|
||||
State(state): State<AppState>,
|
||||
Path(doc_id): Path<String>,
|
||||
jar: SignedCookieJar,
|
||||
) -> Result<Json<Vec<CollaboratorView>>, (StatusCode, String)> {
|
||||
let user_id = jar.get("session_user_id").map(|c| c.value().to_string())
|
||||
.ok_or((StatusCode::UNAUTHORIZED, "Not logged in".to_string()))?;
|
||||
|
||||
// Only owner or collaborators on the document can see the list
|
||||
let has_access = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM documents WHERE id = ? AND owner_id = ? \
|
||||
UNION ALL SELECT COUNT(*) FROM collaborators WHERE document_id = ? AND user_id = ?"
|
||||
)
|
||||
.bind(&doc_id).bind(&user_id).bind(&doc_id).bind(&user_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||
.into_iter().sum::<i64>() > 0;
|
||||
|
||||
if !has_access {
|
||||
return Err((StatusCode::FORBIDDEN, "Access denied".to_string()));
|
||||
}
|
||||
|
||||
let collaborators = sqlx::query_as::<_, CollaboratorView>(
|
||||
"SELECT c.id, c.user_id, u.username, u.email, c.role, c.created_at \
|
||||
FROM collaborators c \
|
||||
INNER JOIN users u ON u.id = c.user_id \
|
||||
WHERE c.document_id = ? \
|
||||
ORDER BY c.created_at ASC"
|
||||
)
|
||||
.bind(&doc_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(collaborators))
|
||||
}
|
||||
|
||||
pub async fn remove_collaborator(
|
||||
State(state): State<AppState>,
|
||||
Path((doc_id, collab_id)): Path<(String, String)>,
|
||||
jar: SignedCookieJar,
|
||||
) -> Result<StatusCode, (StatusCode, String)> {
|
||||
let user_id = jar.get("session_user_id").map(|c| c.value().to_string())
|
||||
.ok_or((StatusCode::UNAUTHORIZED, "Not logged in".to_string()))?;
|
||||
|
||||
// Only the document owner can remove collaborators
|
||||
let is_owner = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM documents WHERE id = ? AND owner_id = ?"
|
||||
)
|
||||
.bind(&doc_id)
|
||||
.bind(&user_id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? > 0;
|
||||
|
||||
if !is_owner {
|
||||
return Err((StatusCode::FORBIDDEN, "Only the document owner can remove collaborators".to_string()));
|
||||
}
|
||||
|
||||
let result = sqlx::query(
|
||||
"DELETE FROM collaborators WHERE id = ? AND document_id = ?"
|
||||
)
|
||||
.bind(&collab_id)
|
||||
.bind(&doc_id)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err((StatusCode::NOT_FOUND, "Collaborator not found".to_string()));
|
||||
}
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
pub async fn get_comments(
|
||||
State(state): State<AppState>,
|
||||
Path(doc_id): Path<String>,
|
||||
|
||||
@@ -17,6 +17,28 @@ pub struct ListDocsQuery {
|
||||
pub folder_id: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list_shared_documents(
|
||||
State(state): State<AppState>,
|
||||
jar: SignedCookieJar,
|
||||
) -> Result<Json<Vec<Document>>, (StatusCode, String)> {
|
||||
let user_id = jar.get("session_user_id").map(|c| c.value().to_string())
|
||||
.ok_or((StatusCode::UNAUTHORIZED, "Not logged in".to_string()))?;
|
||||
|
||||
let docs = sqlx::query_as::<_, Document>(
|
||||
"SELECT d.id, d.owner_id, d.folder_id, d.title, d.content, d.thumbnail_svg, \
|
||||
d.public_role, d.created_at, d.updated_at, c.role as effective_role \
|
||||
FROM documents d \
|
||||
INNER JOIN collaborators c ON c.document_id = d.id AND c.user_id = ? \
|
||||
ORDER BY d.updated_at DESC"
|
||||
)
|
||||
.bind(&user_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(docs))
|
||||
}
|
||||
|
||||
pub async fn list_documents(
|
||||
axum::extract::Query(query): axum::extract::Query<ListDocsQuery>,
|
||||
State(state): State<AppState>,
|
||||
|
||||
+27
-2
@@ -208,7 +208,7 @@ pub async fn compile_handler(
|
||||
|
||||
if has_access {
|
||||
if let Ok(files) = sqlx::query_as::<_, (String, Vec<u8>)>("SELECT name, data FROM files WHERE owner_id = ?")
|
||||
.bind(doc.owner_id)
|
||||
.bind(&doc.owner_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
{
|
||||
@@ -216,6 +216,19 @@ pub async fn compile_handler(
|
||||
files_map.insert(name, data);
|
||||
}
|
||||
}
|
||||
// Also include files uploaded by collaborators specifically for this document
|
||||
if let Ok(collab_files) = sqlx::query_as::<_, (String, Vec<u8>)>(
|
||||
"SELECT name, data FROM files WHERE document_id = ? AND owner_id != ?"
|
||||
)
|
||||
.bind(doc_id)
|
||||
.bind(&doc.owner_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
{
|
||||
for (name, data) in collab_files {
|
||||
files_map.insert(name, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,7 +311,7 @@ pub async fn export_handler(
|
||||
|
||||
if has_access {
|
||||
if let Ok(files) = sqlx::query_as::<_, (String, Vec<u8>)>("SELECT name, data FROM files WHERE owner_id = ?")
|
||||
.bind(doc.owner_id)
|
||||
.bind(&doc.owner_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
{
|
||||
@@ -306,6 +319,18 @@ pub async fn export_handler(
|
||||
files_map.insert(name, data);
|
||||
}
|
||||
}
|
||||
if let Ok(collab_files) = sqlx::query_as::<_, (String, Vec<u8>)>(
|
||||
"SELECT name, data FROM files WHERE document_id = ? AND owner_id != ?"
|
||||
)
|
||||
.bind(doc_id)
|
||||
.bind(&doc.owner_id)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
{
|
||||
for (name, data) in collab_files {
|
||||
files_map.insert(name, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +113,13 @@ async fn main() {
|
||||
.route("/files", get(files::list_files).post(files::upload_file_global))
|
||||
.route("/files/{id}", delete(files::delete_file).patch(files::update_file))
|
||||
.route("/files/{id}/data", get(files::get_file_data))
|
||||
.route("/docs/shared", get(docs::list_shared_documents))
|
||||
.route("/docs", get(docs::list_documents).post(docs::create_document))
|
||||
.route("/docs/accept-invite", get(collab::accept_invite))
|
||||
.route("/docs/{id}", get(docs::get_document).delete(docs::delete_document).patch(docs::update_document))
|
||||
.route("/docs/{id}/files", post(docs::upload_file))
|
||||
.route("/docs/{id}/collaborators", get(collab::list_collaborators))
|
||||
.route("/docs/{id}/collaborators/{collab_id}", delete(collab::remove_collaborator))
|
||||
.route("/docs/{id}/invite", post(collab::invite_collaborator))
|
||||
.route("/docs/{id}/comments", get(collab::get_comments).post(collab::add_comment))
|
||||
.route("/docs/{id}/versions", get(collab::get_versions).post(collab::create_version))
|
||||
|
||||
@@ -133,6 +133,16 @@ pub struct Collaborator {
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct CollaboratorView {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub role: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct Invitation {
|
||||
pub id: String,
|
||||
|
||||
Reference in New Issue
Block a user