118 lines
4.8 KiB
Python
118 lines
4.8 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from flask_login import UserMixin
|
|
from werkzeug.security import check_password_hash, generate_password_hash
|
|
|
|
from .extensions import db
|
|
|
|
|
|
class Company(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(120), unique=True, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
# Optional per-company storage quota for uploaded media (bytes).
|
|
# If NULL or <=0: unlimited.
|
|
storage_max_bytes = db.Column(db.BigInteger, nullable=True)
|
|
|
|
users = db.relationship("User", back_populates="company", cascade="all, delete-orphan")
|
|
displays = db.relationship("Display", back_populates="company", cascade="all, delete-orphan")
|
|
playlists = db.relationship("Playlist", back_populates="company", cascade="all, delete-orphan")
|
|
|
|
|
|
class User(UserMixin, db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
# Backwards compatibility: older SQLite DBs (and some templates) expect a username column.
|
|
# The app no longer uses username for login/display, but we keep it populated (= email)
|
|
# to avoid integrity errors without introducing Alembic migrations.
|
|
username = db.Column(db.String(255), unique=True, nullable=False)
|
|
email = db.Column(db.String(255), unique=True, nullable=False)
|
|
password_hash = db.Column(db.String(255), nullable=True)
|
|
is_admin = db.Column(db.Boolean, default=False, nullable=False)
|
|
|
|
company_id = db.Column(db.Integer, db.ForeignKey("company.id"), nullable=True)
|
|
company = db.relationship("Company", back_populates="users")
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
def set_password(self, password: str):
|
|
self.password_hash = generate_password_hash(password)
|
|
|
|
def check_password(self, password: str) -> bool:
|
|
if not self.password_hash:
|
|
return False
|
|
return check_password_hash(self.password_hash, password)
|
|
|
|
|
|
class Playlist(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
company_id = db.Column(db.Integer, db.ForeignKey("company.id"), nullable=False)
|
|
name = db.Column(db.String(120), nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
company = db.relationship("Company", back_populates="playlists")
|
|
items = db.relationship(
|
|
"PlaylistItem",
|
|
back_populates="playlist",
|
|
cascade="all, delete-orphan",
|
|
order_by="PlaylistItem.position.asc()",
|
|
)
|
|
|
|
|
|
class PlaylistItem(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
playlist_id = db.Column(db.Integer, db.ForeignKey("playlist.id"), nullable=False)
|
|
|
|
# image|video|webpage|youtube
|
|
item_type = db.Column(db.String(20), nullable=False)
|
|
title = db.Column(db.String(200), nullable=True)
|
|
|
|
# For image/video: stored under /static/uploads
|
|
file_path = db.Column(db.String(400), nullable=True)
|
|
# For webpage
|
|
url = db.Column(db.String(1000), nullable=True)
|
|
|
|
# For image/webpage duration (seconds). For video we play until ended.
|
|
duration_seconds = db.Column(db.Integer, default=10, nullable=False)
|
|
position = db.Column(db.Integer, default=0, nullable=False)
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
playlist = db.relationship("Playlist", back_populates="items")
|
|
|
|
|
|
class Display(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
company_id = db.Column(db.Integer, db.ForeignKey("company.id"), nullable=False)
|
|
name = db.Column(db.String(120), nullable=False)
|
|
# Optional short description (e.g. "entrance", "office")
|
|
description = db.Column(db.String(200), nullable=True)
|
|
token = db.Column(db.String(64), unique=True, nullable=False, default=lambda: uuid.uuid4().hex)
|
|
|
|
assigned_playlist_id = db.Column(db.Integer, db.ForeignKey("playlist.id"), nullable=True)
|
|
assigned_playlist = db.relationship("Playlist")
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
company = db.relationship("Company", back_populates="displays")
|
|
|
|
|
|
class DisplaySession(db.Model):
|
|
"""Tracks active viewers of a display token so we can limit concurrent usage."""
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
display_id = db.Column(db.Integer, db.ForeignKey("display.id"), nullable=False, index=True)
|
|
sid = db.Column(db.String(64), nullable=False)
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
last_seen_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, index=True)
|
|
|
|
# Optional diagnostics
|
|
ip = db.Column(db.String(64), nullable=True)
|
|
user_agent = db.Column(db.String(300), nullable=True)
|
|
|
|
display = db.relationship("Display")
|
|
|
|
__table_args__ = (db.UniqueConstraint("display_id", "sid", name="uq_display_session_display_sid"),)
|