Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
9 changes: 5 additions & 4 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 Down Expand Up @@ -454,8 +454,9 @@ def get_paginated_users(
)

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}%")
)

if descending and order_by:
users = users.order_by(desc(User.__table__.c[order_by]))
Expand All @@ -467,7 +468,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
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: [ rejected@example.com ]
rejected_emails: [rejected@example.com]
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
2 changes: 1 addition & 1 deletion server/mergin/sync/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ 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")
version = fields.Function(
lambda obj: ProjectVersion.to_v_name(obj.Project.latest_version)
)
Expand Down
10 changes: 5 additions & 5 deletions server/mergin/sync/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,16 @@ def filter_projects(
def workspace_count():
return 1

def projects_query(self, name=None, workspace=None):
def projects_query(self, like: str = None):
ws = self.factory_method()
query = db.session.query(
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
20 changes: 10 additions & 10 deletions server/mergin/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,27 +694,27 @@ def test_paginate_users(client):
url = "/app/admin/users?page=1&per_page=10"
# get 5 users (default + 5 new added - 1 deleted & inactive)
resp = client.get(url)
list_of_usernames = [user["username"] for user in resp.json["users"]]
assert resp.json["total"] == 5
assert resp.json["users"][0]["username"] == "mergin"
list_of_usernames = [user["username"] for user in resp.json["items"]]
assert resp.json["count"] == 5
assert resp.json["items"][0]["username"] == "mergin"
assert user_inactive.username in list_of_usernames
assert deleted_active.username in list_of_usernames
assert deleted_inactive.username not in list_of_usernames
# order by username
resp = client.get(url + "&order_by=username")
assert resp.json["total"] == 5
assert resp.json["users"][0]["username"] == "alice"
assert resp.json["count"] == 5
assert resp.json["items"][0]["username"] == "alice"
# exact match with username
resp = client.get(url + "&like=bob")
assert resp.json["total"] == 1
assert resp.json["users"][0]["username"] == "bob"
assert resp.json["count"] == 1
assert resp.json["items"][0]["username"] == "bob"
# ilike search with email
resp = client.get(url + "&like=@mergin.com")
assert resp.json["total"] == 5
assert resp.json["count"] == 5
# exact search by email
resp = client.get(url + "&like=alice@mergin.com")
assert resp.json["total"] == 1
assert resp.json["users"][0]["username"] == "alice"
assert resp.json["count"] == 1
assert resp.json["items"][0]["username"] == "alice"
# invalid paging
assert client.get("/app/admin/users?page=2&per_page=10").status_code == 404

Expand Down
32 changes: 16 additions & 16 deletions server/mergin/tests/test_private_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def test_restore_project(client, diff_project):
# tests listing
resp = client.get("/app/admin/projects?page=1&per_page=10")
assert resp.json["count"] == 1
assert resp.json["projects"][0]["removed_at"]
assert resp.json["items"][0]["removed_at"]

diff_project.workspace.active = True
db.session.commit()
Expand All @@ -432,7 +432,7 @@ def test_admin_project_list(client):
assert resp.status_code == 200
assert resp.json.get("count") == 1
# mark as inactive
p = Project.query.get(resp.json["projects"][0]["id"])
p = Project.query.get(resp.json["items"][0]["id"])
p.removed_at = datetime.datetime.utcnow()
p.removed_by = user.id
db.session.commit()
Expand All @@ -445,7 +445,7 @@ def test_admin_project_list(client):
resp = client.get("/app/admin/projects?page=1&per_page=10&order_params=name ASC")
assert resp.status_code == 200
assert resp.json.get("count") == 15
projects = resp.json.get("projects")
projects = resp.json.get("items")
assert len(projects) == 10
assert "foo5" in projects[9]["name"]
assert "v0" == projects[9]["version"]
Expand All @@ -454,30 +454,30 @@ def test_admin_project_list(client):
"/app/admin/projects?page=1&per_page=10&order_params=removed_at ASC"
)
assert resp.status_code == 200
assert resp.json["projects"][0]["name"] == p.name
assert resp.json["items"][0]["name"] == p.name

resp = client.get(
"/app/admin/projects?page=1&per_page=15&order_params=created DESC"
)
assert resp.json["projects"][0]["name"] == "foo13"
assert resp.json["items"][0]["name"] == "foo13"

resp = client.get("/app/admin/projects?page=1&per_page=15&name=12")
assert len(resp.json["projects"]) == 1
assert resp.json["projects"][0]["name"] == "foo12"
resp = client.get("/app/admin/projects?page=1&per_page=15&like=12")
assert len(resp.json["items"]) == 1
assert resp.json["items"][0]["name"] == "foo12"

resp = client.get("/app/admin/projects?page=1&per_page=15&name=foo")
assert len(resp.json["projects"]) == 14
resp = client.get("/app/admin/projects?page=1&per_page=15&like=foo")
assert len(resp.json["items"]) == 14

resp = client.get("/app/admin/projects?page=1&per_page=15&workspace=invalid")
assert len(resp.json["projects"]) == 0
resp = client.get("/app/admin/projects?page=1&per_page=15&like=invalid")
assert len(resp.json["items"]) == 0

resp = client.get("/app/admin/projects?page=1&per_page=15&workspace=mergin")
assert len(resp.json["projects"]) == 15
resp = client.get("/app/admin/projects?page=1&per_page=15&like=mergin")
assert len(resp.json["items"]) == 15

# delete project permanently
p.delete()
resp = client.get("/app/admin/projects?page=1&per_page=15&workspace=mergin")
assert len(resp.json["projects"]) == 14
resp = client.get("/app/admin/projects?page=1&per_page=15&like=mergin")
assert len(resp.json["items"]) == 14


def test_get_project_access(client):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const sidebarItems = computed<SideBarItemModel[]>(() => [
to: '/accounts',
icon: 'ti ti-user-circle',
active: route.matched.some((item) => item.name === AdminRoutes.ACCOUNTS)
},
{
title: 'Projects',
to: '/projects',
icon: 'ti ti-article',
active: route.matched.some((item) => item.name === AdminRoutes.PROJECTS)
}
])
</script>
Expand Down
Loading