diff --git a/src/actions/signup.ts b/src/actions/signup.ts
index 3db2a45..98854e0 100644
--- a/src/actions/signup.ts
+++ b/src/actions/signup.ts
@@ -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) {
// check if a team with the same name already exists
if (input.teamName) {
diff --git a/src/actions/user.ts b/src/actions/user.ts
index ca256a7..a91eab6 100644
--- a/src/actions/user.ts
+++ b/src/actions/user.ts
@@ -84,5 +84,41 @@ export const user = {
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({})
+ };
+ }
})
};
diff --git a/src/app/admin/usersBlocked/CreateOrEditPopup.svelte b/src/app/admin/usersBlocked/CreateOrEditPopup.svelte
new file mode 100644
index 0000000..e1432f6
--- /dev/null
+++ b/src/app/admin/usersBlocked/CreateOrEditPopup.svelte
@@ -0,0 +1,101 @@
+
+
+
diff --git a/src/app/admin/usersBlocked/SidebarActions.svelte b/src/app/admin/usersBlocked/SidebarActions.svelte
new file mode 100644
index 0000000..652517f
--- /dev/null
+++ b/src/app/admin/usersBlocked/SidebarActions.svelte
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/src/app/admin/usersBlocked/UsersBlocked.svelte b/src/app/admin/usersBlocked/UsersBlocked.svelte
new file mode 100644
index 0000000..0c9d9fb
--- /dev/null
+++ b/src/app/admin/usersBlocked/UsersBlocked.svelte
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ #
+ UUID
+ Kommentar
+
+
+
+
+ {#each $blockedUsers as blockedUser, i (blockedUser)}
+
+ {i + 1} |
+ {blockedUser.uuid} |
+ {blockedUser.comment} |
+
+
+ |
+
+ {/each}
+
+
+
+
+
diff --git a/src/app/admin/usersBlocked/actions.ts b/src/app/admin/usersBlocked/actions.ts
new file mode 100644
index 0000000..9c89e19
--- /dev/null
+++ b/src/app/admin/usersBlocked/actions.ts
@@ -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;
+ });
+}
diff --git a/src/app/admin/usersBlocked/state.ts b/src/app/admin/usersBlocked/state.ts
new file mode 100644
index 0000000..55ff86d
--- /dev/null
+++ b/src/app/admin/usersBlocked/state.ts
@@ -0,0 +1,6 @@
+import { writable } from 'svelte/store';
+import type { BlockedUserCreateOrEditPopupState, BlockedUsers } from '@app/admin/usersBlocked/types.ts';
+
+export const blockedUsers = writable([]);
+
+export const blockedUserCreateOrEditPopupState = writable(null);
diff --git a/src/app/admin/usersBlocked/types.ts b/src/app/admin/usersBlocked/types.ts
new file mode 100644
index 0000000..36016bc
--- /dev/null
+++ b/src/app/admin/usersBlocked/types.ts
@@ -0,0 +1,9 @@
+import { type ActionReturnType, actions } from 'astro:actions';
+
+export type BlockedUsers = Exclude['data'], undefined>['blocked'];
+export type BlockedUser = BlockedUsers[0];
+
+export type BlockedUserCreateOrEditPopupState =
+ | { create: { onUpdate: (blockedUser: BlockedUser) => void } }
+ | { edit: { blockedUser: BlockedUser; onUpdate: (blockedUser: BlockedUser) => void } }
+ | null;
diff --git a/src/db/database.sql b/src/db/database.sql
index 2cc0350..b9e4431 100644
--- a/src/db/database.sql
+++ b/src/db/database.sql
@@ -28,6 +28,13 @@ CREATE TRIGGER IF NOT EXISTS user_username_update AFTER UPDATE ON user
END;
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
CREATE TABLE IF NOT EXISTS team (
id INT AUTO_INCREMENT PRIMARY KEY,
diff --git a/src/db/database.ts b/src/db/database.ts
index d76e31d..78eda4f 100644
--- a/src/db/database.ts
+++ b/src/db/database.ts
@@ -105,6 +105,17 @@ import {
type GetReportStatusReq,
reportStatus
} 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 {
protected readonly db: MySql2Database<{
@@ -113,6 +124,7 @@ export class Database {
teamDraft: typeof teamDraft;
teamMember: typeof teamMember;
user: typeof user;
+ blockedUser: typeof blockedUser;
death: typeof death;
report: typeof report;
reportStatus: typeof reportStatus;
@@ -139,6 +151,7 @@ export class Database {
teamDraft,
teamMember,
user,
+ blockedUser,
death,
report,
reportStatus,
@@ -173,6 +186,12 @@ export class Database {
getUserByUsername = (values: GetUserByUsernameReq) => getUserByUsername(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 */
addTeam = (values: AddTeamReq) => addTeam(this.db, values);
editTeam = (values: EditTeamReq) => editTeam(this.db, values);
diff --git a/src/db/schema/blockedUser.ts b/src/db/schema/blockedUser.ts
new file mode 100644
index 0000000..528b94e
--- /dev/null
+++ b/src/db/schema/blockedUser.ts
@@ -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);
+}
diff --git a/src/layouts/admin/AdminLayout.astro b/src/layouts/admin/AdminLayout.astro
index 26810b5..af7d3e1 100644
--- a/src/layouts/admin/AdminLayout.astro
+++ b/src/layouts/admin/AdminLayout.astro
@@ -27,6 +27,13 @@ const adminTabs = [
href: 'admin/users',
name: 'Registrierte Nutzer',
icon: 'heroicons:user',
+ subTabs: [
+ {
+ href: 'admin/users/blocked',
+ name: 'Blockierte Nutzer',
+ icon: 'heroicons:user-minus'
+ }
+ ],
enabled: session?.permissions.users
},
{
@@ -65,7 +72,7 @@ const adminTabs = [
-