diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..5a67aeda --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: Fix Code Style + +on: [push] + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.4] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, dom, curl, libxml, mbstring + coverage: none + + - name: Install Pint + run: composer global require laravel/pint + + - name: Run Pint + run: pint + + - name: Commit linted files + uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/README.md b/README.md index 28813f0a..82afbcce 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,189 @@ -
- +# ComputerScienceResources.com -## About Laravel +[](https://github.com/AllanKoder/ComputerScienceResources.com/actions/workflows/feature-tests.yml) +[](https://github.com/AllanKoder/ComputerScienceResources.com/actions/workflows/lint.yml) -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +Welcome to the codebase for [ComputerScienceResources.com](https://computerscienceresources.com) — a curated platform for discovering, reviewing, and sharing the best resources in computer science and software engineering. -https://webdevetc.com/blog/laravel-naming-conventions/ + +This is our mascot, look how studious this little guy is!
-- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +This website helps developers and learners find high-quality, structured, and community-reviewed resources across all areas of computer science. Our mission is to make learning easier by organizing and highlighting the best content, and to empower the community to contribute, review, and improve resource listings. -Laravel is accessible, powerful, and provides tools required for large, robust applications. -## Learning Laravel +## App Features + +Here's what you can do on ComputerScienceResources.com — all designed to make your learning journey easier, more fun, and community-driven: + +- **Add New Resources:** Share your favorite computer science and software engineering resources with the world. +- **Upvote & Downvote:** Show your support (or not!) for resources, reviews, and comments. Change your mind? You can always update or remove your vote. +- **Write Reviews:** Leave thoughtful reviews for resources you’ve tried. Each user can post one review per resource, and reviews can be upvoted, commented on, and edited. +- **Comment Anywhere:** Start conversations on resources, reviews, or even other comments. Comments are nested, paginated, and easy to follow — just like your favorite forums. +- **Suggest Edits:** See something that could be improved? Propose edits to any resource. The community can discuss, vote, and help merge the best changes. +- **Resource Filtering:** Filter resources by name, description, platform, difficulty, pricing, tags, upvotes, review scores, and more — so you always find what you need. +- **Community-Driven:** Everything is built to encourage helpfulness, kindness, and collaboration. Your feedback, reviews, and suggestions shape the site! -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. +We’re always improving and adding new features. If you have ideas or want to help, check out the Contributing section below! -You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. +## Getting Started -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. +This project uses [Laravel 11](https://laravel.com/) (PHP 8.2+) as the backend framework, with [Inertia.js](https://inertiajs.com/) and [Vue 3](https://vuejs.org/) for the frontend. -## Laravel Sponsors +### Laravel Sail -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). +We use [Laravel Sail](https://laravel.com/docs/11.x/sail) for easy setup and configuration. You will need to install [Docker Desktop](https://www.docker.com/products/docker-desktop/) to use Sail. +For detailed instructions, see the [Laravel Sail documentation](https://laravel.com/docs/11.x/installation#docker-installation-using-sail). -### Premium Partners -- **[Vehikl](https://vehikl.com/)** -- **[Tighten Co.](https://tighten.co)** -- **[WebReinvent](https://webreinvent.com/)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** -- **[Cyber-Duck](https://cyber-duck.co.uk)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Jump24](https://jump24.co.uk)** -- **[Redberry](https://redberry.international/laravel/)** -- **[Active Logic](https://activelogic.com)** -- **[byte5](https://byte5.de)** -- **[OP.GG](https://op.gg)** +### Installation -## Contributing +1. **Clone the repository:** + ```bash + git clone https://github.com/AllanKoder/ComputerScienceResources.com.git + cd ComputerScienceResources.com + ``` +2. **Install PHP dependencies:** + ```bash + composer install + ``` +3. **Install JS dependencies (via Sail):** + ```bash + ./sail npm install + ``` +4. **Copy and configure your environment:** + ```bash + cp .env.example .env + # Edit .env to match your database and mail settings + ``` +5. **Generate app key:** + ```bash + ./sail artisan key:generate + ``` +6. **Run migrations:** + ```bash + ./sail artisan migrate + ``` +7. **Start the dev server:** + ```bash + ./sail npm run dev + # In another terminal: + ./sail up + ``` + +Great, you should now see the webapp located in `localhost` + +## Style Guide + +We follow the [Laravel naming conventions](https://webdevetc.com/blog/laravel-naming-conventions/) for controllers, models, migrations, and more. Please: + +### Casing + +- Use `PascalCase` for class names (e.g., `ResourceReviewProcessed`). +- Use `snake_case` for database columns, migration files, and fields in request bodies. +- Use `camelCase` for variable and method names. +- Use `kebab-case` for vue prop inputs to components. + +### Conventions +- Place business logic in Service classes or Actions, not controllers. +- Keep controllers thin and focused on HTTP concerns. +- Use [PSR-12](https://www.php-fig.org/psr/psr-12/) code style (enforced by Pint). + +**Frontend:** +- Use [Vue 3](https://vuejs.org/) with [Inertia.js](https://inertiajs.com/) +- Use [Tailwind CSS](https://tailwindcss.com/) for styling +- Use Components from the `resources/js/Components` folder whenever possible. Such as `PrimaryButton.vue` and `SecondaryButton.vue`. +- Use `primary` and `secondary` colors for classes over raw hex values according to tailwind.config.js + - Example: class='bg-primary' -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). +## Packages Used & Why -## Code of Conduct +This project uses several Laravel and community packages to enhance functionality: -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). +- **cviebrock/eloquent-sluggable**: For generating SEO-friendly slugs for resources +- **inertiajs/inertia-laravel**: Enables server-driven SPA with Vue 3 +- **joelbutcher/socialstream**: Social authentication (OAuth, etc.) +- **laravel/jetstream**: Authentication scaffolding and team management +- **laravel/sanctum**: API token authentication +- **shiftonelabs/laravel-cascade-deletes**: Handles cascading deletes for related models +- **spatie/laravel-activitylog**: Logs user activity for auditing and transparency +- **spatie/laravel-tags**: Flexible tagging for resources +- **tightenco/ziggy**: Exposes Laravel routes to JavaScript + +**Dev Packages:** +- **barryvdh/laravel-debugbar**: Debugging and profiling +- **barryvdh/laravel-ide-helper**: IDE autocompletion for Laravel +- **beyondcode/laravel-query-detector**: Detects N+1 query issues +- **laravel/pint**: Automated code style fixing +- **laravel/telescope**: Debugging and monitoring (local/dev only) +- **worksome/request-factories**: Test request factories + + +See `composer.json` for the full list and version constraints. + +## Running Tests + +To run the test suite, use: + +```bash +./sail test +``` + +By default, all tests are run. To speed up testing, you can exclude the slowest group (marked with `@Group('slow')`): + +```bash +./sail test --exclude-group=slow +``` + +You can also run a specific test file or method: + +```bash +./sail test tests/Feature/ResourceReviewsTest.php +``` + +## Local Debugging with Xdebug & VS Code + +Xdebug is pre-configured in the Sail Docker environment for local debugging. + +1. **Ensure Xdebug is enabled:** + - By default, Xdebug is enabled in Sail via the `SAIL_XDEBUG_MODE` and `SAIL_XDEBUG_CONFIG` environment variables in your `.env` file. +2. **VS Code Setup:** + - Install the [PHP Debug extension](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug). + - Add a launch configuration to your `.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/var/www/html": "${workspaceFolder}" + } + } + ] +} +``` + +3. **Start debugging:** + - Set breakpoints in your PHP code. + - Start the "Listen for Xdebug" configuration in VS Code. + - Trigger a request (web, test, or CLI) and Xdebug will connect to VS Code. + + +## Documentation + +- [Project Roadmap](docs/ROADMAP.md): See the planned phases and milestones for the project. +- [Application Routes & UI Previews](docs/ROUTES.md): Browse all main routes and their associated UI images. + +## Contributing -## Security Vulnerabilities +We welcome contributions! Please open issues or pull requests. For suggestions of features, please use the Discussions tab. -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. ## License -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 5989cafb..d8391f2c 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -57,7 +57,7 @@ public function store(StoreCommentRequest $request) } catch (ValidationException $e) { DB::rollBack(); throw $e; // Let Laravel handle validation errors (422) - } catch(NotFoundHttpException $e) { + } catch (NotFoundHttpException $e) { DB::rollBack(); Log::warning('Comment target not found', [ 'error' => $e->getMessage(), @@ -73,6 +73,7 @@ public function store(StoreCommentRequest $request) 'validated_data' => $validatedData, 'user_id' => Auth::id(), ]); + return response()->json(['message' => 'Failed to save comment'], 500); } } diff --git a/app/Http/Controllers/ComputerScienceResourceController.php b/app/Http/Controllers/ComputerScienceResourceController.php index c804fc55..dfb882a4 100644 --- a/app/Http/Controllers/ComputerScienceResourceController.php +++ b/app/Http/Controllers/ComputerScienceResourceController.php @@ -131,6 +131,7 @@ public function store(StoreResourceRequest $request) 'user_id' => Auth::id(), 'data' => $validatedData, ]); + return back()->withErrors(['error' => 'Failed to create resource. Please try again.']); } } diff --git a/app/Http/Controllers/ResourceEditsController.php b/app/Http/Controllers/ResourceEditsController.php index 48269edd..43b70f18 100644 --- a/app/Http/Controllers/ResourceEditsController.php +++ b/app/Http/Controllers/ResourceEditsController.php @@ -9,10 +9,10 @@ use App\Services\DataNormalizationService; use App\Services\ResourceEditsService; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Storage; -use Inertia\Inertia; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; +use Inertia\Inertia; use Str; use Throwable; @@ -178,6 +178,7 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc 'trace' => $e->getTraceAsString(), 'resource_edit_id' => $resourceEdits->id, ]); + return redirect()->back()->withErrors(['error' => 'Failed to merge resource edits. Please try again.']); } } diff --git a/app/Http/Controllers/UpvoteController.php b/app/Http/Controllers/UpvoteController.php index 38df506b..18b4edef 100644 --- a/app/Http/Controllers/UpvoteController.php +++ b/app/Http/Controllers/UpvoteController.php @@ -4,9 +4,9 @@ use App\Services\ModelResolverService; use Illuminate\Support\Facades\Auth; -use Illuminate\Validation\Rule; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Rule; use Throwable; class UpvoteController extends Controller @@ -42,6 +42,7 @@ public function upvote($typeKey, $id) 'type_key' => $typeKey, 'id' => $id, ]); + return response()->json(['message' => 'Model not found'], 404); } @@ -70,6 +71,7 @@ public function upvote($typeKey, $id) 'id' => $id, 'user_id' => Auth::id(), ]); + return response()->json(['message' => 'Failed to upvote. Please try again.'], 500); } } @@ -98,6 +100,7 @@ public function downvote($typeKey, $id) 'type_key' => $typeKey, 'id' => $id, ]); + return response()->json(['message' => 'Model not found'], 404); } @@ -126,6 +129,7 @@ public function downvote($typeKey, $id) 'id' => $id, 'user_id' => Auth::id(), ]); + return response()->json(['message' => 'Failed to downvote. Please try again.'], 500); } } diff --git a/app/Models/Comment.php b/app/Models/Comment.php index 58eca410..195712ff 100644 --- a/app/Models/Comment.php +++ b/app/Models/Comment.php @@ -20,6 +20,7 @@ class Comment extends Model /** @use HasFactory<\Database\Factories\CommentFactory> */ use HasFactory; + use HasVotes; use LogsActivity; diff --git a/app/Models/ComputerScienceResource.php b/app/Models/ComputerScienceResource.php index 229523e2..dcfebe83 100644 --- a/app/Models/ComputerScienceResource.php +++ b/app/Models/ComputerScienceResource.php @@ -14,9 +14,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Facades\Storage; -use Spatie\Tags\HasTags; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Tags\HasTags; #[ObservedBy([ComputerScienceResourceObserver::class])] class ComputerScienceResource extends Model @@ -25,8 +25,9 @@ class ComputerScienceResource extends Model use HasFactory; use HasTags; use HasVotes; - use Sluggable; use LogsActivity; + use Sluggable; + protected $table = 'computer_science_resources'; protected $guarded = []; @@ -61,7 +62,7 @@ public function user(): BelongsTo protected function imageUrl(): Attribute { return Attribute::make( - get: fn() => $this->image_path ? Storage::url($this->image_path) : null, + get: fn () => $this->image_path ? Storage::url($this->image_path) : null, ); } @@ -92,8 +93,8 @@ public function edits(): HasMany protected function platforms(): Attribute { return Attribute::make( - get: fn($value) => explode(',', $value), - set: fn($value) => implode(',', $value) + get: fn ($value) => explode(',', $value), + set: fn ($value) => implode(',', $value) ); } @@ -103,8 +104,8 @@ protected function platforms(): Attribute protected function topicTags(): Attribute { return Attribute::make( - get: fn() => $this->tagsWithType('topics')->pluck('name')->toArray(), - set: fn(array $value) => $this->syncTagsWithType($value, 'topics') + get: fn () => $this->tagsWithType('topics')->pluck('name')->toArray(), + set: fn (array $value) => $this->syncTagsWithType($value, 'topics') ); } @@ -114,8 +115,8 @@ protected function topicTags(): Attribute protected function programmingLanguageTags(): Attribute { return Attribute::make( - get: fn() => $this->tagsWithType('programming_languages')->pluck('name')->toArray(), - set: fn(array $value) => $this->syncTagsWithType($value, 'programming_languages') + get: fn () => $this->tagsWithType('programming_languages')->pluck('name')->toArray(), + set: fn (array $value) => $this->syncTagsWithType($value, 'programming_languages') ); } @@ -125,8 +126,8 @@ protected function programmingLanguageTags(): Attribute protected function generalTags(): Attribute { return Attribute::make( - get: fn() => $this->tagsWithType('general_tags')->pluck('name')->toArray(), - set: fn(array $value) => $this->syncTagsWithType($value, 'general_tags') + get: fn () => $this->tagsWithType('general_tags')->pluck('name')->toArray(), + set: fn (array $value) => $this->syncTagsWithType($value, 'general_tags') ); } diff --git a/app/Models/ResourceEdits.php b/app/Models/ResourceEdits.php index 7a68b84c..a01f2054 100644 --- a/app/Models/ResourceEdits.php +++ b/app/Models/ResourceEdits.php @@ -24,13 +24,13 @@ class ResourceEdits extends Model { use CascadesDeletes; use HasComments; + /** @use HasFactory<\Database\Factories\ResourceEditsFactory> */ use HasFactory; + use HasVotes; use LogsActivity; - use Sluggable; - use SoftDeletes; protected $cascadeDeletes = ['votes', 'upvoteSummary', 'comments', 'commentsCountRelationship']; @@ -98,7 +98,7 @@ protected function proposedChanges(): Attribute protected function canMergeEdits(): Attribute { return Attribute::make( - get: fn() => app(ResourceEditsService::class)->canMergeEdits($this) && Auth::id() === $this->user_id, + get: fn () => app(ResourceEditsService::class)->canMergeEdits($this) && Auth::id() === $this->user_id, ); } } diff --git a/app/Models/ResourceReview.php b/app/Models/ResourceReview.php index 2b5b73a9..5b76d948 100644 --- a/app/Models/ResourceReview.php +++ b/app/Models/ResourceReview.php @@ -18,6 +18,7 @@ class ResourceReview extends Model /** @use HasFactory<\Database\Factories\ResourceReviewFactory> */ use HasFactory; + use HasVotes; protected $guarded = []; diff --git a/app/Observers/CommentObserver.php b/app/Observers/CommentObserver.php index ef784e8c..5464cb0a 100644 --- a/app/Observers/CommentObserver.php +++ b/app/Observers/CommentObserver.php @@ -4,7 +4,6 @@ use App\Models\Comment; use App\Models\CommentsCount; -use App\Models\UpvoteSummary; use Illuminate\Support\Facades\Log; class CommentObserver diff --git a/app/Observers/ComputerScienceResourceObserver.php b/app/Observers/ComputerScienceResourceObserver.php index d72939be..275a3382 100644 --- a/app/Observers/ComputerScienceResourceObserver.php +++ b/app/Observers/ComputerScienceResourceObserver.php @@ -4,7 +4,6 @@ use App\Events\TagFrequencyChanged; use App\Models\ComputerScienceResource; -use App\Models\UpvoteSummary; class ComputerScienceResourceObserver { diff --git a/app/Observers/ResourceEditsObserver.php b/app/Observers/ResourceEditsObserver.php index 0359b018..0e0c0a8d 100644 --- a/app/Observers/ResourceEditsObserver.php +++ b/app/Observers/ResourceEditsObserver.php @@ -9,10 +9,7 @@ class ResourceEditsObserver /** * Handle the ResourceEdits "created" event. */ - public function created(ResourceEdits $resourceEdits): void - { - - } + public function created(ResourceEdits $resourceEdits): void {} /** * Handle the ResourceEdits "updated" event. diff --git a/app/Observers/ResourceReviewObserver.php b/app/Observers/ResourceReviewObserver.php index ad5083e4..c4f56438 100644 --- a/app/Observers/ResourceReviewObserver.php +++ b/app/Observers/ResourceReviewObserver.php @@ -4,7 +4,6 @@ use App\Events\ResourceReviewProcessed; use App\Models\ResourceReview; -use App\Models\UpvoteSummary; class ResourceReviewObserver { diff --git a/app/Services/CommentService.php b/app/Services/CommentService.php index 32f1f6dd..016489c0 100644 --- a/app/Services/CommentService.php +++ b/app/Services/CommentService.php @@ -159,8 +159,6 @@ public function getPaginatedComments(string $commentableKey, int $commentableId, /** * Create and save a comment * - * @param array $validatedData - * @return Comment * @throws Exception */ public function createComment(array $validatedData): Comment @@ -175,7 +173,7 @@ public function createComment(array $validatedData): Comment // Ensure that the model exists $model = $this->modelResolver->resolve($validatedData['commentable_key'], $commentableId); if (! $model) { - throw new NotFoundHttpException(); + throw new NotFoundHttpException; } // Set the commentable type diff --git a/app/Services/ModelResolverService.php b/app/Services/ModelResolverService.php index fa507464..fd1d966b 100644 --- a/app/Services/ModelResolverService.php +++ b/app/Services/ModelResolverService.php @@ -15,8 +15,8 @@ class ModelResolverService /** * Finds the model that exists for the given key and id * - * @param $key, the colloquial name for the key - * @param $id, the id for the key + * @param $key, the colloquial name for the key + * @param $id, the id for the key * * returns null if no model exists, otherwise, it will return the model */ diff --git a/database/migrations/2025_07_21_224710_create_activity_log_table.php b/database/migrations/2025_07_21_224710_create_activity_log_table.php index 7c05bc89..b788f65e 100644 --- a/database/migrations/2025_07_21_224710_create_activity_log_table.php +++ b/database/migrations/2025_07_21_224710_create_activity_log_table.php @@ -1,8 +1,8 @@ To add or update screenshots, place image files in `docs/routes-images/` and update the image links above as needed. diff --git a/tests/Feature/CommentsTest.php b/tests/Feature/CommentsTest.php index c88aa72a..58ef0e7f 100644 --- a/tests/Feature/CommentsTest.php +++ b/tests/Feature/CommentsTest.php @@ -12,7 +12,6 @@ use Tests\RequestFactories\Comment\StoreCommentRequestFactory; use Tests\TestCase; - class CommentsTest extends TestCase { use RefreshDatabase;