Compare commits
No commits in common. "722026c938e4268b1f7ec319a9995c877e5e941d" and "c57e5146139fe680f2c2e9c920e420d027c0d5aa" have entirely different histories.
722026c938
...
c57e514613
@ -4,13 +4,12 @@
|
|||||||
|
|
||||||
export let id: string | null = null;
|
export let id: string | null = null;
|
||||||
export let name: string | null = null;
|
export let name: string | null = null;
|
||||||
export let type = 'text';
|
export let type: string;
|
||||||
export let value: string | null = null;
|
export let value: string | null = null;
|
||||||
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 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;
|
||||||
|
|
||||||
@ -61,10 +60,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div class="relative flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}>
|
||||||
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'}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
export let id: string;
|
||||||
|
|
||||||
export let id: string | null = null;
|
|
||||||
export let name: string | null = null;
|
export let name: string | null = null;
|
||||||
export let value: any | null = null;
|
export let value: string | null = null;
|
||||||
export let label: string | null = null;
|
export let label: string | null = null;
|
||||||
export let notice: string | null = null;
|
export let notice: string | null = null;
|
||||||
export let required = false;
|
export let required = false;
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
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 readonly = false;
|
|
||||||
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
|
||||||
export let rows = 2;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
</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 w-full"
|
|
||||||
class:textarea-xs={size === 'xs'}
|
|
||||||
class:textarea-sm={size === 'sm'}
|
|
||||||
class:textarea-md={size === 'md'}
|
|
||||||
class:textarea-lg={size === 'lg'}
|
|
||||||
class:textarea-bordered={!readonly}
|
|
||||||
{id}
|
|
||||||
{name}
|
|
||||||
{required}
|
|
||||||
{disabled}
|
|
||||||
{readonly}
|
|
||||||
{rows}
|
|
||||||
bind:value
|
|
||||||
on:click={(e) => dispatch('click', e)}
|
|
||||||
/>
|
|
||||||
{#if notice}
|
|
||||||
<label class="label" for={id}>
|
|
||||||
<span class="label-text-alt">{notice}</span>
|
|
||||||
</label>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -3,8 +3,6 @@ export class Permissions {
|
|||||||
static readonly AdminWrite = 4;
|
static readonly AdminWrite = 4;
|
||||||
static readonly UserRead = 8;
|
static readonly UserRead = 8;
|
||||||
static readonly UserWrite = 16;
|
static readonly UserWrite = 16;
|
||||||
static readonly ReportRead = 32;
|
|
||||||
static readonly ReportWrite = 64;
|
|
||||||
|
|
||||||
readonly value: number;
|
readonly value: number;
|
||||||
|
|
||||||
@ -31,9 +29,7 @@ export class Permissions {
|
|||||||
Permissions.AdminRead,
|
Permissions.AdminRead,
|
||||||
Permissions.AdminWrite,
|
Permissions.AdminWrite,
|
||||||
Permissions.UserRead,
|
Permissions.UserRead,
|
||||||
Permissions.UserWrite,
|
Permissions.UserWrite
|
||||||
Permissions.ReportRead,
|
|
||||||
Permissions.ReportWrite
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,12 +45,6 @@ export class Permissions {
|
|||||||
userWrite(): boolean {
|
userWrite(): boolean {
|
||||||
return (this.value & Permissions.UserWrite) != 0;
|
return (this.value & Permissions.UserWrite) != 0;
|
||||||
}
|
}
|
||||||
reportRead(): boolean {
|
|
||||||
return (this.value & Permissions.ReportRead) != 0;
|
|
||||||
}
|
|
||||||
reportWrite(): boolean {
|
|
||||||
return (this.value & Permissions.ReportWrite) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
asArray(): number[] {
|
asArray(): number[] {
|
||||||
const array = [];
|
const array = [];
|
||||||
|
@ -5,17 +5,15 @@ 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', underscored: true })
|
@Table({ modelName: 'user' })
|
||||||
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;
|
||||||
@ -32,46 +30,10 @@ 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: 'report', underscored: true })
|
@Table({ modelName: 'admin' })
|
||||||
export class Report extends Model {
|
|
||||||
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
|
|
||||||
@Index
|
|
||||||
declare url_hash: 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.ENUM('java', 'bedrock', 'cracked'), allowNull: false })
|
|
||||||
declare status: 'none' | 'review' | 'reviewed';
|
|
||||||
@Column({ type: DataTypes.STRING })
|
|
||||||
declare notice: string;
|
|
||||||
@Column({ type: DataTypes.STRING })
|
|
||||||
declare statement: string;
|
|
||||||
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
|
||||||
@ForeignKey(() => User)
|
|
||||||
declare reporter_id: number;
|
|
||||||
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
|
||||||
@ForeignKey(() => User)
|
|
||||||
declare reported_id: number;
|
|
||||||
@Column({ type: DataTypes.INTEGER })
|
|
||||||
@ForeignKey(() => Admin)
|
|
||||||
declare auditor_id: number;
|
|
||||||
|
|
||||||
@BelongsTo(() => User, 'reporter_id')
|
|
||||||
declare reporter: User;
|
|
||||||
@BelongsTo(() => User, 'reported_id')
|
|
||||||
declare reported: User;
|
|
||||||
@BelongsTo(() => Admin, 'auditor_id')
|
|
||||||
declare auditor: Admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
||||||
@ -106,5 +68,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, Report, Admin]
|
models: [User, Admin]
|
||||||
});
|
});
|
||||||
|
@ -4,5 +4,4 @@ import { writable } from 'svelte/store';
|
|||||||
|
|
||||||
export const playAudio: Writable<boolean> = persisted('playAudio', false);
|
export const playAudio: Writable<boolean> = persisted('playAudio', false);
|
||||||
|
|
||||||
export const reportCount: Writable<number> = writable(0);
|
|
||||||
export const adminCount: Writable<number> = writable(0);
|
export const adminCount: Writable<number> = writable(0);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { LayoutServerLoad } from './$types';
|
import type { LayoutServerLoad } from './$types';
|
||||||
import { Admin, Report, User } from '$lib/server/database';
|
import { Admin, User } from '$lib/server/database';
|
||||||
import { getSession } from '$lib/server/session';
|
import { getSession } from '$lib/server/session';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
@ -12,10 +12,6 @@ export const load: LayoutServerLoad = async ({ route, cookies }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
userCount: session?.permissions.userRead() ? await User.count() : null,
|
userCount: session?.permissions.userRead() ? await User.count() : null,
|
||||||
reportCount: session?.permissions.reportRead()
|
adminCount: session?.permissions.adminRead() ? await Admin.count() : null
|
||||||
? await Report.count({ where: { draft: false, status: ['none', 'review'] } })
|
|
||||||
: null,
|
|
||||||
adminCount: session?.permissions.adminRead() ? await Admin.count() : null,
|
|
||||||
self: session ? await Admin.findOne({ where: { id: session.userId } }) : null
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { LayoutData } from './$types';
|
import type { LayoutData } from './$types';
|
||||||
import { adminCount, reportCount } from '$lib/stores';
|
import { adminCount } from '$lib/stores';
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/logout`, {
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/logout`, {
|
||||||
@ -19,12 +19,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
if (data.reportCount) $reportCount = data.reportCount;
|
|
||||||
if (data.adminCount) $adminCount = data.adminCount;
|
if (data.adminCount) $adminCount = data.adminCount;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`}
|
{#if $page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`}
|
||||||
<div class="flex h-screen w-screen">
|
<div class="flex h-screen">
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<ul class="menu p-4 w-max h-full bg-base-200 text-base-content">
|
<ul class="menu p-4 w-max h-full bg-base-200 text-base-content">
|
||||||
{#if data.userCount != null}
|
{#if data.userCount != null}
|
||||||
@ -36,15 +35,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if data.reportCount != null}
|
|
||||||
<li>
|
|
||||||
<a href="{env.PUBLIC_BASE_PATH}/admin/reports">
|
|
||||||
<IconOutline name="flag-outline" />
|
|
||||||
<span class="ml-1">Reports</span>
|
|
||||||
<div class="badge">{$reportCount}</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{#if data.adminCount != null}
|
{#if data.adminCount != null}
|
||||||
<li>
|
<li>
|
||||||
<a href="{env.PUBLIC_BASE_PATH}/admin/admin">
|
<a href="{env.PUBLIC_BASE_PATH}/admin/admin">
|
||||||
@ -62,7 +52,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full w-full overflow-y-scroll overflow-x-hidden">
|
<div class="h-full w-full overflow-scroll">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="flex justify-center w-full">
|
<div class="flex justify-center items-center w-full">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
|
||||||
import { redirect } from '@sveltejs/kit';
|
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
import { getSession } from '$lib/server/session';
|
|
||||||
import { Permissions } from '$lib/permissions';
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ parent, cookies }) => {
|
|
||||||
const { reportCount } = await parent();
|
|
||||||
if (reportCount == null) throw redirect(302, `${env.PUBLIC_BASE_PATH}/admin`);
|
|
||||||
|
|
||||||
const { self } = await parent();
|
|
||||||
|
|
||||||
return {
|
|
||||||
count: getSession(cookies, { permissions: [Permissions.UserRead] }) != null ? reportCount : 0,
|
|
||||||
self: self
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,229 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { fly } from 'svelte/transition';
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import type { Report } from '$lib/server/database';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { env } from '$env/dynamic/public';
|
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
|
||||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
|
||||||
import { reportCount } from '$lib/stores';
|
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
|
|
||||||
let currentPageReports: (typeof Report.prototype.dataValues)[] = [];
|
|
||||||
let reportsPerPage = 50;
|
|
||||||
let reportPage = 0;
|
|
||||||
let reportFilter = { draft: false, status: null, reporter: null, reported: null };
|
|
||||||
let activeReport: typeof Report.prototype.dataValues | null = null;
|
|
||||||
|
|
||||||
async function fetchPageReports(
|
|
||||||
page: number,
|
|
||||||
filter: any
|
|
||||||
): Promise<(typeof Report.prototype.dataValues)[]> {
|
|
||||||
if (!browser) return [];
|
|
||||||
|
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ ...filter, limit: reportsPerPage, from: reportPage * page })
|
|
||||||
});
|
|
||||||
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
$: fetchPageReports(reportPage, reportFilter).then((r) => (currentPageReports = r));
|
|
||||||
|
|
||||||
async function updateActiveReport() {
|
|
||||||
await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: JSON.stringify({
|
|
||||||
id: activeReport.id,
|
|
||||||
auditor: data.self?.id || -1,
|
|
||||||
notice: activeReport.notice || '',
|
|
||||||
statement: activeReport.statement || '',
|
|
||||||
status: activeReport.status
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let saveActiveReportChangesModal: HTMLDialogElement;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="h-screen flex flex-row">
|
|
||||||
<div class="w-full flex flex-col">
|
|
||||||
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
|
||||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reporter}>
|
|
||||||
<span slot="label">Report Ersteller</span>
|
|
||||||
</Input>
|
|
||||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reported}>
|
|
||||||
<span slot="label">Reportete Spieler</span>
|
|
||||||
</Input>
|
|
||||||
<Select label="Bearbeitungsstatus" size="sm" bind:value={reportFilter.status}>
|
|
||||||
<option value="none">Unbearbeitet</option>
|
|
||||||
<option value="review">In Bearbeitung</option>
|
|
||||||
<option value={null}>Unbearbeitet & In Bearbeitung</option>
|
|
||||||
<option value="reviewed">Bearbeitet</option>
|
|
||||||
</Select>
|
|
||||||
<Select label="Reportstatus" size="sm" bind:value={reportFilter.draft}>
|
|
||||||
<option value={false}>Erstellt</option>
|
|
||||||
<option value={true}>Entwurf</option>
|
|
||||||
<option value={null}>Erstellt & Entwurf</option>
|
|
||||||
</Select>
|
|
||||||
</form>
|
|
||||||
<hr class="divider my-1 mx-8 border-none" />
|
|
||||||
<table class="table table-fixed h-fit">
|
|
||||||
<colgroup>
|
|
||||||
<col style="width: 20%" />
|
|
||||||
<col style="width: 15%" />
|
|
||||||
<col style="width: 15%" />
|
|
||||||
<col style="width: 20%" />
|
|
||||||
<col style="width: 15%" />
|
|
||||||
<col style="width: 15%" />
|
|
||||||
</colgroup>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Grund</th>
|
|
||||||
<th>Ersteller</th>
|
|
||||||
<th>Reporteter User</th>
|
|
||||||
<th>Datum</th>
|
|
||||||
<th>Bearbeitungsstatus</th>
|
|
||||||
<th>Reportstatus</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each currentPageReports as report}
|
|
||||||
<tr
|
|
||||||
class="hover [&>*]:text-sm cursor-pointer"
|
|
||||||
class:bg-base-200={activeReport === report}
|
|
||||||
on:click={() => {
|
|
||||||
activeReport = report;
|
|
||||||
activeReport.originalStatus = report.status;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<td title={report.subject}><div class="overflow-scroll">{report.subject}</div></td>
|
|
||||||
<td>{report.reporter.username}</td>
|
|
||||||
<td>{report.reported.username}</td>
|
|
||||||
<td
|
|
||||||
>{new Intl.DateTimeFormat('de-DE', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
}).format(new Date(report.createdAt))} Uhr</td
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
{report.status === 'none'
|
|
||||||
? 'Unbearbeitet'
|
|
||||||
: report.status === 'review'
|
|
||||||
? 'In Bearbeitung'
|
|
||||||
: report.status === 'reviewed'
|
|
||||||
? 'Bearbeitet'
|
|
||||||
: ''}
|
|
||||||
</td>
|
|
||||||
<td>{report.draft ? 'Entwurf' : 'Erstellt'}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{#if activeReport}
|
|
||||||
<div
|
|
||||||
class="relative flex flex-col w-2/5 bg-base-200/50 px-4 py-6 overflow-scroll"
|
|
||||||
transition:fly={{ x: 200, duration: 200 }}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
|
||||||
on:click={() => (activeReport = null)}>✕</button
|
|
||||||
>
|
|
||||||
<h3 class="font-roboto font-semibold text-2xl">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">
|
|
||||||
<Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} />
|
|
||||||
<Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} />
|
|
||||||
</div>
|
|
||||||
<div class="divider mx-4" />
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="w-full"
|
|
||||||
title={activeReport.status === 'none'
|
|
||||||
? 'Zum Bearbeiten den Bearbeitungsstatus ändern'
|
|
||||||
: ''}
|
|
||||||
>
|
|
||||||
<Textarea
|
|
||||||
label="Interne Notizen"
|
|
||||||
readonly={activeReport.auditor === null && activeReport.notice === null}
|
|
||||||
rows={1}
|
|
||||||
bind:value={activeReport.notice}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w-full"
|
|
||||||
title={activeReport.status === 'none'
|
|
||||||
? 'Zum Bearbeiten den Bearbeitungsstatus ändern'
|
|
||||||
: ''}
|
|
||||||
>
|
|
||||||
<Textarea
|
|
||||||
label="(Öffentliche) Report Antwort"
|
|
||||||
readonly={activeReport.auditor === null && activeReport.notice === null}
|
|
||||||
rows={3}
|
|
||||||
bind:value={activeReport.statement}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Select label="Bearbeitungsstatus" size="sm" bind:value={activeReport.status}>
|
|
||||||
<option value="none" disabled={activeReport.auditor != null || activeReport.notice}
|
|
||||||
>Unbearbeitet</option
|
|
||||||
>
|
|
||||||
<option value="review">In Bearbeitung</option>
|
|
||||||
<option value="reviewed">Bearbeitet</option>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div class="self-end mt-auto pt-6 w-full flex justify-center">
|
|
||||||
<Input
|
|
||||||
type="submit"
|
|
||||||
value="Speichern"
|
|
||||||
on:click={() => saveActiveReportChangesModal.show()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dialog class="modal" bind:this={saveActiveReportChangesModal}>
|
|
||||||
<form method="dialog" class="modal-box">
|
|
||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
|
||||||
<h3 class="font-roboto text-xl">Änderungen Speichern?</h3>
|
|
||||||
<div class="flex flex-row space-x-2 mt-6">
|
|
||||||
<Input
|
|
||||||
type="submit"
|
|
||||||
value="Speichern"
|
|
||||||
on:click={async () => {
|
|
||||||
await updateActiveReport();
|
|
||||||
currentPageReports = [...currentPageReports];
|
|
||||||
if (activeReport.originalStatus !== 'reviewed' && activeReport.status === 'reviewed') {
|
|
||||||
$reportCount -= 1;
|
|
||||||
} else if (
|
|
||||||
activeReport.originalStatus === 'reviewed' &&
|
|
||||||
activeReport.status !== 'reviewed'
|
|
||||||
) {
|
|
||||||
$reportCount += 1;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Input type="submit" value="Abbrechen" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
|
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
@ -1,106 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { getSession } from '$lib/server/session';
|
|
||||||
import { Permissions } from '$lib/permissions';
|
|
||||||
import { Admin, Report, User } from '$lib/server/database';
|
|
||||||
import type { Attributes } from 'sequelize';
|
|
||||||
import { Op } from 'sequelize';
|
|
||||||
import { env } from '$env/dynamic/private';
|
|
||||||
|
|
||||||
export const POST = (async ({ request, cookies }) => {
|
|
||||||
if (getSession(cookies, { permissions: [Permissions.ReportRead] }) == null) {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 401
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: {
|
|
||||||
limit: number | null;
|
|
||||||
from: number | null;
|
|
||||||
|
|
||||||
draft: boolean | null;
|
|
||||||
status: 'none' | 'review' | 'reviewed' | null;
|
|
||||||
reporter: string | null;
|
|
||||||
reported: string | null;
|
|
||||||
} = await request.json();
|
|
||||||
|
|
||||||
const reportFindOptions: Attributes<Report> = {};
|
|
||||||
if (data.draft != null) reportFindOptions.draft = data.draft;
|
|
||||||
reportFindOptions.status = data.status == null ? ['none', 'review'] : data.status;
|
|
||||||
if (data.reporter != null) {
|
|
||||||
const reporter_ids = await User.findAll({
|
|
||||||
attributes: ['id'],
|
|
||||||
where: { username: { [Op.like]: `%${data.reporter}%` } }
|
|
||||||
});
|
|
||||||
reportFindOptions.reporter_id = reporter_ids.map((u) => u.id);
|
|
||||||
}
|
|
||||||
if (data.reported != null) {
|
|
||||||
const reported_ids = await User.findAll({
|
|
||||||
attributes: ['id'],
|
|
||||||
where: { username: { [Op.like]: `%${data.reported}%` } }
|
|
||||||
});
|
|
||||||
reportFindOptions.reported_id = reported_ids.map((u) => u.id);
|
|
||||||
}
|
|
||||||
let reports = await Report.findAll({
|
|
||||||
where: reportFindOptions,
|
|
||||||
include: [
|
|
||||||
{ model: User, as: 'reporter' },
|
|
||||||
{ model: User, as: 'reported' },
|
|
||||||
{ model: Admin, as: 'auditor' }
|
|
||||||
],
|
|
||||||
order: ['created_at'],
|
|
||||||
offset: data.from || 0,
|
|
||||||
limit: data.limit || 100
|
|
||||||
});
|
|
||||||
reports = reports.map((r) => {
|
|
||||||
if (r.auditor_id === null && r.status != 'none') {
|
|
||||||
// if the report was edited by the admin account set by the env variable, it has no relation to the admin
|
|
||||||
// table in the database, so it gets manually created here. we just assume that the auditor_id is never null
|
|
||||||
// when not edited by the env admin account
|
|
||||||
r.auditor_id = -1;
|
|
||||||
r.dataValues.auditor = {
|
|
||||||
id: -1,
|
|
||||||
username: env.ADMIN_USER,
|
|
||||||
permissions: new Permissions(Permissions.allPermissions()),
|
|
||||||
createdAt: 0,
|
|
||||||
updatedAt: 0
|
|
||||||
};
|
|
||||||
} else if (r.auditor) {
|
|
||||||
delete r.dataValues.auditor.password;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(reports));
|
|
||||||
}) satisfies RequestHandler;
|
|
||||||
|
|
||||||
export const PATCH = (async ({ request, cookies }) => {
|
|
||||||
if (getSession(cookies, { permissions: [Permissions.ReportWrite] }) == null) {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 401
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: {
|
|
||||||
id: number;
|
|
||||||
auditor: number;
|
|
||||||
notice: string | null;
|
|
||||||
statement: string | null;
|
|
||||||
status: 'none' | 'review' | 'reviewed' | null;
|
|
||||||
} = await request.json();
|
|
||||||
|
|
||||||
if (data.id === null || data.auditor === null) return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
const report = await Report.findOne({ where: { id: data.id } });
|
|
||||||
const admin = await Admin.findOne({ where: { id: data.auditor } });
|
|
||||||
if (report === null || (admin === null && data.auditor != -1))
|
|
||||||
return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
if (data.notice != null) report.notice = data.notice;
|
|
||||||
if (data.statement != null) report.statement = data.statement;
|
|
||||||
if (data.status != null) report.status = data.status;
|
|
||||||
if (admin != null) report.auditor_id = admin.id;
|
|
||||||
|
|
||||||
await report.save();
|
|
||||||
|
|
||||||
return new Response();
|
|
||||||
}) satisfies RequestHandler;
|
|
@ -1,5 +0,0 @@
|
|||||||
<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>
|
|
@ -1,33 +0,0 @@
|
|||||||
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,
|
|
||||||
status: 'none',
|
|
||||||
url_hash: crypto.randomBytes(18).toString('hex'),
|
|
||||||
completed: false,
|
|
||||||
reporter_id: reporter.id,
|
|
||||||
reported_id: reported.id
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ url: `${url.toString().replace(/\/$/, '')}/${report.url_hash}` }),
|
|
||||||
{
|
|
||||||
status: 201
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}) satisfies RequestHandler;
|
|
@ -1,3 +0,0 @@
|
|||||||
<div class="flex justify-center items-center w-full min-h-screen h-full">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
@ -1,28 +0,0 @@
|
|||||||
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_hash: params.url_hash },
|
|
||||||
include: [
|
|
||||||
{ model: User, as: 'reporter' },
|
|
||||||
{ model: User, as: 'reported' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (report == null) throw redirect(302, `${env.PUBLIC_BASE_PATH}/`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
draft: report.draft,
|
|
||||||
status: report.status,
|
|
||||||
reason: report.subject,
|
|
||||||
reporter: {
|
|
||||||
name: report.reporter.username
|
|
||||||
},
|
|
||||||
reported: {
|
|
||||||
name: report.reported.username
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,32 +0,0 @@
|
|||||||
<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.status === 'reviewed'}
|
|
||||||
<ReportCompleted />
|
|
||||||
{:else}
|
|
||||||
<div class="col-[1] row-[1]" transition:fly={{ x: 200, duration: 300 }}>
|
|
||||||
<ReportSubmitted />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import { Report } from '$lib/server/database';
|
|
||||||
|
|
||||||
export const POST = (async ({ params }) => {
|
|
||||||
const report = await Report.findOne({ where: { url_hash: params.url_hash } });
|
|
||||||
|
|
||||||
if (report == null) return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
report.draft = false;
|
|
||||||
await report.save();
|
|
||||||
|
|
||||||
return new Response(null, { status: 200 });
|
|
||||||
}) satisfies RequestHandler;
|
|
@ -1,3 +0,0 @@
|
|||||||
<div>
|
|
||||||
<h2 class="text-xl text-center">Dieser Report wurde von einem Admin bearbeitet</h2>
|
|
||||||
</div>
|
|
@ -1,67 +0,0 @@
|
|||||||
<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_hash}`, {
|
|
||||||
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>
|
|
@ -1,6 +0,0 @@
|
|||||||
<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,10 +11,7 @@ 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,9 +8,5 @@ export default {
|
|||||||
roboto: ['Roboto']
|
roboto: ['Roboto']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [require('daisyui')],
|
plugins: [require('daisyui')]
|
||||||
|
|
||||||
daisyui: {
|
|
||||||
logs: false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user