from app import db import uuid from datetime import datetime, timedelta import jwt from config import Config import bcrypt class User(db.Model): __tablename__ = 'user' id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) username = db.Column(db.String(64), index=True, unique=True, nullable=False) display_name = db.Column(db.String(120)) email = db.Column(db.String(120), index=True, unique=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) role = db.Column(db.String(20), default='user') print_jobs = db.relationship('PrintJob', backref='user', lazy='dynamic', cascade='all, delete-orphan') sessions = db.relationship('Session', backref='user', lazy='dynamic', cascade='all, delete-orphan') def set_password(self, password): """Hash and set the user's password""" password_bytes = password.encode('utf-8') salt = bcrypt.gensalt() self.password_hash = bcrypt.hashpw(password_bytes, salt).decode('utf-8') def check_password(self, password): """Check if the provided password matches the stored hash""" password_bytes = password.encode('utf-8') stored_hash = self.password_hash.encode('utf-8') return bcrypt.checkpw(password_bytes, stored_hash) def generate_token(self): """Generate a JWT token for this user""" payload = { 'user_id': self.id, 'username': self.username, 'email': self.email, 'role': self.role, 'exp': datetime.utcnow() + timedelta(seconds=Config.JWT_ACCESS_TOKEN_EXPIRES) } return jwt.encode(payload, Config.JWT_SECRET, algorithm='HS256') @staticmethod def verify_token(token): """Verify and decode a JWT token""" try: payload = jwt.decode(token, Config.JWT_SECRET, algorithms=['HS256']) return payload except: return None class Session(db.Model): __tablename__ = 'session' id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id = db.Column(db.String(36), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) expires_at = db.Column(db.Integer, nullable=False) class Printer(db.Model): __tablename__ = 'printer' id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) name = db.Column(db.String(120), nullable=False) description = db.Column(db.Text, nullable=False) status = db.Column(db.Integer, nullable=False, default=0) # 0: OPERATIONAL, 1: OUT_OF_ORDER print_jobs = db.relationship('PrintJob', backref='printer', lazy='dynamic', cascade='all, delete-orphan') class PrintJob(db.Model): __tablename__ = 'printJob' id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) printer_id = db.Column(db.String(36), db.ForeignKey('printer.id', ondelete='CASCADE'), nullable=False) user_id = db.Column(db.String(36), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) start_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) duration_in_minutes = db.Column(db.Integer, nullable=False) comments = db.Column(db.Text) aborted = db.Column(db.Boolean, nullable=False, default=False) abort_reason = db.Column(db.Text) def get_end_time(self): return self.start_at + timedelta(minutes=self.duration_in_minutes) def is_active(self): now = datetime.utcnow() return (not self.aborted and self.start_at <= now and now < self.get_end_time()) def get_remaining_time(self): if self.aborted: return 0 now = datetime.utcnow() if now < self.start_at: # Job hasn't started yet return self.duration_in_minutes * 60 end_time = self.get_end_time() if now >= end_time: # Job has ended return 0 # Job is ongoing remaining_seconds = (end_time - now).total_seconds() return int(remaining_seconds) def to_dict(self): return { 'id': self.id, 'printer_id': self.printer_id, 'user_id': self.user_id, 'start_at': self.start_at.isoformat(), 'duration_in_minutes': self.duration_in_minutes, 'comments': self.comments, 'aborted': self.aborted, 'abort_reason': self.abort_reason, 'remaining_time': self.get_remaining_time(), 'is_active': self.is_active() }