make reported user nullable
All checks were successful
delpoy / build-and-deploy (push) Successful in 44s

This commit is contained in:
bytedream 2023-11-03 18:10:02 +01:00
parent 72eeb59230
commit 81d97380ca
11 changed files with 107 additions and 65 deletions

View File

@ -9,6 +9,7 @@
export let placeholder: string | null = null; export let placeholder: string | null = null;
export let required = false; export let required = false;
export let disabled = false; export let disabled = false;
export let readonly = false;
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md'; export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
export let pickyWidth = true; export let pickyWidth = true;
@ -85,6 +86,7 @@
{placeholder} {placeholder}
{required} {required}
{disabled} {disabled}
{readonly}
bind:this={inputElement} bind:this={inputElement}
autocomplete="off" autocomplete="off"
on:input={(e) => { on:input={(e) => {

View File

@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte';
export let id: string | null = null; export let id: string | null = null;
export let value = ''; export let value = '';
export let inputValue = '';
export let suggestionRequired = false; export let suggestionRequired = false;
export let emptyAllowed = false;
export let searchSuggestionFunc: ( export let searchSuggestionFunc: (
input: string input: string
) => Promise<{ name: string; value: string }[]> = () => Promise.resolve([]); ) => Promise<{ name: string; value: string }[]> = () => Promise.resolve([]);
@ -10,7 +14,8 @@
export let label: string | null = null; export let label: string | null = null;
export let required = false; export let required = false;
let inputValue: string; const dispatch = createEventDispatcher();
let searchSuggestions: { name: string; value: string }[] = []; let searchSuggestions: { name: string; value: string }[] = [];
$: if (!suggestionRequired) value = inputValue; $: if (!suggestionRequired) value = inputValue;
</script> </script>
@ -47,13 +52,18 @@
value = searchSuggestion.value; value = searchSuggestion.value;
searchSuggestions = []; searchSuggestions = [];
e.target?.setCustomValidity(''); e.target?.setCustomValidity('');
dispatch('submit', { input: inputValue, value: value });
} else if (inputValue === '' && emptyAllowed) {
dispatch('submit', { input: '', value: '' });
} }
}); });
}} }}
on:invalid={(e) => { on:invalid={(e) => {
if (invalidMessage != null) e.target?.setCustomValidity(invalidMessage); if (invalidMessage != null) e.target?.setCustomValidity(invalidMessage);
}} }}
pattern={suggestionRequired ? `${value ? inputValue : 'a^'}` : null} pattern={suggestionRequired
? `${value ? inputValue : 'a^' + (emptyAllowed ? '|$^' : '')}`
: null}
/> />
</div> </div>
@ -68,6 +78,7 @@
inputValue = searchSuggestion.name; inputValue = searchSuggestion.name;
value = searchSuggestion.value; value = searchSuggestion.value;
searchSuggestions = []; searchSuggestions = [];
dispatch('submit', { input: inputValue, value: value });
}}>{searchSuggestion.name}</button }}>{searchSuggestion.name}</button
> >
</li> </li>
@ -76,6 +87,7 @@
{/if} {/if}
</div> </div>
<!-- close the search suggestions box when clicking outside -->
{#if inputValue && searchSuggestions.length !== 0} {#if inputValue && searchSuggestions.length !== 0}
<button <button
class="absolute top-0 left-0 z-10 w-full h-full cursor-default" class="absolute top-0 left-0 z-10 w-full h-full cursor-default"

View File

@ -56,7 +56,7 @@ export class Report extends Model {
@Column({ type: DataTypes.INTEGER, allowNull: false }) @Column({ type: DataTypes.INTEGER, allowNull: false })
@ForeignKey(() => User) @ForeignKey(() => User)
declare reporter_id: number; declare reporter_id: number;
@Column({ type: DataTypes.INTEGER, allowNull: false }) @Column({ type: DataTypes.INTEGER })
@ForeignKey(() => User) @ForeignKey(() => User)
declare reported_id: number; declare reported_id: number;
@Column({ type: DataTypes.INTEGER }) @Column({ type: DataTypes.INTEGER })

18
src/lib/utils.ts Normal file
View File

@ -0,0 +1,18 @@
import { env } from '$env/dynamic/public';
export async function usernameSuggestions(
username: string
): Promise<{ name: string; value: string }[]> {
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
method: 'POST',
body: JSON.stringify({
limit: 6,
search: username,
slim: true
})
});
const json: { username: string; uuid: string }[] = await response.json();
return json.map((v) => {
return { name: v.username, value: v.uuid };
});
}

View File

@ -13,6 +13,8 @@
import NewReportModal from './NewReportModal.svelte'; import NewReportModal from './NewReportModal.svelte';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Search from '$lib/components/Input/Search.svelte';
import { usernameSuggestions } from '$lib/utils';
export let data: PageData; export let data: PageData;
@ -83,7 +85,8 @@
auditor: data.self?.id || -1, auditor: data.self?.id || -1,
notice: activeReport.notice || '', notice: activeReport.notice || '',
statement: activeReport.statement || '', statement: activeReport.statement || '',
status: activeReport.status status: activeReport.status,
reported: activeReport.reported?.uuid || null
}) })
}); });
} }
@ -93,7 +96,7 @@
</script> </script>
<div class="h-screen flex flex-row"> <div class="h-screen flex flex-row">
<div class="w-full flex flex-col"> <div class="w-full flex flex-col overflow-scroll">
<HeaderBar bind:reportFilter /> <HeaderBar bind:reportFilter />
<hr class="divider my-1 mx-8 border-none" /> <hr class="divider my-1 mx-8 border-none" />
<table class="table table-fixed h-fit"> <table class="table table-fixed h-fit">
@ -140,14 +143,17 @@
</button> </button>
</td> </td>
<td> <td>
{report.reported.username} {report.reported?.username || ''}
{#if report.reported?.id}
<button <button
class="pl-1" class="pl-1"
title="Nach Reportetem Spieler filtern" title="Nach Reportetem Spieler filtern"
on:click|stopPropagation={() => (reportFilter.reported = report.reported.username)} on:click|stopPropagation={() =>
(reportFilter.reported = report.reported.username)}
> >
<IconOutline name="magnifying-glass-outline" width="14" height="14" /> <IconOutline name="magnifying-glass-outline" width="14" height="14" />
</button> </button>
{/if}
</td> </td>
<td <td
>{new Intl.DateTimeFormat('de-DE', { >{new Intl.DateTimeFormat('de-DE', {
@ -185,7 +191,7 @@
</div> </div>
{#if activeReport} {#if activeReport}
<div <div
class="relative flex flex-col w-2/5 bg-base-200/50 px-4 py-6 overflow-scroll" class="relative flex flex-col w-2/5 h-screen bg-base-200/50 px-4 py-6 overflow-scroll"
transition:fly={{ x: 200, duration: 200 }} transition:fly={{ x: 200, duration: 200 }}
> >
<div class="absolute right-2 top-2 flex justify-center"> <div class="absolute right-2 top-2 flex justify-center">
@ -226,19 +232,26 @@
}}>✕</button }}>✕</button
> >
</div> </div>
<h3 class="font-roboto font-semibold text-2xl">Report</h3> <h3 class="font-roboto font-semibold text-2xl mb-2">Report</h3>
<div class="break-words my-2">
<i class="font-medium">{activeReport.reporter.username}</i> hat
<i class="font-medium">{activeReport.reported.username}</i>
am {new Intl.DateTimeFormat('de-DE', {
year: 'numeric',
month: 'long',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).format(new Date(activeReport.createdAt))} Uhr reportet.
</div>
<div class="w-full"> <div class="w-full">
<Input readonly={true} size="sm" value={activeReport.reporter.username} pickyWidth={false}>
<span slot="label">Reporter</span>
</Input>
<Search
size="sm"
suggestionRequired={true}
emptyAllowed={true}
searchSuggestionFunc={usernameSuggestions}
invalidMessage="Es können nur registrierte Spieler reportet werden"
label="Reporteter User"
inputValue={activeReport.reported?.username || ''}
on:submit={(e) =>
(activeReport.reported = {
...activeReport.reported,
username: e.detail.input,
uuid: e.detail.value
})}
/>
<Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} /> <Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} />
<Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} /> <Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} />
</div> </div>
@ -301,6 +314,11 @@
value="Speichern" value="Speichern"
on:click={async () => { on:click={async () => {
await updateActiveReport(); await updateActiveReport();
if (activeReport.reported?.username && activeReport.reported?.id === undefined) {
activeReport.reported.id = -1;
} else {
activeReport.reported = undefined;
}
currentPageReports = [...currentPageReports]; currentPageReports = [...currentPageReports];
if (activeReport.originalStatus !== 'reviewed' && activeReport.status === 'reviewed') { if (activeReport.originalStatus !== 'reviewed' && activeReport.status === 'reviewed') {
$reportCount -= 1; $reportCount -= 1;

View File

@ -92,6 +92,7 @@ export const PATCH = (async ({ request, cookies }) => {
const data: { const data: {
id: number; id: number;
reported: string | null;
auditor: number; auditor: number;
notice: string | null; notice: string | null;
statement: string | null; statement: string | null;
@ -102,9 +103,13 @@ export const PATCH = (async ({ request, cookies }) => {
const report = await Report.findOne({ where: { id: data.id } }); const report = await Report.findOne({ where: { id: data.id } });
const admin = await Admin.findOne({ where: { id: data.auditor } }); const admin = await Admin.findOne({ where: { id: data.auditor } });
if (report === null || (admin === null && data.auditor != -1)) const reported = data.reported
? await User.findOne({ where: { uuid: data.reported } })
: undefined;
if (report === null || (admin === null && data.auditor != -1) || reported === null)
return new Response(null, { status: 400 }); return new Response(null, { status: 400 });
report.reported_id = reported?.id || null;
if (data.notice != null) report.notice = data.notice; if (data.notice != null) report.notice = data.notice;
if (data.statement != null) report.statement = data.statement; if (data.statement != null) report.statement = data.statement;
if (data.status != null) report.status = data.status; if (data.status != null) report.status = data.status;
@ -115,7 +120,7 @@ export const PATCH = (async ({ request, cookies }) => {
return new Response(); return new Response();
}) satisfies RequestHandler; }) satisfies RequestHandler;
export const PUT = (async ({ request, cookies, url }) => { export const PUT = (async ({ request, cookies }) => {
if (getSession(cookies, { permissions: [Permissions.ReportWrite] }) == null) { if (getSession(cookies, { permissions: [Permissions.ReportWrite] }) == null) {
return new Response(null, { return new Response(null, {
status: 401 status: 401
@ -124,23 +129,18 @@ export const PUT = (async ({ request, cookies, url }) => {
const data: { const data: {
reporter: string; reporter: string;
reported: string; reported: string | null;
reason: string; reason: string;
body: string | null; body: string | null;
} = await request.json(); } = await request.json();
if ( if (data.reporter == null || data.reason == null || data.body === undefined)
data.reporter == null ||
data.reported == null ||
data.reason == null ||
data.body === undefined
)
return new Response(null, { status: 400 }); return new Response(null, { status: 400 });
const reporter = await User.findOne({ where: { uuid: data.reporter } }); const reporter = await User.findOne({ where: { uuid: data.reporter } });
const reported = await User.findOne({ where: { uuid: data.reported } }); const reported = data.reported ? await User.findOne({ where: { uuid: data.reported } }) : null;
if (reporter == null || reported == null) return new Response(null, { status: 400 }); if (reporter == null) return new Response(null, { status: 400 });
const report = await Report.create({ const report = await Report.create({
subject: data.reason, subject: data.reason,
@ -150,10 +150,12 @@ export const PUT = (async ({ request, cookies, url }) => {
url_hash: crypto.randomBytes(18).toString('hex'), url_hash: crypto.randomBytes(18).toString('hex'),
completed: false, completed: false,
reporter_id: reporter.id, reporter_id: reporter.id,
reported_id: reported.id reported_id: reported?.id || null
}); });
report.dataValues.reporter = await User.findOne({ where: { id: report.reporter_id } }); report.dataValues.reporter = await User.findOne({ where: { id: report.reporter_id } });
report.dataValues.reported = await User.findOne({ where: { id: report.reported_id } }); report.dataValues.reported = report.reported_id
? await User.findOne({ where: { id: report.reported_id } })
: null;
report.dataValues.auditor = null; report.dataValues.auditor = null;
return new Response(JSON.stringify(report), { return new Response(JSON.stringify(report), {

View File

@ -4,6 +4,7 @@
import Textarea from '$lib/components/Input/Textarea.svelte'; import Textarea from '$lib/components/Input/Textarea.svelte';
import Search from '$lib/components/Input/Search.svelte'; import Search from '$lib/components/Input/Search.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { usernameSuggestions } from '$lib/utils';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -12,27 +13,12 @@
let reason = ''; let reason = '';
let body = ''; let body = '';
async function usernameSuggestions(username: string): Promise<{ name: string; value: string }[]> {
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
method: 'POST',
body: JSON.stringify({
limit: 6,
search: username,
slim: true
})
});
const json: { username: string; uuid: string }[] = await response.json();
return json.map((v) => {
return { name: v.username, value: v.uuid };
});
}
async function newReport() { async function newReport() {
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, { const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
method: 'PUT', method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
reporter: reporter, reporter: reporter,
reported: reported, reported: reported || null,
reason: reason, reason: reason,
body: body || null body: body || null
}) })
@ -66,10 +52,10 @@
<Search <Search
size="sm" size="sm"
suggestionRequired={true} suggestionRequired={true}
emptyAllowed={true}
searchSuggestionFunc={usernameSuggestions} searchSuggestionFunc={usernameSuggestions}
invalidMessage="Es können nur registrierte Spieler reportet werden" invalidMessage="Es können nur registrierte Spieler reportet werden"
label="Reporteter User" label="Reporteter User"
required={true}
bind:value={reported} bind:value={reported}
/> />
</div> </div>

View File

@ -8,15 +8,14 @@ export const POST = (async ({ request, url }) => {
if (env.REPORT_SECRET && url.searchParams.get('secret') !== env.REPORT_SECRET) if (env.REPORT_SECRET && url.searchParams.get('secret') !== env.REPORT_SECRET)
return new Response(null, { status: 401 }); return new Response(null, { status: 401 });
const data: { reporter: string; reported: string; reason: string } = await request.json(); const data: { reporter: string; reported: string | null; reason: string } = await request.json();
if (data.reporter == null || data.reported == null || data.reason == null) if (data.reporter == null || data.reason == null) return new Response(null, { status: 400 });
return new Response(null, { status: 400 });
const reporter = await User.findOne({ where: { uuid: data.reporter } }); const reporter = await User.findOne({ where: { uuid: data.reporter } });
const reported = await User.findOne({ where: { uuid: data.reported } }); const reported = data.reported ? await User.findOne({ where: { uuid: data.reported } }) : null;
if (reporter == null || reported == null) return new Response(null, { status: 400 }); if (reporter == null) return new Response(null, { status: 400 });
const report = await Report.create({ const report = await Report.create({
subject: data.reason, subject: data.reason,
@ -26,7 +25,7 @@ export const POST = (async ({ request, url }) => {
url_hash: crypto.randomBytes(18).toString('hex'), url_hash: crypto.randomBytes(18).toString('hex'),
completed: false, completed: false,
reporter_id: reporter.id, reporter_id: reporter.id,
reported_id: reported.id reported_id: reported?.id || null
}); });
return new Response( return new Response(

View File

@ -22,7 +22,7 @@ export const load: PageServerLoad = async ({ params }) => {
name: report.reporter.username name: report.reporter.username
}, },
reported: { reported: {
name: report.reported.username name: report.reported?.username || null
} }
}; };
}; };

View File

@ -18,6 +18,7 @@
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}> <div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
<ReportDraft <ReportDraft
reason={data.reason} reason={data.reason}
reporterName={data.reporter.name}
reportedName={data.reported.name} reportedName={data.reported.name}
on:submit={() => (data.draft = false)} on:submit={() => (data.draft = false)}
/> />

View File

@ -5,7 +5,8 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
export let reportedName: string; export let reporterName: string;
export let reportedName: string | null;
export let reason: string; export let reason: string;
let body: string; let body: string;
@ -25,7 +26,10 @@
</script> </script>
<div> <div>
<h2 class="text-3xl text-center">Report für <code>{reportedName}</code></h2> <h2 class="text-3xl text-center">
Report von <span class="underline">{reporterName}</span> gegen
<span class="underline">{reportedName || 'unbekannt'}</span>
</h2>
<form on:submit|preventDefault={() => submitModal.show()}> <form on:submit|preventDefault={() => submitModal.show()}>
<div class="space-y-4 my-4"> <div class="space-y-4 my-4">
<div> <div>