Browse Source
- Introduce generic InputSchema and InputSchemaField for dynamic UI management. - Replace legacy SettingSchema with new schema-based system across multiple domains. - Add Channel API synchronization logic with SynchronizeWithApi and SendWebhook actions. - Refactor authentication credentials to use typed DTOs (AuthBasicDTO, AuthBearerDTO). - Update Finance and System domains to support the new schema architecture. - Standardize CoreUI attributes and form component bindings.master
79 changed files with 861 additions and 465 deletions
@ -0,0 +1,11 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\Actions\Api; |
|||
|
|||
class SendWebhook |
|||
{ |
|||
public function execute(): void |
|||
{ |
|||
// |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\Actions\Api; |
|||
|
|||
use App\Domains\Channel\Enums\Api\ApiType; |
|||
use App\Domains\Channel\Models\ApiEndpoint; |
|||
use Illuminate\Support\Facades\Http; |
|||
|
|||
class SynchronizeWithApi |
|||
{ |
|||
public function execute(ApiType $apiType): void |
|||
{ |
|||
$endpoint = ApiEndpoint::query() |
|||
->with('client') |
|||
->where('key', $apiType) |
|||
->first(); |
|||
|
|||
$domain = $endpoint->client->domain; |
|||
$url = $endpoint->value; |
|||
$credentials = $endpoint->client->credentials; |
|||
$authType = $endpoint->client->auth_type; |
|||
$url = "https://$domain$url"; |
|||
$header = $authType->buildCredentials($credentials); |
|||
|
|||
$response = Http::withHeaders($header)->get($url); |
|||
dd($response); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\DTOs\AppProvisioning\Type; |
|||
|
|||
class AuthBasicDTO implements AuthCredentials |
|||
{ |
|||
public function __construct( |
|||
public string $username, |
|||
public string $password, |
|||
) {} |
|||
|
|||
public static function fromArray(array $data): static |
|||
{ |
|||
return new self( |
|||
username: $data['username'], |
|||
password: $data['password'], |
|||
); |
|||
} |
|||
|
|||
public function toArray(): array |
|||
{ |
|||
return [ |
|||
'username' => $this->username, |
|||
'password' => $this->password, |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\DTOs\AppProvisioning\Type; |
|||
|
|||
class AuthBearerDTO implements AuthCredentials |
|||
{ |
|||
public function __construct( |
|||
public string $token, |
|||
) {} |
|||
|
|||
public static function fromArray(array $data): static |
|||
{ |
|||
return new self( |
|||
token: $data['token'], |
|||
); |
|||
} |
|||
|
|||
public function toArray(): array |
|||
{ |
|||
return [ |
|||
'token' => $this->token, |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Channel\DTOs\AppProvisioning\Type; |
|||
|
|||
interface AuthCredentials |
|||
{ |
|||
public static function fromArray(array $data): static; |
|||
|
|||
public function toArray(): array; |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Support\Schema; |
|||
|
|||
use App\UI\Enums\InputType; |
|||
use App\UI\Support\Settings\BaseSchema; |
|||
use App\UI\Support\Settings\SettingSchema; |
|||
|
|||
class InvoiceSchema implements BaseSchema |
|||
{ |
|||
protected array $context = []; |
|||
protected string $form = ''; |
|||
|
|||
public function __construct( |
|||
protected array $uiSchema = [], |
|||
protected bool $coaVisibility = false |
|||
){} |
|||
|
|||
public static function make(bool $coaVisible = false): static |
|||
{ |
|||
return new self([], $coaVisible); |
|||
} |
|||
|
|||
public function addSchema(string$key, string $label, SettingSchema $schema): static |
|||
{ |
|||
$schema = $schema->attributes(['label' => $label]); |
|||
$uiSchema = array_merge($this->uiSchema, [[ |
|||
'key' => $key, |
|||
'schema' => $schema |
|||
]]); |
|||
return new self($uiSchema, $this->coaVisibility); |
|||
} |
|||
|
|||
public function withContext(array $context): static |
|||
{ |
|||
$this->context = $context; |
|||
return $this; |
|||
} |
|||
|
|||
public function withFormString(string $form): static |
|||
{ |
|||
$this->form = $form; |
|||
return $this; |
|||
} |
|||
|
|||
public function getSchema(): array |
|||
{ |
|||
$fields = []; |
|||
foreach($this->uiSchema as $field) { |
|||
/** @var SettingSchema $schema */ |
|||
$schema = $field['schema']; |
|||
$attributes['options'] =$schema->type->isSelect() ? $schema->resolveOptions($this->context) : ''; |
|||
|
|||
$fields[$field['key']] = (object) [ |
|||
'attributes' => array_merge($schema->attributes, $attributes), |
|||
'schema' => $schema, |
|||
]; |
|||
} |
|||
|
|||
return $fields; |
|||
} |
|||
|
|||
public function isCoaVisible(): bool |
|||
{ |
|||
return $this->coaVisibility; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
<?php |
|||
|
|||
namespace App\Domains\Finance\Support\Schemas; |
|||
|
|||
use App\UI\Support\Schemas\AbstractGroupSchema; |
|||
|
|||
class InvoiceSchema extends AbstractGroupSchema |
|||
{ |
|||
protected bool $coaVisibility = false; |
|||
|
|||
/** |
|||
* Fluent setter for COA visibility. |
|||
*/ |
|||
public function withCoaVisibility(bool $visible = true): static |
|||
{ |
|||
$this->coaVisibility = $visible; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function isCoaVisible(): bool |
|||
{ |
|||
return $this->coaVisibility; |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Schemas; |
|||
|
|||
abstract class AbstractGroupSchema extends AbstractSchema |
|||
{ |
|||
/** |
|||
* @var array<string, InputSchema> |
|||
*/ |
|||
protected array $fields = []; |
|||
|
|||
/** |
|||
* Add one or multiple fields to the container. |
|||
*/ |
|||
public function addInput(InputSchema $input): static |
|||
{ |
|||
$inputSchema = $input->getSchema(); |
|||
$this->fields[$inputSchema->key] = $input; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Pass dynamic context down to all child fields. |
|||
*/ |
|||
public function withContext(array $context): static |
|||
{ |
|||
parent::withContext($context); |
|||
|
|||
foreach ($this->fields as $field) { |
|||
$field->withContext($this->context); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Resolve all field schemas, keyed by their input keys. |
|||
* |
|||
* @return array<string, InputSchemaField> |
|||
*/ |
|||
public function getSchema(): array |
|||
{ |
|||
return array_map(function ($schema) { |
|||
return $schema->getSchema(); |
|||
}, $this->fields); |
|||
} |
|||
|
|||
/** |
|||
* Extract validation rules for FormRequest or Livewire. |
|||
* |
|||
* @return array<string, array> |
|||
*/ |
|||
public function getValidationRules(string $prefix = ''): array |
|||
{ |
|||
$rules = []; |
|||
|
|||
foreach ($this->getSchema() as $key => $schema) { |
|||
$fieldKey = $prefix ? "{$prefix}.{$key}" : $key; |
|||
$rules[$fieldKey] = $schema->rules; |
|||
} |
|||
|
|||
return $rules; |
|||
} |
|||
|
|||
/** |
|||
* Extract validation rules for FormRequest or Livewire. |
|||
* |
|||
* @return array<string, array> |
|||
*/ |
|||
public function getValidationAttributes(string $prefix = ''): array |
|||
{ |
|||
$rules = []; |
|||
|
|||
foreach ($this->getSchema() as $key => $schema) { |
|||
$fieldKey = $prefix ? "{$prefix}.{$key}" : $key; |
|||
$rules[$fieldKey] = $schema->label; |
|||
} |
|||
|
|||
return $rules; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Schemas; |
|||
|
|||
abstract class AbstractSchema implements BaseSchema |
|||
{ |
|||
protected array $context = []; |
|||
|
|||
/** |
|||
* Parameterless default instantiation. |
|||
*/ |
|||
public static function make(): static |
|||
{ |
|||
return new static; |
|||
} |
|||
|
|||
public function withContext(array $context): static |
|||
{ |
|||
$this->context = array_merge($this->context, $context); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
abstract public function getSchema(): array|InputSchemaField; |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Schemas; |
|||
|
|||
interface BaseSchema |
|||
{ |
|||
/** |
|||
* Parameterless factory method for fluent API instantiation. |
|||
*/ |
|||
public static function make(): static; |
|||
|
|||
/** |
|||
* Pass dynamic context (e.g., form state or Livewire state) into the schema. |
|||
*/ |
|||
public function withContext(array $context): static; |
|||
|
|||
/** |
|||
* Resolve the schema payload. |
|||
* Returns SchemaField for individual inputs, or array<string, SchemaField> for group containers. |
|||
*/ |
|||
public function getSchema(): array|InputSchemaField; |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Schemas; |
|||
|
|||
use App\UI\Enums\InputType; |
|||
use Closure; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class InputSchema extends AbstractSchema |
|||
{ |
|||
protected string $key = ''; |
|||
|
|||
protected string $label = ''; |
|||
|
|||
protected InputType $type = InputType::TEXTLINE; |
|||
|
|||
protected mixed $default = null; |
|||
|
|||
protected array|Closure $options = []; |
|||
|
|||
protected array $attributes = []; |
|||
|
|||
protected array $rules = ['nullable', 'string']; |
|||
|
|||
protected string $depends = ''; |
|||
|
|||
protected array $contextKeys = []; |
|||
|
|||
protected bool $live = false; |
|||
|
|||
/** |
|||
* Set the field unique key / name. |
|||
*/ |
|||
public function key(string $key): static |
|||
{ |
|||
$this->key = $key; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Set the display label. |
|||
*/ |
|||
public function label(string $label): static |
|||
{ |
|||
$this->label = $label; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function type(InputType $type): static |
|||
{ |
|||
$this->type = $type; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function default(mixed $default): static |
|||
{ |
|||
$this->default = $default; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function rules(array|string $rules): static |
|||
{ |
|||
$this->rules = is_array($rules) ? $rules : explode('|', $rules); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function options(array|Closure $options): static |
|||
{ |
|||
$this->options = $options; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function attributes(array $attributes): static |
|||
{ |
|||
$this->attributes = array_merge($this->attributes, $attributes); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function setContextKeys(array|string $keys): static |
|||
{ |
|||
$this->contextKeys = (array) $keys; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function select2(array $config = []): static |
|||
{ |
|||
return $this->attributes(['x-select2' => json_encode($config)]); |
|||
} |
|||
|
|||
public function live(): static |
|||
{ |
|||
$this->live = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function dependsOn(string $depends): static |
|||
{ |
|||
$this->depends = $depends; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function isLive(): bool |
|||
{ |
|||
return $this->live; |
|||
} |
|||
|
|||
public function resolveOptions(array $extraContext = []): array|Collection |
|||
{ |
|||
if (! $this->options instanceof Closure) { |
|||
return $this->options; |
|||
} |
|||
|
|||
$mergedContext = array_merge($this->context, $extraContext); |
|||
|
|||
if (! empty($this->contextKeys)) { |
|||
$mergedContext = array_intersect_key( |
|||
$mergedContext, |
|||
array_flip($this->contextKeys) |
|||
); |
|||
} |
|||
|
|||
return ($this->options)($mergedContext); |
|||
} |
|||
|
|||
private function getAttributes(): array |
|||
{ |
|||
$attributes = array_merge(['label' => $this->label], $this->attributes); |
|||
|
|||
if ($this->type->isSelect()) { |
|||
$attributes['options'] = $this->resolveOptions(); |
|||
} |
|||
|
|||
return $attributes; |
|||
} |
|||
|
|||
public function getSchema(): InputSchemaField |
|||
{ |
|||
return new InputSchemaField( |
|||
key: $this->key, |
|||
label: $this->label, |
|||
type: $this->type, |
|||
attributes: $this->getAttributes(), |
|||
rules: $this->rules, |
|||
default: $this->default, |
|||
isLive: $this->isLive(), |
|||
dependsOn: $this->depends, |
|||
schema: $this, |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Schemas; |
|||
|
|||
use App\UI\Enums\InputType; |
|||
|
|||
readonly class InputSchemaField |
|||
{ |
|||
public function __construct( |
|||
public string $key, |
|||
public string $label, |
|||
public InputType $type, |
|||
public array $attributes, |
|||
public array $rules, |
|||
public mixed $default, |
|||
public bool $isLive, |
|||
public string $dependsOn, |
|||
public InputSchema $schema, |
|||
) {} |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Settings; |
|||
|
|||
interface BaseSchema {} |
|||
@ -1,122 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\UI\Support\Settings; |
|||
|
|||
use App\UI\Enums\InputType; |
|||
use Closure; |
|||
use Illuminate\Support\Collection; |
|||
|
|||
class SettingSchema implements BaseSchema |
|||
{ |
|||
public mixed $default = null; |
|||
public array|Closure $options = []; |
|||
public array $attributes = []; |
|||
public array $rules = ['required', 'string']; |
|||
public string $depends = ''; |
|||
protected array $context = []; |
|||
protected array $contextKey = []; |
|||
protected bool $live = false; |
|||
|
|||
public function __construct( |
|||
public InputType $type, |
|||
) {} |
|||
|
|||
public static function make(InputType $type): self |
|||
{ |
|||
return new static($type); |
|||
} |
|||
|
|||
public function default(mixed $default): static |
|||
{ |
|||
$this->default = $default; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function rules(array $rules): static |
|||
{ |
|||
$this->rules = $rules; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function options(array|Closure $options): static |
|||
{ |
|||
$this->options = $options; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function attributes(array $attributes): static |
|||
{ |
|||
$this->attributes = array_merge($this->attributes, $attributes); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function setContextKey(array|string $contextKey): static |
|||
{ |
|||
$this->contextKey = array_merge($this->contextKey, (array) $contextKey); |
|||
return $this; |
|||
} |
|||
|
|||
public function withContext(array $context): static |
|||
{ |
|||
$this->context = array_merge($this->context, $context); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function select2(array $config = []): static |
|||
{ |
|||
$this->attributes(['x-select2' => json_encode($config)]); |
|||
return $this; |
|||
} |
|||
|
|||
public function live(): static |
|||
{ |
|||
$this->live = true; |
|||
return $this; |
|||
} |
|||
|
|||
public function dependsOn(string $depends): static |
|||
{ |
|||
$this->depends = $depends; |
|||
return $this; |
|||
} |
|||
|
|||
public function isLive(): bool |
|||
{ |
|||
return $this->live; |
|||
} |
|||
|
|||
public function resolveOptions(array $extraContext = []): array|Collection |
|||
{ |
|||
if ($this->options instanceof Closure) { |
|||
$mergedContext = array_merge($this->context, $extraContext); |
|||
|
|||
if(!empty($this->contextKey)) { |
|||
$temporaryContext = []; |
|||
foreach($this->contextKey as $key) { |
|||
$temporaryContext[$key] = !empty($mergedContext[$key]) ? $mergedContext[$key] : null; |
|||
} |
|||
$mergedContext = $temporaryContext; |
|||
} |
|||
|
|||
return ($this->options)($mergedContext); |
|||
} |
|||
|
|||
return $this->options; |
|||
} |
|||
|
|||
public function getAttributes(array $extraContext = []): array |
|||
{ |
|||
$attributes = $this->attributes; |
|||
|
|||
if ($this->type->isSelect()) { |
|||
$attributes['options'] = $this->resolveOptions($extraContext); |
|||
} |
|||
|
|||
return $attributes; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue