ein-dateien backend erstellt

This commit is contained in:
2025-03-07 20:58:34 +01:00
parent 68a1910bdc
commit 55936c81f0
45 changed files with 2070 additions and 0 deletions

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('api', __name__)
from app.api import printers, jobs, users

View File

@ -0,0 +1,219 @@
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()
})

View File

@ -0,0 +1,177 @@
from flask import request, jsonify
from app import db
from app.api import bp
from app.models import Printer, PrintJob
from app.auth.routes import token_required, admin_required
from datetime import datetime
@bp.route('/printers', methods=['GET'])
def get_printers():
"""Get all printers"""
printers = Printer.query.all()
result = []
for printer in printers:
# Get active job for the printer if any
now = datetime.utcnow()
active_job = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(PrintJob.start_at <= now) \
.filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \
.first()
printer_data = {
'id': printer.id,
'name': printer.name,
'description': printer.description,
'status': printer.status,
'is_available': printer.status == 0 and active_job is None,
'active_job': active_job.to_dict() if active_job else None
}
result.append(printer_data)
return jsonify(result)
@bp.route('/printers/<printer_id>', methods=['GET'])
def get_printer(printer_id):
"""Get a specific printer"""
printer = Printer.query.get_or_404(printer_id)
# Get active job for the printer if any
now = datetime.utcnow()
active_job = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(PrintJob.start_at <= now) \
.filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \
.first()
# Get upcoming jobs
upcoming_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(PrintJob.start_at > now) \
.order_by(PrintJob.start_at) \
.limit(5) \
.all()
result = {
'id': printer.id,
'name': printer.name,
'description': printer.description,
'status': printer.status,
'is_available': printer.status == 0 and active_job is None,
'active_job': active_job.to_dict() if active_job else None,
'upcoming_jobs': [job.to_dict() for job in upcoming_jobs]
}
return jsonify(result)
@bp.route('/printers', methods=['POST'])
@admin_required
def create_printer():
"""Create a new printer (admin only)"""
data = request.get_json() or {}
required_fields = ['name', 'description']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Missing required field: {field}'}), 400
printer = Printer(
name=data['name'],
description=data['description'],
status=data.get('status', 0)
)
db.session.add(printer)
db.session.commit()
return jsonify({
'id': printer.id,
'name': printer.name,
'description': printer.description,
'status': printer.status
}), 201
@bp.route('/printers/<printer_id>', methods=['PUT'])
@admin_required
def update_printer(printer_id):
"""Update a printer (admin only)"""
printer = Printer.query.get_or_404(printer_id)
data = request.get_json() or {}
if 'name' in data:
printer.name = data['name']
if 'description' in data:
printer.description = data['description']
if 'status' in data:
printer.status = data['status']
db.session.commit()
return jsonify({
'id': printer.id,
'name': printer.name,
'description': printer.description,
'status': printer.status
})
@bp.route('/printers/<printer_id>', methods=['DELETE'])
@admin_required
def delete_printer(printer_id):
"""Delete a printer (admin only)"""
printer = Printer.query.get_or_404(printer_id)
# Check if the printer has active jobs
now = datetime.utcnow()
active_jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(PrintJob.start_at <= now) \
.filter(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) > now) \
.all()
if active_jobs:
return jsonify({'error': 'Cannot delete printer with active jobs'}), 400
db.session.delete(printer)
db.session.commit()
return jsonify({'message': 'Printer deleted successfully'})
@bp.route('/printers/availability', methods=['GET'])
def get_availability():
"""Get availability information for all printers"""
start_date = request.args.get('start_date')
end_date = request.args.get('end_date')
if not start_date or not end_date:
return jsonify({'error': 'start_date and end_date are required'}), 400
try:
start = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
end = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
except ValueError:
return jsonify({'error': 'Invalid date format'}), 400
if start >= end:
return jsonify({'error': 'start_date must be before end_date'}), 400
printers = Printer.query.all()
result = []
for printer in printers:
# Get all jobs for this printer in the date range
jobs = PrintJob.query.filter_by(printer_id=printer.id, aborted=False) \
.filter(
(PrintJob.start_at <= end) &
(PrintJob.start_at.op('+')(PrintJob.duration_in_minutes * 60) >= start)
) \
.order_by(PrintJob.start_at) \
.all()
# Convert to availability slots
availability = {
'printer_id': printer.id,
'printer_name': printer.name,
'status': printer.status,
'jobs': [job.to_dict() for job in jobs]
}
result.append(availability)
return jsonify(result)

View File

@ -0,0 +1,139 @@
from flask import request, jsonify
from app import db
from app.api import bp
from app.models import User, PrintJob
from app.auth.routes import admin_required, token_required
@bp.route('/users', methods=['GET'])
@admin_required
def get_users():
"""Get all users (admin only)"""
users = User.query.all()
result = []
for user in users:
# Count jobs
total_jobs = PrintJob.query.filter_by(user_id=user.id).count()
active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).count()
user_data = {
'id': user.id,
'github_id': user.github_id,
'username': user.username,
'display_name': user.display_name,
'email': user.email,
'role': user.role,
'job_count': total_jobs,
'active_job_count': active_jobs
}
result.append(user_data)
return jsonify(result)
@bp.route('/users/<user_id>', methods=['GET'])
@admin_required
def get_user(user_id):
"""Get a specific user (admin only)"""
user = User.query.get_or_404(user_id)
# Count jobs
total_jobs = PrintJob.query.filter_by(user_id=user.id).count()
active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).count()
result = {
'id': user.id,
'github_id': user.github_id,
'username': user.username,
'display_name': user.display_name,
'email': user.email,
'role': user.role,
'job_count': total_jobs,
'active_job_count': active_jobs
}
return jsonify(result)
@bp.route('/users/<user_id>', methods=['PUT'])
@admin_required
def update_user(user_id):
"""Update a user (admin only)"""
user = User.query.get_or_404(user_id)
data = request.get_json() or {}
if 'role' in data and data['role'] in ['admin', 'user', 'guest']:
user.role = data['role']
if 'display_name' in data:
user.display_name = data['display_name']
db.session.commit()
return jsonify({
'id': user.id,
'github_id': user.github_id,
'username': user.username,
'display_name': user.display_name,
'email': user.email,
'role': user.role
})
@bp.route('/users/<user_id>', methods=['DELETE'])
@admin_required
def delete_user(user_id):
"""Delete a user (admin only)"""
user = User.query.get_or_404(user_id)
# Check if user has active jobs
active_jobs = PrintJob.query.filter_by(user_id=user.id, aborted=False).first()
if active_jobs:
return jsonify({'error': 'Cannot delete user with active jobs'}), 400
db.session.delete(user)
db.session.commit()
return jsonify({'message': 'User deleted successfully'})
@bp.route('/me', methods=['GET'])
@token_required
def get_current_user():
"""Get the current user's profile"""
user = User.query.get(request.user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
result = {
'id': user.id,
'github_id': user.github_id,
'username': user.username,
'display_name': user.display_name,
'email': user.email,
'role': user.role
}
return jsonify(result)
@bp.route('/me', methods=['PUT'])
@token_required
def update_current_user():
"""Update the current user's profile"""
user = User.query.get(request.user_id)
if not user:
return jsonify({'error': 'User not found'}), 404
data = request.get_json() or {}
if 'display_name' in data:
user.display_name = data['display_name']
db.session.commit()
result = {
'id': user.id,
'github_id': user.github_id,
'username': user.username,
'display_name': user.display_name,
'email': user.email,
'role': user.role
}
return jsonify(result)