Skip to content

Commit

Permalink
Merge pull request #207 from scify/master
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
PavlosIsaris authored Dec 12, 2024
2 parents eef6ab7 + e64edf6 commit 2bd3ec6
Show file tree
Hide file tree
Showing 40 changed files with 765 additions and 63 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,28 @@ And finally, run the tests:
php artisan test --env=testing --filter {METHOD OR CLASS NAME} --coverage
```

### Run the tests with the `run-tests.sh` script

You can also run the tests using the `run-tests.sh` script, which is a wrapper around the PHPUnit command.

```bash
chmod +x run-tests.sh

./run-tests.sh
```

This can also take any arguments (like the `--filter` or `--coverage` flag) that you would pass to the PHPUnit command.

```bash
./run-tests.sh --filter {METHOD OR CLASS NAME}
```

or

```bash
./run-tests.sh --coverage
```

## How to debug

By using Docker Compose, you can debug the application by following these steps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
class CrowdSourcingProjectManager {
const DEFAULT_IMAGE_PATH = '/images/image_temp.png';
const DEFAULT_IMAGE_PATH_QUESTIONNAIRE_BG = '/images/questionnaire_bg_default.webp';
const DEFAULT_MAX_VOTES_PER_USER_FOR_SOLUTIONS = 10;
const DEFAULT_LP_PRIMARY_COLOR = '#F5BA16';
const DEFAULT_LP_BTN_TEXT_COLOR_THEME = 'dark';

protected CrowdSourcingProjectRepository $crowdSourcingProjectRepository;
protected QuestionnaireRepository $questionnaireRepository;
Expand Down Expand Up @@ -283,18 +286,20 @@ protected function setDefaultValuesForSocialMediaFields(array $attributes): arra
}

public function populateInitialValuesForProjectIfNotSet(CrowdSourcingProject $project): CrowdSourcingProject {
$project->lp_show_speak_up_btn = true;
$project->lp_show_speak_up_btn = $project->lp_show_speak_up_btn ?? true;
$project->max_votes_per_user_for_solutions = $project->max_votes_per_user_for_solutions
?? self::DEFAULT_MAX_VOTES_PER_USER_FOR_SOLUTIONS;
$project = $this->populateInitialFileValuesForProjectIfNotSet($project);

return $this->populateInitialColorValuesForProjectIfNotSet($project);
}

public function populateInitialColorValuesForProjectIfNotSet(CrowdSourcingProject $project): CrowdSourcingProject {
if (!$project->lp_primary_color) {
$project->lp_primary_color = '#F5BA16';
$project->lp_primary_color = self::DEFAULT_LP_PRIMARY_COLOR;
}
if (!$project->lp_btn_text_color_theme) {
$project->lp_btn_text_color_theme = 'dark';
$project->lp_btn_text_color_theme = self::DEFAULT_LP_BTN_TEXT_COLOR_THEME;
}

return $project;
Expand All @@ -311,7 +316,7 @@ public function populateInitialFileValuesForProjectIfNotSet(CrowdSourcingProject
$project->sm_featured_img_path = self::DEFAULT_IMAGE_PATH;
}
if (!$project->lp_questionnaire_img_path) {
$project->lp_questionnaire_img_path = '/images/bgsectionnaire.webp';
$project->lp_questionnaire_img_path = self::DEFAULT_IMAGE_PATH_QUESTIONNAIRE_BG;
}

return $project;
Expand All @@ -336,7 +341,7 @@ protected function storeProjectRelatedFiles(array $attributes): array {
return $attributes;
}

protected function createProjectStatusHistoryRecord($projectId, $statusId) {
protected function createProjectStatusHistoryRecord($projectId, $statusId): void {
$this->crowdSourcingProjectStatusHistoryRepository->create([
'project_id' => $projectId,
'status_id' => $statusId,
Expand Down Expand Up @@ -386,12 +391,11 @@ public function getCrowdSourcingProjectsListPageViewModel(): AllCrowdSourcingPro
public function getUnavailableCrowdSourcingProjectViewModelForLandingPage($project_slug): CrowdSourcingProjectUnavailable {
$project = $this->getCrowdSourcingProjectBySlug($project_slug);
$projects = $this->getCrowdSourcingProjectsForHomePage();
// TODO translate the messages below
$message = match ($project->status_id) {
CrowdSourcingProjectStatusLkp::FINALIZED => 'This project is finalized.<br>Thank you for your contribution!',
CrowdSourcingProjectStatusLkp::UNPUBLISHED => 'This project is unpublished.',
CrowdSourcingProjectStatusLkp::DELETED => 'This project has been archived.',
default => 'This project is not currently available',
CrowdSourcingProjectStatusLkp::FINALIZED => __('project.project_finalized_message'),
CrowdSourcingProjectStatusLkp::UNPUBLISHED => __('project.project_unpublished_message'),
CrowdSourcingProjectStatusLkp::DELETED => __('project.project_archived_message'),
default => __('project.project_unavailable_message'),
};

return new CrowdSourcingProjectUnavailable($project, $projects, $message);
Expand Down
76 changes: 76 additions & 0 deletions app/BusinessLogicLayer/Solution/SolutionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use App\Repository\Problem\ProblemRepository;
use App\Repository\RepositoryException;
use App\Repository\Solution\SolutionRepository;
use App\Repository\Solution\SolutionUpvoteRepository;
use App\Utils\FileHandler;
use App\ViewModels\Solution\CreateEditSolution;
use App\ViewModels\Solution\ProposeSolutionPage;
Expand All @@ -25,6 +26,7 @@

class SolutionManager {
protected SolutionRepository $solutionRepository;
protected SolutionUpvoteRepository $solutionUpvoteRepository;
protected ProblemRepository $problemRepository;
protected CrowdSourcingProjectRepository $crowdSourcingProjectRepository;
protected SolutionTranslationManager $solutionTranslationManager;
Expand All @@ -35,6 +37,7 @@ class SolutionManager {

public function __construct(
SolutionRepository $solutionRepository,
SolutionUpvoteRepository $solutionUpvoteRepository,
ProblemRepository $problemRepository,
CrowdSourcingProjectRepository $crowdSourcingProjectRepository,
SolutionTranslationManager $solutionTranslationManager,
Expand All @@ -44,6 +47,7 @@ public function __construct(
ProblemTranslationManager $problemTranslationManager,
) {
$this->solutionRepository = $solutionRepository;
$this->solutionUpvoteRepository = $solutionUpvoteRepository;
$this->problemRepository = $problemRepository;
$this->crowdSourcingProjectRepository = $crowdSourcingProjectRepository;
$this->solutionTranslationManager = $solutionTranslationManager;
Expand Down Expand Up @@ -251,4 +255,76 @@ public function getSolutionSubmittedViewModel(string $project_slug, string $prob

return new SolutionSubmitted($solution, $problem, $project);
}

/**
* Vote or downvote a solution
*
* Checks if the current user has already voted for this solution.
* if they have, we need to remove their vote
* if they haven't, we need to add their vote
* in the end, we return:
* - the current number of votes for the solution
* - the current user's vote status (voted or not)
* - the current user's remaining votes for this problem
*
* @param int $solution_id the id of the solution
* @return array described above
*/
public function voteOrDownVoteSolution(int $solution_id): array {
$upvote = false;
$user_id = Auth::id();
// also get how many votes the user has left for this problem
$problem = $this->solutionRepository->find($solution_id)->problem;
$project = $problem->project;

$user_votes = $this->getUserVotesNum($problem->id);
$votes_left = $project->max_votes_per_user_for_solutions - $user_votes;

$solution_upvote = $this->solutionUpvoteRepository->where([
'solution_id' => $solution_id,
'user_voter_id' => $user_id,
]);


if ($solution_upvote) {
$solution_upvote->delete();
} else {
// if the user does not have any votes left, we return an error
if ($votes_left <= 0) {
return [
'error' => 'You have no votes left for this problem',
];
}
$this->solutionUpvoteRepository->create([
'solution_id' => $solution_id,
'user_voter_id' => $user_id,
]);
$upvote = true;
}

$solution_votes = $this->solutionUpvoteRepository->allWhere(['solution_id' => $solution_id])->count();

return [
'solution_votes' => $solution_votes,
'upvote' => $upvote,
'user_votes_left' => $upvote ? $votes_left - 1 : $votes_left,
];
}

/**
* Gets the number of votes the current user has for this problem
* @param int $problem_id the id of the problem
* @return int the number of votes the current user has for this problem
*/
public function getUserVotesNum(int $problem_id): int {
$user_id = Auth::id();
if (!$user_id) {
return 0;
}
$project = $this->problemRepository->find($problem_id)->project;
$problem_ids = $project->problems->pluck('id')->toArray();
$solution_ids = $this->solutionRepository->getSolutionsForProblems($problem_ids)->pluck('id')->toArray();

return $this->solutionUpvoteRepository->getNumberOfVotesForUser($user_id, $solution_ids);
}
}
13 changes: 12 additions & 1 deletion app/Http/Controllers/Solution/SolutionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ public function getSolutions(Request $request): JsonResponse {
'problem_id' => 'required|exists:problems,id',
]);

return response()->json($this->solutionManager->getSolutions($request->problem_id));
return response()->json([
'user_votes' => $this->solutionManager->getUserVotesNum($request->problem_id),
'solutions' => $this->solutionManager->getSolutions($request->problem_id),
]);
}

public function userProposalCreate(string $locale, string $project_slug, string $problem_slug): View|RedirectResponse {
Expand Down Expand Up @@ -221,4 +224,12 @@ public function userProposalSubmitted(string $locale, string $project_slug, stri

return view('solution.submitted', ['viewModel' => $viewModel]);
}

public function voteOrDownVoteSolution(Request $request): JsonResponse {
$this->validate($request, [
'solution_id' => 'required|exists:solutions,id',
]);

return response()->json($this->solutionManager->voteOrDownVoteSolution($request->solution_id));
}
}
4 changes: 3 additions & 1 deletion app/Models/CrowdSourcingProject/CrowdSourcingProject.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @property string $logo_path
* @property int $user_creator_id
* @property int $language_id
* @property int $max_votes_per_user_for_solutions
* @property int $status_id
* @property string $sm_featured_img_path
* @property string $lp_questionnaire_img_path
Expand Down Expand Up @@ -56,7 +57,8 @@ class CrowdSourcingProject extends Model {
*/
protected $fillable = [
'slug', 'external_url', 'img_path',
'logo_path', 'user_creator_id', 'language_id', 'status_id',
'logo_path', 'user_creator_id', 'language_id',
'max_votes_per_user_for_solutions', 'status_id',
'sm_featured_img_path', 'lp_questionnaire_img_path',
'lp_show_speak_up_btn', 'lp_primary_color', 'lp_btn_text_color_theme',
'should_send_email_after_questionnaire_response',
Expand Down
4 changes: 4 additions & 0 deletions app/Repository/Solution/SolutionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public function getSolutions(int $problem_id, int $lang_id, ?int $current_user_i
}
});
}

public function getSolutionsForProblems($problem_ids) {
return Solution::whereIn('problem_id', $problem_ids)->get();
}
}
7 changes: 7 additions & 0 deletions app/Repository/Solution/SolutionUpvoteRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ class SolutionUpvoteRepository extends Repository {
public function getModelClassName() {
return SolutionUpvote::class;
}

public function getNumberOfVotesForUser(int $user_id, array $solution_ids): int {
return SolutionUpvote::whereIn('solution_id', $solution_ids)
->where('user_id', $user_id)
->get()
->count();
}
}
23 changes: 23 additions & 0 deletions database/factories/Solution/SolutionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Database\Factories\Solution;

use App\BusinessLogicLayer\lkp\SolutionStatusLkp;
use App\Models\Solution\Solution;
use Illuminate\Database\Eloquent\Factories\Factory;

class SolutionFactory extends Factory {
protected $model = Solution::class;

public function definition() {
return [
'problem_id' => 1,
'user_creator_id' => 1,
'slug' => $this->faker->slug,
'status_id' => SolutionStatusLkp::PUBLISHED,
'img_url' => $this->faker->imageUrl(),
'created_at' => now(),
'updated_at' => now(),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void {
Schema::table('crowd_sourcing_projects', function (Blueprint $table) {
$table->integer('max_votes_per_user_for_solutions')->after('language_id')->default(5);
});
}

/**
* Reverse the migrations.
*/
public function down(): void {
Schema::table('crowd_sourcing_projects', function (Blueprint $table) {
//
});
}
};
Loading

0 comments on commit 2bd3ec6

Please sign in to comment.