Files
Fossign/app/models.py

153 lines
6.1 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"),)
class AppSettings(db.Model):
"""Singleton-ish app-wide settings.
For this small project we avoid Alembic migrations; this table can be created via
`flask init-db` (db.create_all) and is also created best-effort on app startup.
NOTE: SMTP password is stored in plaintext in the database.
Prefer environment variables / secrets management in production when possible.
"""
id = db.Column(db.Integer, primary_key=True)
smtp_host = db.Column(db.String(255), nullable=True)
smtp_port = db.Column(db.Integer, nullable=True)
smtp_username = db.Column(db.String(255), nullable=True)
smtp_password = db.Column(db.String(255), nullable=True)
smtp_from = db.Column(db.String(255), nullable=True)
smtp_starttls = db.Column(db.Boolean, default=True, nullable=False)
smtp_timeout_seconds = db.Column(db.Float, default=10.0, nullable=False)
smtp_debug = db.Column(db.Boolean, default=False, nullable=False)
# Public domain for generating absolute links in emails.
# Example: "signage.example.com" (no scheme)
public_domain = db.Column(db.String(255), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(
db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
nullable=False,
)