diff --git a/src/api/membershipService.ts b/src/api/membershipService.ts new file mode 100644 index 0000000..9f1db33 --- /dev/null +++ b/src/api/membershipService.ts @@ -0,0 +1,185 @@ +import { fetchAPIEvent } from '$api/apiService'; +import type { User } from '$models/user'; +import type { RequestEvent } from '@sveltejs/kit'; + +export interface MembershipFilter { + username?: string; + role_name?: string; + role_id?: string; + page?: number; + page_size?: number; + sort_by?: string; + sort_desc?: boolean; +} + +export interface PaginatedUsers { + users: User[]; + pagination: { + page: number; + page_size: number; + total: number; + total_pages: number; + }; +} + +export interface CreateUserRequest { + username: string; + password: string; + role: string; +} + +export interface UpdateUserRequest { + username?: string; + password?: string; + roleId?: string; +} + +export interface Role { + id: string; + name: string; +} + +export const membershipService = { + async getUsers(event: RequestEvent, filter?: MembershipFilter): Promise { + const queryParams = new URLSearchParams(); + + if (filter) { + if (filter.username) queryParams.append('username', filter.username); + if (filter.role_name) queryParams.append('role_name', filter.role_name); + if (filter.role_id) queryParams.append('role_id', filter.role_id); + if (filter.page) queryParams.append('page', filter.page.toString()); + if (filter.page_size) queryParams.append('page_size', filter.page_size.toString()); + if (filter.sort_by) queryParams.append('sort_by', filter.sort_by); + if (filter.sort_desc !== undefined) + queryParams.append('sort_desc', filter.sort_desc.toString()); + } + + const endpoint = `/membership${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; + return await fetchAPIEvent(event, endpoint); + }, + + async getUsersPaginated(event: RequestEvent, filter?: MembershipFilter): Promise { + const users = await this.getUsers(event, filter); + + const page = filter?.page || 1; + const pageSize = filter?.page_size || 10; + const startIndex = (page - 1) * pageSize; + const endIndex = startIndex + pageSize; + + const paginatedUsers = users.slice(startIndex, endIndex); + const totalPages = Math.ceil(users.length / pageSize); + + return { + users: paginatedUsers, + pagination: { + page, + page_size: pageSize, + total: users.length, + total_pages: totalPages + } + }; + }, + + async getUser(event: RequestEvent, userId: string): Promise { + return await fetchAPIEvent(event, `/membership/${userId}`); + }, + + async createUser(event: RequestEvent, userData: CreateUserRequest): Promise { + return await fetchAPIEvent(event, '/membership', 'POST', userData); + }, + + async updateUser( + event: RequestEvent, + userId: string, + userData: UpdateUserRequest + ): Promise { + return await fetchAPIEvent(event, `/membership/${userId}`, 'PUT', userData); + }, + + async deleteUser(event: RequestEvent, userId: string): Promise { + await fetchAPIEvent(event, `/membership/${userId}`, 'DELETE'); + }, + + async getRoles(event: RequestEvent): Promise { + return await fetchAPIEvent(event, '/membership/roles'); + } +}; + +// Client-side service for browser usage (not currently used) +export const membershipClientService = { + async getUsers(filter?: MembershipFilter): Promise { + const queryParams = new URLSearchParams(); + + if (filter) { + if (filter.username) queryParams.append('username', filter.username); + if (filter.role_name) queryParams.append('role_name', filter.role_name); + if (filter.role_id) queryParams.append('role_id', filter.role_id); + if (filter.page) queryParams.append('page', filter.page.toString()); + if (filter.page_size) queryParams.append('page_size', filter.page_size.toString()); + if (filter.sort_by) queryParams.append('sort_by', filter.sort_by); + if (filter.sort_desc !== undefined) + queryParams.append('sort_desc', filter.sort_desc.toString()); + } + + const endpoint = `/membership${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; + const response = await fetch(endpoint); + + if (!response.ok) { + throw new Error(`Failed to fetch users: ${response.statusText}`); + } + + return response.json(); + }, + + async createUser(userData: CreateUserRequest): Promise { + const response = await fetch('/membership', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(userData) + }); + + if (!response.ok) { + throw new Error(`Failed to create user: ${response.statusText}`); + } + + return response.json(); + }, + + async updateUser(userId: string, userData: UpdateUserRequest): Promise { + const response = await fetch(`/membership/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(userData) + }); + + if (!response.ok) { + throw new Error(`Failed to update user: ${response.statusText}`); + } + + return response.json(); + }, + + async deleteUser(userId: string): Promise { + const response = await fetch(`/membership/${userId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error(`Failed to delete user: ${response.statusText}`); + } + }, + + async getRoles(): Promise { + const response = await fetch('/membership/roles'); + + if (!response.ok) { + throw new Error(`Failed to fetch roles: ${response.statusText}`); + } + + return response.json(); + } +}; diff --git a/src/api/serverService.ts b/src/api/serverService.ts index 42db858..6146053 100644 --- a/src/api/serverService.ts +++ b/src/api/serverService.ts @@ -110,15 +110,15 @@ export const updateConfig = async ( ); }; -export const restartService = async (event: RequestEvent, serverId: number) => { +export const restartService = async (event: RequestEvent, serverId: string) => { return fetchAPIEvent(event, '/api/restart', 'POST', { serverId }); }; -export const startService = async (event: RequestEvent, serverId: number) => { +export const startService = async (event: RequestEvent, serverId: string) => { return fetchAPIEvent(event, '/api/start', 'POST', { serverId }); }; -export const stopService = async (event: RequestEvent, serverId: number) => { +export const stopService = async (event: RequestEvent, serverId: string) => { return fetchAPIEvent(event, '/api/stop', 'POST', { serverId }); }; diff --git a/src/components/Pagination.svelte b/src/components/Pagination.svelte new file mode 100644 index 0000000..46745db --- /dev/null +++ b/src/components/Pagination.svelte @@ -0,0 +1,95 @@ + + +
+ + + + + {#each pages as page} + {#if page === '...'} + ... + {:else} + + {/if} + {/each} + + + +
+ + +
+ Page {currentPage} of {totalPages} +
diff --git a/src/components/ServerCard.svelte b/src/components/ServerCard.svelte index c691eb4..e4857a9 100644 --- a/src/components/ServerCard.svelte +++ b/src/components/ServerCard.svelte @@ -34,11 +34,11 @@
Track: - {server.state.track} + {server.state?.track}
Players: - {server.state.playerCount} + {server.state?.playerCount}
diff --git a/src/models/server.ts b/src/models/server.ts index dedba59..e769a93 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -8,7 +8,7 @@ interface State { } export interface Server { - id: number; + id: string; name: string; status: ServiceStatus; state: State; diff --git a/src/routes/api/membership/+server.ts b/src/routes/api/membership/+server.ts new file mode 100644 index 0000000..8a1f0a6 --- /dev/null +++ b/src/routes/api/membership/+server.ts @@ -0,0 +1,27 @@ +import { fetchAPIEvent } from '$api/apiService'; +import { json, type RequestEvent } from '@sveltejs/kit'; + +export async function GET(event: RequestEvent) { + try { + const { url } = event; + const queryParams = url.searchParams.toString(); + const endpoint = `/membership${queryParams ? `?${queryParams}` : ''}`; + + const users = await fetchAPIEvent(event, endpoint); + return json(users); + } catch (error) { + console.error('Failed to fetch users:', error); + return json({ error: 'Failed to fetch users' }, { status: 500 }); + } +} + +export async function POST(event: RequestEvent) { + try { + const userData = await event.request.json(); + const user = await fetchAPIEvent(event, '/membership', 'POST', userData); + return json(user); + } catch (error) { + console.error('Failed to create user:', error); + return json({ error: 'Failed to create user' }, { status: 500 }); + } +} diff --git a/src/routes/api/membership/[id]/+server.ts b/src/routes/api/membership/[id]/+server.ts new file mode 100644 index 0000000..e0c5307 --- /dev/null +++ b/src/routes/api/membership/[id]/+server.ts @@ -0,0 +1,42 @@ +import { fetchAPIEvent } from '$api/apiService'; +import { json, type RequestEvent } from '@sveltejs/kit'; + +export async function GET(event: RequestEvent) { + try { + const { params } = event; + const userId = params.id; + + const user = await fetchAPIEvent(event, `/membership/${userId}`); + return json(user); + } catch (error) { + console.error('Failed to fetch user:', error); + return json({ error: 'Failed to fetch user' }, { status: 500 }); + } +} + +export async function PUT(event: RequestEvent) { + try { + const { params } = event; + const userId = params.id; + const userData = await event.request.json(); + + const user = await fetchAPIEvent(event, `/membership/${userId}`, 'PUT', userData); + return json(user); + } catch (error) { + console.error('Failed to update user:', error); + return json({ error: 'Failed to update user' }, { status: 500 }); + } +} + +export async function DELETE(event: RequestEvent) { + try { + const { params } = event; + const userId = params.id; + + await fetchAPIEvent(event, `/membership/${userId}`, 'DELETE'); + return json({ success: true }); + } catch (error) { + console.error('Failed to delete user:', error); + return json({ error: 'Failed to delete user' }, { status: 500 }); + } +} diff --git a/src/routes/dashboard/+page.server.ts b/src/routes/dashboard/+page.server.ts index 7a7eedf..4cc40a4 100644 --- a/src/routes/dashboard/+page.server.ts +++ b/src/routes/dashboard/+page.server.ts @@ -11,7 +11,7 @@ export const load = async (event: RequestEvent) => { // Helper function to create a server action with validation and error handling const createServerAction = ( - action: (event: RequestEvent, id: number) => Promise, + action: (event: RequestEvent, id: string) => Promise, { success, failure }: { success: string; failure: string } ) => { return async (event: RequestEvent) => { @@ -22,13 +22,8 @@ const createServerAction = ( return fail(400, { message: 'Invalid server ID provided.' }); } - const serverId = Number(id); - if (isNaN(serverId)) { - return fail(400, { message: 'Server ID must be a number.' }); - } - try { - await action(event, serverId); + await action(event, id); return { success: true, message: success }; } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 967b312..5837af0 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -17,7 +17,7 @@

ACC Server Manager

- {#if false && hasPermission($user, 'membership.view')} + {#if hasPermission($user, 'membership.view')} { - if (!locals.user) { +export const load = async (event) => { + if (!event.locals.user) { throw redirect(303, '/login'); } - const response = await fetch('/api/users'); - if (!response.ok) { - return { users: [] }; - } + // Get filter parameters from URL + const page = parseInt(event.url.searchParams.get('page') || '1'); + const pageSize = parseInt(event.url.searchParams.get('page_size') || '10'); + const username = event.url.searchParams.get('username') || ''; + const roleName = event.url.searchParams.get('role_name') || ''; + const sortBy = event.url.searchParams.get('sort_by') || 'username'; + const sortDesc = event.url.searchParams.get('sort_desc') === 'true'; - const users = await response.json(); - - return { - users + const filter = { + page, + page_size: pageSize, + username: username || undefined, + role_name: roleName || undefined, + sort_by: sortBy, + sort_desc: sortDesc }; + + try { + const [users, roles] = await Promise.all([ + membershipService.getUsers(event, filter), + membershipService.getRoles(event) + ]); + + console.log(users); + // Simple client-side pagination + const startIndex = (page - 1) * pageSize; + const endIndex = startIndex + pageSize; + const paginatedUsers = users.slice(startIndex, endIndex); + const totalPages = Math.ceil(users.length / pageSize); + + return { + users: paginatedUsers, + roles, + pagination: { + page, + page_size: pageSize, + total: users.length, + total_pages: totalPages + }, + filter: { + username, + role_name: roleName, + sort_by: sortBy, + sort_desc: sortDesc + } + }; + } catch (error) { + console.error('Failed to load users or roles:', error); + return { + users: [], + roles: [], + pagination: { + page: 1, + page_size: 10, + total: 0, + total_pages: 0 + }, + filter: { + username: '', + role_name: '', + sort_by: 'username', + sort_desc: false + } + }; + } }; + +export const actions = { + create: async (event) => { + const formData = await event.request.formData(); + const username = formData.get('username'); + const password = formData.get('password'); + const role = formData.get('role'); + + if (!username || !password || !role) { + return fail(400, { message: 'All fields are required' }); + } + + try { + await membershipService.createUser(event, { + username: username.toString(), + password: password.toString(), + role: role.toString() + }); + return { success: true, message: 'User created successfully' }; + } catch (error) { + console.error('Failed to create user:', error); + return fail(500, { message: 'Failed to create user' }); + } + }, + + delete: async (event) => { + const formData = await event.request.formData(); + const id = formData.get('id'); + + if (!id) { + return fail(400, { message: 'User ID is required' }); + } + + try { + await membershipService.deleteUser(event, id.toString()); + return { success: true, message: 'User deleted successfully' }; + } catch (error) { + console.error('Failed to delete user:', error); + return fail(500, { message: 'Failed to delete user' }); + } + } +} satisfies Actions; diff --git a/src/routes/dashboard/membership/+page.svelte b/src/routes/dashboard/membership/+page.svelte index ae8303b..080c7cb 100644 --- a/src/routes/dashboard/membership/+page.svelte +++ b/src/routes/dashboard/membership/+page.svelte @@ -1,46 +1,379 @@ -
-

User Management

+{#if form?.message} + +{/if} - {#if hasPermission($user, 'membership.create')} -
- +
+
+
+
+ + + + + +

User Management

+
+ {#if hasPermission($user, 'membership.create')} + + {/if}
- {/if} +
-
- - - - - - {#if hasPermission($user, 'membership.edit')} - - {/if} - - - - {#each users as user (user.id)} +
+ +
+

Filters

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ Showing {users.length} of {pagination.total} users + {#if pagination.total_pages > 1} + (Page {pagination.page} of {pagination.total_pages}) + {/if} +
+ + +
+
UsernameRoleActions
+ - - - {#if hasPermission(user, 'membership.edit')} - + + + {#if hasPermission($user, 'membership.edit')} + {/if} - {/each} - -
{user.username}{user.role.name} - - - + + + + + Actions +
-
+ + + {#each users as userItem (userItem.id)} + + + {userItem.username} + + + + {userItem.role.name} + + + {#if hasPermission($user, 'membership.edit')} + +
+ + {#if hasPermission($user, 'membership.delete')} + + {/if} +
+ + {/if} + + {:else} + + + No users found + + + {/each} + + +
+ + + {#if pagination.total_pages > 1} +
+ +
+ {/if} +
+ + +{#if showCreateModal} +
+
+

Create New User

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+{/if} + + +{#if showDeleteModal && selectedUser} +
+
+

Delete User

+

+ Are you sure you want to delete the user "{selectedUser.username}"? This action cannot be + undone. +

+
+ +
+ + +
+
+
+
+{/if} + + + User Management - ACC Server Manager +