diff --git a/app/Events/CompanyToken/CompanyTokenWasDeleted.php b/app/Events/CompanyToken/CompanyTokenWasDeleted.php new file mode 100644 index 000000000000..c6e914951841 --- /dev/null +++ b/app/Events/CompanyToken/CompanyTokenWasDeleted.php @@ -0,0 +1,42 @@ +company_token = $company_token; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Factory/CompanyTokenFactory.php b/app/Factory/CompanyTokenFactory.php new file mode 100644 index 000000000000..a3a0df7834a8 --- /dev/null +++ b/app/Factory/CompanyTokenFactory.php @@ -0,0 +1,31 @@ +user_id = $user_id; + $token->account_id = $account_id; + $token->token = Str::random(64); + $token->name = ''; + $token->company_id = $company_id; + + return $token; + } +} \ No newline at end of file diff --git a/app/Factory/SubscriptionFactory.php b/app/Factory/SubscriptionFactory.php new file mode 100644 index 000000000000..eb62417bc22c --- /dev/null +++ b/app/Factory/SubscriptionFactory.php @@ -0,0 +1,29 @@ +company_id = $company_id; + $subscription->user_id = $user_id; + $subscription->target_url = ''; + $subscription->event_id = 1; + $subscription->format = 'JSON'; + + return $subscription; + } +} diff --git a/app/Filters/SubscriptionFilters.php b/app/Filters/SubscriptionFilters.php new file mode 100644 index 000000000000..17125707b0b2 --- /dev/null +++ b/app/Filters/SubscriptionFilters.php @@ -0,0 +1,143 @@ +builder; + } + + return $this->builder->where(function ($query) use ($filter) { + $query->where('subscriptions.target_url', 'like', '%'.$filter.'%'); + }); + } + + /** + * Filters the list based on the status + * archived, active, deleted + * + * @param string filter + * @return Illuminate\Database\Query\Builder + */ + public function status(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + $table = 'subscriptions'; + $filters = explode(',', $filter); + + return $this->builder->where(function ($query) use ($filters, $table) { + $query->whereNull($table . '.id'); + + if (in_array(parent::STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table . '.deleted_at'); + } + + if (in_array(parent::STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table . '.deleted_at'); + + if (! in_array($table, ['users'])) { + $query->where($table . '.is_deleted', '=', 0); + } + }); + } + + if (in_array(parent::STATUS_DELETED, $filters)) { + $query->orWhere($table . '.is_deleted', '=', 1); + } + }); + } + + /** + * Sorts the list based on $sort + * + * @param string sort formatted as column|asc + * @return Illuminate\Database\Query\Builder + */ + public function sort(string $sort) : Builder + { + $sort_col = explode("|", $sort); + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Returns the base query + * + * @param int company_id + * @return Illuminate\Database\Query\Builder + * @deprecated + */ + public function baseQuery(int $company_id, User $user) : Builder + { + $query = DB::table('subscriptions') + ->join('companies', 'companies.id', '=', 'subscriptions.company_id') + ->where('subscriptions.company_id', '=', $company_id) + //->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices + ->select( + 'subscriptions.id', + 'subscriptions.target_url', + 'subscriptions.event_id', + 'subscriptions.created_at', + 'subscriptions.created_at as token_created_at', + 'subscriptions.deleted_at', + 'subscriptions.format', + 'subscriptions.user_id', + ); + + + /** + * If the user does not have permissions to view all invoices + * limit the user to only the invoices they have created + */ + if (Gate::denies('view-list', Subscription::class)) { + $query->where('subscriptions.user_id', '=', $user->id); + } + + + return $query; + } + + /** + * Filters the query by the users company ID + * + * @param $company_id The company Id + * @return Illuminate\Database\Query\Builder + */ + public function entityFilter() + { + return $this->builder->company(); + } +} diff --git a/app/Filters/TokenFilters.php b/app/Filters/TokenFilters.php new file mode 100644 index 000000000000..f49b4fd56f6c --- /dev/null +++ b/app/Filters/TokenFilters.php @@ -0,0 +1,143 @@ +builder; + } + + return $this->builder->where(function ($query) use ($filter) { + $query->where('company_tokens.name', 'like', '%'.$filter.'%'); + }); + } + + /** + * Filters the list based on the status + * archived, active, deleted + * + * @param string filter + * @return Illuminate\Database\Query\Builder + */ + public function status(string $filter = '') : Builder + { + if (strlen($filter) == 0) { + return $this->builder; + } + + $table = 'company_tokens'; + $filters = explode(',', $filter); + + return $this->builder->where(function ($query) use ($filters, $table) { + $query->whereNull($table . '.id'); + + if (in_array(parent::STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table . '.deleted_at'); + } + + if (in_array(parent::STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table . '.deleted_at'); + + if (! in_array($table, ['users'])) { + $query->where($table . '.is_deleted', '=', 0); + } + }); + } + + if (in_array(parent::STATUS_DELETED, $filters)) { + $query->orWhere($table . '.is_deleted', '=', 1); + } + }); + } + + /** + * Sorts the list based on $sort + * + * @param string sort formatted as column|asc + * @return Illuminate\Database\Query\Builder + */ + public function sort(string $sort) : Builder + { + $sort_col = explode("|", $sort); + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Returns the base query + * + * @param int company_id + * @return Illuminate\Database\Query\Builder + * @deprecated + */ + public function baseQuery(int $company_id, User $user) : Builder + { + $query = DB::table('company_tokens') + ->join('companies', 'companies.id', '=', 'company_tokens.company_id') + ->where('company_tokens.company_id', '=', $company_id) + //->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices + ->select( + 'company_tokens.id', + 'company_tokens.name', + 'company_tokens.token', + 'company_tokens.created_at', + 'company_tokens.created_at as token_created_at', + 'company_tokens.deleted_at', + 'company_tokens.is_deleted', + 'company_tokens.user_id', + ); + + + /** + * If the user does not have permissions to view all invoices + * limit the user to only the invoices they have created + */ + if (Gate::denies('view-list', CompanyToken::class)) { + $query->where('company_tokens.user_id', '=', $user->id); + } + + + return $query; + } + + /** + * Filters the query by the users company ID + * + * @param $company_id The company Id + * @return Illuminate\Database\Query\Builder + */ + public function entityFilter() + { + return $this->builder->company(); + } +} diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index e7a702dc40b5..1b2a9b88f71b 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -262,7 +262,7 @@ class CreditController extends BaseController * * @OA\Get( * path="/api/v1/credits/{id}/edit", - * operationId="editInvoice", + * operationId="editCredit", * tags={"credits"}, * summary="Shows an credit for editting", * description="Displays an credit by id", diff --git a/app/Http/Controllers/OpenAPI/SubscriptionSchema.php b/app/Http/Controllers/OpenAPI/SubscriptionSchema.php new file mode 100644 index 000000000000..ba59cfaa64fc --- /dev/null +++ b/app/Http/Controllers/OpenAPI/SubscriptionSchema.php @@ -0,0 +1,11 @@ +delete(); - return response()->json([], 200); + return $this->itemResponse($product); } /** diff --git a/app/Http/Controllers/SubscriptionController.php b/app/Http/Controllers/SubscriptionController.php new file mode 100644 index 000000000000..cd36a1fda971 --- /dev/null +++ b/app/Http/Controllers/SubscriptionController.php @@ -0,0 +1,598 @@ +base_repo = $base_repo; + } + + /** + * @OA\Get( + * path="/api/v1/subscriptions", + * operationId="getSubscriptions", + * tags={"subscriptions"}, + * summary="Gets a list of subscriptions", + * description="Lists subscriptions, search and filters allow fine grained lists to be generated. + * + * Query parameters can be added to performed more fine grained filtering of the subscriptions, these are handled by the SubscriptionFilters class which defines the methods available", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\Response( + * response=200, + * description="A list of subscriptions", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function index(SubscriptionFilters $filters) + { + $subscriptions = Subscription::filter($filters); + + return $this->listResponse($subscriptions); + } + + /** + * Display the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Get( + * path="/api/v1/subscriptions/{id}", + * operationId="showSubscription", + * tags={"subscriptions"}, + * summary="Shows a subscription", + * description="Displays a subscription by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Subscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the subscription object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function show(ShowSubscriptionRequest $request, Subscription $subscription) + { + return $this->itemResponse($subscription); + } + + /** + * Show the form for editing the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Get( + * path="/api/v1/subscriptions/{id}/edit", + * operationId="editSubscription", + * tags={"subscriptions"}, + * summary="Shows a subscription for editting", + * description="Displays a subscription by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Subscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the subscription object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function edit(EditSubscriptionRequest $request, Subscription $subscription) + { + return $this->itemResponse($subscription); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param App\Models\Subscription $subscription + * @return \Illuminate\Http\Response + * + * + * + * @OA\Put( + * path="/api/v1/subscriptions/{id}", + * operationId="updateSubscription", + * tags={"subscriptions"}, + * summary="Updates a subscription", + * description="Handles the updating of a subscription by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Subscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the subscription object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function update(UpdateSubscriptionRequest $request, Subscription $subscription) + { + if ($request->entityIsDeleted($subscription)) { + return $request->disallowUpdate(); + } + + $subscription->fill($request->all()); + $subscription->save(); + + return $this->itemResponse($subscription); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + * + * + * + * @OA\Get( + * path="/api/v1/subscriptions/create", + * operationId="getSubscriptionsCreate", + * tags={"subscriptions"}, + * summary="Gets a new blank subscription object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A blank subscription object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function create(CreateSubscriptionRequest $request) + { + $subscription = SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($subscription); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * + * + * @OA\Post( + * path="/api/v1/subscriptions", + * operationId="storeSubscription", + * tags={"subscriptions"}, + * summary="Adds a subscription", + * description="Adds an subscription to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved subscription object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function store(StoreSubscriptionRequest $request) + { + + $subscription = SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); + $subscription->fill($request->all()); + $subscription->save(); + + return $this->itemResponse($subscription); + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Delete( + * path="/api/v1/subscriptions/{id}", + * operationId="deleteSubscription", + * tags={"subscriptions"}, + * summary="Deletes a subscription", + * description="Handles the deletion of a subscription by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Subscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function destroy(DestroySubscriptionRequest $request, Subscription $subscription) + { + //may not need these destroy routes as we are using actions to 'archive/delete' + $subscription->delete(); + + return $this->itemResponse($subscription); + } + + /** + * Perform bulk actions on the list view + * + * @param BulkSubscriptionRequest $request + * @return \Illuminate\Http\Response + * + * + * @OA\Post( + * path="/api/v1/subscriptions/bulk", + * operationId="bulkSubscriptions", + * tags={"subscriptions"}, + * summary="Performs bulk actions on an array of subscriptions", + * description="", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\RequestBody( + * description="User credentials", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="array", + * @OA\Items( + * type="integer", + * description="Array of hashed IDs to be bulk 'actioned", + * example="[0,1,2,3]", + * ), + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="The Subscription User response", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function bulk() + { + $action = request()->input('action'); + + $ids = request()->input('ids'); + $subscriptions = Subscription::withTrashed()->find($this->transformKeys($ids)); + + $subscriptions->each(function ($subscription, $key) use ($action) { + if (auth()->user()->can('edit', $subscription)) { + $this->base_repo->{$action}($subscription); + } + }); + + return $this->listResponse(Subscription::withTrashed()->whereIn('id', $this->transformKeys($ids))); + } + + + /** + * Store a newly created resource in storage. + * + * @OA\Post( + * path="/api/v1/hooks", + * operationId="storeHook", + * tags={"hooks"}, + * summary="Adds a hook", + * description="Adds a hooks to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved hooks object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Subscription"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function subscribe(StoreSubscriptionRequest $request) + { + $event_id = $request->input('event_id'); + $target_url = $request->input('target_url'); + + if (! in_array($event_id, Subscription::$valid_events)) { + return response()->json("Invalid event",400); + } + + $subscription = new Subscription; + $subscription->company_id = auth()->user()->company()->id; + $subscription->user_id = auth()->user()->id; + $subscription->event_id = $event_id; + $subscription->target_url = $target_url; + $subscription->save(); + + if (!$subscription->id) { + return response()->json('Failed to create subscription', 400); + } + + return $this->itemResponse($subscription); + + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Delete( + * path="/api/v1/hooks/{subscription_id}", + * operationId="deleteHook", + * tags={"hooks"}, + * summary="Deletes a hook", + * description="Handles the deletion of a hook by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="subscription_id", + * in="path", + * description="The Subscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function unsubscribe(DestroySubscriptionRequest $request, Subscription $subscription) + { + $subscription->delete(); + + return $this->itemResponse($subscription); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php new file mode 100644 index 000000000000..e38941f532c0 --- /dev/null +++ b/app/Http/Controllers/TokenController.php @@ -0,0 +1,495 @@ +token_repo = $token_repo; + } + + /** + * @OA\Get( + * path="/api/v1/tokens", + * operationId="getTokens", + * tags={"tokens"}, + * summary="Gets a list of company tokens", + * description="Lists company tokens. + * + * Query parameters can be added to performed more fine grained filtering of the tokens, these are handled by the TokenFilters class which defines the methods available", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\Response( + * response=200, + * description="A list of tokens", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function index(TokenFilters $filters) + { + $tokens = CompanyToken::filter($filters); + + return $this->listResponse($tokens); + } + + /** + * Display the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Get( + * path="/api/v1/tokens/{id}", + * operationId="showToken", + * tags={"tokens"}, + * summary="Shows a token", + * description="Displays a token by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Token Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the token object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function show(ShowTokenRequest $request, CompanyToken $token) + { + return $this->itemResponse($token); + } + + /** + * Show the form for editing the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Get( + * path="/api/v1/tokens/{id}/edit", + * operationId="editToken", + * tags={"tokens"}, + * summary="Shows a token for editting", + * description="Displays a token by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Token Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the token object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function edit(EditTokenRequest $request, CompanyToken $token) + { + return $this->itemResponse($token); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param App\Models\Token $token + * @return \Illuminate\Http\Response + * + * + * + * @OA\Put( + * path="/api/v1/tokens/{id}", + * operationId="updateToken", + * tags={"tokens"}, + * summary="Updates a token", + * description="Handles the updating of a token by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Token Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the token object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function update(UpdateTokenRequest $request, CompanyToken $token) + { + if ($request->entityIsDeleted($token)) { + return $request->disallowUpdate(); + } + + $token = $this->token_repo->save($request->all(), $token); + + return $this->itemResponse($token->fresh()); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + * + * + * + * @OA\Get( + * path="/api/v1/tokens/create", + * operationId="getTokensCreate", + * tags={"tokens"}, + * summary="Gets a new blank token object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="A blank token object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function create(CreateTokenRequest $request) + { + $token = CompanyTokenFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + return $this->itemResponse($token); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * + * + * @OA\Post( + * path="/api/v1/tokens", + * operationId="storeToken", + * tags={"tokens"}, + * summary="Adds a token", + * description="Adds an token to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="Returns the saved token object", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function store(StoreTokenRequest $request) + { + + $company_token = CompanyTokenFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + $token = $this->token_repo->save($request->all(), $company_token); + + return $this->itemResponse($token); + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + * + * + * @OA\Delete( + * path="/api/v1/tokens/{id}", + * operationId="deleteToken", + * tags={"tokens"}, + * summary="Deletes a token", + * description="Handles the deletion of a token by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The Token Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * + */ + public function destroy(DestroyTokenRequest $request, CompanyToken $token) + { + //may not need these destroy routes as we are using actions to 'archive/delete' + $token->delete(); + + return $this->itemResponse($token); + } + + /** + * Perform bulk actions on the list view + * + * @param BulkTokenRequest $request + * @return \Illuminate\Http\Response + * + * + * @OA\Post( + * path="/api/v1/tokens/bulk", + * operationId="bulkTokens", + * tags={"tokens"}, + * summary="Performs bulk actions on an array of tokens", + * description="", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\RequestBody( + * description="Token ids", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * type="array", + * @OA\Items( + * type="integer", + * description="Array of hashed IDs to be bulk 'actioned", + * example="[0,1,2,3]", + * ), + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="The Token response", + * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/CompanyToken"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function bulk() + { + $action = request()->input('action'); + + $ids = request()->input('ids'); + $tokens = CompanyToken::withTrashed()->find($this->transformKeys($ids)); + + $tokens->each(function ($token, $key) use ($action) { + if (auth()->user()->can('edit', $token)) { + $this->token_repo->{$action}($token); + } + }); + + return $this->listResponse(CompanyToken::withTrashed()->whereIn('id', $this->transformKeys($ids))); + } + +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index b4d5d388a891..d4079b9b1cc6 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -322,11 +322,6 @@ class UserController extends BaseController /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response - * - * * @OA\Put( * path="/api/v1/users/{id}", * operationId="updateUser", @@ -349,7 +344,7 @@ class UserController extends BaseController * ), * ), * @OA\Response( -f * response=200, + * response=200, * description="Returns the User object", * @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"), * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), diff --git a/app/Http/Requests/Product/DestroyProductRequest.php b/app/Http/Requests/Product/DestroyProductRequest.php new file mode 100644 index 000000000000..c6bf3665eef2 --- /dev/null +++ b/app/Http/Requests/Product/DestroyProductRequest.php @@ -0,0 +1,29 @@ +user()->can('edit', $this->product); + } +} diff --git a/app/Http/Requests/Subscription/BulkSubscriptionRequest.php b/app/Http/Requests/Subscription/BulkSubscriptionRequest.php new file mode 100644 index 000000000000..3fc119941ec3 --- /dev/null +++ b/app/Http/Requests/Subscription/BulkSubscriptionRequest.php @@ -0,0 +1,47 @@ +has('action')) { + return false; + } + + if (!in_array($this->action, $this->getBulkOptions(), true)) { + return false; + } + + return auth()->user()->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = $this->getGlobalRules(); + + /** We don't require IDs on bulk storing. */ + if ($this->action !== self::$STORE_METHOD) { + $rules['ids'] = ['required']; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Subscription/CreateSubscriptionRequest.php b/app/Http/Requests/Subscription/CreateSubscriptionRequest.php new file mode 100644 index 000000000000..28bf5a5cd14e --- /dev/null +++ b/app/Http/Requests/Subscription/CreateSubscriptionRequest.php @@ -0,0 +1,29 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/Subscription/DestroySubscriptionRequest.php b/app/Http/Requests/Subscription/DestroySubscriptionRequest.php new file mode 100644 index 000000000000..c8c51ae1aaa0 --- /dev/null +++ b/app/Http/Requests/Subscription/DestroySubscriptionRequest.php @@ -0,0 +1,31 @@ +user()->isAdmin(); + } + + +} diff --git a/app/Http/Requests/Subscription/EditSubscriptionRequest.php b/app/Http/Requests/Subscription/EditSubscriptionRequest.php new file mode 100644 index 000000000000..f9d269ca1008 --- /dev/null +++ b/app/Http/Requests/Subscription/EditSubscriptionRequest.php @@ -0,0 +1,30 @@ +user()->isAdmin(); + } + +} diff --git a/app/Http/Requests/Subscription/ShowSubscriptionRequest.php b/app/Http/Requests/Subscription/ShowSubscriptionRequest.php new file mode 100644 index 000000000000..45a239c14149 --- /dev/null +++ b/app/Http/Requests/Subscription/ShowSubscriptionRequest.php @@ -0,0 +1,29 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/Subscription/StoreSubscriptionRequest.php b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php new file mode 100644 index 000000000000..03c06099db07 --- /dev/null +++ b/app/Http/Requests/Subscription/StoreSubscriptionRequest.php @@ -0,0 +1,45 @@ +user()->isAdmin(); + } + + public function rules() + { + return [ + 'target_url' => 'required', + 'event_id' => 'required', + ]; + } + + protected function prepareForValidation() + { + $input = $this->all(); + + + $this->replace($input); + } +} diff --git a/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php new file mode 100644 index 000000000000..3313b6942eff --- /dev/null +++ b/app/Http/Requests/Subscription/UpdateSubscriptionRequest.php @@ -0,0 +1,46 @@ +user()->isAdmin(); + } + + public function rules() + { + return [ + ]; + } + + protected function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } +} diff --git a/app/Http/Requests/Token/BulkTokenRequest.php b/app/Http/Requests/Token/BulkTokenRequest.php new file mode 100644 index 000000000000..4d111ffafa03 --- /dev/null +++ b/app/Http/Requests/Token/BulkTokenRequest.php @@ -0,0 +1,46 @@ +has('action')) { + return false; + } + + if (!in_array($this->action, $this->getBulkOptions(), true)) { + return false; + } + + return auth()->user()->isAdmin(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = $this->getGlobalRules(); + + /** We don't require IDs on bulk storing. */ + if ($this->action !== self::$STORE_METHOD) { + $rules['ids'] = ['required']; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Token/CreateTokenRequest.php b/app/Http/Requests/Token/CreateTokenRequest.php new file mode 100644 index 000000000000..2eefc1cd5f2c --- /dev/null +++ b/app/Http/Requests/Token/CreateTokenRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/Token/DestroyTokenRequest.php b/app/Http/Requests/Token/DestroyTokenRequest.php new file mode 100644 index 000000000000..90c09721ad0d --- /dev/null +++ b/app/Http/Requests/Token/DestroyTokenRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/Token/EditTokenRequest.php b/app/Http/Requests/Token/EditTokenRequest.php new file mode 100644 index 000000000000..65c2be159049 --- /dev/null +++ b/app/Http/Requests/Token/EditTokenRequest.php @@ -0,0 +1,29 @@ +user()->isAdmin(); + } + +} diff --git a/app/Http/Requests/Token/ShowTokenRequest.php b/app/Http/Requests/Token/ShowTokenRequest.php new file mode 100644 index 000000000000..b608284d5b3d --- /dev/null +++ b/app/Http/Requests/Token/ShowTokenRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/app/Http/Requests/Token/StoreTokenRequest.php b/app/Http/Requests/Token/StoreTokenRequest.php new file mode 100644 index 000000000000..fa767d558bbb --- /dev/null +++ b/app/Http/Requests/Token/StoreTokenRequest.php @@ -0,0 +1,38 @@ +user()->isAdmin(); + } + + public function rules() + { + + return [ + 'name' => 'required', + ]; + } + +} diff --git a/app/Http/Requests/Token/UpdateTokenRequest.php b/app/Http/Requests/Token/UpdateTokenRequest.php new file mode 100644 index 000000000000..335719311a14 --- /dev/null +++ b/app/Http/Requests/Token/UpdateTokenRequest.php @@ -0,0 +1,34 @@ +user()->isAdmin(); + } + + +} diff --git a/app/Jobs/Company/CreateCompanyToken.php b/app/Jobs/Company/CreateCompanyToken.php index db9e0a13f61c..26bdf1601140 100644 --- a/app/Jobs/Company/CreateCompanyToken.php +++ b/app/Jobs/Company/CreateCompanyToken.php @@ -53,14 +53,15 @@ class CreateCompanyToken implements ShouldQueue { $this->custom_token_name = $this->custom_token_name ?: $this->user->first_name. ' '. $this->user->last_name; - $ct = CompanyToken::create([ - 'user_id' => $this->user->id, - 'account_id' => $this->user->account->id, - 'token' => Str::random(64), - 'name' => $this->custom_token_name ?: $this->user->first_name. ' '. $this->user->last_name, - 'company_id' => $this->company->id, - ]); + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->user->account->id; + $company_token->name = $this->custom_token_name ?: $this->user->first_name. ' '. $this->user->last_name; + $company_token->token = Str::random(64); + $company_token->save(); + - return $ct; + return $company_token; } } diff --git a/app/Models/CompanyToken.php b/app/Models/CompanyToken.php index d238d22eb6a1..9cb8295cf4ae 100644 --- a/app/Models/CompanyToken.php +++ b/app/Models/CompanyToken.php @@ -11,33 +11,22 @@ namespace App\Models; +use App\Models\Filterable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; -class CompanyToken extends Model +class CompanyToken extends BaseModel { use SoftDeletes; - - /** - * @var bool - */ - public $timestamps = false; + use Filterable; - protected $guarded = [ - 'id', + protected $fillable = [ + 'name' ]; protected $with = [ - // 'user', - // 'company', ]; - protected $casts = [ - 'updated_at' => 'timestamp', - 'created_at' => 'timestamp', - 'deleted_at' => 'timestamp', - ]; - public function account() { return $this->belongsTo(Account::class); diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php new file mode 100644 index 000000000000..99efd71d4a4a --- /dev/null +++ b/app/Models/Subscription.php @@ -0,0 +1,69 @@ +belongsTo(User::class); + } + + public function company() + { + return $this->belongsTo(Company::class); + } +} diff --git a/app/Policies/CompanyTokenPolicy.php b/app/Policies/CompanyTokenPolicy.php new file mode 100644 index 000000000000..13d4ff20109c --- /dev/null +++ b/app/Policies/CompanyTokenPolicy.php @@ -0,0 +1,22 @@ + ActivityPolicy::class, Client::class => ClientPolicy::class, Company::class => CompanyPolicy::class, - Design::class => DesignPolicy::class, - Product::class => ProductPolicy::class, - Invoice::class => InvoicePolicy::class, + CompanyToken::class => CompanyTokenPolicy::class, + CompanyGateway::class => CompanyGatewayPolicy::class, Credit::class => CreditPolicy::class, + Design::class => DesignPolicy::class, + Expense::class => ExpensePolicy::class, + GroupSetting::class => GroupSettingPolicy::class, + Invoice::class => InvoicePolicy::class, Payment::class => PaymentPolicy::class, + Product::class => ProductPolicy::class, + Quote::class => QuotePolicy::class, RecurringInvoice::class => RecurringInvoicePolicy::class, RecurringQuote::class => RecurringQuotePolicy::class, - Quote::class => QuotePolicy::class, - User::class => UserPolicy::class, - GroupSetting::class => GroupSettingPolicy::class, - CompanyGateway::class => CompanyGatewayPolicy::class, + Subscription::class => SubscriptionPolicy::class, TaxRate::class => TaxRatePolicy::class, + User::class => UserPolicy::class, Vendor::class => VendorPolicy::class, - Expense::class => ExpensePolicy::class, ]; /** diff --git a/app/Repositories/TokenRepository.php b/app/Repositories/TokenRepository.php new file mode 100644 index 000000000000..82543528f10f --- /dev/null +++ b/app/Repositories/TokenRepository.php @@ -0,0 +1,45 @@ +fill($data); + + $company_token->save(); + + return $company_token; + } +} diff --git a/app/Transformers/CompanyTokenTransformer.php b/app/Transformers/CompanyTokenTransformer.php index cefa9022797a..55ace4cf0004 100644 --- a/app/Transformers/CompanyTokenTransformer.php +++ b/app/Transformers/CompanyTokenTransformer.php @@ -47,6 +47,7 @@ class CompanyTokenTransformer extends EntityTransformer 'updated_at' => (int)$company_token->updated_at, 'archived_at' => (int)$company_token->deleted_at, 'created_at' => (int)$company_token->created_at, + 'is_deleted' => (bool)$company_token->is_deleted, ]; } } diff --git a/app/Transformers/SubscriptionTransformer.php b/app/Transformers/SubscriptionTransformer.php new file mode 100644 index 000000000000..b27d8d172db4 --- /dev/null +++ b/app/Transformers/SubscriptionTransformer.php @@ -0,0 +1,39 @@ + (string) $this->encodePrimaryKey($subscription->id), + 'company_id' => (string) $this->encodePrimaryKey($subscription->company_id), + 'user_id' => (string) $this->encodePrimaryKey($subscription->user_id), + 'archived_at' => (int)$subscription->deleted_at, + 'updated_at' => (int)$subscription->updated_at, + 'created_at' => (int)$subscription->created_at, + 'is_deleted' => (bool)$subscription->is_deleted, + 'target_url' => $subscription->target_url ? (string) $subscription->target_url : '', + 'event_id' => (string) $subscription->event_id, + 'format' => (string) $subscription->format, + ]; + } +} diff --git a/database/migrations/2020_04_08_234530_add_is_deleted_column_to_company_tokens_table.php b/database/migrations/2020_04_08_234530_add_is_deleted_column_to_company_tokens_table.php new file mode 100644 index 000000000000..c9df14d60e46 --- /dev/null +++ b/database/migrations/2020_04_08_234530_add_is_deleted_column_to_company_tokens_table.php @@ -0,0 +1,46 @@ +boolean('is_deleted')->default(0); + }); + + /* add ability of external APIs to be triggered after a model has been created nor updated */ + Schema::create('subscriptions', function ($table) { + $table->increments('id'); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('user_id')->nullable(); + $table->unsignedInteger('event_id')->nullable(); + $table->boolean('is_deleted')->default(0); + $table->string('target_url'); + $table->enum('format', ['JSON', 'UBL'])->default('JSON'); + $table->timestamps(6); + $table->softDeletes('deleted_at', 6); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/routes/api.php b/routes/api.php index b01be9db040b..b09daa25c385 100644 --- a/routes/api.php +++ b/routes/api.php @@ -93,6 +93,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('migration/start/{company}', 'MigrationController@startMigration'); Route::resource('companies', 'CompanyController');// name = (companies. index / create / show / update / destroy / edit + + Route::resource('tokens', 'TokenController');// name = (tokens. index / create / show / update / destroy / edit Route::resource('company_gateways', 'CompanyGatewayController'); @@ -116,6 +118,12 @@ 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::resource('subscriptions', 'SubscriptionController'); + Route::post('subscriptions/bulk', 'SubscriptionController@bulk')->name('subscriptions.bulk'); + /* Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 1c4f12e4842c..899fcaba71b4 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -17,11 +17,12 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Http\Request; +use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; +use Illuminate\Support\Str; use Tests\TestCase; -use Illuminate\Routing\Middleware\ThrottleRequests; /** * @test @@ -295,15 +296,16 @@ class ClientTest extends TestCase 'is_locked' => 0, ]); - $ct = CompanyToken::create([ - 'account_id' => $account->id, - 'company_id' => $company->id, - 'user_id' => $user->id, - 'token' => \Illuminate\Support\Str::random(64), - 'name' => $user->first_name. ' '. $user->last_name, - ]); - - $token = $ct->token; + $company_token = new CompanyToken; + $company_token->user_id = $user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $account->id; + $company_token->name = $user->first_name. ' '. $user->last_name; + $company_token->token = Str::random(64); + $company_token->save(); + + + $token = $company_token->token; $data = [ 'name' => 'A loyal Client', diff --git a/tests/Feature/CompanyTokenApiTest.php b/tests/Feature/CompanyTokenApiTest.php new file mode 100644 index 000000000000..8c2af519ce49 --- /dev/null +++ b/tests/Feature/CompanyTokenApiTest.php @@ -0,0 +1,129 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + } + + public function testCompanyTokenList() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->get('/api/v1/tokens'); + + + $response->assertStatus(200); + } + + public function testCompanyTokenPost() + { + $data = [ + 'name' => $this->faker->firstName, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->post('/api/v1/tokens', $data); + + + $response->assertStatus(200); + } + + public function testCompanyTokenPut() + { + $company_token = CompanyToken::whereCompanyId($this->company->id)->first(); + + $data = [ + 'name' => "newname", + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->put('/api/v1/tokens/'.$this->encodePrimaryKey($company_token->id), $data); + + + $response->assertStatus(200); + $arr = $response->json(); + + $this->assertEquals('newname', $arr['data']['name']); + + } + + public function testCompanyTokenGet() + { + + $company_token = CompanyToken::whereCompanyId($this->company->id)->first(); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->get('/api/v1/tokens/'.$this->encodePrimaryKey($company_token->id)); + + + $response->assertStatus(200); + } + + public function testCompanyTokenNotArchived() + { + $company_token = CompanyToken::whereCompanyId($this->company->id)->first(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->get('/api/v1/tokens/'.$this->encodePrimaryKey($company_token->id)); + + $arr = $response->json(); + + $this->assertEquals(0, $arr['data']['archived_at']); + } + +} diff --git a/tests/Feature/LoginTest.php b/tests/Feature/LoginTest.php index 43008ac3e616..c0a87b99d9ef 100644 --- a/tests/Feature/LoginTest.php +++ b/tests/Feature/LoginTest.php @@ -145,14 +145,14 @@ class LoginTest extends TestCase $account->default_company_id = $company->id; $account->save(); + $company_token = new CompanyToken; + $company_token->user_id = $user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $account->id; + $company_token->name = $user->first_name. ' '. $user->last_name; + $company_token->token = \Illuminate\Support\Str::random(64); + $company_token->save(); - $ct = CompanyToken::create([ - 'user_id' => $user->id, - 'account_id' => $account->id, - 'token' => \Illuminate\Support\Str::random(64), - 'name' => $user->first_name. ' '. $user->last_name, - 'company_id' => $company->id, - ]); $user->companies()->attach($company->id, [ 'account_id' => $account->id, diff --git a/tests/Feature/SubscriptionAPITest.php b/tests/Feature/SubscriptionAPITest.php new file mode 100644 index 000000000000..09aa65646d19 --- /dev/null +++ b/tests/Feature/SubscriptionAPITest.php @@ -0,0 +1,127 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + } + + public function testSubscriptionGetRoute() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/subscriptions'); + + $response->assertStatus(200); + } + + public function testSubscriptionPostRoute() + { + $data = [ + 'target_url' => 'http://hook.com', + 'event_id' => 1, + 'format' => 'JSON' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/subscriptions', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(1, $arr['data']['event_id']); + + $data = [ + 'event_id' => 2, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->put('/api/v1/subscriptions/'.$arr['data']['id'], $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(2, $arr['data']['event_id']); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->delete('/api/v1/subscriptions/'.$arr['data']['id']); + + $arr = $response->json(); + + $this->assertNotNull($arr['data']['archived_at']); + + + $data = [ + 'ids' => [$arr['data']['id']], + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->post('/api/v1/subscriptions/bulk?action=restore', $data); + + $arr = $response->json(); + +\Log::error(print_r($arr,1)); + + $this->assertEquals(0,$arr['data'][0]['archived_at']); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token + ])->post('/api/v1/subscriptions/bulk?action=delete', $data); + + $arr = $response->json(); + + $this->assertNotNull($arr['data'][0]['archived_at']); + $this->assertTrue($arr['data'][0]['is_deleted']); + } + +} \ No newline at end of file diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index ab96484f6b16..167b8a468d4d 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -132,15 +132,15 @@ class UserTest extends TestCase $company2 = factory(\App\Models\Company::class)->create([ 'account_id' => $this->account->id, ]); + + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $company2->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = \Illuminate\Support\Str::random(64); + $company_token->save(); - /* Create New Company Token*/ - $user_1_company_token = CompanyToken::create([ - 'user_id' => $this->user->id, - 'company_id' => $company2->id, - 'account_id' => $this->account->id, - 'name' => 'test token', - 'token' => \Illuminate\Support\Str::random(64), - ]); /*Manually link this user to the company*/ $cu = CompanyUserFactory::create($this->user->id, $company2->id, $this->account->id); @@ -156,7 +156,7 @@ class UserTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $user_1_company_token->token, + 'X-API-TOKEN' => $company_token->token, ])->post('/api/v1/users/'.$this->encodePrimaryKey($new_user->id).'/attach_to_company?include=company_user'); $response->assertStatus(200); @@ -179,7 +179,7 @@ class UserTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $user_1_company_token->token, + 'X-API-TOKEN' => $company_token->token, ])->post('/api/v1/users?include=company_user', $data); $response->assertStatus(200); @@ -215,7 +215,7 @@ class UserTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $user_1_company_token->token, + 'X-API-TOKEN' => $company_token->token, 'X-API-PASSWORD' => 'ALongAndBriliantPassword', ])->put('/api/v1/users/'.$this->encodePrimaryKey($user->id).'?include=company_user', $data); diff --git a/tests/Integration/CompanyLedgerTest.php b/tests/Integration/CompanyLedgerTest.php index 596c009e4fce..cac91786f1a6 100644 --- a/tests/Integration/CompanyLedgerTest.php +++ b/tests/Integration/CompanyLedgerTest.php @@ -115,13 +115,13 @@ class CompanyLedgerTest extends TestCase $this->token = \Illuminate\Support\Str::random(64); - $company_token = CompanyToken::create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'account_id' => $this->account->id, - 'name' => 'test token', - 'token' => $this->token, - ]); + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->token; + $company_token->save(); $this->client = factory(\App\Models\Client::class)->create([ 'user_id' => $this->user->id, diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 0038d1cee407..19059b0a82f5 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -132,25 +132,14 @@ trait MockAccountData $this->token = \Illuminate\Support\Str::random(64); - $company_token = CompanyToken::create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'account_id' => $this->account->id, - 'name' => 'test token', - 'token' => $this->token, - ]); + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->token; + $company_token->save(); - // $this->user->companies()->attach($this->company->id, [ - // 'account_id' => $this->account->id, - // 'is_owner' => 1, - // 'is_admin' => 1, - // 'is_locked' => 0, - // 'permissions' => '', - // 'settings' => json_encode(DefaultSettings::userSettings()), - // ]); - - // $this->client = ClientFactory::create($this->company->id, $this->user->id); - // $this->client->save(); $this->client = factory(\App\Models\Client::class)->create([ 'user_id' => $this->user->id,