Skip to content

Commit

Permalink
IP-349: As a content manager I can export the data related to solutio…
Browse files Browse the repository at this point in the history
…ns and voting via the solutions page so that I can download a relevant .csv file
  • Loading branch information
PavlosIsaris committed Jan 8, 2025
1 parent fd3d9e3 commit 1652d29
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 55 deletions.
64 changes: 22 additions & 42 deletions app/BusinessLogicLayer/Solution/SolutionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,51 +30,27 @@
use Illuminate\Support\Str;

class SolutionManager {
protected SolutionRepository $solutionRepository;
protected SolutionUpvoteRepository $solutionUpvoteRepository;
protected ProblemRepository $problemRepository;
protected CrowdSourcingProjectRepository $crowdSourcingProjectRepository;
protected SolutionTranslationManager $solutionTranslationManager;
protected SolutionStatusManager $solutionStatusManager;
protected LanguageRepository $languageRepository;
protected CrowdSourcingProjectTranslationManager $crowdSourcingProjectTranslationManager;
protected ProblemTranslationManager $problemTranslationManager;
protected SolutionShareRepository $solutionShareRepository;

public function __construct(
SolutionRepository $solutionRepository,
SolutionUpvoteRepository $solutionUpvoteRepository,
ProblemRepository $problemRepository,
CrowdSourcingProjectRepository $crowdSourcingProjectRepository,
SolutionTranslationManager $solutionTranslationManager,
SolutionStatusManager $solutionStatusManager,
LanguageRepository $languageRepository,
CrowdSourcingProjectTranslationManager $crowdSourcingProjectTranslationManager,
ProblemTranslationManager $problemTranslationManager,
SolutionShareRepository $solutionShareRepository
) {
$this->solutionRepository = $solutionRepository;
$this->solutionUpvoteRepository = $solutionUpvoteRepository;
$this->problemRepository = $problemRepository;
$this->crowdSourcingProjectRepository = $crowdSourcingProjectRepository;
$this->solutionTranslationManager = $solutionTranslationManager;
$this->solutionStatusManager = $solutionStatusManager;
$this->languageRepository = $languageRepository;
$this->crowdSourcingProjectTranslationManager = $crowdSourcingProjectTranslationManager;
$this->problemTranslationManager = $problemTranslationManager;
$this->solutionShareRepository = $solutionShareRepository;
}
public function __construct(protected SolutionRepository $solutionRepository,
protected SolutionUpvoteRepository $solutionUpvoteRepository,
protected ProblemRepository $problemRepository,
protected CrowdSourcingProjectRepository $crowdSourcingProjectRepository,
protected SolutionTranslationManager $solutionTranslationManager,
protected SolutionStatusManager $solutionStatusManager,
protected LanguageRepository $languageRepository,
protected CrowdSourcingProjectTranslationManager $crowdSourcingProjectTranslationManager,
protected ProblemTranslationManager $problemTranslationManager,
protected SolutionShareRepository $solutionShareRepository) {}

/**
* In create mode $problem_id is passed to fn & $solution_id is null
* In edit mode $problem_id is null & $solution_id is passed to fn
*/
public function getCreateEditSolutionViewModel(?int $problem_id, ?int $solution_id = null): CreateEditSolution {
if ($problem_id) {
if ($problem_id !== null && $problem_id !== 0) {
$problem = $this->problemRepository->find($problem_id);
}

if ($solution_id) {
if ($solution_id !== null && $solution_id !== 0) {
$solution = $this->solutionRepository->find($solution_id);
$problem = $solution->problem;
} else {
Expand Down Expand Up @@ -166,14 +142,14 @@ protected function storeSolutionWithStatus(array $attributes, int $status_id): S
} catch (\Exception $e) {
Log::error('Error: ' . $e->getCode() . ' ' . $e->getMessage());
DB::rollBack();
throw new \Exception('An error occurred while creating the solution');
throw new \Exception('An error occurred while creating the solution', $e->getCode(), $e);
}
}

/**
* @throws RepositoryException
*/
public function updateSolution(int $id, array $attributes) {
public function updateSolution(int $id, array $attributes): void {
if (isset($attributes['solution-image']) && $attributes['solution-image']->isValid()) {
$imgPath = FileHandler::uploadAndGetPath($attributes['solution-image'], 'solution_img');
} else {
Expand All @@ -193,7 +169,7 @@ public function updateSolution(int $id, array $attributes) {
'title' => $attributes['solution-title'],
'description' => $attributes['solution-description'],
];
$extraTranslations = isset($attributes['extra_translations']) ? json_decode($attributes['extra_translations']) : [];
$extraTranslations = isset($attributes['extra_translations']) ? json_decode((string) $attributes['extra_translations']) : [];
$this->solutionTranslationManager
->updateSolutionTranslations($id, $defaultTranslation, $extraTranslations);
}
Expand All @@ -217,8 +193,8 @@ public function getSolutionStatusesForManagementPage(): Collection {
return $solutionStatuses;
}

public function getFilteredSolutionsForManagement($filters): Collection {
if (count($filters['problemFilters'])) {
public function getFilteredSolutionsForManagement(array $filters): Collection {
if (count($filters['problemFilters']) > 0) {
return $this->solutionRepository->getSolutionsForManagementFilteredByProblemIds($filters['problemFilters']);
}
$problem_ids = [];
Expand All @@ -232,7 +208,7 @@ public function getFilteredSolutionsForManagement($filters): Collection {
public function getSolutions(mixed $problem_id): Collection {
$current_language_code = app()->getLocale();
$current_language = $this->languageRepository->getLanguageByCode($current_language_code);
$current_language_id = ($current_language && $current_language->id) ? $current_language->id : $this->languageRepository->getDefaultLanguage()->id;
$current_language_id = ($current_language->id) ? $current_language->id : $this->languageRepository->getDefaultLanguage()->id;

return $this->solutionRepository->getSolutions($problem_id, $current_language_id, Auth::id());
}
Expand Down Expand Up @@ -388,4 +364,8 @@ public function getSolutionsProposedByUser(User $user) {
'problem', 'problem.defaultTranslation', 'upvotes', 'problem.project',
]);
}

public function getSolutionsByProjectId(int $project_id): Collection {
return $this->solutionRepository->getSolutionsByProjectId($project_id);
}
}
39 changes: 39 additions & 0 deletions app/Http/Controllers/Solution/SolutionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,43 @@ public function handleShareSolution(Request $request): JsonResponse {

return response()->json($this->solutionManager->handleShareSolution($request->solution_id));
}

public function exportSolutions(int $project_id) {
$validator = Validator::make([
'project_id' => $project_id,
], [
'project_id' => 'required|exists:crowd_sourcing_projects,id',
]);

if ($validator->fails()) {
abort(ResponseAlias::HTTP_BAD_REQUEST);
}

$solutions = $this->solutionManager->getSolutionsByProjectId($project_id);

$callback = function () use ($solutions) {
$columns = ['Solution ID', 'Title', 'Description', 'Status', 'Created At'];
$file = fopen('php://output', 'w');
fputcsv($file, $columns);

foreach ($solutions as $solution) {
fputcsv($file, [
$solution->id,
$solution->defaultTranslation?->title,
$solution->defaultTranslation?->description,
$solution->status->title,
$solution->created_at,
]);
}

fclose($file);
};

$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="solutions.csv"',
];

return response()->stream($callback, 200, $headers);
}
}
10 changes: 10 additions & 0 deletions app/Models/Solution/Solution.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Models\Solution;

use App\Models\CrowdSourcingProject\CrowdSourcingProject;
use App\Models\Problem\Problem;
use App\Models\User\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -10,8 +11,10 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Znck\Eloquent\Traits\BelongsToThrough;

class Solution extends Model {
use BelongsToThrough;
use HasFactory, SoftDeletes;

protected $table = 'solutions';
Expand All @@ -23,6 +26,13 @@ public function problem(): BelongsTo {
return $this->belongsTo(Problem::class, 'problem_id');
}

public function project(): \Znck\Eloquent\Relations\BelongsToThrough {
return $this->belongsToThrough(CrowdSourcingProject::class,
Problem::class,
foreignKeyLookup: [CrowdSourcingProject::class => 'project_id'],
localKeyLookup: [CrowdSourcingProject::class => 'id']);
}

// default translation relationship
// the solution has the same default translation as the problem it belongs to.
// but the records are stored in different tables (solution_translations and problem_translations)
Expand Down
10 changes: 8 additions & 2 deletions app/Repository/Solution/SolutionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ public function getSolutions(int $problem_id, int $lang_id, ?int $current_user_i
});
}

public function getSolutionsForProblems($problem_ids) {
public function getSolutionsForProblems(array $problem_ids): Collection {
return Solution::whereIn('problem_id', $problem_ids)->get();
}

public function getPublishedSolutionsProposedByUser(int $userId) {
public function getPublishedSolutionsProposedByUser(int $userId): Collection {
return Solution::where('user_creator_id', $userId)->where('status_id', SolutionStatusLkp::PUBLISHED)->get();
}

public function getSolutionsByProjectId(int $project_id): Collection {
return Solution::whereHas('project', function ($query) use ($project_id) {
$query->where('project_id', $project_id);
})->get();
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"socialiteproviders/twitter": "^4.1",
"spatie/laravel-newsletter": "^5.2.0",
"spatie/laravel-sitemap": "^7.2.1",
"staudenmeir/belongs-to-through": "^2.5",
"symfony/http-client": "^7.1",
"symfony/mailgun-mailer": "^7.1"
},
Expand Down
67 changes: 66 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1652d29

Please sign in to comment.