Skip to content

Commit bfd28c3

Browse files
committed
Add possibility to lock a project
- add column to project table - block push - do not count to workspace storage and project number
1 parent 58e1a2d commit bfd28c3

5 files changed

Lines changed: 50 additions & 8 deletions

File tree

server/mergin/sync/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from pygeodiff import GeoDiff
1717
from sqlalchemy import text, null, desc, nullslast
1818
from sqlalchemy.dialects.postgresql import ARRAY, BIGINT, UUID, JSONB, ENUM
19-
from sqlalchemy.orm.attributes import flag_modified
2019
from sqlalchemy.types import String
2120
from sqlalchemy.ext.hybrid import hybrid_property
2221
from pygeodiff.geodifflib import GeoDiffLibError
@@ -68,6 +67,7 @@ class Project(db.Model):
6867
db.Integer, db.ForeignKey("user.id"), nullable=True, index=True
6968
)
7069
public = db.Column(db.Boolean, default=False, index=True, nullable=False)
70+
locked_until = db.Column(db.DateTime, index=True)
7171

7272
creator = db.relationship(
7373
"User", uselist=False, backref=db.backref("projects"), foreign_keys=[creator_id]
@@ -88,6 +88,7 @@ def __init__(
8888
self.creator = creator
8989
self.latest_version = 0
9090
self.public = kwargs.get("public", False)
91+
self.locked_until = None
9192
latest_files = LatestProjectFiles(project=self)
9293
db.session.add(latest_files)
9394

server/mergin/sync/private_api_controller.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
from flask import render_template, request, current_app, jsonify, abort
77
from flask_login import current_user
88
from sqlalchemy.orm import defer
9-
from sqlalchemy import text, and_, desc, asc
9+
from sqlalchemy import text
1010

1111
from ..app import db
1212
from ..auth import auth_required
13-
from ..auth.models import User, UserProfile
1413
from .forms import AccessPermissionForm
1514
from .models import (
1615
Project,
@@ -22,7 +21,6 @@
2221
ProjectListSchema,
2322
ProjectAccessRequestSchema,
2423
AdminProjectSchema,
25-
ProjectAccessSchema,
2624
ProjectAccessDetailSchema,
2725
)
2826
from .permissions import (

server/mergin/sync/public_api_controller.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,11 @@ def project_push(namespace, project_name):
785785
changes = request.json["changes"]
786786
project_permission = current_app.project_handler.get_push_permission(changes)
787787
project = require_project(namespace, project_name, project_permission)
788+
if project.locked_until:
789+
abort(
790+
423,
791+
f"This project is currently locked. You cannot make changes to it until {project.locked_until.strftime('%Y-%m-%d %H:%M UTC')}.",
792+
)
788793
# pass full project object to request for later use
789794
request.view_args["project"] = project
790795
ws = project.workspace
@@ -1027,6 +1032,11 @@ def push_finish(transaction_id):
10271032
upload.changes
10281033
)
10291034
project = upload.project
1035+
if project.locked_until:
1036+
abort(
1037+
423,
1038+
f"This project is currently locked. You cannot make changes to it until {project.locked_until.strftime('%Y-%m-%d %H:%M UTC')}.",
1039+
)
10301040
project_path = get_project_path(project)
10311041
corrupted_files = []
10321042

server/mergin/sync/workspace.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime, timedelta, timezone
66
from typing import Dict, Tuple, Optional, Set, List
77
from flask_login import current_user
8-
from sqlalchemy import Column, literal, extract
8+
from sqlalchemy import Column, literal, extract, and_
99
from sqlalchemy.sql.operators import is_
1010

1111
from .errors import UpdateProjectAccessError
@@ -66,7 +66,7 @@ def disk_usage(self):
6666

6767
projects_usage_list = (
6868
db.session.query(Project.disk_usage)
69-
.filter(Project.removed_at.is_(None))
69+
.filter(Project.removed_at.is_(None), Project.locked_until.is_(None))
7070
.all()
7171
)
7272
return sum(p.disk_usage for p in projects_usage_list)
@@ -107,8 +107,11 @@ def project_count(self):
107107

108108
return (
109109
db.session.query(Project.disk_usage)
110-
.filter(Project.workspace_id == self.id)
111-
.filter(Project.removed_at.is_(None))
110+
.filter(
111+
Project.workspace_id == self.id,
112+
Project.removed_at.is_(None),
113+
Project.locked_until.is_(None),
114+
)
112115
.count()
113116
)
114117

server/mergin/tests/test_project_controller.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2621,3 +2621,33 @@ def test_supported_file_upload(client):
26212621
resp.json["detail"]
26222622
== f"Unsupported file type detected: {spoof_name}. Please remove the file or try compressing it into a ZIP file before uploading."
26232623
)
2624+
2625+
2626+
def test_locked_project(client, diff_project):
2627+
"""Users cannot push to the locked project. Moreover, it does not count to the storage and project count67"""
2628+
# before project is locked
2629+
orig_p_count = diff_project.workspace.project_count()
2630+
orig_storage = diff_project.workspace.disk_usage()
2631+
# after locking the project
2632+
diff_project.locked_until = datetime.datetime.utcnow() + datetime.timedelta(
2633+
weeks=26
2634+
)
2635+
db.session.commit()
2636+
assert diff_project.workspace.project_count() == orig_p_count - 1
2637+
assert diff_project.workspace.disk_usage() == orig_storage - diff_project.disk_usage
2638+
# push is not possible to the locked project
2639+
changes = _get_changes_without_added(test_project_dir)
2640+
project_path = get_project_path(diff_project)
2641+
data = {"version": "v1", "changes": changes}
2642+
resp = client.post(
2643+
f"/v1/project/push/{project_path}",
2644+
data=json.dumps(data, cls=DateTimeEncoder).encode("utf-8"),
2645+
headers=json_headers,
2646+
)
2647+
assert resp.status_code == 423
2648+
# to play safe push finish is also blocked
2649+
upload, upload_dir = create_transaction("mergin", changes)
2650+
url = "/v1/project/push/finish/{}".format(upload.id)
2651+
2652+
resp = client.post(url, headers=json_headers)
2653+
assert resp.status_code == 423

0 commit comments

Comments
 (0)