mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 18:14:32 -04:00
commit
444df3d850
@ -286,6 +286,7 @@ class CreateTestData extends Command
|
||||
$company = factory(\App\Models\Company::class)->create([
|
||||
'account_id' => $account->id,
|
||||
'slack_webhook_url' => config('ninja.notification.slack'),
|
||||
'is_large' => true,
|
||||
]);
|
||||
|
||||
$account->default_company_id = $company->id;
|
||||
|
@ -33,8 +33,8 @@ class ClientFactory
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
|
||||
$client_contact = ClientContactFactory::create($company_id, $user_id);
|
||||
$client->contacts->add($client_contact);
|
||||
// $client_contact = ClientContactFactory::create($company_id, $user_id);
|
||||
// $client->contacts->add($client_contact);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ class BaseController extends Controller
|
||||
|
||||
$query->with($includes);
|
||||
|
||||
if (!auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) {
|
||||
if (auth()->user() && !auth()->user()->hasPermission('view_'.lcfirst(class_basename($this->entity_type)))) {
|
||||
$query->where('user_id', '=', auth()->user()->id);
|
||||
}
|
||||
|
||||
@ -346,7 +346,7 @@ class BaseController extends Controller
|
||||
|
||||
$data = $this->createItem($item, $transformer, $this->entity_type);
|
||||
|
||||
if (request()->include_static) {
|
||||
if (auth()->user() && request()->include_static) {
|
||||
$data['static'] = Statics::company(auth()->user()->getCompany()->getLocale());
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class InvitationController extends Controller
|
||||
|
||||
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
|
||||
|
||||
$this->fireEntityViewedEvent($invitation->{$entity}, $entity);
|
||||
$this->fireEntityViewedEvent($invitation, $entity);
|
||||
}
|
||||
|
||||
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]);
|
||||
|
82
app/Http/Controllers/Shop/ClientController.php
Normal file
82
app/Http/Controllers/Shop/ClientController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
|
||||
use App\Events\Client\ClientWasCreated;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\Client\StoreClientRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Transformers\ClientTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Client::class;
|
||||
|
||||
protected $entity_transformer = ClientTransformer::class;
|
||||
|
||||
/**
|
||||
* @var ClientRepository
|
||||
*/
|
||||
protected $client_repo;
|
||||
|
||||
/**
|
||||
* ClientController constructor.
|
||||
* @param ClientRepository $clientRepo
|
||||
*/
|
||||
public function __construct(ClientRepository $client_repo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->client_repo = $client_repo;
|
||||
}
|
||||
|
||||
public function show(Request $request, string $contact_key)
|
||||
{
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
$contact = ClientContact::with('client')
|
||||
->where('company_id', $company->id)
|
||||
->where('contact_key', $contact_key)
|
||||
->firstOrFail();
|
||||
|
||||
return $this->itemResponse($contact->client);
|
||||
}
|
||||
|
||||
public function store(StoreClientRequest $request)
|
||||
{
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
app('queue')->createPayloadUsing(function () use ($company) {
|
||||
return ['db' => $company->db];
|
||||
});
|
||||
|
||||
$client = $this->client_repo->save($request->all(), ClientFactory::create($company->id, $company->owner()->id));
|
||||
|
||||
$client->load('contacts', 'primary_contact');
|
||||
|
||||
$this->uploadLogo($request->file('company_logo'), $company, $client);
|
||||
|
||||
event(new ClientWasCreated($client, $company, Ninja::eventVars()));
|
||||
|
||||
return $this->itemResponse($client);
|
||||
}
|
||||
}
|
85
app/Http/Controllers/Shop/InvoiceController.php
Normal file
85
app/Http/Controllers/Shop/InvoiceController.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasCreated;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Models\Client;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InvoiceController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Invoice::class;
|
||||
|
||||
protected $entity_transformer = InvoiceTransformer::class;
|
||||
|
||||
/**
|
||||
* @var InvoiceRepository
|
||||
*/
|
||||
protected $invoice_repo;
|
||||
|
||||
/**
|
||||
* InvoiceController constructor.
|
||||
*
|
||||
* @param \App\Repositories\InvoiceRepository $invoice_repo The invoice repo
|
||||
*/
|
||||
public function __construct(InvoiceRepository $invoice_repo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->invoice_repo = $invoice_repo;
|
||||
}
|
||||
|
||||
public function show(string $invitation_key)
|
||||
{
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
$invitation = InvoiceInvitation::with(['invoice'])
|
||||
->where('company_id', $company->id)
|
||||
->where('key',$invitation_key)
|
||||
->firstOrFail();
|
||||
|
||||
return $this->itemResponse($invitation->invoice);
|
||||
}
|
||||
|
||||
|
||||
public function store(StoreInvoiceRequest $request)
|
||||
{
|
||||
app('queue')->createPayloadUsing(function () use ($company) {
|
||||
return ['db' => $company->db];
|
||||
});
|
||||
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
$client = Client::find($request->input('client_id'));
|
||||
|
||||
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create($company_id, $company->owner()->id));
|
||||
|
||||
event(new InvoiceWasCreated($invoice, $company, Ninja::eventVars()));
|
||||
|
||||
$invoice = $invoice->service()->triggeredActions($request)->save();
|
||||
|
||||
return $this->itemResponse($invoice);
|
||||
}
|
||||
|
||||
}
|
54
app/Http/Controllers/Shop/ProductController.php
Normal file
54
app/Http/Controllers/Shop/ProductController.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Shop;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\Product;
|
||||
use App\Transformers\ProductTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Product::class;
|
||||
|
||||
protected $entity_transformer = ProductTransformer::class;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
$products = Product::where('company_id', $company->id);
|
||||
|
||||
return $this->listResponse($products);
|
||||
}
|
||||
|
||||
public function show(Request $request, string $product_key)
|
||||
{
|
||||
$company = Company::where('company_key', $request->header('X-API-COMPANY_KEY'))->first();
|
||||
|
||||
$product = Product::where('company_id', $company->id)
|
||||
->where('product_key', $product_key)
|
||||
->first();
|
||||
|
||||
return $this->itemResponse($product);
|
||||
}
|
||||
}
|
@ -73,6 +73,11 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\StartupCheck::class,
|
||||
\App\Http\Middleware\QueryLogging::class,
|
||||
],
|
||||
'shop' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
'query_logging',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
@ -106,7 +111,10 @@ class Kernel extends HttpKernel
|
||||
'url_db' => \App\Http\Middleware\UrlSetDb::class,
|
||||
'web_db' => \App\Http\Middleware\SetWebDb::class,
|
||||
'api_db' => \App\Http\Middleware\SetDb::class,
|
||||
'company_key_db' => \App\Http\Middleware\SetDbByCompanyKey::class,
|
||||
'locale' => \App\Http\Middleware\Locale::class,
|
||||
'contact.register' => \App\Http\Middleware\ContactRegister::class,
|
||||
'shop_token_auth' => \App\Http\Middleware\Shop\ShopTokenAuth::class,
|
||||
|
||||
];
|
||||
}
|
||||
|
48
app/Http/Middleware/SetDbByCompanyKey.php
Normal file
48
app/Http/Middleware/SetDbByCompanyKey.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CompanyToken;
|
||||
use Closure;
|
||||
|
||||
class SetDbByCompanyKey
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$error = [
|
||||
'message' => 'Invalid Token',
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
|
||||
if ($request->header('X-API-COMPANY_KEY') && config('ninja.db.multi_db_enabled')) {
|
||||
if (! MultiDB::findAndSetDbByCompanyKey($request->header('X-API-COMPANY_KEY'))) {
|
||||
return response()->json($error, 403);
|
||||
}
|
||||
} elseif (!config('ninja.db.multi_db_enabled')) {
|
||||
return $next($request);
|
||||
} else {
|
||||
return response()->json($error, 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ class TokenAuth
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if ($request->header('X-API-TOKEN') && ($company_token = CompanyToken::with(['user','company'])->whereRaw("BINARY `token`= ?", [$request->header('X-API-TOKEN')])->first())) {
|
||||
|
||||
$user = $company_token->user;
|
||||
|
||||
$error = [
|
||||
|
@ -180,6 +180,17 @@ class MultiDB
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByCompanyKey($company_key) :bool
|
||||
{
|
||||
foreach (self::$dbs as $db) {
|
||||
if ($company = Company::on($db)->where('company_key', $company_key)->first()) {
|
||||
self::setDb($company->db);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findAndSetDbByDomain($subdomain) :bool
|
||||
{
|
||||
foreach (self::$dbs as $db) {
|
||||
|
@ -46,10 +46,10 @@ class InvoiceViewedActivity implements ShouldQueue
|
||||
|
||||
$fields = new \stdClass;
|
||||
|
||||
$fields->user_id = $event->invoice->user_id;
|
||||
$fields->company_id = $event->invoice->company_id;
|
||||
$fields->user_id = $event->invitation->user_id;
|
||||
$fields->company_id = $event->invitation->company_id;
|
||||
$fields->activity_type_id = Activity::VIEW_INVOICE;
|
||||
$fields->client_id = $event->invitation->client_id;
|
||||
$fields->client_id = $event->invitation->invoice->client_id;
|
||||
$fields->client_contact_id = $event->invitation->client_contact_id;
|
||||
$fields->invitation_id = $event->invitation->id;
|
||||
$fields->invoice_id = $event->invitation->invoice_id;
|
||||
|
@ -46,6 +46,7 @@ class CompanyUser extends Pivot
|
||||
'is_owner',
|
||||
'is_locked',
|
||||
'slack_webhook_url',
|
||||
'shop_restricted'
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -57,6 +57,8 @@ class RouteServiceProvider extends ServiceProvider
|
||||
$this->mapContactApiRoutes();
|
||||
|
||||
$this->mapClientApiRoutes();
|
||||
|
||||
$this->mapShopApiRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,4 +119,12 @@ class RouteServiceProvider extends ServiceProvider
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/client.php'));
|
||||
}
|
||||
|
||||
protected function mapShopApiRoutes()
|
||||
{
|
||||
Route::prefix('')
|
||||
->middleware('shop')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/shop.php'));
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ class ClientRepository extends BaseRepository
|
||||
$data['name'] = $client->present()->name();
|
||||
}
|
||||
|
||||
info("{$client->present()->name} has a balance of {$client->balance} with a paid to date of {$client->paid_to_date}");
|
||||
//info("{$client->present()->name} has a balance of {$client->balance} with a paid to date of {$client->paid_to_date}");
|
||||
|
||||
if (array_key_exists('documents', $data)) {
|
||||
$this->saveDocuments($data['documents'], $client);
|
||||
|
30
database/migrations/2020_07_28_104218_shop_token.php
Normal file
30
database/migrations/2020_07_28_104218_shop_token.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ShopToken extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('enable_shop_api')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -136,8 +136,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::post('emails', 'EmailController@send')->name('email.send');
|
||||
|
||||
/*Subscription and Webhook routes */
|
||||
Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe');
|
||||
Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe');
|
||||
// Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe');
|
||||
// Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe');
|
||||
|
||||
Route::resource('webhooks', 'WebhookController');
|
||||
Route::post('webhooks/bulk', 'WebhookController@bulk')->name('webhooks.bulk');
|
||||
|
14
routes/shop.php
Normal file
14
routes/shop.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group(['middleware' => ['company_key_db','locale'], 'prefix' => 'api/v1'], function () {
|
||||
|
||||
Route::get('shop/products', 'Shop\ProductController@index');
|
||||
Route::get('shop/clients', 'Shop\ClientController@index');
|
||||
Route::get('shop/invoices', 'Shop\InvoiceController@index');
|
||||
Route::get('shop/client/{contact_key}', 'Shop\ClientController@show');
|
||||
Route::get('shop/invoice/{invitation_key}', 'Shop\InvoiceController@show');
|
||||
Route::get('shop/product/{product_key}', 'Shop\ProductController@show');
|
||||
|
||||
});
|
101
tests/Feature/Shop/ShopInvoiceTest.php
Normal file
101
tests/Feature/Shop/ShopInvoiceTest.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Shop;
|
||||
|
||||
use App\Factory\CompanyUserFactory;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\Controllers\Shop\InvoiceController
|
||||
*/
|
||||
|
||||
class ShopInvoiceTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use MockAccountData;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
}
|
||||
|
||||
public function testTokenFailure()
|
||||
{
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-COMPANY_KEY' => $this->company->company_key
|
||||
])->get('/api/v1/shop/products');
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testTokenSuccess()
|
||||
{
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-COMPANY_KEY' => $this->company->company_key
|
||||
])->get('/api/v1/products');
|
||||
|
||||
|
||||
$response->assertStatus(403);
|
||||
|
||||
$arr = $response->json();
|
||||
}
|
||||
|
||||
public function testGetByProductKey()
|
||||
{
|
||||
$product = factory(\App\Models\Product::class)->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-COMPANY_KEY' => $this->company->company_key
|
||||
])->get('/api/v1/shop/product/'.$product->product_key);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals($product->hashed_id, $arr['data']['id']);
|
||||
}
|
||||
|
||||
public function testGetByClientByContactKey()
|
||||
{
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-COMPANY_KEY' => $this->company->company_key
|
||||
])->get('/api/v1/shop/client/'.$this->client->contacts->first()->contact_key);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals($this->client->hashed_id, $arr['data']['id']);
|
||||
|
||||
}
|
||||
}
|
@ -126,8 +126,8 @@ class FactoryCreationTest extends TestCase
|
||||
$cliz->save();
|
||||
|
||||
$this->assertNotNull($cliz->contacts);
|
||||
$this->assertEquals(1, $cliz->contacts->count());
|
||||
$this->assertInternalType("int", $cliz->contacts->first()->id);
|
||||
$this->assertEquals(0, $cliz->contacts->count());
|
||||
$this->assertInternalType("int", $cliz->id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user