2025-03-12 12:33:05 +01:00

219 lines
7.3 KiB
Python
Executable File

from flask import request, jsonify
from app import db
from app.api import bp
from app.models import PrintJob, Printer, User
from app.auth.routes import token_required, admin_required
from datetime import datetime, timedelta
@bp.route('/jobs', methods=['GET'])
@token_required
def get_jobs():
"""Get jobs for the current user or all jobs for admin"""
is_admin = request.user_role == 'admin'
user_id = request.user_id
# Parse query parameters
status = request.args.get('status') # active, upcoming, completed, aborted, all
printer_id = request.args.get('printer_id')
# Base query
query = PrintJob.query
# Filter by user unless admin
if not is_admin:
query = query.filter_by(user_id=user_id)
# Filter by printer if provided
if printer_id:
query = query.filter_by(printer_id=printer_id)
# Apply status filter
now = datetime.utcnow()
if status == 'active':
query = query.filter_by(aborted=False) \
.filter(PrintJob.start_at <= now) \
.filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now)
elif status == 'upcoming':
query = query.filter_by(aborted=False) \
.filter(PrintJob.start_at > now)
elif status == 'completed':
query = query.filter_by(aborted=False) \
.filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) <= now)
elif status == 'aborted':
query = query.filter_by(aborted=True)
# Order by start time, most recent first
query = query.order_by(PrintJob.start_at.desc())
# Execute query
jobs = query.all()
result = [job.to_dict() for job in jobs]
return jsonify(result)
@bp.route('/jobs/<job_id>', methods=['GET'])
@token_required
def get_job(job_id):
"""Get a specific job"""
job = PrintJob.query.get_or_404(job_id)
# Check permissions
is_admin = request.user_role == 'admin'
user_id = request.user_id
if not is_admin and job.user_id != user_id:
return jsonify({'error': 'Not authorized to view this job'}), 403
return jsonify(job.to_dict())
@bp.route('/jobs', methods=['POST'])
@token_required
def create_job():
"""Create a new print job (reserve a printer)"""
data = request.get_json() or {}
required_fields = ['printer_id', 'start_at', 'duration_in_minutes']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Missing required field: {field}'}), 400
# Validate printer
printer = Printer.query.get(data['printer_id'])
if not printer:
return jsonify({'error': 'Printer not found'}), 404
if printer.status != 0: # Not operational
return jsonify({'error': 'Printer is not operational'}), 400
# Parse start time
try:
start_at = datetime.fromisoformat(data['start_at'].replace('Z', '+00:00'))
except ValueError:
return jsonify({'error': 'Invalid start_at format'}), 400
# Validate duration
try:
duration = int(data['duration_in_minutes'])
if duration <= 0 or duration > 480: # Max 8 hours
return jsonify({'error': 'Invalid duration (must be between 1 and 480 minutes)'}), 400
except ValueError:
return jsonify({'error': 'Duration must be a number'}), 400
end_at = start_at + timedelta(minutes=duration)
# Check if the printer is available during the requested time
conflicting_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(
(PrintJob.start_at < end_at) &
(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > start_at)
) \
.all()
if conflicting_jobs:
return jsonify({'error': 'Printer is not available during the requested time'}), 409
# Create job
job = PrintJob(
printer_id=data['printer_id'],
user_id=request.user_id,
start_at=start_at,
duration_in_minutes=duration,
comments=data.get('comments', '')
)
db.session.add(job)
db.session.commit()
return jsonify(job.to_dict()), 201
@bp.route('/jobs/<job_id>', methods=['PUT'])
@token_required
def update_job(job_id):
"""Update a job"""
job = PrintJob.query.get_or_404(job_id)
# Check permissions
is_admin = request.user_role == 'admin'
user_id = request.user_id
if not is_admin and job.user_id != user_id:
return jsonify({'error': 'Not authorized to update this job'}), 403
data = request.get_json() or {}
# Only allow certain fields to be updated
if 'comments' in data:
job.comments = data['comments']
# Admin or owner can abort a job
if 'aborted' in data and data['aborted'] and not job.aborted:
job.aborted = True
job.abort_reason = data.get('abort_reason', '')
# Admin or owner can extend a job if it's active
now = datetime.utcnow()
is_active = (not job.aborted and
job.start_at <= now and
job.get_end_time() > now)
if 'extend_minutes' in data and is_active:
try:
extend_minutes = int(data['extend_minutes'])
if extend_minutes <= 0 or extend_minutes > 120: # Max extend 2 hours
return jsonify({'error': 'Invalid extension (must be between 1 and 120 minutes)'}), 400
new_end_time = job.get_end_time() + timedelta(minutes=extend_minutes)
# Check for conflicts with the extension
conflicting_jobs = PrintJob.query.filter_by(printer_id=job.printer_id, aborted=False) \
.filter(PrintJob.id != job.id) \
.filter(PrintJob.start_at < new_end_time) \
.filter(PrintJob.start_at > job.get_end_time()) \
.all()
if conflicting_jobs:
return jsonify({'error': 'Cannot extend job due to conflicts with other reservations'}), 409
job.duration_in_minutes += extend_minutes
except ValueError:
return jsonify({'error': 'Extend minutes must be a number'}), 400
db.session.commit()
return jsonify(job.to_dict())
@bp.route('/jobs/<job_id>', methods=['DELETE'])
@token_required
def delete_job(job_id):
"""Delete a job (cancel reservation)"""
job = PrintJob.query.get_or_404(job_id)
# Check permissions
is_admin = request.user_role == 'admin'
user_id = request.user_id
if not is_admin and job.user_id != user_id:
return jsonify({'error': 'Not authorized to delete this job'}), 403
# Only allow deletion of upcoming jobs
now = datetime.utcnow()
if job.start_at <= now and not is_admin:
return jsonify({'error': 'Cannot delete an active or completed job'}), 400
db.session.delete(job)
db.session.commit()
return jsonify({'message': 'Job deleted successfully'})
@bp.route('/jobs/<job_id>/remaining-time', methods=['GET'])
def get_remaining_time(job_id):
"""Get remaining time for a job (public endpoint)"""
job = PrintJob.query.get_or_404(job_id)
remaining_seconds = job.get_remaining_time()
return jsonify({
'job_id': job.id,
'remaining_seconds': remaining_seconds,
'is_active': job.is_active()
})