diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index ac7bcf8daa56..0045796945a9 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -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; diff --git a/app/Factory/ClientFactory.php b/app/Factory/ClientFactory.php index 9c599ab6e143..7f6460ffb8b2 100644 --- a/app/Factory/ClientFactory.php +++ b/app/Factory/ClientFactory.php @@ -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; } diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index e741f1466da2..ccf9ee51c46b 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -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()); } diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 8453e788f76c..533c419e067a 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -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})]); diff --git a/app/Http/Controllers/Shop/ClientController.php b/app/Http/Controllers/Shop/ClientController.php new file mode 100644 index 000000000000..31002e57b4fd --- /dev/null +++ b/app/Http/Controllers/Shop/ClientController.php @@ -0,0 +1,82 @@ +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); + } +} diff --git a/app/Http/Controllers/Shop/InvoiceController.php b/app/Http/Controllers/Shop/InvoiceController.php new file mode 100644 index 000000000000..198613dc516e --- /dev/null +++ b/app/Http/Controllers/Shop/InvoiceController.php @@ -0,0 +1,85 @@ +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); + } + +} diff --git a/app/Http/Controllers/Shop/ProductController.php b/app/Http/Controllers/Shop/ProductController.php new file mode 100644 index 000000000000..5122ba1918c4 --- /dev/null +++ b/app/Http/Controllers/Shop/ProductController.php @@ -0,0 +1,54 @@ +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); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4799bf0fc7ab..ca3680122a97 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -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, + ]; } diff --git a/app/Http/Middleware/SetDbByCompanyKey.php b/app/Http/Middleware/SetDbByCompanyKey.php new file mode 100644 index 000000000000..74c995127244 --- /dev/null +++ b/app/Http/Middleware/SetDbByCompanyKey.php @@ -0,0 +1,48 @@ + '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); + } +} diff --git a/app/Http/Middleware/TokenAuth.php b/app/Http/Middleware/TokenAuth.php index b3f840fa6ece..088e51cd43f2 100644 --- a/app/Http/Middleware/TokenAuth.php +++ b/app/Http/Middleware/TokenAuth.php @@ -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 = [ diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index 457eee507023..d15196a40bce 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -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) { diff --git a/app/Listeners/Invoice/InvoiceViewedActivity.php b/app/Listeners/Invoice/InvoiceViewedActivity.php index c3b0e52efecd..b3afaf4032ac 100644 --- a/app/Listeners/Invoice/InvoiceViewedActivity.php +++ b/app/Listeners/Invoice/InvoiceViewedActivity.php @@ -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; diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index 5ce07b3efdc7..fa699b40e274 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -46,6 +46,7 @@ class CompanyUser extends Pivot 'is_owner', 'is_locked', 'slack_webhook_url', + 'shop_restricted' ]; protected $touches = []; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 778e1d5ff03a..feddf0b08bc2 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -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')); + } } diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 49d46d692024..8b5582cb71fa 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.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); diff --git a/database/migrations/2020_07_28_104218_shop_token.php b/database/migrations/2020_07_28_104218_shop_token.php new file mode 100644 index 000000000000..7996ffa0a8b9 --- /dev/null +++ b/database/migrations/2020_07_28_104218_shop_token.php @@ -0,0 +1,30 @@ +boolean('enable_shop_api')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/routes/api.php b/routes/api.php index e1feb2bdeea4..1db2f96a6b8d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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'); diff --git a/routes/shop.php b/routes/shop.php new file mode 100644 index 000000000000..aee2d2f75669 --- /dev/null +++ b/routes/shop.php @@ -0,0 +1,14 @@ + ['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'); + +}); \ No newline at end of file diff --git a/tests/Feature/Shop/ShopInvoiceTest.php b/tests/Feature/Shop/ShopInvoiceTest.php new file mode 100644 index 000000000000..26657dc6be0d --- /dev/null +++ b/tests/Feature/Shop/ShopInvoiceTest.php @@ -0,0 +1,101 @@ +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']); + + } +} diff --git a/tests/Unit/FactoryCreationTest.php b/tests/Unit/FactoryCreationTest.php index 32eaf760b6fa..47c947da53a9 100644 --- a/tests/Unit/FactoryCreationTest.php +++ b/tests/Unit/FactoryCreationTest.php @@ -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); } /**