Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions crates/nvisy-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ use std::process;
use axum::Router;
use nvisy_server::handler::{CustomRoutes, routes};
use nvisy_server::middleware::*;
use nvisy_server::service::ServiceState;
use nvisy_server::worker::WebhookWorker;
use nvisy_server::service::{ServiceState, WebhookWorker};
use tokio_util::sync::CancellationToken;

use crate::config::{Cli, MiddlewareConfig};
Expand Down
16 changes: 5 additions & 11 deletions crates/nvisy-server/src/handler/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use uuid::Uuid;
use super::request::{AccountPathParams, UpdateAccount};
use super::response::{Account, ErrorResponse};
use crate::extract::{AuthState, Json, Path, ValidateJson};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::service::{PasswordHasher, PasswordStrength, ServiceState};

/// Tracing target for account operations.
Expand Down Expand Up @@ -183,11 +183,7 @@ async fn delete_own_account(
let mut conn = pg_client.get_connection().await?;
conn.delete_account(auth_claims.account_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Account not found.")
.with_resource("account")
})?;
.ok_or_else(|| Error::not_found("account"))?;

tracing::info!(target: TRACING_TARGET, "Account deleted");

Expand All @@ -211,11 +207,9 @@ fn build_password_user_inputs<'a>(display_name: &'a str, email_address: &'a str)

/// Finds an account by ID or returns NotFound error.
async fn find_account(conn: &mut PgConn, account_id: Uuid) -> Result<AccountModel> {
conn.find_account_by_id(account_id).await?.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Account not found")
.with_resource("account")
})
conn.find_account_by_id(account_id)
.await?
.ok_or_else(|| Error::not_found("account"))
}

/// Returns a [`Router`] with all related routes.
Expand Down
14 changes: 3 additions & 11 deletions crates/nvisy-server/src/handler/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::handler::request::{
AnnotationPathParams, CreateAnnotation, CursorPagination, FilePathParams, UpdateAnnotation,
};
use crate::handler::response::{Annotation, AnnotationsPage, ErrorResponse};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::service::ServiceState;

/// Tracing target for annotation operations.
Expand All @@ -29,22 +29,14 @@ async fn find_annotation(
) -> Result<WorkspaceFileAnnotation> {
conn.find_workspace_file_annotation_by_id(annotation_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Annotation not found")
.with_resource("annotation")
})
.ok_or_else(|| Error::not_found("annotation"))
}

/// Finds a file by ID or returns NotFound error.
async fn find_file(conn: &mut PgConn, file_id: Uuid) -> Result<WorkspaceFile> {
conn.find_workspace_file_by_id(file_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("File not found")
.with_resource("file")
})
.ok_or_else(|| Error::not_found("file"))
}

/// Creates a new annotation on a file.
Expand Down
7 changes: 3 additions & 4 deletions crates/nvisy-server/src/handler/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ async fn login(
};

// Check for login failures and return appropriate errors
match &account {
let account = match account {
None => {
tracing::warn!(target: TRACING_TARGET, reason = "account_not_found", "Login failed");
return Err(ErrorKind::Unauthorized
Expand All @@ -100,10 +100,9 @@ async fn login(
.with_resource("account")
.with_message("Account has been deleted"));
}
_ => {}
}
Some(acc) => acc,
};

let account = account.unwrap(); // Safe because we verified above
let expired_at = Timestamp::now() + Span::new().hours(90 * 24);
let new_token = NewAccountApiToken {
account_id: account.id,
Expand Down
8 changes: 2 additions & 6 deletions crates/nvisy-server/src/handler/connections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::handler::request::{
WorkspacePathParams,
};
use crate::handler::response::{Connection, ConnectionsPage, ErrorResponse};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::service::crypto::encrypt_json;
use crate::service::{MasterKey, ServiceState};

Expand Down Expand Up @@ -341,11 +341,7 @@ fn delete_connection_docs(op: TransformOperation) -> TransformOperation {
async fn find_connection(conn: &mut PgConn, connection_id: Uuid) -> Result<WorkspaceConnection> {
conn.find_workspace_connection_by_id(connection_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Connection not found")
.with_resource("connection")
})
.ok_or_else(|| Error::not_found("connection"))
}

/// Returns routes for workspace connection management.
Expand Down
8 changes: 2 additions & 6 deletions crates/nvisy-server/src/handler/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use uuid::Uuid;
use crate::extract::{AuthProvider, AuthState, Json, Multipart, Path, Permission, Query};
use crate::handler::request::{ContextPathParams, CursorPagination, WorkspacePathParams};
use crate::handler::response::{Context, ContextsPage, ErrorResponse};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::middleware::DEFAULT_MAX_FILE_BODY_SIZE;
use crate::service::crypto::encrypt;
use crate::service::{MasterKey, ServiceState};
Expand Down Expand Up @@ -461,11 +461,7 @@ fn delete_context_docs(op: TransformOperation) -> TransformOperation {
async fn find_context(conn: &mut PgConn, context_id: Uuid) -> Result<WorkspaceContext> {
conn.find_workspace_context_by_id(context_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Context not found")
.with_resource("context")
})
.ok_or_else(|| Error::not_found("context"))
}

/// Returns routes for workspace context management.
Expand Down
7 changes: 7 additions & 0 deletions crates/nvisy-server/src/handler/error/http_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ impl Error<'static> {
suggestion: None,
}
}

/// Creates a [`NotFound`](ErrorKind::NotFound) error for the given resource.
pub fn not_found(resource: &'static str) -> Self {
Self::new(ErrorKind::NotFound)
.with_message(format!("{resource} not found"))
.with_resource(resource)
}
}

impl<'a> Error<'a> {
Expand Down
33 changes: 18 additions & 15 deletions crates/nvisy-server/src/handler/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use aide::transform::TransformOperation;
use axum::body::Body;
use axum::extract::multipart::Field;
use axum::extract::{DefaultBodyLimit, State};
use axum::http::{HeaderMap, StatusCode};
use axum::http::{HeaderMap, HeaderValue, StatusCode};
use futures::StreamExt;
use nvisy_nats::NatsClient;
use nvisy_nats::object::{FileKey, FilesBucket, ObjectStore};
Expand All @@ -30,7 +30,7 @@ use crate::handler::request::{
CursorPagination, FilePathParams, ListFiles, UpdateFile, WorkspacePathParams,
};
use crate::handler::response::{self, ErrorResponse, File, Files, FilesPage};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::middleware::DEFAULT_MAX_FILE_BODY_SIZE;
use crate::service::{ServiceState, WebhookEmitter};

Expand All @@ -44,11 +44,7 @@ type FileJobPublisher = EventPublisher<FileJob<()>, FileStream>;
async fn find_file(conn: &mut PgConn, file_id: Uuid) -> Result<FileModel> {
conn.find_workspace_file_by_id(file_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("File not found")
.with_resource("file")
})
.ok_or_else(|| Error::not_found("file"))
}

/// Lists files in a workspace with cursor-based pagination.
Expand Down Expand Up @@ -476,15 +472,22 @@ async fn download_file(
ErrorKind::NotFound.with_message("File content not found")
})?;

// Set up response headers
let mut headers = HeaderMap::new();
// Set up response headers.
//
// The display name is user-controlled, so strip characters that are
// invalid in a quoted header value to avoid header injection and a
// failed parse; fall back to a generic disposition if it still fails.
let safe_name: String = file
.display_name
.chars()
.filter(|c| !c.is_control() && *c != '"' && *c != '\\')
.collect();
let disposition = format!("attachment; filename=\"{safe_name}\"")
.parse()
.unwrap_or_else(|_| HeaderValue::from_static("attachment"));

headers.insert(
"content-disposition",
format!("attachment; filename=\"{}\"", file.display_name)
.parse()
.unwrap(),
);
let mut headers = HeaderMap::new();
headers.insert("content-disposition", disposition);
headers.insert(
"content-length",
get_result.size().to_string().parse().unwrap(),
Expand Down
8 changes: 2 additions & 6 deletions crates/nvisy-server/src/handler/webhooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::handler::request::{
use crate::handler::response::{
ErrorResponse, Webhook, WebhookCreated, WebhookResult, WebhooksPage,
};
use crate::handler::{ErrorKind, Result};
use crate::handler::{Error, ErrorKind, Result};
use crate::service::ServiceState;

/// Tracing target for workspace webhook operations.
Expand Down Expand Up @@ -340,11 +340,7 @@ fn test_webhook_docs(op: TransformOperation) -> TransformOperation {
async fn find_webhook(conn: &mut PgConn, webhook_id: Uuid) -> Result<WorkspaceWebhook> {
conn.find_workspace_webhook_by_id(webhook_id)
.await?
.ok_or_else(|| {
ErrorKind::NotFound
.with_message("Webhook not found")
.with_resource("webhook")
})
.ok_or_else(|| Error::not_found("webhook"))
}

/// Returns routes for workspace webhook management.
Expand Down
1 change: 0 additions & 1 deletion crates/nvisy-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ pub mod extract;
pub mod handler;
pub mod middleware;
pub mod service;
pub mod worker;

pub use crate::error::{BoxedError, Error, ErrorKind, Result};
2 changes: 1 addition & 1 deletion crates/nvisy-server/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use crate::service::security::{
MasterKey, MasterKeyConfig, PasswordHasher, PasswordStrength, SessionKeys, SessionKeysConfig,
UserAgentParser,
};
pub use crate::service::webhook::WebhookEmitter;
pub use crate::service::webhook::{WebhookEmitter, WebhookWorker};
use crate::{Error, Result};

/// Application state.
Expand Down
8 changes: 6 additions & 2 deletions crates/nvisy-server/src/service/webhook/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! Webhook event emission service.
//! Webhook event emission and delivery services.
//!
//! Provides helpers for emitting domain events to webhooks via NATS JetStream.
//! Provides helpers for emitting domain events to webhooks via NATS JetStream
//! ([`WebhookEmitter`]) and the background worker that delivers them
//! ([`WebhookWorker`]).

mod emitter;
mod worker;

pub use emitter::WebhookEmitter;
pub use worker::WebhookWorker;
5 changes: 0 additions & 5 deletions crates/nvisy-server/src/worker/mod.rs

This file was deleted.