diff --git a/src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.html b/src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.html
deleted file mode 100644
index bc58a13..0000000
--- a/src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- @if (user()?.loginToken; as loginToken) {
-
- }
-
-
-
-
diff --git a/src/client/src/app/components/users/user-dialog/user-dialog.component.html b/src/client/src/app/components/users/user-dialog/user-dialog.component.html
index b532764..f8b7c70 100644
--- a/src/client/src/app/components/users/user-dialog/user-dialog.component.html
+++ b/src/client/src/app/components/users/user-dialog/user-dialog.component.html
@@ -29,30 +29,36 @@
@if (userToUpdate()) {
-
- @if (isTokenLoading()) {
-
- } @else if (!tokenVisible()) {
-
- } @else {
-
- }
-
+
+ @if (isTokenLoading()) {
+
+ } @else if (!tokenVisible()) {
+
+ } @else {
+
+ }
+
+
+
+
-
-
+
}
@@ -193,4 +199,4 @@
{{ translations.users_dialog_roles_title() }}
-
+
diff --git a/src/client/src/app/components/users/user-dialog/user-dialog.component.ts b/src/client/src/app/components/users/user-dialog/user-dialog.component.ts
index c544cf0..5e7ebc1 100644
--- a/src/client/src/app/components/users/user-dialog/user-dialog.component.ts
+++ b/src/client/src/app/components/users/user-dialog/user-dialog.component.ts
@@ -10,7 +10,7 @@ import {
} from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
-import { InterpolatePipe } from '@ngneers/signal-translate';
+import { interpolate, InterpolatePipe } from '@ngneers/signal-translate';
import { Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import copyToClipboard from 'copy-to-clipboard';
@@ -41,8 +41,8 @@ import { TranslateService } from '../../../services/translate.service';
import { areArraysEqual } from '../../../utils/array.utils';
import { notNullish } from '../../../utils/common.utils';
import { selectSignal } from '../../../utils/ngrx.utils';
-import { UserCreatedDialogComponent } from '../user-created-dialog/user-created-dialog.component';
import { UserItemComponent } from '../user-item/user-item.component';
+import { UserWelcomeDialogComponent } from '../user-welcome-dialog/user-welcome-dialog.component';
@Component({
selector: 'app-user-dialog',
@@ -61,8 +61,8 @@ import { UserItemComponent } from '../user-item/user-item.component';
MessagesModule,
OverlayPanelModule,
ReactiveFormsModule,
- UserCreatedDialogComponent,
UserItemComponent,
+ UserWelcomeDialogComponent,
],
templateUrl: './user-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -74,7 +74,7 @@ export class UserDialogComponent {
private readonly _allUsers = selectSignal(userSelectors.selectEntities);
private readonly _randomId = Math.random().toString(36).substring(2, 9);
- private readonly _userCreatedDialog = viewChild.required(UserCreatedDialogComponent);
+ private readonly _userWelcomeDialog = viewChild.required(UserWelcomeDialogComponent);
protected readonly form = this._formBuilder.group({
id: new FormControl
(null),
@@ -131,7 +131,7 @@ export class UserDialogComponent {
.subscribe(({ type, response }) => {
this.close();
if (type === addUserAction.success.type) {
- this._userCreatedDialog().open(response);
+ this._userWelcomeDialog().open(response);
}
});
actions$
@@ -229,6 +229,17 @@ export class UserDialogComponent {
}
protected submit() {
+ if (Object.values(this._allUsers()).some(x => x?.alias === this.form.value.alias)) {
+ this._messageService.add({
+ severity: 'error',
+ summary: interpolate(this.translations.users_dialog_error_exists(), {
+ user: this.form.value.alias,
+ }),
+ life: 2000,
+ });
+ return;
+ }
+
if (!this.form.valid) {
this.form.markAllAsTouched();
return;
@@ -274,6 +285,13 @@ export class UserDialogComponent {
return `${purpose}-${this._randomId}`;
}
+ protected openUserWelcomeDialog() {
+ const user = this.userToUpdate();
+ if (user) {
+ this._userWelcomeDialog().open(user);
+ }
+ }
+
private getUsersByIds(ids: string[]) {
return ids.map(id => this._allUsers()[id]).filter(notNullish);
}
diff --git a/src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.html b/src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.html
new file mode 100644
index 0000000..3496cbf
--- /dev/null
+++ b/src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.html
@@ -0,0 +1,46 @@
+
+
+
+ {{ translations.users_userWelcomeDialog_title() | interpolate: user() }}
+
+
+ @if (isLoading()) {
+
+ } @else {
+
+
+
+ @if (loginToken(); as loginToken) {
+
+ }
+
+
+ }
+
+
+
+
diff --git a/src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.ts b/src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.ts
similarity index 51%
rename from src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.ts
rename to src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.ts
index dcfd29d..7263918 100644
--- a/src/client/src/app/components/users/user-created-dialog/user-created-dialog.component.ts
+++ b/src/client/src/app/components/users/user-welcome-dialog/user-welcome-dialog.component.ts
@@ -1,41 +1,68 @@
import { CommonModule } from '@angular/common';
-import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { interpolate, InterpolatePipe } from '@ngneers/signal-translate';
+import { Store } from '@ngrx/store';
import copyToClipboard from 'copy-to-clipboard';
import { MessageService } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
+import { ProgressSpinnerModule } from 'primeng/progressspinner';
+import { isActionBusy } from '../../../+state/action-state';
+import {
+ loadUserLoginTokenAction,
+ selectUserLoginToken,
+ selectUsersActionState,
+} from '../../../+state/users';
import { User } from '../../../models/parsed-models';
import { TranslateService } from '../../../services/translate.service';
+import { selectSignal } from '../../../utils/ngrx.utils';
@Component({
- selector: 'app-user-created-dialog',
+ selector: 'app-user-welcome-dialog',
standalone: true,
- imports: [ButtonModule, CommonModule, DialogModule, InterpolatePipe],
- templateUrl: './user-created-dialog.component.html',
+ imports: [ButtonModule, CommonModule, DialogModule, InterpolatePipe, ProgressSpinnerModule],
+ templateUrl: './user-welcome-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class UserCreatedDialogComponent {
+export class UserWelcomeDialogComponent {
private readonly _messageService = inject(MessageService);
+ private readonly _store = inject(Store);
protected readonly translations = inject(TranslateService).translations;
protected readonly visible = signal(false);
protected readonly user = signal(undefined);
+ private readonly _loadTokenState = selectSignal(selectUsersActionState('loadLoginToken'));
+ protected readonly isLoading = computed(() => isActionBusy(this._loadTokenState()));
+ protected readonly loginToken = selectSignal(
+ computed(() => selectUserLoginToken(this.user()?.id))
+ );
+
+ protected loadLoginToken() {
+ if (this.loginToken()) {
+ return;
+ }
+
+ const user = this.user();
+ if (!user) return;
+
+ this._store.dispatch(loadUserLoginTokenAction({ userId: user.id }));
+ }
public open(user: User) {
this.user.set(user);
+ this.loadLoginToken();
this.visible.set(true);
}
protected copyWelcomeMessage() {
- const message = interpolate(this.translations.users_userCreatedDialog_welcomeMessage(), {
+ const message = interpolate(this.translations.users_userWelcomeDialog_welcomeMessage(), {
url: (document.head.querySelector('base') as HTMLBaseElement).href,
});
copyToClipboard(message);
this._messageService.add({
severity: 'success',
- summary: this.translations.users_userCreatedDialog_welcomeMessageCopied(),
+ summary: this.translations.users_userWelcomeDialog_welcomeMessageCopied(),
life: 2000,
});
}
diff --git a/src/client/src/app/i18n/de.json b/src/client/src/app/i18n/de.json
index 943f5db..5eea8f6 100644
--- a/src/client/src/app/i18n/de.json
+++ b/src/client/src/app/i18n/de.json
@@ -195,7 +195,8 @@
"developer": "Entwickler"
},
"error": {
- "save": "Fehler beim Speichern des Benutzers."
+ "save": "Fehler beim Speichern des Benutzers.",
+ "exists": "Benutzer \"{{user}}\" existiert bereits"
}
},
"error": {
@@ -209,8 +210,8 @@
"body": "Inhalt",
"bodyDefault": "Benachrichtigungen funktionieren!!! Wohoo ⛳"
},
- "userCreatedDialog": {
- "title": "User \"{{alias}}\" created",
+ "userWelcomeDialog": {
+ "title": "Willkommen {{alias}}",
"copyWelcomeMessage": "Willkommensnachricht kopieren",
"copyPassword": "Passwort kopieren",
"welcomeMessage": "Hallo Liebe Minigolffreunde!\nDie Anmeldung zum Minigolffreitag läuft ab jetzt über die Web-basierte App, welche du unter:\n{{url}}\naufrufen kannst.\nSpeichere sie dir als Lesezeichen, oder füge sie zu deinem Home-Bildschirm hinzu um jederzeit darauf zugreifen zu können.\nMelde dich dort bitte mit deinem Passwort an und registriere dich selber für deine Spielzeiten.\nBitte schreibe doch trotzdem im Facebook Beitrag, dass du dich registriert hast, damit dort die Konversation aufrecht erhalten wird.\nViel Spaß beim Minigolfen!\nDein Orga-Team",
diff --git a/src/client/src/app/i18n/en.json b/src/client/src/app/i18n/en.json
index 52ec1ef..8127df2 100644
--- a/src/client/src/app/i18n/en.json
+++ b/src/client/src/app/i18n/en.json
@@ -195,7 +195,8 @@
"developer": "Developer"
},
"error": {
- "save": "Failed to save user."
+ "save": "Failed to save user.",
+ "exists": "User \"{{user}}\" already exists"
}
},
"error": {
@@ -209,8 +210,8 @@
"body": "body",
"bodyDefault": "Notifications are working!!! Wohoo ⛳"
},
- "userCreatedDialog": {
- "title": "User \"{{alias}}\" created",
+ "userWelcomeDialog": {
+ "title": "Welcome {{alias}}",
"copyWelcomeMessage": "Copy welcome message",
"copyPassword": "Copy password",
"welcomeMessage": "Hello dear minigolf friends!\nRegistration for Minigolf Friday is now possible via the web-based app, which you can download at:\n{{url}}.\nSave it as a bookmark or add it to your home screen so that you can access it at any time.\nPlease log in there with your password and register yourself for your game times.\nPlease still write in the Facebook post that you have registered so that the conversation is maintained there.\nHave fun playing mini golf!\nYour organization team",
diff --git a/src/server/data/Entities/UserEntity.cs b/src/server/data/Entities/UserEntity.cs
index 7a09ed0..523b989 100644
--- a/src/server/data/Entities/UserEntity.cs
+++ b/src/server/data/Entities/UserEntity.cs
@@ -58,5 +58,6 @@ public static void Configure(EntityTypeBuilder builder)
builder.HasKey(x => x.Id);
builder.HasIndex(x => x.LoginToken).IsUnique();
+ builder.HasIndex(x => x.Alias).IsUnique();
}
}
diff --git a/src/server/host/Endpoints/Administration/Users/CreateUserEndpoint.cs b/src/server/host/Endpoints/Administration/Users/CreateUserEndpoint.cs
index 9f84f7b..11a7ed4 100644
--- a/src/server/host/Endpoints/Administration/Users/CreateUserEndpoint.cs
+++ b/src/server/host/Endpoints/Administration/Users/CreateUserEndpoint.cs
@@ -1,10 +1,14 @@
using System.ComponentModel.DataAnnotations;
using FastEndpoints;
using FluentValidation;
+using Microsoft.EntityFrameworkCore;
using MinigolfFriday.Data;
using MinigolfFriday.Data.Entities;
using MinigolfFriday.Domain.Models;
using MinigolfFriday.Domain.Models.RealtimeEvents;
+using MinigolfFriday.Host.Common;
+using MinigolfFriday.Host.Mappers;
+using MinigolfFriday.Host.Options;
using MinigolfFriday.Host.Services;
using MinigolfFriday.Host.Utilities;
@@ -51,7 +55,8 @@ public CreateUserRequestValidator(IIdService idService)
public class CreateUserEndpoint(
DatabaseContext databaseContext,
IRealtimeEventsService realtimeEventsService,
- IIdService idService
+ IIdService idService,
+ IUserMapper userMapper
) : Endpoint
{
public override void Configure()
@@ -59,10 +64,21 @@ public override void Configure()
Post("");
Group();
Description(x => x.ClearDefaultProduces(200).Produces(201));
+ this.ProducesError(EndpointErrors.UserExists);
}
public override async Task HandleAsync(CreateUserRequest req, CancellationToken ct)
{
+ var existentUser = await databaseContext
+ .Users.Where(x => x.Alias == req.Alias)
+ .Select(userMapper.MapUserExpression)
+ .FirstOrDefaultAsync(ct);
+ if (existentUser != null)
+ {
+ await this.SendErrorAsync(EndpointErrors.UserExists, existentUser.Alias, ct);
+ return;
+ }
+
var user = new UserEntity()
{
Alias = req.Alias,
diff --git a/src/server/host/Endpoints/EndpointErrors.cs b/src/server/host/Endpoints/EndpointErrors.cs
index 02de19a..fb226ed 100644
--- a/src/server/host/Endpoints/EndpointErrors.cs
+++ b/src/server/host/Endpoints/EndpointErrors.cs
@@ -6,6 +6,8 @@ public class EndpointErrors
{
public static readonly EndpointError.Params1 UserNotFound =
new(404, "A user with the id {0} does not exist.", "UserId");
+ public static readonly EndpointError.Params1 UserExists =
+ new(404, "A user with the alias {0} does already exist.", "Alias");
public static readonly EndpointError UserIdNotInClaims =
new(403, "Could not extract user id from claims.");
public static readonly EndpointError CannotDeleteSelf = new(409, "You cannot delete yourself.");
diff --git a/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.Designer.cs b/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.Designer.cs
new file mode 100644
index 0000000..5f4367b
--- /dev/null
+++ b/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.Designer.cs
@@ -0,0 +1,616 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using MinigolfFriday.Data;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.MsSql.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20240718112149_UserHasUniqueAlias")]
+ partial class UserHasUniqueAlias
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Date")
+ .HasColumnType("date")
+ .HasColumnName("date");
+
+ b.Property("RegistrationDeadline")
+ .HasColumnType("datetimeoffset")
+ .HasColumnName("registration_deadline");
+
+ b.Property("Staged")
+ .HasColumnType("bit")
+ .HasColumnName("staged");
+
+ b.Property("StartedAt")
+ .HasColumnType("datetimeoffset")
+ .HasColumnName("started_at");
+
+ b.HasKey("Id");
+
+ b.ToTable("events", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("GroupCode")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("nvarchar(20)")
+ .HasColumnName("group_code");
+
+ b.Property("timeslot_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("timeslot_id");
+
+ b.ToTable("event_instances", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("event_timeslot_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("event_timeslot_id");
+
+ b.ToTable("event_instance_preconfigurations", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("EventId")
+ .HasColumnType("bigint")
+ .HasColumnName("event_id");
+
+ b.Property("IsFallbackAllowed")
+ .HasColumnType("bit")
+ .HasColumnName("is_fallback_allowed");
+
+ b.Property("MapId")
+ .HasColumnType("bigint")
+ .HasColumnName("map_id");
+
+ b.Property("Time")
+ .HasColumnType("time")
+ .HasColumnName("time");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MapId");
+
+ b.HasIndex("EventId", "Time")
+ .IsUnique();
+
+ b.ToTable("event_timeslots", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("EventTimeslotId")
+ .HasColumnType("bigint")
+ .HasColumnName("event_timeslot_id");
+
+ b.Property("FallbackEventTimeslotId")
+ .HasColumnType("bigint")
+ .HasColumnName("fallback_event_timeslot_id");
+
+ b.Property("PlayerId")
+ .HasColumnType("bigint")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EventTimeslotId");
+
+ b.HasIndex("FallbackEventTimeslotId");
+
+ b.HasIndex("PlayerId");
+
+ b.ToTable("event_timeslot_registration", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(true)
+ .HasColumnName("active");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(150)
+ .HasColumnType("nvarchar(150)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("maps", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.RoleEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("int")
+ .HasColumnName("id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("roles", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = 0,
+ Name = "Player"
+ },
+ new
+ {
+ Id = 1,
+ Name = "Admin"
+ },
+ new
+ {
+ Id = 2,
+ Name = "Developer"
+ });
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Alias")
+ .HasMaxLength(150)
+ .HasColumnType("nvarchar(150)")
+ .HasColumnName("alias");
+
+ b.Property("LoginToken")
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)")
+ .HasColumnName("login_token");
+
+ b.Property("SettingsId")
+ .HasColumnType("bigint")
+ .HasColumnName("settings_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Alias")
+ .IsUnique()
+ .HasFilter("[alias] IS NOT NULL");
+
+ b.HasIndex("LoginToken")
+ .IsUnique()
+ .HasFilter("[login_token] IS NOT NULL");
+
+ b.HasIndex("SettingsId");
+
+ b.ToTable("users", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Auth")
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)")
+ .HasColumnName("auth");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("nvarchar(2048)")
+ .HasColumnName("endpoint");
+
+ b.Property("Lang")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)")
+ .HasColumnName("lang");
+
+ b.Property("P256DH")
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)")
+ .HasColumnName("p256dh");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Endpoint")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_push_subscriptions", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("EnableNotifications")
+ .HasColumnType("bit")
+ .HasColumnName("enable_notifications");
+
+ b.Property("NotifyOnEventPublish")
+ .HasColumnType("bit")
+ .HasColumnName("notify_on_event_publish");
+
+ b.Property("NotifyOnEventStart")
+ .HasColumnType("bit")
+ .HasColumnName("notify_on_event_start");
+
+ b.Property("NotifyOnEventUpdated")
+ .HasColumnType("bit")
+ .HasColumnName("notify_on_event_updated");
+
+ b.Property("NotifyOnTimeslotStart")
+ .HasColumnType("bit")
+ .HasColumnName("notify_on_timeslot_start");
+
+ b.Property("SecondsToNotifyBeforeTimeslotStart")
+ .HasColumnType("int")
+ .HasColumnName("seconds_to_notify_before_timeslot_start");
+
+ b.HasKey("Id");
+
+ b.ToTable("user_settings", (string)null);
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.Property("event_instance_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("event_instance_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("event_instances_to_users");
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.Property("avoided_user_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("avoided_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_avoided_users");
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.Property("event_instance_preconfiguration_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("event_instance_preconfiguration_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_event_instance_preconfigurations");
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.Property("preferred_user_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("preferred_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_preferred_users");
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.Property("role_id")
+ .HasColumnType("int");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("role_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_roles");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Instances")
+ .HasForeignKey("timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeSlot")
+ .WithMany("Preconfigurations")
+ .HasForeignKey("event_timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeSlot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventEntity", "Event")
+ .WithMany("Timeslots")
+ .HasForeignKey("EventId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.MinigolfMapEntity", "Map")
+ .WithMany("EventTimeslots")
+ .HasForeignKey("MapId");
+
+ b.Navigation("Event");
+
+ b.Navigation("Map");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Registrations")
+ .HasForeignKey("EventTimeslotId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "FallbackEventTimeslot")
+ .WithMany()
+ .HasForeignKey("FallbackEventTimeslotId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "Player")
+ .WithMany()
+ .HasForeignKey("PlayerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+
+ b.Navigation("FallbackEventTimeslot");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserSettingsEntity", "Settings")
+ .WithMany("Users")
+ .HasForeignKey("SettingsId");
+
+ b.Navigation("Settings");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "User")
+ .WithMany("PushSubscriptions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstanceEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("avoided_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.ClientCascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_preconfiguration_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("preferred_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.ClientCascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.RoleEntity", null)
+ .WithMany()
+ .HasForeignKey("role_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Navigation("Timeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Navigation("Instances");
+
+ b.Navigation("Preconfigurations");
+
+ b.Navigation("Registrations");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Navigation("EventTimeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Navigation("PushSubscriptions");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Navigation("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.cs b/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.cs
new file mode 100644
index 0000000..3af15c5
--- /dev/null
+++ b/src/server/migrations/mssql/Migrations/20240718112149_UserHasUniqueAlias.cs
@@ -0,0 +1,29 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.MsSql.Migrations
+{
+ ///
+ public partial class UserHasUniqueAlias : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateIndex(
+ name: "IX_users_alias",
+ table: "users",
+ column: "alias",
+ unique: true,
+ filter: "[alias] IS NOT NULL");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_users_alias",
+ table: "users");
+ }
+ }
+}
diff --git a/src/server/migrations/mssql/Migrations/DatabaseContextModelSnapshot.cs b/src/server/migrations/mssql/Migrations/DatabaseContextModelSnapshot.cs
index c457801..89f46d3 100644
--- a/src/server/migrations/mssql/Migrations/DatabaseContextModelSnapshot.cs
+++ b/src/server/migrations/mssql/Migrations/DatabaseContextModelSnapshot.cs
@@ -252,6 +252,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("Alias")
+ .IsUnique()
+ .HasFilter("[alias] IS NOT NULL");
+
b.HasIndex("LoginToken")
.IsUnique()
.HasFilter("[login_token] IS NOT NULL");
diff --git a/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.Designer.cs b/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.Designer.cs
new file mode 100644
index 0000000..d4f7a46
--- /dev/null
+++ b/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.Designer.cs
@@ -0,0 +1,614 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using MinigolfFriday.Data;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.PostgreSql.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20240718112151_UserHasUniqueAlias")]
+ partial class UserHasUniqueAlias
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.6")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Date")
+ .HasColumnType("date")
+ .HasColumnName("date");
+
+ b.Property("RegistrationDeadline")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("registration_deadline");
+
+ b.Property("Staged")
+ .HasColumnType("boolean")
+ .HasColumnName("staged");
+
+ b.Property("StartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("started_at");
+
+ b.HasKey("Id");
+
+ b.ToTable("events", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("GroupCode")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("group_code");
+
+ b.Property("timeslot_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("timeslot_id");
+
+ b.ToTable("event_instances", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("event_timeslot_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("event_timeslot_id");
+
+ b.ToTable("event_instance_preconfigurations", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("EventId")
+ .HasColumnType("bigint")
+ .HasColumnName("event_id");
+
+ b.Property("IsFallbackAllowed")
+ .HasColumnType("boolean")
+ .HasColumnName("is_fallback_allowed");
+
+ b.Property("MapId")
+ .HasColumnType("bigint")
+ .HasColumnName("map_id");
+
+ b.Property("Time")
+ .HasColumnType("time without time zone")
+ .HasColumnName("time");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MapId");
+
+ b.HasIndex("EventId", "Time")
+ .IsUnique();
+
+ b.ToTable("event_timeslots", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("EventTimeslotId")
+ .HasColumnType("bigint")
+ .HasColumnName("event_timeslot_id");
+
+ b.Property("FallbackEventTimeslotId")
+ .HasColumnType("bigint")
+ .HasColumnName("fallback_event_timeslot_id");
+
+ b.Property("PlayerId")
+ .HasColumnType("bigint")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EventTimeslotId");
+
+ b.HasIndex("FallbackEventTimeslotId");
+
+ b.HasIndex("PlayerId");
+
+ b.ToTable("event_timeslot_registration", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(true)
+ .HasColumnName("active");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(150)
+ .HasColumnType("character varying(150)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("maps", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.RoleEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("roles", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = 0,
+ Name = "Player"
+ },
+ new
+ {
+ Id = 1,
+ Name = "Admin"
+ },
+ new
+ {
+ Id = 2,
+ Name = "Developer"
+ });
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Alias")
+ .HasMaxLength(150)
+ .HasColumnType("character varying(150)")
+ .HasColumnName("alias");
+
+ b.Property("LoginToken")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("login_token");
+
+ b.Property("SettingsId")
+ .HasColumnType("bigint")
+ .HasColumnName("settings_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Alias")
+ .IsUnique();
+
+ b.HasIndex("LoginToken")
+ .IsUnique();
+
+ b.HasIndex("SettingsId");
+
+ b.ToTable("users", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Auth")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("auth");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)")
+ .HasColumnName("endpoint");
+
+ b.Property("Lang")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("character varying(10)")
+ .HasColumnName("lang");
+
+ b.Property("P256DH")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("p256dh");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Endpoint")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_push_subscriptions", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("EnableNotifications")
+ .HasColumnType("boolean")
+ .HasColumnName("enable_notifications");
+
+ b.Property("NotifyOnEventPublish")
+ .HasColumnType("boolean")
+ .HasColumnName("notify_on_event_publish");
+
+ b.Property("NotifyOnEventStart")
+ .HasColumnType("boolean")
+ .HasColumnName("notify_on_event_start");
+
+ b.Property("NotifyOnEventUpdated")
+ .HasColumnType("boolean")
+ .HasColumnName("notify_on_event_updated");
+
+ b.Property("NotifyOnTimeslotStart")
+ .HasColumnType("boolean")
+ .HasColumnName("notify_on_timeslot_start");
+
+ b.Property("SecondsToNotifyBeforeTimeslotStart")
+ .HasColumnType("integer")
+ .HasColumnName("seconds_to_notify_before_timeslot_start");
+
+ b.HasKey("Id");
+
+ b.ToTable("user_settings", (string)null);
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.Property("event_instance_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("event_instance_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("event_instances_to_users");
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.Property("avoided_user_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("avoided_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_avoided_users");
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.Property("event_instance_preconfiguration_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("event_instance_preconfiguration_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_event_instance_preconfigurations");
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.Property("preferred_user_id")
+ .HasColumnType("bigint");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("preferred_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_preferred_users");
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.Property("role_id")
+ .HasColumnType("integer");
+
+ b.Property("user_id")
+ .HasColumnType("bigint");
+
+ b.HasKey("role_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_roles");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Instances")
+ .HasForeignKey("timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeSlot")
+ .WithMany("Preconfigurations")
+ .HasForeignKey("event_timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeSlot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventEntity", "Event")
+ .WithMany("Timeslots")
+ .HasForeignKey("EventId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.MinigolfMapEntity", "Map")
+ .WithMany("EventTimeslots")
+ .HasForeignKey("MapId");
+
+ b.Navigation("Event");
+
+ b.Navigation("Map");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Registrations")
+ .HasForeignKey("EventTimeslotId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "FallbackEventTimeslot")
+ .WithMany()
+ .HasForeignKey("FallbackEventTimeslotId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "Player")
+ .WithMany()
+ .HasForeignKey("PlayerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+
+ b.Navigation("FallbackEventTimeslot");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserSettingsEntity", "Settings")
+ .WithMany("Users")
+ .HasForeignKey("SettingsId");
+
+ b.Navigation("Settings");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "User")
+ .WithMany("PushSubscriptions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstanceEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("avoided_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_preconfiguration_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("preferred_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.RoleEntity", null)
+ .WithMany()
+ .HasForeignKey("role_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Navigation("Timeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Navigation("Instances");
+
+ b.Navigation("Preconfigurations");
+
+ b.Navigation("Registrations");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Navigation("EventTimeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Navigation("PushSubscriptions");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Navigation("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.cs b/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.cs
new file mode 100644
index 0000000..7d12b5b
--- /dev/null
+++ b/src/server/migrations/postgresql/Migrations/20240718112151_UserHasUniqueAlias.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.PostgreSql.Migrations
+{
+ ///
+ public partial class UserHasUniqueAlias : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateIndex(
+ name: "IX_users_alias",
+ table: "users",
+ column: "alias",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_users_alias",
+ table: "users");
+ }
+ }
+}
diff --git a/src/server/migrations/postgresql/Migrations/DatabaseContextModelSnapshot.cs b/src/server/migrations/postgresql/Migrations/DatabaseContextModelSnapshot.cs
index 521aa94..872d1fe 100644
--- a/src/server/migrations/postgresql/Migrations/DatabaseContextModelSnapshot.cs
+++ b/src/server/migrations/postgresql/Migrations/DatabaseContextModelSnapshot.cs
@@ -252,6 +252,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("Alias")
+ .IsUnique();
+
b.HasIndex("LoginToken")
.IsUnique();
diff --git a/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.Designer.cs b/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.Designer.cs
new file mode 100644
index 0000000..b3a2639
--- /dev/null
+++ b/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.Designer.cs
@@ -0,0 +1,591 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using MinigolfFriday.Data;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.Sqlite.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20240718112146_UserHasUniqueAlias")]
+ partial class UserHasUniqueAlias
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.6");
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("Date")
+ .HasColumnType("TEXT")
+ .HasColumnName("date");
+
+ b.Property("RegistrationDeadline")
+ .HasColumnType("TEXT")
+ .HasColumnName("registration_deadline");
+
+ b.Property("Staged")
+ .HasColumnType("INTEGER")
+ .HasColumnName("staged");
+
+ b.Property("StartedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("started_at");
+
+ b.HasKey("Id");
+
+ b.ToTable("events", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("GroupCode")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasColumnName("group_code");
+
+ b.Property("timeslot_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("timeslot_id");
+
+ b.ToTable("event_instances", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("event_timeslot_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("event_timeslot_id");
+
+ b.ToTable("event_instance_preconfigurations", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("EventId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("event_id");
+
+ b.Property("IsFallbackAllowed")
+ .HasColumnType("INTEGER")
+ .HasColumnName("is_fallback_allowed");
+
+ b.Property("MapId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("map_id");
+
+ b.Property("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MapId");
+
+ b.HasIndex("EventId", "Time")
+ .IsUnique();
+
+ b.ToTable("event_timeslots", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("EventTimeslotId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("event_timeslot_id");
+
+ b.Property("FallbackEventTimeslotId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("fallback_event_timeslot_id");
+
+ b.Property("PlayerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EventTimeslotId");
+
+ b.HasIndex("FallbackEventTimeslotId");
+
+ b.HasIndex("PlayerId");
+
+ b.ToTable("event_timeslot_registration", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("IsActive")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true)
+ .HasColumnName("active");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(150)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("maps", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.RoleEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.ToTable("roles", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = 0,
+ Name = "Player"
+ },
+ new
+ {
+ Id = 1,
+ Name = "Admin"
+ },
+ new
+ {
+ Id = 2,
+ Name = "Developer"
+ });
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("Alias")
+ .HasMaxLength(150)
+ .HasColumnType("TEXT")
+ .HasColumnName("alias");
+
+ b.Property("LoginToken")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT")
+ .HasColumnName("login_token");
+
+ b.Property("SettingsId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("settings_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Alias")
+ .IsUnique();
+
+ b.HasIndex("LoginToken")
+ .IsUnique();
+
+ b.HasIndex("SettingsId");
+
+ b.ToTable("users", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("Auth")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT")
+ .HasColumnName("auth");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("TEXT")
+ .HasColumnName("endpoint");
+
+ b.Property("Lang")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("TEXT")
+ .HasColumnName("lang");
+
+ b.Property("P256DH")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT")
+ .HasColumnName("p256dh");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Endpoint")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_push_subscriptions", (string)null);
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property("EnableNotifications")
+ .HasColumnType("INTEGER")
+ .HasColumnName("enable_notifications");
+
+ b.Property("NotifyOnEventPublish")
+ .HasColumnType("INTEGER")
+ .HasColumnName("notify_on_event_publish");
+
+ b.Property("NotifyOnEventStart")
+ .HasColumnType("INTEGER")
+ .HasColumnName("notify_on_event_start");
+
+ b.Property("NotifyOnEventUpdated")
+ .HasColumnType("INTEGER")
+ .HasColumnName("notify_on_event_updated");
+
+ b.Property("NotifyOnTimeslotStart")
+ .HasColumnType("INTEGER")
+ .HasColumnName("notify_on_timeslot_start");
+
+ b.Property("SecondsToNotifyBeforeTimeslotStart")
+ .HasColumnType("INTEGER")
+ .HasColumnName("seconds_to_notify_before_timeslot_start");
+
+ b.HasKey("Id");
+
+ b.ToTable("user_settings", (string)null);
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.Property("event_instance_id")
+ .HasColumnType("INTEGER");
+
+ b.Property("user_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("event_instance_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("event_instances_to_users");
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.Property("avoided_user_id")
+ .HasColumnType("INTEGER");
+
+ b.Property("user_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("avoided_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_avoided_users");
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.Property("event_instance_preconfiguration_id")
+ .HasColumnType("INTEGER");
+
+ b.Property("user_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("event_instance_preconfiguration_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_event_instance_preconfigurations");
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.Property("preferred_user_id")
+ .HasColumnType("INTEGER");
+
+ b.Property("user_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("preferred_user_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_preferred_users");
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.Property("role_id")
+ .HasColumnType("INTEGER");
+
+ b.Property("user_id")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("role_id", "user_id");
+
+ b.HasIndex("user_id");
+
+ b.ToTable("users_to_roles");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstanceEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Instances")
+ .HasForeignKey("timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeSlot")
+ .WithMany("Preconfigurations")
+ .HasForeignKey("event_timeslot_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeSlot");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventEntity", "Event")
+ .WithMany("Timeslots")
+ .HasForeignKey("EventId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.MinigolfMapEntity", "Map")
+ .WithMany("EventTimeslots")
+ .HasForeignKey("MapId");
+
+ b.Navigation("Event");
+
+ b.Navigation("Map");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotRegistrationEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "EventTimeslot")
+ .WithMany("Registrations")
+ .HasForeignKey("EventTimeslotId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.EventTimeslotEntity", "FallbackEventTimeslot")
+ .WithMany()
+ .HasForeignKey("FallbackEventTimeslotId")
+ .OnDelete(DeleteBehavior.SetNull);
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "Player")
+ .WithMany()
+ .HasForeignKey("PlayerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("EventTimeslot");
+
+ b.Navigation("FallbackEventTimeslot");
+
+ b.Navigation("Player");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserSettingsEntity", "Settings")
+ .WithMany("Users")
+ .HasForeignKey("SettingsId");
+
+ b.Navigation("Settings");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserPushSubscriptionEntity", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", "User")
+ .WithMany("PushSubscriptions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("event_instances_to_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstanceEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_avoided_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("avoided_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_event_instance_preconfigurations", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.EventInstancePreconfigurationEntity", null)
+ .WithMany()
+ .HasForeignKey("event_instance_preconfiguration_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_preferred_users", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("preferred_user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("users_to_roles", b =>
+ {
+ b.HasOne("MinigolfFriday.Data.Entities.RoleEntity", null)
+ .WithMany()
+ .HasForeignKey("role_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("MinigolfFriday.Data.Entities.UserEntity", null)
+ .WithMany()
+ .HasForeignKey("user_id")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventEntity", b =>
+ {
+ b.Navigation("Timeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.EventTimeslotEntity", b =>
+ {
+ b.Navigation("Instances");
+
+ b.Navigation("Preconfigurations");
+
+ b.Navigation("Registrations");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.MinigolfMapEntity", b =>
+ {
+ b.Navigation("EventTimeslots");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserEntity", b =>
+ {
+ b.Navigation("PushSubscriptions");
+ });
+
+ modelBuilder.Entity("MinigolfFriday.Data.Entities.UserSettingsEntity", b =>
+ {
+ b.Navigation("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.cs b/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.cs
new file mode 100644
index 0000000..aebe127
--- /dev/null
+++ b/src/server/migrations/sqlite/Migrations/20240718112146_UserHasUniqueAlias.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace MinigolfFriday.Migrations.Sqlite.Migrations
+{
+ ///
+ public partial class UserHasUniqueAlias : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateIndex(
+ name: "IX_users_alias",
+ table: "users",
+ column: "alias",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_users_alias",
+ table: "users");
+ }
+ }
+}
diff --git a/src/server/migrations/sqlite/Migrations/DatabaseContextModelSnapshot.cs b/src/server/migrations/sqlite/Migrations/DatabaseContextModelSnapshot.cs
index d3e202e..ad159ee 100644
--- a/src/server/migrations/sqlite/Migrations/DatabaseContextModelSnapshot.cs
+++ b/src/server/migrations/sqlite/Migrations/DatabaseContextModelSnapshot.cs
@@ -233,6 +233,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("Alias")
+ .IsUnique();
+
b.HasIndex("LoginToken")
.IsUnique();