diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e2cc773..3c7c2632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### ⚠️ Breaking changes - Renamed `lunar_model` to `model_contract` in `domains.php` config file +- Renamed `shipping-options` to `shipping_options` +- Renamed `payment-options` to `payment_options` ## 1.0.0-beta.2 diff --git a/composer.json b/composer.json index 8bc7a4fd..9941ec2a 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "laravel-json-api/hashids": "^3.0", "laravel-json-api/laravel": "^4.0", "laravel-json-api/non-eloquent": "^4.0", - "lunarphp/lunar": "^1.0.0-beta.1" + "lunarphp/lunar": "^1.0.0-beta.3", + "spatie/php-structure-discoverer": "^2.0" }, "require-dev": { "barryvdh/laravel-ide-helper": "^3.0", diff --git a/src/Base/Attributes/ReplaceModel.php b/src/Base/Attributes/ReplaceModel.php new file mode 100644 index 00000000..4fb1ccaa --- /dev/null +++ b/src/Base/Attributes/ReplaceModel.php @@ -0,0 +1,16 @@ +withServer($this->server) + ->withSchema($this); + } + /** * {@inheritDoc} */ @@ -51,10 +62,22 @@ public function authorizable(): bool /** * {@inheritDoc} */ - public function repository(): PaymentOptionRepository + public static function type(): string { - return PaymentOptionRepository::make() - ->withServer($this->server) - ->withSchema($this); + $resolver = new TypeResolver; + + return $resolver(static::class); + } + + /** + * {@inheritDoc} + */ + public function uriType(): string + { + if ($this->uriType) { + return $this->uriType; + } + + return $this->uriType = $this->type(); } } diff --git a/src/Domain/Prices/Models/Price.php b/src/Domain/Prices/Models/Price.php index 0097417d..a24db8a3 100644 --- a/src/Domain/Prices/Models/Price.php +++ b/src/Domain/Prices/Models/Price.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\Prices\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Prices\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Prices\Contracts\Price as PriceContract; +use Lunar\Models\Contracts\Price as LunarPriceContract; use Lunar\Models\Price as LunarPrice; +#[ReplaceModel(LunarPriceContract::class)] class Price extends LunarPrice implements PriceContract { use InteractsWithLunarApi; diff --git a/src/Domain/ProductAssociations/Models/ProductAssociation.php b/src/Domain/ProductAssociations/Models/ProductAssociation.php index bbd4b1cc..9187b239 100644 --- a/src/Domain/ProductAssociations/Models/ProductAssociation.php +++ b/src/Domain/ProductAssociations/Models/ProductAssociation.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\ProductAssociations\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\ProductAssociations\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\ProductAssociations\Contracts\ProductAssociation as ProductAssociationContract; +use Lunar\Models\Contracts\ProductAssociation as LunarProductAssociationContract; use Lunar\Models\ProductAssociation as LunarProductAssociation; +#[ReplaceModel(LunarProductAssociationContract::class)] class ProductAssociation extends LunarProductAssociation implements ProductAssociationContract { use InteractsWithLunarApi; diff --git a/src/Domain/ProductOptionValues/Models/ProductOptionValue.php b/src/Domain/ProductOptionValues/Models/ProductOptionValue.php index 63be2762..2450a5b5 100644 --- a/src/Domain/ProductOptionValues/Models/ProductOptionValue.php +++ b/src/Domain/ProductOptionValues/Models/ProductOptionValue.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\ProductOptionValues\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\ProductOptionValues\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\ProductOptionValues\Contracts\ProductOptionValue as ProductOptionValueContract; +use Lunar\Models\Contracts\ProductOptionValue as LunarProductOptionValueContract; use Lunar\Models\ProductOptionValue as LunarProductOptionValue; +#[ReplaceModel(LunarProductOptionValueContract::class)] class ProductOptionValue extends LunarProductOptionValue implements ProductOptionValueContract { use InteractsWithLunarApi; diff --git a/src/Domain/ProductOptions/Models/ProductOption.php b/src/Domain/ProductOptions/Models/ProductOption.php index 9b40ff76..45f2b31e 100644 --- a/src/Domain/ProductOptions/Models/ProductOption.php +++ b/src/Domain/ProductOptions/Models/ProductOption.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\ProductOptions\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\ProductOptions\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\ProductOptions\Contracts\ProductOption as ProductOptionContract; +use Lunar\Models\Contracts\ProductOption as LunarProductOptionContract; use Lunar\Models\ProductOption as LunarProductOption; +#[ReplaceModel(LunarProductOptionContract::class)] class ProductOption extends LunarProductOption implements ProductOptionContract { use InteractsWithLunarApi; diff --git a/src/Domain/ProductTypes/Models/ProductType.php b/src/Domain/ProductTypes/Models/ProductType.php index 050ee3c4..19590602 100644 --- a/src/Domain/ProductTypes/Models/ProductType.php +++ b/src/Domain/ProductTypes/Models/ProductType.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\ProductTypes\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\ProductTypes\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\ProductTypes\Contracts\ProductType as ProductTypeContract; +use Lunar\Models\Contracts\ProductType as LunarProductTypeContract; use Lunar\Models\ProductType as LunarProductType; +#[ReplaceModel(LunarProductTypeContract::class)] class ProductType extends LunarProductType implements ProductTypeContract { use InteractsWithLunarApi; diff --git a/src/Domain/ProductVariants/Models/ProductVariant.php b/src/Domain/ProductVariants/Models/ProductVariant.php index f6b6dd18..e7d04d41 100644 --- a/src/Domain/ProductVariants/Models/ProductVariant.php +++ b/src/Domain/ProductVariants/Models/ProductVariant.php @@ -2,18 +2,21 @@ namespace Dystcz\LunarApi\Domain\ProductVariants\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Base\Contracts\HasAvailability; use Dystcz\LunarApi\Base\Contracts\Translatable; use Dystcz\LunarApi\Domain\Products\Models\Product; use Dystcz\LunarApi\Domain\ProductVariants\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\ProductVariants\Contracts\ProductVariant as ProductVariantContract; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Lunar\Models\Contracts\ProductVariant as LunarPoductVariantContract; use Lunar\Models\ProductVariant as LunarPoductVariant; use Spatie\MediaLibrary\HasMedia; /** * @method MorphMany notifications() Get the notifications relation if `lunar-api-product-notifications` package is installed. */ +#[ReplaceModel(LunarPoductVariantContract::class)] class ProductVariant extends LunarPoductVariant implements HasAvailability, HasMedia, ProductVariantContract, Translatable { use InteractsWithLunarApi; diff --git a/src/Domain/Products/Models/Product.php b/src/Domain/Products/Models/Product.php index b3a9b1e4..66cdb2c7 100644 --- a/src/Domain/Products/Models/Product.php +++ b/src/Domain/Products/Models/Product.php @@ -2,14 +2,17 @@ namespace Dystcz\LunarApi\Domain\Products\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Products\Builders\ProductBuilder; use Dystcz\LunarApi\Domain\Products\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Products\Contracts\Product as ProductContract; +use Lunar\Models\Contracts\Product as LunarProductContract; use Lunar\Models\Product as LunarProduct; /** * @method static ProductBuilder query() */ +#[ReplaceModel(LunarProductContract::class)] class Product extends LunarProduct implements ProductContract { use InteractsWithLunarApi; diff --git a/src/Domain/ShippingOptions/JsonApi/V1/ShippingOptionSchema.php b/src/Domain/ShippingOptions/JsonApi/V1/ShippingOptionSchema.php index 6db1aec6..efecf6a1 100644 --- a/src/Domain/ShippingOptions/JsonApi/V1/ShippingOptionSchema.php +++ b/src/Domain/ShippingOptions/JsonApi/V1/ShippingOptionSchema.php @@ -2,6 +2,7 @@ namespace Dystcz\LunarApi\Domain\ShippingOptions\JsonApi\V1; +use Dystcz\LunarApi\Domain\JsonApi\Core\Schema\TypeResolver; use Dystcz\LunarApi\Domain\ShippingOptions\Entities\ShippingOption; use LaravelJsonApi\Core\Schema\Schema; use LaravelJsonApi\Eloquent\Fields\ArrayHash; @@ -33,6 +34,16 @@ public function fields(): iterable ]; } + /** + * {@inheritDoc} + */ + public function repository(): ShippingOptionRepository + { + return ShippingOptionRepository::make() + ->withServer($this->server) + ->withSchema($this); + } + /** * {@inheritDoc} */ @@ -44,10 +55,22 @@ public function authorizable(): bool /** * {@inheritDoc} */ - public function repository(): ShippingOptionRepository + public static function type(): string { - return ShippingOptionRepository::make() - ->withServer($this->server) - ->withSchema($this); + $resolver = new TypeResolver; + + return $resolver(static::class); + } + + /** + * {@inheritDoc} + */ + public function uriType(): string + { + if ($this->uriType) { + return $this->uriType; + } + + return $this->uriType = $this->type(); } } diff --git a/src/Domain/Tags/Models/Tag.php b/src/Domain/Tags/Models/Tag.php index e4c3754a..507f35d4 100644 --- a/src/Domain/Tags/Models/Tag.php +++ b/src/Domain/Tags/Models/Tag.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\Tags\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Tags\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Tags\Contracts\Tag as TagContract; +use Lunar\Models\Contracts\Tag as LunarTagContract; use Lunar\Models\Tag as LunarTag; +#[ReplaceModel(LunarTagContract::class)] class Tag extends LunarTag implements TagContract { use InteractsWithLunarApi; diff --git a/src/Domain/TaxZones/Models/TaxZone.php b/src/Domain/TaxZones/Models/TaxZone.php index 701466fe..ed8296d4 100644 --- a/src/Domain/TaxZones/Models/TaxZone.php +++ b/src/Domain/TaxZones/Models/TaxZone.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\TaxZones\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\TaxZones\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\TaxZones\Contracts\TaxZone as TaxZoneContract; +use Lunar\Models\Contracts\TaxZone as LunarTaxZoneContract; use Lunar\Models\TaxZone as LunarTaxZone; +#[ReplaceModel(LunarTaxZoneContract::class)] class TaxZone extends LunarTaxZone implements TaxZoneContract { use InteractsWithLunarApi; diff --git a/src/Domain/Transactions/Models/Transaction.php b/src/Domain/Transactions/Models/Transaction.php index 821bc740..ce653a79 100644 --- a/src/Domain/Transactions/Models/Transaction.php +++ b/src/Domain/Transactions/Models/Transaction.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\Transactions\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Transactions\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Transactions\Contracts\Transaction as TransactionContract; +use Lunar\Models\Contracts\Transaction as LunarTransactionContract; use Lunar\Models\Transaction as LunarTransaction; +#[ReplaceModel(LunarTransactionContract::class)] class Transaction extends LunarTransaction implements TransactionContract { use InteractsWithLunarApi; diff --git a/src/Domain/Urls/Models/Url.php b/src/Domain/Urls/Models/Url.php index ffba006e..61d2f7d4 100644 --- a/src/Domain/Urls/Models/Url.php +++ b/src/Domain/Urls/Models/Url.php @@ -2,10 +2,13 @@ namespace Dystcz\LunarApi\Domain\Urls\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Urls\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Urls\Contracts\Url as UrlContract; +use Lunar\Models\Contracts\Url as LunarUrlContract; use Lunar\Models\Url as LunarUrl; +#[ReplaceModel(LunarUrlContract::class)] class Url extends LunarUrl implements UrlContract { use InteractsWithLunarApi; diff --git a/src/Domain/Users/Models/User.php b/src/Domain/Users/Models/User.php index c6657bbe..cd84fe90 100644 --- a/src/Domain/Users/Models/User.php +++ b/src/Domain/Users/Models/User.php @@ -2,6 +2,7 @@ namespace Dystcz\LunarApi\Domain\Users\Models; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; use Dystcz\LunarApi\Domain\Users\Concerns\InteractsWithLunarApi; use Dystcz\LunarApi\Domain\Users\Contracts\User as UserContract; use Dystcz\LunarApi\Domain\Users\Factories\UserFactory; @@ -13,14 +14,14 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Config; -use Lunar\Base\LunarUser as LunarUserContract; use Lunar\Base\Traits\HasModelExtending; use Lunar\Base\Traits\LunarUser; use Lunar\Models\Cart; use Lunar\Models\Customer; use Lunar\Models\Order; -class User extends Authenticatable implements LunarUserContract, UserContract +#[ReplaceModel(UserContract::class)] +class User extends Authenticatable implements UserContract { use HasFactory; use HasModelExtending; diff --git a/src/Facades/LunarApi.php b/src/Facades/LunarApi.php index 10de45a5..7a95fbb4 100644 --- a/src/Facades/LunarApi.php +++ b/src/Facades/LunarApi.php @@ -14,6 +14,7 @@ * @method static \Dystcz\LunarApi\LunarApi checkoutCartUsing(class-string $class) * @method static \Dystcz\LunarApi\LunarApi hashIds(bool $value) Set ID hashing * @method static bool usesHashids() Check if the API hashes resource IDs + * @method static string getRoot() * * @see \Dystcz\LunarApi\LunarApi */ diff --git a/src/Facades/LunarApiConfig.php b/src/Facades/LunarApiConfig.php new file mode 100644 index 00000000..668babcd --- /dev/null +++ b/src/Facades/LunarApiConfig.php @@ -0,0 +1,24 @@ + $domains */ + public array $domains = []; + + public function __construct() + { + $this->config = DomainConfigCollection::make(); + } + + public function domain(string $domain): DomainConfig + { + return $this->config->get($domain); + } + + /** + * @param string[] $domains + */ + public function domains(...$domains): DomainConfig + { + return $this->config->only($domains); + } + + public function getSchemas(): Collection + { + return $this->config->getSchemas(); + } + + public function getRoutes(): Collection + { + return $this->config->getRoutes(); + } + + public function getModels(): Collection + { + return $this->config->getModelsForModelManifest(); + } + + public function getPolicies(): Collection + { + return $this->config->getPolicies(); + } +} diff --git a/src/LunarApiServiceProvider.php b/src/LunarApiServiceProvider.php index 0c00bb92..b36efed6 100644 --- a/src/LunarApiServiceProvider.php +++ b/src/LunarApiServiceProvider.php @@ -6,8 +6,7 @@ use Dystcz\LunarApi\Domain\Carts\Actions\CreateUserFromCart; use Dystcz\LunarApi\Domain\Users\Actions\CreateUser; use Dystcz\LunarApi\Domain\Users\Actions\RegisterUser; -use Dystcz\LunarApi\Facades\LunarApi; -use Dystcz\LunarApi\LunarApi as Api; +use Dystcz\LunarApi\Facades\LunarApi as LunarApiFacade; use Dystcz\LunarApi\Support\Config\Collections\DomainConfigCollection; use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Config; @@ -15,7 +14,7 @@ use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; use Lunar\Base\CartSessionInterface; -use Lunar\Facades\ModelManifest; +use Lunar\Facades\ModelManifest as ModelManifestFacade; class LunarApiServiceProvider extends ServiceProvider { @@ -44,11 +43,8 @@ public function register(): void $this->registerPolicies(); }); - // Register the main class to use with the facade. - $this->app->singleton( - 'lunar-api', - fn () => new Api, - ); + $this->app->singleton('lunar-api', fn () => new LunarApi); + $this->app->singleton('lunar-api-config', fn () => new LunarApiConfig); $this->bindControllers(); $this->bindModels(); @@ -87,10 +83,10 @@ public function boot(): void $this->registerEvents(); $this->registerPayments(); - LunarApi::createUserUsing(CreateUser::class); - LunarApi::createUserFromCartUsing(CreateUserFromCart::class); - LunarApi::registerUserUsing(RegisterUser::class); - LunarApi::checkoutCartUsing(CheckoutCart::class); + LunarApiFacade::createUserUsing(CreateUser::class); + LunarApiFacade::createUserFromCartUsing(CreateUserFromCart::class); + LunarApiFacade::registerUserUsing(RegisterUser::class); + LunarApiFacade::checkoutCartUsing(CheckoutCart::class); if ($this->app->runningInConsole()) { $this->publishConfig(); @@ -408,7 +404,7 @@ protected function registerObservers(): void protected function registerModels(): void { foreach (DomainConfigCollection::make()->getModelsForModelManifest() as $contract => $model) { - ModelManifest::replace($contract, $model); + ModelManifestFacade::add($contract, $model); } } diff --git a/src/Support/Config/Collections/DomainConfigCollection.php b/src/Support/Config/Collections/DomainConfigCollection.php index ec9eadf0..abfca32f 100644 --- a/src/Support/Config/Collections/DomainConfigCollection.php +++ b/src/Support/Config/Collections/DomainConfigCollection.php @@ -2,9 +2,26 @@ namespace Dystcz\LunarApi\Support\Config\Collections; +use Dystcz\LunarApi\Base\Attributes\ReplaceModel; +use Dystcz\LunarApi\Domain\JsonApi\Queries\CollectionQuery; +use Dystcz\LunarApi\Domain\JsonApi\Queries\Query; +use Dystcz\LunarApi\Domain\JsonApi\Resources\JsonApiResource; +use Dystcz\LunarApi\Facades\LunarApi; +use Dystcz\LunarApi\Routing\Contracts\RouteGroup; use Dystcz\LunarApi\Support\Config\Data\DomainConfig; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Str; +use LaravelJsonApi\Contracts\Schema\Schema as SchemaContract; +use LaravelJsonApi\Core\Schema\Schema as BaseSchema; +use ReflectionAttribute; +use ReflectionClass; +use Spatie\StructureDiscoverer\Data\DiscoveredStructure; +use Spatie\StructureDiscoverer\Discover; +use Spatie\StructureDiscoverer\Support\Conditions\ConditionBuilder; class DomainConfigCollection extends Collection { @@ -20,14 +37,112 @@ public static function make($items = []): self return self::fromConfig('lunar-api.domains'); } + /** + * Create domain config collection from a given config file. + */ + public static function discover(?string $root = null): self + { + $root = $root ?? LunarApi::getRoot(); + + $schemas = Discover::in($root) + ->classes() + ->any( + ConditionBuilder::create() + ->implementing(SchemaContract::class) + ->custom(fn (DiscoveredStructure $structure) => ! $structure->isAbstract), + + ConditionBuilder::create() + ->custom(fn (DiscoveredStructure $structure) => in_array($structure->extends, [BaseSchema::class])) + ) + ->get(); + + $domains = Collection::make($schemas) + ->mapWithKeys(function (string $schema) use ($root) { + $domain = (string) Str::of($schema)->after('Domain')->betweenFirst('\\', '\\'); + $domainRoot = "{$root}/Domain/{$domain}"; + + $model = Arr::first( + Discover::in($domainRoot) + ->classes() + ->custom(fn (DiscoveredStructure $structure) => Str::contains("{$structure->namespace}/{$structure->name}", "Models/{$structure->name}")) + ->custom(fn (DiscoveredStructure $structure) => ! in_array(Pivot::class, $structure->extendsChain)) + ->get() + ); + + if ($model) { + $modelReflection = new ReflectionClass($model); + /** @var ReflectionAttribute|null $replacesModelAttribute */ + $replacesModelAttribute = Arr::first( + $modelReflection->getAttributes(), + fn (ReflectionAttribute $attribute) => $attribute->getName() === ReplaceModel::class, + ); + $modelContract = Arr::first($replacesModelAttribute->getArguments()); + } + + $policy = Arr::first( + Discover::in($domainRoot) + ->classes() + ->custom(fn (DiscoveredStructure $structure) => Str::contains($structure->namespace, 'Policies')) + ->get() + ); + + $resource = Arr::first( + Discover::in($domainRoot) + ->classes() + ->extending(JsonApiResource::class) + ->get() + ); + + $query = Arr::first( + Discover::in($domainRoot) + ->classes() + ->extending(Query::class) + ->get() + ); + + $collectionQuery = Arr::first( + Discover::in($domainRoot) + ->classes() + ->extending(CollectionQuery::class) + ->get() + ); + + $routeGroup = Arr::first( + Discover::in($domainRoot) + ->classes() + ->implementing(RouteGroup::class) + ->custom(fn (DiscoveredStructure $structure) => Str::contains($structure->namespace, 'Domain')) + ->get() + ); + + return [ + $schema::type() => new DomainConfig( + domain: $schema::type(), + schema: $schema, + model: $model, + model_contract: $modelContract ?? null, + policy: $policy, + resource: $resource, + query: $query, + collection_query: $collectionQuery, + routes: $routeGroup, + ), + ]; + }); + + return new static($domains); + } + /** * Create domain config collection from a given config file. */ public static function fromConfig(string $configKey): self { - $items = array_map(function (array $domain) { - return new DomainConfig(...$domain); - }, Config::get($configKey, [])); + $items = array_map( + fn (string $domain, array $config) => new DomainConfig($domain, ...$config), + array_keys(Config::get($configKey, [])), + array_values(Config::get($configKey, [])) + ); return new static($items); } diff --git a/src/Support/Config/Data/DomainConfig.php b/src/Support/Config/Data/DomainConfig.php index 7b84c171..8c0aa6d7 100644 --- a/src/Support/Config/Data/DomainConfig.php +++ b/src/Support/Config/Data/DomainConfig.php @@ -2,18 +2,21 @@ namespace Dystcz\LunarApi\Support\Config\Data; +use Dystcz\LunarApi\Domain\JsonApi\Resources\JsonApiResource; +use Dystcz\LunarApi\Routing\Contracts\RouteGroup as RouteGroupContract; use Exception; use Illuminate\Support\Str; +use LaravelJsonApi\Contracts\Schema\Schema; +use Lunar\Base\Traits\HasModelExtending; class DomainConfig { /** - * @param array $route_actions - * @param array $actions - * @param array $notifications - * @param array $settings + * @param array $rest + * @param array $controllers */ public function __construct( + public string $domain, public ?string $model = null, public ?string $model_contract = null, public ?string $policy = null, @@ -22,20 +25,114 @@ public function __construct( public ?string $query = null, public ?string $collection_query = null, public ?string $routes = null, - public array $route_actions = [], - public array $actions = [], - public array $notifications = [], - public array $settings = [], + public array $controllers = [], + array ...$rest, ) { $this->validate(); } + /** + * Get domain name. + */ + public function getDomain(): string + { + return $this->domain; + } + + /** + * Get model class. + * + * @return class-string|null + */ + public function getModel(): ?string + { + return $this->model; + } + + /** + * Get model contract. + * + * @return interface-string|null + */ + public function getModelContract(): ?string + { + return $this->model_contract; + } + + /** + * Get policy class. + * + * @return class-string|null + */ + public function getPolicy(): ?string + { + return $this->policy; + } + + /** + * Get schema class. + * + * @return class-string|null + */ + public function getSchema(): ?string + { + return $this->schema; + } + + /** + * Get resource class. + * + * @return class-string|null + */ + public function getResource(): ?string + { + return $this->resource; + } + + /** + * Get query class. + * + * @return class-string|null + */ + public function getQuery(): ?string + { + return $this->query; + } + + /** + * Get collection query class. + * + * @return class-string|null + */ + public function getCollectionQuery(): ?string + { + return $this->collection_query; + } + + /** + * Get routes class. + * + * @return class-string|null + */ + public function getRoutes(): ?string + { + return $this->routes; + } + /** * Check if domain has schema. */ public function hasSchema(): bool { - return ! is_null($this->schema); + return ! is_null($this->getSchema()); + } + + /** + * Check if domain has resource. + */ + public function hasResource(): bool + { + return ! is_null($this->getResource()); } /** @@ -43,7 +140,7 @@ public function hasSchema(): bool */ public function hasModel(): bool { - return ! is_null($this->model); + return ! is_null($this->getModel()); } /** @@ -67,7 +164,7 @@ public function swapsModel(): bool */ public function hasPolicy(): bool { - return ! is_null($this->policy); + return ! is_null($this->getPolicy()); } /** @@ -75,7 +172,7 @@ public function hasPolicy(): bool */ public function hasRoutes(): bool { - return ! is_null($this->routes); + return ! is_null($this->getRoutes()); } /** @@ -84,8 +181,12 @@ public function hasRoutes(): bool public function validate(): void { $this->validateClassExistence(); + $this->validateModel(); + $this->validateSchema(); + $this->validateResource(); + $this->validateRoutes(); - // TODO: Add remaining checks + // ... } /** @@ -112,6 +213,70 @@ private function validateClassExistence(): void } } + /** + * Validate model. + */ + private function validateModel(): void + { + if (! $this->swapsModel()) { + return; + } + + $this->validateClassUses( + 'model', + $this->getModel(), + HasModelExtending::class, + ); + } + + /** + * Validate schema. + */ + private function validateSchema(): void + { + if (! $this->hasSchema()) { + return; + } + + $this->validateClassImplements( + 'schema', + $this->getSchema(), + Schema::class, + ); + } + + /** + * Validate resource. + */ + private function validateResource(): void + { + if (! $this->hasResource()) { + return; + } + + $this->validateSubclassOf( + 'resource', + $this->getResource(), + JsonApiResource::class, + ); + } + + /** + * Validate domain route groups. + */ + private function validateRoutes(): void + { + if (! $this->hasRoutes()) { + return; + } + + $this->validateClassImplements( + 'routes', + $this->getRoutes(), + RouteGroupContract::class, + ); + } + /** * Validate that a class exists. * @@ -126,6 +291,21 @@ private function validateClassExists(string $type, string $class): void } } + /** + * Validate that a class uses a trait. + * + * @param class-string $class + * @param trait-string $trait + */ + private function validateClassUses(string $type, string $class, string $trait): void + { + $traits = class_uses_recursive($class); + + if (! in_array($trait, $traits)) { + throw new Exception("{$type} class {$class} does not use {$trait}."); + } + } + /** * Validate that a class is a subclass of another class. * diff --git a/tests/Feature/Domain/Cart/JsonApi/V1/SetPaymentOptionTest.php b/tests/Feature/Domain/Cart/JsonApi/V1/SetPaymentOptionTest.php index 6201d4e3..eab06066 100644 --- a/tests/Feature/Domain/Cart/JsonApi/V1/SetPaymentOptionTest.php +++ b/tests/Feature/Domain/Cart/JsonApi/V1/SetPaymentOptionTest.php @@ -44,7 +44,7 @@ ]); expect($this->cart->fresh()->payment_option)->toBe($data['attributes']['payment_option']); -})->group('carts', 'payment-options'); +})->group('carts', 'payment_options'); it('validates payment option attribute when setting payment option to cart', function () { /** @var TestCase $this */ @@ -63,4 +63,4 @@ 'detail' => __('lunar-api::validations.payments.set_payment_option.payment_option.required'), 'status' => '422', ]); -})->group('carts', 'payment-options'); +})->group('carts', 'payment_options'); diff --git a/tests/Feature/Domain/Cart/JsonApi/V1/UnsetPaymentOptionTest.php b/tests/Feature/Domain/Cart/JsonApi/V1/UnsetPaymentOptionTest.php index 4ca81a97..a1e5543b 100644 --- a/tests/Feature/Domain/Cart/JsonApi/V1/UnsetPaymentOptionTest.php +++ b/tests/Feature/Domain/Cart/JsonApi/V1/UnsetPaymentOptionTest.php @@ -38,4 +38,4 @@ ]); expect($this->cart->fresh()->payment_option)->toBeNull(); -})->group('carts', 'payment-options'); +})->group('carts', 'payment_options'); diff --git a/tests/Feature/Domain/CartAddresses/JsonApi/V1/SetShippingOptionTest.php b/tests/Feature/Domain/CartAddresses/JsonApi/V1/SetShippingOptionTest.php index 0f656c65..828b4d2e 100644 --- a/tests/Feature/Domain/CartAddresses/JsonApi/V1/SetShippingOptionTest.php +++ b/tests/Feature/Domain/CartAddresses/JsonApi/V1/SetShippingOptionTest.php @@ -48,7 +48,7 @@ ]); expect($this->cartAddress->fresh()->shipping_option)->toBe($this->data['attributes']['shipping_option']); -})->group('cart-addresses', 'shipping-options'); +})->group('cart-addresses', 'shipping_options'); it('validates shipping option attribute when setting shipping option to cart address', function () { /** @var TestCase $this */ @@ -70,7 +70,7 @@ 'detail' => __('lunar-api::validations.shipping.set_shipping_option.shipping_option.required'), 'status' => '422', ]); -})->group('cart-addresses', 'shipping-options'); +})->group('cart-addresses', 'shipping_options'); test('only the user who owns the cart address can set shipping option for it', function () { /** @var TestCase $this */ @@ -87,4 +87,4 @@ 'status' => '401', 'title' => 'Unauthorized', ]); -})->group('cart-addresses', 'shipping-options'); +})->group('cart-addresses', 'shipping_options'); diff --git a/tests/Feature/Domain/CartAddresses/JsonApi/V1/UnsetShippingOptionTest.php b/tests/Feature/Domain/CartAddresses/JsonApi/V1/UnsetShippingOptionTest.php index 360aa95f..8338d700 100644 --- a/tests/Feature/Domain/CartAddresses/JsonApi/V1/UnsetShippingOptionTest.php +++ b/tests/Feature/Domain/CartAddresses/JsonApi/V1/UnsetShippingOptionTest.php @@ -40,7 +40,7 @@ ]); expect($this->cartAddress->fresh()->shipping_option)->toBeNull(); -})->group('cart-addresses', 'shipping-options'); +})->group('cart-addresses', 'shipping_options'); test('only the user who owns the cart address can unset shipping option for it', function () { /** @var TestCase $this */ @@ -56,4 +56,4 @@ 'status' => '401', 'title' => 'Unauthorized', ]); -})->group('cart-addresses', 'shipping-options'); +})->group('cart-addresses', 'shipping_options'); diff --git a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/CreatePaymentOptionTest.php b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/CreatePaymentOptionTest.php index 37c06487..f80b1f50 100644 --- a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/CreatePaymentOptionTest.php +++ b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/CreatePaymentOptionTest.php @@ -7,10 +7,10 @@ test('users cannot create new payment options', function () { /** @var TestCase $this */ - $response = $this->createTest('payment-options', []); + $response = $this->createTest('payment_options', []); $response->assertErrorStatus([ 'status' => '405', 'title' => 'Method Not Allowed', ]); -})->group('payment-options', 'policies'); +})->group('payment_options', 'policies'); diff --git a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/DeletePaymentOptionTest.php b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/DeletePaymentOptionTest.php index 3007bcbe..1544d154 100644 --- a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/DeletePaymentOptionTest.php +++ b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/DeletePaymentOptionTest.php @@ -18,11 +18,11 @@ /** @var TestCase $this */ $response = $this ->jsonApi() - ->expects('payment-options') - ->delete(serverUrl('/payment-options/1')); + ->expects('payment_options') + ->delete(serverUrl('/payment_options/1')); $response->assertErrorStatus([ 'status' => '405', 'title' => 'Method Not Allowed', ]); -})->group('payment-options', 'policies'); +})->group('payment_options', 'policies'); diff --git a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/ListPaymentOptionsTest.php b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/ListPaymentOptionsTest.php index 433d0bb6..b46be317 100644 --- a/tests/Feature/Domain/PaymentOptions/JsonApi/V1/ListPaymentOptionsTest.php +++ b/tests/Feature/Domain/PaymentOptions/JsonApi/V1/ListPaymentOptionsTest.php @@ -14,8 +14,8 @@ /** @var TestCase $this */ $response = $this ->jsonApi() - ->expects('payment-options') - ->get(serverUrl('/payment-options')); + ->expects('payment_options') + ->get(serverUrl('/payment_options')); $response->assertSuccessful(); @@ -24,7 +24,7 @@ $response->assertFetchedMany( $options->map(function (PaymentOption $paymentOption) { return [ - 'type' => 'payment-options', + 'type' => 'payment_options', 'id' => Str::slug($paymentOption->getId()), 'attributes' => [ 'driver' => $paymentOption->getDriver(), @@ -42,4 +42,4 @@ })->toArray() ); -})->group('payment-options'); +})->group('payment_options'); diff --git a/tests/Feature/Domain/Shipping/JsonApi/V1/CreateShippingOptionTest.php b/tests/Feature/Domain/Shipping/JsonApi/V1/CreateShippingOptionTest.php index fb41d99a..0c2b18fe 100644 --- a/tests/Feature/Domain/Shipping/JsonApi/V1/CreateShippingOptionTest.php +++ b/tests/Feature/Domain/Shipping/JsonApi/V1/CreateShippingOptionTest.php @@ -7,10 +7,10 @@ test('users cannot create new shipping options', function () { /** @var TestCase $this */ - $response = $this->createTest('shipping-options', []); + $response = $this->createTest('shipping_options', []); $response->assertErrorStatus([ 'status' => '405', 'title' => 'Method Not Allowed', ]); -})->group('shipping-options', 'policies'); +})->group('shipping_options', 'policies'); diff --git a/tests/Feature/Domain/Shipping/JsonApi/V1/DeleteShippingOptionTest.php b/tests/Feature/Domain/Shipping/JsonApi/V1/DeleteShippingOptionTest.php index e17269ec..d554479d 100644 --- a/tests/Feature/Domain/Shipping/JsonApi/V1/DeleteShippingOptionTest.php +++ b/tests/Feature/Domain/Shipping/JsonApi/V1/DeleteShippingOptionTest.php @@ -18,11 +18,11 @@ /** @var TestCase $this */ $response = $this ->jsonApi() - ->expects('shipping-options') - ->delete(serverUrl('/shipping-options/1')); + ->expects('shipping_options') + ->delete(serverUrl('/shipping_options/1')); $response->assertErrorStatus([ 'status' => '405', 'title' => 'Method Not Allowed', ]); -})->group('shipping-options', 'policies'); +})->group('shipping_options', 'policies'); diff --git a/tests/Feature/Domain/Shipping/JsonApi/V1/ListShippingOptionsTest.php b/tests/Feature/Domain/Shipping/JsonApi/V1/ListShippingOptionsTest.php index 97208216..5e4b5257 100644 --- a/tests/Feature/Domain/Shipping/JsonApi/V1/ListShippingOptionsTest.php +++ b/tests/Feature/Domain/Shipping/JsonApi/V1/ListShippingOptionsTest.php @@ -26,8 +26,8 @@ /** @var TestCase $this */ $response = $this ->jsonApi() - ->expects('shipping-options') - ->get(serverUrl('/shipping-options')); + ->expects('shipping_options') + ->get(serverUrl('/shipping_options')); $response->assertSuccessful(); @@ -35,7 +35,7 @@ $response->assertFetchedMany([ [ - 'type' => 'shipping-options', + 'type' => 'shipping_options', 'id' => 'ffcdel', 'attributes' => [ 'name' => 'Basic Delivery', @@ -49,7 +49,7 @@ ], ], ]); -})->group('shipping-options'); +})->group('shipping_options'); it('can list shipping options for a cart based on country', function () { /** @var TestCase $this */ @@ -69,8 +69,8 @@ $response = $this ->jsonApi() - ->expects('shipping-options') - ->get(serverUrl('/shipping-options')); + ->expects('shipping_options') + ->get(serverUrl('/shipping_options')); $response->assertSuccessful(); @@ -78,7 +78,7 @@ $response->assertFetchedMany([ [ - 'type' => 'shipping-options', + 'type' => 'shipping_options', 'id' => (string) Str::slug($shippingOptions[0]->getIdentifier()), 'attributes' => [ 'name' => $shippingOptions[0]->getName(), @@ -87,7 +87,7 @@ ], ], [ - 'type' => 'shipping-options', + 'type' => 'shipping_options', 'id' => (string) Str::slug($shippingOptions[1]->getIdentifier()), 'attributes' => [ 'name' => $shippingOptions[1]->getName(), @@ -96,4 +96,4 @@ ], ], ]); -})->group('shipping-options'); +})->group('shipping_options'); diff --git a/tests/Feature/Domain/Shipping/JsonApi/V1/UpdateShippingOptionTest.php b/tests/Feature/Domain/Shipping/JsonApi/V1/UpdateShippingOptionTest.php index ca99d7c1..7a941d3e 100644 --- a/tests/Feature/Domain/Shipping/JsonApi/V1/UpdateShippingOptionTest.php +++ b/tests/Feature/Domain/Shipping/JsonApi/V1/UpdateShippingOptionTest.php @@ -21,10 +21,10 @@ /** @var TestCase $this */ $shippingOption = App::get(ShippingModifiers::class)->getModifiers()->first(); - $response = $this->updateTest('shipping-options', Tag::class, []); + $response = $this->updateTest('shipping_options', Tag::class, []); $response->assertErrorStatus([ 'status' => '405', 'title' => 'Method Not Allowed', ]); -})->group('shipping-options', 'policies'); +})->group('shipping_options', 'policies'); diff --git a/tests/Unit/Domain/Carts/Models/CartTest.php b/tests/Unit/Domain/Carts/Models/CartTest.php index e11de579..4b5f598d 100644 --- a/tests/Unit/Domain/Carts/Models/CartTest.php +++ b/tests/Unit/Domain/Carts/Models/CartTest.php @@ -77,4 +77,4 @@ $this->assertEquals(600, $cart->paymentTotal->value); $this->assertEquals(1000, $cart->total->value); -})->group('carts', 'carts.payment-options'); +})->group('carts', 'carts.payment_options'); diff --git a/tests/Unit/Domain/Carts/Pipelines/SetPaymentTest.php b/tests/Unit/Domain/Carts/Pipelines/SetPaymentTest.php index f7418992..2038f652 100644 --- a/tests/Unit/Domain/Carts/Pipelines/SetPaymentTest.php +++ b/tests/Unit/Domain/Carts/Pipelines/SetPaymentTest.php @@ -56,7 +56,7 @@ }); $this->assertInstanceOf(PriceDataType::class, $cart->paymentSubTotal); -})->group('payment-options', 'carts.payment-options'); +})->group('payment_options', 'carts.payment_options'); it('can set payment totals', function () { /** @var TestCase $this */ @@ -116,4 +116,4 @@ $this->assertInstanceOf(PriceDataType::class, $cart->paymentSubTotal); $this->assertEquals(500, $cart->paymentSubTotal->value); -})->group('payment-options', 'carts.payment-options'); +})->group('payment_options', 'carts.payment_options'); diff --git a/tests/Unit/Domain/PaymentOptions/Data/PaymentOptionTest.php b/tests/Unit/Domain/PaymentOptions/Data/PaymentOptionTest.php index 3781f24d..c5310ab5 100644 --- a/tests/Unit/Domain/PaymentOptions/Data/PaymentOptionTest.php +++ b/tests/Unit/Domain/PaymentOptions/Data/PaymentOptionTest.php @@ -86,4 +86,4 @@ $this->assertEquals(0, $cart->paymentTotal->value); $this->assertEquals(0, $cart->total->value); -})->group('payment-options'); +})->group('payment_options'); diff --git a/tests/Unit/LunarApiConfigTest.php b/tests/Unit/LunarApiConfigTest.php new file mode 100644 index 00000000..2ace2b2a --- /dev/null +++ b/tests/Unit/LunarApiConfigTest.php @@ -0,0 +1,64 @@ +getSchemas(); + + expect($schemas)->toBeInstanceOf(\Illuminate\Support\Collection::class); + +})->group('config'); + +it('can list configured route groups', function () { + /** @var TestCase $this */ + + /** @var \Dystcz\LunarApi\LunarApiConfig $config */ + $config = App::make('lunar-api-config'); + + $routes = $config->getRoutes(); + + expect($routes)->toBeInstanceOf(\Illuminate\Support\Collection::class); + +})->group('config'); + +it('can list configured models for lunar model manifest', function () { + /** @var TestCase $this */ + + /** @var \Dystcz\LunarApi\LunarApiConfig $config */ + $config = App::make('lunar-api-config'); + + $models = $config->getModels(); + + expect($models)->toBeInstanceOf(\Illuminate\Support\Collection::class); + + foreach ($models as $contract => $model) { + $this->assertSame($model, ModelManifest::get($contract)); + } + +})->group('config'); + +it('can list configured policies', function () { + /** @var TestCase $this */ + + /** @var \Dystcz\LunarApi\LunarApiConfig $config */ + $config = App::make('lunar-api-config'); + + $policies = $config->getPolicies(); + + expect($policies)->toBeInstanceOf(\Illuminate\Support\Collection::class); + +})->group('config');