Skip to content

Commit 875590c

Browse files
committed
import route and schema
1 parent ac85cf4 commit 875590c

5 files changed

Lines changed: 109 additions & 8 deletions

File tree

src/opengeodeweb_back/routes/blueprint_routes.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Standard library imports
22
import os
33
import time
4+
import shutil
45

56
# Third party imports
67
import flask
@@ -286,12 +287,61 @@ def export_project() -> flask.Response:
286287
filename: str = params.filename or f"project_{int(time.time())}.zip"
287288
export_zip_path = os.path.join(upload_folder, filename)
288289

289-
with zipfile.ZipFile(export_zip_path, "w", compression=8) as zipf:
290+
with zipfile.ZipFile(export_zip_path, "w", compression=8) as zip_file:
290291
pattern = os.path.join(data_folder_path, "**", "*")
291292
for full_path in glob.glob(pattern, recursive=True):
292293
if os.path.isfile(full_path):
293294
archive_name = os.path.relpath(full_path, start=data_folder_path)
294-
zipf.write(full_path, archive_name)
295-
zipf.writestr("snapshot.json", flask.json.dumps(params.snapshot))
295+
zip_file.write(full_path, archive_name)
296+
zip_file.writestr("snapshot.json", flask.json.dumps(params.snapshot))
296297

297298
return utils_functions.send_file(upload_folder, [export_zip_path], filename)
299+
300+
301+
@routes.route(
302+
schemas_dict["import_project"]["route"],
303+
methods=schemas_dict["import_project"]["methods"],
304+
)
305+
def import_project() -> flask.Response:
306+
if flask.request.method == "OPTIONS":
307+
return flask.make_response({}, 200)
308+
if "file" not in flask.request.files:
309+
flask.abort(400, "No zip file provided under 'file'")
310+
311+
zip_file = flask.request.files["file"]
312+
filename = werkzeug.utils.secure_filename(os.path.basename(zip_file.filename))
313+
if not filename.lower().endswith(".zip"):
314+
flask.abort(400, "Uploaded file must be a .zip")
315+
316+
data_folder_path: str = flask.current_app.config["DATA_FOLDER_PATH"]
317+
os.makedirs(data_folder_path, exist_ok=True)
318+
for entry in os.listdir(data_folder_path):
319+
entry_path = os.path.join(data_folder_path, entry)
320+
try:
321+
if os.path.isdir(entry_path):
322+
shutil.rmtree(entry_path, ignore_errors=True)
323+
else:
324+
os.remove(entry_path)
325+
except FileNotFoundError:
326+
pass
327+
except PermissionError:
328+
flask.abort(423, "Project files are locked; cannot overwrite")
329+
330+
zip_file.stream.seek(0)
331+
with zipfile.ZipFile(zip_file.stream) as zf:
332+
base = os.path.abspath(data_folder_path)
333+
for member in zf.namelist():
334+
target = os.path.abspath(
335+
os.path.normpath(os.path.join(base, member))
336+
)
337+
if not (target == base or target.startswith(base + os.sep)):
338+
flask.abort(400, "Zip contains unsafe paths")
339+
zf.extractall(data_folder_path)
340+
snapshot = {}
341+
try:
342+
raw = zf.read("snapshot.json").decode("utf-8")
343+
snapshot = flask.json.loads(raw)
344+
except KeyError:
345+
snapshot = {}
346+
347+
return flask.make_response({"snapshot": snapshot}, 200)

src/opengeodeweb_back/routes/schemas/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
from .allowed_objects import *
1414
from .allowed_files import *
1515
from .export_project import *
16+
from .import_project import *
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"route": "/import_project",
3+
"methods": [
4+
"POST"
5+
],
6+
"type": "object",
7+
"properties": {},
8+
"required": [],
9+
"additionalProperties": false
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from dataclasses_json import DataClassJsonMixin
2+
from dataclasses import dataclass
3+
4+
5+
@dataclass
6+
class ImportProject(DataClassJsonMixin):
7+
pass

tests/test_models_routes.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_extract_brep_uuids(client, test_id):
6060
assert isinstance(uuid_dict, dict)
6161

6262

63-
def test_export_project_route(client):
63+
def test_export_project_route(client, tmp_path):
6464
route = "/opengeodeweb_back/export_project"
6565
snapshot = {"styles": {"1": {"visibility": True, "opacity": 1.0, "color": [0.2, 0.6, 0.9]}}}
6666
filename = "export_project_test.zip"
@@ -69,15 +69,48 @@ def test_export_project_route(client):
6969
assert response.headers.get("new-file-name") == filename
7070
assert response.mimetype == "application/octet-binary"
7171
response.direct_passthrough = False
72-
data = response.get_data()
73-
with zipfile.ZipFile(io.BytesIO(data), "r") as zf:
74-
names = zf.namelist()
72+
zip_bytes = response.get_data()
73+
tmp_zip_path = tmp_path / filename
74+
tmp_zip_path.write_bytes(zip_bytes)
75+
with zipfile.ZipFile(tmp_zip_path, "r") as zip_file:
76+
names = zip_file.namelist()
7577
assert "snapshot.json" in names
76-
parsed = json.loads(zf.read("snapshot.json").decode("utf-8"))
78+
parsed = json.loads(zip_file.read("snapshot.json").decode("utf-8"))
7779
assert parsed == snapshot
7880
assert "1/project.db" in names
7981
response.close()
8082
upload_folder = client.application.config["UPLOAD_FOLDER"]
8183
export_path = os.path.join(upload_folder, filename)
8284
if os.path.exists(export_path):
8385
os.remove(export_path)
86+
87+
88+
def test_import_project_route(client, tmp_path):
89+
route = "/opengeodeweb_back/import_project"
90+
snapshot = {"styles": {"1": {"visibility": True, "opacity": 1.0, "color": [0.2, 0.6, 0.9]}}}
91+
92+
data_folder = client.application.config["DATA_FOLDER_PATH"]
93+
pre_existing_db_path = os.path.join(data_folder, "1", "project.db")
94+
os.makedirs(os.path.dirname(pre_existing_db_path), exist_ok=True)
95+
with open(pre_existing_db_path, "wb") as file:
96+
file.write(b"old_db_content")
97+
98+
tmp_zip = tmp_path / "import_project_test.zip"
99+
new_database_bytes = b"new_db_content"
100+
with zipfile.ZipFile(tmp_zip, "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
101+
zip_file.writestr("snapshot.json", json.dumps(snapshot))
102+
zip_file.writestr("1/project.db", new_database_bytes)
103+
104+
with open(tmp_zip, "rb") as file:
105+
response = client.post(
106+
route,
107+
data={"file": (file, "import_project_test.zip")},
108+
content_type="multipart/form-data",
109+
)
110+
111+
assert response.status_code == 200
112+
assert response.json.get("snapshot") == snapshot
113+
114+
assert os.path.exists(pre_existing_db_path)
115+
with open(pre_existing_db_path, "rb") as file:
116+
assert file.read() == new_database_bytes

0 commit comments

Comments
 (0)