Compare commits
3 Commits
b610b30a78
...
8b18623232
Author | SHA1 | Date | |
---|---|---|---|
8b18623232 | |||
ba1146facf | |||
7e6a09563a |
@ -66,6 +66,17 @@ export const signup = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if user is blocked
|
||||||
|
if (uuid) {
|
||||||
|
const blockedUser = await db.getBlockedUserByUuid({ uuid: uuid });
|
||||||
|
if (blockedUser) {
|
||||||
|
throw new ActionError({
|
||||||
|
code: 'FORBIDDEN',
|
||||||
|
message: 'Du bist für die Registrierung gesperrt'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!teamDraft) {
|
if (!teamDraft) {
|
||||||
// check if a team with the same name already exists
|
// check if a team with the same name already exists
|
||||||
if (input.teamName) {
|
if (input.teamName) {
|
||||||
|
@ -84,5 +84,41 @@ export const user = {
|
|||||||
users: users
|
users: users
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
addBlocked: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
uuid: z.string(),
|
||||||
|
comment: z.string().nullable()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
const { id } = await db.addBlockedUser(input);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editBlocked: defineAction({
|
||||||
|
input: z.object({
|
||||||
|
id: z.number(),
|
||||||
|
uuid: z.string(),
|
||||||
|
comment: z.string().nullable()
|
||||||
|
}),
|
||||||
|
handler: async (input, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
await db.editBlockedUser(input);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
blocked: defineAction({
|
||||||
|
handler: async (_, context) => {
|
||||||
|
Session.actionSessionFromCookies(context.cookies, Permissions.Users);
|
||||||
|
|
||||||
|
return {
|
||||||
|
blocked: await db.getBlockedUsers({})
|
||||||
|
};
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
101
src/app/admin/usersBlocked/CreateOrEditPopup.svelte
Normal file
101
src/app/admin/usersBlocked/CreateOrEditPopup.svelte
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Input from '@components/input/Input.svelte';
|
||||||
|
import Textarea from '@components/input/Textarea.svelte';
|
||||||
|
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||||
|
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
|
||||||
|
import { blockedUserCreateOrEditPopupState } from '@app/admin/usersBlocked/state.ts';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
|
// html bindings
|
||||||
|
let modal: HTMLDialogElement;
|
||||||
|
let modalForm: HTMLFormElement;
|
||||||
|
|
||||||
|
// states
|
||||||
|
let action = $state<'create' | 'edit' | null>(null);
|
||||||
|
let blockedUser = $state({} as BlockedUser);
|
||||||
|
let onUpdate = $state((_: BlockedUser) => {});
|
||||||
|
|
||||||
|
let submitEnabled = $derived(!!blockedUser.uuid);
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
const cancel = blockedUserCreateOrEditPopupState.subscribe((value) => {
|
||||||
|
if (value && 'create' in value) {
|
||||||
|
action = 'create';
|
||||||
|
blockedUser = {
|
||||||
|
id: -1,
|
||||||
|
uuid: '',
|
||||||
|
comment: null
|
||||||
|
};
|
||||||
|
onUpdate = value?.create.onUpdate;
|
||||||
|
modal.show();
|
||||||
|
} else if (value && 'edit' in value) {
|
||||||
|
action = 'edit';
|
||||||
|
blockedUser = value.edit.blockedUser;
|
||||||
|
onUpdate = value.edit.onUpdate;
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(cancel);
|
||||||
|
|
||||||
|
// texts
|
||||||
|
const texts = {
|
||||||
|
create: {
|
||||||
|
title: 'Blockierten Nutzer erstellen',
|
||||||
|
buttonTitle: 'Erstellen',
|
||||||
|
confirmPopupTitle: 'Nutzer blockieren?',
|
||||||
|
confirmPopupMessage:
|
||||||
|
'Bist du sicher, dass der Nutzer blockiert werden soll?\nEin blockierter Nutzer kann sich nicht mehr registrieren.'
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: 'Blockierten Nutzer bearbeiten',
|
||||||
|
buttonTitle: 'Speichern',
|
||||||
|
confirmPopupTitle: 'Änderungen speichern?',
|
||||||
|
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
|
||||||
|
},
|
||||||
|
null: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
async function onSaveButtonClick(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
$confirmPopupState = {
|
||||||
|
title: texts[action!].confirmPopupTitle,
|
||||||
|
message: texts[action!].confirmPopupMessage,
|
||||||
|
onConfirm: () => {
|
||||||
|
modalForm.submit();
|
||||||
|
onUpdate(blockedUser);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancelButtonClick(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
modalForm.submit();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog class="modal" bind:this={modal}>
|
||||||
|
<form method="dialog" class="modal-box" bind:this={modalForm}>
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
|
||||||
|
<div class="space-y-5">
|
||||||
|
<h3 class="text-xt font-geist font-bold">{texts[action!].title}</h3>
|
||||||
|
<div>
|
||||||
|
<Input bind:value={blockedUser.uuid} label="UUID" required dynamicWidth />
|
||||||
|
<Textarea label="Kommentar" bind:value={blockedUser.comment} rows={3} dynamicWidth />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-success"
|
||||||
|
class:disabled={!submitEnabled}
|
||||||
|
disabled={!submitEnabled}
|
||||||
|
onclick={onSaveButtonClick}>{texts[action!].buttonTitle}</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-error" onclick={onCancelButtonClick}>Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
|
||||||
|
<button class="!cursor-default">close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
26
src/app/admin/usersBlocked/SidebarActions.svelte
Normal file
26
src/app/admin/usersBlocked/SidebarActions.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import { addBlockedUser, fetchBlockedUsers } from '@app/admin/usersBlocked/actions.ts';
|
||||||
|
import { blockedUserCreateOrEditPopupState } from '@app/admin/usersBlocked/state.ts';
|
||||||
|
|
||||||
|
// lifecycle
|
||||||
|
$effect(() => {
|
||||||
|
fetchBlockedUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
async function onNewUserButtonClick() {
|
||||||
|
$blockedUserCreateOrEditPopupState = {
|
||||||
|
create: {
|
||||||
|
onUpdate: addBlockedUser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-soft w-full" onclick={() => onNewUserButtonClick()}>
|
||||||
|
<Icon icon="heroicons:plus-16-solid" />
|
||||||
|
<span>Neuer blockierter Nutzer</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
48
src/app/admin/usersBlocked/UsersBlocked.svelte
Normal file
48
src/app/admin/usersBlocked/UsersBlocked.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Icon from '@iconify/svelte';
|
||||||
|
import CreateOrEditPopup from './CreateOrEditPopup.svelte';
|
||||||
|
import SortableTr from '@components/admin/table/SortableTr.svelte';
|
||||||
|
import SortableTh from '@components/admin/table/SortableTh.svelte';
|
||||||
|
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
|
||||||
|
import { blockedUserCreateOrEditPopupState, blockedUsers } from '@app/admin/usersBlocked/state.ts';
|
||||||
|
import { editBlockedUser } from '@app/admin/usersBlocked/actions.ts';
|
||||||
|
|
||||||
|
// callbacks
|
||||||
|
async function onBlockedUserEditButtonClick(blockedUser: BlockedUser) {
|
||||||
|
$blockedUserCreateOrEditPopupState = {
|
||||||
|
edit: {
|
||||||
|
blockedUser: blockedUser,
|
||||||
|
onUpdate: editBlockedUser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="h-screen overflow-x-auto">
|
||||||
|
<table class="table table-pin-rows">
|
||||||
|
<thead>
|
||||||
|
<SortableTr data={blockedUsers}>
|
||||||
|
<SortableTh style="width: 5%">#</SortableTh>
|
||||||
|
<SortableTh style="width: 20%" key="uuid">UUID</SortableTh>
|
||||||
|
<SortableTh style="width: 70%">Kommentar</SortableTh>
|
||||||
|
<SortableTh style="width: 5%"></SortableTh>
|
||||||
|
</SortableTr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each $blockedUsers as blockedUser, i (blockedUser)}
|
||||||
|
<tr class="hover:bg-base-200">
|
||||||
|
<td>{i + 1}</td>
|
||||||
|
<td>{blockedUser.uuid}</td>
|
||||||
|
<td>{blockedUser.comment}</td>
|
||||||
|
<td>
|
||||||
|
<button class="cursor-pointer" onclick={() => onBlockedUserEditButtonClick(blockedUser)}>
|
||||||
|
<Icon icon="heroicons:pencil-square" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CreateOrEditPopup />
|
41
src/app/admin/usersBlocked/actions.ts
Normal file
41
src/app/admin/usersBlocked/actions.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { actions } from 'astro:actions';
|
||||||
|
import { actionErrorPopup } from '@util/action.ts';
|
||||||
|
import { blockedUsers } from '@app/admin/usersBlocked/state.ts';
|
||||||
|
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
|
||||||
|
|
||||||
|
export async function fetchBlockedUsers() {
|
||||||
|
const { data, error } = await actions.user.blocked();
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedUsers.set(data.blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addBlockedUser(blockedUser: BlockedUser) {
|
||||||
|
const { data, error } = await actions.user.addBlocked(blockedUser);
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedUsers.update((old) => {
|
||||||
|
old.push(Object.assign(blockedUser, { id: data.id }));
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editBlockedUser(blockedUser: BlockedUser) {
|
||||||
|
const { data, error } = await actions.user.editBlocked(blockedUser);
|
||||||
|
if (error) {
|
||||||
|
actionErrorPopup(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedUsers.update((old) => {
|
||||||
|
const index = old.findIndex((a) => a.id == user.id);
|
||||||
|
old[index] = blockedUser;
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
}
|
6
src/app/admin/usersBlocked/state.ts
Normal file
6
src/app/admin/usersBlocked/state.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import type { BlockedUserCreateOrEditPopupState, BlockedUsers } from '@app/admin/usersBlocked/types.ts';
|
||||||
|
|
||||||
|
export const blockedUsers = writable<BlockedUsers>([]);
|
||||||
|
|
||||||
|
export const blockedUserCreateOrEditPopupState = writable<BlockedUserCreateOrEditPopupState>(null);
|
9
src/app/admin/usersBlocked/types.ts
Normal file
9
src/app/admin/usersBlocked/types.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { type ActionReturnType, actions } from 'astro:actions';
|
||||||
|
|
||||||
|
export type BlockedUsers = Exclude<ActionReturnType<typeof actions.user.blocked>['data'], undefined>['blocked'];
|
||||||
|
export type BlockedUser = BlockedUsers[0];
|
||||||
|
|
||||||
|
export type BlockedUserCreateOrEditPopupState =
|
||||||
|
| { create: { onUpdate: (blockedUser: BlockedUser) => void } }
|
||||||
|
| { edit: { blockedUser: BlockedUser; onUpdate: (blockedUser: BlockedUser) => void } }
|
||||||
|
| null;
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Steve from '@assets/img/steve.png';
|
import Steve from '@assets/img/steve.png';
|
||||||
import Team from '@components/website/Team.svelte';
|
|
||||||
import type { GetDeathsRes } from '@db/schema/death.ts';
|
import type { GetDeathsRes } from '@db/schema/death.ts';
|
||||||
import { type ActionReturnType, actions } from 'astro:actions';
|
import { type ActionReturnType, actions } from 'astro:actions';
|
||||||
|
|
||||||
@ -26,7 +25,10 @@
|
|||||||
{#each teams as team (team.id)}
|
{#each teams as team (team.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<Team name={team.name} color={team.color} />
|
<div class="flex items-center gap-x-2">
|
||||||
|
<div class="rounded-sm w-3 h-3" style="background-color: {team.color}"></div>
|
||||||
|
<h3 class="text-xs sm:text-xl">{team.name}</h3>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="max-w-9 overflow-ellipsis">
|
<td class="max-w-9 overflow-ellipsis">
|
||||||
{#if team.memberOne.id}
|
{#if team.memberOne.id}
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { registeredPopupState } from '@components/website/signup/RegisteredPopup.ts';
|
import { registeredPopupState } from '@app/website/signup/RegisteredPopup.ts';
|
||||||
import Input from '@components/input/Input.svelte';
|
import Input from '@components/input/Input.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { teamPopupOpen, teamPopupName } from '@components/website/signup/TeamPopup.ts';
|
import { teamPopupOpen, teamPopupName } from '@app/website/signup/TeamPopup.ts';
|
||||||
|
|
||||||
let modal: HTMLDialogElement;
|
let modal: HTMLDialogElement;
|
||||||
let form: HTMLFormElement;
|
let form: HTMLFormElement;
|
@ -1,13 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
interface Props {
|
|
||||||
name: string;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, color }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-x-2">
|
|
||||||
<div class="rounded-sm w-3 h-3" style="background-color: {color}"></div>
|
|
||||||
<h3 class="text-xs sm:text-xl">{name}</h3>
|
|
||||||
</div>
|
|
@ -28,6 +28,13 @@ CREATE TRIGGER IF NOT EXISTS user_username_update AFTER UPDATE ON user
|
|||||||
END;
|
END;
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|
||||||
|
-- blocked user
|
||||||
|
CREATE TABLE IF NOT EXISTS blocked_user (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
uuid VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
comment TINYTEXT
|
||||||
|
);
|
||||||
|
|
||||||
-- team
|
-- team
|
||||||
CREATE TABLE IF NOT EXISTS team (
|
CREATE TABLE IF NOT EXISTS team (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
@ -40,8 +47,8 @@ CREATE TABLE IF NOT EXISTS team (
|
|||||||
CREATE TABLE IF NOT EXISTS team_member (
|
CREATE TABLE IF NOT EXISTS team_member (
|
||||||
team_id INT NOT NULL,
|
team_id INT NOT NULL,
|
||||||
user_id INT NOT NULL,
|
user_id INT NOT NULL,
|
||||||
FOREIGN KEY (team_id) REFERENCES team(id),
|
FOREIGN KEY (team_id) REFERENCES team(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (user_id) REFERENCES user(id)
|
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- team draft
|
-- team draft
|
||||||
@ -49,7 +56,7 @@ CREATE TABLE IF NOT EXISTS team_draft (
|
|||||||
member_one_name VARCHAR(255) NOT NULL,
|
member_one_name VARCHAR(255) NOT NULL,
|
||||||
member_two_name VARCHAR(255) NOT NULL,
|
member_two_name VARCHAR(255) NOT NULL,
|
||||||
team_id INT NOT NULL,
|
team_id INT NOT NULL,
|
||||||
FOREIGN KEY (team_id) REFERENCES team(id)
|
FOREIGN KEY (team_id) REFERENCES team(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- death
|
-- death
|
||||||
@ -57,8 +64,8 @@ CREATE TABLE IF NOT EXISTS death (
|
|||||||
message VARCHAR(1024) NOT NULL,
|
message VARCHAR(1024) NOT NULL,
|
||||||
dead_user_id INT NOT NULL,
|
dead_user_id INT NOT NULL,
|
||||||
killer_user_id INT,
|
killer_user_id INT,
|
||||||
FOREIGN KEY (dead_user_id) REFERENCES user(id),
|
FOREIGN KEY (dead_user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (killer_user_id) REFERENCES user(id)
|
FOREIGN KEY (killer_user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- strike reason
|
-- strike reason
|
||||||
@ -73,7 +80,7 @@ CREATE TABLE IF NOT EXISTS strike (
|
|||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
at TIMESTAMP NOT NULL,
|
at TIMESTAMP NOT NULL,
|
||||||
strike_reason_id INT NOT NULL,
|
strike_reason_id INT NOT NULL,
|
||||||
FOREIGN KEY (strike_reason_id) REFERENCES strike_reason(id)
|
FOREIGN KEY (strike_reason_id) REFERENCES strike_reason(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- report
|
-- report
|
||||||
@ -85,8 +92,8 @@ CREATE TABLE IF NOT EXISTS report (
|
|||||||
created_at TIMESTAMP,
|
created_at TIMESTAMP,
|
||||||
reporter_team_id INT NOT NULL,
|
reporter_team_id INT NOT NULL,
|
||||||
reported_team_id INT,
|
reported_team_id INT,
|
||||||
FOREIGN KEY (reporter_team_id) REFERENCES team(id),
|
FOREIGN KEY (reporter_team_id) REFERENCES team(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (reported_team_id) REFERENCES team(id)
|
FOREIGN KEY (reported_team_id) REFERENCES team(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- report status
|
-- report status
|
||||||
@ -97,9 +104,9 @@ CREATE TABLE IF NOT EXISTS report_status (
|
|||||||
report_id INT NOT NULL UNIQUE,
|
report_id INT NOT NULL UNIQUE,
|
||||||
reviewer_id INT,
|
reviewer_id INT,
|
||||||
strike_id INT,
|
strike_id INT,
|
||||||
FOREIGN KEY (report_id) REFERENCES report(id),
|
FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (reviewer_id) REFERENCES admin(id),
|
FOREIGN KEY (reviewer_id) REFERENCES admin(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (strike_id) REFERENCES strike(id)
|
FOREIGN KEY (strike_id) REFERENCES strike(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- feedback
|
-- feedback
|
||||||
@ -111,7 +118,7 @@ CREATE TABLE IF NOT EXISTS feedback (
|
|||||||
url_hash VARCHAR(255) NOT NULL UNIQUE,
|
url_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||||
last_changed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
last_changed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
user_id INT,
|
user_id INT,
|
||||||
FOREIGN KEY (user_id) REFERENCES user(id)
|
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- settings
|
-- settings
|
||||||
|
@ -105,6 +105,17 @@ import {
|
|||||||
type GetReportStatusReq,
|
type GetReportStatusReq,
|
||||||
reportStatus
|
reportStatus
|
||||||
} from '@db/schema/reportStatus.ts';
|
} from '@db/schema/reportStatus.ts';
|
||||||
|
import {
|
||||||
|
addBlockedUser,
|
||||||
|
type AddBlockedUserReq,
|
||||||
|
getBlockedUsers,
|
||||||
|
type GetBlockedUsersReq,
|
||||||
|
blockedUser,
|
||||||
|
type GetBlockedUserByUuidReq,
|
||||||
|
getBlockedUserByUuid,
|
||||||
|
type EditBlockedUserReq,
|
||||||
|
editBlockedUser
|
||||||
|
} from '@db/schema/blockedUser.ts';
|
||||||
|
|
||||||
export class Database {
|
export class Database {
|
||||||
protected readonly db: MySql2Database<{
|
protected readonly db: MySql2Database<{
|
||||||
@ -113,6 +124,7 @@ export class Database {
|
|||||||
teamDraft: typeof teamDraft;
|
teamDraft: typeof teamDraft;
|
||||||
teamMember: typeof teamMember;
|
teamMember: typeof teamMember;
|
||||||
user: typeof user;
|
user: typeof user;
|
||||||
|
blockedUser: typeof blockedUser;
|
||||||
death: typeof death;
|
death: typeof death;
|
||||||
report: typeof report;
|
report: typeof report;
|
||||||
reportStatus: typeof reportStatus;
|
reportStatus: typeof reportStatus;
|
||||||
@ -139,6 +151,7 @@ export class Database {
|
|||||||
teamDraft,
|
teamDraft,
|
||||||
teamMember,
|
teamMember,
|
||||||
user,
|
user,
|
||||||
|
blockedUser,
|
||||||
death,
|
death,
|
||||||
report,
|
report,
|
||||||
reportStatus,
|
reportStatus,
|
||||||
@ -173,6 +186,12 @@ export class Database {
|
|||||||
getUserByUsername = (values: GetUserByUsernameReq) => getUserByUsername(this.db, values);
|
getUserByUsername = (values: GetUserByUsernameReq) => getUserByUsername(this.db, values);
|
||||||
getUsersByUuid = (values: GetUsersByUuidReq) => getUsersByUuid(this.db, values);
|
getUsersByUuid = (values: GetUsersByUuidReq) => getUsersByUuid(this.db, values);
|
||||||
|
|
||||||
|
/* user blocks */
|
||||||
|
addBlockedUser = (values: AddBlockedUserReq) => addBlockedUser(this.db, values);
|
||||||
|
editBlockedUser = (values: EditBlockedUserReq) => editBlockedUser(this.db, values);
|
||||||
|
getBlockedUserByUuid = (values: GetBlockedUserByUuidReq) => getBlockedUserByUuid(this.db, values);
|
||||||
|
getBlockedUsers = (values: GetBlockedUsersReq) => getBlockedUsers(this.db, values);
|
||||||
|
|
||||||
/* team */
|
/* team */
|
||||||
addTeam = (values: AddTeamReq) => addTeam(this.db, values);
|
addTeam = (values: AddTeamReq) => addTeam(this.db, values);
|
||||||
editTeam = (values: EditTeamReq) => editTeam(this.db, values);
|
editTeam = (values: EditTeamReq) => editTeam(this.db, values);
|
||||||
|
50
src/db/schema/blockedUser.ts
Normal file
50
src/db/schema/blockedUser.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { int, mysqlTable, varchar } from 'drizzle-orm/mysql-core';
|
||||||
|
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
type Database = MySql2Database<{ blockedUser: typeof blockedUser }>;
|
||||||
|
|
||||||
|
export const blockedUser = mysqlTable('blocked_user', {
|
||||||
|
id: int('id').primaryKey().autoincrement(),
|
||||||
|
uuid: varchar('uuid', { length: 255 }).unique().notNull(),
|
||||||
|
comment: varchar('comment', { length: 255 })
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AddBlockedUserReq = {
|
||||||
|
uuid: string;
|
||||||
|
comment: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EditBlockedUserReq = {
|
||||||
|
id: number;
|
||||||
|
uuid: string;
|
||||||
|
comment: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetBlockedUserByUuidReq = {
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetBlockedUsersReq = {};
|
||||||
|
|
||||||
|
export async function addBlockedUser(db: Database, values: AddBlockedUserReq) {
|
||||||
|
const bu = await db.insert(blockedUser).values(values).$returningId();
|
||||||
|
|
||||||
|
return bu[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editBlockedUser(db: Database, values: EditBlockedUserReq) {
|
||||||
|
await db.update(blockedUser).set(values).where(eq(blockedUser.id, values.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBlockedUserByUuid(db: Database, values: GetBlockedUserByUuidReq) {
|
||||||
|
const bu = await db.query.blockedUser.findFirst({
|
||||||
|
where: eq(blockedUser.uuid, values.uuid)
|
||||||
|
});
|
||||||
|
|
||||||
|
return bu ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBlockedUsers(db: Database, _values: GetBlockedUsersReq) {
|
||||||
|
return db.select().from(blockedUser);
|
||||||
|
}
|
@ -27,6 +27,13 @@ const adminTabs = [
|
|||||||
href: 'admin/users',
|
href: 'admin/users',
|
||||||
name: 'Registrierte Nutzer',
|
name: 'Registrierte Nutzer',
|
||||||
icon: 'heroicons:user',
|
icon: 'heroicons:user',
|
||||||
|
subTabs: [
|
||||||
|
{
|
||||||
|
href: 'admin/users/blocked',
|
||||||
|
name: 'Blockierte Nutzer',
|
||||||
|
icon: 'heroicons:user-minus'
|
||||||
|
}
|
||||||
|
],
|
||||||
enabled: session?.permissions.users
|
enabled: session?.permissions.users
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -65,7 +72,7 @@ const adminTabs = [
|
|||||||
<BaseLayout title={title}>
|
<BaseLayout title={title}>
|
||||||
<ClientRouter />
|
<ClientRouter />
|
||||||
<div class="flex flex-row max-h-[100vh]">
|
<div class="flex flex-row max-h-[100vh]">
|
||||||
<ul class="menu bg-base-200 w-64 h-[100vh] flex">
|
<ul class="menu bg-base-200 w-68 h-[100vh] flex">
|
||||||
{
|
{
|
||||||
preTabs.map((tab) => (
|
preTabs.map((tab) => (
|
||||||
<li>
|
<li>
|
||||||
@ -84,6 +91,18 @@ const adminTabs = [
|
|||||||
<Icon name={tab.icon} />
|
<Icon name={tab.icon} />
|
||||||
<span>{tab.name}</span>
|
<span>{tab.name}</span>
|
||||||
</a>
|
</a>
|
||||||
|
{tab.subTabs && (
|
||||||
|
<ul>
|
||||||
|
{tab.subTabs.map((subTab) => (
|
||||||
|
<li>
|
||||||
|
<a href={subTab.href}>
|
||||||
|
<Icon name={subTab.icon} />
|
||||||
|
<span>{subTab.name}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import '@assets/website_layout.css';
|
import '@assets/website_layout.css';
|
||||||
import BaseLayout from '../BaseLayout.astro';
|
import BaseLayout from '../BaseLayout.astro';
|
||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from 'astro-icon/components';
|
||||||
import Menu from '@components/website/layout/Menu.svelte';
|
import Menu from '@app/layout/Menu.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
16
src/pages/admin/users/blocked.astro
Normal file
16
src/pages/admin/users/blocked.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import AdminLayout from '@layouts/admin/AdminLayout.astro';
|
||||||
|
import UsersBlocked from '@app/admin/usersBlocked/UsersBlocked.svelte';
|
||||||
|
import SidebarActions from '@app/admin/usersBlocked/SidebarActions.svelte';
|
||||||
|
import { Session } from '@util/session.ts';
|
||||||
|
import { Permissions } from '@util/permissions.ts';
|
||||||
|
import { BASE_PATH } from 'astro:env/server';
|
||||||
|
|
||||||
|
const session = Session.sessionFromCookies(Astro.cookies, Permissions.Admin);
|
||||||
|
if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
|
||||||
|
---
|
||||||
|
|
||||||
|
<AdminLayout title="Blockierte Nutzer">
|
||||||
|
<SidebarActions slot="actions" client:load />
|
||||||
|
<UsersBlocked client:load />
|
||||||
|
</AdminLayout>
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||||
import Scroll from '@components/website/index/Scroll.svelte';
|
import Scroll from '@app/website/index/Scroll.svelte';
|
||||||
import Teams from '@app/webite/index/Teams.svelte';
|
import Teams from '@app/website/index/Teams.svelte';
|
||||||
import Countdown from '@components/website/index/Countdown.svelte';
|
import Countdown from '@app/website/index/Countdown.svelte';
|
||||||
import Varo from '@assets/img/varo.webp';
|
import Varo from '@assets/img/varo.webp';
|
||||||
import Background from '@assets/img/background.webp';
|
import Background from '@assets/img/background.webp';
|
||||||
import { START_DATE } from 'astro:env/server';
|
import { START_DATE } from 'astro:env/server';
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||||
import Checkbox from '@components/input/Checkbox.svelte';
|
import Checkbox from '@components/input/Checkbox.svelte';
|
||||||
import Input from '@components/input/Input.svelte';
|
import Input from '@components/input/Input.svelte';
|
||||||
import RulesPopup from '@components/website/signup/RulesPopup.svelte';
|
import RulesPopup from '@app/website/signup/RulesPopup.svelte';
|
||||||
import Popup from '@components/popup/Popup.svelte';
|
import Popup from '@components/popup/Popup.svelte';
|
||||||
import TeamPopup from '@components/website/signup/TeamPopup.svelte';
|
import TeamPopup from '@app/website/signup/TeamPopup.svelte';
|
||||||
import RegisteredPopup from '@components/website/signup/RegisteredPopup.svelte';
|
import RegisteredPopup from '@app/website/signup/RegisteredPopup.svelte';
|
||||||
import { getSettings, SettingKey } from '@util/settings';
|
import { getSettings, SettingKey } from '@util/settings';
|
||||||
import { db } from '@db/database.ts';
|
import { db } from '@db/database.ts';
|
||||||
import { DISCORD_LINK, PAYPAL_LINK, START_DATE, TEAMSPEAK_LINK } from 'astro:env/server';
|
import { DISCORD_LINK, PAYPAL_LINK, START_DATE, TEAMSPEAK_LINK } from 'astro:env/server';
|
||||||
@ -151,9 +151,9 @@ const signupDisabledSubMessage = signupSetting[SettingKey.SignupDisabledSubMessa
|
|||||||
<script>
|
<script>
|
||||||
import { actions } from 'astro:actions';
|
import { actions } from 'astro:actions';
|
||||||
import { popupState } from '@components/popup/Popup';
|
import { popupState } from '@components/popup/Popup';
|
||||||
import { rulesPopupState, rulesPopupRead } from '@components/website/signup/RulesPopup';
|
import { rulesPopupState, rulesPopupRead } from '@app/website/signup/RulesPopup';
|
||||||
import { teamPopupName, teamPopupOpen } from '@components/website/signup/TeamPopup';
|
import { teamPopupName, teamPopupOpen } from '@app/website/signup/TeamPopup';
|
||||||
import { registeredPopupState } from '@components/website/signup/RegisteredPopup';
|
import { registeredPopupState } from '@app/website/signup/RegisteredPopup';
|
||||||
|
|
||||||
/* ----- client validation ----- */
|
/* ----- client validation ----- */
|
||||||
const rulesCheckbox = document.getElementById('rules')! as HTMLInputElement;
|
const rulesCheckbox = document.getElementById('rules')! as HTMLInputElement;
|
||||||
|
Reference in New Issue
Block a user