feat(phase1): add image storage utilities

- Create backend/services/image_storage.py with 4 core functions:
  - sanitize_filename(): remove unsafe chars, limit to 255 chars, convert to lowercase
  - get_unique_filename(): handle collisions with UUID suffix (format: {name}_{uuid8}_{variant}.jpg)
  - ensure_image_directories(): create /images/ root and category subdirs on startup
  - save_image(): save bytes to /images/{category}/{filename}, returns relative path
- Create comprehensive test suite (22 tests) covering all functionality
- Integrate ensure_image_directories() into FastAPI startup event
- Directory structure: /images/{category}/{filename}
- Collision handling: auto-suffix with UUID if filename exists
- All tests passing, pathlib.Path for safe operations
This commit is contained in:
2026-04-20 21:57:26 +03:00
parent 39fab336ba
commit ea49cd6e4a
4487 changed files with 1305959 additions and 4 deletions

View File

@@ -38,7 +38,7 @@ class Item(Base):
category = Column(String, index=True)
category_id = Column(Integer, ForeignKey("categories.id"), nullable=True)
type = Column(String, index=True, nullable=True)
category_rel = relationship("Category", back_populates="items")
part_number = Column(String, index=True, nullable=True)
color = Column(String, index=True, nullable=True)
@@ -50,12 +50,17 @@ class Item(Base):
quantity = Column(Float, default=0.0)
min_quantity = Column(Float, default=1.0)
image_url = Column(String, nullable=True)
# Generic box/container association for multi-item OCR scanning
box_label = Column(String, index=True, nullable=True)
# Full AI metadata
labels_data = Column(Text, nullable=True)
labels_data = Column(Text, nullable=True)
# Photo fields (Phase 1: Image System)
photo_path = Column(String, nullable=True) # e.g., "networking/SFP-LR_original.jpg"
photo_thumbnail_path = Column(String, nullable=True) # e.g., "networking/SFP-LR_thumb.jpg"
photo_upload_date = Column(DateTime, nullable=True)
class AuditLog(Base):
__tablename__ = "audit_logs"
@@ -96,6 +101,19 @@ class InterventionItem(Base):
intervention = relationship("Intervention", back_populates="items")
class Box(Base):
__tablename__ = "boxes"
id = Column(Integer, primary_key=True, index=True)
box_label = Column(String, unique=True, index=True)
description = Column(String, nullable=True)
# Photo fields (Phase 1: Image System)
photo_path = Column(String, nullable=True) # e.g., "boxes/container-001_original.jpg"
photo_thumbnail_path = Column(String, nullable=True) # e.g., "boxes/container-001_thumb.jpg"
photo_upload_date = Column(DateTime, nullable=True)
class SystemSetting(Base):
__tablename__ = "system_settings"