diff --git a/app/Helpers/CollectionHelpers.php b/app/Helpers/CollectionHelpers.php index 2da3ee8ba2..9ad5aedfed 100644 --- a/app/Helpers/CollectionHelpers.php +++ b/app/Helpers/CollectionHelpers.php @@ -12,6 +12,6 @@ class CollectionHelpers */ public static function collectApi($value = null) { - return new \App\Libraries\Api\Models\ApiCollection($value); + return new \Aic\Hub\Foundation\Library\Api\Models\ApiCollection($value); } } diff --git a/app/Http/Controllers/Twill/BaseApiController.php b/app/Http/Controllers/Twill/BaseApiController.php index 9af017827c..282fb36735 100644 --- a/app/Http/Controllers/Twill/BaseApiController.php +++ b/app/Http/Controllers/Twill/BaseApiController.php @@ -13,7 +13,27 @@ namespace App\Http\Controllers\Twill; +use A17\Twill\Facades\TwillPermissions; +use A17\Twill\Models\Behaviors\HasTranslation; +use A17\Twill\Models\Contracts\TwillModelContract; +use A17\Twill\Services\Forms\Fields\Input; +use A17\Twill\Services\Forms\Fieldset; +use A17\Twill\Services\Forms\Form; +use A17\Twill\Services\Listings\Columns\Boolean; +use A17\Twill\Services\Listings\Columns\FeaturedStatus; +use A17\Twill\Services\Listings\Columns\Languages; +use A17\Twill\Services\Listings\Columns\PublishStatus; +use A17\Twill\Services\Listings\Columns\ScheduledStatus; +use A17\Twill\Services\Listings\Columns\Text; +use A17\Twill\Services\Listings\Filters\BasicFilter; +use A17\Twill\Services\Listings\Filters\QuickFilter; +use A17\Twill\Services\Listings\TableColumn; +use A17\Twill\Services\Listings\TableColumns; use App\Helpers\UrlHelpers; +use App\Http\Controllers\Twill\Columns\ApiImage; +use Aic\Hub\Foundation\Library\Api\Filters\Search; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; class BaseApiController extends \App\Http\Controllers\Twill\ModuleController { @@ -28,6 +48,24 @@ class BaseApiController extends \App\Http\Controllers\Twill\ModuleController 'search' => 'search', ]; + protected $displayName = 'Datahub'; + + protected function setUpController(): void + { + $this->setFeatureField('is_featured'); + $this->setResultsPerPage(5); + + $this->disableBulkDelete(); + $this->disableBulkEdit(); + $this->disableBulkPublish(); + $this->disableCreate(); + $this->disableDelete(); + $this->disableEdit(); + $this->disablePermalink(); + $this->disablePublish(); + $this->disableRestore(); + } + public function getIndexTableMainFilters($items, $scopes = []) { // Remove Twill table filters. @@ -54,6 +92,17 @@ public function augment($datahubId) return $this->redirectToForm($item->id); } + public function feature() + { + if (($id = $this->request->get('id'))) { + if ($apiModel = $this->getApiRepository()->getById($id)) { + $augmentedModel = $this->getRepository()->firstOrCreate(['datahub_id' => $apiModel->id]); + $this->request->merge(['id' => $augmentedModel->id]); + } + } + return parent::feature(); + } + protected function getRepository(): \A17\Twill\Repositories\ModuleRepository { if ($this->hasAugmentedModel) { @@ -63,7 +112,12 @@ protected function getRepository(): \A17\Twill\Repositories\ModuleRepository return $this->getApiRepository(); } - protected function getBrowserTableData(\Illuminate\Support\Collection|\Illuminate\Pagination\LengthAwarePaginator $items, bool $forRepeater = false): array + protected function getApiRepository() + { + return $this->app->make("{$this->namespace}\Repositories\\Api\\" . $this->modelName . 'Repository'); + } + + protected function getBrowserTableData(Collection|LengthAwarePaginator $items, bool $forRepeater = false): array { // Ensure data is an array and not an object to avoid json_encode wrong conversion $results = array_values(parent::getBrowserTableData($items)); @@ -80,16 +134,254 @@ protected function getBrowserTableData(\Illuminate\Support\Collection|\Illuminat return $results; } - protected function getApiRepository() + public function getIndexItems(array $scopes = [], bool $forcePagination = false): Collection|LengthAwarePaginator { - return $this->app->make("{$this->namespace}\Repositories\\Api\\" . $this->modelName . 'Repository'); + if (TwillPermissions::enabled() && TwillPermissions::getPermissionModule($this->moduleName)) { + $scopes += ['accessible' => true]; + } + + $requestFilters = $this->getRequestFilters(); + $appliedFilters = []; + $this->applyQuickFilters($requestFilters, $appliedFilters); + $this->applyBasicFilters($requestFilters, $appliedFilters); + return $this->transformIndexItems( + $this->getApiData($scopes, $forcePagination, $appliedFilters) + ); + } + + /** + * Get the applied quick filter. + */ + protected function applyQuickFilters(array &$requestFilters, array &$appliedFilters): void + { + if (array_key_exists('status', $requestFilters)) { + $filter = $this->quickFilters()->filter( + fn (QuickFilter $filter) => $filter->getQueryString() === $requestFilters['status'] + )->first(); + + if ($filter !== null) { + $appliedFilters[] = $filter; + } + } + unset($requestFilters['status']); + } + + /** + * Get other filters that need to applied. Use the API search filter when + * requested. + */ + protected function applyBasicFilters(array &$requestFilters, array &$appliedFilters): void + { + foreach ($requestFilters as $filterKey => $filterValue) { + $filter = $this->filters()->filter( + fn (BasicFilter $filter) => $filter->getQueryString() === $filterKey + )->first(); + + if ($filter !== null) { + $appliedFilters[] = $filter->withFilterValue($filterValue); + } elseif ($filterKey === 'search') { + $appliedFilters[] = Search::make() + ->searchFor($filterValue) + ->searchColumns($this->searchColumns); + } + } + } + + public function getApiData($scopes = [], $forcePagination = false, $appliedFilters = []) + { + return $this->getApiRepository()->get( + with: $this->indexWith, + scopes: $scopes, + orders: $this->orderScope(), + perPage: $this->request->get('offset') ?? $this->perPage, + forcePagination: $forcePagination, + appliedFilters: $appliedFilters + ); + } + + protected function getIndexTableColumns(): TableColumns + { + $columns = TableColumns::make(); + + if ($this->getIndexOption('publish')) { + $columns->add( + PublishStatus::make() + ->title(twillTrans('twill::lang.listing.columns.published')) + ->sortable() + ->optional() + ); + } + + if ($this->getIndexOption('showImage')) { + $columns->add( + ApiImage::make() + ->field('thumbnail') + ->title(twillTrans('Image')) + ); + } + + if ($this->getIndexOption('feature') && $this->repository->isFillable('featured')) { + $columns->add( + FeaturedStatus::make() + ->title(twillTrans('twill::lang.listing.columns.featured')) + ); + } + + $columns->add( + Boolean::make() + ->field('is_augmented') + ->optional() + ->hide() + ); + $columns->add( + Text::make() + ->field('id') + ->title($this->displayName . ' Id') + ->optional() + ); + $columns->add( + Text::make() + ->field('source_updated_at') + ->optional() + ->hide() + ); + $columns->add( + Text::make() + ->field('updated_at') + ->optional() + ->hide() + ); + + $title = $this->titleColumnKey === 'title' && $this->titleColumnLabel === 'Title' + ? twillTrans('twill::lang.main.title') + : $this->titleColumnLabel; + $columns->add( + Text::make() + ->field($this->titleColumnKey) + ->title($title) + ->sortable() + ->linkCell(function (TwillModelContract $model) { + if ($model->is_augmented) { + $action = 'edit'; + $id = $model->getAugmentedModel()->id; + } else { + $action = 'augment'; + $id = $model->id; + } + return moduleRoute($this->moduleName, $this->routePrefix, $action, [$id]); + }) + ); + + $columns = $columns->merge($this->additionalIndexTableColumns()); + + if ($this->getIndexOption('includeScheduledInList') && $this->repository->isFillable('publish_start_date')) { + $columns->add( + ScheduledStatus::make() + ->title(twillTrans('twill::lang.publisher.scheduled')) + ->optional() + ); + } + + if ($this->moduleHas('translations') && count(getLocales()) > 1) { + $columns->add( + Languages::make() + ->title(twillTrans('twill::lang.listing.languages')) + ->optional() + ); + } + + return $columns; } - protected function getIndexItems($scopes = [], $forcePagination = false) + protected function getBrowserTableColumns(): TableColumns { - $perPage = request('offset') ?? $this->perPage ?? 50; - $items = $this->getApiRepository()->get($this->indexWith, $scopes, $this->orderScope(), $perPage, $forcePagination); + $columns = TableColumns::make(); + + if ($this->moduleHas('medias')) { + $columns->add( + ApiImage::make() + ->field('thumbnail') + ->rounded() + ->title(twillTrans('Image')) + ); + } + $columns = $columns->merge($this->additionalBrowserTableColumns()); + + return $columns; + } + + public function getForm(TwillModelContract $model): Form + { + $model->refreshApi(); + $title = $this->getTitleField(); + if (classHasTrait($model::class, HasTranslation::class)) { + $title->translatable(); + } + $content = Form::make() + ->add($title) + ->merge($this->additionalFormFields($model, $model->getApiModel())); + return parent::getForm($model) + ->addFieldset( + Fieldset::make() + ->title('Content') + ->id('content') + ->fields($content->toArray()) + ); + } + + protected function additionalFormFields($model, $apiModel): Form + { + return new Form(); + } + + public function getSideFieldSets(TwillModelContract $model): Form + { + return parent::getSideFieldSets($model) + // For some reason, the side form will not render unless there is a + // field in the default Content fieldset. 🤷 + ->add( + Input::make() + ->name('id') + ->disabled() + ->note('readonly') + ) + ->addFieldset( + Fieldset::make() + ->id('datahub') + ->title('Datahub') + ->closed() + ->fields([ + Input::make() + ->name('datahub_id') + ->disabled() + ->note('readonly'), + Input::make() + ->name('source_updated_at') + ->disabled() + ->note('readonly'), + ]) + ) + ->addFieldset( + Fieldset::make() + ->id('timestamps') + ->title('Timestamps') + ->closed() + ->fields([ + Input::make() + ->name('created_at') + ->disabled() + ->note('readonly'), + Input::make() + ->name('updated_at') + ->disabled() + ->note('readonly'), + ]) + ); + } + + protected function transformIndexItems(Collection|LengthAwarePaginator $items): Collection|LengthAwarePaginator + { if ($this->hasAugmentedModel) { $ids = $items->pluck('id')->toArray(); $this->localElements = $this->repository->whereIn('datahub_id', $ids)->get(); @@ -97,11 +389,9 @@ protected function getIndexItems($scopes = [], $forcePagination = false) if ($element = collect($this->localElements)->where('datahub_id', $item->id)->first()) { $item->setAugmentedModel($element); } - return $item; })); } - return $items; } @@ -127,4 +417,17 @@ protected function indexItemData($item) return ['edit' => $editRoute]; } + + /** + * Option to setup links and the possibility of augmenting a model + */ + protected function enableAugmentedModel(): void + { + $this->hasAugmentedModel = true; + } + + protected function setDisplayName(string $displayName): void + { + $this->displayName = $displayName; + } } diff --git a/app/Http/Controllers/Twill/Columns/ApiImage.php b/app/Http/Controllers/Twill/Columns/ApiImage.php new file mode 100644 index 0000000000..5f7bd392e4 --- /dev/null +++ b/app/Http/Controllers/Twill/Columns/ApiImage.php @@ -0,0 +1,26 @@ +getApiModel(); + } + if (!classHasTrait($model::class, HasMediasApi::class)) { + throw new InvalidArgumentException('Cannot use image column on model not implementing HasMediasApi trait'); + } + if ($renderFunction = $this->render) { + return $renderFunction($model); + } + + return $this->getThumbnail($model); + } +} diff --git a/app/Libraries/Api/Builders/ApiModelBuilder.php b/app/Libraries/Api/Builders/ApiModelBuilder.php deleted file mode 100644 index 4c6148bb23..0000000000 --- a/app/Libraries/Api/Builders/ApiModelBuilder.php +++ /dev/null @@ -1,675 +0,0 @@ -query = $query; - } - - /** - * Set a model instance for the model being queried. - * - * @param \App\Libraries\Api\Models\BaseApiModel - * @return $this - */ - public function setModel($model) - { - $this->model = $model; - - return $this; - } - - /** - * Set the relationships that should be included. - * - * @param mixed $relations - * @return $this - */ - public function with($relations, $callback = null) - { - $this->eagerLoad = array_merge($this->eagerLoad, $relations); - - return $this; - } - - /** - * Return counting data from the request. - * TODO: Implement it for the API. Bypassing for the moment. - * - * @param mixed $relations - * @return $this - */ - public function withCount($relations) - { - return $this; - } - - /** - * Add a basic where clause to the query. - * - * @param string|array|\Closure $column - * @param string $operator - * @param mixed $value - * @param string $boolean - * @return $this - */ - public function where($column, $operator = null, $value = null, $boolean = 'and') - { - $this->query->where(...func_get_args()); - - return $this; - } - - /** - * Perform a search - * - * @param string $search - * @return $this - */ - public function search($search) - { - $this->query->search(...func_get_args()); - $this->performSearch = true; - - return $this; - } - - /** - * Perform a raw ES search - * - * @param array $search - * @return $this - */ - public function rawSearch($search) - { - $this->query->rawSearch(...func_get_args()); - $this->performSearch = true; - - return $this; - } - - /** - * Perform a raw ES query - * - * @param array $params - * @return $this - */ - public function rawQuery($params) - { - $this->query->rawQuery(...func_get_args()); - $this->performSearch = true; - - return $this; - } - - /** - * Add aggregations to the raw ES search - * - * @param array $aggregations - * @return $this - */ - public function aggregations($aggregations) - { - $this->query->aggregations(...func_get_args()); - - return $this; - } - - /** - * When searching filter by specific resources - * - * @return $this - */ - public function resources(array $resources) - { - $this->query->resources($resources); - - return $this; - } - - /** - * Setup a TTL for this specific query call - * - * @param integer $ttl - * @return $this - */ - public function ttl($ttl) - { - $this->ttl = $ttl; - $this->query->ttl($ttl); - - return $this; - } - - /** - * Filter elements by specific ID's - * - * @return $this - */ - public function ids(array $ids) - { - $this->query->ids($ids); - - return $this; - } - - /** - * Include fields at the results - * - * @return $this - */ - public function include(array $inclusions) - { - $this->query->include($inclusions); - - return $this; - } - - /** - * Find a model by its primary key. - * - * @param mixed $id - * @param array $columns - */ - public function find($id, $columns = ['*']) - { - if (is_array($id) || $id instanceof Arrayable) { - return $this->findMany($id, $columns); - } - - return $this->findSingle($id, $columns); - } - - /** - * Find a model by its primary key, return exception if empty. - * - * @param mixed $id - * @param array $columns - */ - public function findOrFail($id, $columns = ['*']) - { - $result = $this->find($id, $columns); - - if (isset($result->status) && $result->status == 404) { - abort(404); - } - - if (is_array($id)) { - if (count($result) == count(array_unique($id))) { - return $result; - } - } elseif (!is_null($result)) { - return $result; - } - - throw (new ModelNotFoundException())->setModel( - get_class($this->model), - $id - ); - } - - public function findSingle($id, $columns = ['*']) - { - $builder = clone $this; - - // Eager load relationships - if ($result = $builder->getSingle($id, $columns)) { - $result = $builder->eagerLoadRelations([$result]); - } - - return $builder->getModel()->newCollection($result)->first(); - } - - public function findMany($ids, $columns = ['*']) - { - if (empty($ids)) { - return $this->model->newCollection(); - } - - return $this->ids($ids)->get($columns); - } - - /** - * Execute the query and return a collection of results - * - * @param array $columns - */ - public function get($columns = ['*']) - { - $builder = clone $this; - - if (count($models = $builder->getModels($columns)) > 0) { - $models = $builder->eagerLoadRelations($models); - } - - // Return direct body if status is different than a HIT - if (isset($models->status) && $models->status != 200) { - return $models; - } - - return $builder->getModel()->newCollection($models); - } - - /** - * Execute the query and return a raw response - * - * @param array $columns - */ - public function getRaw($columns = ['*']) - { - $builder = clone $this; - - return $this->query->getRaw($columns, $this->getEndpoint($this->resolveCollectionEndpoint()))->all(); - } - - /** - * Get the hydrated models - * - * @param array $columns - */ - public function getModels($columns = ['*']) - { - $results = $this->query->get($columns, $this->getEndpoint($this->resolveCollectionEndpoint())); - - // Return direct body if status is different than a HIT - if (isset($results->status) && $results->status != 200) { - return $results; - } - - $models = $this->model->hydrate($results->all()); - - // Preserve metadata after hydrating the collection - return CollectionHelpers::collectApi($models)->setMetadata($results->getMetadata()); - } - - /** - * Get a plain search request - * - * @param array $columns - */ - public function getSearch($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - $page = is_null($page) ? Paginator::resolveCurrentPage($pageName) : $page; - $perPage = is_null($perPage) ? $this->model->getPerPage() : $perPage; - - $results = $this->forPage($page, $perPage)->get($columns); - - $paginationData = $results->getMetadata('pagination'); - $total = $paginationData ? $paginationData->total : $results->count(); - - // Extract IDS - $ids = $results->pluck('id')->toArray(); - - // Load the actual models using the IDS returned by search - if (empty($ids)) { - $models = CollectionHelpers::collectApi(); - } else { - $models = $this->model->newQuery()->ttl($this->ttl)->ids($ids)->get(); - } - - // Sort them by the original ids listing - $sorted = $models->sortBy(function ($model, $key) use ($ids) { - return CollectionHelpers::collectApi($ids)->search(function ($id, $key) use ($model) { - return $id == $model->id; - }); - })->values(); - - // Preserve original metadata - $sorted->setMetadata($results->getMetadata()); - - return $this->paginator($sorted, $total, $perPage ?: 1, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - /** - * Paginate the given query and transform the Search results to the API models - * - * @param int $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - * - * @throws \InvalidArgumentException - */ - public function getPaginatedModel($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - $results = $this->getPaginated($perPage, $columns, $pageName, $page); - - $paginationData = $results->getMetadata('pagination'); - $total = $paginationData ? $paginationData->total : $results->count(); - - // Transform each Search model to the correct instance using typeMap - $hydratedModels = $results->transform(function ($item, $key) { - return $this->model::hydrate([$item->toArray()])[0]; - }); - - // Rebuild the paginator - return $this->paginator($hydratedModels, $total, $perPage ?: 1, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - /** - * Eager load the relationships for the models. - * On this case just a flat include, not nested queries because - * we get all id's to be loaded on the first request to the parent model - * - * @param array $models - * @return array - */ - public function eagerLoadRelations($models) - { - // Preserve metadata when loading relationships - if ($models instanceof ApiCollection) { - $metadata = $models->getMetadata(); - } - - foreach ($this->eagerLoad as $name) { - $models = $this->eagerLoadRelation($models, $name); - } - - return isset($metadata) ? $models->setMetadata($metadata) : $models; - } - - /** - * Eagerly load the relationship on a set of models. - * - * @param array $models - * @param string $name - * @return array - */ - protected function eagerLoadRelation(array $models, $name, ?\Closure $constraints = null) - { - foreach ($models as $model) { - if ($model instanceof BaseApiModel) { - // For each model get the relationship - $relation = $model->{$name}(); - - // Set the relationship loading the data from the API - // this will generate N + 1 calls in total - // improve later using real eager loading to - // reduce the number of calls to 1 + relationships_number - if ($relation) { - $model->setRelation($name, $relation->getEager()); - } - } - } - - return $models; - } - - /** - * Execute the query and return a single element - * - * @param array $columns - */ - public function getSingle($id, $columns = ['*']) - { - $endpoint = $this->getEndpoint($this->resolveResourceEndpoint(), ['id' => $id]); - - $results = $this->query->get($columns, $endpoint); - - // Return direct body if status is different than a HIT - if (isset($results->status) && $results->status != 200) { - return $results; - } - - $models = $this->model->hydrate($results->all()); - - return collect($models)->first(); - } - - /** - * Paginate the given query. - * - * @param int $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - * - * @throws \InvalidArgumentException - */ - public function getPaginated($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - $page = $page ?: Paginator::resolveCurrentPage($pageName); - - $perPage = $perPage ?: $this->model->getPerPage(); - - $results = $this->forPage($page, $perPage)->get($columns); - $paginationData = $results->getMetadata('pagination'); - $total = $paginationData ? $paginationData->total : $results->count(); - - return $this->paginator($results, $total, $perPage, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - /** - * Paginate the given query. - * - * @param int $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - * - * @throws \InvalidArgumentException - */ - public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) - { - if ($this->performSearch) { - return $this->getSearch($perPage, $columns, $pageName, $page); - } - - return $this->getPaginated($perPage, $columns, $pageName, $page); - } - - protected function paginator($items, $total, $perPage, $currentPage, $options) - { - return new LengthAwarePaginator( - $items, - $total, - $perPage, - $currentPage, - $options - ); - } - - /** - * Get the model instance being queried. - * - * @return string - */ - public function getEndpoint($name, $params = []) - { - return $this->model->parseEndpoint($name, $params); - } - - /** - * Force to use a specific endpoint - * - * @return string - */ - public function forceEndpoint($name) - { - $this->customEndpoint = $name; - - return $this; - } - - /** - * Resolve endpoint. Because search and listing contains different ones - * We will check if we are calling a search, and use that endpoint in that case - * - * @return string - */ - public function resolveCollectionEndpoint() - { - if ($this->customEndpoint) { - return $this->customEndpoint; - } - - return $this->performSearch ? 'search' : 'collection'; - } - - /** - * Resolve single element endpoint - * - * @return string - */ - public function resolveResourceEndpoint() - { - return $this->customEndpoint ?? 'resource'; - } - - /** - * Get the model instance being queried. - * - * @return \App\Libraries\Api\Models\BaseApiModel; - */ - public function getModel() - { - return $this->model; - } - - /** - * TODO: Apply scopes before running a passthrough - */ - public function toBase() - { - return $this; - } - - /** - * Apply the given scope on the current builder instance. - * - * @param array $parameters - * @return mixed - */ - protected function callScope(callable $scope, array $parameters = []) - { - array_unshift($parameters, $this); - $result = $scope(...array_values($parameters)) ?? $this; - - return $result; - } - - /** - * Dynamically handle calls into the query instance. - * - * @param string $method - * @param array $parameters - */ - public function __call($method, $parameters): mixed - { - if (method_exists($this->model, $scope = 'scope' . ucfirst($method))) { - return $this->callScope([$this->model, $scope], $parameters); - } - - if (in_array($method, $this->passthru)) { - return $this->query->{$method}(...$parameters); - } - - $this->query->{$method}(...$parameters); - - return $this; - } - - /** - * Dynamically retrieve attributes on the model. - * - * @param string $key - */ - public function __get($key): mixed - { - return $this->query->{$key}; - } -} diff --git a/app/Libraries/Api/Builders/ApiModelBuilderSearch.php b/app/Libraries/Api/Builders/ApiModelBuilderSearch.php deleted file mode 100644 index 7f589fbf6c..0000000000 --- a/app/Libraries/Api/Builders/ApiModelBuilderSearch.php +++ /dev/null @@ -1,215 +0,0 @@ - API class, ...] - * - */ - protected $typeMap = []; - - /** - * Get a plain search request - * - * @param array $columns - */ - public function getSearch($perPage = null, $columns = [], $pageName = 'page', $page = null, $options = []) - { - $builder = clone $this; - - $page = is_null($page) ? Paginator::resolveCurrentPage($pageName) : $page; - $perPage = is_null($perPage) ? $this->model->getPerPage() : $perPage; - - if ($columns) { - $columns = array_merge( - ['thumbnail', 'api_model', 'is_boosted', 'api_link', 'id', 'title', 'timestamp'], - $columns - ); - } - $results = $this->forPage($page, $perPage)->get($columns); - - $paginationData = $results->getMetadata('pagination'); - $total = $paginationData ? $paginationData->total : $results->count(); - - if (isset($options['do-not-extract']) && $options['do-not-extract']) { - if (isset($options['segregated']) && $options['segregated']) { - $models = $this->makeModels($results); - } else { - $models = $this->makeModelsFlat($results); - } - } else { - if (isset($options['segregated']) && $options['segregated']) { - $models = $this->extractModels($results); - } else { - $models = $this->extractModelsFlat($results); - } - } - - return $this->paginator($models, $total, $perPage ?: 1, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - /** - * Paginate the given query and transform the Search results to the correct API models - * - * @param int $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - * - * @throws \InvalidArgumentException - */ - public function getPaginatedModel($perPage = null, $columns = [], $pageName = 'page', $page = null) - { - $results = $this->getPaginated($perPage, $columns, $pageName, $page); - - $paginationData = $results->getMetadata('pagination'); - $total = $paginationData ? $paginationData->total : $results->count(); - - // Transform each Search model to the correct instance using typeMap - $hydratedModels = $results->transform(function ($item, $key) { - return $this->getTypeMap()[$item->api_model]::hydrate([$item->toArray()])[0]; - }); - - // Rebuild the paginator - return $this->paginator($hydratedModels, $total, $perPage ?: 1, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - protected function extractModelsFlat($results) - { - $original = clone $results; - - // It's more efficient to get segregated results, and then reshuffle them into the - // original order - $segregatedResults = $this->extractModels($results); - - // Mix them all up together - $flatResults = CollectionHelpers::collectApi(array_filter(Arr::flatten($segregatedResults))); - - // Sort results in their original order - $sorted = $flatResults->sortBy(function ($model, $key) use ($original) { - return $original->search(function ($item, $key) use ($model) { - if (isset($this->getTypeMap()[$item->api_model])) { - return $this->getTypeMap()[$item->api_model] == (string) get_class($model) && $item->id == $model->id; - } - }); - })->values(); - - // Preserve metadata - $sorted->setMetadata($original->getMetadata()); - - return $sorted; - } - - protected function extractModels($results) - { - $original = clone $results; - - // Group results by type - $resultsByType = $results->groupBy('api_model'); - - // Segregate results to load a single query per entity to load them all - $segregatedResults = $resultsByType->map(function ($collection, $type) { - $ids = $collection->pluck('id')->toArray(); - $class = $this->getTypeMap()[$type]; - - if (isset($class) && $class) { - $elements = $this->getTypeMap()[$type]::query()->ids($ids); - - if ($elements && method_exists($elements, 'ttl')) { - $elements->ttl($this->ttl); - } - - return $elements->get(); - } - - if (!$class) { - return $collection; // e.g. static-pages - } - }); - - // Remove empty categories - $filtered = $segregatedResults->filter(function ($value, $key) { - return !empty($value); - }); - - return $filtered; - } - - protected function makeModelsFlat($results) - { - $original = clone $results; - - // It's more efficient to get segregated results, and then reshuffle them into the - // original order - $segregatedResults = $this->makeModels($results); - - // Mix them all up together - $flatResults = CollectionHelpers::collectApi(array_filter(Arr::flatten($segregatedResults))); - - // Sort results in their original order - $sorted = $flatResults->sortBy(function ($model, $key) use ($original) { - return $original->search(function ($item, $key) use ($model) { - if (isset($this->getTypeMap()[$item->api_model])) { - return $this->getTypeMap()[$item->api_model] == (string) get_class($model) && $item->id == $model->id; - } - }); - })->values(); - - // Preserve metadata - $sorted->setMetadata($original->getMetadata()); - - return $sorted; - } - - protected function makeModels($results) - { - $original = clone $results; - - // Group results by type - $resultsByType = $results->groupBy('api_model'); - - // Segregate results to load a single query per entity to load them all - $segregatedResults = $resultsByType->map(function ($collection, $type) { - $class = $this->getTypeMap()[$type]; - - if (isset($class) && $class) { - $elements = $collection->map(function ($searchItem) use ($class) { - $item = new $class($searchItem->getAttributes()); - - return $item; - }); - - return $elements; - } - - if (!$class) { - return $collection; // e.g. static-pages - } - }); - - // Remove empty categories - $filtered = $segregatedResults->filter(function ($value, $key) { - return !empty($value); - }); - - return $filtered; - } - - protected function getTypeMap() - { - return $this->model->typeMap; - } -} diff --git a/app/Libraries/Api/Builders/ApiQueryBuilder.php b/app/Libraries/Api/Builders/ApiQueryBuilder.php deleted file mode 100644 index f48a3c1ebd..0000000000 --- a/app/Libraries/Api/Builders/ApiQueryBuilder.php +++ /dev/null @@ -1,614 +0,0 @@ -', '<=', '>=', '<>', '!=', '<=>' - ]; - - /** - * The orderings for the query. - * - * @var array - */ - public $orders; - - /** - * The maximum number of records to return. - * - * @var int - */ - public $limit; - - /** - * The number of records to skip. - * - * @var int - */ - public $offset; - - /** - * Whether to apply boosting or not - * - * @var boolean - */ - public $boost = true; - - /** - * The current page number - * - * @var int - */ - public $page; - - /** - * The database query grammar instance. - * - * @var \Illuminate\Database\Query\Grammars\Grammar - */ - public $grammar; - - /** - * The Cache TTL for this specific query builder - * - * @var array - */ - public $ttl; - - /** - * The columns that should be returned. - * - * @var array - */ - public $columns; - - /** - * The ids of the records that should be returned. - * - * @var array - */ - public $ids = []; - - /** - * The list of extra fields to be included - * - * @var array - */ - public $include = []; - - /** - * The where constraints for the query. - * - * @var array - */ - public $wheres = []; - - /** - * Search constraints for the query. - * - * @var string - */ - public $searchText; - - /** - * Search parameters for a raw ES query. - * - * @var array - */ - public $searchParameters = []; - - /** - * Completely raw ES query. - * - * @var array - */ - public $rawQuery = []; - - /** - * Aggregations parameters for a raw ES query. - * - * @var array - */ - public $aggregationParameters = []; - - /** - * Search specific resources. Useful only for general searches - * - * @var array - */ - public $searchResources = []; - - /** - * Pagination data saved after a request - */ - public $paginationData; - - /** - * Aggregation data saved after a request - */ - public $aggregationsData; - - /** - * Suggestion data saved after a request - */ - public $suggestionsData; - - public function __construct($connection, $grammar = null) - { - $this->connection = $connection; - $this->grammar = $grammar ?: $connection->getQueryGrammar(); - } - - /** - * Bypass whereNotIn function until it's implemented on the API - * - */ - public function whereNotIn($column, $values, $boolean = 'and') - { - return $this; - } - - public function whereIn($column, $values, $boolean = 'and', $not = false) - { - if ($column == 'id') { - $this->ids($values); - - return $this; - } - - throw new \Exception('whereIn function has been defined only for IDS at the API Query Builder'); - } - - /** - * Add a basic where clause to the query. - * - * @param string|array|\Closure $column - * @param string|null $operator - * @param mixed $value - * @param string $boolean - * @return $this - */ - public function where($column, $operator = null, $value = null, $boolean = 'and') - { - // If the column is an array, we will assume it is an array of key-value pairs - // and can add them each as a where clause. We will maintain the boolean we - // received when the method was called and pass it into the nested where. - if (is_array($column)) { - throw new \Exception('where function should be called with 1 level of nesting. No arrays.'); - } - - // If the given operator is not found in the list of valid operators we will - // assume that the developer is just short-cutting the '=' operators and - // we will set the operators to '=' and set the values appropriately. - if ($this->invalidOperator($operator)) { - [$value, $operator] = [$operator, '=']; - } - - // Now that we are working with just a simple query we can put the elements - // in our array and add the query binding to our array of bindings that - // will be bound to each SQL statements when it is finally executed. - $type = 'Basic'; - - $this->wheres[] = compact( - 'type', - 'column', - 'operator', - 'value', - 'boolean' - ); - - return $this; - } - - /** - * Add an "order by" clause to the query. - * - * @param string $column - * @param string $direction - * @return $this - */ - public function orderBy($column, $direction = 'asc') - { - $this->orders[] = [ - $column => ['order' => strtolower($direction) == 'asc' ? 'asc' : 'desc'] - ]; - - return $this; - } - - /** - * Add an "ids" clause to the query. This will bring only records with these ids - * - * @return $this - */ - public function ids($ids = []) - { - if (!empty($ids)) { - $this->ids = $ids; - } - - return $this; - } - - /** - * Add an "includes" clause to the query. This will add those attributes - * - * @return $this - */ - public function include($inclusions = []) - { - if (!empty($inclusions)) { - $this->include = $inclusions; - } - - return $this; - } - - /** - * Paginate the given query into a simple paginator. - * - * @param int $perPage - * @param array $columns - * @param string $pageName - * @param int|null $page - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function paginate($perPage = 15, $columns = [], $pageName = 'page', $page = null) - { - $page = $page ?: Paginator::resolveCurrentPage($pageName); - - $results = $this->forPage($page, $perPage)->get($columns); - - $paginationData = $this->getPaginationData(); - $total = $paginationData ? $paginationData->total : $results->count(); - - $data = $results['body']->data; - - return $this->paginator($data, $total, $perPage, $page, [ - 'path' => Paginator::resolveCurrentPath(), - 'pageName' => $pageName, - ]); - } - - /** - * Create a new length-aware paginator instance. - * - * @param \Illuminate\Support\Collection $items - * @param int $total - * @param int $perPage - * @param int $currentPage - * @param array $options - * @return \Illuminate\Pagination\LengthAwarePaginator - */ - protected function paginator($items, $total, $perPage, $currentPage, $options) - { - return new LengthAwarePaginator( - $items, - $total, - $perPage, - $currentPage, - $options - ); - } - - /** - * Set the limit and offset for a given page. - * - * @param int $page - * @param int $perPage - * @return \Illuminate\Database\Query\Builder|static - */ - public function forPage($page, $perPage = 15) - { - $this->page = $page; - - return $this->skip(($page - 1) * $perPage)->take($perPage); - } - - /** - * Alias to set the "offset" value of the query. - * - * @param int $value - * @return \Illuminate\Database\Query\Builder|static - */ - public function skip($value) - { - return $this->offset($value); - } - - /** - * Set the "offset" value of the query. - * - * @param int $value - * @return $this - */ - public function offset($value) - { - $this->offset = max(0, $value); - - return $this; - } - - /** - * Alias to set the "limit" value of the query. - * - * @param int $value - * @return \Illuminate\Database\Query\Builder|static - */ - public function take($value) - { - return $this->limit($value); - } - - /** - * Set the "limit" value of the query. - * - * @param int $value - * @return $this - */ - public function limit($value) - { - if ($value >= 0) { - $this->limit = $value; - } - - return $this; - } - - /** - * Set the "boost" value of the query. - * - * @param boolean $value - * @return $this - */ - public function boost($value = true) - { - $this->boost = $value; - - return $this; - } - - /** - * Search for specific resources - * - * @return $this - */ - public function resources($resources) - { - $this->searchResources = $resources; - - return $this; - } - - /** - * Perform a search - * - * @param string $search - * @return $this - */ - public function search($search) - { - $this->searchText = empty($search) ? null : $search; - - return $this; - } - - /** - * Perform a raw ES search - * - * @param array $search - * @return $this - */ - public function rawSearch($search) - { - $this->searchParameters = array_merge_recursive($this->searchParameters, $search); - - return $this; - } - - /** - * Perform a completely raw ES query - * - * @param array $search - * @return $this - */ - public function rawQuery($search) - { - $this->rawQuery = $search; - - return $this; - } - - /** - * Add aggregations to the raw ES search - * - * @param array $aggregations - * @return $this - */ - public function aggregations($aggregations) - { - $this->aggregationParameters = array_merge_recursive($this->aggregationParameters, $aggregations); - - return $this; - } - - /** - * Execute a get query and setup pagination data - * - * @param array $columns - * @return \Illuminate\Support\Collection - */ - public function get($columns = [], $endpoint = null) - { - $original = $this->columns; - - if (is_null($original)) { - $this->columns = $columns; - } - - $results = $this->runGet($endpoint); - - // If we got anything different than a HIT return the body - if (isset($results->status) && $results->status != 200) { - if (isset($results->body)) { - return $results->body; - } - - return $results; - } - - $this->columns = $original; - - if (is_array($results->body)) { - // If it's an msearch result return first element - $collection = CollectionHelpers::collectApi($results->body[0]->data); - } elseif (is_array($results->body->data)) { - // If it's a single element return as a collection with 1 element - $collection = CollectionHelpers::collectApi($results->body->data); - } else { - $collection = CollectionHelpers::collectApi([$results->body->data]); - } - - $collection = $this->getSortedCollection($collection); - - $collection->setMetadata([ - 'pagination' => $results->body->pagination ?? null, - 'aggregations' => $results->body->aggregations ?? null, - 'suggestions' => $results->body->suggest ?? null - ]); - - return $collection; - } - - /** - * Execute a get query and return a raw response - * - * @param array $columns - * @return \Illuminate\Support\Collection - */ - public function getRaw($columns = [], $endpoint = null) - { - $original = $this->columns; - - if (is_null($original)) { - $this->columns = $columns; - } - - $results = $this->runGet($endpoint); - - if (is_array($results->body)) { - $collection = CollectionHelpers::collectApi($results->body); - } else { - $collection = CollectionHelpers::collectApi([$results->body]); - } - - $collection = $this->getSortedCollection($collection); - - $collection->setMetadata([ - 'pagination' => $results->body->pagination ?? null, - 'aggregations' => $results->body->aggregations ?? null, - 'suggestions' => $results->body->suggest ?? null - ]); - - return $collection; - } - - public function getPaginationData() - { - return $this->paginationData; - } - - /** - * Build and execute against the API connection a GET call - * - * @return array - */ - public function runGet($endpoint) - { - $grammar = null; - - if (Str::endsWith($endpoint, '/msearch')) { - $grammar = new MsearchGrammar(); - } elseif (Str::endsWith($endpoint, '/search')) { - $grammar = new SearchGrammar(); - } - - return $this->connection->ttl($this->ttl)->get($endpoint, $this->resolveParameters($grammar)); - } - - /** - * Use grammar to generate all parameters from the scopes as an array - * - * @return string - */ - public function resolveParameters($grammar = null) - { - if ($grammar) { - return $grammar->compileParameters($this); - } - - return $this->grammar->compileParameters($this); - } - - /** - * Set a specific Caching TTL for this request - * - * @return array - */ - public function ttl($ttl = null) - { - $this->ttl = $ttl; - - return $this; - } - - /** - * Determine if the given operator is supported. - * - * @param string $operator - * @return bool - */ - protected function invalidOperator($operator) - { - return !in_array(strtolower($operator), $this->operators, true); - } - - /** - * WEB-1626: If this was an `ids` query, reorder results to match `ids`. - */ - private function getSortedCollection(\Illuminate\Support\Collection $collection) - { - if (empty($this->ids)) { - return $collection; - } - - return $collection->sort(function ($a, $b) use ($collection) { - if (!isset($a->id) || !isset($b->id)) { - return 0; - } - - $ia = array_search($a->id, $this->ids); - $ib = array_search($b->id, $this->ids); - - if ($ia === $ib) { - return 0; - } - - return ($ia < $ib) ? -1 : 1; - }); - } -} diff --git a/app/Libraries/Api/Builders/Connection/AicConnection.php b/app/Libraries/Api/Builders/Connection/AicConnection.php deleted file mode 100644 index baed276751..0000000000 --- a/app/Libraries/Api/Builders/Connection/AicConnection.php +++ /dev/null @@ -1,146 +0,0 @@ -client = \App::make('ApiClient'); - $this->useDefaultQueryGrammar(); - } - - protected function useDefaultQueryGrammar() - { - $this->queryGrammar = new $this->defaultGrammar(); - } - - public function getQueryGrammar() - { - return $this->queryGrammar; - } - - public function setQueryGrammar($queryGrammar) - { - return $this->queryGrammar = $queryGrammar; - } - - /** - * Define a custom TTL for this connection instance - * - * @param integer $ttl - * @return object - */ - public function ttl($ttl = null) - { - $this->ttl = $ttl; - - return $this; - } - - /** - * Run a get statement against the API. - * - * @param array $params - * @return object - */ - public function get($endpoint, $params) - { - $response = $this->execute($endpoint, $params); - - return $response; - } - - /** - * Execute a general call to the API client - * - * @param array $params - * @return object - */ - public function execute($endpoint = null, $params = []) - { - $headers = $this->client->headers($params); - $options = $headers; - - $queryKeys = ['ids', 'include']; - $queryParams = Arr::only($params, $queryKeys); - $bodyParams = Arr::except($params, $queryKeys); - - $verb = empty($bodyParams) ? 'GET' : 'POST'; - - if (in_array(config('api.force_verb'), ['GET', 'POST'])) { - $verb = config('api.force_verb'); - } - - if ($verb === 'GET') { - if (!empty($params)) { - // WEB-979: See DecodeParams middleware in data-aggregator - $endpoint = $endpoint . '?params=' . urlencode(json_encode($params)); - } - } else { - if (!empty($bodyParams)) { - $adaptedParameters = $this->client->adaptParameters($params); - $options = array_merge($adaptedParameters, $headers); - } - } - - if (config('api.logger')) { - $ttl = $this->ttl ?? config('api.cache_ttl'); - \Log::info($verb . ' ttl = ' . $ttl . ' ' . $endpoint); - \Log::info(print_r($options, true)); - } - - // Perform API request and caching - if (config('api.cache_enabled')) { - $cacheKey = $this->buildCacheKey($verb, $endpoint, $options, config('api.cache_version')); - - // Manual cachebusting - $decacheHash = Request::input('nocache'); - - if ($decacheHash && config('api.cache_buster') && $decacheHash === config('api.cache_buster')) { - \Cache::forget($cacheKey); - } - - // Use default TTL if no explicit has been defined - $ttl = $this->ttl ?? config('api.cache_ttl'); - - $response = \Cache::remember($cacheKey, $ttl, function () use ($verb, $endpoint, $options) { - // WEB-2259: Error handling is done in the ApiConsumer - return $this->client->request($verb, $endpoint, $options); - }); - - if (isset($response->status) && $response->status != 200) { - \Cache::forget($cacheKey); - } - - return $response; - } - - return $this->client->request($verb, $endpoint, $options); - } - - /** - * WEB-1805: The cache key *must* be 250 bytes or less! Otherwise, it'll silently fail on the lookup. - */ - protected function buildCacheKey() - { - return md5(json_encode(func_get_args())); - } -} diff --git a/app/Libraries/Api/Builders/Connection/ApiConnectionInterface.php b/app/Libraries/Api/Builders/Connection/ApiConnectionInterface.php deleted file mode 100644 index 0e0b4547dd..0000000000 --- a/app/Libraries/Api/Builders/Connection/ApiConnectionInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -columns; - - // To compile the query, we'll spin through each component of the query and - // see if that component exists. If it does we'll just call the compiler - // function for the component which is responsible for making the parameters - $compiled = $this->compileComponents($query); - - $query->columns = $original; - - return $compiled; - } - - /** - * Compile the components necessary for a select clause. - * - * @param ApiQueryBuilder $query - * @return array - */ - protected function compileComponents($query) - { - $parameters = []; - - foreach ($this->selectComponents as $component) { - // To compile the query, we'll spin through each component of the query and - // see if that component exists. If it does we'll just call the compiler - // function for the component which is responsible for making the parameter/s. - if (!is_null($query->{$component})) { - $method = 'compile' . ucfirst($component); - - $parameters = array_merge($parameters, $this->{$method}($query, $query->{$component})); - } - } - - return $parameters; - } - - /** - * Compile the "where" portions of the query. - * - * @param ApiQueryBuilder $query - * @return string - */ - protected function compileWheres($query) - { - return []; - } - - /** - * Compile the "columns" portions of the query. This translates to 'fields' - * which are the columns the API will return. - * - * @param ApiQueryBuilder $query - * @return array - */ - protected function compileColumns($query, $columns) - { - // By default, the API returns all columns if none are specified. - return $columns === ['*'] ? [] : ['fields' => join(',', $columns)]; - } - - /** - * Compile the "include" portions of the query. - * - * @param ApiQueryBuilder $query - * @return array - */ - protected function compileInclude($query, $columns) - { - return empty($columns) ? [] : ['include' => join(',', $columns)]; - } - - /** - * Compile the "ids" portions of the query. This will filter by an IDs array - * - * @param ApiQueryBuilder $query - * @return array - */ - protected function compileIds($query, $ids) - { - return empty($ids) ? [] : ['ids' => join(',', $ids)]; - } - - protected function compileSearchResources($query, $resources) - { - return empty($resources) ? [] : ['resources' => join(',', $resources)]; - } - - protected function compileSearchText($query, $text) - { - if ($text) { - return ['q' => $text]; - } - - return []; - } - - protected function compileSearchParameters($query, array $elasticParameters) - { - return empty($elasticParameters) ? [] : ['query' => $elasticParameters]; - } - - protected function compileRawQuery($query, array $rawQuery) - { - return empty($rawQuery) ? [] : $rawQuery; - } - - protected function compileAggregationParameters($query, array $aggregations) - { - return empty($aggregations) ? [] : ['aggregations' => $aggregations]; - } - - protected function compileOrders($query, $order) - { - return empty($order) ? [] : ['sort' => $order]; - } - - protected function compileLimit($query, $limit) - { - return [ - 'limit' => $limit, - 'size' => $limit // Elasticsearch search parameter for limiting - ]; - } - - protected function compileOffset($query, $offset) - { - return [ - 'offset' => $offset, - 'from' => $offset // Elasticsearch search parameter for offset - ]; - } - - protected function compileBoost($query, $boost) - { - return []; - } - - protected function compilePage($query, $page) - { - return ['page' => $page]; - } -} diff --git a/app/Libraries/Api/Builders/Grammar/MsearchGrammar.php b/app/Libraries/Api/Builders/Grammar/MsearchGrammar.php deleted file mode 100644 index 45cd8c2723..0000000000 --- a/app/Libraries/Api/Builders/Grammar/MsearchGrammar.php +++ /dev/null @@ -1,31 +0,0 @@ - $columns]; - } -} diff --git a/app/Libraries/Api/Builders/Grammar/SearchGrammar.php b/app/Libraries/Api/Builders/Grammar/SearchGrammar.php deleted file mode 100644 index 6a3a4d69cd..0000000000 --- a/app/Libraries/Api/Builders/Grammar/SearchGrammar.php +++ /dev/null @@ -1,13 +0,0 @@ - $boost, - ]; - } -} diff --git a/app/Libraries/Api/Builders/Relations/HasMany.php b/app/Libraries/Api/Builders/Relations/HasMany.php deleted file mode 100644 index d672649c28..0000000000 --- a/app/Libraries/Api/Builders/Relations/HasMany.php +++ /dev/null @@ -1,79 +0,0 @@ -query = $query; - $this->parent = $parent; - $this->localKey = $localKey; - $this->limit = $limit; - - $this->addConstraints(); - } - - public function addConstraints() - { - // On this case we just save the Id's array coming from the API - // And pass it to the query to filter by ID. - $ids = $this->parent->{$this->localKey}; - - // Sometimes it's just an id and not an array - $ids = is_array($ids) ? $ids : [$ids]; - - if ($this->limit > -1) { - $ids = array_slice($ids, 0, $this->limit); - } - - $this->query->ids($ids); - } - - /** - * Execute eager loading. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getEager() - { - return $this->get(); - } - - /** - * Execute the query - * - * @param array $columns - * @return \Illuminate\Database\Eloquent\Collection - */ - public function get($columns = []) - { - return $this->query->get($columns); - } -} diff --git a/app/Libraries/Api/Consumers/ApiConsumerInterface.php b/app/Libraries/Api/Consumers/ApiConsumerInterface.php deleted file mode 100644 index b3bb66ba4a..0000000000 --- a/app/Libraries/Api/Consumers/ApiConsumerInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -client = new \GuzzleHttp\Client($options); - } - - /** - * Intercept the Guzzle request and return a cleaner object - * - */ - public function request($method, $uri = '', array $options = []) - { - // WEB-2259, WEB-2345: Allow 4xx and 5xx responses - $options = array_merge($options, ['http_errors' => false]); - - $response = $this->client->request($method, $uri, $options); - $contents = $response->getBody()->getContents(); - $body = json_decode($contents); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \Exception('Invalid JSON: ' . $contents); - } - - if (is_object($body) && isset($body->error) && $body->status !== 404) { - throw new \Exception('API error: ' . $contents); - } - - if (!in_array($response->getStatusCode(), [200, 404])) { - throw new \Exception('API invalid response: ' . $contents); - } - - return (object) [ - 'body' => $body, - 'status' => $response->getStatusCode() - ]; - } - - /** - * Adapt raw parameters to be implemented correctly by the client library. - * You can send parameters directly, or adapt them manually. - * This method should be defined for each consumer to ease configuration. - * - */ - public function adaptParameters($params) - { - return ['body' => json_encode($params)]; - } - - /** - * Add a default header and merge with headers coming from the parameters - * - */ - public function headers($params) - { - $headers = [ - 'Content-Type' => 'application/json', - 'Accept-Encoding' => 'gzip', - ]; - - if (config('api.token')) { - $headers = array_merge($headers, [ - 'Authorization' => 'Bearer ' . config('api.token'), - ]); - } - - if (isset($params['headers'])) { - $headers = array_merge($default, $params['headers']); - } - - return ['headers' => $headers]; - } - - /** - * Captures all method calls, and bypass them to the client in case they don't - * exists locally. This way it's easier to extend/ - * - */ - public function __call($name, $args): mixed - { - if (isset($this->__classMethods[$name]) && $this->__classMethods[$name] instanceof \Closure) { - return call_user_func_array($this->__classMethods[$name], $args); - } - - // If it doesn't exists locally push the call to the API client. - return $this->client->{$name}(...$args); - } -} diff --git a/app/Libraries/Api/Filters/Search.php b/app/Libraries/Api/Filters/Search.php new file mode 100644 index 0000000000..5f0f7afbcc --- /dev/null +++ b/app/Libraries/Api/Filters/Search.php @@ -0,0 +1,39 @@ +searchString) && $this->searchColumns !== []) { + $shoulds = []; + foreach ($this->searchColumns as $column) { + if ($column != 'id' || is_numeric($this->searchString)) { + $shoulds[] = [ + 'match' => [ + $column => $this->searchString, + ], + ]; + } + } + $params = [ + 'bool' => [ + 'should' => $shoulds, + 'minimum_should_match' => 1, + ], + ]; + + // \App\Libraries\Api\Models\BaseApiModel with \Aic\Hub\Foundation\Library\Api\Models\Behaviors\HasApiCalls::rawSearch() + return $builder->getModel()->rawSearch($params); + } + + return $builder; + } +} diff --git a/app/Libraries/Api/Models/ApiCollection.php b/app/Libraries/Api/Models/ApiCollection.php deleted file mode 100644 index 6d512144c5..0000000000 --- a/app/Libraries/Api/Models/ApiCollection.php +++ /dev/null @@ -1,42 +0,0 @@ -metadata) { - if ($name) { - return $this->metadata->get($name); - } - - return $this->metadata; - } - } - - public function setMetadata($data) - { - if ($this->metadata) { - if ($data instanceof \Illuminate\Support\Collection) { - $this->metadata = $this->metadata->merge($data); - } else { - $this->metadata = $this->metadata->merge(collect($data)); - } - } else { - $this->metadata = collect($data); - } - - return $this; - } -} diff --git a/app/Libraries/Api/Models/BaseApiModel.php b/app/Libraries/Api/Models/BaseApiModel.php index 7ec84b7da5..5d7b3a4a9b 100644 --- a/app/Libraries/Api/Models/BaseApiModel.php +++ b/app/Libraries/Api/Models/BaseApiModel.php @@ -10,10 +10,10 @@ use A17\Twill\Models\Behaviors\HasPresenter; use A17\Twill\Models\Contracts\TwillModelContract; -use App\Libraries\Api\Models\ApiCollection as BaseCollection; -use App\Libraries\Api\Models\Behaviors\HasApiCalls; -use App\Libraries\Api\Models\Behaviors\HasAugmentedModel; -use App\Libraries\Api\Models\Behaviors\HasRelationships; +use Aic\Hub\Foundation\Library\Api\Models\ApiCollection as BaseCollection; +use Aic\Hub\Foundation\Library\Api\Models\Behaviors\HasApiCalls; +use Aic\Hub\Foundation\Library\Api\Models\Behaviors\HasAugmentedModel; +use Aic\Hub\Foundation\Library\Api\Models\Behaviors\HasRelationships; use ArrayAccess; use DateTime; use Illuminate\Contracts\Routing\UrlRoutable; diff --git a/app/Libraries/Api/Models/Behaviors/HasApiCalls.php b/app/Libraries/Api/Models/Behaviors/HasApiCalls.php deleted file mode 100644 index 684556f93a..0000000000 --- a/app/Libraries/Api/Models/Behaviors/HasApiCalls.php +++ /dev/null @@ -1,146 +0,0 @@ -newQuery()->with( - is_string($relations) ? func_get_args() : $relations - ); - } - - /** - * Begin querying the model. - * - * @return App\Libraries\Api\Builders\ApiModelBuilder - */ - public static function query() - { - return (new static())->newQuery(); - } - - /** - * Begin querying the model. - * - * @return App\Libraries\Api\Builders\ApiModelBuilder - */ - public static function search($value) - { - return (new static())->newQuery()->search($value); - } - - /** - * Get a new query builder for the model's table. - * - * @return App\Libraries\Api\Builders\ApiModelBuilder - */ - public function newQuery() - { - return $this->registerDefaultScopes($this->newQueryWithoutScopes()); - } - - /** - * Register the global scopes for this builder instance. - * - * @param App\Libraries\Api\Builders\ApiModelBuilder $builder - * @return App\Libraries\Api\Builders\ApiModelBuilder - */ - public function registerDefaultScopes($builder) - { - foreach ($this->getDefaultScopes() as $name => $parameters) { - if (empty($parameters)) { - $builder->{$name}(); - } else { - $builder->{$name}($parameters); - } - } - - return $builder; - } - - /** - * Get the default scopes for this class instance. - * - * @return array - */ - public function getDefaultScopes() - { - return static::$defaultScopes; - } - - /** - * Get a new query builder that doesn't have any global scopes. - * - * @return App\Libraries\Api\Builders\ApiModelBuilder|static - */ - public function newQueryWithoutScopes() - { - $builder = $this->newApiModelBuilder($this->newApiQueryBuilder()); - - // Once we have the query builders, we will set the model instances so the - // builder can easily access any information it may need from the model - // while it is constructing and executing various queries against it. - return $builder->setModel($this) - ->with($this->with); - } - - public function newApiModelBuilder($query) - { - return new ApiModelBuilder($query); - } - - /** - * Get a new query builder instance for the connection. - * - * @return \Illuminate\Database\Query\Builder - */ - protected function newApiQueryBuilder() - { - $connection = $this->getConnection(); - - return new ApiQueryBuilder($connection, $connection->getQueryGrammar()); - } - - public function getConnection() - { - return new AicConnection(); - } - - /** - * Parse API endpoint. Replace brackets {name} with the 'name' attribute value (usually datahub_id) - * - * This way you can define an endpoint like: - * protected $endpoint = '/api/v1/exhibitions/{datahub_id}/artwork/{id}'; - * - * And the elements will be dinamically replaced with the params values passed - * - * @return string - */ - public function parseEndpoint($type, $params = []) - { - return preg_replace_callback('!\{(\w+)\}!', function ($matches) use ($params) { - $name = $matches[1]; - - return $params[$name]; - }, $this->getEndpoint($type)); - } -} diff --git a/app/Libraries/Api/Models/Behaviors/HasAugmentedModel.php b/app/Libraries/Api/Models/Behaviors/HasAugmentedModel.php deleted file mode 100644 index 8a39ba4268..0000000000 --- a/app/Libraries/Api/Models/Behaviors/HasAugmentedModel.php +++ /dev/null @@ -1,63 +0,0 @@ -augmentedModel = $model; - $this->augmented = true; - } - - public function getAugmentedModelClass() - { - return $this->augmentedModelClass; - } - - public function getAugmentedModel() - { - if (!$this->augmented) { - return; - } - - if ($this->augmentedModel) { - return $this->augmentedModel; - } - - $this->augmentedModel = $this->augmentedModelClass::where('datahub_id', $this->id)->first(); - - if (!$this->augmentedModel) { - $this->augmented = false; - } - - return $this->augmentedModel; - } - - public function hasAugmentedModel() - { - return $this->augmented; - } - - /** - * Bypass missed methods to the augmented model if existent - * - */ - public function __call($method, $parameters): mixed - { - if (method_exists($this, $method)) { - return $this->{$method}($parameters); - } - - if ($this->hasAugmentedModel() && $this->getAugmentedModel() && method_exists($this->getAugmentedModel(), $method)) { - return $this->getAugmentedModel()->{$method}(...$parameters); - } - - return null; - } -} diff --git a/app/Libraries/Api/Models/Behaviors/HasRelationships.php b/app/Libraries/Api/Models/Behaviors/HasRelationships.php deleted file mode 100644 index 505ce7e3bc..0000000000 --- a/app/Libraries/Api/Models/Behaviors/HasRelationships.php +++ /dev/null @@ -1,124 +0,0 @@ -relations[$relation] = $value; - - return $this; - } - - /** - * Define a one-to-many relationship. On this case we just load all ids coming - * from the API - * - * @param string $related - * @param string $localKey - * @return App\Libraries\Api\Builders\Relations\HasMany - */ - public function hasMany($related, $localKey = 'id', $limit = -1) - { - $queryInstance = $related::query(); - - // If we have no data in our localKey we ignore the relationship to - // avoid calling to an endpoint with no data - if (empty($this->{$localKey})) { - return; - } - - return $this->newHasMany( - $queryInstance, - $this, - $localKey, - $limit - ); - } - - /** - * Instantiate a new HasMany relationship. - * - * @param App\Libraries\Api\Builders\ApiQueryBuilder $query - * @param App\Libraries\Api\Models\BaseApiModel; $parent - * @param string $localKey - * @return App\Libraries\Api\Builders\Relations\HasMany - */ - protected function newHasMany($query, $parent, $localKey, $limit) - { - return new HasMany($query, $parent, $localKey, $limit); - } - - /** - * Get a relationship. - * - * @param string $key - * @return mixed - */ - public function getRelationValue($key) - { - // If the key already exists in the relationships array, it just means the - // relationship has already been loaded, so we'll just return it out of - // here because there is no need to query within the relations twice. - if ($this->relationLoaded($key)) { - return $this->relations[$key]; - } - - if (method_exists($this, $key)) { - return $this->getRelationshipFromMethod($key); - } - } - - protected function getRelationshipFromMethod($method) - { - $relation = $this->{$method}(); - - if (!$relation) { // Empty relationships return null to avoid calling the API - return null; - } - - return tap($relation->get(), function ($results) use ($method) { - $this->setRelation($method, $results); - }); - } - - /** - * Determine if the given relation is loaded. - * - * @param string $key - * @return bool - */ - public function relationLoaded($key) - { - return array_key_exists($key, $this->relations); - } - - public function getMorphClass() - { - $morphMap = Relation::morphMap(); - - if (!empty($morphMap) && in_array(static::class, $morphMap)) { - return array_search(static::class, $morphMap, true); - } - - return static::class; - } -} diff --git a/app/Models/Api/Artwork.php b/app/Models/Api/Artwork.php index b5f382ae1e..985062cf6d 100644 --- a/app/Models/Api/Artwork.php +++ b/app/Models/Api/Artwork.php @@ -3,7 +3,7 @@ namespace App\Models\Api; use App\Libraries\Api\Models\BaseApiModel; -use App\Libraries\Api\Builders\ApiModelBuilder; +use Aic\Hub\Foundation\Library\Api\Builders\ApiModelBuilder; use App\Models\Behaviors\HasMediasApi; use App\Models\Behaviors\HasFeaturedRelated; use App\Helpers\DateHelpers; diff --git a/app/Models/Api/Asset.php b/app/Models/Api/Asset.php index c3a1c74143..02d845495c 100644 --- a/app/Models/Api/Asset.php +++ b/app/Models/Api/Asset.php @@ -3,7 +3,7 @@ namespace App\Models\Api; use App\Libraries\Api\Models\BaseApiModel; -use App\Libraries\Api\Builders\ApiModelBuilder; +use Aic\Hub\Foundation\Library\Api\Builders\ApiModelBuilder; use App\Models\Behaviors\HasMediasApi; use App\Helpers\ImageHelpers; use Illuminate\Support\Str; diff --git a/app/Models/Api/Exhibition.php b/app/Models/Api/Exhibition.php index 6ae6471a7b..1ecc1368cf 100644 --- a/app/Models/Api/Exhibition.php +++ b/app/Models/Api/Exhibition.php @@ -5,7 +5,7 @@ use Illuminate\Support\Carbon; use App\Models\Behaviors\HasFeaturedRelated; use App\Libraries\Api\Models\BaseApiModel; -use App\Libraries\Api\Builders\ApiModelBuilder; +use Aic\Hub\Foundation\Library\Api\Builders\ApiModelBuilder; use App\Helpers\StringHelpers; use Database\Factories\Api\HasApiFactory; diff --git a/app/Models/Api/Search.php b/app/Models/Api/Search.php index 47dafa6385..432f9f1499 100644 --- a/app/Models/Api/Search.php +++ b/app/Models/Api/Search.php @@ -3,13 +3,13 @@ namespace App\Models\Api; use App\Libraries\Api\Models\BaseApiModel; -use App\Libraries\Api\Builders\ApiModelBuilderSearch; +use Aic\Hub\Foundation\Library\Api\Builders\ApiModelBuilderSearch; use App\Libraries\Search\Filters\Departments as DepartmentFilter; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Str; use App\Helpers\DateHelpers; -use App\Libraries\Api\Builders\ApiModelBuilder; +use Aic\Hub\Foundation\Library\Api\Builders\ApiModelBuilder; class Search extends BaseApiModel { diff --git a/app/Models/Behaviors/HasMediasApi.php b/app/Models/Behaviors/HasMediasApi.php index db29ff14f9..4673f1cdb5 100644 --- a/app/Models/Behaviors/HasMediasApi.php +++ b/app/Models/Behaviors/HasMediasApi.php @@ -7,6 +7,40 @@ trait HasMediasApi { /** + * Replaces `imageFront()`. This more closely resembles Twill 3's + * `HasMedias::image()` method, see: + * https://twillcms.com/docs/api/3.x/A17/Twill/Models/Behaviors/HasMedias.html#method_image + * + * Define $mediasParams as they are defined in the Twill documentation: + * https://twillcms.com/docs/form-fields/medias.html#content-example + * + * An additional `field` key may be defined for a crop that specifies the field + * on the API record that contains the image ID. The default is `image_id`. + * + * Example: + * public $mediasParams = [ + * 'iiif' => [ + * 'default' => [ + * [ + * 'name' => 'default', + * 'field' => 'image_id', + * 'height' => 800, + * 'width' => 800, + * ] + * ] + * ] + * ]; + */ + public function image($role, $crop = 'default', $params = []) + { + $cropParams = $this->getMediasParams()[$role][$crop][0]; + $imageField = $cropParams['field'] ?? 'image_id'; + return DamsImageService::getUrl($this->{$imageField}, $cropParams + $params); + } + + /** + * DEPRECATED + * * You have to define roles and crop on the API model. * * 'field': Optional, API field with the image ID (if not defined, default to image_id) @@ -50,9 +84,9 @@ public function imageFront($role = 'hero', $crop = null) } } - public function defaultCmsImage($params = []) + public function cmsImage($role, $crop = 'default', $params = []) { - $image = DamsImageService::getImage($this, 'image_id', 100, 100); + $image = DamsImageService::getImage($this, $this->getImageField($role, $crop), $params['w'], $params['h']); if ($image) { return $image['src']; @@ -61,6 +95,18 @@ public function defaultCmsImage($params = []) return ImageService::getTransparentFallbackUrl($params); } + public function defaultCmsImage($params = []) + { + return $this->cmsImage('iiif', 'default', ['w' => 100, 'h' => 100]); + } + + public function getMediasParams(): array + { + return (isset($this->mediasParams) && is_array($this->mediasParams)) + ? $this->mediasParams + : config('twill.default_crops'); + } + protected function getImageField($role, $crop) { if (isset($this->mediasParams[$role][$crop]['field'])) { diff --git a/app/Presenters/Admin/ArtistPresenter.php b/app/Presenters/Admin/ArtistPresenter.php index 14a5b2b7b0..7b70897f7a 100644 --- a/app/Presenters/Admin/ArtistPresenter.php +++ b/app/Presenters/Admin/ArtistPresenter.php @@ -15,7 +15,7 @@ public function itemprops() ]; } - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Presenters/Admin/ArtworkPresenter.php b/app/Presenters/Admin/ArtworkPresenter.php index 2be8699165..3a85024f93 100644 --- a/app/Presenters/Admin/ArtworkPresenter.php +++ b/app/Presenters/Admin/ArtworkPresenter.php @@ -17,7 +17,7 @@ */ class ArtworkPresenter extends BasePresenter { - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Presenters/Admin/CategoryTermPresenter.php b/app/Presenters/Admin/CategoryTermPresenter.php index 245d4c7da1..72d0b3e095 100644 --- a/app/Presenters/Admin/CategoryTermPresenter.php +++ b/app/Presenters/Admin/CategoryTermPresenter.php @@ -6,7 +6,7 @@ class CategoryTermPresenter extends BasePresenter { - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Presenters/Admin/DepartmentPresenter.php b/app/Presenters/Admin/DepartmentPresenter.php index 4ab9290676..0785154216 100644 --- a/app/Presenters/Admin/DepartmentPresenter.php +++ b/app/Presenters/Admin/DepartmentPresenter.php @@ -6,7 +6,7 @@ class DepartmentPresenter extends BasePresenter { - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Presenters/Admin/ExhibitionPresenter.php b/app/Presenters/Admin/ExhibitionPresenter.php index c41aed8d25..ea55b4e66d 100644 --- a/app/Presenters/Admin/ExhibitionPresenter.php +++ b/app/Presenters/Admin/ExhibitionPresenter.php @@ -291,7 +291,7 @@ protected function closingSoonLink() } } - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Presenters/Admin/GalleryPresenter.php b/app/Presenters/Admin/GalleryPresenter.php index dc57e5e028..10921b1b13 100644 --- a/app/Presenters/Admin/GalleryPresenter.php +++ b/app/Presenters/Admin/GalleryPresenter.php @@ -6,7 +6,7 @@ class GalleryPresenter extends BasePresenter { - protected function augmented() + public function augmented() { return $this->entity->getAugmentedModel() ? 'Yes' : 'No'; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bd96ef635a..251d60f409 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,7 +5,7 @@ use A17\Twill\Http\Controllers\Front\Helpers\Seo; use A17\Twill\Models\File; use App\Models\Hour; -use App\Libraries\Api\Consumers\GuzzleApiConsumer; +use Aic\Hub\Foundation\Library\Api\Consumers\GuzzleApiConsumer; use App\Libraries\EmbedConverterService; use App\Libraries\DamsImageService; use App\Observers\FileObserver; diff --git a/composer.json b/composer.json index 0bc72a387b..6d6093c16e 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,8 @@ "type": "project", "repositories": [ { - "type": "vcs", - "url": "https://github.com/art-institute-of-chicago/data-hub-foundation.git" + "type": "path", + "url": "../data-hub-foundation" }, { "type": "package", @@ -42,12 +42,12 @@ ], "require": { "php": "^8.1", - "aic/data-hub-foundation": "^2.0", + "aic/data-hub-foundation": "dev-feature/api-models", "area17/twill": "^3.0", "chillerlan/php-qrcode": "^5.0", "eluceo/ical": "2.0", "erusev/parsedown": "^1.7", - "guzzlehttp/guzzle": "^7.0.1", + "guzzlehttp/guzzle": "^7.9", "intervention/httpauth": "^4.0", "kalnoy/nestedset": "^6.0", "laravel/framework": "^10.0", diff --git a/composer.lock b/composer.lock index 879eca4b2b..456de137b1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,15 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "afb7a824c1ec47b590a8761a7dc6a596", + "content-hash": "bd199dd2bc6d8f71ee804a8fe52b3059", "packages": [ { "name": "aic/data-hub-foundation", - "version": "2.3", - "source": { - "type": "git", - "url": "https://github.com/art-institute-of-chicago/data-hub-foundation.git", - "reference": "cc6d507a4ebdc5f46a578580158e589d738bc6ff" - }, + "version": "dev-feature/api-models", "dist": { - "type": "zip", - "url": "https://api.github.com/repos/art-institute-of-chicago/data-hub-foundation/zipball/cc6d507a4ebdc5f46a578580158e589d738bc6ff", - "reference": "cc6d507a4ebdc5f46a578580158e589d738bc6ff", - "shasum": "" + "type": "path", + "url": "../data-hub-foundation", + "reference": "cc6d507a4ebdc5f46a578580158e589d738bc6ff" }, "require": { "composer": "^2.2.0", @@ -80,11 +74,9 @@ } ], "description": "Shared components for our data hub services.", - "support": { - "source": "https://github.com/art-institute-of-chicago/data-hub-foundation/tree/2.3", - "issues": "https://github.com/art-institute-of-chicago/data-hub-foundation/issues" - }, - "time": "2023-09-20T21:14:09+00:00" + "transport-options": { + "relative": true + } }, { "name": "area17/twill", @@ -2291,22 +2283,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2315,11 +2307,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -2397,7 +2389,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -2413,28 +2405,28 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -2480,7 +2472,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -2496,20 +2488,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.0", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -2523,9 +2515,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2596,7 +2588,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -2612,7 +2604,7 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:06:02+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "guzzlehttp/uri-template", @@ -6128,16 +6120,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -6174,26 +6166,26 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -6217,7 +6209,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -6229,9 +6221,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -8434,16 +8426,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -8452,7 +8444,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -8481,7 +8473,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -8497,7 +8489,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/dom-crawler", @@ -9509,34 +9501,30 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9576,7 +9564,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -9592,36 +9580,33 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9660,7 +9645,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -9676,24 +9661,24 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -9703,12 +9688,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9743,7 +9725,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -9759,83 +9741,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", @@ -14747,7 +14653,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "aic/data-hub-foundation": 20, + "salesforce-mc/fuel-sdk-php": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/database/factories/Api/ApiFactory.php b/database/factories/Api/ApiFactory.php index 504d5b6e30..1d87529a5b 100644 --- a/database/factories/Api/ApiFactory.php +++ b/database/factories/Api/ApiFactory.php @@ -3,7 +3,7 @@ namespace Database\Factories\Api; use App\Libraries\Api\Models\BaseApiModel as ApiModel; -use App\Libraries\Api\Models\ApiCollection; +use Aic\Hub\Foundation\Library\Api\Models\ApiCollection; use Closure; use Faker\Generator as FakerGenerator; use Illuminate\Container\Container; diff --git a/public/main-buckets.html b/public/main-buckets.html new file mode 100644 index 0000000000..de77c081c2 --- /dev/null +++ b/public/main-buckets.html @@ -0,0 +1 @@ +