add report page
This commit is contained in:
parent
c57e514613
commit
37c230575d
@ -10,6 +10,7 @@
|
|||||||
export let required = false;
|
export let required = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
||||||
|
export let pickyWidth = true;
|
||||||
|
|
||||||
export let inputElement: HTMLInputElement | undefined = undefined;
|
export let inputElement: HTMLInputElement | undefined = undefined;
|
||||||
|
|
||||||
@ -60,7 +61,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="relative flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}>
|
<div
|
||||||
|
class="relative flex items-center"
|
||||||
|
class:sm:max-w-[16rem]={type !== 'checkbox' && pickyWidth}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
class:checkbox={type === 'checkbox'}
|
class:checkbox={type === 'checkbox'}
|
||||||
class:checkbox-xs={type === 'checkbox' && size === 'xs'}
|
class:checkbox-xs={type === 'checkbox' && size === 'xs'}
|
||||||
|
42
src/lib/components/Input/Textarea.svelte
Normal file
42
src/lib/components/Input/Textarea.svelte
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let id: string | null = null;
|
||||||
|
export let name: string | null = null;
|
||||||
|
export let value: string | null = null;
|
||||||
|
export let label: string | null = null;
|
||||||
|
export let notice: string | null = null;
|
||||||
|
export let required = false;
|
||||||
|
export let disabled = false;
|
||||||
|
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
||||||
|
export let rows = 2;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if label}
|
||||||
|
<label class="label" for={id}>
|
||||||
|
<span class="label-text">
|
||||||
|
{label}
|
||||||
|
{#if required}
|
||||||
|
<span class="text-red-700">*</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
<textarea
|
||||||
|
class="textarea textarea-bordered w-full"
|
||||||
|
class:textarea-xs={size === 'xs'}
|
||||||
|
class:textarea-sm={size === 'sm'}
|
||||||
|
class:textarea-md={size === 'md'}
|
||||||
|
class:textarea-lg={size === 'lg'}
|
||||||
|
{id}
|
||||||
|
{name}
|
||||||
|
{required}
|
||||||
|
{disabled}
|
||||||
|
{rows}
|
||||||
|
bind:value
|
||||||
|
/>
|
||||||
|
{#if notice}
|
||||||
|
<label class="label" for={id}>
|
||||||
|
<span class="label-text-alt">{notice}</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -5,15 +5,17 @@ import * as bcrypt from 'bcrypt';
|
|||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
BeforeUpdate,
|
BeforeUpdate,
|
||||||
|
BelongsTo,
|
||||||
Column,
|
Column,
|
||||||
|
ForeignKey,
|
||||||
|
Index,
|
||||||
Model,
|
Model,
|
||||||
Sequelize,
|
Sequelize,
|
||||||
Table,
|
Table
|
||||||
Unique
|
|
||||||
} from 'sequelize-typescript';
|
} from 'sequelize-typescript';
|
||||||
import { Permissions } from '$lib/permissions';
|
import { Permissions } from '$lib/permissions';
|
||||||
|
|
||||||
@Table({ modelName: 'user' })
|
@Table({ modelName: 'user', underscored: true })
|
||||||
export class User extends Model {
|
export class User extends Model {
|
||||||
@Column({ type: DataTypes.STRING, allowNull: false })
|
@Column({ type: DataTypes.STRING, allowNull: false })
|
||||||
declare firstname: string;
|
declare firstname: string;
|
||||||
@ -30,10 +32,37 @@ export class User extends Model {
|
|||||||
@Column({ type: DataTypes.STRING })
|
@Column({ type: DataTypes.STRING })
|
||||||
declare password: string;
|
declare password: string;
|
||||||
@Column({ type: DataTypes.UUIDV4 })
|
@Column({ type: DataTypes.UUIDV4 })
|
||||||
|
@Index
|
||||||
declare uuid: string;
|
declare uuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Table({ modelName: 'admin' })
|
@Table({ modelName: 'report', underscored: true })
|
||||||
|
export class Report extends Model {
|
||||||
|
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
|
||||||
|
@Index
|
||||||
|
declare url_id: string;
|
||||||
|
@Column({ type: DataTypes.STRING, allowNull: false })
|
||||||
|
declare subject: string;
|
||||||
|
@Column({ type: DataTypes.STRING })
|
||||||
|
declare body: string;
|
||||||
|
@Column({ type: DataTypes.BOOLEAN, allowNull: false })
|
||||||
|
declare draft: boolean;
|
||||||
|
@Column({ type: DataTypes.BOOLEAN, allowNull: false })
|
||||||
|
declare completed: boolean;
|
||||||
|
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
||||||
|
@ForeignKey(() => User)
|
||||||
|
declare reporter_user_id: number;
|
||||||
|
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
||||||
|
@ForeignKey(() => User)
|
||||||
|
declare reported_user_id: number;
|
||||||
|
|
||||||
|
@BelongsTo(() => User)
|
||||||
|
declare reporter: User;
|
||||||
|
@BelongsTo(() => User)
|
||||||
|
declare reported: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table({ modelName: 'admin', underscored: true })
|
||||||
export class Admin extends Model {
|
export class Admin extends Model {
|
||||||
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
|
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
|
||||||
declare username: string;
|
declare username: string;
|
||||||
@ -68,5 +97,5 @@ export class Admin extends Model {
|
|||||||
export const sequelize = new Sequelize(building ? 'sqlite::memory:' : env.DATABASE_URI, {
|
export const sequelize = new Sequelize(building ? 'sqlite::memory:' : env.DATABASE_URI, {
|
||||||
// only log sql queries in dev mode
|
// only log sql queries in dev mode
|
||||||
logging: dev ? console.log : false,
|
logging: dev ? console.log : false,
|
||||||
models: [User, Admin]
|
models: [User, Report, Admin]
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="flex justify-center items-center w-full">
|
<div class="flex justify-center w-full">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
5
src/routes/report/+page.svelte
Normal file
5
src/routes/report/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div class="h-screen flex justify-center items-center">
|
||||||
|
<h1 class="text-4xl">
|
||||||
|
Reports können nur ingame mittels des <code>/report</code> Befehls erstellt werden
|
||||||
|
</h1>
|
||||||
|
</div>
|
32
src/routes/report/+server.ts
Normal file
32
src/routes/report/+server.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { Report, User } from '$lib/server/database';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
|
export const POST = (async ({ request, url }) => {
|
||||||
|
const data: { reporter: string; reported: string; reason: string } = await request.json();
|
||||||
|
|
||||||
|
if (data.reporter == undefined || data.reported == undefined || data.reason == undefined)
|
||||||
|
return new Response(null, { status: 400 });
|
||||||
|
|
||||||
|
const reporter = await User.findOne({ where: { uuid: data.reporter } });
|
||||||
|
const reported = await User.findOne({ where: { uuid: data.reported } });
|
||||||
|
|
||||||
|
if (reporter == null || reported == null) return new Response(null, { status: 400 });
|
||||||
|
|
||||||
|
const report = await Report.create({
|
||||||
|
subject: data.reason,
|
||||||
|
body: null,
|
||||||
|
draft: true,
|
||||||
|
url_id: crypto.randomBytes(18).toString('hex'),
|
||||||
|
completed: false,
|
||||||
|
reporter_user_id: reporter.id,
|
||||||
|
reported_user_id: reported.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ url: `${url.toString().replace(/\/$/, '')}/${report.url_id}` }),
|
||||||
|
{
|
||||||
|
status: 201
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}) satisfies RequestHandler;
|
3
src/routes/report/[...url_id]/+layout.svelte
Normal file
3
src/routes/report/[...url_id]/+layout.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="flex justify-center items-center w-full min-h-screen h-full">
|
||||||
|
<slot />
|
||||||
|
</div>
|
28
src/routes/report/[...url_id]/+page.server.ts
Normal file
28
src/routes/report/[...url_id]/+page.server.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { Report, User } from '$lib/server/database';
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
|
const report = await Report.findOne({
|
||||||
|
where: { url_id: params.url_id },
|
||||||
|
include: [
|
||||||
|
{ model: User, as: 'reporter' },
|
||||||
|
{ model: User, as: 'reported' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (report == null) throw redirect(302, `${env.PUBLIC_BASE_PATH}/`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
draft: report.draft,
|
||||||
|
completed: report.completed,
|
||||||
|
reason: report.subject,
|
||||||
|
reporter: {
|
||||||
|
name: report.reporter.username
|
||||||
|
},
|
||||||
|
reported: {
|
||||||
|
name: report.reported.username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
32
src/routes/report/[...url_id]/+page.svelte
Normal file
32
src/routes/report/[...url_id]/+page.svelte
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
import ReportDraft from './ReportDraft.svelte';
|
||||||
|
import ReportCompleted from './ReportCompleted.svelte';
|
||||||
|
import ReportSubmitted from './ReportSubmitted.svelte';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<!-- just in case... -->
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="absolute top-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
|
||||||
|
{#if data.draft}
|
||||||
|
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
|
||||||
|
<ReportDraft
|
||||||
|
reason={data.reason}
|
||||||
|
reportedName={data.reported.name}
|
||||||
|
on:submit={() => (data.draft = false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if data.completed}
|
||||||
|
<ReportCompleted />
|
||||||
|
{:else}
|
||||||
|
<div class="col-[1] row-[1]" transition:fly={{ x: 200, duration: 300 }}>
|
||||||
|
<ReportSubmitted />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
13
src/routes/report/[...url_id]/+server.ts
Normal file
13
src/routes/report/[...url_id]/+server.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { Report } from '$lib/server/database';
|
||||||
|
|
||||||
|
export const POST = (async ({ params }) => {
|
||||||
|
const report = await Report.findOne({ where: { url_id: params.url_id } });
|
||||||
|
|
||||||
|
if (report == null) return new Response(null, { status: 400 });
|
||||||
|
|
||||||
|
report.draft = false;
|
||||||
|
await report.save();
|
||||||
|
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}) satisfies RequestHandler;
|
3
src/routes/report/[...url_id]/ReportCompleted.svelte
Normal file
3
src/routes/report/[...url_id]/ReportCompleted.svelte
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<h2 class="text-xl text-center">Dieser Report wurde von einem Admin bearbeitet</h2>
|
||||||
|
</div>
|
67
src/routes/report/[...url_id]/ReportDraft.svelte
Normal file
67
src/routes/report/[...url_id]/ReportDraft.svelte
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||||
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let reportedName: string;
|
||||||
|
export let reason: string;
|
||||||
|
|
||||||
|
async function submitReport() {
|
||||||
|
await fetch(`${env.PUBLIC_BASE_PATH}/report/${$page.params.url_id}`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let submitModal: HTMLDialogElement;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl text-center">Report für <code>{reportedName}</code></h2>
|
||||||
|
<form on:submit|preventDefault={() => submitModal.show()}>
|
||||||
|
<div class="space-y-4 my-4">
|
||||||
|
<div>
|
||||||
|
<Input type="text" value={reason} required={true} pickyWidth={false}>
|
||||||
|
<span slot="label">Report Grund</span>
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Textarea required={true} rows={4} label="Details über den Report Grund" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input type="submit" value="Reporten" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dialog class="modal" bind:this={submitModal}>
|
||||||
|
<form method="dialog" class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-roboto font-medium text-xl">Report abschicken?</h3>
|
||||||
|
<div class="my-4">
|
||||||
|
<p>
|
||||||
|
Nach dem Abschicken des Reports wird sich ein Moderator schnellstmöglich darum kümmern.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row space-x-1">
|
||||||
|
<Input
|
||||||
|
type="submit"
|
||||||
|
value="Abschicken"
|
||||||
|
on:click={async () => {
|
||||||
|
await submitReport();
|
||||||
|
dispatch('submit');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input type="submit" value="Abbrechen" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
6
src/routes/report/[...url_id]/ReportSubmitted.svelte
Normal file
6
src/routes/report/[...url_id]/ReportSubmitted.svelte
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<div>
|
||||||
|
<h2 class="text-2xl text-center">Report abgeschickt</h2>
|
||||||
|
<p class="mt-4">
|
||||||
|
Dein Report wurde abgeschickt und wird so schnell wie möglich von einem Admin bearbeitet.
|
||||||
|
</p>
|
||||||
|
</div>
|
@ -11,7 +11,10 @@ const config = {
|
|||||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter(),
|
||||||
|
csrf: {
|
||||||
|
checkOrigin: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,5 +8,9 @@ export default {
|
|||||||
roboto: ['Roboto']
|
roboto: ['Roboto']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [require('daisyui')]
|
plugins: [require('daisyui')],
|
||||||
|
|
||||||
|
daisyui: {
|
||||||
|
logs: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user