Skip to content

Commit

Permalink
Merge pull request #329 from MerginMaps/build-2024.7.0
Browse files Browse the repository at this point in the history
Release 2024.7.0
  • Loading branch information
MarcelGeo authored Nov 18, 2024
2 parents a444246 + 16185da commit cbc826a
Show file tree
Hide file tree
Showing 353 changed files with 5,157 additions and 19,066 deletions.
2 changes: 2 additions & 0 deletions server/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

Configuration.SERVER_TYPE = "ce"
Configuration.USER_SELF_REGISTRATION = False

application = create_app(
[
"DOCS_URL",
Expand All @@ -37,6 +38,7 @@
"GLOBAL_ADMIN",
"GLOBAL_READ",
"GLOBAL_WRITE",
"ENABLE_SUPERADMIN_ASSIGNMENT",
]
)
register_stats(application)
Expand Down
9 changes: 7 additions & 2 deletions server/mergin/auth/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ paths:
count:
type: integer
example: 10
users:
items:
type: array
items:
$ref: "#/components/schemas/User"
Expand Down Expand Up @@ -670,6 +670,11 @@ components:
type: string
format: date-time
example: 2023-07-30T08:47:58Z
registration_date:
nullable: true
type: string
format: date-time
example: 2023-07-30T08:47:58Z
profile:
$ref: "#/components/schemas/UserProfile"
PaginatedUsers:
Expand Down Expand Up @@ -839,4 +844,4 @@ components:
- editor
- reader
- guest
example: reader
example: reader
22 changes: 16 additions & 6 deletions server/mergin/auth/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytz
from datetime import datetime, timedelta
from connexion import NoContent
from sqlalchemy import func, desc, asc
from sqlalchemy import func, desc, asc, or_
from sqlalchemy.sql.operators import is_
from flask import request, current_app, jsonify, abort, render_template
from flask_login import login_user, logout_user, current_user
Expand All @@ -23,7 +23,7 @@
user_account_closed,
)
from .bearer import encode_token
from .models import User, LoginHistory
from .models import User, LoginHistory, UserProfile
from .schemas import UserSchema, UserSearchSchema, UserProfileSchema, UserInfoSchema
from .forms import (
LoginForm,
Expand Down Expand Up @@ -408,11 +408,17 @@ def update_user(username): # pylint: disable=W0613,W0612
form = UserForm.from_json(request.json)
if not form.validate_on_submit():
return jsonify(form.errors), 400
if request.json.get("is_admin") is not None and not current_app.config.get(
"ENABLE_SUPERADMIN_ASSIGNMENT"
):
abort(400, "Unable to assign super admin role")

user = User.query.filter_by(username=username).first_or_404("User not found")
form.update_obj(user)

# remove inactive since flag for ban or re-activation
user.inactive_since = None

db.session.add(user)
db.session.commit()
return jsonify(UserSchema().dump(user))
Expand Down Expand Up @@ -449,13 +455,17 @@ def get_paginated_users(
:rtype: Dict[str: List[User], str: Integer]
"""
users = User.query.filter(
users = User.query.join(UserProfile).filter(
is_(User.username.ilike("deleted_%"), False) | is_(User.active, True)
)

if like:
attr = User.email if "@" in like else User.username
users = users.filter(attr.ilike(f"%{like}%"))
users = users.filter(
User.username.ilike(f"%{like}%")
| User.email.ilike(f"%{like}%")
| UserProfile.first_name.ilike(f"%{like}%")
| UserProfile.last_name.ilike(f"%{like}%")
)

if descending and order_by:
users = users.order_by(desc(User.__table__.c[order_by]))
Expand All @@ -467,7 +477,7 @@ def get_paginated_users(

result_users = UserSchema(many=True).dump(result)

data = {"users": result_users, "total": total}
data = {"items": result_users, "count": total}
return data, 200


Expand Down
1 change: 1 addition & 0 deletions server/mergin/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Meta:
"verified_email",
"profile",
"scheduled_removal",
"registration_date",
)
load_instance = True

Expand Down
4 changes: 4 additions & 0 deletions server/mergin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ class Configuration(object):

# build hash number
BUILD_HASH = config("BUILD_HASH", default="")
# Allow changing access to admin panel
ENABLE_SUPERADMIN_ASSIGNMENT = config(
"ENABLE_SUPERADMIN_ASSIGNMENT", default=True, cast=bool
)
# backend version
VERSION = config("VERSION", default=get_version())
SERVER_TYPE = config("SERVER_TYPE", default="ce")
1 change: 1 addition & 0 deletions server/mergin/stats/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def send_statistics():
"workspaces_count": current_app.ws_handler.workspace_count(),
"last_change": str(last_change_item.updated) + "Z" if last_change_item else "",
"server_version": current_app.config["VERSION"],
"monthly_contributors": current_app.ws_handler.monthly_contributors_count(),
}

try:
Expand Down
2 changes: 1 addition & 1 deletion server/mergin/sync/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def create(name, namespace, user): # pylint: disable=W0612

@project.command()
@click.argument("project-name")
@click.option("--version", required=True)
@click.option("--version", type=int, required=True)
@click.option("--directory", type=click.Path(), required=True)
def download(project_name, version, directory): # pylint: disable=W0612
"""Download files for project at particular version"""
Expand Down
7 changes: 7 additions & 0 deletions server/mergin/sync/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ def workspace_count():
"""
pass

@staticmethod
def monthly_contributors_count():
"""
Return number of workspace contributors in current month and year
"""
pass


class AbstractProjectHandler(ABC):
@abstractmethod
Expand Down
1 change: 0 additions & 1 deletion server/mergin/sync/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

from .files import (
File,
UploadFile,
UploadChanges,
ChangesSchema,
ProjectFile,
Expand Down
29 changes: 11 additions & 18 deletions server/mergin/sync/private_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,13 @@ paths:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PerPage"
- $ref: "#/components/parameters/OrderParams"
- name: name
- name: like
in: query
description: Filter projects by name with ilike pattern
description: Filter projects by name or workspace name with ilike pattern
required: false
schema:
type: string
example: survey
- name: workspace
in: query
description: Filter projects by workspace with ilike pattern
required: false
schema:
type: string
example: my-workspace
responses:
"200":
description: List of projects
Expand All @@ -209,7 +202,7 @@ paths:
type: integer
description: Total number of all projects
example: 20
projects:
items:
type: array
items:
$ref: "#/components/schemas/ProjectListItem"
Expand Down Expand Up @@ -292,7 +285,7 @@ paths:
id:
type: string
format: uuid
example: 'd4ecda97-0595-40af-892c-e7522de70bd2'
example: "d4ecda97-0595-40af-892c-e7522de70bd2"
name:
type: string
example: survey
Expand Down Expand Up @@ -460,7 +453,7 @@ components:
- detail
UsersLimitHit:
allOf:
- $ref: '#/components/schemas/CustomError'
- $ref: "#/components/schemas/CustomError"
type: object
properties:
rejected_emails:
Expand All @@ -473,7 +466,7 @@ components:
example:
code: UsersLimitHit
detail: Maximum number of people in this workspace is reached. Please upgrade your subscription to add more people (UsersLimitHit)
rejected_emails: [ [email protected] ]
rejected_emails: [[email protected]]
users_quota: 6
ProjectAccessRequestList:
type: array
Expand All @@ -488,7 +481,7 @@ components:
project_id:
type: string
format: uuid
example: 'd4ecda97-0595-40af-892c-e7522de70bd2'
example: "d4ecda97-0595-40af-892c-e7522de70bd2"
project_name:
type: string
example: survey
Expand Down Expand Up @@ -619,25 +612,25 @@ components:
nullable: false
items:
type: string
example: [ john.doe ]
example: [john.doe]
writersnames:
type: array
nullable: false
items:
type: string
example: [ john.doe ]
example: [john.doe]
editorsnames:
type: array
nullable: false
items:
type: string
example: [ john.doe ]
example: [john.doe]
readersnames:
nullable: false
type: array
items:
type: string
example: [ john.doe ]
example: [john.doe]
public:
type: boolean
example: true
Expand Down
8 changes: 3 additions & 5 deletions server/mergin/sync/private_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,8 @@ def list_namespace_project_access_requests(


@auth_required(permissions=["admin"])
def list_projects(
page, per_page, order_params=None, name=None, workspace=None
): # noqa: E501
projects = current_app.ws_handler.projects_query(name, workspace)
def list_projects(page, per_page, order_params=None, like=None): # noqa: E501
projects = current_app.ws_handler.projects_query(like)
# do not fetch from db what is not needed
projects = projects.options(
defer(Project.storage_params),
Expand All @@ -211,7 +209,7 @@ def list_projects(
result = projects.paginate(page, per_page).items
total = projects.paginate(page, per_page).total
data = AdminProjectSchema(many=True).dump(result)
data = {"projects": data, "count": total}
data = {"items": data, "count": total}
return data, 200


Expand Down
4 changes: 2 additions & 2 deletions server/mergin/sync/public_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def add_project(namespace): # noqa: E501

p = Project(**request.json, creator=current_user, workspace=workspace)
p.updated = datetime.utcnow()
db.session.add(p)
pa = ProjectAccess(p, public=request.json.get("public", False))

template_name = request.json.get("template", None)
Expand Down Expand Up @@ -230,7 +231,6 @@ def add_project(namespace): # noqa: E501
get_device_id(request),
)

db.session.add(p)
db.session.add(pa)
db.session.add(version)
db.session.commit()
Expand Down Expand Up @@ -1186,6 +1186,7 @@ def clone_project(namespace, project_name): # noqa: E501
workspace=ws,
)
p.updated = datetime.utcnow()
db.session.add(p)
pa = ProjectAccess(p, public=False)

try:
Expand All @@ -1211,7 +1212,6 @@ def clone_project(namespace, project_name): # noqa: E501
user_agent,
device_id,
)
db.session.add(p)
db.session.add(pa)
db.session.add(project_version)
db.session.commit()
Expand Down
3 changes: 2 additions & 1 deletion server/mergin/sync/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ class Meta:
class AdminProjectSchema(ma.SQLAlchemyAutoSchema):
id = fields.UUID(attribute="Project.id")
name = fields.Str(attribute="Project.name")
namespace = fields.Method("_workspace_name")
workspace = fields.Method("_workspace_name")
workspace_id = fields.Int(attribute="Project.workspace_id")
version = fields.Function(
lambda obj: ProjectVersion.to_v_name(obj.Project.latest_version)
)
Expand Down
40 changes: 31 additions & 9 deletions server/mergin/sync/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Dict, Tuple, Optional, Set, List
from flask_login import current_user
from sqlalchemy import or_, and_, Column, literal
from sqlalchemy import or_, and_, Column, literal, extract
from sqlalchemy.orm import joinedload

from .errors import UpdateProjectAccessError
from .models import Project, ProjectAccess, AccessRequest, ProjectAccessDetail
from .models import (
Project,
ProjectAccess,
AccessRequest,
ProjectAccessDetail,
ProjectVersion,
)
from .permissions import projects_query, ProjectPermissions
from .public_api_controller import parse_project_access_update_request
from .. import db
Expand Down Expand Up @@ -248,16 +254,32 @@ def filter_projects(
def workspace_count():
return 1

def projects_query(self, name=None, workspace=None):
@staticmethod
def monthly_contributors_count():
today = datetime.now(timezone.utc)
year = today.year
month = today.month
return (
db.session.query(ProjectVersion.author_id)
.filter(
extract("year", ProjectVersion.created) == year,
extract("month", ProjectVersion.created) == month,
)
.group_by(ProjectVersion.author_id)
.count()
)

def projects_query(self, like: str = None):
ws = self.factory_method()
query = db.session.query(
Project, literal(ws.name).label("workspace_name")
Project,
literal(ws.name).label("workspace_name"),
).filter(Project.storage_params.isnot(None))

if name:
query = query.filter(Project.name.ilike(f"%{name}%"))
if workspace:
query = query.filter(literal(ws.name).ilike(f"%{workspace}%"))
if like:
query = query.filter(
Project.name.ilike(f"%{like}%") | literal(ws.name).ilike(f"%{like}%")
)
return query

@staticmethod
Expand Down
Loading

0 comments on commit cbc826a

Please sign in to comment.