Skip to content
Merged

Perff #133

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions backend/src/platform/isolationEngine/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,38 @@ def migrate_schema(self, template_schema: str, target_schema: str) -> None:
)
meta.create_all(translated)

# Ensure newer columns exist even if template was seeded before they
# were added to the ORM models. Uses IF NOT EXISTS so it's safe to
# run repeatedly.
self._ensure_box_columns(target_schema)

# Copy GIN / non-standard indexes that MetaData.reflect doesn't capture
self._copy_custom_indexes(template_schema, target_schema)

self._set_replica_identity(target_schema)

def _ensure_box_columns(self, schema: str) -> None:
"""Add columns that may be missing from older template snapshots."""
_columns = [
# (table, column, SQL type, default)
("box_folders", "path", "VARCHAR(500)", "'/'"),
("box_files", "path", "VARCHAR(500)", "'/0/'"),
]
with self.session_manager.base_engine.begin() as conn:
for table, col, sql_type, default in _columns:
try:
conn.execute(
text(
f"ALTER TABLE {schema}.{table} "
f"ADD COLUMN IF NOT EXISTS {col} {sql_type} "
f"DEFAULT {default}"
)
)
except Exception as exc:
logger.warning(
f"Could not ensure column {schema}.{table}.{col}: {exc}"
)

def _copy_custom_indexes(self, src_schema: str, dst_schema: str) -> None:
"""Copy GIN trigram and other custom indexes from template to target schema."""
with self.session_manager.base_engine.begin() as conn:
Expand Down
178 changes: 178 additions & 0 deletions backend/src/services/box/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,82 @@ async def list_file_comments(request: Request) -> Response:
return _error_response(e)


async def get_comment_by_id(request: Request) -> Response:
"""
GET /2.0/comments/{comment_id}

Retrieves a specific comment.

SDK Reference: CommentsManager.get_comment_by_id()
"""
try:
session = _session(request)
comment_id = request.path_params["comment_id"]
fields = _parse_fields(request)

comment = ops.get_comment_by_id(session, comment_id)
if not comment:
_box_error(BoxErrorCode.NOT_FOUND, "Not Found")

comment_data = comment.to_dict()
filtered_data = _filter_fields(comment_data, fields)

return _json_response(filtered_data)

except BoxAPIError as e:
return _error_response(e)


async def update_comment_by_id(request: Request) -> Response:
"""
PUT /2.0/comments/{comment_id}

Updates a comment's message.

SDK Reference: CommentsManager.update_comment_by_id()
"""
try:
session = _session(request)
comment_id = request.path_params["comment_id"]
fields = _parse_fields(request)

body = await _parse_json_body(request)
message = body.get("message")

comment = ops.update_comment(session, comment_id, message=message)

comment_data = comment.to_dict()
filtered_data = _filter_fields(comment_data, fields)

return _json_response(filtered_data)

except BoxAPIError as e:
return _error_response(e)


async def delete_comment_by_id(request: Request) -> Response:
"""
DELETE /2.0/comments/{comment_id}

Deletes a comment.

SDK Reference: CommentsManager.delete_comment_by_id()

Returns:
204 No Content on success
"""
try:
session = _session(request)
comment_id = request.path_params["comment_id"]

ops.delete_comment(session, comment_id)

return Response(status_code=status.HTTP_204_NO_CONTENT)

except BoxAPIError as e:
return _error_response(e)


# Task Endpoints


Expand Down Expand Up @@ -1567,6 +1643,102 @@ async def create_task(request: Request) -> Response:
return _error_response(e)


async def get_task_by_id(request: Request) -> Response:
"""
GET /2.0/tasks/{task_id}

Retrieves a specific task.

SDK Reference: TasksManager.get_task_by_id()
"""
try:
session = _session(request)
task_id = request.path_params["task_id"]
fields = _parse_fields(request)

task = ops.get_task_by_id(session, task_id)
if not task:
_box_error(BoxErrorCode.NOT_FOUND, "Not Found")

task_data = task.to_dict()
filtered_data = _filter_fields(task_data, fields)

return _json_response(filtered_data)

except BoxAPIError as e:
return _error_response(e)


async def update_task_by_id(request: Request) -> Response:
"""
PUT /2.0/tasks/{task_id}

Updates a task.

SDK Reference: TasksManager.update_task_by_id()
"""
try:
session = _session(request)
task_id = request.path_params["task_id"]
fields = _parse_fields(request)

body = await _parse_json_body(request)

action = body.get("action")
message = body.get("message")
due_at_str = body.get("due_at")
completion_rule = body.get("completion_rule")

due_at = None
if due_at_str:
from datetime import datetime

try:
due_at = datetime.fromisoformat(due_at_str.replace("Z", "+00:00"))
except (ValueError, AttributeError):
_box_error(BoxErrorCode.BAD_REQUEST, "Invalid 'due_at' format")

task = ops.update_task(
session,
task_id,
action=action,
message=message,
due_at=due_at,
completion_rule=completion_rule,
)

task_data = task.to_dict()
filtered_data = _filter_fields(task_data, fields)

return _json_response(filtered_data)

except BoxAPIError as e:
return _error_response(e)


async def delete_task_by_id(request: Request) -> Response:
"""
DELETE /2.0/tasks/{task_id}

Deletes a task.

SDK Reference: TasksManager.delete_task_by_id()

Returns:
204 No Content on success
"""
try:
session = _session(request)
task_id = request.path_params["task_id"]

ops.delete_task(session, task_id)

return Response(status_code=status.HTTP_204_NO_CONTENT)

except BoxAPIError as e:
return _error_response(e)


# Hub Endpoints (Requires box-version: 2025.0 header)


Expand Down Expand Up @@ -2079,8 +2251,14 @@ async def get_collection_items(request: Request) -> Response:
Route("/files/{file_id}/tasks", list_file_tasks, methods=["GET"]),
# Comments
Route("/comments", create_comment, methods=["POST"]),
Route("/comments/{comment_id}", get_comment_by_id, methods=["GET"]),
Route("/comments/{comment_id}", update_comment_by_id, methods=["PUT"]),
Route("/comments/{comment_id}", delete_comment_by_id, methods=["DELETE"]),
# Tasks
Route("/tasks", create_task, methods=["POST"]),
Route("/tasks/{task_id}", get_task_by_id, methods=["GET"]),
Route("/tasks/{task_id}", update_task_by_id, methods=["PUT"]),
Route("/tasks/{task_id}", delete_task_by_id, methods=["DELETE"]),
# Hubs (requires box-version: 2025.0 header)
Route("/hubs", list_hubs, methods=["GET"]),
Route("/hubs", create_hub, methods=["POST"]),
Expand Down