diff --git a/app/models/sound.py b/app/models/sound.py index 9d75142..2554361 100644 --- a/app/models/sound.py +++ b/app/models/sound.py @@ -3,11 +3,13 @@ from datetime import datetime from enum import Enum from typing import Optional +from zoneinfo import ZoneInfo -from app.database import db from sqlalchemy import Boolean, DateTime, Integer, String from sqlalchemy.orm import Mapped, mapped_column +from app.database import db + class SoundType(Enum): """Sound type enumeration.""" @@ -75,13 +77,13 @@ class Sound(db.Model): # Timestamps created_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, - onupdate=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), + onupdate=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) @@ -114,7 +116,7 @@ class Sound(db.Model): def increment_play_count(self) -> None: """Increment the play count for this sound.""" self.play_count += 1 - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) db.session.commit() def set_normalized_info( @@ -130,7 +132,7 @@ class Sound(db.Model): self.normalized_size = normalized_size self.normalized_hash = normalized_hash self.is_normalized = True - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) def clear_normalized_info(self) -> None: """Clear normalized sound information.""" @@ -139,7 +141,7 @@ class Sound(db.Model): self.normalized_hash = None self.normalized_size = None self.is_normalized = False - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) def update_file_info( self, @@ -153,7 +155,7 @@ class Sound(db.Model): self.duration = duration self.size = size self.hash = hash_value - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) @classmethod def find_by_hash(cls, hash_value: str) -> Optional["Sound"]: diff --git a/app/models/sound_played.py b/app/models/sound_played.py index 6854790..19cd27e 100644 --- a/app/models/sound_played.py +++ b/app/models/sound_played.py @@ -1,11 +1,13 @@ """Sound played tracking model.""" from datetime import datetime +from zoneinfo import ZoneInfo -from app.database import db from sqlalchemy import DateTime, ForeignKey, Integer, func, text from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.database import db + class SoundPlayed(db.Model): """Model to track when users play sounds.""" @@ -16,15 +18,21 @@ class SoundPlayed(db.Model): # Foreign keys user_id: Mapped[int] = mapped_column( - Integer, ForeignKey("users.id"), nullable=False, + Integer, + ForeignKey("users.id"), + nullable=False, ) sound_id: Mapped[int] = mapped_column( - Integer, ForeignKey("sounds.id"), nullable=False, + Integer, + ForeignKey("sounds.id"), + nullable=False, ) # Timestamp played_at: Mapped[datetime] = mapped_column( - DateTime, default=datetime.utcnow, nullable=False, + DateTime, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), + nullable=False, ) # Relationships @@ -45,17 +53,25 @@ class SoundPlayed(db.Model): "user_id": self.user_id, "sound_id": self.sound_id, "played_at": self.played_at.isoformat(), - "user": { - "id": self.user.id, - "name": self.user.name, - "email": self.user.email, - } if self.user else None, - "sound": { - "id": self.sound.id, - "name": self.sound.name, - "filename": self.sound.filename, - "type": self.sound.type, - } if self.sound else None, + "user": ( + { + "id": self.user.id, + "name": self.user.name, + "email": self.user.email, + } + if self.user + else None + ), + "sound": ( + { + "id": self.sound.id, + "name": self.sound.name, + "filename": self.sound.filename, + "type": self.sound.type, + } + if self.sound + else None + ), } @classmethod @@ -79,7 +95,10 @@ class SoundPlayed(db.Model): @classmethod def get_user_plays( - cls, user_id: int, limit: int = 50, offset: int = 0, + cls, + user_id: int, + limit: int = 50, + offset: int = 0, ) -> list["SoundPlayed"]: """Get recent plays for a specific user.""" return ( @@ -92,7 +111,10 @@ class SoundPlayed(db.Model): @classmethod def get_sound_plays( - cls, sound_id: int, limit: int = 50, offset: int = 0, + cls, + sound_id: int, + limit: int = 50, + offset: int = 0, ) -> list["SoundPlayed"]: """Get recent plays for a specific sound.""" return ( @@ -105,7 +127,9 @@ class SoundPlayed(db.Model): @classmethod def get_recent_plays( - cls, limit: int = 100, offset: int = 0, + cls, + limit: int = 100, + offset: int = 0, ) -> list["SoundPlayed"]: """Get recent plays across all users and sounds.""" return ( @@ -127,7 +151,9 @@ class SoundPlayed(db.Model): @classmethod def get_popular_sounds( - cls, limit: int = 10, days: int | None = None, + cls, + limit: int = 10, + days: int | None = None, ) -> list[dict]: """Get most popular sounds with play counts.""" from app.models.sound import Sound @@ -144,7 +170,7 @@ class SoundPlayed(db.Model): if days: query = query.filter( - cls.played_at >= text(f"datetime('now', '-{days} days')") + cls.played_at >= text(f"datetime('now', '-{days} days')"), ) results = query.limit(limit).all() @@ -154,15 +180,17 @@ class SoundPlayed(db.Model): for result in results: sound = Sound.query.get(result.sound_id) if sound: - popular_sounds.append({ - "sound": sound.to_dict(), - "play_count": result.play_count, - "last_played": ( - result.last_played.isoformat() - if result.last_played - else None - ), - }) + popular_sounds.append( + { + "sound": sound.to_dict(), + "play_count": result.play_count, + "last_played": ( + result.last_played.isoformat() + if result.last_played + else None + ), + } + ) return popular_sounds @@ -193,7 +221,8 @@ class SoundPlayed(db.Model): # Get favorite sound favorite_query = ( db.session.query( - cls.sound_id, func.count(cls.id).label("play_count") + cls.sound_id, + func.count(cls.id).label("play_count"), ) .filter_by(user_id=user_id) .group_by(cls.sound_id) @@ -232,4 +261,4 @@ class SoundPlayed(db.Model): "last_play": ( last_play.played_at.isoformat() if last_play else None ), - } \ No newline at end of file + } diff --git a/app/models/user.py b/app/models/user.py index 53a3d74..898cc9c 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -3,6 +3,7 @@ import secrets from datetime import datetime from typing import TYPE_CHECKING, Optional +from zoneinfo import ZoneInfo from sqlalchemy import DateTime, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -63,13 +64,13 @@ class User(db.Model): # Timestamps created_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, - onupdate=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), + onupdate=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) @@ -103,9 +104,11 @@ class User(db.Model): "role": self.role, "is_active": self.is_active, "api_token": self.api_token, - "api_token_expires_at": self.api_token_expires_at.isoformat() - if self.api_token_expires_at - else None, + "api_token_expires_at": ( + self.api_token_expires_at.isoformat() + if self.api_token_expires_at + else None + ), "providers": providers, "plan": self.plan.to_dict() if self.plan else None, "credits": self.credits, @@ -129,13 +132,13 @@ class User(db.Model): self.email = provider_data.get("email", self.email) self.name = provider_data.get("name", self.name) self.picture = provider_data.get("picture", self.picture) - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) db.session.commit() def set_password(self, password: str) -> None: """Hash and set user password.""" self.password_hash = generate_password_hash(password) - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) def check_password(self, password: str) -> bool: """Check if provided password matches user's password.""" @@ -151,7 +154,7 @@ class User(db.Model): """Generate a new API token for the user.""" self.api_token = secrets.token_urlsafe(32) self.api_token_expires_at = None # No expiration by default - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) return self.api_token def is_api_token_valid(self) -> bool: @@ -162,23 +165,23 @@ class User(db.Model): if self.api_token_expires_at is None: return True # No expiration - return datetime.utcnow() < self.api_token_expires_at + return datetime.now(tz=ZoneInfo("UTC")) < self.api_token_expires_at def revoke_api_token(self) -> None: """Revoke the user's API token.""" self.api_token = None self.api_token_expires_at = None - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) def activate(self) -> None: """Activate the user account.""" self.is_active = True - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) def deactivate(self) -> None: """Deactivate the user account.""" self.is_active = False - self.updated_at = datetime.utcnow() + self.updated_at = datetime.now(tz=ZoneInfo("UTC")) @classmethod def find_by_email(cls, email: str) -> Optional["User"]: @@ -218,7 +221,7 @@ class User(db.Model): oauth_provider.email = email oauth_provider.name = name oauth_provider.picture = picture - oauth_provider.updated_at = datetime.utcnow() + oauth_provider.updated_at = datetime.now(tz=ZoneInfo("UTC")) # Update user info with latest data user.update_from_provider( diff --git a/app/models/user_oauth.py b/app/models/user_oauth.py index fedeb13..b987e72 100644 --- a/app/models/user_oauth.py +++ b/app/models/user_oauth.py @@ -2,6 +2,7 @@ from datetime import datetime from typing import TYPE_CHECKING, Optional +from zoneinfo import ZoneInfo from sqlalchemy import DateTime, ForeignKey, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -34,13 +35,13 @@ class UserOAuth(db.Model): # Timestamps created_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime, - default=datetime.utcnow, - onupdate=datetime.utcnow, + default=lambda: datetime.now(tz=ZoneInfo("UTC")), + onupdate=lambda: datetime.now(tz=ZoneInfo("UTC")), nullable=False, ) @@ -107,7 +108,7 @@ class UserOAuth(db.Model): oauth_provider.email = email oauth_provider.name = name oauth_provider.picture = picture - oauth_provider.updated_at = datetime.utcnow() + oauth_provider.updated_at = datetime.now(tz=ZoneInfo("UTC")) else: # Create new provider oauth_provider = cls( diff --git a/app/services/credit_service.py b/app/services/credit_service.py index d065cca..f9dacd6 100644 --- a/app/services/credit_service.py +++ b/app/services/credit_service.py @@ -2,6 +2,7 @@ import logging from datetime import datetime +from zoneinfo import ZoneInfo from app.database import db from app.models.user import User @@ -62,7 +63,7 @@ class CreditService: if credits_added > 0: user.credits = new_credits - user.updated_at = datetime.utcnow() + user.updated_at = datetime.now(tz=ZoneInfo("UTC")) total_credits_added += credits_added logger.debug(