Skip to content

Commit af25c1c

Browse files
authored
Feature: support occurrence locations (#1204)
1 parent a743e3f commit af25c1c

267 files changed

Lines changed: 32435 additions & 18051 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ prompts/
2222
/plans
2323

2424
.claude/worktrees/
25+
.claude/scheduled_tasks.lock
26+
tmp_translate/

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ cd docker/development
5858
- **ALWAYS** wrap all translatable strings in `__()` helper
5959
- Domain Objects are auto-generated via `php artisan generate-domain-objects` - never edit manually
6060
- **Always** create unit tests for new features in `backend/tests/Unit/`
61+
- **NEVER leave dead code.** Code that has no production callers — unused methods, unused DTO fields, unused constants, columns that are written but never read, classes only called from tests — must be deleted, not left "for future use". This applies to both backend and frontend. If you add a method speculatively, wire it to a real caller in the same change or remove it. The same rule applies after refactors: if something becomes unreferenced, it goes. Confirm with grep before claiming a method or class is reachable.
6162
- **DON'T** add comments unless absolutely necessary
6263
- **ALWAYS** sanitize user-provided content with `HtmlPurifierService` before storing, especially content rendered as HTML
6364

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Enums;
4+
5+
enum LocationType
6+
{
7+
use BaseEnum;
8+
9+
case IN_PERSON;
10+
case ONLINE;
11+
}

backend/app/DomainObjects/EventDomainObject.php

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use Illuminate\Support\Collection;
1515
use Illuminate\Support\Str;
1616

17-
class EventDomainObject extends Generated\EventDomainObjectAbstract implements IsSortable, IsFilterable
17+
class EventDomainObject extends Generated\EventDomainObjectAbstract implements IsFilterable, IsSortable
1818
{
1919
private ?Collection $products = null;
2020

@@ -44,6 +44,8 @@ class EventDomainObject extends Generated\EventDomainObjectAbstract implements I
4444

4545
private ?AccountDomainObject $account = null;
4646

47+
private ?EventLocationDomainObject $eventLocation = null;
48+
4749
public static function getAllowedFilterFields(): array
4850
{
4951
return [
@@ -96,6 +98,7 @@ public function getProducts(): ?Collection
9698
public function setQuestions(?Collection $questions): EventDomainObject
9799
{
98100
$this->questions = $questions;
101+
99102
return $this;
100103
}
101104

@@ -112,6 +115,7 @@ public function getSlug(): string
112115
public function setImages(?Collection $images): EventDomainObject
113116
{
114117
$this->images = $images;
118+
115119
return $this;
116120
}
117121

@@ -128,6 +132,7 @@ public function getEventSettings(): ?EventSettingDomainObject
128132
public function setEventSettings(?EventSettingDomainObject $settings): EventDomainObject
129133
{
130134
$this->settings = $settings;
135+
131136
return $this;
132137
}
133138

@@ -151,6 +156,7 @@ public function getAccount(): ?AccountDomainObject
151156
public function setAccount(?AccountDomainObject $account): self
152157
{
153158
$this->account = $account;
159+
154160
return $this;
155161
}
156162

@@ -175,6 +181,7 @@ public function getDescriptionPreview(): string
175181
public function setEventOccurrences(?Collection $eventOccurrences): self
176182
{
177183
$this->eventOccurrences = $eventOccurrences;
184+
178185
return $this;
179186
}
180187

@@ -190,7 +197,7 @@ public function getStartDate(): ?string
190197
}
191198

192199
return $this->eventOccurrences->min(
193-
fn(EventOccurrenceDomainObject $o) => $o->getStartDate()
200+
fn (EventOccurrenceDomainObject $o) => $o->getStartDate()
194201
);
195202
}
196203

@@ -201,17 +208,17 @@ public function getEndDate(): ?string
201208
}
202209

203210
$withEndDates = $this->eventOccurrences->filter(
204-
fn(EventOccurrenceDomainObject $o) => $o->getEndDate() !== null
211+
fn (EventOccurrenceDomainObject $o) => $o->getEndDate() !== null
205212
);
206213

207214
if ($withEndDates->isEmpty()) {
208215
return $this->eventOccurrences->max(
209-
fn(EventOccurrenceDomainObject $o) => $o->getStartDate()
216+
fn (EventOccurrenceDomainObject $o) => $o->getStartDate()
210217
);
211218
}
212219

213220
return $withEndDates->max(
214-
fn(EventOccurrenceDomainObject $o) => $o->getEndDate()
221+
fn (EventOccurrenceDomainObject $o) => $o->getEndDate()
215222
);
216223
}
217224

@@ -224,9 +231,9 @@ public function getNextOccurrenceStartDate(): ?string
224231
$now = Carbon::now();
225232

226233
$nextOccurrence = $this->eventOccurrences
227-
->filter(fn(EventOccurrenceDomainObject $o) => $o->getStatus() === EventOccurrenceStatus::ACTIVE->name)
228-
->filter(fn(EventOccurrenceDomainObject $o) => Carbon::parse($o->getStartDate(), 'UTC')->isFuture())
229-
->sortBy(fn(EventOccurrenceDomainObject $o) => $o->getStartDate())
234+
->filter(fn (EventOccurrenceDomainObject $o) => $o->getStatus() === EventOccurrenceStatus::ACTIVE->name)
235+
->filter(fn (EventOccurrenceDomainObject $o) => Carbon::parse($o->getStartDate(), 'UTC')->isFuture())
236+
->sortBy(fn (EventOccurrenceDomainObject $o) => $o->getStartDate())
230237
->first();
231238

232239
return $nextOccurrence?->getStartDate();
@@ -347,12 +354,14 @@ public function getEventStatistics(): ?EventStatisticDomainObject
347354
public function setEventStatistics(?EventStatisticDomainObject $eventStatistics): self
348355
{
349356
$this->eventStatistics = $eventStatistics;
357+
350358
return $this;
351359
}
352360

353361
public function setProductCategories(?Collection $productCategories): EventDomainObject
354362
{
355363
$this->productCategories = $productCategories;
364+
356365
return $this;
357366
}
358367

@@ -369,6 +378,7 @@ public function getWebhooks(): ?Collection
369378
public function setWebhooks(?Collection $webhooks): EventDomainObject
370379
{
371380
$this->webhooks = $webhooks;
381+
372382
return $this;
373383
}
374384

@@ -380,6 +390,19 @@ public function getAffiliates(): ?Collection
380390
public function setAffiliates(?Collection $affiliates): EventDomainObject
381391
{
382392
$this->affiliates = $affiliates;
393+
394+
return $this;
395+
}
396+
397+
public function getEventLocation(): ?EventLocationDomainObject
398+
{
399+
return $this->eventLocation;
400+
}
401+
402+
public function setEventLocation(?EventLocationDomainObject $eventLocation): self
403+
{
404+
$this->eventLocation = $eventLocation;
405+
383406
return $this;
384407
}
385408
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiEvents\DomainObjects;
6+
7+
use HiEvents\DomainObjects\Generated\EventLocationDomainObjectAbstract;
8+
9+
class EventLocationDomainObject extends EventLocationDomainObjectAbstract
10+
{
11+
private ?LocationDomainObject $location = null;
12+
13+
public function getLocation(): ?LocationDomainObject
14+
{
15+
return $this->location;
16+
}
17+
18+
public function setLocation(?LocationDomainObject $location): self
19+
{
20+
$this->location = $location;
21+
22+
return $this;
23+
}
24+
}

backend/app/DomainObjects/EventOccurrenceDomainObject.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use HiEvents\DomainObjects\Status\EventOccurrenceStatus;
1111
use Illuminate\Support\Collection;
1212

13-
class EventOccurrenceDomainObject extends EventOccurrenceDomainObjectAbstract implements IsSortable, IsFilterable
13+
class EventOccurrenceDomainObject extends EventOccurrenceDomainObjectAbstract implements IsFilterable, IsSortable
1414
{
1515
private ?EventDomainObject $event = null;
1616

@@ -24,6 +24,8 @@ class EventOccurrenceDomainObject extends EventOccurrenceDomainObjectAbstract im
2424

2525
private ?EventOccurrenceStatisticDomainObject $eventOccurrenceStatistics = null;
2626

27+
private ?EventLocationDomainObject $eventLocation = null;
28+
2729
public static function getAllowedFilterFields(): array
2830
{
2931
return [
@@ -57,6 +59,7 @@ public static function getDefaultSortDirection(): string
5759
public function setEvent(?EventDomainObject $event): self
5860
{
5961
$this->event = $event;
62+
6063
return $this;
6164
}
6265

@@ -68,6 +71,7 @@ public function getEvent(): ?EventDomainObject
6871
public function setOrderItems(?Collection $orderItems): self
6972
{
7073
$this->orderItems = $orderItems;
74+
7175
return $this;
7276
}
7377

@@ -79,6 +83,7 @@ public function getOrderItems(): ?Collection
7983
public function setAttendees(?Collection $attendees): self
8084
{
8185
$this->attendees = $attendees;
86+
8287
return $this;
8388
}
8489

@@ -90,6 +95,7 @@ public function getAttendees(): ?Collection
9095
public function setCheckInLists(?Collection $checkInLists): self
9196
{
9297
$this->checkInLists = $checkInLists;
98+
9399
return $this;
94100
}
95101

@@ -101,6 +107,7 @@ public function getCheckInLists(): ?Collection
101107
public function setPriceOverrides(?Collection $priceOverrides): self
102108
{
103109
$this->priceOverrides = $priceOverrides;
110+
104111
return $this;
105112
}
106113

@@ -112,6 +119,7 @@ public function getPriceOverrides(): ?Collection
112119
public function setEventOccurrenceStatistics(?EventOccurrenceStatisticDomainObject $statistics): self
113120
{
114121
$this->eventOccurrenceStatistics = $statistics;
122+
115123
return $this;
116124
}
117125

@@ -138,6 +146,7 @@ public function isSoldOut(): bool
138146
public function isPast(): bool
139147
{
140148
$endDate = $this->getEndDate() ?? $this->getStartDate();
149+
141150
return Carbon::parse($endDate, 'UTC')->isPast();
142151
}
143152

@@ -154,4 +163,16 @@ public function getAvailableCapacity(): ?int
154163

155164
return max(0, $this->getCapacity() - $this->getUsedCapacity());
156165
}
166+
167+
public function setEventLocation(?EventLocationDomainObject $eventLocation): self
168+
{
169+
$this->eventLocation = $eventLocation;
170+
171+
return $this;
172+
}
173+
174+
public function getEventLocation(): ?EventLocationDomainObject
175+
{
176+
return $this->eventLocation;
177+
}
157178
}

backend/app/DomainObjects/EventSettingDomainObject.php

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22

33
namespace HiEvents\DomainObjects;
44

5-
use HiEvents\DataTransferObjects\AddressDTO;
6-
use HiEvents\Helper\AddressHelper;
7-
85
class EventSettingDomainObject extends Generated\EventSettingDomainObjectAbstract
96
{
107
/**
11-
* @return string
128
* @todo This should not be here.
139
*/
1410
public function getGetEmailFooterHtml(): string
@@ -23,22 +19,4 @@ public function getGetEmailFooterHtml(): string
2319
</div>
2420
HTML;
2521
}
26-
27-
public function getAddressString(): string
28-
{
29-
return AddressHelper::formatAddress($this->getLocationDetails());
30-
}
31-
32-
public function getAddress(): AddressDTO
33-
{
34-
return new AddressDTO(
35-
venue_name: $this->getLocationDetails()['venue_name'] ?? null,
36-
address_line_1: $this->getLocationDetails()['address_line_1'] ?? null,
37-
address_line_2: $this->getLocationDetails()['address_line_2'] ?? null,
38-
city: $this->getLocationDetails()['city'] ?? null,
39-
state_or_region: $this->getLocationDetails()['state_or_region'] ?? null,
40-
zip_or_postal_code: $this->getLocationDetails()['zip_or_postal_code'] ?? null,
41-
country: $this->getLocationDetails()['country'] ?? null,
42-
);
43-
}
4422
}

backend/app/DomainObjects/Generated/EventDomainObjectAbstract.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ abstract class EventDomainObjectAbstract extends \HiEvents\DomainObjects\Abstrac
1414
final public const ACCOUNT_ID = 'account_id';
1515
final public const USER_ID = 'user_id';
1616
final public const ORGANIZER_ID = 'organizer_id';
17+
final public const EVENT_LOCATION_ID = 'event_location_id';
1718
final public const TITLE = 'title';
1819
final public const DESCRIPTION = 'description';
1920
final public const STATUS = 'status';
20-
final public const LOCATION_DETAILS = 'location_details';
2121
final public const CURRENCY = 'currency';
2222
final public const TIMEZONE = 'timezone';
2323
final public const ATTRIBUTES = 'attributes';
@@ -35,10 +35,10 @@ abstract class EventDomainObjectAbstract extends \HiEvents\DomainObjects\Abstrac
3535
protected int $account_id;
3636
protected int $user_id;
3737
protected ?int $organizer_id = null;
38+
protected ?int $event_location_id = null;
3839
protected string $title;
3940
protected ?string $description = null;
4041
protected ?string $status = null;
41-
protected array|string|null $location_details = null;
4242
protected string $currency = 'USD';
4343
protected ?string $timezone = null;
4444
protected array|string|null $attributes = null;
@@ -59,10 +59,10 @@ public function toArray(): array
5959
'account_id' => $this->account_id ?? null,
6060
'user_id' => $this->user_id ?? null,
6161
'organizer_id' => $this->organizer_id ?? null,
62+
'event_location_id' => $this->event_location_id ?? null,
6263
'title' => $this->title ?? null,
6364
'description' => $this->description ?? null,
6465
'status' => $this->status ?? null,
65-
'location_details' => $this->location_details ?? null,
6666
'currency' => $this->currency ?? null,
6767
'timezone' => $this->timezone ?? null,
6868
'attributes' => $this->attributes ?? null,
@@ -122,6 +122,17 @@ public function getOrganizerId(): ?int
122122
return $this->organizer_id;
123123
}
124124

125+
public function setEventLocationId(?int $event_location_id): self
126+
{
127+
$this->event_location_id = $event_location_id;
128+
return $this;
129+
}
130+
131+
public function getEventLocationId(): ?int
132+
{
133+
return $this->event_location_id;
134+
}
135+
125136
public function setTitle(string $title): self
126137
{
127138
$this->title = $title;
@@ -155,17 +166,6 @@ public function getStatus(): ?string
155166
return $this->status;
156167
}
157168

158-
public function setLocationDetails(array|string|null $location_details): self
159-
{
160-
$this->location_details = $location_details;
161-
return $this;
162-
}
163-
164-
public function getLocationDetails(): array|string|null
165-
{
166-
return $this->location_details;
167-
}
168-
169169
public function setCurrency(string $currency): self
170170
{
171171
$this->currency = $currency;

0 commit comments

Comments
 (0)