Files
poshmanager/server/models/jobOutputModel.js
2026-06-25 20:57:09 -05:00

77 lines
2.5 KiB
JavaScript

import { config } from '../config.js';
import { db, parseJson } from '../db.js';
import { camelJobOutput } from './mappers.js';
import { isPathInside, path } from '../utils/pathUtils.js';
// Keep output artifact visibility identical to job visibility so logs and
// generated files do not leak across users.
function visibleJobClause() {
return `(
j.triggered_by = ?
OR r.visibility = 'shared'
OR r.owner_user_id = ?
OR r.group_id IN (SELECT group_id FROM user_groups WHERE user_id = ?)
)`;
}
function outputSelect() {
return `
SELECT jo.*, j.status AS job_status, j.runplan_id, j.script_id, j.triggered_by,
r.name AS runplan_name, s.name AS script_name
FROM job_outputs jo
JOIN jobs j ON j.id = jo.job_id
LEFT JOIN runplans r ON r.id = j.runplan_id
LEFT JOIN scripts s ON s.id = j.script_id
`;
}
export function listJobOutputs(userId, isAdmin = false) {
const rows = isAdmin
? db.prepare(`
${outputSelect()}
ORDER BY jo.updated_at DESC
LIMIT 250
`).all()
: db.prepare(`
${outputSelect()}
WHERE ${visibleJobClause()}
ORDER BY jo.updated_at DESC
LIMIT 250
`).all(userId, userId, userId);
return rows.map(camelJobOutput);
}
export function listJobOutputsForJob(jobId, userId, isAdmin = false) {
const rows = isAdmin
? db.prepare(`
${outputSelect()}
WHERE jo.job_id = ?
ORDER BY jo.kind
`).all(jobId)
: db.prepare(`
${outputSelect()}
WHERE jo.job_id = ? AND ${visibleJobClause()}
ORDER BY jo.kind
`).all(jobId, userId, userId, userId);
return rows.map(camelJobOutput);
}
export function getJobOutput(outputId, userId, isAdmin = false) {
const row = isAdmin
? db.prepare(`${outputSelect()} WHERE jo.id = ?`).get(outputId)
: db.prepare(`${outputSelect()} WHERE jo.id = ? AND ${visibleJobClause()}`).get(outputId, userId, userId, userId);
return row ? camelJobOutput(row) : null;
}
export function getJobOutputFile(outputId, userId, isAdmin = false) {
const row = isAdmin
? db.prepare(`${outputSelect()} WHERE jo.id = ?`).get(outputId)
: db.prepare(`${outputSelect()} WHERE jo.id = ? AND ${visibleJobClause()}`).get(outputId, userId, userId, userId);
if (!row) return null;
const filePath = path.resolve(row.storage_path);
if (!isPathInside(config.fileLockerDir, filePath)) {
throw new Error('Job output path is outside the configured FileLocker directory');
}
return { output: camelJobOutput(row), filePath, metadata: parseJson(row.metadata_json, {}) };
}