Skip to content
Draft
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
6 changes: 5 additions & 1 deletion packages/harvest/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2025 Sequent Tech Inc <legal@sequentech.io>
//
// SPDX-License-Identifier: AGPL-3.0-only
//! Harvest REST API
#![recursion_limit = "256"]
#[macro_use]
extern crate rocket;
Expand All @@ -14,8 +15,11 @@ use windmill::services::{
probe::{setup_probe, AppName},
};

/// HTTP routes
mod routes;
/// Authorization and database services
mod services;
/// Types and data structures
mod types;

#[launch]
Expand All @@ -25,7 +29,7 @@ async fn rocket() -> _ {

setup_probe(AppName::HARVEST).await;
set_is_app_active(true);
init_plugin_manager().await.unwrap();
init_plugin_manager().await;

rocket::build()
.register(
Expand Down
34 changes: 29 additions & 5 deletions packages/harvest/src/routes/api_datafix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ use serde::Serialize;
use tracing::{error, info, instrument};
use windmill::services;
use windmill::services::database::{get_hasura_pool, get_keycloak_pool};
use windmill::services::datafix::types::*;
use windmill::services::datafix::types::{
DatafixResponse, JsonErrorResponse, MarkVotedBody, VoterInformationBody,
};

/// Adds a new voter to the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/add-voter", format = "json", data = "<body>")]
pub async fn add_voter(
Expand Down Expand Up @@ -59,6 +64,9 @@ pub async fn add_voter(
.await
}

/// Updates an existing voter's information in the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/update-voter", format = "json", data = "<body>")]
pub async fn update_voter(
Expand Down Expand Up @@ -114,11 +122,16 @@ pub async fn update_voter(
.await
}

/// Request body
#[derive(Deserialize, Debug)]
pub struct VoterIdBody {
/// The unique identifier of the voter
voter_id: String,
}

/// Deletes a voter from the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/delete-voter", format = "json", data = "<body>")]
pub async fn delete_voter(
Expand Down Expand Up @@ -174,6 +187,9 @@ pub async fn delete_voter(
.await
}

/// Unmarks a voter as voted in the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/unmark-voted", format = "json", data = "<body>")]
pub async fn unmark_voted(
Expand Down Expand Up @@ -229,6 +245,9 @@ pub async fn unmark_voted(
.await
}

/// Marks a voter as voted in the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/mark-voted", format = "json", data = "<body>")]
pub async fn mark_voted(
Expand Down Expand Up @@ -284,11 +303,16 @@ pub async fn mark_voted(
.await
}

/// Response containing a replaced PIN for a voter.
#[derive(Serialize, Debug)]
pub struct ReplacePinOutput {
/// The newly generated PIN
pin: String,
}

/// Generates and replaces a voter's PIN in the datafix event.
///
/// Requires `DATAFIX_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/replace-pin", format = "json", data = "<body>")]
pub async fn replace_pin(
Expand All @@ -313,23 +337,23 @@ pub async fn replace_pin(

let mut hasura_db_client: DbClient =
get_hasura_pool().await.get().await.map_err(|e| {
error!("Error getting hasura client {}", e);
error!("Error getting hasura client {e:?}");
DatafixResponse::new(Status::InternalServerError)
})?;
let hasura_transaction =
hasura_db_client.transaction().await.map_err(|e| {
error!("Error starting hasura transaction {}", e);
error!("Error starting hasura transaction {e:?}");
DatafixResponse::new(Status::InternalServerError)
})?;

let mut keycloak_db_client: DbClient =
get_keycloak_pool().await.get().await.map_err(|e| {
error!("Error getting keycloak client {}", e);
error!("Error getting keycloak client {e:?}");
DatafixResponse::new(Status::InternalServerError)
})?;
let keycloak_transaction =
keycloak_db_client.transaction().await.map_err(|e| {
error!("Error starting keycloak transaction {}", e);
error!("Error starting keycloak transaction {e:?}");
DatafixResponse::new(Status::InternalServerError)
})?;

Expand Down
66 changes: 46 additions & 20 deletions packages/harvest/src/routes/applications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,28 @@ use windmill::types::application::{
ApplicationStatus, ApplicationType, ApplicationsError,
};

/// Request body for verifying a user application.
#[derive(Deserialize, Debug)]
pub struct ApplicationVerifyBody {
/// The ID of the applicant
applicant_id: String,
/// The applicant's data as key-value pairs
applicant_data: HashMap<String, String>,
/// The tenant ID
tenant_id: String,
/// The election event ID
election_event_id: String,
/// Optional area ID
area_id: Option<String>,
/// Optional labels for the application
labels: Option<Value>,
/// Application annotations
annotations: ApplicationAnnotations,
}

/// Verifies a user application against criteria.
///
/// Requires `SERVICE_ACCOUNT` permission.
#[instrument(skip(claims))]
#[post("/verify-application", format = "json", data = "<body>")]
pub async fn verify_user_application(
Expand All @@ -66,7 +77,7 @@ pub async fn verify_user_application(
.map_err(|e| {
ErrorResponse::new(
Status::Unauthorized,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::Unauthorized,
)
})?;
Expand All @@ -75,7 +86,7 @@ pub async fn verify_user_application(
get_hasura_pool().await.get().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::InternalServerError,
)
})?;
Expand All @@ -84,7 +95,7 @@ pub async fn verify_user_application(
hasura_db_client.transaction().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
Expand All @@ -93,15 +104,15 @@ pub async fn verify_user_application(
get_keycloak_pool().await.get().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
let keycloak_transaction =
keycloak_db_client.transaction().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
Expand All @@ -120,12 +131,12 @@ pub async fn verify_user_application(
.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::InternalServerError,
)
})?;

let _commit = hasura_transaction.commit().await.map_err(|e| {
hasura_transaction.commit().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("commit failed: {e:?}"),
Expand All @@ -136,24 +147,39 @@ pub async fn verify_user_application(
Ok(Json(result))
}

/// Request body for changing application status.
#[derive(Deserialize, Debug)]
pub struct ApplicationChangeStatusBody {
/// The tenant ID
tenant_id: String,
/// The election event ID
election_event_id: String,
/// Optional area ID
area_id: Option<String>,
/// The application ID
id: String,
/// The user ID associated with the application
user_id: String,
rejection_reason: Option<String>, // Optional for rejection
rejection_message: Option<String>, // Optional for rejection
/// Optional rejection reason
rejection_reason: Option<String>,
/// Optional rejection message
rejection_message: Option<String>,
}

/// Response for application status change operation.
#[derive(Serialize, Deserialize, Debug)]
pub struct ApplicationChangeStatusOutput {
/// Success message
message: Option<String>,
/// Error message if any
error: Option<String>,
}

/// Changes the status of an application.
///
/// Requires `APPLICATION_WRITE` permission.
#[instrument(skip(claims))]
#[allow(clippy::too_many_lines)]
#[post("/change-application-status", format = "json", data = "<body>")]
pub async fn change_application_status(
claims: jwt::JwtClaims,
Expand All @@ -162,7 +188,7 @@ pub async fn change_application_status(
let input = body.into_inner();

info!("Changing application status: {input:?}");
info!("claims::: {:?}", &claims);
info!("claims::: {claims:?}");

let required_perm: Permissions = Permissions::APPLICATION_WRITE;
authorize(
Expand All @@ -174,7 +200,7 @@ pub async fn change_application_status(
.map_err(|e| {
ErrorResponse::new(
Status::Unauthorized,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::Unauthorized,
)
})?;
Expand All @@ -183,7 +209,7 @@ pub async fn change_application_status(
get_hasura_pool().await.get().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error obtaining hasura pool: {:?}", e),
&format!("Error obtaining hasura pool: {e:?}"),
ErrorCode::InternalServerError,
)
})?;
Expand All @@ -192,7 +218,7 @@ pub async fn change_application_status(
hasura_db_client.transaction().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error obtaining transaction: {:?}", e),
&format!("Error obtaining transaction: {e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
Expand All @@ -203,7 +229,7 @@ pub async fn change_application_status(
get_group_names(&tenant_realm, user_id).await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error getting group names: {:#?}", e),
&format!("Error getting group names: {e:#?}"),
ErrorCode::InternalServerError,
)
})?;
Expand All @@ -230,7 +256,7 @@ pub async fn change_application_status(
.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error rejecting application: {:?}", e),
&format!("Error rejecting application: {e:?}"),
ErrorCode::InternalServerError,
)
})?;
Expand All @@ -239,15 +265,15 @@ pub async fn change_application_status(
get_keycloak_pool().await.get().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
let keycloak_transaction =
keycloak_db_client.transaction().await.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("{:?}", e),
&format!("{e:?}"),
ErrorCode::GetTransactionFailed,
)
})?;
Expand All @@ -263,7 +289,7 @@ pub async fn change_application_status(
.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error in check_is_user_verified: {:?}", e),
&format!("Error in check_is_user_verified: {e:?}"),
ErrorCode::InternalServerError,
)
})?;
Expand Down Expand Up @@ -292,7 +318,7 @@ pub async fn change_application_status(
.map_err(|e| {
ErrorResponse::new(
Status::InternalServerError,
&format!("Error confirming application: {:?}", e),
&format!("Error confirming application: {e:?}"),
ErrorCode::InternalServerError,
)
})?;
Expand All @@ -302,7 +328,7 @@ pub async fn change_application_status(
"Invalid request: rejection_reason and rejection_message must either both be present or both absent",
ErrorCode::InternalServerError,
)));
};
}

hasura_transaction.commit().await.map_err(|e| {
ErrorResponse::new(
Expand Down
Loading
Loading