Add admin panel
This commit is contained in:
@@ -13,6 +13,17 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.url.pathname.startsWith('/admin')) {
|
||||||
|
const current_user = await authenticateUser(event);
|
||||||
|
if (!current_user) {
|
||||||
|
throw redirect(302, '/login');
|
||||||
|
}
|
||||||
|
if (!current_user.is_admin) {
|
||||||
|
throw redirect(302, '/');
|
||||||
|
}
|
||||||
|
event.locals.user = current_user;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await resolve(event);
|
const response = await resolve(event);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { Actions } from './$types';
|
||||||
|
|
||||||
|
const api_url = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
addUser: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const userData = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/user`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(userData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to add user');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUser: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const userId = formData.get('userId');
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/user/${userId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to delete user');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
addMovie: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const movieData = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/movie`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(movieData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to add movie');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
setMovieWatched: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const movieId = formData.get('movieId');
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/movie/${movieId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ is_watched: true })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to update movie status');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
addCard: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const cardData = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/card`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(cardData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to add card');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCard: async ({ request, fetch }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const cardId = formData.get('cardId');
|
||||||
|
|
||||||
|
const response = await fetch(`${api_url}/admin/card/${cardId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw error(response.status, 'Failed to delete card');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
let activeTab = 'users';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="admin-panel">
|
||||||
|
<nav class="tabs">
|
||||||
|
<button
|
||||||
|
class:active={activeTab === 'users'}
|
||||||
|
on:click={() => activeTab = 'users'}>Users</button>
|
||||||
|
<button
|
||||||
|
class:active={activeTab === 'movies'}
|
||||||
|
on:click={() => activeTab = 'movies'}>Movies</button>
|
||||||
|
<button
|
||||||
|
class:active={activeTab === 'cards'}
|
||||||
|
on:click={() => activeTab = 'cards'}>Cards</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
{#if activeTab === 'users'}
|
||||||
|
<div class="section">
|
||||||
|
<h2>Add User</h2>
|
||||||
|
<form method="POST" action="?/addUser" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Add User</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Delete User</h2>
|
||||||
|
<form method="POST" action="?/deleteUser" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="userId">User ID</label>
|
||||||
|
<input type="text" id="userId" name="userId" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="danger">Delete User</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeTab === 'movies'}
|
||||||
|
<div class="section">
|
||||||
|
<h2>Add Movie</h2>
|
||||||
|
<form method="POST" action="?/addMovie" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" id="title" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea id="description" name="description" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Add Movie</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Mark Movie as Watched</h2>
|
||||||
|
<form method="POST" action="?/setMovieWatched" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="movieId">Movie ID</label>
|
||||||
|
<input type="text" id="movieId" name="movieId" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Mark as Watched</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeTab === 'cards'}
|
||||||
|
<div class="section">
|
||||||
|
<h2>Add Card</h2>
|
||||||
|
<form method="POST" action="?/addCard" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cardName">Card Name</label>
|
||||||
|
<input type="text" id="cardName" name="cardName" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cardValue">Card Value</label>
|
||||||
|
<input type="number" id="cardValue" name="cardValue" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Add Card</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2>Delete Card</h2>
|
||||||
|
<form method="POST" action="?/deleteCard" use:enhance>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cardId">Card ID</label>
|
||||||
|
<input type="text" id="cardId" name="cardId" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="danger">Delete Card</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.admin-panel {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button.active {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger {
|
||||||
|
background: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user