Job status work
This commit is contained in:
parent
2874d3a885
commit
c138940250
@ -15,7 +15,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::feed_utils::fetch_feed;
|
use crate::feed_utils::fetch_feed;
|
||||||
use crate::feeds::Feed;
|
use crate::feeds::Feed;
|
||||||
use crate::jobs::{JobStatus, SharedJobStore};
|
use crate::jobs::{JobStatus, JobType, SharedJobStore};
|
||||||
use crate::user::AuthenticatedUser;
|
use crate::user::AuthenticatedUser;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ pub async fn import_opml(
|
|||||||
// Create a background job
|
// Create a background job
|
||||||
let job_id = {
|
let job_id = {
|
||||||
let mut store = job_store.write().await;
|
let mut store = job_store.write().await;
|
||||||
store.create_job("opml_import".to_string())
|
store.create_job(JobType::OpmlImport, user.user_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Launch background job
|
// Launch background job
|
||||||
@ -252,3 +252,39 @@ fn extract_feeds(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct JobStatusResponse {
|
||||||
|
status: String,
|
||||||
|
completed: usize,
|
||||||
|
total: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/jobs/<job_id>")]
|
||||||
|
pub async fn get_job_status(
|
||||||
|
job_id: Uuid,
|
||||||
|
job_store: &State<SharedJobStore>,
|
||||||
|
user: AuthenticatedUser,
|
||||||
|
) -> Result<Json<JobStatusResponse>, Status> {
|
||||||
|
let store = job_store.read().await;
|
||||||
|
let status = store
|
||||||
|
.get_job_status(job_id, user.user_id)
|
||||||
|
.ok_or(Status::NotFound)?;
|
||||||
|
|
||||||
|
let response = match status {
|
||||||
|
JobStatus::InProgress { completed, total } => JobStatusResponse {
|
||||||
|
status: "in_progress".to_string(),
|
||||||
|
completed,
|
||||||
|
total,
|
||||||
|
},
|
||||||
|
JobStatus::Completed { success_count } => JobStatusResponse {
|
||||||
|
status: "completed".to_string(),
|
||||||
|
completed: success_count,
|
||||||
|
total: success_count,
|
||||||
|
},
|
||||||
|
JobStatus::Failed(_) => return Err(Status::InternalServerError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
31
src/jobs.rs
31
src/jobs.rs
@ -3,18 +3,30 @@ use std::sync::Arc;
|
|||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum JobType {
|
||||||
|
OpmlImport,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum JobStatus {
|
pub enum JobStatus {
|
||||||
InProgress { completed: usize, total: usize },
|
InProgress {
|
||||||
Completed { success_count: usize },
|
completed: usize,
|
||||||
|
total: usize,
|
||||||
|
},
|
||||||
|
Completed {
|
||||||
|
success_count: usize,
|
||||||
|
},
|
||||||
|
#[allow(dead_code)]
|
||||||
Failed(String),
|
Failed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Job {
|
pub struct Job {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub job_type: String,
|
pub job_type: JobType,
|
||||||
pub status: JobStatus,
|
pub status: JobStatus,
|
||||||
|
pub user_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@ -29,7 +41,7 @@ impl JobStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_job(&mut self, job_type: String) -> Uuid {
|
pub fn create_job(&mut self, job_type: JobType, user_id: Uuid) -> Uuid {
|
||||||
let job_id = Uuid::new_v4();
|
let job_id = Uuid::new_v4();
|
||||||
self.jobs.insert(
|
self.jobs.insert(
|
||||||
job_id,
|
job_id,
|
||||||
@ -40,6 +52,7 @@ impl JobStore {
|
|||||||
completed: 0,
|
completed: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
},
|
},
|
||||||
|
user_id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
job_id
|
job_id
|
||||||
@ -51,8 +64,14 @@ impl JobStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_job_status(&self, job_id: Uuid) -> Option<JobStatus> {
|
pub fn get_job_status(&self, job_id: Uuid, user_id: Uuid) -> Option<JobStatus> {
|
||||||
self.jobs.get(&job_id).map(|job| job.status.clone())
|
self.jobs.get(&job_id).and_then(|job| {
|
||||||
|
if job.user_id == user_id {
|
||||||
|
Some(job.status.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +177,7 @@ fn rocket() -> _ {
|
|||||||
poll::poll_feed,
|
poll::poll_feed,
|
||||||
poll::update_entry_state,
|
poll::update_entry_state,
|
||||||
import::import_opml,
|
import::import_opml,
|
||||||
|
import::get_job_status,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
|
@ -88,9 +88,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showStatusModal(result.message, false);
|
showStatusModal(result.message, false);
|
||||||
// TODO: Poll job status endpoint when implemented
|
if (result.job_id) {
|
||||||
// For now, just refresh the feed list after a delay
|
pollJobStatus(result.job_id);
|
||||||
setTimeout(handleFeeds, 5000);
|
}
|
||||||
} else {
|
} else {
|
||||||
showStatusModal('OPML import failed: ' + result.message, true);
|
showStatusModal('OPML import failed: ' + result.message, true);
|
||||||
}
|
}
|
||||||
@ -106,6 +106,40 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function pollJobStatus(jobId) {
|
||||||
|
const maxAttempts = 30; // 30 attempts * 2 second delay = 1 minute maximum
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
const poll = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/jobs/${jobId}`);
|
||||||
|
if (response.ok) {
|
||||||
|
const status = await response.json();
|
||||||
|
if (status.status === 'completed') {
|
||||||
|
showStatusModal(`Import completed. Successfully imported ${status.completed} feeds.`, false);
|
||||||
|
handleFeeds();
|
||||||
|
return;
|
||||||
|
} else if (status.status === 'in_progress') {
|
||||||
|
showStatusModal(`Importing feeds... ${status.completed}/${status.total} completed`, false);
|
||||||
|
if (attempts++ < maxAttempts) {
|
||||||
|
setTimeout(poll, 2000); // Poll every 2 seconds
|
||||||
|
} else {
|
||||||
|
showStatusModal('Import taking longer than expected. Check feeds list in a few minutes.', false);
|
||||||
|
setTimeout(handleFeeds, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Failed to fetch job status');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to poll job status:', error);
|
||||||
|
showStatusModal('Failed to check import status. Please refresh the page.', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
poll();
|
||||||
|
}
|
||||||
|
|
||||||
// User menu handlers
|
// User menu handlers
|
||||||
userMenuButton.addEventListener('click', (e) => {
|
userMenuButton.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
Loading…
Reference in New Issue
Block a user