Skip to content

Commit

Permalink
Import/export questions (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
mako321 authored Aug 10, 2023
1 parent c2e6ce0 commit 3cfa729
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 2 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"type": "package",
"require": {
"php": "^7.4|^8.0|^8.1",
"darkaonline/l5-swagger": "^8.5",
"escolalms/auth": "^0",
"escolalms/categories": "^0",
"escolalms/core": "^1",
"escolalms/settings": "^0",
"escolalms/topic-types": "^0",
"laravel/framework": "^8|^9"
"laravel/framework": "^8|^9",
"maatwebsite/excel": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
Expand Down
33 changes: 33 additions & 0 deletions src/Dtos/Criteria/ExportQuestionsCriteriaDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace EscolaLms\TopicTypeGift\Dtos\Criteria;

use EscolaLms\Core\Dtos\Contracts\DtoContract;
use EscolaLms\Core\Dtos\Contracts\InstantiateFromRequest;
use EscolaLms\Core\Dtos\CriteriaDto as BaseCriteriaDto;
use EscolaLms\Core\Repositories\Criteria\Primitives\EqualCriterion;
use EscolaLms\Core\Repositories\Criteria\Primitives\InCriterion;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;

class ExportQuestionsCriteriaDto extends BaseCriteriaDto implements DtoContract, InstantiateFromRequest
{
public static function instantiateFromRequest(Request $request): self
{
$criteria = new Collection();

if ($request->has('topic_gift_quiz_id')) {
$criteria->push(new EqualCriterion('topic_gift_quiz_id', $request->get('topic_gift_quiz_id')));
}

if ($request->has('category_ids')) {
$criteria->push(new InCriterion('category_id', $request->get('category_ids')));
}

if ($request->has('ids')) {
$criteria->push(new InCriterion('id', $request->get('ids')));
}

return new static($criteria);
}
}
2 changes: 2 additions & 0 deletions src/Enum/TopicTypeGiftPermissionEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class TopicTypeGiftPermissionEnum extends BasicEnum
public const CREATE_GIFT_QUIZ_QUESTION = 'gift-quiz-question_create';
public const UPDATE_GIFT_QUIZ_QUESTION = 'gift-quiz-question_update';
public const DELETE_GIFT_QUIZ_QUESTION = 'gift-quiz-question_delete';
public const EXPORT_GIFT_QUIZ_QUESTION = 'gift-quiz-question_export';
public const IMPORT_GIFT_QUIZ_QUESTION = 'gift-quiz-question_import';

public static function studentPermissions():array
{
Expand Down
47 changes: 47 additions & 0 deletions src/Export/QuestionExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace EscolaLms\TopicTypeGift\Export;

use EscolaLms\TopicTypeGift\Dtos\Criteria\ExportQuestionsCriteriaDto;
use EscolaLms\TopicTypeGift\Models\GiftQuestion;
use EscolaLms\TopicTypeGift\Repositories\Contracts\GiftQuestionRepositoryContract;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;

class QuestionExport implements FromCollection, WithHeadings, ShouldAutoSize, WithStrictNullComparison
{
use Exportable;

private ExportQuestionsCriteriaDto $criteria;

public function __construct(ExportQuestionsCriteriaDto $criteriaDto)
{
$this->criteria = $criteriaDto;
}

public function collection(): Collection
{
return app(GiftQuestionRepositoryContract::class)
->searchByCriteria($this->criteria->toArray())
->map(function (GiftQuestion $question) {
return [
'value' => $question->value,
'type' => $question->type,
'score' => $question->score,
];
});
}

public function headings(): array
{
return [
__('Question'),
__('Type'),
__('Score'),
];
}
}
23 changes: 23 additions & 0 deletions src/Http/Controllers/GiftQuestionApiAdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@

namespace EscolaLms\TopicTypeGift\Http\Controllers;

use Carbon\Carbon;
use EscolaLms\Core\Http\Controllers\EscolaLmsBaseController;
use EscolaLms\TopicTypeGift\Export\QuestionExport;
use EscolaLms\TopicTypeGift\Http\Controllers\Swagger\GiftQuestionApiAdminSwagger;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminCreateGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminDeleteGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminExportGiftQuestionsRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminImportGiftQuestionsRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminSortGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminUpdateGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Resources\AdminGiftQuestionResource;
use EscolaLms\TopicTypeGift\Import\QuestionImport;
use EscolaLms\TopicTypeGift\Services\Contracts\GiftQuestionServiceContract;
use Illuminate\Http\JsonResponse;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class GiftQuestionApiAdminController extends EscolaLmsBaseController implements GiftQuestionApiAdminSwagger
{
Expand Down Expand Up @@ -48,4 +55,20 @@ public function sort(AdminSortGiftQuestionRequest $request): JsonResponse

return $this->sendSuccess(__('Gift questions sorted successfully.'));
}

public function export(AdminExportGiftQuestionsRequest $request): BinaryFileResponse
{
return Excel::download(
new QuestionExport($request->toDto()),
'questions.xlsx',
\Maatwebsite\Excel\Excel::XLSX
);
}

public function import(AdminImportGiftQuestionsRequest $request): JsonResponse
{
Excel::import(new QuestionImport($request->getQuizId()), $request->getFile());

return $this->sendSuccess(__('Gift questions imported successfully.'));
}
}
95 changes: 95 additions & 0 deletions src/Http/Controllers/Swagger/GiftQuestionApiAdminSwagger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminCreateGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminDeleteGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminExportGiftQuestionsRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminImportGiftQuestionsRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminSortGiftQuestionRequest;
use EscolaLms\TopicTypeGift\Http\Requests\Admin\AdminUpdateGiftQuestionRequest;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

interface GiftQuestionApiAdminSwagger
{
Expand Down Expand Up @@ -184,4 +187,96 @@ public function delete(AdminDeleteGiftQuestionRequest $request): JsonResponse;
* )
*/
public function sort(AdminSortGiftQuestionRequest $request): JsonResponse;

/**
* @OA\Get(
* path="/api/admin/gift-questions/export",
* summary="Export Gift Questions",
* tags={"Admin Gift Question"},
* description="Export Gift Questions",
* security={
* {"passport": {}},
* },
* @OA\Parameter(
* name="topic_gift_quiz_id",
* required=false,
* in="path",
* @OA\Schema(
* type="integer",
* ),
* ),
* @OA\Parameter(
* name="category_ids[]",
* required=false,
* in="query",
* @OA\Schema(
* type="array",
* @OA\Items(
* @OA\Schema(
* type="integer"
* ),
* ),
* ),
* ),
* @OA\Parameter(
* name="ids[]",
* required=false,
* in="query",
* @OA\Schema(
* type="array",
* @OA\Items(
* @OA\Schema(
* type="integer"
* ),
* ),
* ),
* ),
* @OA\Response(
* response=200,
* description="successful operation",
* @OA\MediaType(
* mediaType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
* ),
* ),
* @OA\Response(
* response=422,
* description="Bad request",
* @OA\MediaType(
* mediaType="application/json"
* )
* )
* )
*/
public function export(AdminExportGiftQuestionsRequest $request): BinaryFileResponse;

/**
* @OA\Post(
* path="/api/admin/gift-questions/import",
* summary="Import Gift Questions",
* tags={"Admin Gift Question"},
* description="Import Gift Questions",
* security={
* {"passport": {}},
* },
* @OA\RequestBody(
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(ref="#/components/schemas/AdminImportGiftQuestionsRequest")
* ),
* ),
* @OA\Response(
* response=200,
* description="successful operation",
* ),
* @OA\Response(
* response=422,
* description="Bad request",
* @OA\MediaType(
* mediaType="application/json"
* )
* )
* )
*/
public function import(AdminImportGiftQuestionsRequest $request): JsonResponse;
}
31 changes: 31 additions & 0 deletions src/Http/Requests/Admin/AdminExportGiftQuestionsRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace EscolaLms\TopicTypeGift\Http\Requests\Admin;

use EscolaLms\TopicTypeGift\Dtos\Criteria\ExportQuestionsCriteriaDto;
use EscolaLms\TopicTypeGift\Enum\TopicTypeGiftPermissionEnum;
use Illuminate\Foundation\Http\FormRequest;

class AdminExportGiftQuestionsRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can(TopicTypeGiftPermissionEnum::EXPORT_GIFT_QUIZ_QUESTION);
}

public function rules(): array
{
return [
'topic_gift_quiz_id' => ['sometimes', 'integer'],
'category_ids' => ['sometimes', 'array'],
'category_ids.*' => ['sometimes', 'integer'],
'ids' => ['sometimes', 'array'],
'ids.*' => ['sometimes', 'integer'],
];
}

public function toDto(): ExportQuestionsCriteriaDto
{
return ExportQuestionsCriteriaDto::instantiateFromRequest($this);
}
}
50 changes: 50 additions & 0 deletions src/Http/Requests/Admin/AdminImportGiftQuestionsRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace EscolaLms\TopicTypeGift\Http\Requests\Admin;

use EscolaLms\TopicTypeGift\Enum\TopicTypeGiftPermissionEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\UploadedFile;

/**
* @OA\Schema(
* schema="AdminImportGiftQuestionsRequest",
* required={"topic_gift_quiz_id", "file"},
* @OA\Property(
* property="topic_gift_quiz_id",
* description="topic_gift_quiz_id",
* type="number"
* ),
* @OA\Property(
* property="file",
* description="file",
* type="file"
* )
* )
*
*/
class AdminImportGiftQuestionsRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can(TopicTypeGiftPermissionEnum::IMPORT_GIFT_QUIZ_QUESTION);
}

public function rules(): array
{
return [
'topic_gift_quiz_id' => ['required', 'integer', 'exists:topic_gift_quizzes,id'],
'file' => ['required', 'file'],
];
}

public function getQuizId(): int
{
return $this->get('topic_gift_quiz_id');
}

public function getFile(): UploadedFile
{
return $this->file('file');
}
}
42 changes: 42 additions & 0 deletions src/Import/QuestionImport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace EscolaLms\TopicTypeGift\Import;

use EscolaLms\TopicTypeGift\Dtos\GiftQuestionDto;
use EscolaLms\TopicTypeGift\Services\Contracts\GiftQuestionServiceContract;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;

class QuestionImport implements ToCollection, WithHeadingRow, WithValidation
{
private int $quizId;

public function __construct(int $quizId)
{
$this->quizId = $quizId;
}

public function collection(Collection $collection): void
{
DB::transaction(function () use ($collection) {
/** @var GiftQuestionServiceContract $service */
$service = app(GiftQuestionServiceContract::class);

foreach ($collection as $item) {
$dto = new GiftQuestionDto($this->quizId, $item->get('question'), $item->get('score'), null, null);
$service->create($dto);
}
});
}

public function rules(): array
{
return [
'*.question' => ['required', 'string'],
'*.score' => ['required', 'integer', 'min:1'],
];
}
}
2 changes: 2 additions & 0 deletions src/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
Route::delete('{id}', [GiftQuestionApiAdminController::class, 'delete']);
Route::put('{id}', [GiftQuestionApiAdminController::class, 'update']);
Route::post('sort', [GiftQuestionApiAdminController::class, 'sort']);
Route::get('export', [GiftQuestionApiAdminController::class, 'export']);
Route::post('import', [GiftQuestionApiAdminController::class, 'import']);
});

Route::prefix('quiz-attempts')->group(function () {
Expand Down
Loading

0 comments on commit 3cfa729

Please sign in to comment.