feat: v1.1.0 - Production-Ready Docker Deployment

🚀 Major Release: DigiServer v1.1.0 Production Deployment

## 📁 Project Restructure
- Moved all application code to app/ directory for Docker containerization
- Centralized persistent data in data/ directory with volume mounting
- Removed development artifacts and cleaned up project structure

## 🐳 Docker Integration
- Added production-ready Dockerfile with LibreOffice and poppler-utils
- Updated docker-compose.yml for production deployment
- Added .dockerignore for optimized build context
- Created automated deployment script (deploy-docker.sh)
- Added cleanup script (cleanup-docker.sh)

## 📄 Document Processing Enhancements
- Integrated LibreOffice for professional PPTX to PDF conversion
- Implemented PPTX → PDF → 4K JPG workflow for optimal quality
- Added poppler-utils for enhanced PDF processing
- Simplified PDF conversion to 300 DPI for reliability

## 🔧 File Management Improvements
- Fixed absolute path resolution for containerized deployment
- Updated all file deletion functions with proper path handling
- Enhanced bulk delete functions for players and groups
- Improved file upload workflow with consistent path management

## 🛠️ Code Quality & Stability
- Cleaned up pptx_converter.py from 442 to 86 lines
- Removed all Python cache files (__pycache__/, *.pyc)
- Updated file operations for production reliability
- Enhanced error handling and logging

## 📚 Documentation Updates
- Updated README.md with Docker deployment instructions
- Added comprehensive DEPLOYMENT.md guide
- Included production deployment best practices
- Added automated deployment workflow documentation

## 🔐 Security & Production Features
- Environment-based configuration
- Health checks and container monitoring
- Automated admin user creation
- Volume-mounted persistent data
- Production logging and error handling

##  Ready for Production
- Clean project structure optimized for Docker
- Automated deployment with ./deploy-docker.sh
- Professional document processing pipeline
- Reliable file management system
- Complete documentation and deployment guides

Access: http://localhost:8880 | Admin: admin/Initial01!
This commit is contained in:
2025-08-05 18:04:02 -04:00
parent 4e5aff1c02
commit 1eb0aa3658
71 changed files with 2017 additions and 379 deletions

5
app/models/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .user import User
from .player import Player
from .group import Group, group_player
from .content import Content
from .server_log import ServerLog

21
app/models/clear_db.py Normal file
View File

@@ -0,0 +1,21 @@
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# Ensure the instance directory exists (relative to project root)
instance_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance'))
os.makedirs(instance_dir, exist_ok=True)
# Set the correct database URI
db_path = os.path.join(instance_dir, 'dashboard.db')
print(f"Using database at: {db_path}")
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
with app.app_context():
db.reflect() # This loads all tables from the database
db.drop_all()
print("Dropped all tables successfully.")

8
app/models/content.py Normal file
View File

@@ -0,0 +1,8 @@
from extensions import db
class Content(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_name = db.Column(db.String(255), nullable=False)
duration = db.Column(db.Integer, nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False)
position = db.Column(db.Integer, default=0)

View File

@@ -0,0 +1,18 @@
#from app import app, db, User, bcrypt
import os
def create_default_user(db, User, bcrypt):
username = os.getenv('DEFAULT_USER', 'admin')
password = os.getenv('DEFAULT_PASSWORD', '1234')
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
existing_user = User.query.filter_by(username=username).first()
if not existing_user:
default_user = User(username=username, password=hashed_password, role='admin')
db.session.add(default_user)
db.session.commit()
print(f"Default user '{username}' created with password '{password}'")
else:
print(f"Default user '{username}' already exists.")
#with app.app_context():
# create_default_user(db, User, bcrypt)

13
app/models/group.py Normal file
View File

@@ -0,0 +1,13 @@
from extensions import db
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, unique=True)
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
players = db.relationship('Player', secondary='group_player', backref='groups')
playlist_version = db.Column(db.Integer, default=0)
group_player = db.Table('group_player',
db.Column('group_id', db.Integer, db.ForeignKey('group.id'), primary_key=True),
db.Column('player_id', db.Integer, db.ForeignKey('player.id'), primary_key=True)
)

24
app/models/init_db.py Normal file
View File

@@ -0,0 +1,24 @@
import os
from app import app
from extensions import db, bcrypt
from models import User, ServerLog # Import from models.py instead of app.py
def create_admin_user():
admin_username = os.getenv('ADMIN_USER', 'admin')
admin_password = os.getenv('ADMIN_PASSWORD', 'admin')
hashed_password = bcrypt.generate_password_hash(admin_password).decode('utf-8')
if not User.query.filter_by(username=admin_username).first():
admin_user = User(username=admin_username, password=hashed_password, role='admin')
db.session.add(admin_user)
db.session.commit()
print(f"Admin user '{admin_username}' created with password '{admin_password}'")
else:
print(f"Admin user '{admin_username}' already exists")
if __name__ == '__main__':
with app.app_context():
db.create_all()
create_admin_user()
print("Database initialized with all models including ServerLog")

18
app/models/player.py Normal file
View File

@@ -0,0 +1,18 @@
from extensions import db
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt()
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), nullable=False)
hostname = db.Column(db.String(255), nullable=False)
password = db.Column(db.String(255), nullable=False)
quickconnect_password = db.Column(db.String(255), nullable=True)
playlist_version = db.Column(db.Integer, default=1)
locked_to_group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
locked_to_group = db.relationship('Group', foreign_keys=[locked_to_group_id], backref='locked_players')
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
def verify_quickconnect_code(self, code):
return bcrypt.check_password_hash(self.quickconnect_password, code)

10
app/models/server_log.py Normal file
View File

@@ -0,0 +1,10 @@
from extensions import db
import datetime
class ServerLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
action = db.Column(db.String(255), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
def __repr__(self):
return f"<ServerLog {self.action}>"

33
app/models/user.py Normal file
View File

@@ -0,0 +1,33 @@
from extensions import db
from flask_bcrypt import Bcrypt
from flask_login import UserMixin
bcrypt = Bcrypt()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(80), nullable=False)
theme = db.Column(db.String(80), default='light')
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
@property
def is_active(self):
return True
@property
def is_authenticated(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)