Various API updates.

This commit is contained in:
James Cole 2018-12-03 07:18:05 +01:00
parent 0256337855
commit 050334a648
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
10 changed files with 212 additions and 39 deletions

View File

@ -78,7 +78,7 @@ class UserController extends Controller
{
/** @var User $admin */
$admin = auth()->user();
if ($this->repository->hasRole($admin, 'owner')) {
if ($admin->id !== $user->id && $this->repository->hasRole($admin, 'owner')) {
$this->repository->destroy($user);
return response()->json([], 204);

View File

@ -47,6 +47,7 @@ class AccountRequest extends Request
*/
public function getAll(): array
{
$data = [
'name' => $this->string('name'),
'active' => $this->boolean('active'),
@ -95,28 +96,34 @@ class AccountRequest extends Request
$types = implode(',', array_keys(config('firefly.subTitlesByIdentifier')));
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
$rules = [
'name' => 'required|min:1|uniqueAccountForUser',
'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
'iban' => 'iban|nullable',
'bic' => 'bic|nullable',
'virtual_balance' => 'numeric|nullable',
'currency_id' => 'numeric|exists:transaction_currencies,id|required_without:currency_code',
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:currency_id',
'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser',
'account_role' => 'in:' . $accountRoles . '|required_if:type,asset',
'active' => 'required|boolean',
'include_net_worth' => 'required|boolean',
'cc_type' => 'in:' . $ccPaymentTypes . '|required_if:account_role,ccAsset',
'cc_monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:cc_type,monthlyFull',
'type' => 'required|in:' . $types,
'notes' => 'min:0|max:65536',
'name' => 'required|min:1|uniqueAccountForUser',
'type' => 'required|in:' . $types,
'active' => 'required|boolean',
'account_role' => 'in:' . $accountRoles . '|required_if:type,asset',
'currency_id' => 'numeric|exists:transaction_currencies,id|required_without:currency_code',
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:currency_id',
'notes' => 'min:0|max:65536',
'monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:cc_type,monthlyFull',
'credit_card_type' => 'in:' . $ccPaymentTypes . '|required_if:account_role,ccAsset',
'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser',
'iban' => 'iban|nullable',
'bic' => 'bic|nullable',
'virtual_balance' => 'numeric|nullable',
'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
'include_net_worth' => 'required|boolean',
// required fields for liabilities:
'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage,credit card',
'liability_amount' => 'required_if:type,liability|min:0|numeric',
'liability_start_date' => 'required_if:type,liability|date',
'interest' => 'required_if:type,liability|between:0,100|numeric',
'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly',
'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage,credit card',
'liability_amount' => 'required_if:type,liability|min:0|numeric',
'liability_start_date' => 'required_if:type,liability|date',
'interest' => 'required_if:type,liability|between:0,100|numeric',
'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly',
];
switch ($this->method()) {

View File

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\User;
@ -68,6 +69,7 @@ class UserRequest extends Request
'email' => $this->string('email'),
'blocked' => $this->boolean('blocked'),
'blocked_code' => $this->string('blocked_code'),
'role' => $this->string('role'),
];
return $data;
@ -82,8 +84,9 @@ class UserRequest extends Request
{
$rules = [
'email' => 'required|email|unique:users,email,',
'blocked' => 'required|boolean',
'blocked' => [new IsBoolean],
'blocked_code' => 'in:email_changed',
'role' => 'in:owner,demo',
];
switch ($this->method()) {
default:

View File

@ -288,7 +288,7 @@ class UserRepository implements UserRepositoryInterface
*/
public function store(array $data): User
{
return User::create(
$user = User::create(
[
'blocked' => $data['blocked'] ?? false,
'blocked_code' => $data['blocked_code'] ?? null,
@ -296,6 +296,12 @@ class UserRepository implements UserRepositoryInterface
'password' => str_random(24),
]
);
$role = $data['role'] ?? '';
if ('' !== $role) {
$this->attachRole($user, $role);
}
return $user;
}
/**

56
app/Rules/IsBoolean.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace FireflyIII\Rules;
use Illuminate\Contracts\Validation\Rule;
/**
* Class IsBoolean
*/
class IsBoolean implements Rule
{
/**
* Get the validation error message.
*
* @return string
*/
public function message(): string
{
return (string)trans('validation.boolean');
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
*
* @return bool
*/
public function passes($attribute, $value): bool
{
if (\is_bool($value)) {
return true;
}
if (\is_int($value) && 0 === $value) {
return true;
}
if (\is_int($value) && 1 === $value) {
return true;
}
if (\is_string($value) && '1' === $value) {
return true;
}
if (\is_string($value) && '0' === $value) {
return true;
}
if (\is_string($value) && 'true' === $value) {
return true;
}
if (\is_string($value) && 'false' === $value) {
return true;
}
return false;
}
}

View File

@ -196,6 +196,7 @@ class AccountTransformer extends TransformerAbstract
'name' => $account->name,
'active' => 1 === (int)$account->active,
'type' => $type,
'account_role' => $role,
'currency_id' => $currencyId,
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
@ -211,13 +212,12 @@ class AccountTransformer extends TransformerAbstract
'virtual_balance' => round($account->virtual_balance, $decimalPlaces),
'opening_balance' => $openingBalance,
'opening_balance_date' => $openingBalanceDate,
'role' => $role,
'liability_type' => $type,
'liability_amount' => $openingBalance,
'liability_start_date' => $openingBalanceDate,
'interest' => $interest,
'interest_period' => $interestPeriod,
'include_in_net_worth' => $includeNetworth,
'include_net_worth' => $includeNetworth,
'links' => [
[
'rel' => 'self',

View File

@ -194,7 +194,7 @@ class UserTransformer extends TransformerAbstract
'created_at' => $user->created_at->toAtomString(),
'email' => $user->email,
'blocked' => 1 === (int)$user->blocked,
'blocked_code' => $user->blocked_code,
'blocked_code' => '' === $user->blocked_code ? null : $user->blocked_code,
'role' => $role,
'links' => [
[

View File

@ -628,6 +628,11 @@ class FireflyValidator extends Validator
{
/** @var array $search */
$search = Config::get('firefly.accountTypeByIdentifier.' . $type);
if (null === $search) {
return false;
}
/** @var Collection $accountTypes */
$accountTypes = AccountType::whereIn('type', $search)->get();
$ignore = (int)($parameters[0] ?? 0.0);

View File

@ -314,6 +314,7 @@ return [
'column_sepa-ci' => 'SEPA Creditor Identifier',
'column_sepa-ep' => 'SEPA External Purpose',
'column_sepa-country' => 'SEPA Country Code',
'column_sepa-batch-id' => 'SEPA Batch ID',
'column_tags-comma' => 'Tags (comma separated)',
'column_tags-space' => 'Tags (space separated)',
'column_account-number' => 'Asset account (account number)',

View File

@ -59,9 +59,8 @@ class UserControllerTest extends TestCase
$userRepository = $this->mock(UserRepositoryInterface::class);
$userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
$userRepository->shouldReceive('destroy')->once();
// create a user first:
// call API
$response = $this->delete('/api/v1/users/' . $this->user()->id);
$response = $this->delete('/api/v1/users/2');
$response->assertStatus(204);
}
@ -81,11 +80,28 @@ class UserControllerTest extends TestCase
$user = User::create(['email' => 'some@newu' . random_int(1, 10000) . 'ser.nl', 'password' => 'hello', 'blocked' => 0]);
// call API
$response = $this->delete('/api/v1/users/' . $user->id);
$response = $this->delete('/api/v1/users/' . $user->id, [], ['Accept' => 'application/json']);
$response->assertStatus(302);
$this->assertDatabaseHas('users', ['id' => $user->id]);
}
/**
* Cannot delete yourself.
*
* @covers \FireflyIII\Api\V1\Controllers\UserController
* @covers \FireflyIII\Api\V1\Requests\UserRequest
*/
public function testDeleteYourself(): void
{
$userRepository = $this->mock(UserRepositoryInterface::class);
$userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true);
// create a user first:
// call API
$response = $this->delete('/api/v1/users/' . $this->user()->id, [], ['Accept' => 'application/json']);
$response->assertStatus(500);
$response->assertSee('No access to method.');
}
/**
* Show list of users.
*
@ -103,13 +119,11 @@ class UserControllerTest extends TestCase
$repository->shouldReceive('all')->withAnyArgs()->andReturn($users)->once();
// test API
$response = $this->get('/api/v1/users');
$response = $this->get('/api/v1/users', ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertJson(['data' => [],]);
$response->assertJson(['meta' => ['pagination' => ['total' => 10, 'count' => 10, 'current_page' => 1, 'total_pages' => 1]],]);
$response->assertJson(
['links' => ['self' => true, 'first' => true, 'last' => true,],]
);
$response->assertJson(['links' => ['self' => true, 'first' => true, 'last' => true,],]);
$response->assertHeader('Content-Type', 'application/vnd.api+json');
}
@ -125,7 +139,7 @@ class UserControllerTest extends TestCase
$repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->once()->andReturn(true);
// test API
$response = $this->get('/api/v1/users/' . $user->id);
$response = $this->get('/api/v1/users/' . $user->id, ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertSee($user->email);
}
@ -139,8 +153,7 @@ class UserControllerTest extends TestCase
public function testStoreBasic(): void
{
$data = [
'email' => 'some_new@user' . random_int(1, 10000) . '.com',
'blocked' => 0,
'email' => 'some_new@user' . random_int(1, 10000) . '.com',
];
// mock
@ -149,7 +162,32 @@ class UserControllerTest extends TestCase
$userRepos->shouldReceive('store')->once()->andReturn($this->user());
// test API
$response = $this->post('/api/v1/users', $data);
$response = $this->post('/api/v1/users', $data, ['Content-Type' => 'application/x-www-form-urlencoded']);
$response->assertStatus(200);
$response->assertSee($this->user()->email);
}
/**
* Store new user using JSON.
*
* @covers \FireflyIII\Api\V1\Controllers\UserController
* @covers \FireflyIII\Api\V1\Requests\UserRequest
*/
public function testStoreBasicJson(): void
{
$data = [
'email' => 'some_new@user' . random_int(1, 10000) . '.com',
'blocked' => true,
'blocked_code' => 'email_changed',
];
// mock
$userRepos = $this->mock(UserRepositoryInterface::class);
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->twice()->andReturn(true);
$userRepos->shouldReceive('store')->once()->andReturn($this->user());
// test API
$response = $this->postJson('/api/v1/users', $data, ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertSee($this->user()->email);
}
@ -185,6 +223,37 @@ class UserControllerTest extends TestCase
);
}
/**
* Store user with info already used.
*
* @covers \FireflyIII\Api\V1\Controllers\UserController
* @covers \FireflyIII\Api\V1\Requests\UserRequest
*/
public function testStoreNotUniqueJson(): void
{
$data = [
'email' => $this->user()->email,
'blocked' => 0,
];
// mock
$userRepos = $this->mock(UserRepositoryInterface::class);
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->twice()->andReturn(true);
// test API
$response = $this->postJson('/api/v1/users', $data, ['Accept' => 'application/json']);
$response->assertStatus(422);
$response->assertExactJson(
[
'message' => 'The given data was invalid.',
'errors' => [
'email' => [
'The email address has already been taken.',
],
],
]
);
}
/**
* Update user.
*
@ -208,9 +277,35 @@ class UserControllerTest extends TestCase
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->twice()->andReturn(true);
// call API
$response = $this->put('/api/v1/users/' . $user->id, $data);
$response = $this->put('/api/v1/users/' . $user->id, $data, ['Accept' => 'application/json']);
$response->assertStatus(200);
}
/**
* Update user.
*
* @covers \FireflyIII\Api\V1\Controllers\UserController
* @covers \FireflyIII\Api\V1\Requests\UserRequest
*/
public function testUpdateJson(): void
{
// create a user first:
$user = User::create(['email' => 'some@newu' . random_int(1, 10000) . 'ser.nl', 'password' => 'hello', 'blocked' => 0]);
// data:
$data = [
'email' => 'some-new@email' . random_int(1, 10000) . '.com',
'blocked' => 0,
];
// mock
$userRepos = $this->mock(UserRepositoryInterface::class);
$userRepos->shouldReceive('update')->once()->andReturn($user);
$userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->twice()->andReturn(true);
// call API
$response = $this->putJson('/api/v1/users/' . $user->id, $data, ['Accept' => 'application/json']);
$response->assertStatus(200);
}
}