Halfway rule API.

This commit is contained in:
James Cole 2018-06-30 16:46:51 +02:00
parent 7abcdea816
commit 36a6981329
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
8 changed files with 361 additions and 25 deletions

View File

@ -23,13 +23,30 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\RuleRequest;
use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Transformers\PiggyBankTransformer;
use FireflyIII\Transformers\RuleTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Validation\Validator;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class RuleController
*/
class RuleController extends Controller
{
/** @var RuleRepositoryInterface */
private $ruleRepository;
public function __construct()
{
parent::__construct();
@ -38,7 +55,9 @@ class RuleController extends Controller
/** @var User $user */
$user = auth()->user();
// todo add local repositories.
$this->ruleRepository = app(RuleRepositoryInterface::class);
$this->ruleRepository->setUser($user);
return $next($request);
}
);
@ -67,7 +86,28 @@ class RuleController extends Controller
*/
public function index(Request $request): JsonResponse
{
// todo implement.
// create some objects:
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// types to get, page size:
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
// get list of budgets. Count it and split it.
$collection = $this->ruleRepository->getAll();
$count = $collection->count();
$rules = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($rules, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.piggy_banks.index') . $this->buildParams());
// present to user.
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new FractalCollection($rules, new RuleTransformer($this->parameters), 'rules');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
@ -75,13 +115,23 @@ class RuleController extends Controller
* List single resource.
*
* @param Request $request
* @param string $object
* @param Rule $rule
*
* @return JsonResponse
*/
public function show(Request $request, string $object): JsonResponse
public function show(Request $request, Rule $rule): JsonResponse
{
// todo implement me.
$manager = new Manager();
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($rule, new RuleTransformer($this->parameters), 'rules');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
@ -92,10 +142,11 @@ class RuleController extends Controller
*
* @return JsonResponse
*/
public function store(Request $request): JsonResponse
public function store(RuleRequest $request): JsonResponse
{
// todo replace code and replace request object.
print_r($request->getAll());
print_r($request->all());
exit;
}
/**

View File

@ -0,0 +1,144 @@
<?php
/**
* RuleRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
use Illuminate\Validation\Validator;
/**
* Class RuleRequest
*/
class RuleRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* @return array
*/
public function getAll(): array
{
$data = [
'title' => $this->string('title'),
'rule_group_id' => $this->integer('rule_group_id'),
'rule_group_title' => $this->string('rule_group_title'),
'trigger' => $this->string('trigger'),
'strict' => $this->boolean('strict'),
'stop_processing' => $this->boolean('stop_processing'),
'active' => $this->boolean('active'),
'rule-triggers' => [],
'rule-actions' => [],
];
foreach ($this->get('rule-triggers') as $trigger) {
$data['rule-triggers'][] = [
'name' => $trigger['name'],
'value' => $trigger['value'],
];
}
}
/**
* @return array
*/
public function rules(): array
{
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validActions = array_keys(config('firefly.rule-actions'));
// some actions require text:
$contextActions = implode(',', config('firefly.rule-actions-text'));
$rules = [
'title' => 'required|between:1,100|uniqueObjectForUser:rules,title',
'description' => 'between:1,5000|nullable',
'rule_group_id' => 'required|belongsToUser:rule_groups|required_without:rule_group_title',
'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title',
'trigger' => 'required|in:store-journal,update-journal',
'rule-triggers.*.name' => 'required|in:' . implode(',', $validTriggers),
'rule-triggers.*.value' => 'required|min:1|ruleTriggerValue', //
'rule-actions.*.name' => 'required|in:' . implode(',', $validActions),
'rule-actions.*.value' => 'required_if:rule-action.*.type,' . $contextActions . '|ruleActionValue',
'strict' => 'required|boolean',
'stop_processing' => 'required|boolean',
'active' => 'required|boolean',
];
return $rules;
}
/**
* Configure the validator instance.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
$this->atLeastOneTrigger($validator);
$this->atLeastOneAction($validator);
}
);
}
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneAction(Validator $validator): void
{
$data = $validator->getData();
$repetitions = $data['rule-actions'] ?? [];
// need at least one transaction
if (\count($repetitions) === 0) {
$validator->errors()->add('title', trans('validation.at_least_one_action'));
}
}
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneTrigger(Validator $validator): void
{
$data = $validator->getData();
$repetitions = $data['rule-triggers'] ?? [];
// need at least one transaction
if (\count($repetitions) === 0) {
$validator->errors()->add('title', trans('validation.at_least_one_trigger'));
}
}
}

View File

@ -25,6 +25,7 @@ namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
@ -64,6 +65,7 @@ class Rule extends Model
'active' => 'boolean',
'order' => 'int',
'stop_processing' => 'boolean',
'id' => 'int',
'strict' => 'boolean',
];
/** @var array */
@ -98,18 +100,18 @@ class Rule extends Model
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function ruleGroup()
public function ruleGroup(): BelongsTo
{
return $this->belongsTo(RuleGroup::class);
}
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return HasMany
*/
public function ruleTriggers()
public function ruleTriggers(): HasMany
{
return $this->hasMany(RuleTrigger::class);
}
@ -117,16 +119,16 @@ class Rule extends Model
/**
* @param $value
*/
public function setDescriptionAttribute($value)
public function setDescriptionAttribute($value): void
{
$this->attributes['description'] = e($value);
}
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return BelongsTo
*/
public function user()
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

View File

@ -80,6 +80,16 @@ class RuleRepository implements RuleRepositoryInterface
return $rule;
}
/**
* Get all the users rules.
*
* @return Collection
*/
public function getAll(): Collection
{
return $this->user->rules()->with(['ruleGroup'])->get();
}
/**
* FIxXME can return null.
*

View File

@ -54,6 +54,13 @@ interface RuleRepositoryInterface
*/
public function find(int $ruleId): Rule;
/**
* Get all the users rules.
*
* @return Collection
*/
public function getAll(): Collection;
/**
* @return RuleGroup
*/

View File

@ -225,6 +225,8 @@ class FireflyValidator extends Validator
}
/**
* TODO lots of if-else because of API calls.
*
* @param $attribute
*
* @return bool
@ -234,12 +236,27 @@ class FireflyValidator extends Validator
// get the index from a string like "rule-action-value.2".
$parts = explode('.', $attribute);
$index = $parts[\count($parts) - 1];
if ($index === 'value') {
// user is coming from API.
$index = $parts[\count($parts) - 2];
}
$index = (int)$index;
// get actions from $this->data
$actions = [];
if (isset($this->data['rule-action']) && \is_array($this->data['rule-action'])) {
$actions = $this->data['rule-action'];
}
if (isset($this->data['rule-actions']) && \is_array($this->data['rule-actions'])) {
$actions = $this->data['rule-actions'];
}
// loop all rule-actions.
// check if rule-action-value matches the thing.
if (\is_array($this->data['rule-action'])) {
$name = $this->data['rule-action'][$index] ?? 'invalid';
$value = $this->data['rule-action-value'][$index] ?? false;
if (\is_array($actions)) {
$name = $this->getRuleActionName($index);
$value = $this->getRuleActionValue($index);
switch ($name) {
default:
@ -271,6 +288,8 @@ class FireflyValidator extends Validator
}
/**
* TODO This method uses a lot of if-then to handle the API calls as well. Fix.
*
* @param $attribute
*
* @return bool
@ -280,20 +299,60 @@ class FireflyValidator extends Validator
// get the index from a string like "rule-trigger-value.2".
$parts = explode('.', $attribute);
$index = $parts[\count($parts) - 1];
// if the index is not a number, then we might be dealing with an API $attribute
// which is formatted "rule-triggers.0.value"
if ($index === 'value') {
$index = $parts[\count($parts) - 2];
}
$index = (int)$index;
// get triggers from $this->data
$triggers = [];
if (isset($this->data['rule-trigger']) && \is_array($this->data['rule-trigger'])) {
$triggers = $this->data['rule-trigger'];
}
if (isset($this->data['rule-triggers']) && \is_array($this->data['rule-triggers'])) {
$triggers = $this->data['rule-triggers'];
}
// loop all rule-triggers.
// check if rule-value matches the thing.
if (\is_array($this->data['rule-trigger'])) {
if (\is_array($triggers)) {
$name = $this->getRuleTriggerName($index);
$value = $this->getRuleTriggerValue($index);
// break on some easy checks:
switch ($name) {
case 'amount_less':
case 'amount_more':
case 'amount_exactly':
$result = is_numeric($value);
if (false === $result) {
return false;
}
break;
case 'from_account_starts':
case 'from_account_ends':
case 'from_account_is':
case 'from_account_contains':
case 'to_account_starts':
case 'to_account_ends':
case 'to_account_is':
case 'to_account_contains':
case 'description_starts':
case 'description_ends':
case 'description_contains':
case 'description_is':
case 'category_is':
case 'budget_is':
case 'tag_is':
case 'currency_is':
case 'notes_contain':
case 'notes_start':
case 'notes_end':
case 'notes_are':
return \strlen($value) > 0;
break;
case 'transaction_type':
$count = TransactionType::where('type', $value)->count();
@ -489,23 +548,71 @@ class FireflyValidator extends Validator
}
/**
* TODO this method needs a lot of logic to be able to handle API calls. Fix that.
*
* @param int $index
*
* @return string
*/
private function getRuleTriggerName($index): string
private function getRuleActionName(int $index): string
{
return $this->data['rule-trigger'][$index] ?? 'invalid';
$name = $this->data['rule-action'][$index] ?? 'invalid';
if (!isset($this->data['rule-action'][$index])) {
$name = $this->data['rule-actions'][$index]['name'] ?? 'invalid';
}
return $name;
}
/**
* TODO this method needs a lot of logic to be able to handle API calls. Fix that.
*
* @param int $index
*
* @return string
*/
private function getRuleTriggerValue($index): string
private function getRuleActionValue(int $index): string
{
return $this->data['rule-trigger-value'][$index] ?? '';
$value = $this->data['rule-action-value'][$index] ?? '';
if (!isset($this->data['rule-action-value'][$index])) {
$value = $this->data['rule-actions'][$index]['value'] ?? '';
}
return $value;
}
/**
* TODO this method needs a lot of logic to be able to handle API calls. Fix that.
*
* @param int $index
*
* @return string
*/
private function getRuleTriggerName(int $index): string
{
$name = $this->data['rule-trigger'][$index] ?? 'invalid';
if (!isset($this->data['rule-trigger'][$index])) {
$name = $this->data['rule-triggers'][$index]['name'] ?? 'invalid';
}
return $name;
}
/**
* TODO this method needs a lot of logic to be able to handle API calls. Fix that.
*
* @param int $index
*
* @return string
*/
private function getRuleTriggerValue(int $index): string
{
$value = $this->data['rule-trigger-value'][$index] ?? '';
if (!isset($this->data['rule-trigger-value'][$index])) {
$value = $this->data['rule-triggers'][$index]['value'] ?? '';
}
return $value;
}
/**

View File

@ -46,6 +46,8 @@ return [
'belongs_to_user' => 'The value of :attribute is unknown.',
'accepted' => 'The :attribute must be accepted.',
'bic' => 'This is not a valid BIC.',
'at_least_one_trigger' => 'Rule must have at least one trigger',
'at_least_one_action' => 'Rule must have at least one action',
'base64' => 'This is not valid base64 encoded data.',
'model_id_invalid' => 'The given ID seems invalid for this model.',
'more' => ':attribute must be larger than zero.',

View File

@ -206,6 +206,19 @@ Route::group(
}
);
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rules', 'as' => 'api.v1.rules.'],
function () {
// Rules API routes:
Route::get('', ['uses' => 'RuleController@index', 'as' => 'index']);
Route::post('', ['uses' => 'RuleController@store', 'as' => 'store']);
Route::get('{rule}', ['uses' => 'RuleController@show', 'as' => 'show']);
Route::put('{rule}', ['uses' => 'RuleController@update', 'as' => 'update']);
Route::delete('{rule}', ['uses' => 'RuleController@delete', 'as' => 'delete']);
}
);
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'],
function () {