From 8d1c205d8e7335ef7c292afb125a7270976f327b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 10 Aug 2026 19:46:46 -0600 Subject: [PATCH] refac --- backend/open_webui/models/notes.py | 45 +++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/models/notes.py b/backend/open_webui/models/notes.py index c651c6f51e..e9c06021e1 100644 --- a/backend/open_webui/models/notes.py +++ b/backend/open_webui/models/notes.py @@ -7,7 +7,8 @@ from open_webui.internal.db import Base, get_async_db_context from open_webui.models.access_grants import AccessGrantModel, AccessGrants from open_webui.models.groups import Groups from open_webui.models.users import User, UserModel, UserResponse, Users -from pydantic import BaseModel, ConfigDict, Field +from open_webui.utils.json_codec import JSONCodec +from pydantic import BaseModel, ConfigDict, Field, field_validator from sqlalchemy import JSON, BigInteger, Boolean, Column, ForeignKey, Text, delete, func, or_, select, update from sqlalchemy.ext.asyncio import AsyncSession @@ -30,6 +31,32 @@ class Note(Base): updated_at = Column(BigInteger) +def sanitize_note_data(data: Optional[dict]) -> Optional[dict]: + """Sanitize malformed note.data so content.md is always markdown text.""" + if data is None: + return None + if not isinstance(data, dict): + return {'content': {'md': str(data)}} + + content = data.get('content') + if not isinstance(content, dict) or 'md' not in content or isinstance(content.get('md'), str): + return data + + md = content.get('md') if content.get('md') is not None else '' + if isinstance(md, (dict, list)): + md = f'```json\n{JSONCodec.dumps(md, indent=2, ensure_ascii=False)}\n```' + else: + md = str(md) + + return { + **data, + 'content': { + **content, + 'md': md, + }, + } + + class NoteModel(BaseModel): model_config = ConfigDict(from_attributes=True) @@ -46,6 +73,11 @@ class NoteModel(BaseModel): created_at: int # timestamp in epoch updated_at: int # timestamp in epoch + @field_validator('data', mode='before') + @classmethod + def sanitize_data(cls, data): + return sanitize_note_data(data) + class PinnedNote(Base): __tablename__ = 'pinned_note' @@ -67,6 +99,11 @@ class NoteForm(BaseModel): meta: Optional[dict] = None access_grants: Optional[list[dict]] = None + @field_validator('data', mode='before') + @classmethod + def sanitize_data(cls, data): + return sanitize_note_data(data) + class NoteUpdateForm(BaseModel): title: Optional[str] = None @@ -74,6 +111,11 @@ class NoteUpdateForm(BaseModel): meta: Optional[dict] = None access_grants: Optional[list[dict]] = None + @field_validator('data', mode='before') + @classmethod + def sanitize_data(cls, data): + return sanitize_note_data(data) + class NoteUserResponse(NoteModel): user: Optional[UserResponse] = None @@ -304,6 +346,7 @@ class NoteTable: return None form_data = form_data.model_dump(exclude_unset=True) + note.data = sanitize_note_data(note.data) or {} if 'title' in form_data: note.title = form_data['title']