Skip to content

Commit 16b90d5

Browse files
authored
Merge pull request #325 from mapswipe/dev
Dev
2 parents 0c4b605 + e074ae9 commit 16b90d5

File tree

4 files changed

+339
-38
lines changed

4 files changed

+339
-38
lines changed
Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
"""
22
Archive a project.
33
"""
4+
import re
5+
from typing import Iterable
6+
7+
from firebase_admin import exceptions
48

59
from mapswipe_workers import auth
6-
from mapswipe_workers.definitions import logger
10+
from mapswipe_workers.definitions import logger, CustomError
11+
712

13+
def chunks(data: list, size: int = 250) -> Iterable[list]:
14+
"""Yield successive n-sized chunks from list."""
15+
for i in range(0, len(data), size):
16+
yield data[i : i + size] # noqa E203
817

9-
def archive_project(project_ids: list) -> None:
18+
19+
def archive_project(project_ids: list) -> bool:
1020
"""
1121
Archive a project.
1222
@@ -15,46 +25,55 @@ def archive_project(project_ids: list) -> None:
1525
"""
1626
for project_id in project_ids:
1727
logger.info(f"Archive project with the id {project_id}")
18-
logger.info(f"Delete results of project with the id {project_id}")
1928

2029
fb_db = auth.firebaseDB()
21-
fb_db.reference(f"v2/results/{project_id}").set({})
22-
23-
# get group keys for this project to estimate size in firebase
24-
groups = fb_db.reference(f"v2/groups/{project_id}").get(shallow=True)
25-
26-
if not groups:
27-
logger.info("no groups to delete in firebase")
28-
else:
29-
group_keys = list(groups.keys())
30-
chunk_size = 250
31-
chunks = int(len(group_keys) / chunk_size) + 1
32-
33-
# delete groups, tasks in firebase for each chunk using the update function
34-
for i in range(0, chunks):
35-
logger.info(
36-
f"Delete max {chunk_size} groups and tasks"
37-
f"of project with the id {project_id}"
38-
)
39-
update_dict = {}
40-
for group_id in group_keys[:chunk_size]:
41-
update_dict[group_id] = None
42-
fb_db.reference(f"v2/groups/{project_id}").update(update_dict)
43-
fb_db.reference(f"v2/tasks/{project_id}").update(update_dict)
44-
group_keys = group_keys[chunk_size:]
45-
46-
logger.info(
47-
f"Set status=archived in Firebase for project with the id {project_id}"
48-
)
49-
fb_db = auth.firebaseDB()
30+
ref = fb_db.reference(f"v2/results/{project_id}")
31+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
32+
raise CustomError(
33+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
34+
{ref.path}"""
35+
)
36+
try:
37+
ref.delete()
38+
except exceptions.InvalidArgumentError:
39+
# Data to write exceeds the maximum size that can be modified
40+
# with a single request. Delete chunks of data instead.
41+
childs = ref.get(shallow=True)
42+
for chunk in chunks(list(childs.keys())):
43+
ref.update({key: None for key in chunk})
44+
ref.delete()
45+
46+
ref = fb_db.reference(f"v2/tasks/{project_id}")
47+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
48+
raise CustomError(
49+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
50+
{ref.path}"""
51+
)
52+
try:
53+
ref.delete()
54+
except exceptions.InvalidArgumentError:
55+
# Data to write exceeds the maximum size that can be modified
56+
# with a single request. Delete chunks of data instead.
57+
childs = ref.get(shallow=True)
58+
for chunk in chunks(list(childs.keys())):
59+
ref.update({key: None for key in chunk})
60+
ref.delete()
61+
62+
ref = fb_db.reference(f"v2/groups/{project_id}")
63+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
64+
raise CustomError(
65+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
66+
{ref.path}"""
67+
)
68+
ref.delete()
69+
5070
fb_db.reference(f"v2/projects/{project_id}/status").set("archived")
5171

52-
logger.info(
53-
f"Set status=archived in Postgres for project with the id {project_id}"
54-
)
5572
pg_db = auth.postgresDB()
5673
sql_query = """
5774
UPDATE projects SET status = 'archived'
5875
WHERE project_id = %(project_id)s;
5976
"""
6077
pg_db.query(sql_query, {"project_id": project_id})
78+
79+
return True
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""
2+
Delete projects.
3+
"""
4+
import re
5+
from typing import Iterable
6+
7+
from firebase_admin import exceptions
8+
9+
from mapswipe_workers import auth
10+
from mapswipe_workers.definitions import logger, CustomError
11+
12+
13+
def chunks(data: list, size: int = 250) -> Iterable[list]:
14+
"""Yield successive n-sized chunks from list."""
15+
for i in range(0, len(data), size):
16+
yield data[i : i + size] # noqa E203
17+
18+
19+
def delete_project(project_ids: list) -> None:
20+
"""
21+
Deletes project, groups, tasks and results from Firebase and Postgres.
22+
"""
23+
for project_id in project_ids:
24+
logger.info(
25+
f"Delete project, groups, tasks and results of project: {project_id}"
26+
)
27+
28+
fb_db = auth.firebaseDB()
29+
ref = fb_db.reference(f"v2/results/{project_id}")
30+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
31+
raise CustomError(
32+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
33+
{ref.path}"""
34+
)
35+
try:
36+
ref.delete()
37+
except exceptions.InvalidArgumentError:
38+
# Data to write exceeds the maximum size that can be modified
39+
# with a single request. Delete chunks of data instead.
40+
childs = ref.get(shallow=True)
41+
for chunk in chunks(list(childs.keys())):
42+
ref.update({key: None for key in chunk})
43+
ref.delete()
44+
45+
ref = fb_db.reference(f"v2/tasks/{project_id}")
46+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
47+
raise CustomError(
48+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
49+
{ref.path}"""
50+
)
51+
try:
52+
ref.delete()
53+
except exceptions.InvalidArgumentError:
54+
# Data to write exceeds the maximum size that can be modified
55+
# with a single request. Delete chunks of data instead.
56+
childs = ref.get(shallow=True)
57+
for chunk in chunks(list(childs.keys())):
58+
ref.update({key: None for key in chunk})
59+
ref.delete()
60+
61+
ref = fb_db.reference(f"v2/groups/{project_id}")
62+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
63+
raise CustomError(
64+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
65+
{ref.path}"""
66+
)
67+
ref.delete()
68+
ref = fb_db.reference(f"v2/projects/{project_id}")
69+
if not re.match(r"/v2/\w+/[-a-zA-Z0-9]+", ref.path):
70+
raise CustomError(
71+
f"""Given argument resulted in invalid Firebase Realtime Database reference.
72+
{ref.path}"""
73+
)
74+
ref.delete()
75+
76+
pg_db = auth.postgresDB()
77+
sql_query = "DELETE FROM results WHERE project_id = %(project_id)s;"
78+
pg_db.query(sql_query, {"project_id": project_id})
79+
sql_query = "DELETE FROM tasks WHERE project_id = %(project_id)s;"
80+
pg_db.query(sql_query, {"project_id": project_id})
81+
sql_query = "DELETE FROM groups WHERE project_id = %(project_id)s;"
82+
pg_db.query(sql_query, {"project_id": project_id})
83+
sql_query = "DELETE FROM projects WHERE project_id = %(project_id)s;"
84+
pg_db.query(sql_query, {"project_id": project_id})
85+
86+
return True

mapswipe_workers/mapswipe_workers/mapswipe_workers.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import json
55
import time
66

7-
import click
87
import schedule as sched
8+
9+
import click
910
from mapswipe_workers import auth
1011
from mapswipe_workers.definitions import CustomError, logger, sentry
1112
from mapswipe_workers.firebase_to_postgres import (
1213
archive_project,
14+
delete_project,
1315
transfer_results,
1416
update_data,
1517
)
@@ -198,11 +200,59 @@ def run_create_tutorial(input_file) -> None:
198200
)
199201
def run_archive_project(project_id, project_ids):
200202
"""Archive projects in Postgres. Delete groups, tasks and results from Firebase."""
201-
if not project_ids:
203+
if not project_ids and not project_id:
204+
click.echo("Missing argument")
205+
return None
206+
elif not project_ids:
202207
project_ids = [project_id]
208+
click.echo("Start archive")
203209
update_data.update_project_data(project_ids)
204210
transfer_results.transfer_results(project_ids)
205-
archive_project.archive_project(project_ids)
211+
if archive_project.archive_project(project_ids):
212+
click.echo("Finished archive")
213+
214+
215+
@cli.command("delete")
216+
@click.option(
217+
"--project-id", "-i", help=("Delete project with giving project id"), type=str,
218+
)
219+
@click.option(
220+
"--project-ids",
221+
cls=PythonLiteralOption,
222+
default="[]",
223+
help=(
224+
f"Delete multiple projects. "
225+
f"Provide project id strings as a list: "
226+
f"""["project_a", "project_b"]"""
227+
),
228+
)
229+
def run_delete_project(project_id, project_ids):
230+
"""Delete tasks, groups, project and results."""
231+
if not project_ids and not project_id:
232+
click.echo("Missing argument")
233+
return None
234+
elif not project_ids:
235+
project_ids = [project_id]
236+
237+
click.echo(
238+
"Projects and all associated data including results "
239+
+ "with following project ids will be deleted permantly:"
240+
)
241+
for project_id in project_ids:
242+
click.echo(project_id)
243+
click.echo()
244+
click.echo("Continue with deletion? [y/n] ", nl=False)
245+
click.echo()
246+
c = click.getchar()
247+
248+
if c == "y":
249+
click.echo("Start deletion")
250+
if delete_project.delete_project(project_ids):
251+
click.echo("Finished deletions")
252+
elif c == "n":
253+
click.echo("Abort!")
254+
else:
255+
click.echo("Invalid input")
206256

207257

208258
@cli.command("run")

0 commit comments

Comments
 (0)