Files
maziggy 6855d65d12 Let other applications send messages through the notification channels
POST /notifications/app-message delivers an app's message to every channel
with the new "Messages from connected apps" switch on (off by default),
through quiet hours, the digest and the log. API keys need the new "Send
notifications" permission, and their owner notifications:update; plain text,
http(s) links, 20 messages a minute per key. The electricity-price door and
this one now share one scoped-key check. /queue?batch=<id> opens and
highlights one batch order.
2026-09-27 12:45:50 +02:00

66 lines
3.3 KiB
Python

from datetime import datetime
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column
from backend.app.core.database import Base
class APIKey(Base):
"""API key for external webhook access."""
__tablename__ = "api_keys"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100)) # User-friendly name
key_hash: Mapped[str] = mapped_column(String(255)) # bcrypt hash of the key
key_prefix: Mapped[str] = mapped_column(String(20)) # First 8 chars + "..." for display
# Owner — required for new keys, NULL only on legacy rows that predate per-user
# ownership. Cloud routes reject calls from keys without an owner so callers are
# forced to recreate them. CASCADE so deleting a user removes their keys.
user_id: Mapped[int | None] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=True,
index=True,
)
# Permissions
can_queue: Mapped[bool] = mapped_column(Boolean, default=True) # Add to queue
can_control_printer: Mapped[bool] = mapped_column(Boolean, default=False) # Start/stop/cancel
can_read_status: Mapped[bool] = mapped_column(Boolean, default=True) # Query status
can_manage_library: Mapped[bool] = mapped_column(
Boolean, default=True
) # Upload/rename/delete own library files + MakerWorld import
can_manage_inventory: Mapped[bool] = mapped_column(
Boolean, default=True
) # Inventory write ops (incl. SpoolBuddy kiosk NFC/scale/system)
can_manage_maintenance: Mapped[bool] = mapped_column(
Boolean, default=True
) # Log/reset per-printer maintenance, edit intervals, manage the type catalog (#1832 follow-up)
can_manage_archives: Mapped[bool] = mapped_column(
Boolean, default=True
) # Create/update/delete print archives (not purge) (#1888)
can_manage_projects: Mapped[bool] = mapped_column(
Boolean, default=True
) # Create/update/delete projects + manage membership (add archives) (#1893)
can_access_cloud: Mapped[bool] = mapped_column(Boolean, default=False) # Read /cloud/* on the owner's behalf
# Narrowly-scoped settings write: only POST /settings/electricity-price.
# Lets HA/Tibber-style automations push dynamic tariff updates without
# granting full SETTINGS_UPDATE (which is denied for API keys because it
# could rewrite SMTP/LDAP/MQTT credentials).
can_update_energy_cost: Mapped[bool] = mapped_column(Boolean, default=False)
# Send a message through the notification channels that accept app
# messages (POST /notifications/app-message). Nothing else: no reading or
# changing the channels themselves.
can_send_notifications: Mapped[bool] = mapped_column(Boolean, default=False)
# Optional scope limits
printer_ids: Mapped[list | None] = mapped_column(JSON, nullable=True) # null = all printers
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
last_used: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Optional expiry