# retoor <retoor@molodetz.nl>
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",
},
),
],
}