printedparts stage 15: print-file revision history + role-based alerts
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s

printeditemfiles lands as the plugin's first incremental migration
(0002 on the plugin chain - the ADR-008 payoff). Revisions are
append-only per item: upload assigns the next number, records the
uploader from the JWT, enforces an extension allowlist and a 100 MB
cap; download serves the original filename; a permission-gated delete
covers wrong-file mistakes. The detail page gains the revision table
with a current badge. Unique storedfilename is sized 191 so the index
fits MySQL's 767-byte prefix - the per-plugin chain does not apply the
core env's ROW_FORMAT hook.

Alert recipients gain roles: Role joins the 0.13.0 surface, a role
picker on the settings page, and every active member of the selected
roles is folded into the deduped recipient list.
This commit is contained in:
cproudlock
2026-07-17 08:47:41 -04:00
parent 26b6b6b32f
commit aa4bfcd41c
14 changed files with 479 additions and 11 deletions

View File

@@ -16,7 +16,7 @@ from flask import Flask, Blueprint
from shopdb.plugins.base import BasePlugin, PluginMeta
from shopdb.api import db, Setting
from .models import PrintedItem, PrintedItemTransaction
from .models import PrintedItem, PrintedItemTransaction, PrintedItemFile
from .api import printedparts_bp
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ class PrintedpartsPlugin(BasePlugin):
return printedparts_bp
def get_models(self) -> List[Type]:
return [PrintedItem, PrintedItemTransaction]
return [PrintedItem, PrintedItemTransaction, PrintedItemFile]
def init_app(self, app: Flask, db_instance) -> None:
logger.info(f'Printedparts plugin initialized (v{self.meta.version})')
@@ -136,6 +136,9 @@ class PrintedpartsPlugin(BasePlugin):
('printedparts_alert_userids', '', 'string',
'Comma-separated shopdb user ids whose account emails receive '
'low-stock alerts'),
('printedparts_alert_roleids', '', 'string',
'Comma-separated role ids; every active member of these roles '
'receives low-stock alerts'),
]
for key, value, valuetype, description in defaults:
if Setting.get(key) is None: