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
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
38 changes: 13 additions & 25 deletions web-app/packages/admin-app/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

import {
AccountView
// ProfileView,
AccountsView,
AccountDetailView
// SettingsView,
// ProjectSettingsView,
// ProjectsView,
Expand Down Expand Up @@ -67,7 +67,7 @@ export const createRouter = (pinia: Pinia) => {
path: '/accounts',
name: 'accounts',
components: {
default: AccountView,
default: AccountsView,
sidebar: Sidebar,
header: AppHeader
},
Expand All @@ -77,30 +77,18 @@ export const createRouter = (pinia: Pinia) => {
routeUtils.isAuthenticatedGuard(to, from, next, userStore)
routeUtils.isSuperUser(to, from, next, userStore)
}
},
{
path: '/user/:username',
name: 'account',
components: {
default: AccountDetailView,
sidebar: Sidebar,
header: AppHeader
},
props: true
}
// {
// path: '/user/:username',
// name: 'profile',
// component: ProfileView,
// props: true,
// beforeEnter: async (to, from, next) => {
// const adminStore = useAdminStore(pinia)
// adminStore.setUserAdminProfile(null)
// try {
// await adminStore.fetchUserProfileByName({
// username: to.params.username
// })
// next()
// } catch (e) {
// next(
// Error(
// errorUtils.getErrorMessage(e, 'Failed to fetch user profile')
// )
// )
// }
// }
// },
// {
// path: '/projects',
// name: 'projects',
// component: ProjectsView,
Expand Down
1 change: 1 addition & 0 deletions web-app/packages/admin-app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default defineConfig(({ mode }) => ({
sourcemap: mode !== 'production'
},
optimizeDeps: {
include: ['vue-router'],
exclude: ['vue', '@mergin', 'vue-demi'],
esbuildOptions: {
define: {
Expand Down
6 changes: 6 additions & 0 deletions web-app/packages/admin-lib/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ export {}

declare module 'vue' {
export interface GlobalComponents {
PAvatar: typeof import('primevue/avatar')['default']
PButton: typeof import('primevue/button')['default']
PColumn: typeof import('primevue/column')['default']
PDataTable: typeof import('primevue/datatable')['default']
PInputSwitch: typeof import('primevue/inputswitch')['default']
PInputText: typeof import('primevue/inputtext')['default']
PPassword: typeof import('primevue/password')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
Expand Down
31 changes: 17 additions & 14 deletions web-app/packages/admin-lib/src/modules/admin/adminApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,13 @@ export const AdminApi = {
return AdminModule.httpService.get(`/app/admin/users`, { params })
},

async fetchUserProfileByName(
async fetchUserByName(
username: string
): Promise<AxiosResponse<UserResponse>> {
return AdminModule.httpService?.get(`/app/admin/user/${username}?random=${Math.random()}`)
return AdminModule.httpService?.get(`/app/admin/user/${username}`)
},

async deleteUser(
username: number,
withRetry?: boolean
): Promise<AxiosResponse<void>> {
async deleteUser(username: number): Promise<AxiosResponse<void>> {
return AdminModule.httpService.delete(
`/app/admin/user/${username}` /*, {
...(withRetry ? getDefaultRetryOptions() : {})
Expand All @@ -50,8 +47,7 @@ export const AdminApi = {

async updateUser(
username: string,
data: UpdateUserData,
withRetry?: boolean
data: UpdateUserData
): Promise<AxiosResponse<UserResponse>> {
return AdminModule.httpService.patch(
`/app/admin/user/${username}`,
Expand Down Expand Up @@ -118,9 +114,12 @@ export const AdminApi = {
* @return Result promise
*/
async removeProject(id: number): Promise<AxiosResponse<void>> {
return await AdminModule.httpService.delete(`/app/project/removed-project/${id}`, {
'axios-retry': { retries: 5 }
})
return await AdminModule.httpService.delete(
`/app/project/removed-project/${id}`,
{
'axios-retry': { retries: 5 }
}
)
},

/**
Expand All @@ -129,8 +128,12 @@ export const AdminApi = {
* @return Result promise
*/
async restoreProject(id: number): Promise<AxiosResponse<void>> {
return await AdminModule.httpService.post(`/app/project/removed-project/restore/${id}`, null, {
'axios-retry': { retries: 5 }
})
return await AdminModule.httpService.post(
`/app/project/removed-project/restore/${id}`,
null,
{
'axios-retry': { retries: 5 }
}
)
}
}
Loading