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