|
16 | 16 | # along with this program; if not, write to the Free Software Foundation, |
17 | 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | 18 |
|
| 19 | +import io |
| 20 | + |
19 | 21 | from marshmallow import ValidationError |
20 | 22 | from datetime import datetime |
21 | 23 | from flask import Blueprint |
22 | 24 | from flask import request |
| 25 | +from flask import send_file |
| 26 | +from werkzeug.utils import secure_filename |
23 | 27 |
|
24 | 28 | from app.db import db |
25 | 29 | from app import app |
|
62 | 66 | case_notes_rest_blueprint = Blueprint('case_notes_rest', __name__) |
63 | 67 |
|
64 | 68 |
|
| 69 | +def _note_export_filename(title, note_id): |
| 70 | + safe_title = secure_filename(title or '') |
| 71 | + if safe_title.lower().endswith('.md'): |
| 72 | + safe_title = safe_title[:-3] |
| 73 | + safe_title = safe_title[:120].rstrip('._-') or f'note-{note_id}' |
| 74 | + return f'{safe_title}.md' |
| 75 | + |
| 76 | + |
65 | 77 | @case_notes_rest_blueprint.route('/case/notes/<int:cur_id>', methods=['GET']) |
66 | 78 | @endpoint_deprecated('GET', '/api/v2/cases/{case_identifier}/notes/{identifier}') |
67 | 79 | @ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) |
@@ -112,6 +124,28 @@ def case_note_detail(cur_id, caseid): |
112 | 124 | return response_error(msg="Data error", data=e.messages) |
113 | 125 |
|
114 | 126 |
|
| 127 | +@case_notes_rest_blueprint.route('/case/notes/<int:cur_id>/export', methods=['GET']) |
| 128 | +@ac_requires_case_identifier(CaseAccessLevel.read_only, CaseAccessLevel.full_access) |
| 129 | +@ac_api_requires() |
| 130 | +def case_note_export(cur_id, caseid): |
| 131 | + note = get_note(cur_id) |
| 132 | + if not note or note.note_case_id != caseid: |
| 133 | + return response_error(msg="Invalid note ID") |
| 134 | + |
| 135 | + content = (note.note_content or '').encode('utf-8') |
| 136 | + response = send_file( |
| 137 | + io.BytesIO(content), |
| 138 | + mimetype='text/markdown; charset=utf-8', |
| 139 | + as_attachment=True, |
| 140 | + download_name=_note_export_filename(note.note_title, note.note_id), |
| 141 | + max_age=0, |
| 142 | + ) |
| 143 | + response.cache_control.no_store = True |
| 144 | + response.cache_control.private = True |
| 145 | + response.headers['X-Content-Type-Options'] = 'nosniff' |
| 146 | + return response |
| 147 | + |
| 148 | + |
115 | 149 | @case_notes_rest_blueprint.route('/case/notes/delete/<int:cur_id>', methods=['POST']) |
116 | 150 | @endpoint_deprecated('DELETE', '/api/v2/cases/{case_identifier}/notes/{identifier}') |
117 | 151 | @ac_requires_case_identifier(CaseAccessLevel.full_access) |
|
0 commit comments