mirror of
https://github.com/C4illin/ConvertX.git
synced 2026-03-03 02:57:01 +00:00
@@ -1,38 +1,115 @@
|
||||
import { rmSync } from "node:fs";
|
||||
import { Elysia } from "elysia";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { outputDir, uploadsDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
import { Jobs } from "../db/types";
|
||||
|
||||
export const deleteJob = new Elysia().use(userService).get(
|
||||
"/delete/:jobId",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
export const deleteJob = new Elysia()
|
||||
.use(userService)
|
||||
.get(
|
||||
"/delete/:jobId",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
return redirect(`${WEBROOT}/history`, 302);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
return redirect(`${WEBROOT}/history`, 302);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/delete-multiple",
|
||||
async ({ body, user, set }) => {
|
||||
const { jobIds } = body;
|
||||
|
||||
if (!Array.isArray(jobIds) || jobIds.length === 0) {
|
||||
set.status = 400;
|
||||
return { success: false, message: "Invalid job IDs provided" };
|
||||
}
|
||||
|
||||
const results = {
|
||||
success: [] as string[],
|
||||
failed: [] as { jobId: string; error: string }[],
|
||||
};
|
||||
|
||||
// Process deletions sequentially for safety
|
||||
for (const jobId of jobIds) {
|
||||
try {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, jobId);
|
||||
|
||||
if (!job) {
|
||||
results.failed.push({
|
||||
jobId,
|
||||
error: "Job not found or unauthorized",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete the directories
|
||||
try {
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete output directory for job ${jobId}:`, error);
|
||||
}
|
||||
|
||||
try {
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete uploads directory for job ${jobId}:`, error);
|
||||
}
|
||||
|
||||
// Delete the job from database
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
results.success.push(jobId);
|
||||
} catch (error) {
|
||||
results.failed.push({
|
||||
jobId,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: results.failed.length === 0,
|
||||
deleted: results.success.length,
|
||||
failed: results.failed.length,
|
||||
details: results,
|
||||
};
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
body: t.Object({
|
||||
jobIds: t.Array(t.String(), { maxItems: 100 }),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -47,7 +47,24 @@ export const history = new Elysia().use(userService).get(
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Results</h1>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<div id="delete-selected-container">
|
||||
<button
|
||||
id="delete-selected-btn"
|
||||
class={`
|
||||
flex btn-secondary flex-row gap-2 text-contrast
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
disabled
|
||||
>
|
||||
<DeleteIcon />{" "}
|
||||
<span>
|
||||
Delete Selected (<span id="selected-count">0</span>)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
@@ -57,6 +74,19 @@ export const history = new Elysia().use(userService).get(
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select-all"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
title="Select all"
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
@@ -112,6 +142,14 @@ export const history = new Elysia().use(userService).get(
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 cursor-pointer"
|
||||
data-checkbox-type="job"
|
||||
data-job-id={job.id}
|
||||
/>
|
||||
</td>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
@@ -159,7 +197,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="6">
|
||||
<td colspan="7">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
@@ -196,26 +234,97 @@ export const history = new Elysia().use(userService).get(
|
||||
<script>
|
||||
{`
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Expand/collapse job details
|
||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
const jobId = this.dataset.jobId;
|
||||
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
||||
// The arrow SVG itself has the ID arrow-\${jobId}
|
||||
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
||||
|
||||
if (detailsRow && arrow) {
|
||||
detailsRow.classList.toggle("hidden");
|
||||
if (detailsRow.classList.contains("hidden")) {
|
||||
// Right-facing arrow (collapsed)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
||||
} else {
|
||||
// Down-facing arrow (expanded)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Checkbox management
|
||||
const selectAllCheckbox = document.getElementById('select-all');
|
||||
const jobCheckboxes = document.querySelectorAll('[data-checkbox-type="job"]');
|
||||
const deleteSelectedBtn = document.getElementById('delete-selected-btn');
|
||||
const deleteSelectedContainer = document.getElementById('delete-selected-container');
|
||||
const selectedCountSpan = document.getElementById('selected-count');
|
||||
|
||||
function updateDeleteButton() {
|
||||
const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
|
||||
if (checkedBoxes.length > 0) {
|
||||
deleteSelectedBtn.disabled = false;
|
||||
selectedCountSpan.textContent = checkedBoxes.length;
|
||||
} else {
|
||||
deleteSelectedBtn.disabled = true;
|
||||
selectedCountSpan.textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
selectAllCheckbox?.addEventListener('change', function() {
|
||||
jobCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
});
|
||||
updateDeleteButton();
|
||||
});
|
||||
|
||||
jobCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const allChecked = Array.from(jobCheckboxes).every(cb => cb.checked);
|
||||
const someChecked = Array.from(jobCheckboxes).some(cb => cb.checked);
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = allChecked;
|
||||
selectAllCheckbox.indeterminate = someChecked && !allChecked;
|
||||
}
|
||||
updateDeleteButton();
|
||||
});
|
||||
});
|
||||
|
||||
deleteSelectedBtn?.addEventListener('click', async function() {
|
||||
const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
|
||||
const jobIds = checkedBoxes.map(cb => cb.dataset.jobId);
|
||||
|
||||
if (jobIds.length === 0) return;
|
||||
|
||||
const confirmed = confirm(\`Are you sure you want to delete \${jobIds.length} job(s)? This action cannot be undone.\`);
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('${WEBROOT}/delete-multiple', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ jobIds }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(\`HTTP error! status: \${response.status}\`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success || result.deleted > 0) {
|
||||
alert(\`Successfully deleted \${result.deleted} job(s).\${result.failed > 0 ? \` Failed to delete \${result.failed} job(s).\` : ''}\`);
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Failed to delete jobs. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting jobs:', error);
|
||||
alert('An error occurred while deleting jobs. Please try again.');
|
||||
}
|
||||
});
|
||||
});
|
||||
`}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user