Skip to content

Commit

Permalink
add new file endpoints("v1") to use query parameters instead of path …
Browse files Browse the repository at this point in the history
…parameters for proxy compatibility

Signed-off-by: bigcat88 <bigcat88@icloud.com>
  • Loading branch information
bigcat88 committed Feb 8, 2025
1 parent 5cbf797 commit 0c2067a
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 0 deletions.
129 changes: 129 additions & 0 deletions app/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,21 @@ def get_user_data_path(request, check_exists = False, param = "file"):

return path

def get_user_data_path_v1(request, check_exists=False, param="file"):
"""Reads a file-like parameter from the query string."""
file = request.query.get(param)
if not file:
return web.Response(status=400)

path = self.get_request_user_filepath(request, file)
if not path:
return web.Response(status=403)

if check_exists and not os.path.exists(path):
return web.Response(status=404)

return path

@routes.get("/userdata/{file}")
async def getuserdata(request):
path = get_user_data_path(request, check_exists=True)
Expand All @@ -219,6 +234,14 @@ async def getuserdata(request):

return web.FileResponse(path)

@routes.get("/v1/userdata/file")
async def getuserdata_v1(request):
path = get_user_data_path_v1(request, check_exists=True)
if not isinstance(path, str):
return path

return web.FileResponse(path)

@routes.post("/userdata/{file}")
async def post_userdata(request):
"""
Expand Down Expand Up @@ -268,6 +291,53 @@ async def post_userdata(request):

return web.json_response(resp)

@routes.post("/v1/userdata/file")
async def post_userdata_v1(request):
"""
Upload or update a user data file.
This endpoint handles file uploads to a user's data directory, with options for
controlling overwrite behavior and response format.
Query Parameters:
- file: The target file path (URL encoded if necessary).
- overwrite (optional): If "false", prevents overwriting existing files. Defaults to "true".
- full_info (optional): If "true", returns detailed file information (path, size, modified time).
If "false", returns only the relative file path.
Returns:
- 400: If 'file' parameter is missing.
- 403: If the requested path is not allowed.
- 409: If overwrite=false and the file already exists.
- 200: JSON response with either:
- Full file information (if full_info=true)
- Relative file path (if full_info=false)
The request body should contain the raw file content to be written.
"""
path = get_user_data_path_v1(request)
if not isinstance(path, str):
return path

overwrite = request.query.get("overwrite", 'true') != "false"
full_info = request.query.get("full_info", 'false').lower() == "true"

if not overwrite and os.path.exists(path):
return web.Response(status=409, text="File already exists")

body = await request.read()

with open(path, "wb") as f:
f.write(body)

user_path = self.get_request_user_filepath(request, None)
if full_info:
resp = get_file_info(path, user_path)
else:
resp = os.path.relpath(path, user_path)

return web.json_response(resp)

@routes.delete("/userdata/{file}")
async def delete_userdata(request):
path = get_user_data_path(request, check_exists=True)
Expand All @@ -278,6 +348,16 @@ async def delete_userdata(request):

return web.Response(status=204)

@routes.delete("/v1/userdata/file")
async def delete_userdata_v1(request):
path = get_user_data_path_v1(request, check_exists=True)
if not isinstance(path, str):
return path

os.remove(path)

return web.Response(status=204)

@routes.post("/userdata/{file}/move/{dest}")
async def move_userdata(request):
"""
Expand Down Expand Up @@ -328,3 +408,52 @@ async def move_userdata(request):
resp = os.path.relpath(dest, user_path)

return web.json_response(resp)

@routes.post("/v1/userdata/file/move")
async def move_userdata_v1(request):
"""
Move or rename a user data file.
This endpoint handles moving or renaming files within a user's data directory, with options for
controlling overwrite behavior and response format.
Query Parameters:
- source: The source file path (URL encoded if necessary)
- dest: The destination file path (URL encoded if necessary)
- overwrite (optional): If "false", prevents overwriting existing files. Defaults to "true".
- full_info (optional): If "true", returns detailed file information (path, size, modified time).
If "false", returns only the relative file path.
Returns:
- 400: If either 'file' or 'dest' parameter is missing
- 403: If either requested path is not allowed
- 404: If the source file does not exist
- 409: If overwrite=false and the destination file already exists
- 200: JSON response with either:
- Full file information (if full_info=true)
- Relative file path (if full_info=false)
"""
source = get_user_data_path_v1(request, check_exists=True, param="source")
if not isinstance(source, str):
return source

dest = get_user_data_path_v1(request, check_exists=False, param="dest")
if not isinstance(dest, str):
return dest

overwrite = request.query.get("overwrite", 'true') != "false"
full_info = request.query.get('full_info', 'false').lower() == "true"

if not overwrite and os.path.exists(dest):
return web.Response(status=409, text="File already exists")

logging.info(f"moving '{source}' -> '{dest}'")
shutil.move(source, dest)

user_path = self.get_request_user_filepath(request, None)
if full_info:
resp = get_file_info(dest, user_path)
else:
resp = os.path.relpath(dest, user_path)

return web.json_response(resp)
113 changes: 113 additions & 0 deletions tests-unit/prompt_server_test/user_manager_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,19 @@ async def test_post_userdata_new_file(aiohttp_client, app, tmp_path):
assert f.read() == content


async def test_post_userdata_new_file_v1(aiohttp_client, app, tmp_path):
client = await aiohttp_client(app)
content = b"test content"
resp = await client.post("/v1/userdata/file?file=test.txt", data=content)

assert resp.status == 200
assert await resp.text() == '"test.txt"'

# Verify file was created with correct content
with open(tmp_path / "test.txt", "rb") as f:
assert f.read() == content


async def test_post_userdata_overwrite_existing(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "test.txt", "w") as f:
Expand All @@ -148,6 +161,23 @@ async def test_post_userdata_overwrite_existing(aiohttp_client, app, tmp_path):
assert f.read() == new_content


async def test_post_userdata_overwrite_existing_v1(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "test.txt", "w") as f:
f.write("initial content")

client = await aiohttp_client(app)
new_content = b"updated content"
resp = await client.post("/v1/userdata/file?file=test.txt", data=new_content)

assert resp.status == 200
assert await resp.text() == '"test.txt"'

# Verify file was overwritten
with open(tmp_path / "test.txt", "rb") as f:
assert f.read() == new_content


async def test_post_userdata_no_overwrite(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "test.txt", "w") as f:
Expand All @@ -163,6 +193,21 @@ async def test_post_userdata_no_overwrite(aiohttp_client, app, tmp_path):
assert f.read() == "initial content"


async def test_post_userdata_no_overwrite_v1(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "test.txt", "w") as f:
f.write("initial content")

client = await aiohttp_client(app)
resp = await client.post("/v1/userdata/file?file=test.txt&overwrite=false", data=b"new content")

assert resp.status == 409

# Verify original content unchanged
with open(tmp_path / "test.txt", "r") as f:
assert f.read() == "initial content"


async def test_post_userdata_full_info(aiohttp_client, app, tmp_path):
client = await aiohttp_client(app)
content = b"test content"
Expand All @@ -175,6 +220,18 @@ async def test_post_userdata_full_info(aiohttp_client, app, tmp_path):
assert "modified" in result


async def test_post_userdata_full_info_v1(aiohttp_client, app, tmp_path):
client = await aiohttp_client(app)
content = b"test content"
resp = await client.post("/v1/userdata/file?file=test.txt&full_info=true", data=content)

assert resp.status == 200
result = await resp.json()
assert result["path"] == "test.txt"
assert result["size"] == len(content)
assert "modified" in result


async def test_move_userdata(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "source.txt", "w") as f:
Expand All @@ -192,6 +249,23 @@ async def test_move_userdata(aiohttp_client, app, tmp_path):
assert f.read() == "test content"


async def test_move_userdata_v1(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "source.txt", "w") as f:
f.write("test content")

client = await aiohttp_client(app)
resp = await client.post("/v1/userdata/file/move?source=source.txt&dest=dest.txt")

assert resp.status == 200
assert await resp.text() == '"dest.txt"'

# Verify file was moved
assert not os.path.exists(tmp_path / "source.txt")
with open(tmp_path / "dest.txt", "r") as f:
assert f.read() == "test content"


async def test_move_userdata_no_overwrite(aiohttp_client, app, tmp_path):
# Create source and destination files
with open(tmp_path / "source.txt", "w") as f:
Expand All @@ -211,6 +285,25 @@ async def test_move_userdata_no_overwrite(aiohttp_client, app, tmp_path):
assert f.read() == "destination content"


async def test_move_userdata_no_overwrite_v1(aiohttp_client, app, tmp_path):
# Create source and destination files
with open(tmp_path / "source.txt", "w") as f:
f.write("source content")
with open(tmp_path / "dest.txt", "w") as f:
f.write("destination content")

client = await aiohttp_client(app)
resp = await client.post("/v1/userdata/file/move?source=source.txt&dest=dest.txt&overwrite=false")

assert resp.status == 409

# Verify files remain unchanged
with open(tmp_path / "source.txt", "r") as f:
assert f.read() == "source content"
with open(tmp_path / "dest.txt", "r") as f:
assert f.read() == "destination content"


async def test_move_userdata_full_info(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "source.txt", "w") as f:
Expand All @@ -229,3 +322,23 @@ async def test_move_userdata_full_info(aiohttp_client, app, tmp_path):
assert not os.path.exists(tmp_path / "source.txt")
with open(tmp_path / "dest.txt", "r") as f:
assert f.read() == "test content"


async def test_move_userdata_full_info_v1(aiohttp_client, app, tmp_path):
# Create initial file
with open(tmp_path / "source.txt", "w") as f:
f.write("test content")

client = await aiohttp_client(app)
resp = await client.post("/v1/userdata/file/move?source=source.txt&dest=dest.txt&full_info=true")

assert resp.status == 200
result = await resp.json()
assert result["path"] == "dest.txt"
assert result["size"] == len("test content")
assert "modified" in result

# Verify file was moved
assert not os.path.exists(tmp_path / "source.txt")
with open(tmp_path / "dest.txt", "r") as f:
assert f.read() == "test content"

0 comments on commit 0c2067a

Please sign in to comment.