Skip to content

Commit

Permalink
feat: allow map to be empty during creation of event (#66)
Browse files Browse the repository at this point in the history
Closes: #9
  • Loading branch information
AnSch1510 authored Jun 15, 2024
1 parent 1e3a40f commit efa4159
Show file tree
Hide file tree
Showing 25 changed files with 3,469 additions and 1,669 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const addEventTimeslotAction = createHttpAction<
{
eventId: string;
time: Time;
mapId: string;
mapId: string | null;
isFallbackAllowed: boolean;
},
EventTimeslot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const updateEventTimeslotAction = createHttpAction<{
eventId: string;
timeslotId: string;
changes: {
mapId?: string;
mapId?: string | null;
isFallbackAllowed?: boolean;
};
}>()(EVENTS_ACTION_SCOPE, 'Update Event Timeslot');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
[label]="translations.events_start()"
(onClick)="startEvent()"
[loading]="isStartBusy()"
[disabled]="!allowToStart()"
/>
}
</div>
Expand Down Expand Up @@ -76,9 +77,11 @@ <h2 class="m-0 grow">{{ translations.events_timeslots() }}</h2>
timeslot.time.minute | number: '2.0-0'
}}</span
>
@if (maps()[timeslot.mapId]; as map) {
<span class="i-[mdi--golf] ml-4"></span>
<span class="truncate">{{ map.name }}</span>
@if (timeslot.mapId) {
@if (maps()[timeslot.mapId]; as map) {
<span class="i-[mdi--golf] ml-4"></span>
<span class="truncate">{{ map.name }}</span>
}
}
<span class="i-[mdi--account] ml-2"></span>
<span>{{ timeslot.playerIds.length }}</span>
Expand Down Expand Up @@ -118,9 +121,11 @@ <h3 class="m-0 flex flex-row items-center gap-2 pb-2" [class.pt-4]="ti > 0">
timeslot.time.minute | number: '2.0-0'
}}
</span>
@if (maps()[timeslot.mapId]; as map) {
<span class="i-[mdi--golf] ml-4"></span>
<span class="truncate">{{ map.name }}</span>
@if (timeslot.mapId) {
@if (maps()[timeslot.mapId]; as map) {
<span class="i-[mdi--golf] ml-4"></span>
<span class="truncate">{{ map.name }}</span>
}
}
</h3>
@for (instance of timeslot.instances; track ii; let ii = $index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export class EventDetailsComponent {
this.canBuildInstances() && this.event() && !this.event()?.startedAt && this.hasInstances()
);

protected readonly allowToStart = computed(
() =>
!this.event()
?.timeslots.filter(x => x.instances.length > 0)
.some(x => x.mapId === null || x.mapId === undefined)
);

constructor() {
this._store.dispatch(loadMapsAction({ reload: false }));
this._store.dispatch(loadUsersAction({ reload: false }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<small [formControlErrors]="form.controls.time.errors"></small>
</span>

<span class="p-float-label required">
<span class="p-float-label">
<p-dropdown
formControlName="mapId"
[options]="maps()"
Expand All @@ -46,6 +46,7 @@
appendTo="body"
[filter]="true"
[filterFields]="['name']"
[showClear]="true"
/>
<label>{{ translations.events_timeslot_mapToPlay() }}</label>
<small [formControlErrors]="form.controls.mapId.errors"></small>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class EventTimeslotDialogComponent {
Validators.required,
this.getTimeValidator(),
]),
mapId: this._formBuilder.control<string | null>(null, [Validators.required]),
mapId: this._formBuilder.control<string | null>(null),
isFallbackAllowed: this._formBuilder.control<boolean>(false, { nonNullable: true }),
});

Expand Down Expand Up @@ -104,7 +104,7 @@ export class EventTimeslotDialogComponent {
untracked(() =>
this.form.setValue({
time: dateWithTime(new Date(), timeslot.time),
mapId: timeslot.mapId,
mapId: timeslot.mapId ?? null,
isFallbackAllowed: timeslot.isFallbackAllowed,
})
);
Expand Down Expand Up @@ -144,24 +144,23 @@ export class EventTimeslotDialogComponent {
const timeslot = this.timeslot();

if (timeslot) {
if (!mapId) return;
this._store.dispatch(
updateEventTimeslotAction({
eventId: event.id,
timeslotId: timeslot.id,
changes: {
mapId,
mapId: mapId ?? null,
isFallbackAllowed: !!isFallbackAllowed,
},
})
);
} else {
if (!time || !mapId) return;
if (!time) return;
this._store.dispatch(
addEventTimeslotAction({
eventId: event.id,
time: getTimeFromDate(time),
mapId,
mapId: mapId ?? null,
isFallbackAllowed: !!isFallbackAllowed,
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@
>
</span>
<span>{{ event.registrationDeadline | date: 'medium' : undefined : locale() }}</span>
@if (maps()[timeslot.mapId]; as map) {
<span>
<span class="i-[mdi--golf] mr-2"></span>
<span class="truncate font-semibold">{{
translations.events_timeslot_map()
}}</span>
</span>
<span class="truncate">{{ map.name }}</span>
@if (timeslot.mapId) {
@if (maps()[timeslot.mapId]; as map) {
<span>
<span class="i-[mdi--golf] mr-2"></span>
<span class="truncate font-semibold">{{
translations.events_timeslot_map()
}}</span>
</span>
<span class="truncate">{{ map.name }}</span>
}
}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/server/data/Entities/EventTimeslotEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public class EventTimeslotEntity
public required TimeOnly Time { get; set; }
public bool IsFallbackAllowed { get; set; }
public long EventId { get; set; }
public long MapId { get; set; }
public long? MapId { get; set; }

public EventEntity Event { get; set; } = null!;
public MinigolfMapEntity Map { get; set; } = null!;
public MinigolfMapEntity? Map { get; set; } = null;

public List<EventInstancePreconfigurationEntity> Preconfigurations { get; set; } = [];
public List<EventInstanceEntity> Instances { get; set; } = [];
Expand All @@ -25,7 +25,7 @@ public static void Configure(EntityTypeBuilder<EventTimeslotEntity> builder)
builder.Property(x => x.Id).HasColumnName("id").IsRequired().ValueGeneratedOnAdd();
builder.Property(x => x.Time).HasColumnName("time").IsRequired();
builder.Property(x => x.EventId).HasColumnName("event_id").IsRequired();
builder.Property(x => x.MapId).HasColumnName("map_id").IsRequired();
builder.Property(x => x.MapId).HasColumnName("map_id");
builder
.Property(x => x.IsFallbackAllowed)
.HasColumnName("is_fallback_allowed")
Expand Down
2 changes: 1 addition & 1 deletion src/server/domain/Models/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public record Event(
public record EventTimeslot(
[property: Required] string Id,
[property: Required] TimeOnly Time,
[property: Required] string MapId,
string? MapId,
[property: Required] bool IsFallbackAllowed,
[property: Required] EventInstancePreconfiguration[] Preconfigurations,
[property: Required] string[] PlayerIds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public override void Configure()
EndpointErrors.EventNotFound,
EndpointErrors.EventRegistrationNotElapsed,
EndpointErrors.EventHasNoInstances,
EndpointErrors.EventAlreadyStarted
EndpointErrors.EventAlreadyStarted,
EndpointErrors.EventMissingMapOnStart
);
}

Expand All @@ -50,7 +51,10 @@ public override async Task HandleAsync(StartEventRequest req, CancellationToken
{
x.RegistrationDeadline,
HasInstances = x.Timeslots.Any(t => t.Instances.Any()),
IsStarted = x.StartedAt != null
IsStarted = x.StartedAt != null,
HasMissingMap = x
.Timeslots.Where(t => t.Instances.Count > 0)
.Any(x => x.MapId == null)
})
.FirstOrDefaultAsync(ct);

Expand Down Expand Up @@ -91,6 +95,13 @@ await this.SendErrorAsync(
return;
}

if (info.HasMissingMap)
{
Logger.LogWarning(EndpointErrors.EventMissingMapOnStart, eventId);
await this.SendErrorAsync(EndpointErrors.EventMissingMapOnStart, req.EventId, ct);
return;
}

var now = DateTimeOffset.Now;
await databaseContext
.Events.Where(x => x.Id == eventId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace MinigolfFriday.Host.Endpoints.Administration.Events.Timeslots;
public record CreateEventTimeslotRequest(
[property: Required] string EventId,
[property: Required] TimeOnly Time,
[property: Required] string MapId,
string? MapId,
[property: Required] bool IsFallbackAllowed
);

Expand All @@ -32,7 +32,7 @@ public CreateEventTimeslotRequestValidator(IIdService idService)
{
RuleFor(x => x.EventId).NotEmpty().ValidSqid(idService.Event);
RuleFor(x => x.Time).NotEmpty();
RuleFor(x => x.MapId).NotEmpty().ValidSqid(idService.Map);
When(x => x.MapId != null, () => RuleFor(x => x.MapId!).ValidSqid(idService.Map));
}
}

Expand Down Expand Up @@ -72,22 +72,27 @@ public override async Task HandleAsync(CreateEventTimeslotRequest req, Cancellat
await this.SendErrorAsync(EndpointErrors.EventAlreadyStarted, req.EventId, ct);
return;
}

var mapId = idService.Map.DecodeSingle(req.MapId);
var mapExists = await databaseContext.Maps.AnyAsync(x => x.Id == mapId, ct);

if (!mapExists)
long? mapId = null;
if (req.MapId != null)
{
Logger.LogWarning(EndpointErrors.MapNotFound, mapId);
ValidationFailures.Add(new ValidationFailure(nameof(req.MapId), "Map does not exist."));
mapId = idService.Map.DecodeSingle(req.MapId);
var mapExists = await databaseContext.Maps.AnyAsync(x => x.Id == mapId, ct);

if (!mapExists)
{
Logger.LogWarning(EndpointErrors.MapNotFound, mapId);
ValidationFailures.Add(
new ValidationFailure(nameof(req.MapId), "Map does not exist.")
);
}
}

ThrowIfAnyErrors();

var timeslot = new EventTimeslotEntity
{
Time = req.Time,
Map = databaseContext.MapById(mapId),
Map = mapId != null ? databaseContext.MapById(mapId.Value) : null,
Event = databaseContext.EventById(eventId),
IsFallbackAllowed = req.IsFallbackAllowed,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ await this.SendErrorAsync(
);
}
}
else
{
updateBuilder.With(x => x.SetProperty(x => x.MapId, (long?)null));
}

if (req.IsFallbackAllowed != null)
updateBuilder.With(x => x.SetProperty(x => x.IsFallbackAllowed, req.IsFallbackAllowed));
Expand Down
2 changes: 2 additions & 0 deletions src/server/host/Endpoints/EndpointErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class EndpointErrors
new(404, "An event with the id {0} does not exist.", "EventId");
public static readonly EndpointError.Params1 EventAlreadyStarted =
new(409, "The event with id {0} has already been started.", "EventId");
public static readonly EndpointError.Params1 EventMissingMapOnStart =
new(409, "The event with id {0} has timeslots with players and no map.", "EventId");
public static readonly EndpointError.Params2 EventRegistrationNotElapsed =
new(
409,
Expand Down
2 changes: 1 addition & 1 deletion src/server/host/Mappers/EventMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public EventTimeslot Map(EventTimeslotEntity entity)
return new EventTimeslot(
idService.EventTimeslot.Encode(entity.Id),
entity.Time,
idService.Map.Encode(entity.MapId),
entity.MapId != null ? idService.Map.Encode(entity.MapId.Value) : null,
entity.IsFallbackAllowed,
entity.Preconfigurations.Select(Map).ToArray(),
entity
Expand Down
2 changes: 1 addition & 1 deletion src/server/host/Mappers/PlayerEventMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public PlayerEventInstance Map(EventInstanceEntity entity)
return new(
idService.EventInstance.Encode(entity.Id),
entity.GroupCode,
Map(entity.EventTimeslot.Map),
Map(entity.EventTimeslot.Map!),
entity.Players.Count
);
}
Expand Down
Loading

0 comments on commit efa4159

Please sign in to comment.