diff --git a/datasette/views/special.py b/datasette/views/special.py index 468680d3..1d9e557e 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -336,7 +336,7 @@ class ApiExplorerView(BaseView): { "path": self.ds.urls.table(name, table) + "/-/drop", "label": "Drop table {}".format(table), - "json": {}, + "json": {"confirm": False}, "method": "POST", } ) diff --git a/datasette/views/table.py b/datasette/views/table.py index 5c75ffbb..298b63c7 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -1236,6 +1236,26 @@ class TableDropView(BaseView): 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() diff --git a/docs/json_api.rst b/docs/json_api.rst index 34c13211..61e14589 100644 --- a/docs/json_api.rst +++ b/docs/json_api.rst @@ -572,6 +572,24 @@ To drop a table, make a ``POST`` to ``///-/drop``. This require Content-Type: application/json Authorization: Bearer dstok_ -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. diff --git a/tests/test_api_write.py b/tests/test_api_write.py index 0b567f48..98792d03 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -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, @@ -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") @@ -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