diff --git a/auth/src/users/api/views.py b/auth/src/users/api/views.py index 891af05..272c320 100644 --- a/auth/src/users/api/views.py +++ b/auth/src/users/api/views.py @@ -3,6 +3,7 @@ from django.db import DatabaseError from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope from rest_framework import permissions +from rest_framework.exceptions import PermissionDenied from rest_framework.generics import RetrieveAPIView from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet @@ -50,9 +51,7 @@ def update(self, request, *args, **kwargs): edit_user = self.get_object() if not is_admin(request.user) and not request.user.is_superuser: - # Normal users cannot edit their own roles - print(f">>> Not admin: {request.user} trying to edit roles. Ignoring.") - request.data.pop("roles", None) + raise PermissionDenied("Normal users cannot edit their own roles") try: user = self.user_service.update_user(edit_user, **request.data) diff --git a/auth/src/users/services.py b/auth/src/users/services.py index 0051b04..37658fd 100644 --- a/auth/src/users/services.py +++ b/auth/src/users/services.py @@ -31,17 +31,19 @@ def create_user(self, **data: dict): def update_user(self, user: User, **data: dict): updated = False if username := data.pop("username", None): - updated = True - user.username = username + if user.username != username: + updated = True + user.username = username if password := data.pop("password", None): user.set_password(password) + user.save() if updated: user.save() - if roles := data.pop("roles", None): - updated = True + roles = data.pop("roles", None) + if roles is not None: user.roles.set(_roles(roles)) if updated: diff --git a/frontend/src/lib/api/user.ts b/frontend/src/lib/api/user.ts index efbdb6b..f9515b3 100644 --- a/frontend/src/lib/api/user.ts +++ b/frontend/src/lib/api/user.ts @@ -11,4 +11,12 @@ export class UserService { async getMe() { return await this.api.request('/api/v1/users/me/'); } + + async listUsers() { + return await this.api.request('/api/v1/users/'); + } + + async editUser(user: UserEdit) { + return await this.api.jsonRequest('PUT', `/api/v1/users/${user.id}/`, user); + } } diff --git a/frontend/src/lib/components/NavBar.svelte b/frontend/src/lib/components/NavBar.svelte index c33fa4e..3ac1b7c 100644 --- a/frontend/src/lib/components/NavBar.svelte +++ b/frontend/src/lib/components/NavBar.svelte @@ -47,7 +47,7 @@ height: 100%; width: 100%; object-fit: cover; - opacity: 0.4; + opacity: 0.35; border-radius: 2em; z-index: -1; } diff --git a/frontend/src/lib/components/TaskCard.svelte b/frontend/src/lib/components/TaskCard.svelte index 9588272..f937269 100644 --- a/frontend/src/lib/components/TaskCard.svelte +++ b/frontend/src/lib/components/TaskCard.svelte @@ -1,8 +1,7 @@ + +
+
+ + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index 86a36a9..a3e4d22 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -37,7 +37,7 @@ type ApiClientParams = { }; class ApiClient { - constructor(private params: ApiClientParams) {} + constructor(private params: ApiClientParams) { } async request(url: string, options?: RequestInit): Promise { try { @@ -48,6 +48,18 @@ class ApiClient { } } + async jsonRequest(method: "PUT" | "POST", url: string, body: any, options?: RequestInit): Promise { + return await this.request(url, { + ...options, + method: method, + headers: { + ...options?.headers, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body), + }); + } + async unsafeRequest(url: string, options?: RequestInit): Promise { if (url.startsWith('/')) url = url.substring(1); var authBackend = this.params.backendUrl; diff --git a/frontend/src/lib/typeUtils.ts b/frontend/src/lib/typeUtils.ts new file mode 100644 index 0000000..a3100c1 --- /dev/null +++ b/frontend/src/lib/typeUtils.ts @@ -0,0 +1,20 @@ +import { RequestError } from "$lib"; +import { isHttpError } from "@sveltejs/kit"; + +export function checkUserRoles(user: User, roles: UserRole[]) { + return roles.some(role => user.roles.includes(role)); +} + + +export function getErrorMsg(e: any) { + if (isHttpError(e)) { + return `${e.body.message}: ${e.body.data?.detail?.detail}`; + } + + if (e instanceof RequestError) { + return e.message; + } + + let er = e as { status: number; body: { message: string } }; + return `${er.status}: ${er.body.message}`; +} \ No newline at end of file diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index c9bae3a..ef7c360 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,8 +1,18 @@ +type UserRole = "admin" | "manager" | "performer"; + type User = { id: string; username: string; name: string; token: string; - roles: string[]; + roles: UserRole[]; publicId: string; }; + + + +type UserEdit = { + id: string; + username: string; + roles: UserRole[]; +}; \ No newline at end of file diff --git a/frontend/src/routes/me/+page.server.ts b/frontend/src/routes/me/+page.server.ts index e47594c..cc794bc 100644 --- a/frontend/src/routes/me/+page.server.ts +++ b/frontend/src/routes/me/+page.server.ts @@ -5,6 +5,7 @@ export const load = async ({ locals }) => { let { tokenInfo } = await ensureAuthenticated(await locals.auth()); return { + accessToken: tokenInfo.accessToken, me: await new UserService(tokenInfo.accessToken).getMe() }; }; diff --git a/frontend/src/routes/me/+page.svelte b/frontend/src/routes/me/+page.svelte index 58c22d9..37ca858 100644 --- a/frontend/src/routes/me/+page.svelte +++ b/frontend/src/routes/me/+page.svelte @@ -1,13 +1,64 @@ -

🦜 Me

+

🦜 {me.name}

+ +

Edit personal info

+ + + -
-

User

-
{JSON.stringify(data.me, null, 2)}
-

Session

-
{JSON.stringify(user, null, 2)}
+{#if meAdmin} +
+

Manage users

+ +
+ {#each users as user} + + {/each}
+ +{/if} + + + \ No newline at end of file diff --git a/tracker/src/tracker/api/views.py b/tracker/src/tracker/api/views.py index 930c8b6..4b2fc5c 100644 --- a/tracker/src/tracker/api/views.py +++ b/tracker/src/tracker/api/views.py @@ -62,5 +62,6 @@ def is_powerful_popug(self): def post(self, request, *args, **kwargs): self.is_powerful_popug() + self.task_service.reassign_tasks() return Response(status=status.HTTP_200_OK)