mirror of
https://github.com/perstarkse/minne.git
synced 2026-03-20 16:44:12 +01:00
feat: displaying and managing active jobs
This commit is contained in:
@@ -31,9 +31,8 @@ use zettle_db::{
|
||||
admin_panel::{show_admin_panel, toggle_registration_status},
|
||||
documentation::index::show_documentation_index,
|
||||
gdpr::{accept_gdpr, deny_gdpr},
|
||||
index::index_handler,
|
||||
ingress_form::{process_ingress_form, show_ingress_form},
|
||||
ingress_tasks::{delete_task, show_queue_tasks},
|
||||
index::{delete_job, delete_text_content, index_handler},
|
||||
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
||||
privacy_policy::show_privacy_policy,
|
||||
search_result::search_result_handler,
|
||||
signin::{authenticate_user, show_signin_form},
|
||||
@@ -168,8 +167,9 @@ fn html_routes(
|
||||
"/ingress-form",
|
||||
get(show_ingress_form).post(process_ingress_form),
|
||||
)
|
||||
.route("/queue", get(show_queue_tasks))
|
||||
.route("/queue/:delivery_tag", delete(delete_task))
|
||||
.route("/hide-ingress-form", get(hide_ingress_form))
|
||||
.route("/text-content/:id", delete(delete_text_content))
|
||||
.route("/jobs/:job_id", delete(delete_job))
|
||||
.route("/account", get(show_account_page))
|
||||
.route("/admin", get(show_admin_panel))
|
||||
.route("/toggle-registrations", patch(toggle_registration_status))
|
||||
|
||||
@@ -57,6 +57,31 @@ impl JobQueue {
|
||||
Ok(jobs)
|
||||
}
|
||||
|
||||
/// Gets all active jobs for a specific user
|
||||
pub async fn get_unfinished_user_jobs(&self, user_id: &str) -> Result<Vec<Job>, AppError> {
|
||||
let jobs: Vec<Job> = self
|
||||
.db
|
||||
.query(
|
||||
"SELECT * FROM type::table($table)
|
||||
WHERE user_id = $user_id
|
||||
AND (
|
||||
status = 'Created'
|
||||
OR (
|
||||
status.InProgress != NONE
|
||||
AND status.InProgress.attempts < $max_attempts
|
||||
)
|
||||
)
|
||||
ORDER BY created_at DESC",
|
||||
)
|
||||
.bind(("table", Job::table_name()))
|
||||
.bind(("user_id", user_id.to_owned()))
|
||||
.bind(("max_attempts", MAX_ATTEMPTS))
|
||||
.await?
|
||||
.take(0)?;
|
||||
debug!("{:?}", jobs);
|
||||
Ok(jobs)
|
||||
}
|
||||
|
||||
pub async fn delete_job(&self, id: &str, user_id: &str) -> Result<(), AppError> {
|
||||
get_item::<Job>(&self.db.client, id)
|
||||
.await?
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use axum::{extract::State, response::IntoResponse};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use axum_session::Session;
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
@@ -6,17 +9,23 @@ use surrealdb::{engine::any::Any, Surreal};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::HtmlError,
|
||||
error::{AppError, HtmlError},
|
||||
page_data,
|
||||
server::{routes::html::render_template, AppState},
|
||||
storage::types::{text_content::TextContent, user::User},
|
||||
server::{
|
||||
routes::html::{render_block, render_template},
|
||||
AppState,
|
||||
},
|
||||
storage::{
|
||||
db::delete_item,
|
||||
types::{job::Job, text_content::TextContent, user::User},
|
||||
},
|
||||
};
|
||||
|
||||
page_data!(IndexData, "index/index.html", {
|
||||
gdpr_accepted: bool,
|
||||
queue_length: u32,
|
||||
user: Option<User>,
|
||||
latest_text_contents: Vec<TextContent>
|
||||
latest_text_contents: Vec<TextContent>,
|
||||
active_jobs: Vec<Job>
|
||||
});
|
||||
|
||||
pub async fn index_handler(
|
||||
@@ -28,17 +37,16 @@ pub async fn index_handler(
|
||||
|
||||
let gdpr_accepted = auth.current_user.is_some() | session.get("gdpr_accepted").unwrap_or(false);
|
||||
|
||||
let queue_length = match auth.current_user.is_some() {
|
||||
let active_jobs = match auth.current_user.is_some() {
|
||||
true => state
|
||||
.job_queue
|
||||
.get_user_jobs(&auth.current_user.clone().unwrap().id)
|
||||
.get_unfinished_user_jobs(&auth.current_user.clone().unwrap().id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?
|
||||
.len(),
|
||||
false => 0,
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?,
|
||||
false => vec![],
|
||||
};
|
||||
|
||||
let latest_text_contents = match auth.current_user.is_some() {
|
||||
let latest_text_contents = match auth.current_user.clone().is_some() {
|
||||
true => User::get_latest_text_contents(
|
||||
auth.current_user.clone().unwrap().id.as_str(),
|
||||
&state.surreal_db_client,
|
||||
@@ -65,10 +73,90 @@ pub async fn index_handler(
|
||||
let output = render_template(
|
||||
IndexData::template_name(),
|
||||
IndexData {
|
||||
queue_length: queue_length.try_into().unwrap(),
|
||||
gdpr_accepted,
|
||||
user: auth.current_user,
|
||||
latest_text_contents,
|
||||
active_jobs,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LatestTextContentData {
|
||||
latest_text_contents: Vec<TextContent>,
|
||||
user: User,
|
||||
}
|
||||
|
||||
pub async fn delete_text_content(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match &auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/").into_response()),
|
||||
};
|
||||
|
||||
delete_item::<TextContent>(&state.surreal_db_client, &id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
|
||||
let latest_text_contents = User::get_latest_text_contents(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
info!("{:?}", latest_text_contents);
|
||||
|
||||
let output = render_block(
|
||||
"index/signed_in/recent_content.html",
|
||||
"latest_content_section",
|
||||
LatestTextContentData {
|
||||
user: user.clone(),
|
||||
latest_text_contents,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ActiveJobsData {
|
||||
active_jobs: Vec<Job>,
|
||||
user: User,
|
||||
}
|
||||
|
||||
pub async fn delete_job(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
state
|
||||
.job_queue
|
||||
.delete_job(&id, &user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let active_jobs = state
|
||||
.job_queue
|
||||
.get_unfinished_user_jobs(&user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let output = render_block(
|
||||
"index/signed_in/active_jobs.html",
|
||||
"active_jobs_section",
|
||||
ActiveJobsData {
|
||||
user: user.clone(),
|
||||
active_jobs,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
@@ -33,6 +33,19 @@ pub async fn show_ingress_form(
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
pub async fn hide_ingress_form(
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
if !auth.is_authenticated() {
|
||||
return Ok(Redirect::to("/").into_response());
|
||||
}
|
||||
|
||||
Ok(Html(
|
||||
"<a class='btn btn-primary' hx-get='/ingress-form' hx-swap='outerHTML'>Add Content</a>",
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
#[derive(Debug, TryFromMultipart)]
|
||||
pub struct IngressParams {
|
||||
pub content: Option<String>,
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
use crate::{
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::AppState,
|
||||
storage::types::{job::Job, user::User},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{Html, IntoResponse, Redirect},
|
||||
};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use super::render_template;
|
||||
|
||||
page_data!(ShowQueueTasks, "queue_tasks.html", {user : User,jobs: Vec<Job>});
|
||||
|
||||
pub async fn show_queue_tasks(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
let jobs = state
|
||||
.job_queue
|
||||
.get_user_jobs(&user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let rendered = render_template(
|
||||
ShowQueueTasks::template_name(),
|
||||
ShowQueueTasks { jobs, user },
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(rendered.into_response())
|
||||
}
|
||||
|
||||
pub async fn delete_task(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
state
|
||||
.job_queue
|
||||
.delete_job(&id, &user.id)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
Ok(Html("").into_response())
|
||||
}
|
||||
@@ -11,7 +11,6 @@ pub mod documentation;
|
||||
pub mod gdpr;
|
||||
pub mod index;
|
||||
pub mod ingress_form;
|
||||
pub mod ingress_tasks;
|
||||
pub mod privacy_policy;
|
||||
pub mod search_result;
|
||||
pub mod signin;
|
||||
|
||||
Reference in New Issue
Block a user