feat: Add VLC service for sound playback and management

- Implemented VLCService to handle sound playback using VLC.
- Added routes for soundboard management including play, stop, and status.
- Introduced admin routes for sound normalization and scanning.
- Updated user model and services to accommodate new functionalities.
- Enhanced error handling and logging throughout the application.
- Updated dependencies to include python-vlc for sound playback capabilities.
This commit is contained in:
JSC
2025-07-03 21:25:50 +02:00
parent 8f17dd730a
commit 7455811860
20 changed files with 760 additions and 91 deletions

View File

@@ -29,12 +29,15 @@ class User(db.Model):
# Password authentication (optional - users can use OAuth instead)
password_hash: Mapped[str | None] = mapped_column(
String(255), nullable=True,
String(255),
nullable=True,
)
# Role-based access control
role: Mapped[str] = mapped_column(
String(50), nullable=False, default="user",
String(50),
nullable=False,
default="user",
)
# User status
@@ -42,7 +45,9 @@ class User(db.Model):
# Plan relationship
plan_id: Mapped[int] = mapped_column(
Integer, ForeignKey("plans.id"), nullable=False,
Integer,
ForeignKey("plans.id"),
nullable=False,
)
# User credits (populated from plan credits on creation)
@@ -51,12 +56,15 @@ class User(db.Model):
# API token for programmatic access
api_token: Mapped[str | None] = mapped_column(String(255), nullable=True)
api_token_expires_at: Mapped[datetime | None] = mapped_column(
DateTime, nullable=True,
DateTime,
nullable=True,
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, nullable=False,
DateTime,
default=datetime.utcnow,
nullable=False,
)
updated_at: Mapped[datetime] = mapped_column(
DateTime,
@@ -67,7 +75,9 @@ class User(db.Model):
# Relationships
oauth_providers: Mapped[list["UserOAuth"]] = relationship(
"UserOAuth", back_populates="user", cascade="all, delete-orphan",
"UserOAuth",
back_populates="user",
cascade="all, delete-orphan",
)
plan: Mapped["Plan"] = relationship("Plan", back_populates="users")
@@ -198,7 +208,8 @@ class User(db.Model):
# First, try to find existing OAuth provider
oauth_provider = UserOAuth.find_by_provider_and_id(
provider, provider_id,
provider,
provider_id,
)
if oauth_provider:
@@ -256,7 +267,10 @@ class User(db.Model):
@classmethod
def create_with_password(
cls, email: str, password: str, name: str,
cls,
email: str,
password: str,
name: str,
) -> "User":
"""Create new user with email and password."""
from app.models.plan import Plan
@@ -293,7 +307,9 @@ class User(db.Model):
@classmethod
def authenticate_with_password(
cls, email: str, password: str,
cls,
email: str,
password: str,
) -> Optional["User"]:
"""Authenticate user with email and password."""
user = cls.find_by_email(email)

View File

@@ -33,7 +33,9 @@ class UserOAuth(db.Model):
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow, nullable=False,
DateTime,
default=datetime.utcnow,
nullable=False,
)
updated_at: Mapped[datetime] = mapped_column(
DateTime,
@@ -45,13 +47,16 @@ class UserOAuth(db.Model):
# Unique constraint on provider + provider_id combination
__table_args__ = (
db.UniqueConstraint(
"provider", "provider_id", name="unique_provider_user",
"provider",
"provider_id",
name="unique_provider_user",
),
)
# Relationships
user: Mapped["User"] = relationship(
"User", back_populates="oauth_providers",
"User",
back_populates="oauth_providers",
)
def __repr__(self) -> str:
@@ -73,11 +78,14 @@ class UserOAuth(db.Model):
@classmethod
def find_by_provider_and_id(
cls, provider: str, provider_id: str,
cls,
provider: str,
provider_id: str,
) -> Optional["UserOAuth"]:
"""Find OAuth provider by provider name and provider ID."""
return cls.query.filter_by(
provider=provider, provider_id=provider_id,
provider=provider,
provider_id=provider_id,
).first()
@classmethod