Security hardening, multi-site portability, and a documentation and API-surface sweep

This commit is contained in:
2026-08-14 16:28:34 -04:00
parent 72f8da0e58
commit 265ca2eb01
76 changed files with 3684 additions and 811 deletions

View File

@@ -236,8 +236,7 @@ def upload_item_image(item_id: int):
@printedparts_bp.route('/image/<path:filename>', methods=['GET'])
def serve_item_image(filename):
"""Serve an uploaded item image (public - kiosk and list read it)."""
from flask import send_from_directory
return send_from_directory(_imagedir(), filename)
return send_upload(_imagedir(), filename)
@printedparts_bp.route('/items/<int:item_id>/image', methods=['DELETE'])
@@ -643,6 +642,7 @@ def report_by_person():
# --- print files: append-only revisions per item ------------------------------
from flask_jwt_extended import get_jwt_identity
from shopdb.api import send_upload
from ..models import PrintedItemFile
@@ -737,12 +737,11 @@ def upload_item_file(item_id: int):
@jwt_required(optional=True)
def download_item_file(file_id: int):
"""Download a revision under its original filename."""
from flask import send_from_directory
record = db.session.get(PrintedItemFile, file_id)
if not record:
return error_response(ErrorCodes.NOT_FOUND, 'File not found',
http_code=404)
return send_from_directory(_filedir(), record.storedfilename,
return send_upload(_filedir(), record.storedfilename,
as_attachment=True,
download_name=record.filename)

View File

@@ -97,7 +97,7 @@ import { withBase } from '@/utils/basePath'
import TouchKeypad from '@/components/TouchKeypad.vue'
// Leading text on the physical labels, shown before the number box so operators
// type only the digits. It was hardcoded to 'WJ', which is West Jefferson's tag
// type only the digits. It was hardcoded to 'WJ', which is one site's tag
// format and nobody else's - on another site's kiosk it told operators to expect
// letters that are not on their labels. Display only: the lookup sends whatever
// was typed, unprefixed.

View File

@@ -52,7 +52,7 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import QRCode from 'qrcode'
import { qrPngDataUrl } from '@/utils/codes'
import { printedpartsApi } from '@/api'
const items = ref([])
@@ -123,9 +123,7 @@ watch(printLabels, async labels => {
await Promise.all(labels.map(async (label, index) => {
// margin 2 = quiet zone (scannability); EC 'M' keeps modules big for the
// short payload on tiny media; no logo overlay on a 0.36in code.
urls[index] = await QRCode.toDataURL(labelPayload(label), {
margin: 2, errorCorrectionLevel: 'M', width: 160
})
urls[index] = await qrPngDataUrl(labelPayload(label), { margin: 2, width: 160 })
}))
qrDataUrls.value = urls
}, { deep: true, immediate: true })

View File

@@ -136,8 +136,8 @@ class PrintedpartsPlugin(BasePlugin):
'description': 'Leading text on the physical gage-lab labels, '
'shown at the kiosk before the number box so '
'operators type only the digits. Empty shows no '
'prefix. Site-specific: West Jefferson labels '
'read WJ',
'prefix. Site-specific: set it to whatever this '
"site's labels are printed with, e.g. WJ",
},
{
'key': 'printedparts_default_threshold',