feat(api): expand API endpoints for repo, PR, user, workspace management
- Add git operation endpoints: archive, compare branches, diff, tree, repository extras - Add repo endpoints: contributors, delete fork, get branch/commit status/deploy key/invitation/member/release/tag/webhook, topics, release assets, webhook deliveries/retry - Add PR endpoints: review requests, templates - Add user endpoints: block/unblock, follow/unfollow, presence, personal access tokens, account restore - Add workspace endpoints: billing history, approvals, domains, integrations, invitations, members, webhooks, restore - Add internal API, notification API, IM API modules - Update route configuration and OpenAPI spec
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Clear all notifications (dismiss all)
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/notifications",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationClearAll",
|
||||
responses(
|
||||
(status = 200, description = "All notifications cleared", body = ApiResponse<i64>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn clear_all_notifications(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service.notify.clear_all_notifications(&session).await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationBlock;
|
||||
use crate::service::AppService;
|
||||
use crate::service::notify::blocks::CreateBlockParams;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Create a notification block
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/notifications/blocks",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationCreateBlock",
|
||||
request_body(
|
||||
content = CreateBlockParams,
|
||||
description = "Block creation parameters",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 201, description = "Block created", body = ApiResponse<NotificationBlock>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn create_block(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
params: web::Json<CreateBlockParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.create_block(&session, params.into_inner())
|
||||
.await?;
|
||||
Ok(HttpResponse::Created().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationSubscription;
|
||||
use crate::service::AppService;
|
||||
use crate::service::notify::subscriptions::CreateSubscriptionParams;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Create a notification subscription
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/notifications/subscriptions",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationCreateSubscription",
|
||||
request_body(
|
||||
content = CreateSubscriptionParams,
|
||||
description = "Subscription creation parameters",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 201, description = "Subscription created", body = ApiResponse<NotificationSubscription>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn create_subscription(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
params: web::Json<CreateSubscriptionParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.create_subscription(&session, params.into_inner())
|
||||
.await?;
|
||||
Ok(HttpResponse::Created().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationTemplate;
|
||||
use crate::service::AppService;
|
||||
use crate::service::notify::templates::CreateTemplateParams;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Create a notification template (requires system admin)
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/notifications/templates",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationCreateTemplate",
|
||||
request_body(
|
||||
content = CreateTemplateParams,
|
||||
description = "Template creation parameters",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 201, description = "Template created", body = ApiResponse<NotificationTemplate>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 403, description = "System admin access required", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn create_template(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
params: web::Json<CreateTemplateParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.create_template(&session, params.into_inner())
|
||||
.await?;
|
||||
Ok(HttpResponse::Created().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub block_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Delete a notification block
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/notifications/blocks/{block_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationDeleteBlock",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Block deleted", body = ApiResponse<String>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Block not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn delete_block(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
service.notify.delete_block(&session, path.block_id).await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new("Block deleted".to_string())))
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub notification_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Delete a notification
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/notifications/{notification_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationDelete",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Notification deleted", body = ApiResponse<String>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Notification not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn delete_notification(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
service
|
||||
.notify
|
||||
.delete_notification(&session, path.notification_id)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new("Notification deleted".to_string())))
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub subscription_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Delete a notification subscription
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/notifications/subscriptions/{subscription_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationDeleteSubscription",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Subscription deleted", body = ApiResponse<String>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Subscription not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn delete_subscription(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
service
|
||||
.notify
|
||||
.delete_subscription(&session, path.subscription_id)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new("Subscription deleted".to_string())))
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub template_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Delete a notification template (requires system admin)
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/notifications/templates/{template_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationDeleteTemplate",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Template deleted", body = ApiResponse<String>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 403, description = "System admin access required", body = ApiErrorResponse),
|
||||
(status = 404, description = "Template not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn delete_template(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
service
|
||||
.notify
|
||||
.delete_template(&session, path.template_id)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new("Template deleted".to_string())))
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::base_info;
|
||||
use crate::models::notifications::NotificationDetail;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub notification_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Dismiss a notification
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/notifications/{notification_id}/dismiss",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationDismiss",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Notification dismissed", body = ApiResponse<NotificationDetail>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Notification not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn dismiss_notification(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let notification = service
|
||||
.notify
|
||||
.dismiss_notification(&session, path.notification_id)
|
||||
.await?;
|
||||
|
||||
let actor = match notification.actor_id {
|
||||
Some(id) => base_info::resolve_users(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
let workspace = match notification.workspace_id {
|
||||
Some(id) => base_info::resolve_workspaces(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
let repo = match notification.repo_id {
|
||||
Some(id) => base_info::resolve_repos(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(
|
||||
notification.into_detail(actor, workspace, repo),
|
||||
)))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationTemplate;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub template_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Get a notification template by ID (requires system admin)
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/templates/{template_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationGetTemplate",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Template retrieved", body = ApiResponse<NotificationTemplate>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 403, description = "System admin access required", body = ApiErrorResponse),
|
||||
(status = 404, description = "Template not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_template(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.get_template(&session, path.template_id)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Get unread notification count for the current user
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/unread-count",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationUnreadCount",
|
||||
responses(
|
||||
(status = 200, description = "Unread count returned successfully", body = ApiResponse<i64>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_unread_count(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service.notify.count_unread(&session).await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationBlock;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List notification blocks for the current user
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/blocks",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationListBlocks",
|
||||
params(QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Blocks listed successfully", body = ApiResponse<Vec<NotificationBlock>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_blocks(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.list_blocks(
|
||||
&session,
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationDelivery;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List notification deliveries for the current user
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/deliveries",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationListDeliveries",
|
||||
params(QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Deliveries listed successfully", body = ApiResponse<Vec<NotificationDelivery>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_deliveries(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.list_deliveries(
|
||||
&session,
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationDelivery;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub notification_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List deliveries for a specific notification
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/{notification_id}/deliveries",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationListDeliveriesForNotification",
|
||||
params(PathParams, QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Deliveries listed successfully", body = ApiResponse<Vec<NotificationDelivery>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Notification not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_deliveries_for_notification(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.list_deliveries_for_notification(
|
||||
&session,
|
||||
path.notification_id,
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::base_info;
|
||||
use crate::models::notifications::NotificationDetail;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub unread_only: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List notifications for the current user
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationList",
|
||||
params(QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Notifications listed successfully", body = ApiResponse<Vec<NotificationDetail>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_notifications(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let notifications = service
|
||||
.notify
|
||||
.list_notifications(
|
||||
&session,
|
||||
query.unread_only.unwrap_or(false),
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let actor_ids: Vec<_> = notifications.iter().filter_map(|n| n.actor_id).collect();
|
||||
let workspace_ids: Vec<_> = notifications
|
||||
.iter()
|
||||
.filter_map(|n| n.workspace_id)
|
||||
.collect();
|
||||
let repo_ids: Vec<_> = notifications.iter().filter_map(|n| n.repo_id).collect();
|
||||
|
||||
let actors = base_info::resolve_users(&service.ctx.db, &actor_ids).await?;
|
||||
let workspaces = base_info::resolve_workspaces(&service.ctx.db, &workspace_ids).await?;
|
||||
let repos = base_info::resolve_repos(&service.ctx.db, &repo_ids).await?;
|
||||
|
||||
let details: Vec<NotificationDetail> = notifications
|
||||
.into_iter()
|
||||
.map(|n| {
|
||||
let actor = n.actor_id.and_then(|id| actors.get(&id).cloned());
|
||||
let workspace = n.workspace_id.and_then(|id| workspaces.get(&id).cloned());
|
||||
let repo = n.repo_id.and_then(|id| repos.get(&id).cloned());
|
||||
n.into_detail(actor, workspace, repo)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(details)))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationSubscription;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List notification subscriptions for the current user
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/subscriptions",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationListSubscriptions",
|
||||
params(QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Subscriptions listed successfully", body = ApiResponse<Vec<NotificationSubscription>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_subscriptions(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.list_subscriptions(
|
||||
&session,
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationTemplate;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct QueryParams {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
}
|
||||
|
||||
/// List notification templates (requires system admin)
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/notifications/templates",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationListTemplates",
|
||||
params(QueryParams),
|
||||
responses(
|
||||
(status = 200, description = "Templates listed successfully", body = ApiResponse<Vec<NotificationTemplate>>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 403, description = "System admin access required", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn list_templates(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
query: web::Query<QueryParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.list_templates(
|
||||
&session,
|
||||
query.limit.unwrap_or(50),
|
||||
query.offset.unwrap_or(0),
|
||||
)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
/// Mark all notifications as read
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/notifications/read-all",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationMarkAllAsRead",
|
||||
responses(
|
||||
(status = 200, description = "All notifications marked as read", body = ApiResponse<i64>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn mark_all_as_read(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service.notify.mark_all_as_read(&session).await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::base_info;
|
||||
use crate::models::notifications::NotificationDetail;
|
||||
use crate::service::AppService;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub notification_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Mark a notification as read
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/notifications/{notification_id}/read",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationMarkAsRead",
|
||||
params(PathParams),
|
||||
responses(
|
||||
(status = 200, description = "Notification marked as read", body = ApiResponse<NotificationDetail>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Notification not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn mark_as_read(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let notification = service
|
||||
.notify
|
||||
.mark_as_read(&session, path.notification_id)
|
||||
.await?;
|
||||
|
||||
let actor = match notification.actor_id {
|
||||
Some(id) => base_info::resolve_users(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
let workspace = match notification.workspace_id {
|
||||
Some(id) => base_info::resolve_workspaces(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
let repo = match notification.repo_id {
|
||||
Some(id) => base_info::resolve_repos(&service.ctx.db, &[id])
|
||||
.await?
|
||||
.remove(&id),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(
|
||||
notification.into_detail(actor, workspace, repo),
|
||||
)))
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
pub mod clear_all_notifications;
|
||||
pub mod create_block;
|
||||
pub mod create_subscription;
|
||||
pub mod create_template;
|
||||
pub mod delete_block;
|
||||
pub mod delete_notification;
|
||||
pub mod delete_subscription;
|
||||
pub mod delete_template;
|
||||
pub mod dismiss_notification;
|
||||
pub mod get_template;
|
||||
pub mod get_unread_count;
|
||||
pub mod list_blocks;
|
||||
pub mod list_deliveries;
|
||||
pub mod list_deliveries_for_notification;
|
||||
pub mod list_notifications;
|
||||
pub mod list_subscriptions;
|
||||
pub mod list_templates;
|
||||
pub mod mark_all_as_read;
|
||||
pub mod mark_as_read;
|
||||
pub mod update_subscription;
|
||||
pub mod update_template;
|
||||
|
||||
use actix_web::web;
|
||||
|
||||
/// Configure notification routes under `/api/v1/notifications`
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("/notifications")
|
||||
// Non-parameterized paths first
|
||||
.route("", web::get().to(list_notifications::list_notifications))
|
||||
.route(
|
||||
"",
|
||||
web::delete().to(clear_all_notifications::clear_all_notifications),
|
||||
)
|
||||
.route(
|
||||
"/unread-count",
|
||||
web::get().to(get_unread_count::get_unread_count),
|
||||
)
|
||||
.route(
|
||||
"/read-all",
|
||||
web::put().to(mark_all_as_read::mark_all_as_read),
|
||||
)
|
||||
// Parameterized notification paths
|
||||
.route(
|
||||
"/{notification_id}",
|
||||
web::delete().to(delete_notification::delete_notification),
|
||||
)
|
||||
.route(
|
||||
"/{notification_id}/read",
|
||||
web::put().to(mark_as_read::mark_as_read),
|
||||
)
|
||||
.route(
|
||||
"/{notification_id}/dismiss",
|
||||
web::post().to(dismiss_notification::dismiss_notification),
|
||||
)
|
||||
.route(
|
||||
"/{notification_id}/deliveries",
|
||||
web::get().to(list_deliveries_for_notification::list_deliveries_for_notification),
|
||||
)
|
||||
// Subscriptions
|
||||
.route(
|
||||
"/subscriptions",
|
||||
web::get().to(list_subscriptions::list_subscriptions),
|
||||
)
|
||||
.route(
|
||||
"/subscriptions",
|
||||
web::post().to(create_subscription::create_subscription),
|
||||
)
|
||||
.route(
|
||||
"/subscriptions/{subscription_id}",
|
||||
web::put().to(update_subscription::update_subscription),
|
||||
)
|
||||
.route(
|
||||
"/subscriptions/{subscription_id}",
|
||||
web::delete().to(delete_subscription::delete_subscription),
|
||||
)
|
||||
// Blocks
|
||||
.route("/blocks", web::get().to(list_blocks::list_blocks))
|
||||
.route("/blocks", web::post().to(create_block::create_block))
|
||||
.route(
|
||||
"/blocks/{block_id}",
|
||||
web::delete().to(delete_block::delete_block),
|
||||
)
|
||||
// Deliveries
|
||||
.route(
|
||||
"/deliveries",
|
||||
web::get().to(list_deliveries::list_deliveries),
|
||||
)
|
||||
// Templates
|
||||
.route("/templates", web::get().to(list_templates::list_templates))
|
||||
.route(
|
||||
"/templates",
|
||||
web::post().to(create_template::create_template),
|
||||
)
|
||||
.route(
|
||||
"/templates/{template_id}",
|
||||
web::get().to(get_template::get_template),
|
||||
)
|
||||
.route(
|
||||
"/templates/{template_id}",
|
||||
web::put().to(update_template::update_template),
|
||||
)
|
||||
.route(
|
||||
"/templates/{template_id}",
|
||||
web::delete().to(delete_template::delete_template),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationSubscription;
|
||||
use crate::service::AppService;
|
||||
use crate::service::notify::subscriptions::UpdateSubscriptionParams;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub subscription_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Update a notification subscription
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/notifications/subscriptions/{subscription_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationUpdateSubscription",
|
||||
params(PathParams),
|
||||
request_body(
|
||||
content = UpdateSubscriptionParams,
|
||||
description = "Subscription update parameters",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Subscription updated", body = ApiResponse<NotificationSubscription>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 404, description = "Subscription not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn update_subscription(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
params: web::Json<UpdateSubscriptionParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.update_subscription(&session, path.subscription_id, params.into_inner())
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::api::response::{ApiErrorResponse, ApiResponse};
|
||||
use crate::error::AppError;
|
||||
use crate::models::notifications::NotificationTemplate;
|
||||
use crate::service::AppService;
|
||||
use crate::service::notify::templates::UpdateTemplateParams;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PathParams {
|
||||
pub template_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
/// Update a notification template (requires system admin)
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/notifications/templates/{template_id}",
|
||||
tag = "Notifications",
|
||||
operation_id = "notificationUpdateTemplate",
|
||||
params(PathParams),
|
||||
request_body(
|
||||
content = UpdateTemplateParams,
|
||||
description = "Template update parameters",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Template updated", body = ApiResponse<NotificationTemplate>),
|
||||
(status = 401, description = "Authentication required or session expired", body = ApiErrorResponse),
|
||||
(status = 403, description = "System admin access required", body = ApiErrorResponse),
|
||||
(status = 404, description = "Template not found", body = ApiErrorResponse),
|
||||
(status = 500, description = "Internal server error", body = ApiErrorResponse),
|
||||
),
|
||||
security(
|
||||
("session_cookie" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn update_template(
|
||||
service: web::Data<AppService>,
|
||||
session: Session,
|
||||
path: web::Path<PathParams>,
|
||||
params: web::Json<UpdateTemplateParams>,
|
||||
) -> Result<HttpResponse, AppError> {
|
||||
let result = service
|
||||
.notify
|
||||
.update_template(&session, path.template_id, params.into_inner())
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(ApiResponse::new(result)))
|
||||
}
|
||||
Reference in New Issue
Block a user