mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Halfway rule API.
This commit is contained in:
parent
7abcdea816
commit
36a6981329
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
144
app/Api/V1/Requests/RuleRequest.php
Normal file
144
app/Api/V1/Requests/RuleRequest.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.',
|
||||
|
@ -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 () {
|
||||
|
Loading…
Reference in New Issue
Block a user