Working on the API

This commit is contained in:
Hillel Coren 2015-11-03 21:03:24 +02:00
parent ebd63f1805
commit 029e740a6c
12 changed files with 335 additions and 102 deletions

View File

@ -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);
}
}

View File

@ -0,0 +1,28 @@
<?php namespace App\Http\Controllers;
use Utils;
use Response;
use League\Fractal;
use League\Fractal\Manager;
use App\Ninja\Serializers\ArraySerializer;
class BaseAPIController extends Controller
{
protected $manager;
public function __construct()
{
$this->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);
}
}

View File

@ -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);

View File

@ -41,7 +41,7 @@ class Account extends Eloquent
'invoice_settings' => 'object',
];
*/
public function tokens()
public function account_tokens()
{
return $this->hasMany('App\Models\AccountToken');
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -0,0 +1,26 @@
<?php namespace App\Ninja\Transformers;
use App\Models\Invoice;
use League\Fractal;
use League\Fractal\TransformerAbstract;
class QuoteTransformer extends TransformerAbstract
{
protected $defaultIncludes = [
'invoice_items',
];
public function includeInvoiceItems($invoice)
{
return $this->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,
];
}
}

57
composer.lock generated
View File

@ -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",

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ImproveCurrencyLocalization extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('countries', function ($table) {
$table->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');
});
}
}

View File

@ -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<count($countries); $i++) {
$code = $countries[$i];
foreach ($countries as $code => $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();
}
}

View File

@ -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,
]);

View File

@ -131,7 +131,9 @@
<div class="bottom">
<div class="wrap">
<div class="copy">Copyright &copy;2015 <a href="{{ NINJA_WEB_URL }}" target="_blank">Invoice Ninja</a>. All rights reserved.</div>
@if (!isset($hideLogo) || !$hideLogo)
<div class="copy">Copyright &copy;2015 <a href="{{ NINJA_WEB_URL }}" target="_blank">Invoice Ninja</a>. All rights reserved.</div>
@endif
</div><!-- .wrap -->
</div><!-- .bottom -->
</footer><!-- #footer -->