Skip to content

Commit 5088f9d

Browse files
done
1 parent 76cf8a8 commit 5088f9d

13 files changed

Lines changed: 208 additions & 178 deletions

File tree

app/Http/Resources/StoreResource.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,22 @@ public function tiny ( $data ) {
1111
'name' => $data->name,
1212
'phone' => $data->phone,
1313
'email' => $data->email,
14-
'host' => $data->zone?->name ?? $data->domains?->firstWhere('dest', 'client'),
14+
'host' => $data->zone?->name ?? $data->domains?->firstWhere('dest', 'client')?->name,
1515
]);
1616

1717
}
1818
public function relations () {
1919

2020
return $this->setTenant($this->id, callback: function () {
2121

22-
$admin = $this->storeAdmin($this->id);
23-
$owner = $this->storeOwner($this->id);
24-
$zone = $this->zone;
25-
$domains = $this->domains;
26-
2722
return [
2823
'subscription' => SubscriptionResource::info( $this->subscription ),
2924
'plan' => PlanResource::info( $this->plan ),
3025
'parent' => StoreResource::info( $this->parent ),
31-
'owner' => UserResource::info( $owner ),
32-
'admin' => UserResource::info( $admin ),
33-
'zone' => ZoneResource::info( $zone ),
34-
'domains' => DomainResource::collectionInfo( $domains ),
35-
...[
36-
'admin_name' => $admin?->name,
37-
'admin_phone' => $admin?->phone,
38-
'admin_email' => $admin?->email,
39-
'domain_name' => $zone?->name,
40-
'domain_ns1' => $zone?->ns1,
41-
'domain_ns2' => $zone?->ns2,
42-
...($domains?->mapWithKeys(fn($item) => ["{$item->dest}_domain_name" => $item->name]) ?? []),
43-
],
26+
'owner' => UserResource::info( $this->storeOwner($this->id) ),
27+
'admin' => UserResource::info( $this->storeAdmin($this->id) ),
28+
'domains' => DomainResource::collectionInfo( $this->domains ),
29+
'zone' => ZoneResource::info( $this->zone ?? $this->setTenant(callback: fn() => $this->domains?->firstWhere('dest', 'client')?->zone) ),
4430
];
4531

4632
});

app/Models/Domain.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@ class Domain extends Model {
99

1010
use HasBaseModel;
1111

12-
protected $disabledTraits = ['tenancy'];
13-
1412
public function scopeVerified ( Builder $query ) { return $query->active()->where('status', 'verified'); }
1513

1614
public function isVerified () { return $this->status === 'verified'; }
17-
1815
public function setVerified () { return $this->update(['status' => 'verified']); }
1916
public function setFailed () { return $this->update(['status' => 'failed']); }
2017
public function setPending () { return $this->update(['status' => 'pending']); }

app/Models/Zone.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@ class Zone extends Model {
99

1010
use HasBaseModel;
1111

12-
protected $disabledTraits = ['tenancy'];
13-
1412
public function scopeVerified ( Builder $query ) { return $query->active()->where('status', 'verified'); }
1513

1614
public function isVerified () { return $this->active && $this->status === 'verified'; }
1715
public function isValid () { return $this->isVerified() && $this->type === 'internal'; }
18-
1916
public function setVerified () { return $this->update(['status' => 'verified']); }
2017
public function setFailed () { return $this->update(['status' => 'failed']); }
2118
public function setPending () { return $this->update(['status' => 'pending']); }

app/Services/CloudflareService.php

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function deleteDomain ( string $name ) {
7979

8080
try{
8181

82-
$id = $this->getDomain($name)['id'] ?? null;
82+
$id = preg_match('/^[a-f0-9]{32}$/', $name) === 1 ? $name : ($this->getDomain($name)['id'] ?? null);
8383
if ( !$id ) return;
8484

8585
$response = $this->client->delete("/client/v4/zones/{$id}");
@@ -104,15 +104,16 @@ public function domainExists ( string $name ) {
104104
return $this->getDomain($name) !== null;
105105

106106
}
107-
public function domainAvailable ( string $name ) {
107+
public function validateDomain ( string $name ) {
108+
109+
$name = strtolower(trim($name));
108110

109111
$params = [
110-
'required', 'string', 'min:3', 'max:63',
111-
'regex:/^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?$/',
112+
'required', 'string', 'min:3', 'max:253',
113+
'regex:/^(?!-)([a-zA-Z0-9-]{1,63})(?<!-)(\.[a-zA-Z]{2,})+$/',
112114
];
113-
114-
$validator = Validator::make(['domain' => $name], ['domain' => $params]);
115-
return $validator->passes() && !$this->domainExists($name);
115+
116+
return Validator::make(['domain' => $name], ['domain' => $params])->passes() ? $name : null;
116117

117118
}
118119

@@ -132,20 +133,42 @@ public function addSubDomain ( string $name, string $type = 'cname' ) {
132133
try{
133134

134135
$content = strtoupper($type) === 'CNAME' ? config('settings.default.vercel.cname') : config('settings.default.vercel.ip');
135-
$default = ['name' => $name, 'type' => strtoupper($type), 'content' => $content];
136+
$data = ['name' => $name, 'type' => strtoupper($type), 'content' => $content];
136137

137-
$response = $this->client->post("/client/v4/zones/{$this->zoneId}/dns_records", ['json' => $default]);
138-
$response = json_decode($response->getBody(), true);
139-
140-
return $response['result'] ?? null;
138+
$response = $this->client->post("/client/v4/zones/{$this->zoneId}/dns_records", ['json' => $data]);
139+
return json_decode($response->getBody(), true)['result'] ?? null;
140+
141+
} catch ( \Exception $e ) {}
142+
143+
}
144+
public function syncSubDomain ( string $name, string $type = 'cname' ) {
145+
146+
try {
147+
148+
$content = strtoupper($type) === 'CNAME' ? config('settings.default.vercel.cname') : config('settings.default.vercel.ip');
149+
$data = ['name' => $name, 'type' => strtoupper($type), 'content' => $content];
150+
151+
$id = $this->getSubDomain($name)['id'] ?? null;
152+
if ( !$id ) return $this->addSubDomain($name, $type);
153+
154+
$response = $this->client->put("/client/v4/zones/{$this->zoneId}/dns_records/{$id}", ['json' => $data]);
155+
return json_decode($response->getBody(), true)['result'] ?? null;
141156

142157
} catch ( \Exception $e ) {}
143158

144159
}
145160
public function getSubDomain ( string $name ) {
161+
162+
try {
163+
164+
$baseName = $this->findDomain($this->zoneId)['name'] ?? null;
165+
$fullName = str_contains($name, $baseName) ? $name : "{$name}.{$baseName}";
166+
167+
$response = $this->client->get("/client/v4/zones/{$this->zoneId}/dns_records", ['query' => ['name' => $fullName]]);
168+
return json_decode($response->getBody(), true)['result'][0] ?? null;
169+
170+
} catch ( \Exception $e ) {}
146171

147-
return collect($this->allSubDomains())->filter(fn($domain) => str_starts_with($domain['name'], "{$name}."))->first() ?? null;
148-
149172
}
150173
public function findSubDomain ( string $id ) {
151174

@@ -162,7 +185,7 @@ public function deleteSubDomain ( string $name ) {
162185

163186
try{
164187

165-
$id = $this->getSubDomain($name)['id'] ?? null;
188+
$id = preg_match('/^[a-f0-9]{32}$/', $name) === 1 ? $name : ($this->getSubDomain($name)['id'] ?? null);
166189
if ( !$id ) return;
167190

168191
$response = $this->client->delete("/client/v4/zones/{$this->zoneId}/dns_records/{$id}");
@@ -177,16 +200,17 @@ public function subDomainExists ( string $name ) {
177200
return $this->getSubDomain( $name ) !== null;
178201

179202
}
180-
public function subDomainAvailable ( string $name ) {
203+
public function validateSubDomain ( string $name ) {
204+
205+
$name = strtolower(trim($name));
181206

182207
$params = [
183-
'required', 'string', 'min:3', 'max:63',
184-
'regex:/^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$/',
208+
'required', 'string', 'min:1', 'max:63',
209+
'regex:/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/',
185210
'not_in:http,https',
186211
];
187212

188-
$validator = Validator::make(['domain' => $name], ['domain' => $params]);
189-
return $validator->passes() && !$this->subDomainExists($name);
213+
return Validator::make(['domain' => $name], ['domain' => $params])->passes() ? $name : null;
190214

191215
}
192216

app/Services/DomainService.php

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace App\Services;
44
use App\Repositories\DomainRepository;
5-
use App\Models\Store;
5+
use App\Models\Domain;
66
use App\Models\Zone;
77

88
class DomainService extends BaseService {
@@ -14,79 +14,89 @@ public function __construct(
1414
protected VercelService $vercelService,
1515
) { parent::__construct($domainRepository); }
1616

17-
public function resolveDomains ( array $data = [] ) {
17+
public function resolve ( array $data = [] ) {
1818

1919
return collect([
20-
['dest' => 'client', 'name' => $client = data_get($data, 'client_domain_name', 'www')],
21-
['dest' => 'admin', 'name' => data_get($data, 'admin_domain_name', $client === 'www' ? 'admin' : null)],
22-
['dest' => 'vendor', 'name' => data_get($data, 'vendor_domain_name')],
23-
['dest' => 'delivery', 'name' => data_get($data, 'delivery_domain_name')],
24-
['dest' => 'blog', 'name' => data_get($data, 'blog_domain_name')],
25-
['dest' => 'app', 'name' => data_get($data, 'app_domain_name')],
26-
['dest' => 'api', 'name' => data_get($data, 'api_domain_name')],
27-
['dest' => 'cdn', 'name' => data_get($data, 'cdn_domain_name')],
20+
['dest' => 'client', 'name' => $client = data_get($data, 'client_domain_name', 'www')],
21+
['dest' => 'admin', 'name' => data_get($data, 'admin_domain_name', $client === 'www' ? 'admin' : null)],
22+
['dest' => 'vendor', 'name' => data_get($data, 'vendor_domain_name')],
23+
['dest' => 'delivery', 'name' => data_get($data, 'delivery_domain_name')],
24+
['dest' => 'affiliate', 'name' => data_get($data, 'affiliate_domain_name')],
25+
['dest' => 'blog', 'name' => data_get($data, 'blog_domain_name')],
26+
['dest' => 'app', 'name' => data_get($data, 'app_domain_name')],
27+
['dest' => 'api', 'name' => data_get($data, 'api_domain_name')],
28+
['dest' => 'cdn', 'name' => data_get($data, 'cdn_domain_name')],
2829
])->filter(fn($item) => $item['name'])->all();
2930

3031
}
31-
public function validate ( Store $store, Zone $zone, string $name ) {
32+
public function sync ( Domain $domain ) {
3233

33-
$name = trim(explode('.', $name)[0]);
34+
$this->domainRepository->query()->where('dest', $domain->dest)->where('name', '!=', $domain->name)->get()->each(function ($item) {
3435

35-
$record = $this->domainRepository->findByName("{$name}.{$zone->name}");
36-
if ( $record ) return $record->store_id === $store->id ? $name : null;
36+
$this->vercelService->setProject($item->dest)->deleteDomain($item->name);
37+
$this->cloudflareService->deleteSubDomain($item->provider_id ?? $item->name);
38+
$this->delete($item->id);
3739

38-
$isValid = $this->cloudflareService->setZone($zone->provider_id)->subDomainAvailable($name);
39-
return $isValid ? $name : null;
40+
});
41+
42+
$this->deleteCache();
4043

4144
}
42-
public function attach ( Store $store, Zone $zone, string $name, string $dest ) {
45+
public function validate ( Zone $zone, string $name ) {
4346

44-
if ( !$name = $this->validate($store, $zone, $name) ) return [null, null];
47+
$name = explode('.', strtolower(trim($name)))[0];
48+
$full = "{$name}.{$zone->name}";
4549

46-
$domain = $this->cloudflareService->setZone($zone->provider_id)->addSubDomain($name);
47-
if ( !$domain ) $domain = $this->cloudflareService->setZone($zone->provider_id)->getDomain($name);
50+
if ( $domain = $this->domainRepository->findByName($full) ) return $domain;
51+
if ( $this->withoutTenant(fn() => $this->domainRepository->findByName($full)) ) return null;
52+
53+
return $this->cloudflareService->validateSubDomain($name);
4854

55+
}
56+
public function attach ( Zone $zone, string $name, string $dest ) {
57+
58+
$cloudflare = $this->cloudflareService->setZone($zone->provider_id);
59+
$domain = $zone->store_id === store_id() ? $cloudflare->syncSubDomain($name) : $cloudflare->addSubDomain($name);
60+
4961
[$id, $name] = [data_get($domain, 'id'), data_get($domain, 'name')];
50-
51-
$this->vercelService->setProject($dest)->addDomain($name);
62+
if ( $name ) $this->vercelService->setProject($dest)->addDomain($name);
63+
5264
return [$id, $name];
5365

5466
}
55-
public function register ( Store $store, Zone $zone, string $name, string $dest ) {
67+
public function register ( Zone $zone, string $name, string $dest ) {
5668

57-
[$id, $name] = $this->attach($store, $zone, $name, $dest);
58-
if ( !$id || !$name ) return;
69+
$name = $this->validate($zone, $name);
70+
if ( !$name || $name instanceof Domain ) return $name;
5971

60-
return $this->domainRepository->updateOrCreate(
61-
['zone_id' => $zone->id, 'name' => $name],
62-
['store_id' => $store->id, 'provider_id' => $id, 'dest' => $dest]
72+
[$id, $name] = $this->attach($zone, $name, $dest);
73+
if ( !$id || !$name ) return null;
74+
75+
$domain = $this->domainRepository->updateOrCreate(
76+
['name' => $name, 'dest' => $dest],
77+
['zone_id' => $zone->id, 'provider_id' => $id]
6378
);
79+
80+
$this->runJob([static::class, 'sync'], [$domain]);
81+
return $domain;
6482

6583
}
66-
public function check ( Store $store, array $data = [] ) {
84+
public function check ( string $domain = null, array $data = [] ) {
6785

68-
$domain = string(data_get($data, 'domain_name'));
69-
if ( $domain ) return $this->zoneService->check($store, $domain);
86+
if ( $domain && store()?->zone?->name !== $domain ) return (bool) $this->zoneService->validate($domain);
87+
if ( !$zone = $this->zoneService->currentZone() ) return false;
7088

71-
$zone = $this->zoneService->parentZone($store);
72-
if ( !$zone ) return false;
73-
74-
return collect($this->resolveDomains($data))->every(fn($item) =>
75-
$this->validate($store, $zone, $item['name'])
76-
);
89+
return collect($this->resolve($data))->every(fn($item) => $this->validate($zone, $item['name']));
7790

7891
}
79-
public function apply ( Store $store, array $data = [] ) {
92+
public function apply ( array $data = [] ) {
8093

81-
if ( !$this->check($store, $data) ) return;
82-
if ( !$zone = $this->zoneService->apply($store, string(data_get($data, 'domain_name'))) ) return;
83-
84-
return collect($this->resolveDomains($data))->map(function($item) use ($store, $zone) {
94+
$domain = string(data_get($data, 'domain_name'));
8595

86-
[$name, $dest] = [string(data_get($item, 'name')), string(data_get($item, 'dest'))];
87-
if ( $name && $dest ) return $this->register($store, $zone, $name, $dest);
96+
if ( !$this->check($domain, $data) ) return false;
97+
if ( !$zone = $this->zoneService->apply($domain) ) return false;
8898

89-
})->filter()->all();
99+
return collect($this->resolve($data))->every(fn($item) => $this->register($zone, $item['name'], $item['dest']));
90100

91101
}
92102

app/Services/StoreService.php

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

33
namespace App\Services;
44
use App\Repositories\StoreRepository;
5-
use App\Jobs\SeedingStoreData;
5+
use App\Models\Store;
66
use App\Models\User;
77

88
class StoreService extends BaseService {
@@ -14,18 +14,22 @@ public function __construct(
1414
protected SubscriptionService $subscriptionService,
1515
) { parent::__construct($storeRepository); }
1616

17+
public function subscribe ( Store $store, array $data = [] ) {
18+
19+
if ( !$this->subscriptionService->subscribeOrRenew($store, $data) ) throwError('subscription', 'subscription failed');
20+
if ( !$this->withTenant($store->id, callback: fn() => $this->domainService->apply($data)) ) throwError('domains', 'invalid domains');
21+
22+
}
1723
public function store ( array $data = [], array $scopes = [] ) {
1824

1925
return $this->storeRepository->dbTransaction(function () use ( $data, $scopes ) {
2026

2127
$owner = is_client() ? client() : $this->userService->find(integer(data_get($data, 'owner_id')));
2228
$store = $this->storeRepository->create(array_merge($data, ['owner_id' => $owner->id]), $scopes);
2329

24-
if ( !$this->domainService->apply($store, $data) ) throwError('domains', 'invalid domains');
25-
if ( !$this->subscriptionService->subscribeOrRenew($store, $data) ) throwError('subscription', 'subscription failed');
26-
27-
dispatch(new SeedingStoreData($store, withoutFiles($data)));
30+
$this->subscribe($store, $data);
2831
$this->deleteCache();
32+
$this->runJob([SeedingService::class, 'run'], [$store, $data]);
2933

3034
return parent::show($store->id);
3135

@@ -37,10 +41,7 @@ public function update ( int $id, array $data = [], array $scopes = [] ) {
3741
return $this->storeRepository->dbTransaction(function () use ( $id, $data, $scopes ) {
3842

3943
$store = parent::find($id, $scopes);
40-
41-
if ( !$this->domainService->apply($store, $data) ) throwError('domains', 'invalid domains');
42-
if ( !$this->subscriptionService->subscribeOrRenew($store, $data) ) throwError('subscription', 'subscription failed');
43-
44+
$this->subscribe($store, $data);
4445
return parent::update($id, $data, $scopes);
4546

4647
});

app/Services/VercelService.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ public function dnsNames () {
2323
return ['ns1.vercel-dns.com', 'ns2.vercel-dns.com'];
2424

2525
}
26-
public function setProject ( string $name ) {
26+
public function setProject ( string $name = null ) {
2727

28+
$name = $name ?: 'client';
2829
$this->project = config("settings.default.vercel.projects.{$name}.id");
2930
return $this;
3031

0 commit comments

Comments
 (0)