Skip to content

Commit

Permalink
user edits
Browse files Browse the repository at this point in the history
  • Loading branch information
BrittleFoot committed Mar 2, 2024
1 parent 686c877 commit de72c2c
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 29 deletions.
5 changes: 2 additions & 3 deletions auth/src/users/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions auth/src/users/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/lib/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ export class UserService {
async getMe() {
return await this.api.request<User>('/api/v1/users/me/');
}

async listUsers() {
return await this.api.request<User[]>('/api/v1/users/');
}

async editUser(user: UserEdit) {
return await this.api.jsonRequest<User>('PUT', `/api/v1/users/${user.id}/`, user);
}
}
2 changes: 1 addition & 1 deletion frontend/src/lib/components/NavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
height: 100%;
width: 100%;
object-fit: cover;
opacity: 0.4;
opacity: 0.35;
border-radius: 2em;
z-index: -1;
}
Expand Down
16 changes: 4 additions & 12 deletions frontend/src/lib/components/TaskCard.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import { RequestError } from '$lib';
import type { Task } from '$lib/api/tracker';
import { isHttpError } from '@sveltejs/kit';
import { getErrorMsg } from '$lib/typeUtils';
export let onMarkedCompleted: (task: Task) => Promise<void>;
export let task: Task;
Expand All @@ -26,14 +25,7 @@
task.status = prevTaskStatus;
task = task;
if (isHttpError(e)) {
error = `${e.body.message}: ${e.body.data?.detail?.detail}`;
} else if (e instanceof RequestError){
error = e.message;
} else {
let er = e as { status: number; body: { message: string } };
error = `${er.status}: ${er.body.message}`;
}
error = getErrorMsg(e);
}
submitting = false;
}
Expand All @@ -59,8 +51,8 @@
<div class="tooltip" data-tooltip="{error}" data-placement="left"/>
</button>
{:else if task.status === 'done'}
<button type="submit" class="nowrap success" disabled>
👍
<button type="submit" class="nowrap success" disabled aria-busy={submitting}>
{submitting ? "" : '👍'}
<div class="tooltip" data-tooltip="Completed!"/>
</button>
{:else if isMeTaskOwner}
Expand Down
120 changes: 120 additions & 0 deletions frontend/src/lib/components/UserCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

<script lang="ts">
import { getErrorMsg } from "$lib/typeUtils";
export let user: User;
export let update: (user: UserEdit) => Promise<User>;
type UserForm = {
id: string;
username: string;
isAdmin: boolean;
isManager: boolean;
isPerformer: boolean;
}
let {id, username, isAdmin, isManager, isPerformer} = fillForm(user);
let roles: UserRole[] = [];
$: {
roles = [];
if (isAdmin) roles.push("admin");
if (isManager) roles.push("manager");
if (isPerformer) roles.push("performer");
}
let status: "idle" | "pending" | "success" | "error" = "idle";
let errormsg = "";
$: newUser = {id, username, roles} as UserEdit;
$: isEdited = user.username !== newUser.username || !setsEqual(user.roles, newUser.roles);
function setsEqual(a: any[], b: any[]) {
return a.length === b.length && a.every((v, i) => b.includes(v));
}
async function handelSubmit(event: Event) {
status = "pending";
try {
user = await update({id, username, roles} as UserEdit)
status = "success";
} catch (error) {
refillForm(user);
console.log(user);
errormsg = getErrorMsg(error);
console.log(errormsg);
status = "error";
}
}
function fillForm(user: User): UserForm {
return {
id: user.id,
username: user.username,
isAdmin: user.roles.includes("admin"),
isManager: user.roles.includes("manager"),
isPerformer: user.roles.includes("performer"),
}
}
function refillForm(user: User) {
let userForm = fillForm(user);
id = userForm.id;
username = userForm.username;
isAdmin = userForm.isAdmin;
isManager = userForm.isManager;
isPerformer = userForm.isPerformer;
}
</script>

<div class="card">
<form on:submit|preventDefault={handelSubmit}>
<input type="text" placeholder="Username" bind:value={username} />

<label for="admin">
<input type="checkbox" bind:checked={isAdmin} disabled={!isAdmin}/>
Admin
</label>
<label for="manager">
<input type="checkbox" bind:checked={isManager} disabled={!isAdmin}/>
Manager
</label>

<label for="performer">
<input type="checkbox" bind:checked={isPerformer} disabled={!isAdmin}/>
Performer
</label>

<input
type="submit"
value="Update"
class="margin-0"
disabled={!isEdited}
class:secondary={!isEdited}
aria-busy={status === "pending"}
/>

</form>
</div>

<style>
.card {
display: flex;
flex-direction: column;
gap: 1em;
padding: 1em;
max-width: calc(100%/3 - 1em);
min-width: 10em;
background-color: #1a202ccc;
border-radius: 0.5em;
}
.margin-0 {
margin: 0.1em 0;
}
</style>
14 changes: 13 additions & 1 deletion frontend/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type ApiClientParams = {
};

class ApiClient {
constructor(private params: ApiClientParams) {}
constructor(private params: ApiClientParams) { }

async request<T>(url: string, options?: RequestInit): Promise<T> {
try {
Expand All @@ -48,6 +48,18 @@ class ApiClient {
}
}

async jsonRequest<T>(method: "PUT" | "POST", url: string, body: any, options?: RequestInit): Promise<T> {
return await this.request(url, {
...options,
method: method,
headers: {
...options?.headers,
'Content-Type': 'application/json'
},
body: JSON.stringify(body),
});
}

async unsafeRequest<T>(url: string, options?: RequestInit): Promise<T> {
if (url.startsWith('/')) url = url.substring(1);
var authBackend = this.params.backendUrl;
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/lib/typeUtils.ts
Original file line number Diff line number Diff line change
@@ -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}`;
}
12 changes: 11 additions & 1 deletion frontend/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
};
1 change: 1 addition & 0 deletions frontend/src/routes/me/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
};
65 changes: 58 additions & 7 deletions frontend/src/routes/me/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,64 @@
<script lang="ts">
import { UserService } from '$lib/api/user.js';
import UserCard from '$lib/components/UserCard.svelte';
import { checkUserRoles } from '$lib/typeUtils';
import { onMount } from 'svelte';
export let data;
let user = data.session?.user;
const userService = new UserService(data.accessToken);
let me = data.me;
let users: User[] = [];
$: meAdmin = checkUserRoles(me, ['admin']);
onMount(async () => {
await refreshManagedUsers();
});
async function update(user: UserEdit): Promise<User> {
let updated = await userService.editUser(user);
if (updated.publicId === me.publicId) {
me = updated;
await refreshManagedUsers();
}
return updated;
}
async function refreshManagedUsers() {
if (!meAdmin) return;
users = (await userService.listUsers()).filter(u => u.publicId !== me.publicId);
}
</script>

<h1>🦜 Me</h1>
<h1>🦜 {me.name}</h1>

<h2>Edit personal info</h2>

<UserCard user={me} {update}/>


<div class="card">
<h2>User</h2>
<pre>{JSON.stringify(data.me, null, 2)}</pre>
<h2>Session</h2>
<pre>{JSON.stringify(user, null, 2)}</pre>
{#if meAdmin}
<br/>
<h2>Manage users</h2>

<div class="cards">
{#each users as user}
<UserCard {user} {update}/>
{/each}
</div>

{/if}


<style>
.cards {
display: flex;
flex-wrap: wrap;
gap: 1em;
}
</style>
1 change: 1 addition & 0 deletions tracker/src/tracker/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit de72c2c

Please sign in to comment.