Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f945325
add js and py styling
PeterPetrik Apr 21, 2023
72634c1
Added disabling and default count of versions option to project versi…
MarcelGeo Apr 28, 2023
10f234d
Added slot for table footer in versions table
MarcelGeo Apr 28, 2023
c2bd01e
Added non public paths for history
MarcelGeo May 2, 2023
fb8c2ed
Cleanup mixins and small fix interface
MarcelGeo May 3, 2023
4329090
rename disabledRow
MarcelGeo May 3, 2023
5488ad3
API: add get project version by name endpoint
varmar05 May 4, 2023
7c1768a
VersionDetail update api if missing info from store after refresh
MarcelGeo May 4, 2023
0e1e6a1
API: add get project version by name endpoint
varmar05 May 4, 2023
995877c
VersionDetail update api if missing info from store after refresh
MarcelGeo May 4, 2023
51334aa
Merge branch 'add_get_version_by_id' of https://github.com/MerginMaps…
MarcelGeo May 4, 2023
b27ba5e
Refactor if currentVersion
MarcelGeo May 4, 2023
3e0e047
Merge pull request #71 from MerginMaps/CU-861mn8ppv
varmar05 May 4, 2023
1bcc81a
Merge remote-tracking branch 'origin/develop' into add_get_version_by_id
MarcelGeo May 4, 2023
5bed4c7
Merge pull request #74 from MerginMaps/add_get_version_by_id
varmar05 May 4, 2023
112460d
Hide namespace (workspace) from CE
MarcelGeo May 5, 2023
520cccf
Commenting param
MarcelGeo May 5, 2023
37ab941
allow null value for creator
MarcelGeo May 5, 2023
7476fa9
Merge pull request #75 from MerginMaps/CU-861m7mk7c
MarcelGeo May 8, 2023
66aedb4
Added export types from instance module
MarcelGeo May 18, 2023
e3cd614
Merge pull request #76 from MerginMaps/fix-instance-mising-export
varmar05 May 18, 2023
83a7225
Fix file restore from backward diffs
varmar05 May 23, 2023
9d51380
Merge pull request #77 from MerginMaps/fix_restore_file_function
varmar05 May 23, 2023
ef40855
custom field class that filters whitespaces (#78)
harminius May 24, 2023
039fe30
Added lint for every package with eslint
MarcelGeo May 25, 2023
e55851e
Fix:
MarcelGeo May 25, 2023
fdcafc1
Added licenses from develop to prevent conflicts
MarcelGeo May 25, 2023
dfb2b2d
Merge pull request #66 from MerginMaps/ci2
PeterPetrik May 26, 2023
1074525
Lint: Run black to fix pycode formatting
varmar05 May 26, 2023
352fc41
Add ignore list for formatting commits
varmar05 May 26, 2023
2daa98e
Merge pull request #80 from MerginMaps/fix_pycode_style
varmar05 May 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Reformat code with black
107452539ec5a0e1d6e31f0697d215cc3cf3be11
27 changes: 27 additions & 0 deletions .github/workflows/code_style.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Code Layout

on: push

jobs:
code_style_python:
name: Python code convention check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: psf/black@stable
with:
options: "--check --verbose --diff"
src: "./server"

code_style_js:
name: JavaScript code convention check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Check JS
run: |
cd web-app
yarn install
yarn run lint:all
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
</p>

<p align="center">
<img src="https://img.shields.io/badge/License-Dual AGPL3 and MM Commercial licence-blue">
<img src="https://img.shields.io/badge/License-Dual AGPL3 and MM Commercial licence-blue"> <br />
[![Code Layout](https://github.com/MerginMaps/mergin/actions/workflows/code_style.yml/badge.svg)](https://github.com/MerginMaps/mergin/actions/workflows/code_style.yml)
</p>

## About
Expand Down
17 changes: 17 additions & 0 deletions server/mergin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from flask_mail import Mail
from connexion.apps.flask_app import FlaskJSONEncoder
from flask_wtf import FlaskForm
from wtforms import StringField
from pathlib import Path
import sys
import time
Expand Down Expand Up @@ -433,3 +434,19 @@ class ResponseError:

def to_dict(self) -> Dict:
return dict(code=self.code, detail=self.detail + f" ({self.code})")


def whitespace_filter(obj):
return obj.strip() if isinstance(obj, str) else obj


class CustomStringField(StringField):
"""Custom class for string form fields"""

def __init__(self, *args, **kwargs):
filters = kwargs.get("filters")
# add whitespace filter
kwargs["filters"] = (
(*filters, whitespace_filter) if filters else (whitespace_filter,)
)
super().__init__(*args, **kwargs)
25 changes: 10 additions & 15 deletions server/mergin/auth/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@
)

from .models import User
from ..app import UpdateForm


def whitespace_filter(obj):
return obj.strip() if isinstance(obj, str) else obj
from ..app import UpdateForm, CustomStringField


def username_validation(form, field):
Expand Down Expand Up @@ -51,20 +47,18 @@ def __call__(self, form, field):
class LoginForm(FlaskForm):
"""Form with username and password fields for user to sign in."""

login = StringField(validators=[DataRequired(), Length(max=80)])
login = CustomStringField(validators=[DataRequired(), Length(max=80)])
password = PasswordField(validators=[DataRequired()])


class RegisterUserForm(FlaskForm):
username = StringField(
username = CustomStringField(
"Username",
validators=[validators.Length(min=4, max=25), username_validation],
filters=(whitespace_filter,),
)
email = StringField(
email = CustomStringField(
"Email Address",
validators=[DataRequired(), Email()],
filters=(whitespace_filter,),
)

def validate(self):
Expand All @@ -85,8 +79,9 @@ def validate(self):


class ResetPasswordForm(FlaskForm):
email = StringField(
"Email Address", [DataRequired(), Email()], filters=(whitespace_filter,)
email = CustomStringField(
"Email Address",
[DataRequired(), Email()],
)


Expand Down Expand Up @@ -119,9 +114,9 @@ class UserProfileDataForm(UpdateForm):
"""This form is for user profile update"""

receive_notifications = BooleanField("Receive notifications", [Optional()])
first_name = StringField("First Name", [Optional()], filters=(whitespace_filter,))
last_name = StringField("Last Name", [Optional()], filters=(whitespace_filter,))
email = StringField("Email", [Optional(), Email()], filters=(whitespace_filter,))
first_name = CustomStringField("First Name", [Optional()])
last_name = CustomStringField("Last Name", [Optional()])
email = CustomStringField("Email", [Optional(), Email()])


class ApiLoginForm(LoginForm):
Expand Down
14 changes: 12 additions & 2 deletions server/mergin/sync/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,11 @@ def file_diffs_chain(self, file, version):
meta = history[history_end]
# we have either full history of changes or v_x = v_x+n => no basefile in way, it is 'diffable' from the end
if "diff" in meta:
# omit diff for target version as it would lead to previous version if reconstructed backward
diffs = [
value["diff"] for value in list(reversed(history.values()))[1:]
value["diff"]
for key, value in reversed(history.items())
if key != v_x
]
base_meta = history[next(iter(history))]
base_meta["version"] = next(iter(history))
Expand Down Expand Up @@ -249,7 +252,9 @@ def file_diffs_chain(self, file, version):
else:
diffs = [
value["diff"]
for value in list(reversed(history.values()))[1:]
for value in list(reversed(history.values()))[
1:
] # basefile has no diff
]
# file was removed (or renamed for backward compatibility)
else:
Expand Down Expand Up @@ -417,6 +422,11 @@ def __init__(self, project, name, author, changes, files, ip, user_agent=None):
if "diff" in change:
change["diff"].pop("chunks", None)

@property
def int_name(self) -> int:
"""Parsed version name as integer (v5 -> 5)"""
return int(self.name.replace("v", ""))

def diff_summary(self):
"""Calculate diff summary for versioned files updated with geodiff

Expand Down
4 changes: 4 additions & 0 deletions server/mergin/sync/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from flask import abort, current_app
from flask_login import current_user

from .utils import is_valid_uuid
from ..auth.models import User
from .models import ProjectAccess, Project, Upload

Expand Down Expand Up @@ -138,6 +139,9 @@ def require_project(ws, project_name, permission):


def require_project_by_uuid(uuid, permission):
if not is_valid_uuid(uuid):
abort(404)

project = (
Project.query.filter_by(id=uuid)
.filter(Project.removed_at.is_(None))
Expand Down
4 changes: 3 additions & 1 deletion server/mergin/sync/private_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,9 @@ def update_project_access(id: str):
:rtype: None
"""
project = require_project_by_uuid(id, ProjectPermissions.Update)
user = User.query.filter_by(id=request.json["user_id"], active=True).first_or_404("User does not exist")
user = User.query.filter_by(id=request.json["user_id"], active=True).first_or_404(
"User does not exist"
)
# prevent to remove ownership of project creator
if user.id == project.creator_id:
abort(400, "Ownership of project creator cannot be removed")
Expand Down
35 changes: 35 additions & 0 deletions server/mergin/sync/public_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,41 @@ paths:
'404':
$ref: '#/components/responses/NotFoundResp'
x-openapi-router-controller: mergin.sync.public_api_controller
'/project/version/{project_id}/{version}':
get:
tags:
- project
summary: Get project version by its name (e.g. v3)
operationId: get_project_version
parameters:
- name: project_id
in: path
required: true
schema:
type: string
example: ac70181b5-7143-491b-91a6-4f533021c9a2
- name: version
in: path
required: true
schema:
type: string
example: v3
responses:
'200':
description: Project version detail
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectVersion"
'400':
$ref: '#/components/responses/BadStatusResp'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFoundResp'
x-openapi-router-controller: mergin.sync.public_api_controller
/project/download/{namespace}/{project_name}:
get:
tags:
Expand Down
11 changes: 11 additions & 0 deletions server/mergin/sync/public_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1335,3 +1335,14 @@ def get_workspace_by_id(id):
ctx = {"user": current_user}
data = UserWorkspaceSchema(context=ctx).dump(ws)
return data, 200


@auth_required
def get_project_version(project_id: str, version: str):
"""Get project version by its name (e.g. v3)"""
project = require_project_by_uuid(project_id, ProjectPermissions.Read)
pv = ProjectVersion.query.filter_by(
project_id=project.id, name=version
).first_or_404()
data = ProjectVersionSchema(exclude=["files"]).dump(pv)
return data, 200
6 changes: 5 additions & 1 deletion server/mergin/sync/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ def disk_usage(self):
# get only what is necessary from db to calculate final usage
from .models import Project

projects_usage_list = db.session.query(Project.disk_usage).filter(Project.removed_at.is_(None)).all()
projects_usage_list = (
db.session.query(Project.disk_usage)
.filter(Project.removed_at.is_(None))
.all()
)
return sum(p.disk_usage for p in projects_usage_list)

def user_has_permissions(self, user, permissions):
Expand Down
7 changes: 5 additions & 2 deletions server/mergin/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ def client(app):
return client


# login tests: success, success with email login, invalid password, missing password, wrong headers
# login tests: success, success with trailing space, success with email login, invalid password, missing password, wrong headers
test_login_data = [
({"login": "mergin", "password": "ilovemergin"}, json_headers, 200),
({"login": "mergin ", "password": "ilovemergin"}, json_headers, 200),
({"login": "mergin@mergin.com", "password": "ilovemergin"}, json_headers, 200),
({"login": "mergin", "password": "ilovemergi"}, json_headers, 401),
({"login": "mergin"}, json_headers, 401),
Expand All @@ -37,7 +38,9 @@ def client(app):
@pytest.mark.parametrize("data,headers,expected", test_login_data)
def test_login(client, data, headers, expected):
resp = client.post(
url_for("/.mergin_auth_controller_login"), data=json.dumps(data), headers=headers
url_for("/.mergin_auth_controller_login"),
data=json.dumps(data),
headers=headers,
)
assert resp.status_code == expected
if expected == 200:
Expand Down
2 changes: 1 addition & 1 deletion server/mergin/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_config(client):
"server_configured",
"fix",
"major",
"minor"
"minor",
}
resp = client.get("/config")
assert resp.status_code == 200
Expand Down
Loading