diff --git a/src/actions/team.ts b/src/actions/team.ts
index f50ed22..d4b58b0 100644
--- a/src/actions/team.ts
+++ b/src/actions/team.ts
@@ -128,5 +128,53 @@ export const team = {
teams: await db.getTeams(input)
};
}
+ }),
+ addDeath: defineAction({
+ input: z.object({
+ deadUserId: z.number(),
+ killerUserId: z.number().nullish(),
+ message: z.string()
+ }),
+ handler: async (input, context) => {
+ Session.actionSessionFromCookies(context.cookies, Permissions.Users);
+
+ const { id } = await db.addDeath(input);
+
+ return {
+ id: id
+ };
+ }
+ }),
+ editDeath: defineAction({
+ input: z.object({
+ id: z.number(),
+ deadUserId: z.number(),
+ killerUserId: z.number().nullish(),
+ message: z.string()
+ }),
+ handler: async (input, context) => {
+ Session.actionSessionFromCookies(context.cookies, Permissions.Users);
+
+ await db.editDeath(input);
+ }
+ }),
+ deleteDeath: defineAction({
+ input: z.object({
+ id: z.number()
+ }),
+ handler: async (input, context) => {
+ Session.actionSessionFromCookies(context.cookies, Permissions.Users);
+
+ await db.deleteDeath(input);
+ }
+ }),
+ deaths: defineAction({
+ handler: async (_, context) => {
+ Session.actionSessionFromCookies(context.cookies, Permissions.Users);
+
+ return {
+ deaths: await db.getDeaths({})
+ };
+ }
})
};
diff --git a/src/app/admin/reports/BottomBar.svelte b/src/app/admin/reports/BottomBar.svelte
index 5e7e65a..7afb8b4 100644
--- a/src/app/admin/reports/BottomBar.svelte
+++ b/src/app/admin/reports/BottomBar.svelte
@@ -6,7 +6,7 @@
import TeamSearch from '@components/admin/search/TeamSearch.svelte';
import { editReportStatus, getReportStatus } from '@app/admin/reports/reports.ts';
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
- import Icon from "@iconify/svelte";
+ import Icon from '@iconify/svelte';
// html bindings
let previewDialogElem: HTMLDialogElement;
diff --git a/src/app/admin/teamDeaths/SidebarActions.svelte b/src/app/admin/teamDeaths/SidebarActions.svelte
new file mode 100644
index 0000000..0ab6ca9
--- /dev/null
+++ b/src/app/admin/teamDeaths/SidebarActions.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ !!user?.id }
+ },
+ {
+ key: 'killer',
+ type: 'user-search',
+ label: 'Killer',
+ options: { validate: (user) => (user?.username ? !!user?.id : true) }
+ }
+ ],
+ [{ key: 'message', type: 'textarea', label: 'Todesnachricht', options: { required: true, dynamicWidth: true } }]
+ ]}
+ onSubmit={addDeath}
+ bind:open={createPopupOpen}
+/>
diff --git a/src/app/admin/teamDeaths/TeamDeaths.svelte b/src/app/admin/teamDeaths/TeamDeaths.svelte
new file mode 100644
index 0000000..545d73a
--- /dev/null
+++ b/src/app/admin/teamDeaths/TeamDeaths.svelte
@@ -0,0 +1,69 @@
+
+
+{#snippet username(user?: { id: number; username: string })}
+ {user?.username}
+{/snippet}
+
+ (editPopupDeath = death)}
+ onDelete={onDeathDelete}
+/>
+
+ !!user?.id }
+ },
+ {
+ key: 'killer',
+ type: 'user-search',
+ label: 'Killer',
+ options: { validate: (user) => (user?.username ? !!user?.id : true) }
+ }
+ ],
+ [{ key: 'message', type: 'textarea', label: 'Todesnachricht', options: { required: true, dynamicWidth: true } }]
+ ]}
+ onSubmit={editDeath}
+ bind:open={editPopupDeath}
+/>
diff --git a/src/app/admin/teamDeaths/teamDeaths.ts b/src/app/admin/teamDeaths/teamDeaths.ts
new file mode 100644
index 0000000..76e1e34
--- /dev/null
+++ b/src/app/admin/teamDeaths/teamDeaths.ts
@@ -0,0 +1,61 @@
+import { type ActionReturnType, actions } from 'astro:actions';
+import { writable } from 'svelte/store';
+import { actionErrorPopup } from '@util/action.ts';
+import { addToWritableArray, deleteFromWritableArray, updateWritableArray } from '@util/state.ts';
+
+// types
+export type Deaths = Exclude['data'], undefined>['deaths'];
+export type Death = Deaths[0];
+
+// state
+export const deaths = writable([]);
+
+// actions
+export async function fetchDeaths() {
+ const { data, error } = await actions.team.deaths();
+ if (error) {
+ actionErrorPopup(error);
+ return;
+ }
+
+ deaths.set(data.deaths);
+}
+
+export async function addDeath(death: Death) {
+ const { data, error } = await actions.team.addDeath({
+ deadUserId: death.killed.id,
+ killerUserId: death.killer?.id,
+ message: death.message
+ });
+ if (error) {
+ actionErrorPopup(error);
+ return;
+ }
+
+ addToWritableArray(deaths, Object.assign(death, { id: data.id }));
+}
+
+export async function editDeath(death: Death) {
+ const { error } = await actions.team.editDeath({
+ id: death.id,
+ deadUserId: death.killed.id,
+ killerUserId: death.killer?.id,
+ message: death.message
+ });
+ if (error) {
+ actionErrorPopup(error);
+ return;
+ }
+
+ updateWritableArray(deaths, death, (d) => d.id == death.id);
+}
+
+export async function deleteDeath(death: Death) {
+ const { error } = await actions.team.deleteDeath({ id: death.id });
+ if (error) {
+ actionErrorPopup(error);
+ return;
+ }
+
+ deleteFromWritableArray(deaths, (d) => d.id == death.id);
+}
diff --git a/src/components/admin/popup/CrudPopup.svelte b/src/components/admin/popup/CrudPopup.svelte
index 79d8e40..96d7348 100644
--- a/src/components/admin/popup/CrudPopup.svelte
+++ b/src/components/admin/popup/CrudPopup.svelte
@@ -111,11 +111,11 @@
submitEnabled = false;
for (const key of keys) {
for (const k of key) {
- if (k.options?.required) {
- if (k.options?.validate) {
+ if (k.options?.validate) {
+ if (k.options?.required && !target[k.key]) {
+ return;
+ } else if (k.options?.required || target[k.key]) {
if (!k.options.validate(target[k.key])) return;
- } else {
- if (!target[k.key]) return;
}
}
}
diff --git a/src/db/database.sql b/src/db/database.sql
index b4ae7a5..e85bf6b 100644
--- a/src/db/database.sql
+++ b/src/db/database.sql
@@ -61,8 +61,9 @@ CREATE TABLE IF NOT EXISTS team_draft (
-- death
CREATE TABLE IF NOT EXISTS death (
+ id INT AUTO_INCREMENT PRIMARY KEY,
message VARCHAR(1024) NOT NULL,
- dead_user_id INT NOT NULL,
+ dead_user_id INT NOT NULL UNIQUE,
killer_user_id INT,
FOREIGN KEY (dead_user_id) REFERENCES user(id) ON DELETE CASCADE,
FOREIGN KEY (killer_user_id) REFERENCES user(id) ON DELETE CASCADE
diff --git a/src/db/database.ts b/src/db/database.ts
index 92004c0..9deb710 100644
--- a/src/db/database.ts
+++ b/src/db/database.ts
@@ -83,9 +83,13 @@ import {
setSettings
} from './schema/settings';
import {
- addDeath,
type AddDeathReq,
+ addDeath,
death,
+ deleteDeath,
+ type DeleteDeathReq,
+ editDeath,
+ type EditDeathReq,
getDeathByUserId,
type GetDeathByUserIdReq,
getDeaths,
@@ -273,6 +277,8 @@ export class Database {
/* death */
addDeath = (values: AddDeathReq) => addDeath(this.db, values);
+ editDeath = (values: EditDeathReq) => editDeath(this.db, values);
+ deleteDeath = (values: DeleteDeathReq) => deleteDeath(this.db, values);
getDeathByUserId = (values: GetDeathByUserIdReq) => getDeathByUserId(this.db, values);
getDeaths = (values: GetDeathsReq) => getDeaths(this.db, values);
diff --git a/src/db/schema/death.ts b/src/db/schema/death.ts
index da847a5..82a4cc0 100644
--- a/src/db/schema/death.ts
+++ b/src/db/schema/death.ts
@@ -6,6 +6,7 @@ import { eq } from 'drizzle-orm';
type Database = MySql2Database<{ death: typeof death }>;
export const death = mysqlTable('death', {
+ id: int('id').primaryKey().autoincrement(),
message: varchar('message', { length: 1024 }).notNull(),
deadUserId: int('dead_user_id')
.notNull()
@@ -15,10 +16,21 @@ export const death = mysqlTable('death', {
export type AddDeathReq = {
message: string;
- killerUserId?: number;
+ killerUserId?: number | null;
deadUserId: number;
};
+export type EditDeathReq = {
+ id: number;
+ message: string;
+ killerUserId?: number | null;
+ deadUserId: number;
+};
+
+export type DeleteDeathReq = {
+ id: number;
+};
+
export type GetDeathByUserIdReq = {
userId: number;
};
@@ -26,7 +38,24 @@ export type GetDeathByUserIdReq = {
export type GetDeathsReq = {};
export async function addDeath(db: Database, values: AddDeathReq) {
- await db.insert(death).values(values);
+ const ids = await db.insert(death).values(values).$returningId();
+
+ return ids[0];
+}
+
+export async function editDeath(db: Database, values: EditDeathReq) {
+ await db
+ .update(death)
+ .set({
+ message: values.message,
+ killerUserId: values.killerUserId,
+ deadUserId: values.deadUserId
+ })
+ .where(eq(death.id, values.id));
+}
+
+export async function deleteDeath(db: Database, values: DeleteDeathReq) {
+ await db.delete(death).where(eq(death.id, values.id));
}
export async function getDeathByUserId(db: Database, values: GetDeathByUserIdReq) {
@@ -41,6 +70,7 @@ export async function getDeaths(db: Database, _values: GetDeathsReq) {
return db
.select({
+ id: death.id,
message: death.message,
killed: {
id: killed.id,
diff --git a/src/layouts/admin/AdminLayout.astro b/src/layouts/admin/AdminLayout.astro
index 83ed98c..3aca329 100644
--- a/src/layouts/admin/AdminLayout.astro
+++ b/src/layouts/admin/AdminLayout.astro
@@ -40,6 +40,13 @@ const adminTabs = [
href: 'admin/teams',
name: 'Teams',
icon: 'heroicons:users',
+ subTabs: [
+ {
+ href: 'admin/teams/dead',
+ name: 'Tote Spieler',
+ icon: 'heroicons:x-mark'
+ }
+ ],
enabled: session?.permissions.users
},
{
diff --git a/src/pages/admin/teams/dead.astro b/src/pages/admin/teams/dead.astro
new file mode 100644
index 0000000..8e9509d
--- /dev/null
+++ b/src/pages/admin/teams/dead.astro
@@ -0,0 +1,16 @@
+---
+import AdminLayout from '@layouts/admin/AdminLayout.astro';
+import SidebarActions from '@app/admin/teamDeaths/SidebarActions.svelte';
+import TeamDeaths from '@app/admin/teamDeaths/TeamDeaths.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.Users);
+if (!session) return Astro.redirect(`${BASE_PATH}/admin`);
+---
+
+
+
+
+
diff --git a/src/pages/admin/teams.astro b/src/pages/admin/teams/index.astro
similarity index 100%
rename from src/pages/admin/teams.astro
rename to src/pages/admin/teams/index.astro