diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 619d9652e98d..ee3611d37459 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -11,65 +11,63 @@ use App\Ninja\Repositories\AccountRepository; use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Resource\Item; +use League\Fractal\Resource\Collection; use League\Fractal\Manager; use App\Ninja\Serializers\ArraySerializer; use App\Ninja\Transformers\AccountTransformer; +use App\Ninja\Transformers\UserAccountTransformer; +use App\Http\Controllers\BaseAPIController; -class AccountApiController extends Controller +class AccountApiController extends BaseAPIController { protected $accountRepo; public function __construct(AccountRepository $accountRepo) { + parent::__construct(); + $this->accountRepo = $accountRepo; } public function login(Request $request) { if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) { + sleep(ERROR_DELAY); return 'Invalid secret'; } if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) { return $this->processLogin($request); } else { + sleep(ERROR_DELAY); return 'Invalid credentials'; } } - public function index() - { - $manager = new Manager(); - $manager->setSerializer(new ArraySerializer()); - - $account = Auth::user()->account->load('users'); - $resource = new Item($account, new AccountTransformer, 'account'); - - $response = $manager->createData($resource)->toArray(); - $response = json_encode($response, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(); - - return Response::make($response, 200, $headers); - } - private function processLogin(Request $request) { + // Create a new token only if one does not already exist + $this->accountRepo->createTokens(Auth::user(), $request->token_name); - //Create a new token only if one does not already exist - $this->accountRepo->createToken('ios_api_token'); - - $manager = new Manager(); - $manager->setSerializer(new ArraySerializer()); - - $account = Auth::user()->account->load('users','tokens'); - $resource = new Item($account, new AccountTransformer, 'account'); - - $response = $manager->createData($resource)->toArray(); - $response = json_encode($response, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(); - - return Response::make($response, 200, $headers); + return $this->index(); } + public function index() + { + $users = $this->accountRepo->findUsers(Auth::user(), 'account.account_tokens'); + + $resource = new Collection($users, new UserAccountTransformer); + + return $this->returnData($resource); + } + + public function show($accountKey) + { + $account = $this->accountRepo->findByKey($accountKey); + + $resource = new Item($account, new AccountTransformer); + + return $this->returnData($resource); + } } diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php new file mode 100644 index 000000000000..b46213a7427a --- /dev/null +++ b/app/Http/Controllers/BaseAPIController.php @@ -0,0 +1,28 @@ +manager = new Manager(); + $this->manager->setSerializer(new ArraySerializer()); + } + + protected function returnData($resource) + { + $response = $this->manager->createData($resource)->toArray(); + $response = json_encode($response, JSON_PRETTY_PRINT); + $headers = Utils::getApiHeaders(); + + return Response::make($response, 200, $headers); + } + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 125f858691fc..b9ce0c761e70 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -192,6 +192,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::resource('ping', 'ClientApiController@ping'); Route::post('login', 'AccountApiController@login'); Route::get('accounts', 'AccountApiController@index'); + Route::get('accounts/{account_key}', 'AccountApiController@show'); Route::resource('clients', 'ClientApiController'); Route::get('quotes/{client_id?}', 'QuoteApiController@index'); Route::resource('quotes', 'QuoteApiController'); @@ -331,6 +332,7 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_NUM_CLIENTS_PRO', 20000); define('MAX_NUM_CLIENTS_LEGACY', 500); define('LEGACY_CUTOFF', 57800); + define('ERROR_DELAY', 3); define('INVOICE_STATUS_DRAFT', 1); define('INVOICE_STATUS_SENT', 2); diff --git a/app/Models/Account.php b/app/Models/Account.php index 0ffacd5051e5..7b1aa5224bba 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -41,7 +41,7 @@ class Account extends Eloquent 'invoice_settings' => 'object', ]; */ - public function tokens() + public function account_tokens() { return $this->hasMany('App\Models\AccountToken'); } diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index a824315e14c4..774be4434356 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -236,6 +236,15 @@ class AccountRepository return $client; } + public function findByKey($key) + { + $account = Account::whereAccountKey($key) + ->with('clients.invoices.invoice_items', 'clients.contacts') + ->firstOrFail(); + + return $account; + } + public function unlinkUserFromOauth($user) { $user->oauth_provider_id = null; @@ -301,6 +310,17 @@ class AccountRepository ->first(); } + public function findUsers($user, $with = null) + { + $accounts = $this->findUserAccounts($user->id); + + if ($accounts) { + return $this->getUserAccounts($accounts, $with); + } else { + return [$user]; + } + } + public function findUserAccounts($userId1, $userId2 = false) { if (!Schema::hasTable('user_accounts')) { @@ -324,7 +344,8 @@ class AccountRepository return $query->first(['id', 'user_id1', 'user_id2', 'user_id3', 'user_id4', 'user_id5']); } - public function prepareUsersData($record) { + public function getUserAccounts($record, $with = null) + { if (!$record) { return false; } @@ -338,8 +359,18 @@ class AccountRepository } $users = User::with('account') - ->whereIn('id', $userIds) - ->get(); + ->whereIn('id', $userIds); + + if ($with) { + $users->with($with); + } + + return $users->get(); + } + + public function prepareUsersData($record) + { + $users = $this->getUserAccounts($record); $data = []; foreach ($users as $user) { @@ -457,19 +488,20 @@ class AccountRepository return $code; } - public function createToken($name) + public function createTokens($user, $name) { $name = trim($name) ?: 'TOKEN'; + $users = $this->findUsers($user); - if ($token = AccountToken::scope()->whereName($name)->first()) { - return $token; + foreach ($users as $user) { + if ($token = AccountToken::whereUserId($user->id)->whereName($name)->first()) { + continue; + } + + $token = AccountToken::createNew($user); + $token->name = $name; + $token->token = str_random(RANDOM_KEY_LENGTH); + $token->save(); } - - $token = AccountToken::createNew(); - $token->name = $name; - $token->token = str_random(RANDOM_KEY_LENGTH); - $token->save(); - - return $token; } } diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php index dec83101be53..c3041b063b18 100644 --- a/app/Ninja/Transformers/ClientTransformer.php +++ b/app/Ninja/Transformers/ClientTransformer.php @@ -10,6 +10,7 @@ class ClientTransformer extends TransformerAbstract protected $defaultIncludes = [ 'contacts', 'invoices', + 'quotes', ]; public function includeContacts($client) @@ -19,7 +20,20 @@ class ClientTransformer extends TransformerAbstract public function includeInvoices($client) { - return $this->collection($client->invoices, new InvoiceTransformer); + $invoices = $client->invoices->filter(function($invoice) { + return !$invoice->is_quote && !$invoice->is_recurring; + }); + + return $this->collection($invoices, new InvoiceTransformer); + } + + public function includeQuotes($client) + { + $invoices = $client->invoices->filter(function($invoice) { + return $invoice->is_quote && !$invoice->is_recurring; + }); + + return $this->collection($invoices, new QuoteTransformer); } public function transform(Client $client) diff --git a/app/Ninja/Transformers/QuoteTransformer.php b/app/Ninja/Transformers/QuoteTransformer.php new file mode 100644 index 000000000000..7b3cc752d5d0 --- /dev/null +++ b/app/Ninja/Transformers/QuoteTransformer.php @@ -0,0 +1,26 @@ +collection($invoice->invoice_items, new InvoiceItemTransformer); + } + + public function transform(Invoice $invoice) + { + return [ + 'id' => (int) $invoice->public_id, + 'quote_number' => $invoice->invoice_number, + 'amount' => (float) $invoice->amount, + ]; + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 567a3b2e993c..8f95175f878f 100644 --- a/composer.lock +++ b/composer.lock @@ -1,11 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "70ef9e09bca60a19c396c138d8a01d50", - "content-hash": "399b36f7735987d2daf3d182603354b3", + "hash": "d0f3825f6d361f655c7393dd024b676e", "packages": [ { "name": "alfaproject/omnipay-neteller", @@ -340,7 +339,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Chumper/Datatable/zipball/b44834db3d4e560d4368c1a04248b9e6a422ccff", + "url": "https://api.github.com/repos/Chumper/Datatable/zipball/7fa47cb5469f07c620fb69dee94b8e1a96943ee2", "reference": "7fa47cb", "shasum": "" }, @@ -352,7 +351,7 @@ }, "require-dev": { "mockery/mockery": "dev-master", - "orchestra/testbench": "3.1.*", + "orchestra/testbench": "2.1.*", "phpunit/phpunit": "3.7.*" }, "type": "library", @@ -381,7 +380,7 @@ "jquery", "laravel" ], - "time": "2015-10-26 01:21:31" + "time": "2015-04-20 09:21:21" }, { "name": "classpreloader/classpreloader", @@ -771,16 +770,16 @@ }, { "name": "doctrine/cache", - "version": "v1.5.0", + "version": "v1.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "eb8a73619af4f1c8711e2ce482f5de3643258a1f" + "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb8a73619af4f1c8711e2ce482f5de3643258a1f", - "reference": "eb8a73619af4f1c8711e2ce482f5de3643258a1f", + "url": "https://api.github.com/repos/doctrine/cache/zipball/2b9cec5a5e722010cbebc91713d4c11eaa064d5e", + "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e", "shasum": "" }, "require": { @@ -837,7 +836,7 @@ "cache", "caching" ], - "time": "2015-10-28 11:27:45" + "time": "2015-11-02 18:35:48" }, { "name": "doctrine/collections", @@ -1438,16 +1437,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e" + "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", - "reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982", + "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982", "shasum": "" }, "require": { @@ -1492,7 +1491,7 @@ "stream", "uri" ], - "time": "2015-08-15 19:32:36" + "time": "2015-11-03 01:34:55" }, { "name": "guzzlehttp/ringphp", @@ -4944,12 +4943,12 @@ "target-dir": "Symfony/Component/Console", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", + "url": "https://github.com/symfony/Console.git", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", + "url": "https://api.github.com/repos/symfony/Console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", "shasum": "" }, @@ -5002,12 +5001,12 @@ "target-dir": "Symfony/Component/Debug", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", + "url": "https://github.com/symfony/Debug.git", "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/fca5696e0c9787722baa8f2ad6940dfd7a6a6941", + "url": "https://api.github.com/repos/symfony/Debug/zipball/fca5696e0c9787722baa8f2ad6940dfd7a6a6941", "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941", "shasum": "" }, @@ -5216,12 +5215,12 @@ "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", + "url": "https://github.com/symfony/HttpFoundation.git", "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", "shasum": "" }, @@ -5270,12 +5269,12 @@ "target-dir": "Symfony/Component/HttpKernel", "source": { "type": "git", - "url": "https://github.com/symfony/http-kernel.git", + "url": "https://github.com/symfony/HttpKernel.git", "reference": "a3f0ed713255c0400a2db38b3ed01989ef4b7322" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a3f0ed713255c0400a2db38b3ed01989ef4b7322", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/a3f0ed713255c0400a2db38b3ed01989ef4b7322", "reference": "a3f0ed713255c0400a2db38b3ed01989ef4b7322", "shasum": "" }, @@ -6047,16 +6046,16 @@ }, { "name": "facebook/webdriver", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5" + "reference": "d843e33fd19b49db5ac9daaef2610079daab0bad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5", - "reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/d843e33fd19b49db5ac9daaef2610079daab0bad", + "reference": "d843e33fd19b49db5ac9daaef2610079daab0bad", "shasum": "" }, "require": { @@ -6086,7 +6085,7 @@ "selenium", "webdriver" ], - "time": "2015-08-12 20:21:31" + "time": "2015-11-01 20:09:34" }, { "name": "fzaninotto/faker", diff --git a/database/migrations/2015_11_03_181318_improve_currency_localization.php b/database/migrations/2015_11_03_181318_improve_currency_localization.php new file mode 100644 index 000000000000..dd0196ded7cb --- /dev/null +++ b/database/migrations/2015_11_03_181318_improve_currency_localization.php @@ -0,0 +1,36 @@ +boolean('swap_currency_symbol')->default(0); + $table->string('thousand_separator')->nullable(); + $table->string('decimal_separator')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('countries', function ($table) { + $table->dropColumn('swap_currency_symbol'); + $table->dropColumn('thousand_separator'); + $table->dropColumn('decimal_separator'); + }); + } + +} diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index 03cfe984affb..8a0bc902d6ad 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -19,7 +19,7 @@ class PaymentLibrariesSeeder extends Seeder $this->createDateFormats(); $this->createDatetimeFormats(); $this->createInvoiceDesigns(); - $this->updateSwapPostalCode(); + $this->updateLocalization(); } private function createGateways() { @@ -239,36 +239,132 @@ class PaymentLibrariesSeeder extends Seeder } } - private function updateSwapPostalCode() { + private function updateLocalization() { // Source: http://www.bitboost.com/ref/international-address-formats.html + // Source: https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro $countries = [ - 'AR', - 'AT', - 'CH', - 'BE', - 'DE', - 'DK', - 'ES', - 'FI', - 'FR', - 'GL', - 'IL', - 'IS', - 'IT', - 'LU', - 'MY', - 'MX', - 'NL', - 'PL', - 'PT', - 'SE', - 'UY', + 'AR' => [ + 'swap_postal_code' => true, + ], + 'AT' => [ + 'swap_postal_code' => true, + ], + 'BE' => [ + 'swap_postal_code' => true, + ], + 'BG' => [ // Belgium + 'swap_currency_symbol' => true, + ], + 'CH' => [ + 'swap_postal_code' => true, + ], + 'CZ' => [ // Czech Republic + 'swap_currency_symbol' => true, + ], + 'DE' => [ // Germany + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'DK' => [ + 'swap_postal_code' => true, + ], + 'EE' => [ // Estonia + 'swap_currency_symbol' => true, + ], + 'ES' => [ // Spain + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'FI' => [ // Finland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'FR' => [ // France + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'GR' => [ // Greece + 'swap_currency_symbol' => true, + ], + 'HR' => [ // Croatia + 'swap_currency_symbol' => true, + ], + 'HU' => [ // Hungary + 'swap_currency_symbol' => true, + ], + 'GL' => [ + 'swap_postal_code' => true, + ], + 'IE' => [ // Ireland + 'thousand_separator' => ',', + 'decimal_separator' => '.', + ], + 'IL' => [ + 'swap_postal_code' => true, + ], + 'IS' => [ // Iceland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'IT' => [ // Italy + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'LT' => [ // Lithuania + 'swap_currency_symbol' => true, + ], + 'LU' => [ + 'swap_postal_code' => true, + ], + 'MY' => [ + 'swap_postal_code' => true, + ], + 'MX' => [ + 'swap_postal_code' => true, + ], + 'NL' => [ + 'swap_postal_code' => true, + ], + 'PL' => [ // Poland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'PT' => [ // Portugal + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'RO' => [ // Romania + 'swap_currency_symbol' => true, + ], + 'SE' => [ // Sweden + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'SI' => [ // Slovenia + 'swap_currency_symbol' => true, + ], + 'SK' => [ // Slovakia + 'swap_currency_symbol' => true, + ], + 'UY' => [ + 'swap_postal_code' => true, + ], ]; - for ($i=0; $i $data) { $country = Country::where('iso_3166_2', '=', $code)->first(); - $country->swap_postal_code = true; + if (isset($data['swap_postal_code'])) { + $country->swap_postal_code = true; + } + if (isset($data['swap_currency_symbol'])) { + $country->swap_currency_symbol = true; + } + if (isset($data['thousand_separator'])) { + $country->thousand_separator = $data['thousand_separator']; + } + if (isset($data['decimal_separator'])) { + $country->decimal_separator = $data['decimal_separator']; + } $country->save(); } } diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index 51b88a3136f8..2d6c32796d1c 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -15,7 +15,7 @@ class UserTableSeeder extends Seeder $account = Account::create([ 'name' => 'Test Account', - 'account_key' => str_random(16), + 'account_key' => str_random(RANDOM_KEY_LENGTH), 'timezone_id' => 1, ]); diff --git a/resources/views/public/header.blade.php b/resources/views/public/header.blade.php index dccb5fe23ee5..61ec01a54818 100644 --- a/resources/views/public/header.blade.php +++ b/resources/views/public/header.blade.php @@ -131,7 +131,9 @@
-
Copyright ©2015 Invoice Ninja. All rights reserved.
+ @if (!isset($hideLogo) || !$hideLogo) +
Copyright ©2015 Invoice Ninja. All rights reserved.
+ @endif