Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
CustomerController
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
6 / 6
26
100.00% covered (success)
100.00%
1 / 1
 store
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
7
 show
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 update
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 destroy
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 authenticate
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 activateAccount
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Http\Controllers\Api\V2;
4
5use App\Enums\ApiError;
6use App\Exceptions\Api\V2\ApiErrorException;
7use App\Exceptions\Api\V2\CrmException;
8use App\Http\Controllers\Controller;
9use App\Http\Requests\Api\V2\AuthenticateRequest;
10use App\Http\Requests\Api\V2\StoreCustomerRequest;
11use App\Http\Requests\Api\V2\UpdateCustomerRequest;
12use App\Http\Resources\Api\V2\CustomerResource;
13use App\Jobs\UpdateCustomerLastConnection;
14use App\Models\Customer;
15use App\Models\Service;
16use App\Models\SingleOptin;
17use Illuminate\Database\Eloquent\Builder;
18use Illuminate\Database\Eloquent\ModelNotFoundException;
19use Illuminate\Http\JsonResponse;
20use Illuminate\Http\Resources\Json\JsonResource;
21use Illuminate\Support\Facades\DB;
22
23class CustomerController extends Controller
24{
25    /**
26     * Creates a new customer for the specified service.
27     *
28     * @param \App\Http\Requests\Api\V2\StoreCustomerRequest $request
29     * @param \App\Models\Service                            $service
30     * @return \Illuminate\Http\Resources\Json\JsonResource
31     * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::customerExists if
32     *                                                  the customer with this email already exists.
33     * @throws \App\Exceptions\Api\V2\CrmException If saving the customer to DB failed.
34     */
35    public function store(StoreCustomerRequest $request, Service $service): JsonResource
36    {
37        $data = (object)$request->validated();
38
39        /** @var \App\Models\Customer */
40        $customer = Customer::query()->where('email', $data->email)->firstOrCreate([
41            'email' => $data->email
42        ]);
43
44        DB::transaction(function () use (&$customer, $data, $request, $service) {
45
46            $saved = $customer->save();
47            $customer->setOptIn($service->brand, $data->opt_in);
48
49            // * Delete Single optin if exists.
50            /** @var \App\Models\SingleOptin|null $singleOptin */
51            $singleOptin = SingleOptin::query()->where('email', $data->email)
52                ->whereHas('brand', function (Builder $q) use ($service) {
53                    $q->where('id', $service->brand->id);
54                })->first();
55            if ($singleOptin) {
56                $saved = $singleOptin->delete() && $saved;
57            }
58
59            /** @var \App\Models\CustomerInfo */
60            $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_NEW);
61            if ($customerInfo->exists) {
62                throw new ApiErrorException(ApiError::customerExists);
63            }
64            $customerInfo->fill($request->validated());
65            $customerInfo->brand()->associate($service->brand);
66            $customerInfo->customer()->associate($customer);
67            $saved = $customerInfo->save() && $saved;
68
69            /** @var \App\Models\CustomerLogin */
70            $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_NEW);
71            $customerLogin->fill($request->validated());
72            $customerLogin->brand()->associate($service->brand);
73            $customerLogin->customer()->associate($customer);
74            $saved = $customerLogin->save() && $saved;
75
76            if (!$saved) {
77                // @codeCoverageIgnoreStart
78                throw new CrmException('Api failed to store customer.');
79                // @codeCoverageIgnoreEnd
80            }
81        });
82
83        return new CustomerResource($service->brand, $customer);
84    }
85
86    /**
87     * Renders the specific customer infos.
88     *
89     * @param  \App\Models\Service  $service
90     * @param  \App\Models\Customer $customer
91     * @return \Illuminate\Http\Resources\Json\JsonResource
92     */
93    public function show(Service $service, Customer $customer): JsonResource
94    {
95        // * Make sure we are accessing a customer that belongs to our service.
96        $this->authorize('customerBelongsToService', $customer);
97
98        return new CustomerResource($service->brand, $customer);
99    }
100
101    /**
102     * Updates information for a specific customer.
103     *
104     * @param \App\Http\Requests\Api\V2\UpdateCustomerRequest $request
105     * @param \App\Models\Service                             $service
106     * @param \App\Models\Customer                            $customer
107     * @return \Illuminate\Http\Resources\Json\JsonResource
108     * @throws \App\Exceptions\Api\V2\CrmException If saving the customer to DB failed.
109     */
110    public function update(UpdateCustomerRequest $request, Service $service, Customer $customer): JsonResource
111    {
112        // * Make sure we are accessing a customer that belongs to our service.
113        $this->authorize('customerBelongsToService', $customer);
114
115        DB::transaction(function () use ($request, $customer, $service) {
116            // To correct with create.
117            $customer->fill($request->validated());
118            $saved = $customer->save();
119            if ($request->has('opt_in')) {
120                $customer->setOptIn($service->brand, $request->opt_in);
121            }
122
123            /** @var \App\Models\CustomerInfo */
124            $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL);
125            $customerInfo->fill($request->validated());
126            $saved = $customerInfo->save() && $saved;
127
128            /** @var \App\Models\CustomerLogin */
129            $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL);
130            $customerLogin->fill($request->validated());
131            $saved = $customerLogin->save() && $saved;
132
133            if (!$saved) {
134                // @codeCoverageIgnoreStart
135                throw new CrmException('Api failed to update customer.');
136                // @codeCoverageIgnoreEnd
137            }
138        });
139
140        return new CustomerResource($service->brand, $customer);
141    }
142
143    /**
144     * Deletes customer information for a specific service.
145     *
146     * @param \App\Models\Service  $service
147     * @param \App\Models\Customer $customer
148     * @return \Illuminate\Http\JsonResponse
149     * @throws \App\Exceptions\Api\V2\CrmException If removing the customer off the DB failed.
150     */
151    public function destroy(Service $service, Customer $customer): JsonResponse
152    {
153        // * Make sure we are accessing a customer that belongs to our service.
154        $this->authorize('customerBelongsToService', $customer);
155
156        // TODO: Gestion avec mesBR.
157        /** @var \App\Models\CustomerInfo */
158        $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL);
159        /** @var \App\Models\CustomerLogin */
160        $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL);
161
162        DB::transaction(function () use ($service, $customer, $customerLogin, $customerInfo) {
163            if (
164                $customerInfo->delete() === false ||
165                $customerLogin->delete() === false
166            ) {
167                // @codeCoverageIgnoreStart
168                throw new CrmException('Api failed to delete customer.');
169                // @codeCoverageIgnoreEnd
170            };
171
172            $customer->optInHistory()->whereHas('brand', function ($q) use ($service) {
173                $q->where('id', $service->brand_id);
174            })->delete();
175
176            // * Force loading real relation counts.
177            $customer->unsetRelation('logins');
178            $customer->unsetRelation('infos');
179            $logins = $customer->logins()->count();
180            $infos  = $customer->infos()->count();
181
182            // If customer is not associated with any brand then delete.
183            if (!$logins and !$infos) {
184                if ($customer->delete() === false) {
185                    // @codeCoverageIgnoreStart
186                    throw new CrmException('Api failed to update customer.');
187                    // @codeCoverageIgnoreEnd
188                }
189            }
190        });
191
192        return response()->json();
193    }
194
195    /**
196     * Authenticate the user.
197     *
198     * @param \App\Http\Requests\Api\V2\AuthenticateRequest $request
199     * @param \App\Models\Service                           $service
200     * @return \Illuminate\Http\Resources\Json\JsonResource
201     * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::authFailed in case
202     *                                                  of wrong credentials.
203     * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::customerNotActivated if
204     *                                                  customer account is not activated.
205     */
206    public function authenticate(AuthenticateRequest $request, Service $service): JsonResource
207    {
208        try {
209            /** @var \App\Models\Customer */
210            $customer = Customer::query()->where('email', $request->email)->firstOrFail();
211
212            /** @var \App\Models\CustomerLogin $customerLogin */
213            $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL);
214            if (!$customerLogin->passwordMatch($request->password)) {
215                throw new ApiErrorException(ApiError::authFailed);
216            }
217
218            if (!$customerLogin->status) {
219                throw new ApiErrorException(ApiError::customerNotActivated);
220            }
221
222            UpdateCustomerLastConnection::dispatch($customer, $customerLogin, now())
223                ->onConnection('database')->afterResponse();
224
225            return new CustomerResource($service->brand, $customer);
226        } catch (ModelNotFoundException $e) {
227            throw new ApiErrorException(ApiError::authFailed);
228        } //end try
229    }
230
231    /**
232     * Activate the account of the user.
233     *
234     * @param \App\Models\Customer $customer
235     * @param \App\Models\Service  $service
236     * @return \Illuminate\Http\JsonResponse
237     * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::customerAlreadyActive
238     *                                                  if customer account is already active.
239     * @throws \App\Exceptions\Api\V2\CrmException If saving the customer to DB failed.
240     */
241    public function activateAccount(Customer $customer, Service $service): JsonResponse
242    {
243        // * Make sure we are accessing a customer that belongs to our service.
244        $this->authorize('customerBelongsToService', $customer);
245
246        /** @var \App\Models\CustomerLogin $login */
247        $login = $customer->login($service->brand, Customer::FIRST_OR_FAIL);
248
249        if ($login->status) {
250            throw new ApiErrorException(ApiError::customerAlreadyActive);
251        }
252
253        $login->status = true;
254        if (!$login->save()) {
255            // @codeCoverageIgnoreStart
256            throw new CrmException('Failed to save login');
257            // @codeCoverageIgnoreEnd
258        }
259        return response()->json();
260    }
261}