# retoor from .._shared import endpoint, field GROUP = { "slug": "project-files", "title": "Project Filesystem", "intro": """ # Project Filesystem Each project carries a full virtual filesystem - directories and files - so a project can hold a complete software project. Reading is public (anyone can browse a project's tree); creating, editing, uploading, moving and deleting require the project owner. Text files are editable inline; binary files are uploaded and served from `/static/uploads/project_files/...`. Paths are relative POSIX paths inside the project (for example `src/main.py`). Parent directories are created automatically on write, upload and mkdir. Paths containing `..`, null bytes or empty segments are rejected. Every endpoint follows the shared [Conventions & Errors](/docs/conventions.html) (auth, content negotiation, status codes); see [Authentication](/docs/authentication.html) for the four ways to sign requests. """, "endpoints": [ endpoint( id="project-files-list", method="GET", path="/projects/{project_slug}/files", title="List a project's files", summary="Return the flat list of files and directories in a project.", auth="public", params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ) ], sample_response={ "project": {"uid": "PROJECT_UID", "slug": "PROJECT_SLUG"}, "files": [ { "path": "src/main.py", "name": "main.py", "type": "file", "is_binary": False, "size": 42, } ], "is_owner": False, }, ), endpoint( id="project-files-raw", method="GET", path="/projects/{project_slug}/files/raw", title="Read a project file", summary="Return one file's metadata and (for text files) its content.", auth="public", params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "query", "string", True, "src/main.py", "Relative file path inside the project.", ), ], sample_response={ "path": "src/main.py", "name": "main.py", "type": "file", "is_binary": False, "mime_type": "text/plain", "size": 42, "url": None, "content": "print('hello')\n", }, ), endpoint( id="project-files-write", method="POST", path="/projects/{project_slug}/files/write", title="Write a text file", summary="Create or overwrite a text file; parent directories are created automatically.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/main.py", "Relative file path.", ), field( "content", "form", "textarea", True, "print('hello')", "Full file content (max 400000 chars).", ), ], notes=["Owner only; non-owners get `403`. Invalid paths return `400`."], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/main.py", "type": "file"}, }, ), endpoint( id="project-files-lines", method="GET", path="/projects/{project_slug}/files/lines", title="Read a line range", summary="Read a 1-indexed inclusive line range of a text file. Returns lines plus total_lines for targeting edits.", auth="public", params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "query", "string", True, "src/main.py", "Relative file path.", ), field( "start", "query", "integer", False, "1", "First line, 1-indexed (default 1).", ), field( "end", "query", "integer", False, "50", "Last line inclusive; omit or -1 for end of file.", ), ], notes=[ "Text files only; binary, directory, or missing paths return `404`." ], sample_response={ "path": "src/main.py", "start": 1, "end": 2, "total_lines": 2, "lines": ["import os", "print(os.getcwd())"], "content": "import os\nprint(os.getcwd())", }, ), endpoint( id="project-files-replace-lines", method="POST", path="/projects/{project_slug}/files/replace-lines", title="Replace a line range", summary="Replace lines start..end (inclusive) with new content; empty content deletes the range. Leaves the rest of the file untouched.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/main.py", "Relative file path.", ), field( "start", "form", "integer", True, "10", "First line to replace (1-indexed).", ), field( "end", "form", "integer", True, "12", "Last line to replace (inclusive).", ), field( "content", "form", "textarea", False, "new code", "Replacement text (empty deletes the range).", ), ], notes=[ "Owner only. The preferred way to edit a large file; avoids rewriting the whole file." ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/main.py", "type": "file"}, }, ), endpoint( id="project-files-insert-lines", method="POST", path="/projects/{project_slug}/files/insert-lines", title="Insert lines", summary="Insert content before a 1-indexed line. Use at=1 to prepend and at=total_lines+1 to append.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/main.py", "Relative file path.", ), field( "at", "form", "integer", True, "1", "Insert before this 1-indexed line.", ), field( "content", "form", "textarea", True, "# header", "Text to insert.", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/main.py", "type": "file"}, }, ), endpoint( id="project-files-delete-lines", method="POST", path="/projects/{project_slug}/files/delete-lines", title="Delete a line range", summary="Delete lines start..end (inclusive) from a text file.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/main.py", "Relative file path.", ), field( "start", "form", "integer", True, "5", "First line to delete (1-indexed).", ), field( "end", "form", "integer", True, "7", "Last line to delete (inclusive).", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/main.py", "type": "file"}, }, ), endpoint( id="project-files-append", method="POST", path="/projects/{project_slug}/files/append", title="Append to a file", summary="Append content as new lines at the end of a text file; grow a large file across calls without resending it.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "log.txt", "Relative file path." ), field( "content", "form", "textarea", True, "next chunk", "Text to append.", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "log.txt", "type": "file"}, }, ), endpoint( id="project-files-upload", method="POST", path="/projects/{project_slug}/files/upload", title="Upload a file into a project", summary="Upload a file into a directory (parents created); text decodes to an editable file, otherwise stored as binary.", auth="user", encoding="multipart", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field("file", "form", "file", True, "", "The file to upload."), field( "path", "form", "string", False, "assets", "Target directory, empty for the root.", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": { "path": "assets/logo.png", "type": "file", "is_binary": True, }, }, ), endpoint( id="project-files-mkdir", method="POST", path="/projects/{project_slug}/files/mkdir", title="Create a directory", summary="Create a directory and any missing parents.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/components", "Relative directory path.", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/components", "type": "dir"}, }, ), endpoint( id="project-files-move", method="POST", path="/projects/{project_slug}/files/move", title="Move or rename", summary="Move or rename a file or directory (and its descendants).", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "from_path", "form", "string", True, "src/old.py", "Existing path.", ), field("to_path", "form", "string", True, "src/new.py", "New path."), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/new.py"}, }, ), endpoint( id="project-files-delete", method="POST", path="/projects/{project_slug}/files/delete", title="Delete a file or directory", summary="Delete a file, or a directory and everything under it. Project owner or an administrator; soft-deleted (restorable from admin trash). Blocked while the project is read-only.", auth="user", encoding="form", destructive=True, params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "form", "string", True, "src/old.py", "Relative path to delete.", ), ], sample_response={ "ok": True, "redirect": "/projects/PROJECT_SLUG/files", "data": {"path": "src/old.py"}, }, ), endpoint( id="project-files-zip", method="POST", path="/projects/{project_slug}/files/zip", title="Queue a zip of files", summary="Archive the whole tree, or a subtree via the path query. Returns the job uid and status URL to poll with /zips/{uid}.", auth="public", params=[ field( "project_slug", "path", "string", True, "PROJECT_SLUG", "Project slug or uid.", ), field( "path", "query", "string", False, "src", "Relative file or directory to archive; empty for the whole project.", ), ], sample_response={ "uid": "ZIP_JOB_UID", "status_url": "/zips/ZIP_JOB_UID", }, ), ], }