Skip to content

Commit 695b6d0

Browse files
Admin bulk users import (ActiveLearningStudio#113)
* User deletion API in Progres * Admin Users Edit API in Progress * Completed update indexes API of projects and related models * Projects indexes algo modifications * API routes protect, and project clone in progress * User profile update complete and clone project * Clone method support for users other than currently logged in * Admin role verify middleware * Remove unused namespaces * LMS Settings CRUD API's Scope for users and LMS Settings Model * LMS Setting Resource fields define and project indexes quick fix * Get project data for preview/share * Validations checks and Elastic search project udpate fix * Index/de-index related tables data * Hotfix for admin panel change password validation on edit profile page. * Fix for password validation * Activities admin CRUD in progress * Public indexes toggle implementation * LMS Setting field add * Activity types CRUD completed & Activity Item in Progress * Completed Activity Items CRUD Api's and created generic get fucntion * Admin Code Optimizations * User stats includes basic reporting * Validations and formatting fixes * Import Bulk CSV Users Co-authored-by: Aqeel <[email protected]>
1 parent 51202f1 commit 695b6d0

19 files changed

+1173
-4
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/public/storage
55

66
/storage/*.key
7-
7+
/storage/framework/larave-excel/*
88
/vendor
99

1010
.env
@@ -18,4 +18,4 @@ Homestead.yaml
1818
npm-debug.log
1919
yarn-error.log
2020
.vscode/*
21-
/nbproject
21+
/nbproject

app/Exports/ArrayExport.php

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace App\Exports;
4+
5+
use Maatwebsite\Excel\Concerns\Exportable;
6+
use Maatwebsite\Excel\Concerns\FromArray;
7+
use Maatwebsite\Excel\Concerns\WithHeadings;
8+
9+
class ArrayExport implements FromArray, WithHeadings
10+
{
11+
use Exportable;
12+
13+
/**
14+
* @var
15+
*/
16+
private $data;
17+
18+
/**
19+
* @var
20+
*/
21+
private $headings;
22+
23+
/**
24+
* ArrayExport constructor.
25+
* @param $data
26+
* @param $headings
27+
*/
28+
public function __construct($data, $headings)
29+
{
30+
$this->data = $data; //Inject data
31+
$this->headings = $headings; //custom headings
32+
}
33+
34+
/**
35+
* @return array
36+
*/
37+
public function array(): array // this was query() before
38+
{
39+
return $this->data;
40+
}
41+
42+
/**
43+
* @return array
44+
*/
45+
public function headings(): array
46+
{
47+
return $this->headings;
48+
}
49+
}

app/Http/Controllers/Api/V1/Admin/UserController.php

+32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\Exceptions\GeneralException;
66
use App\Http\Controllers\Controller;
7+
use App\Http\Requests\Admin\ImportBulkUsers;
78
use App\Http\Requests\Admin\StoreUser;
89
use App\Http\Requests\Admin\UpdateUser;
910
use App\Http\Resources\V1\Admin\UserResource;
@@ -15,6 +16,7 @@
1516
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
1617
use Illuminate\Http\Response;
1718
use Illuminate\View\View;
19+
use Symfony\Component\HttpFoundation\BinaryFileResponse;
1820

1921
class UserController extends Controller
2022
{
@@ -102,4 +104,34 @@ public function assignStarterProjects(): void
102104
\Artisan::call('project:starters');
103105
dd("Assign starter projects command invoked successfully.");
104106
}
107+
108+
/**
109+
* Users import sample file
110+
* @return BinaryFileResponse
111+
* @throws GeneralException
112+
*/
113+
public function downloadSampleFile(): BinaryFileResponse
114+
{
115+
$sampleFile = config('constants.users.sample-file');
116+
if ($sampleFile) {
117+
return response()->download($sampleFile, 'users-import-sample.csv', ['Content-Type' => 'text/csv']);
118+
}
119+
throw new GeneralException('Sample file not found!');
120+
}
121+
122+
/**
123+
* @param ImportBulkUsers $request
124+
* @return Application|ResponseFactory|Response
125+
* @throws GeneralException
126+
*/
127+
public function bulkImport(ImportBulkUsers $request)
128+
{
129+
$validated = $request->validated();
130+
$response = $this->userRepository->bulkImport($validated);
131+
// if report is present then set status 206 for partial success and show error messages
132+
if ($response['report']){
133+
return response(['errors' => [$response['message']], 'report' => $response['report']], 206);
134+
}
135+
return response(['message' => $response['message'], 'report' => $response['report']], 200);
136+
}
105137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Admin;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class ImportBulkUsers extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*
12+
* @return bool
13+
*/
14+
public function authorize()
15+
{
16+
return true;
17+
}
18+
19+
/**
20+
* Get the validation rules that apply to the request.
21+
*
22+
* @return array
23+
*/
24+
public function rules()
25+
{
26+
return [
27+
'import_file' => 'required|mimes:csv,txt',
28+
];
29+
}
30+
}

app/Imports/UsersImport.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace App\Imports;
4+
5+
use App\Rules\StrongPassword;
6+
use App\User;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Support\Facades\Hash;
9+
use Illuminate\Support\Facades\Log;
10+
use Maatwebsite\Excel\Concerns\Importable;
11+
use Maatwebsite\Excel\Concerns\SkipsErrors;
12+
use Maatwebsite\Excel\Concerns\SkipsFailures;
13+
use Maatwebsite\Excel\Concerns\SkipsOnError;
14+
use Maatwebsite\Excel\Concerns\SkipsOnFailure;
15+
use Maatwebsite\Excel\Concerns\ToModel;
16+
use Maatwebsite\Excel\Concerns\WithBatchInserts;
17+
use Maatwebsite\Excel\Concerns\WithChunkReading;
18+
use Maatwebsite\Excel\Concerns\WithValidation;
19+
use Maatwebsite\Excel\Concerns\WithHeadingRow;
20+
21+
class UsersImport implements ToModel, WithBatchInserts, WithChunkReading, WithValidation, WithHeadingRow, SkipsOnFailure, SkipsOnError
22+
{
23+
use Importable, SkipsFailures, SkipsErrors;
24+
25+
/**
26+
* @param array $row
27+
* @return Model|null
28+
*/
29+
public function model(array $row)
30+
{
31+
return new User([
32+
'first_name' => $row['first_name'],
33+
'last_name' => $row['last_name'],
34+
'organization_name' => $row['organization_name'],
35+
'job_title' => $row['job_title'],
36+
'email' => $row['email'],
37+
'password' => Hash::make($row['password']),
38+
]);
39+
}
40+
41+
/**
42+
* @return int
43+
*/
44+
public function batchSize(): int
45+
{
46+
return 1000;
47+
}
48+
49+
/**
50+
* @return int
51+
*/
52+
public function chunkSize(): int
53+
{
54+
return 1000;
55+
}
56+
57+
/**
58+
* @return array
59+
*/
60+
public function rules(): array
61+
{
62+
return [
63+
'*.first_name' => 'required|string|max:255',
64+
'*.last_name' => 'required|string|max:255',
65+
'*.organization_name' => 'max:255',
66+
'*.job_title' => 'max:255',
67+
'*.email' => 'required|email|max:255|unique:users,email',
68+
'*.password' => ['required', 'string', new StrongPassword],
69+
];
70+
}
71+
}

app/Repositories/Admin/User/UserRepository.php

+51
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
namespace App\Repositories\Admin\User;
44

55
use App\Exceptions\GeneralException;
6+
use App\Exports\ArrayExport;
7+
use App\Imports\UsersImport;
68
use App\Jobs\AssignStarterProjects;
79
use App\Repositories\Admin\BaseRepository;
810
use App\Repositories\Admin\Project\ProjectRepository;
911
use App\User;
1012
use Illuminate\Auth\Events\Registered;
13+
use Illuminate\Foundation\Bus\PendingDispatch;
1114
use Illuminate\Support\Facades\Log;
1215
use Illuminate\Support\Str;
1316

@@ -142,4 +145,52 @@ public function reportBasic($data)
142145
});
143146
return $this->getDtPaginated();
144147
}
148+
149+
/**
150+
* @param $data
151+
* @return array
152+
* @throws GeneralException
153+
*/
154+
public function bulkImport($data)
155+
{
156+
try {
157+
$import = new UsersImport();
158+
$import->import($data['import_file']);
159+
$error = $this->bulkError($import);
160+
return [
161+
'report' => $error ? asset('storage/temporary/users-import-report.csv') : false,
162+
'message' => $error ? 'Failed to import some rows data, please download detailed error report.' : 'All users data imported successfully!'
163+
];
164+
} catch (\Exception $e) {
165+
\Log::error($e->getMessage());
166+
}
167+
throw new GeneralException('Unable to import the users data, please try again later.');
168+
}
169+
170+
/**
171+
* @param $import
172+
* @return bool|PendingDispatch
173+
*/
174+
protected function bulkError($import)
175+
{
176+
$failures = $import->failures();
177+
$errors = [];
178+
$report = false;
179+
// check if any validation failures then create CSV FILE for report
180+
if (count($failures)) {
181+
foreach ($failures as $k => $failure) {
182+
$errors[$k] = $failure->values();
183+
$errors[$k]['status'] = 'failed';
184+
$errors[$k]['reason'] = implode(' | ', $failure->errors());
185+
}
186+
$report = (new ArrayExport($errors, array_keys($errors[0])))->store('public/temporary/users-import-report.csv');
187+
}
188+
$errors = $import->errors();
189+
// if any database error occurred
190+
if (count($errors)) {
191+
Log::error("USER IMPORT DATABASE ERRORS: ");
192+
Log::error(implode(", ", $errors));
193+
}
194+
return $report;
195+
}
145196
}

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"laravel/passport": "^9.3",
2424
"laravel/scout": "^8.2",
2525
"laravel/tinker": "^2.0",
26-
"laravel/ui": "^2.1"
26+
"laravel/ui": "^2.1",
27+
"maatwebsite/excel": "^3.1"
2728
},
2829
"require-dev": {
2930
"facade/ignition": "^2.0",

0 commit comments

Comments
 (0)