Skip to content

Commit

Permalink
confirm: true mechanism for drop table API, closes #1887
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Nov 14, 2022
1 parent db79677 commit 612da8e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 8 deletions.
2 changes: 1 addition & 1 deletion datasette/views/special.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ async def example_links(self, request):
{
"path": self.ds.urls.table(name, table) + "/-/drop",
"label": "Drop table {}".format(table),
"json": {},
"json": {"confirm": False},
"method": "POST",
}
)
Expand Down
20 changes: 20 additions & 0 deletions datasette/views/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,26 @@ async def post(self, request):
request.actor, "drop-table", resource=(database_name, table_name)
):
return _error(["Permission denied"], 403)

confirm = False
try:
data = json.loads(await request.post_body())
confirm = data.get("confirm")
except json.JSONDecodeError as e:
pass

if not confirm:
return Response.json(
{
"ok": True,
"row_count": (
await db.execute("select count(*) from [{}]".format(table_name))
).single_value(),
"message": 'Pass "confirm": true to confirm',
},
status=200,
)

# Drop table
def drop_table(conn):
sqlite_utils.Database(conn)[table_name].drop()
Expand Down
20 changes: 19 additions & 1 deletion docs/json_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,24 @@ To drop a table, make a ``POST`` to ``/<database>/<table>/-/drop``. This require
Content-Type: application/json
Authorization: Bearer dstok_<rest-of-token>

If successful, this will return a ``200`` status code and a ``{"ok": true}`` response body.
Without a POST body this will return a status ``200`` with a note about how many rows will be deleted:

.. code-block:: json
{
"ok": true,
"row_count": 5,
"message": "Pass \"confirm\": true to confirm"
}
If you pass the following POST body:

.. code-block:: json
{
"confirm": true
}
Then the table will be dropped and a status ``200`` response of ``{"ok": true}`` will be returned.

Any errors will return ``{"errors": ["... descriptive message ..."], "ok": false}``, and a ``400`` status code for a bad input or a ``403`` status code for an authentication or permission error.
30 changes: 24 additions & 6 deletions tests/test_api_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,9 @@ async def test_drop_table(ds_write, scenario):
else:
token = write_token(ds_write)
should_work = scenario == "has_perm"

await ds_write.get_database("data").execute_write(
"insert into docs (id, title) values (1, 'Row 1')"
)
path = "/data/{}/-/drop".format("docs" if scenario != "bad_table" else "bad_table")
response = await ds_write.client.post(
path,
Expand All @@ -357,11 +359,7 @@ async def test_drop_table(ds_write, scenario):
"Content-Type": "application/json",
},
)
if should_work:
assert response.status_code == 200
assert response.json() == {"ok": True}
assert (await ds_write.client.get("/data/docs")).status_code == 404
else:
if not should_work:
assert (
response.status_code == 403
if scenario in ("no_token", "bad_token")
Expand All @@ -374,3 +372,23 @@ async def test_drop_table(ds_write, scenario):
else ["Table not found: bad_table"]
)
assert (await ds_write.client.get("/data/docs")).status_code == 200
else:
# It should show a confirmation page
assert response.status_code == 200
assert response.json() == {
"ok": True,
"row_count": 1,
"message": 'Pass "confirm": true to confirm',
}
assert (await ds_write.client.get("/data/docs")).status_code == 200
# Now send confirm: true
response2 = await ds_write.client.post(
path,
json={"confirm": True},
headers={
"Authorization": "Bearer {}".format(token),
"Content-Type": "application/json",
},
)
assert response2.json() == {"ok": True}
assert (await ds_write.client.get("/data/docs")).status_code == 404

0 comments on commit 612da8e

Please sign in to comment.