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