Skip to content

Commit ebe67e2

Browse files
authored
Feature: Admin UTM tracking (#963)
1 parent abe88cd commit ebe67e2

Some content is hidden

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

52 files changed

+7146
-4457
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects;
4+
5+
class AccountAttributionDomainObject extends Generated\AccountAttributionDomainObjectAbstract
6+
{
7+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
<?php
2+
3+
namespace HiEvents\DomainObjects\Generated;
4+
5+
/**
6+
* THIS FILE IS AUTOGENERATED - DO NOT EDIT IT DIRECTLY.
7+
* @package HiEvents\DomainObjects\Generated
8+
*/
9+
abstract class AccountAttributionDomainObjectAbstract extends \HiEvents\DomainObjects\AbstractDomainObject
10+
{
11+
final public const SINGULAR_NAME = 'account_attribution';
12+
final public const PLURAL_NAME = 'account_attributions';
13+
final public const ID = 'id';
14+
final public const ACCOUNT_ID = 'account_id';
15+
final public const UTM_SOURCE = 'utm_source';
16+
final public const UTM_MEDIUM = 'utm_medium';
17+
final public const UTM_CAMPAIGN = 'utm_campaign';
18+
final public const UTM_TERM = 'utm_term';
19+
final public const UTM_CONTENT = 'utm_content';
20+
final public const REFERRER_URL = 'referrer_url';
21+
final public const LANDING_PAGE = 'landing_page';
22+
final public const GCLID = 'gclid';
23+
final public const FBCLID = 'fbclid';
24+
final public const SOURCE_TYPE = 'source_type';
25+
final public const UTM_RAW = 'utm_raw';
26+
final public const CREATED_AT = 'created_at';
27+
final public const UPDATED_AT = 'updated_at';
28+
29+
protected int $id;
30+
protected int $account_id;
31+
protected ?string $utm_source = null;
32+
protected ?string $utm_medium = null;
33+
protected ?string $utm_campaign = null;
34+
protected ?string $utm_term = null;
35+
protected ?string $utm_content = null;
36+
protected ?string $referrer_url = null;
37+
protected ?string $landing_page = null;
38+
protected ?string $gclid = null;
39+
protected ?string $fbclid = null;
40+
protected ?string $source_type = null;
41+
protected array|string|null $utm_raw = null;
42+
protected ?string $created_at = null;
43+
protected ?string $updated_at = null;
44+
45+
public function toArray(): array
46+
{
47+
return [
48+
'id' => $this->id ?? null,
49+
'account_id' => $this->account_id ?? null,
50+
'utm_source' => $this->utm_source ?? null,
51+
'utm_medium' => $this->utm_medium ?? null,
52+
'utm_campaign' => $this->utm_campaign ?? null,
53+
'utm_term' => $this->utm_term ?? null,
54+
'utm_content' => $this->utm_content ?? null,
55+
'referrer_url' => $this->referrer_url ?? null,
56+
'landing_page' => $this->landing_page ?? null,
57+
'gclid' => $this->gclid ?? null,
58+
'fbclid' => $this->fbclid ?? null,
59+
'source_type' => $this->source_type ?? null,
60+
'utm_raw' => $this->utm_raw ?? null,
61+
'created_at' => $this->created_at ?? null,
62+
'updated_at' => $this->updated_at ?? null,
63+
];
64+
}
65+
66+
public function setId(int $id): self
67+
{
68+
$this->id = $id;
69+
return $this;
70+
}
71+
72+
public function getId(): int
73+
{
74+
return $this->id;
75+
}
76+
77+
public function setAccountId(int $account_id): self
78+
{
79+
$this->account_id = $account_id;
80+
return $this;
81+
}
82+
83+
public function getAccountId(): int
84+
{
85+
return $this->account_id;
86+
}
87+
88+
public function setUtmSource(?string $utm_source): self
89+
{
90+
$this->utm_source = $utm_source;
91+
return $this;
92+
}
93+
94+
public function getUtmSource(): ?string
95+
{
96+
return $this->utm_source;
97+
}
98+
99+
public function setUtmMedium(?string $utm_medium): self
100+
{
101+
$this->utm_medium = $utm_medium;
102+
return $this;
103+
}
104+
105+
public function getUtmMedium(): ?string
106+
{
107+
return $this->utm_medium;
108+
}
109+
110+
public function setUtmCampaign(?string $utm_campaign): self
111+
{
112+
$this->utm_campaign = $utm_campaign;
113+
return $this;
114+
}
115+
116+
public function getUtmCampaign(): ?string
117+
{
118+
return $this->utm_campaign;
119+
}
120+
121+
public function setUtmTerm(?string $utm_term): self
122+
{
123+
$this->utm_term = $utm_term;
124+
return $this;
125+
}
126+
127+
public function getUtmTerm(): ?string
128+
{
129+
return $this->utm_term;
130+
}
131+
132+
public function setUtmContent(?string $utm_content): self
133+
{
134+
$this->utm_content = $utm_content;
135+
return $this;
136+
}
137+
138+
public function getUtmContent(): ?string
139+
{
140+
return $this->utm_content;
141+
}
142+
143+
public function setReferrerUrl(?string $referrer_url): self
144+
{
145+
$this->referrer_url = $referrer_url;
146+
return $this;
147+
}
148+
149+
public function getReferrerUrl(): ?string
150+
{
151+
return $this->referrer_url;
152+
}
153+
154+
public function setLandingPage(?string $landing_page): self
155+
{
156+
$this->landing_page = $landing_page;
157+
return $this;
158+
}
159+
160+
public function getLandingPage(): ?string
161+
{
162+
return $this->landing_page;
163+
}
164+
165+
public function setGclid(?string $gclid): self
166+
{
167+
$this->gclid = $gclid;
168+
return $this;
169+
}
170+
171+
public function getGclid(): ?string
172+
{
173+
return $this->gclid;
174+
}
175+
176+
public function setFbclid(?string $fbclid): self
177+
{
178+
$this->fbclid = $fbclid;
179+
return $this;
180+
}
181+
182+
public function getFbclid(): ?string
183+
{
184+
return $this->fbclid;
185+
}
186+
187+
public function setSourceType(?string $source_type): self
188+
{
189+
$this->source_type = $source_type;
190+
return $this;
191+
}
192+
193+
public function getSourceType(): ?string
194+
{
195+
return $this->source_type;
196+
}
197+
198+
public function setUtmRaw(array|string|null $utm_raw): self
199+
{
200+
$this->utm_raw = $utm_raw;
201+
return $this;
202+
}
203+
204+
public function getUtmRaw(): array|string|null
205+
{
206+
return $this->utm_raw;
207+
}
208+
209+
public function setCreatedAt(?string $created_at): self
210+
{
211+
$this->created_at = $created_at;
212+
return $this;
213+
}
214+
215+
public function getCreatedAt(): ?string
216+
{
217+
return $this->created_at;
218+
}
219+
220+
public function setUpdatedAt(?string $updated_at): self
221+
{
222+
$this->updated_at = $updated_at;
223+
return $this;
224+
}
225+
226+
public function getUpdatedAt(): ?string
227+
{
228+
return $this->updated_at;
229+
}
230+
}

backend/app/Http/Actions/Accounts/CreateAccountAction.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ public function __invoke(CreateAccountRequest $request): JsonResponse
5151
: $this->localeService->getLocaleOrDefault($request->getPreferredLanguage()),
5252
'invite_token' => $request->validated('invite_token'),
5353
'marketing_opt_in' => (bool) $request->validated('marketing_opt_in'),
54+
'utm_source' => $request->validated('utm_source'),
55+
'utm_medium' => $request->validated('utm_medium'),
56+
'utm_campaign' => $request->validated('utm_campaign'),
57+
'utm_term' => $request->validated('utm_term'),
58+
'utm_content' => $request->validated('utm_content'),
59+
'referrer_url' => $request->validated('referrer_url'),
60+
'landing_page' => $request->validated('landing_page'),
61+
'gclid' => $request->validated('gclid'),
62+
'fbclid' => $request->validated('fbclid'),
63+
'utm_raw' => $request->validated('utm_raw'),
5464
]));
5565
} catch (EmailAlreadyExists $e) {
5666
throw ValidationException::withMessages([
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiEvents\Http\Actions\Admin\Attribution;
6+
7+
use HiEvents\DomainObjects\Enums\Role;
8+
use HiEvents\Http\Actions\BaseAction;
9+
use HiEvents\Services\Application\Handlers\Admin\DTO\GetUtmAttributionStatsDTO;
10+
use HiEvents\Services\Application\Handlers\Admin\GetUtmAttributionStatsHandler;
11+
use Illuminate\Http\JsonResponse;
12+
use Illuminate\Http\Request;
13+
14+
class GetUtmAttributionStatsAction extends BaseAction
15+
{
16+
public function __construct(
17+
private readonly GetUtmAttributionStatsHandler $handler,
18+
)
19+
{
20+
}
21+
22+
public function __invoke(Request $request): JsonResponse
23+
{
24+
$this->minimumAllowedRole(Role::SUPERADMIN);
25+
26+
$dto = GetUtmAttributionStatsDTO::from([
27+
'group_by' => $request->query('group_by', 'source'),
28+
'date_from' => $request->query('date_from'),
29+
'date_to' => $request->query('date_to'),
30+
'per_page' => (int) $request->query('per_page', 20),
31+
'page' => (int) $request->query('page', 1),
32+
]);
33+
34+
$result = $this->handler->handle($dto);
35+
36+
return $this->jsonResponse($result);
37+
}
38+
}

backend/app/Http/Request/Account/CreateAccountRequest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ public function rules(): array
2626
'locale' => ['nullable', Rule::in(Locale::getSupportedLocales())],
2727
'invite_token' => ['nullable', 'string'],
2828
'marketing_opt_in' => 'boolean|nullable',
29+
// UTM attribution fields
30+
'utm_source' => ['nullable', 'string', 'max:255'],
31+
'utm_medium' => ['nullable', 'string', 'max:255'],
32+
'utm_campaign' => ['nullable', 'string', 'max:255'],
33+
'utm_term' => ['nullable', 'string', 'max:255'],
34+
'utm_content' => ['nullable', 'string', 'max:255'],
35+
'referrer_url' => ['nullable', 'string', 'max:2048'],
36+
'landing_page' => ['nullable', 'string', 'max:2048'],
37+
'gclid' => ['nullable', 'string', 'max:255'],
38+
'fbclid' => ['nullable', 'string', 'max:255'],
39+
'utm_raw' => ['nullable', 'array'],
2940
];
3041
}
3142
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiEvents\Models;
6+
7+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
8+
9+
class AccountAttribution extends BaseModel
10+
{
11+
public function account(): BelongsTo
12+
{
13+
return $this->belongsTo(Account::class);
14+
}
15+
16+
protected function getCastMap(): array
17+
{
18+
return [
19+
'utm_raw' => 'array',
20+
];
21+
}
22+
}

backend/app/Providers/RepositoryServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace HiEvents\Providers;
66

7+
use HiEvents\Repository\Eloquent\AccountAttributionRepository;
78
use HiEvents\Repository\Eloquent\AccountConfigurationRepository;
89
use HiEvents\Repository\Eloquent\AccountRepository;
910
use HiEvents\Repository\Eloquent\AccountStripePlatformRepository;
@@ -47,6 +48,7 @@
4748
use HiEvents\Repository\Eloquent\UserRepository;
4849
use HiEvents\Repository\Eloquent\WebhookLogRepository;
4950
use HiEvents\Repository\Eloquent\WebhookRepository;
51+
use HiEvents\Repository\Interfaces\AccountAttributionRepositoryInterface;
5052
use HiEvents\Repository\Interfaces\AccountConfigurationRepositoryInterface;
5153
use HiEvents\Repository\Interfaces\AccountRepositoryInterface;
5254
use HiEvents\Repository\Interfaces\AccountStripePlatformRepositoryInterface;
@@ -100,6 +102,7 @@ class RepositoryServiceProvider extends ServiceProvider
100102
private static array $interfaceToConcreteMap = [
101103
UserRepositoryInterface::class => UserRepository::class,
102104
AccountRepositoryInterface::class => AccountRepository::class,
105+
AccountAttributionRepositoryInterface::class => AccountAttributionRepository::class,
103106
EventRepositoryInterface::class => EventRepository::class,
104107
ProductRepositoryInterface::class => ProductRepository::class,
105108
OrderRepositoryInterface::class => OrderRepository::class,

0 commit comments

Comments
 (0)