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