diff --git a/app/Api/V1/Controllers/Search/AccountController.php b/app/Api/V1/Controllers/Search/AccountController.php new file mode 100644 index 0000000000..4ab07cb6e5 --- /dev/null +++ b/app/Api/V1/Controllers/Search/AccountController.php @@ -0,0 +1,94 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Search; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Support\Http\Api\AccountFilter; +use FireflyIII\Support\Search\AccountSearch; +use FireflyIII\Transformers\AccountTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; + +/** + * Class AccountController + */ +class AccountController extends Controller +{ + use AccountFilter; + + /** @var array */ + private $validFields; + + public function __construct() + { + parent::__construct(); + $this->validFields = [ + AccountSearch::SEARCH_ALL, + AccountSearch::SEARCH_ID, + AccountSearch::SEARCH_NAME, + AccountSearch::SEARCH_IBAN, + AccountSearch::SEARCH_NUMBER, + ]; + } + + /** + * @param Request $request + * + * @return JsonResponse|Response + */ + public function search(Request $request) + { + $manager = $this->getManager(); + $query = $request->get('query'); + $field = $request->get('field'); + $type = $request->get('type') ?? 'all'; + if ('' === $query || !in_array($field, $this->validFields, true)) { + return response(null, 422); + } + $types = $this->mapAccountTypes($type); + /** @var AccountSearch $search */ + $search = app(AccountSearch::class); + $search->setUser(auth()->user()); + $search->setTypes($types); + $search->setField($field); + $search->setQuery($query); + + $accounts = $search->search(); + + /** @var AccountTransformer $transformer */ + $transformer = app(AccountTransformer::class); + $transformer->setParameters($this->parameters); + $count = $accounts->count(); + $paginator = new LengthAwarePaginator($accounts, $count, $count, 1); + + $resource = new FractalCollection($accounts, $transformer, 'accounts'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/Search/TransactionController.php b/app/Api/V1/Controllers/Search/TransactionController.php new file mode 100644 index 0000000000..af53885e7a --- /dev/null +++ b/app/Api/V1/Controllers/Search/TransactionController.php @@ -0,0 +1,69 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Search; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Support\Search\SearchInterface; +use FireflyIII\Support\Search\TransactionSearch; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response; + +/** + * Class TransactionController + */ +class TransactionController extends Controller +{ + /** @var string */ + const SEARCH_ALL = 'all'; + /** @var string */ + const SEARCH_DESCRIPTION = 'description'; + /** @var string */ + const SEARCH_NOTES = 'notes'; + /** @var string */ + const SEARCH_ACCOUNTS = 'accounts'; + /** @var array */ + private $validFields; + + public function __construct() + { + parent::__construct(); + $this->validFields = [ + self::SEARCH_ALL, + self::SEARCH_DESCRIPTION, + self::SEARCH_NOTES, + self::SEARCH_ACCOUNTS, + ]; + } + + /** + * @param Request $request + * + * @return JsonResponse|Response + */ + public function search(Request $request) + { + die('the route is present but nobody\'s home.'); + } + +} \ No newline at end of file diff --git a/app/Support/Search/AccountSearch.php b/app/Support/Search/AccountSearch.php new file mode 100644 index 0000000000..94a6434597 --- /dev/null +++ b/app/Support/Search/AccountSearch.php @@ -0,0 +1,146 @@ +. + */ + +namespace FireflyIII\Support\Search; + + +use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Collection; + +/** + * Class AccountSearch + */ +class AccountSearch implements GenericSearchInterface +{ + /** @var string */ + public const SEARCH_ALL = 'all'; + /** @var string */ + public const SEARCH_NAME = 'name'; + /** @var string */ + public const SEARCH_IBAN = 'iban'; + /** @var string */ + public const SEARCH_NUMBER = 'number'; + /** @var string */ + public const SEARCH_ID = 'id'; + + /** @var string */ + private $field; + /** @var string */ + private $query; + /** @var array */ + private $types; + + /** @var User */ + private $user; + + public function __construct() + { + $this->types = []; + } + + /** + * @return Collection + */ + public function search(): Collection + { + + $query = $this->user->accounts() + ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id') + ->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id') + ->whereIn('account_types.type', $this->types); + $like = sprintf('%%%s%%', $this->query); + $originalQuery = $this->query; + switch ($this->field) { + case self::SEARCH_ALL: + $query->where( + static function (Builder $q) use ($like) { + $q->where('accounts.id', 'LIKE', $like); + $q->orWhere('accounts.name', 'LIKE', $like); + $q->orWhere('accounts.iban', 'LIKE', $like); + } + ); + // meta data: + $query->orWhere( + static function (Builder $q) use ($originalQuery) { + $json = json_encode($originalQuery, JSON_THROW_ON_ERROR); + $q->where('account_meta.name', 'account_number'); + $q->where('account_meta.data', $json); + } + ); + break; + case self::SEARCH_ID: + $query->where('accounts.id', '=', (int)$originalQuery); + break; + case self::SEARCH_NAME: + $query->where('accounts.name', 'LIKE', $like); + break; + case self::SEARCH_IBAN: + $query->where('accounts.iban', 'LIKE', $like); + break; + case self::SEARCH_NUMBER: + // meta data: + $query->Where( + static function (Builder $q) use ($originalQuery) { + $json = json_encode($originalQuery, JSON_THROW_ON_ERROR); + $q->where('account_meta.name', 'account_number'); + $q->where('account_meta.data', $json); + } + ); + break; + } + + return $query->get(['accounts.*']); + } + + /** + * @param string $field + */ + public function setField(string $field): void + { + $this->field = $field; + } + + /** + * @param string $query + */ + public function setQuery(string $query): void + { + $this->query = $query; + } + + /** + * @param array $types + */ + public function setTypes(array $types): void + { + $this->types = $types; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + +} \ No newline at end of file diff --git a/app/Support/Search/GenericSearchInterface.php b/app/Support/Search/GenericSearchInterface.php new file mode 100644 index 0000000000..47dacfbbdd --- /dev/null +++ b/app/Support/Search/GenericSearchInterface.php @@ -0,0 +1,28 @@ +. + */ + +namespace FireflyIII\Support\Search; + + +interface GenericSearchInterface +{ + +} \ No newline at end of file diff --git a/app/Support/Search/TransactionSearch.php b/app/Support/Search/TransactionSearch.php new file mode 100644 index 0000000000..e8ab4998cd --- /dev/null +++ b/app/Support/Search/TransactionSearch.php @@ -0,0 +1,30 @@ +. + */ + +namespace FireflyIII\Support\Search; + +/** + * Class TransactionSearch + */ +class TransactionSearch implements GenericSearchInterface +{ + +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 7299446288..7eb615c0d7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -334,6 +334,17 @@ Route::group( } ); +Route::group( + ['namespace' => 'FireflyIII\Api\V1\Controllers\Search', 'prefix' => 'search', + 'as' => 'api.v1.search.'], + static function () { + + // Attachment API routes: + Route::get('transactions', ['uses' => 'TransactionController@search', 'as' => 'transactions']); + Route::get('accounts', ['uses' => 'AccountController@search', 'as' => 'accounts']); + } +); + Route::group( ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'summary', 'as' => 'api.v1.summary.'],