2121
2222import hashlib
2323import io
24+ import json
2425import os
2526import smtplib
2627import socket
2930from http import HTTPStatus
3031from typing import BinaryIO , List , Optional , Sequence , Tuple
3132
32- from flask import abort , current_app , g , jsonify , make_response , request
33+ from flask import Response , abort , current_app , g , jsonify , make_response , request
3334from flask_jwt_extended import get_jwt
3435from gramps .cli .clidbman import NAME_FILE , CLIDbManager
3536from gramps .gen .config import config
@@ -58,8 +59,16 @@ class Parser(FlaskParser):
5859
5960 def handle_error (self , error , req , schema , * , error_status_code , error_headers ):
6061 status_code = error_status_code or self .DEFAULT_VALIDATION_STATUS
62+ pretty_message = "" .join ([c for c in str (error .messages ) if c not in "{}[]()'" ])
63+ payload = {
64+ "error" : {
65+ "code" : status_code ,
66+ "message" : pretty_message ,
67+ "messages" : error .messages ,
68+ }
69+ }
6170 abort (
62- make_response (jsonify (error . messages ), status_code ),
71+ make_response (jsonify (payload ), status_code ),
6372 exc = error ,
6473 messages = error .messages ,
6574 schema = schema ,
@@ -135,7 +144,9 @@ def get_db_outside_request(tree: str, view_private: bool, readonly: bool) -> DbR
135144 if not view_private :
136145 if not readonly :
137146 # requesting write access on a private proxy DB is impossible & forbidden!
138- abort (HTTPStatus .FORBIDDEN )
147+ abort_with_message (
148+ HTTPStatus .FORBIDDEN , "Cannot write to a private proxy database"
149+ )
139150 # if we're not authorized to view private records,
140151 # return a proxy DB instead of the real one
141152 return ModifiedPrivateProxyDb (dbstate .db )
@@ -168,7 +179,9 @@ def get_db_handle(readonly: bool = True) -> DbReadBase:
168179 if not view_private :
169180 if not readonly :
170181 # requesting write access on a private proxy DB is impossible & forbidden!
171- abort (HTTPStatus .FORBIDDEN )
182+ abort_with_message (
183+ HTTPStatus .FORBIDDEN , "Cannot write to a private proxy database"
184+ )
172185 return ModifiedPrivateProxyDb (g .db )
173186
174187 if not readonly and "db_write" not in g :
@@ -218,7 +231,7 @@ def get_buffer_for_file(filename: str, delete=True, not_found=False) -> BinaryIO
218231 except FileNotFoundError :
219232 if not_found :
220233 raise FileNotFoundError
221- abort (500 )
234+ abort_with_message (500 , "File not found" )
222235 if delete :
223236 os .remove (filename )
224237 return buffer
@@ -282,7 +295,7 @@ def make_cache_key_thumbnails(*args, **kwargs):
282295 try :
283296 obj = db_handle .get_media_from_handle (handle )
284297 except HandleError :
285- abort (404 )
298+ abort_with_message (404 , f"Handle { handle } not found" )
286299 # checksum in the DB
287300 checksum = obj .checksum
288301
@@ -319,7 +332,7 @@ def get_tree_id(guid: str) -> str:
319332 if not tree_id :
320333 if current_app .config ["TREE" ] == TREE_MULTI :
321334 # multi-tree support enabled but user has no tree ID: forbidden!
322- abort (403 )
335+ abort_with_message (403 , "Forbidden" )
323336 # needed for backwards compatibility: single-tree mode but user without tree ID
324337 dbmgr = WebDbManager (name = current_app .config ["TREE" ], create_if_missing = False )
325338 tree_id = dbmgr .dirname
@@ -343,25 +356,42 @@ def tree_exists(tree_id: str) -> bool:
343356 return True
344357
345358
346- def update_usage_people () -> int :
359+ def update_usage_people (tree : Optional [ str ] = None ) -> int :
347360 """Update the usage of people."""
348- tree = get_tree_from_jwt ()
349- db_handle = get_db_handle ()
361+ if not tree :
362+ tree = get_tree_from_jwt ()
363+ db_handle = get_db_outside_request (
364+ tree = tree ,
365+ view_private = True ,
366+ readonly = True ,
367+ )
350368 usage_people = db_handle .get_number_of_people ()
351369 set_tree_usage (tree , usage_people = usage_people )
352370 return usage_people
353371
354372
355- def check_quota_people (to_add : int ) -> None :
373+ def check_quota_people (to_add : int , tree : Optional [ str ] = None ) -> None :
356374 """Check whether the quota allows adding `to_add` people and abort if not."""
357- tree = get_tree_from_jwt ()
375+ if not tree :
376+ tree = get_tree_from_jwt ()
358377 usage_dict = get_tree_usage (tree )
359378 if not usage_dict or usage_dict .get ("usage_people" ) is None :
360- update_usage_people ()
379+ update_usage_people (tree = tree )
361380 usage_dict = get_tree_usage (tree )
362381 usage = usage_dict ["usage_people" ]
363382 quota = usage_dict .get ("quota_people" )
364383 if quota is None :
365384 return
366385 if usage + to_add > quota :
367- abort (405 )
386+ abort_with_message (405 , "Not allowed by people quota" )
387+
388+
389+ def abort_with_message (status : int , message : str ):
390+ """Abort with a JSON response."""
391+ payload = {"error" : {"code" : status , "message" : message }}
392+ response = Response (
393+ response = json .dumps (payload ),
394+ status = status ,
395+ mimetype = "application/json" ,
396+ )
397+ abort (response )
0 commit comments