diff --git a/app/Http/Controllers/ComputerScienceResourceController.php b/app/Http/Controllers/ComputerScienceResourceController.php index 5fb8dd3a..f68085d2 100644 --- a/app/Http/Controllers/ComputerScienceResourceController.php +++ b/app/Http/Controllers/ComputerScienceResourceController.php @@ -19,21 +19,22 @@ class ComputerScienceResourceController extends Controller { public function __construct( protected ComputerScienceResourceService $resourceService, + protected ComputerScienceResourceFilter $filterService ) {} /** * Display a listing of the resource. */ - public function index(Request $request, ComputerScienceResourceFilter $filterService) + public function index(Request $request) { $query = ComputerScienceResource::query(); // Apply all filters and sorting $filters = $request->query(); - $query = $filterService->applyFilters($query, $filters); + $query = $this->filterService->applyFilters($query, $filters); // Paginate with appended query params - $resources = $query->paginate(10)->appends($request->query()); + $resources = $query->paginate(20)->appends($request->query()); $news = NewsPost::limit(10)->get(); diff --git a/app/Http/Controllers/ResourceEditsController.php b/app/Http/Controllers/ResourceEditsController.php index 2b6ff2bf..ba54741f 100644 --- a/app/Http/Controllers/ResourceEditsController.php +++ b/app/Http/Controllers/ResourceEditsController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\Http\Requests\StoreResourceEdit; +use App\Http\Requests\StoreResourceEditRequest; use App\Models\ComputerScienceResource; use App\Models\ResourceEdits; use App\Services\ResourceEditsService; @@ -36,7 +36,7 @@ public function create(string $slug) /** * Store the edits request. */ - public function store(ComputerScienceResource $computerScienceResource, StoreResourceEdit $request) + public function store(ComputerScienceResource $computerScienceResource, StoreResourceEditRequest $request) { $validatedData = $request->validated(); $proposedChanges = $validatedData['proposed_changes'] ?? []; @@ -95,7 +95,7 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc // Go through each property in proposed_changes, and if it exists. then set the value $changes = $resourceEdits->proposed_changes; - $proposedFields = ['name', 'description', 'page_url', 'platforms', 'difficulty', 'pricing']; + $proposedFields = ['name', 'description', 'page_url', 'platforms', 'difficulties', 'pricing']; foreach ($proposedFields as $field) { if (array_key_exists($field, $changes)) { $resource->$field = $changes[$field]; diff --git a/app/Http/Controllers/ResourceReviewController.php b/app/Http/Controllers/ResourceReviewController.php index bdb62b22..e19b459e 100644 --- a/app/Http/Controllers/ResourceReviewController.php +++ b/app/Http/Controllers/ResourceReviewController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\Http\Requests\StoreResourceReview; +use App\Http\Requests\StoreResourceReviewRequest; use App\Models\ComputerScienceResource; use App\Models\ResourceReview; use Illuminate\Support\Facades\Auth; @@ -11,7 +11,7 @@ class ResourceReviewController extends Controller { // Store the review on the resource - public function store(StoreResourceReview $request, ComputerScienceResource $computerScienceResource) + public function store(StoreResourceReviewRequest $request, ComputerScienceResource $computerScienceResource) { // Validate the request data $validatedData = $request->validated(); @@ -61,7 +61,7 @@ public function store(StoreResourceReview $request, ComputerScienceResource $com return response()->json($review); } - public function update(StoreResourceReview $request, ComputerScienceResource $computerScienceResource) + public function update(StoreResourceReviewRequest $request, ComputerScienceResource $computerScienceResource) { // Validate the request data $validatedData = $request->validated(); diff --git a/app/Http/Requests/StoreResourceEdit.php b/app/Http/Requests/StoreResourceEditRequest.php similarity index 88% rename from app/Http/Requests/StoreResourceEdit.php rename to app/Http/Requests/StoreResourceEditRequest.php index fb9a189a..807eba71 100644 --- a/app/Http/Requests/StoreResourceEdit.php +++ b/app/Http/Requests/StoreResourceEditRequest.php @@ -6,7 +6,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Validation\Rule; -class StoreResourceEdit extends FormRequest +class StoreResourceEditRequest extends FormRequest { /** * Determine if the user is authorized to make this request. @@ -30,14 +30,16 @@ public function rules(): array 'proposed_changes.name' => ['nullable', 'string', 'max:100'], 'proposed_changes.description' => ['nullable', 'string', 'max:10000'], - 'proposed_changes.platforms' => ['nullable', 'array'], + 'proposed_changes.platforms' => ['nullable', 'array', 'min:1'], 'proposed_changes.platforms.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.platforms'))], 'proposed_changes.page_url' => ['nullable', 'string', 'url:http,https', 'max:255'], - 'proposed_changes.difficulty' => ['nullable', 'string', Rule::in(config('computerScienceResource.difficulties'))], + 'proposed_changes.difficulties' => ['nullable', 'array', 'min:1'], + 'proposed_changes.difficulties.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.difficulties'))], 'proposed_changes.pricing' => ['nullable', 'string', Rule::in(config('computerScienceResource.pricings'))], - 'proposed_changes.image_file' => ['nullable', 'image', 'max:500'], // 500 kilobytes 'proposed_changes.topic_tags' => ['nullable', 'array', 'min:2'], 'proposed_changes.topic_tags.*' => ['required', 'distinct', 'string', 'max:50', 'regex:'.config('computerScienceResource.tags_regex')], + + 'proposed_changes.image_file' => ['nullable', 'image', 'max:500'], // 500 kilobytes 'proposed_changes.general_tags' => ['nullable', 'array'], 'proposed_changes.general_tags.*' => ['required', 'distinct', 'string', 'max:50', 'regex:'.config('computerScienceResource.tags_regex')], 'proposed_changes.programming_language_tags' => ['nullable', 'array'], diff --git a/app/Http/Requests/StoreResourceRequest.php b/app/Http/Requests/StoreResourceRequest.php index 0c2d35c5..e29c7a3b 100644 --- a/app/Http/Requests/StoreResourceRequest.php +++ b/app/Http/Requests/StoreResourceRequest.php @@ -29,7 +29,8 @@ public function rules(): array 'platforms' => ['required', 'array', 'min:1'], 'platforms.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.platforms'))], 'page_url' => ['required', 'string', 'url:http,https', 'max:255'], - 'difficulty' => ['required', 'string', Rule::in(config('computerScienceResource.difficulties'))], + 'difficulties' => ['required', 'array', 'min:1'], + 'difficulties.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.difficulties'))], 'pricing' => ['required', 'string', Rule::in(config('computerScienceResource.pricings'))], 'topic_tags' => ['required', 'array', 'min:2'], 'topic_tags.*' => ['required', 'distinct', 'string', 'max:50', 'regex:'.config('computerScienceResource.tags_regex')], diff --git a/app/Http/Requests/StoreResourceReview.php b/app/Http/Requests/StoreResourceReviewRequest.php similarity index 96% rename from app/Http/Requests/StoreResourceReview.php rename to app/Http/Requests/StoreResourceReviewRequest.php index 6966a860..36e04fb7 100644 --- a/app/Http/Requests/StoreResourceReview.php +++ b/app/Http/Requests/StoreResourceReviewRequest.php @@ -5,7 +5,7 @@ use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; -class StoreResourceReview extends FormRequest +class StoreResourceReviewRequest extends FormRequest { /** * Determine if the user is authorized to make this request. diff --git a/app/Http/Resources/ComputerScienceResourceResource.php b/app/Http/Resources/ComputerScienceResourceResource.php index 871cf5bd..415f7e26 100644 --- a/app/Http/Resources/ComputerScienceResourceResource.php +++ b/app/Http/Resources/ComputerScienceResourceResource.php @@ -20,7 +20,7 @@ public function toArray(Request $request): array 'page_url' => $this->page_url, 'image_path' => $this->image_path, 'platforms' => $this->platforms, - 'difficulty' => $this->difficulty, + 'difficulties' => $this->difficulties, 'pricing' => $this->pricing, 'topic_tags' => $this->topic_tags, 'programming_language_tags' => $this->programming_language_tags, diff --git a/app/Models/ComputerScienceResource.php b/app/Models/ComputerScienceResource.php index 5433f2c1..adb2fb68 100644 --- a/app/Models/ComputerScienceResource.php +++ b/app/Models/ComputerScienceResource.php @@ -129,6 +129,14 @@ protected function platforms(): Attribute ); } + protected function difficulties(): Attribute + { + return Attribute::make( + get: fn ($value) => explode(',', $value), + set: fn ($value) => implode(',', $value) + ); + } + /** * Accessor to get topic tags. */ diff --git a/app/Services/ComputerScienceResourceFilter.php b/app/Services/ComputerScienceResourceFilter.php index f1bcb3d5..9ebc93cf 100644 --- a/app/Services/ComputerScienceResourceFilter.php +++ b/app/Services/ComputerScienceResourceFilter.php @@ -25,8 +25,8 @@ public function validate(array $request) 'description' => ['nullable', 'string', 'max:1000'], 'platforms' => ['nullable', 'array'], 'platforms.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.platforms'))], - 'difficulty' => ['nullable', 'array'], - 'difficulty.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.difficulties'))], + 'difficulties' => ['nullable', 'array'], + 'difficulties.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.difficulties'))], 'pricing' => ['nullable', 'array'], 'pricing.*' => ['required', 'distinct', 'string', Rule::in(config('computerScienceResource.pricings'))], 'topics_tags' => ['nullable', 'array'], @@ -84,9 +84,14 @@ public function applyFilters($query, array $filters) }); } - // Filter by difficulty - if (! empty($filters['difficulty'])) { - $query->whereIn('difficulty', (array) $filters['difficulty']); + // Filter by difficulties + if (! empty($filters['difficulties'])) { + $query->where(function ($q) use ($filters) { + foreach ($filters['difficulties'] as $difficulty) { + $q->orWhereRaw('FIND_IN_SET(?, difficulties)', [$difficulty]); + + } + }); } // Filter by pricing diff --git a/app/Services/ComputerScienceResourceService.php b/app/Services/ComputerScienceResourceService.php index af3b2250..3b62b7cf 100644 --- a/app/Services/ComputerScienceResourceService.php +++ b/app/Services/ComputerScienceResourceService.php @@ -53,7 +53,7 @@ public function createResource(array $validatedData): ComputerScienceResource 'description' => $validatedData['description'], 'page_url' => $validatedData['page_url'], 'platforms' => $validatedData['platforms'], - 'difficulty' => $validatedData['difficulty'], + 'difficulties' => $validatedData['difficulties'], 'pricing' => $validatedData['pricing'], ]); diff --git a/database/factories/ComputerScienceResourceFactory.php b/database/factories/ComputerScienceResourceFactory.php index c4f18bae..72e79201 100644 --- a/database/factories/ComputerScienceResourceFactory.php +++ b/database/factories/ComputerScienceResourceFactory.php @@ -36,7 +36,7 @@ public function definition(): array 'image_path' => $imagePath, 'page_url' => fake()->url(), 'platforms' => fake()->randomElements($platforms, rand(1, 3)), - 'difficulty' => fake()->randomElement($difficulties), + 'difficulties' => fake()->randomElements($difficulties, rand(1, 3)), 'pricing' => fake()->randomElement($pricings), ]; } diff --git a/database/factories/ResourceEditsFactory.php b/database/factories/ResourceEditsFactory.php index 2687d964..b34dde71 100644 --- a/database/factories/ResourceEditsFactory.php +++ b/database/factories/ResourceEditsFactory.php @@ -30,7 +30,7 @@ public function definition(): array 'image_path' => $imagePath, 'page_url' => $this->faker->url(), 'platforms' => $this->faker->randomElements($platforms, rand(1, 3)), - 'difficulty' => $this->faker->randomElement($difficulties), + 'difficulties' => $this->faker->randomElements($difficulties, rand(1, 3)), 'pricing' => $this->faker->randomElement($pricings), 'topic_tags' => ['data-structures', 'algorithms'], 'programming_language_tags' => ['python'], diff --git a/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php new file mode 100644 index 00000000..4092301a --- /dev/null +++ b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php @@ -0,0 +1,37 @@ + { description.value = urlParams.get("description") || ""; selectedPlatforms.value = extractIndexedArray(urlParams, "platforms"); - selectedDifficulty.value = extractIndexedArray(urlParams, "difficulty"); - selectedPricing.value = extractIndexedArray(urlParams, "pricing"); + selectedDifficulties.value = extractIndexedArray(urlParams, "difficulties"); + selectedPricings.value = extractIndexedArray(urlParams, "pricing"); selectedTopics.value = extractIndexedArray(urlParams, "topics_tags"); selectedProgrammingLanguages.value = extractIndexedArray( urlParams, @@ -138,11 +138,11 @@ function search() { platforms: selectedPlatforms.value.length ? selectedPlatforms.value : undefined, - difficulty: selectedDifficulty.value.length - ? selectedDifficulty.value + difficulties: selectedDifficulties.value.length + ? selectedDifficulties.value : undefined, - pricing: selectedPricing.value.length - ? selectedPricing.value + pricing: selectedPricings.value.length + ? selectedPricings.value : undefined, topics_tags: selectedTopics.value.length ? selectedTopics.value @@ -182,8 +182,8 @@ function resetFilters() { // Arrays selectedPlatforms.value = []; - selectedDifficulty.value = []; - selectedPricing.value = []; + selectedDifficulties.value = []; + selectedPricings.value = []; selectedTopics.value = []; selectedProgrammingLanguages.value = []; selectedGeneralTags.value = []; @@ -271,7 +271,7 @@ function resetFilters() { Difficulty -import { defineProps } from 'vue'; -import { Icon } from '@iconify/vue'; -import StarRating from '@/Components/StarRating/StarRating.vue'; -import { platformIcons, pricingIcons, difficultyIcons } from '@/Helpers/icons'; -import { platformLabels, pricingLabels, difficultyLabels } from '@/Helpers/labels'; +import { defineProps, computed } from "vue"; +import { Icon } from "@iconify/vue"; +import StarRating from "@/Components/StarRating/StarRating.vue"; +import { platformIcons, pricingIcons, difficultyIcons } from "@/Helpers/icons"; +import { + platformLabels, + pricingLabels, + difficultyLabels, +} from "@/Helpers/labels"; const props = defineProps({ resource: { @@ -15,7 +19,9 @@ const props = defineProps({