{"id":1314,"date":"2026-02-15T20:14:30","date_gmt":"2026-02-15T20:14:30","guid":{"rendered":"https:\/\/docs.ai.drawconclusions.org\/?page_id=1314"},"modified":"2026-02-24T12:45:55","modified_gmt":"2026-02-24T12:45:55","slug":"file-handling","status":"publish","type":"page","link":"https:\/\/docs.ai.drawconclusions.org\/?page_id=1314","title":{"rendered":"File Handling"},"content":{"rendered":"<h2>Overview<\/h2>\n<ul>\n<li>Manages user uploads and stored files.<\/li>\n<li>Upload metadata is stored in the <code>uploads<\/code> table.<\/li>\n<\/ul>\n<h2>Initiation<\/h2>\n<ul>\n<li>An uploads folder must exist and be non-web-accessible.<\/li>\n<li>The uploads base path must be defined in the Paths service.<\/li>\n<li>The uploads directory must allow upload\/delete\/view operations (read\/write\/execute permissions).<\/li>\n<li>The init command must create the uploads folder structure first.<\/li>\n<li>Verification test: <code>python_custom_14 - uploads_storage<\/code> (<code>server\/var_www\/tests\/custom_python\/server_checks\/uploads_storage.py<\/code>).<\/li>\n<\/ul>\n<h2>Storage Rules<\/h2>\n<h2>Storage<\/h2>\n<ul>\n<li>Store every upload under <code>uploads\/{userId}\/<\/code> (per-user folders).<\/li>\n<li>Stored filename must be the UUID from the <code>uploads<\/code> table (no original filenames in storage).<\/li>\n<li>Original filename, extension, MIME type, and size are stored as metadata in the <code>uploads<\/code> row.<\/li>\n<li>Uploads directory is non-public and lives at <code>\/var\/www\/symfony_be\/uploads<\/code>.<\/li>\n<li>Profile pictures use two file types: <code>profile_picture_original<\/code> and <code>profile_picture_cropped<\/code>.<\/li>\n<li>All uploaded images are converted to PNG during upload.<\/li>\n<li>Verification test: <code>python_custom_18 - profile picture upload<\/code> (<code>server\/var_www\/tests\/custom_python\/backend_profile_picture_upload.py<\/code>).<\/li>\n<\/ul>\n<h2>User Registration<\/h2>\n<h2>Registration<\/h2>\n<ul>\n<li>On user creation, ensure a per-user uploads directory exists under the uploads root.<\/li>\n<li>Directory name must be the user UUID.<\/li>\n<li>Create it if missing with permissions that allow upload\/delete\/view.<\/li>\n<li>Log an error if creation fails.<\/li>\n<li>Verification test: <code>python_custom_16 - user uploads dir<\/code> (<code>server\/var_www\/tests\/custom_python\/backend_user_uploads.py<\/code>).<\/li>\n<\/ul>\n<h2>User Deletion<\/h2>\n<h2>Deletion<\/h2>\n<ul>\n<li>On user deletion, remove the per-user uploads directory (uploads\/&lt;userId&gt;) recursively.<\/li>\n<li>Delete all uploads table rows for that user after removing the folder.<\/li>\n<li>Log an error if folder deletion or DB cleanup fails.<\/li>\n<li>Reference: <code>delete_user.md<\/code>.<\/li>\n<li>Verification test: <code>python_custom_17 - user uploads cleanup<\/code> (<code>server\/var_www\/tests\/custom_python\/backend_user_delete_uploads.py<\/code>).<\/li>\n<\/ul>\n<h2>Allowlist<\/h2>\n<ul>\n<li>Allowed file types: text files, common images, PDFs.<\/li>\n<li>Disallowed: videos, apps\/executables, archives.<\/li>\n<li>General allowlist lives in <code>server\/var_www\/symfony_be\/src\/Service\/FileHandlingService.php<\/code>.<\/li>\n<li>Profile picture allowlist lives in <code>server\/var_www\/symfony_be\/src\/Service\/FileHandlingProfileImageService.php<\/code>.<\/li>\n<li>Max upload size: 10 MB.<\/li>\n<\/ul>\n<h2>Upload Profile Picture<\/h2>\n<h2>Link<\/h2>\n<ul>\n<li>Feature details live in <code>upload_profile_picture.md<\/code> (ID: DOC-FILE-HANDLING-PROFILE-PICTURE).<\/li>\n<\/ul>\n<p class=\"developerdocs-id-search\" aria-hidden=\"true\">DeveloperDoc ID: dd_id_2e2a932cba<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Overview Manages user uploads and stored files. Upload metadata is stored in the uploads table. Initiation An uploads folder must exist and be non-web-accessible. The uploads base path must be defined in the Paths service. The uploads directory must allow upload\/delete\/view operations (read\/write\/execute permissions). The init command must create the uploads folder structure first. Verification [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":1289,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"doc_category":[],"doc_layer":[30],"class_list":["post-1314","page","type-page","status-publish","hentry","layer-dd_id_8f59177ca0"],"_links":{"self":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1314","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1314"}],"version-history":[{"count":5,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1314\/revisions"}],"predecessor-version":[{"id":3832,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1314\/revisions\/3832"}],"up":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1289"}],"wp:attachment":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1314"}],"wp:term":[{"taxonomy":"doc_category","embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fdoc_category&post=1314"},{"taxonomy":"doc_layer","embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fdoc_layer&post=1314"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}