mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
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.
66 lines
3.3 KiB
Python
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
|