refactor and limits change
This commit is contained in:
parent
7edbc28a9c
commit
82cdd12d19
|
|
@ -626,7 +626,7 @@ async def get_media(media_path: str):
|
|||
|
||||
|
||||
@api_router.put("/media/{media_path:path}", tags=["Media"])
|
||||
@limiter.limit("30/minute")
|
||||
@limiter.limit("120/minute")
|
||||
async def put_media(media_path: str, request: Request):
|
||||
"""
|
||||
Overwrite an existing media file in place (used for saving drawing PNGs).
|
||||
|
|
@ -662,6 +662,10 @@ async def put_media(media_path: str, request: Request):
|
|||
detail=f"File too large. Maximum size: {UPLOAD_MAX_IMAGE_MB}MB",
|
||||
)
|
||||
|
||||
# Reject non-PNG payloads (defense in depth; path already restricts to .png)
|
||||
if len(body) < 8 or body[:8] != b"\x89PNG\r\n\x1a\n":
|
||||
raise HTTPException(status_code=400, detail="Body must be a valid PNG image")
|
||||
|
||||
try:
|
||||
with open(full_path, 'wb') as f:
|
||||
f.write(body)
|
||||
|
|
|
|||
|
|
@ -508,6 +508,10 @@ function noteApp() {
|
|||
drawingIsPointerDown: false,
|
||||
/** True after the PNG from disk has been decoded into _drawingBaseImage; false after Clear. */
|
||||
drawingHasRasterFromFile: false,
|
||||
/** Prevents overlapping drawingSave() runs (Ctrl+S + autosave + fast retries). */
|
||||
_drawingSaveInFlight: false,
|
||||
/** If true, run drawingSave again after the current one finishes (coalesce). */
|
||||
_drawingSaveQueued: false,
|
||||
_drawingAutosaveTimeout: null,
|
||||
|
||||
// DOM element cache (to avoid repeated querySelector calls)
|
||||
|
|
@ -3268,43 +3272,60 @@ function noteApp() {
|
|||
*/
|
||||
async drawingSave() {
|
||||
if (!this.currentMedia || this.currentMediaType !== 'drawing') return;
|
||||
this._drawingCancelAutosave();
|
||||
const canvas = this._drawingCanvasEl;
|
||||
const ctx = this._drawingCtx;
|
||||
if (!canvas || !ctx) return;
|
||||
this.drawingDraft = null;
|
||||
this.drawingIsPointerDown = false;
|
||||
this.drawingRedraw();
|
||||
await new Promise((r) => requestAnimationFrame(r));
|
||||
const blob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
|
||||
});
|
||||
this.isSaving = true;
|
||||
if (this._drawingSaveInFlight) {
|
||||
this._drawingSaveQueued = true;
|
||||
return;
|
||||
}
|
||||
this._drawingSaveInFlight = true;
|
||||
try {
|
||||
const enc = this._drawingEncodeMediaPath();
|
||||
const res = await fetch(`/api/media/${enc}`, {
|
||||
method: 'PUT',
|
||||
body: blob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
credentials: 'same-origin',
|
||||
this._drawingCancelAutosave();
|
||||
const canvas = this._drawingCanvasEl;
|
||||
const ctx = this._drawingCtx;
|
||||
if (!canvas || !ctx) return;
|
||||
this.drawingDraft = null;
|
||||
this.drawingIsPointerDown = false;
|
||||
this.drawingRedraw();
|
||||
await new Promise((r) => requestAnimationFrame(r));
|
||||
const blob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
let detail = err.detail;
|
||||
if (Array.isArray(detail)) {
|
||||
detail = detail.map((d) => d.msg || d).join(', ');
|
||||
this.isSaving = true;
|
||||
try {
|
||||
const enc = this._drawingEncodeMediaPath();
|
||||
const res = await fetch(`/api/media/${enc}`, {
|
||||
method: 'PUT',
|
||||
body: blob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
let detail = err.detail;
|
||||
if (Array.isArray(detail)) {
|
||||
detail = detail.map((d) => d.msg || d).join(', ');
|
||||
}
|
||||
throw new Error(detail || res.statusText);
|
||||
}
|
||||
throw new Error(detail || res.statusText);
|
||||
await this.loadNotes();
|
||||
this.lastSaved = true;
|
||||
setTimeout(() => {
|
||||
this.lastSaved = false;
|
||||
}, CONFIG.SAVE_INDICATOR_DURATION);
|
||||
} catch (error) {
|
||||
ErrorHandler.handle('save drawing', error);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
await this.loadNotes();
|
||||
this.lastSaved = true;
|
||||
setTimeout(() => {
|
||||
this.lastSaved = false;
|
||||
}, CONFIG.SAVE_INDICATOR_DURATION);
|
||||
} catch (error) {
|
||||
ErrorHandler.handle('save drawing', error);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
this._drawingSaveInFlight = false;
|
||||
if (this._drawingSaveQueued) {
|
||||
this._drawingSaveQueued = false;
|
||||
queueMicrotask(() => {
|
||||
if (this.currentMedia && this.currentMediaType === 'drawing') {
|
||||
this.drawingSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue