feat: Add job cancellation support and update job status handling
This commit introduces a new `CANCELLED` status to the job management system, allowing users to cancel ongoing jobs. The frontend is updated to handle job cancellation requests, providing user feedback during the cancellation process. Additionally, the backend is enhanced to manage job statuses more effectively, ensuring that jobs can be marked as cancelled and that appropriate messages are displayed to users. This improvement enhances the overall user experience by providing better control over job processing.
This commit is contained in:
+50
-9
@@ -18,6 +18,7 @@ from sqlalchemy import (
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
CheckConstraint,
|
||||
TypeDecorator,
|
||||
)
|
||||
from sqlalchemy.orm import declarative_base, relationship
|
||||
|
||||
@@ -29,6 +30,46 @@ if TYPE_CHECKING:
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class PrismaCompatibleDateTime(TypeDecorator):
|
||||
"""
|
||||
DateTime type that stores in a format compatible with Prisma's SQLite driver.
|
||||
|
||||
Prisma's SQLite driver has issues with microseconds in datetime strings.
|
||||
This type ensures datetimes are stored in ISO format without microseconds:
|
||||
'YYYY-MM-DD HH:MM:SS' instead of 'YYYY-MM-DD HH:MM:SS.ffffff'
|
||||
"""
|
||||
impl = DateTime
|
||||
cache_ok = True
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
"""Convert Python datetime to SQL string format."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
# Strip microseconds and format as ISO string without microseconds
|
||||
# This ensures Prisma can read it correctly
|
||||
value = value.replace(microsecond=0)
|
||||
return value.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return value
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
"""Convert SQL string back to Python datetime."""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
# Parse ISO format string
|
||||
try:
|
||||
# Try parsing with microseconds first (for existing data)
|
||||
if '.' in value:
|
||||
return datetime.strptime(value.split('.')[0], '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
return datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
# Fallback to ISO format parser
|
||||
return datetime.fromisoformat(value.replace('Z', '+00:00'))
|
||||
return value
|
||||
|
||||
|
||||
class Photo(Base):
|
||||
"""Photo model - matches desktop schema exactly."""
|
||||
|
||||
@@ -37,7 +78,7 @@ class Photo(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
|
||||
path = Column(Text, unique=True, nullable=False, index=True)
|
||||
filename = Column(Text, nullable=False)
|
||||
date_added = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
date_added = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
date_taken = Column(Date, nullable=True, index=True)
|
||||
processed = Column(Boolean, default=False, nullable=False, index=True)
|
||||
file_hash = Column(Text, nullable=True, index=True) # Nullable to support existing photos without hashes
|
||||
@@ -71,7 +112,7 @@ class Person(Base):
|
||||
middle_name = Column(Text, nullable=True)
|
||||
maiden_name = Column(Text, nullable=True)
|
||||
date_of_birth = Column(Date, nullable=True)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
faces = relationship("Face", back_populates="person")
|
||||
person_encodings = relationship(
|
||||
@@ -142,7 +183,7 @@ class PersonEncoding(Base):
|
||||
quality_score = Column(Numeric, default=0.0, nullable=False, index=True)
|
||||
detector_backend = Column(Text, default="retinaface", nullable=False)
|
||||
model_name = Column(Text, default="ArcFace", nullable=False)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
person = relationship("Person", back_populates="person_encodings")
|
||||
face = relationship("Face", back_populates="person_encodings")
|
||||
@@ -160,7 +201,7 @@ class Tag(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
|
||||
tag_name = Column(Text, unique=True, nullable=False, index=True)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
photo_tags = relationship(
|
||||
"PhotoTagLinkage", back_populates="tag", cascade="all, delete-orphan"
|
||||
@@ -179,7 +220,7 @@ class PhotoTagLinkage(Base):
|
||||
Integer, default=0, nullable=False,
|
||||
server_default="0"
|
||||
)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
photo = relationship("Photo", back_populates="photo_tags")
|
||||
tag = relationship("Tag", back_populates="photo_tags")
|
||||
@@ -200,7 +241,7 @@ class PhotoFavorite(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(Text, nullable=False, index=True)
|
||||
photo_id = Column(Integer, ForeignKey("photos.id"), nullable=False, index=True)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
photo = relationship("Photo", back_populates="favorites")
|
||||
|
||||
@@ -231,8 +272,8 @@ class User(Base):
|
||||
index=True,
|
||||
)
|
||||
password_change_required = Column(Boolean, default=True, nullable=False, index=True)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
last_login = Column(DateTime, nullable=True)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
last_login = Column(PrismaCompatibleDateTime, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_users_username", "username"),
|
||||
@@ -256,7 +297,7 @@ class PhotoPersonLinkage(Base):
|
||||
photo_id = Column(Integer, ForeignKey("photos.id"), nullable=False, index=True)
|
||||
person_id = Column(Integer, ForeignKey("people.id"), nullable=False, index=True)
|
||||
identified_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
|
||||
created_date = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
created_date = Column(PrismaCompatibleDateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
photo = relationship("Photo", back_populates="video_people")
|
||||
person = relationship("Person", back_populates="video_photos")
|
||||
|
||||
Reference in New Issue
Block a user