diff --git a/backend/todo/routes/todos.py b/backend/todo/routes/todos.py index e7f7249..a17eed1 100644 --- a/backend/todo/routes/todos.py +++ b/backend/todo/routes/todos.py @@ -59,6 +59,8 @@ def read_todo( if not (current_user.is_admin or current_user.id == todo_owner.id): raise HTTPException(403, "You are not authorized to view this content.") + return todo + @router.get("/user/{user_id}", response_model=list[todoschema.TodoItem]) def read_todos( diff --git a/frontend/src/lib/api/endpoints.ts b/frontend/src/lib/api/endpoints.ts index a470dd9..462460a 100644 --- a/frontend/src/lib/api/endpoints.ts +++ b/frontend/src/lib/api/endpoints.ts @@ -1,5 +1,5 @@ import { error } from '@sveltejs/kit'; -import type { ItemCount, User } from './types'; +import type { ItemCount, TodoItem, User } from './types'; import { getResponseBodyOrError } from '$lib/api/utils'; import { getTokenFromLocalstorage } from '$lib/auth/session'; @@ -45,10 +45,22 @@ export async function readUser(userId: number, jwt: string = getTokenOrError()): * @param {string} jwt - The JWT appended as a bearer token to authorize the request. * @throws{error} - If the request fails or is not permitted. */ -export async function readUsers(jwt: string = getTokenOrError()): Promise { - // TODO add pagination and sorting query params - // TODO refactor Table.svelte - const endpoint = '/api/admin/users/'; +export async function readUsers( + skip: number, + limit: number, + sortby: string, + sortorder: string, + jwt: string = getTokenOrError(), +): Promise { + + const urlParameters = new URLSearchParams({ + skip: `${skip}`, + limit: `${limit}`, + sortby: `${sortby}`, + sortorder: `${sortorder}`, + }); + const endpoint = `/api/admin/users/?${urlParameters}`; + const response = await fetch(endpoint, { method: 'GET', headers: { @@ -83,3 +95,88 @@ export async function readUserCount(jwt: string = getTokenOrError()): Promise { + + const urlParameters = new URLSearchParams({ + skip: `${skip}`, + limit: `${limit}`, + sortby: `${sortby}`, + sortorder: `${sortorder}`, + }); + const endpoint = `/api/todos/user/${userId}?${urlParameters}`; + + const response = await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${jwt}`, + 'Content-Type': 'application/json', + } + }); + + const responseJson = await getResponseBodyOrError(response); + return responseJson as TodoItem[]; +} + +/** + * Retrieves the total todo-item count for the user with the specified ID from the backend API. + * + * @param {string} jwt - The JWT appended as a bearer token to authorize the request. + * @throws{error} - If the request fails or is not permitted. + */ +export async function readTodoCount( + userId: number, + jwt: string = getTokenOrError() +): Promise { + const endpoint = `/api/todos/user/${userId}/total/`; + const response = await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${jwt}`, + 'Content-Type': 'application/json', + } + }); + + const responseJson = await getResponseBodyOrError(response); + const itemCount = responseJson as ItemCount; + return itemCount.total; +} + +/** + * Retrieves the todo-item with the specified ID from the backend API. + * + * @param {string} jwt - The JWT appended as a bearer token to authorize the request. + * @throws{error} - If the request fails or is not permitted. + */ +export async function readTodo( + todoId: number, + jwt: string = getTokenOrError() +): Promise { + const endpoint = `/api/todos/${todoId}`; + const response = await fetch(endpoint, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${jwt}`, + 'Content-Type': 'application/json', + } + }); + + const responseJson = await getResponseBodyOrError(response); + return responseJson as TodoItem; +} diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index ba16a48..7ee7cc6 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -1,3 +1,7 @@ +export type ItemCount = { + total: number +} + export type User = { id: number, email: string, @@ -8,6 +12,12 @@ export type User = { is_admin: boolean, } -export type ItemCount = { - total: number +export type TodoItem = { + id: number, + title: string, + description: string, + done: boolean, + created: Date, + updated: Date, + finished: Date, } diff --git a/frontend/src/lib/data-table/Table.svelte b/frontend/src/lib/components/data-table/Table.svelte similarity index 70% rename from frontend/src/lib/data-table/Table.svelte rename to frontend/src/lib/components/data-table/Table.svelte index 9adcf21..5bec6ce 100644 --- a/frontend/src/lib/data-table/Table.svelte +++ b/frontend/src/lib/components/data-table/Table.svelte @@ -1,33 +1,35 @@ @@ -19,6 +21,10 @@ {/if} {:else} - {data} + {#if isLink} + {data} + {:else} + {data} + {/if} {/if} diff --git a/frontend/src/lib/data-table/Th.svelte b/frontend/src/lib/components/data-table/Th.svelte similarity index 100% rename from frontend/src/lib/data-table/Th.svelte rename to frontend/src/lib/components/data-table/Th.svelte diff --git a/frontend/src/lib/components/data-table/types.ts b/frontend/src/lib/components/data-table/types.ts new file mode 100644 index 0000000..b741f5c --- /dev/null +++ b/frontend/src/lib/components/data-table/types.ts @@ -0,0 +1,13 @@ +export type Column = { + field: string, + heading: string, + type?: string, + sortable?: boolean, + isLink?: boolean, + linkTarget?: string, +} + +export type Endpoint = { + callable: Function, + args: any[], +} diff --git a/frontend/src/lib/components/data-table/utils.ts b/frontend/src/lib/components/data-table/utils.ts new file mode 100644 index 0000000..0c03d46 --- /dev/null +++ b/frontend/src/lib/components/data-table/utils.ts @@ -0,0 +1,11 @@ +export function interpolateString(templateString: string, replacementItems: { [key: string]: any }): string { + if (typeof templateString !== 'string') { + return ''; + } + return templateString.replace(/%(\w+)%/g, (match, key) => { + if (replacementItems.hasOwnProperty(key)) { + return replacementItems[key].toString(); + } + return match; + }); +} diff --git a/frontend/src/lib/components/navbar/Navbar.svelte b/frontend/src/lib/components/navbar/Navbar.svelte new file mode 100644 index 0000000..e428363 --- /dev/null +++ b/frontend/src/lib/components/navbar/Navbar.svelte @@ -0,0 +1,33 @@ + + + diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 3edeeda..0c4d902 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -23,11 +23,10 @@ - + diff --git a/frontend/src/routes/todos/[todo]/+page.svelte b/frontend/src/routes/todos/[todo]/+page.svelte new file mode 100644 index 0000000..2efc4b3 --- /dev/null +++ b/frontend/src/routes/todos/[todo]/+page.svelte @@ -0,0 +1,7 @@ + + +

{data.todo.title}

diff --git a/frontend/src/routes/todos/[todo]/+page.ts b/frontend/src/routes/todos/[todo]/+page.ts new file mode 100644 index 0000000..19495da --- /dev/null +++ b/frontend/src/routes/todos/[todo]/+page.ts @@ -0,0 +1,18 @@ +import type { PageLoad } from './$types'; +import type { TodoItem } from '$lib/api/types'; +import { readTodo } from '$lib/api/endpoints'; + +export const ssr = false; +export const load = (async ({ params }) => { + // check if user exists + const todoId = params.todo; + const todo = await readTodo(todoId); + return { + todo: todo + }; + +}) satisfies PageLoad; + +export interface UserProfilePage { + todo: TodoItem +} diff --git a/frontend/src/routes/users/[user]/todos/+page.svelte b/frontend/src/routes/users/[user]/todos/+page.svelte index 3755edc..7ce2b67 100644 --- a/frontend/src/routes/users/[user]/todos/+page.svelte +++ b/frontend/src/routes/users/[user]/todos/+page.svelte @@ -1,11 +1,12 @@ diff --git a/frontend/src/routes/users/all/+page.svelte b/frontend/src/routes/users/all/+page.svelte index 27d64df..57d5925 100644 --- a/frontend/src/routes/users/all/+page.svelte +++ b/frontend/src/routes/users/all/+page.svelte @@ -1,43 +1,55 @@