Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions app/Http/Controllers/CommentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
use App\Http\Resources\CommentResource;
use App\Models\Comment;
use App\Services\ModelResolverService;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Collection;
use App\Http\Resources\UserResource;
use App\Models\CommentsCount;
use App\Services\CommentService;
use DB;
use Auth;
use Illuminate\Validation\Rule;
use Log;
Expand Down Expand Up @@ -42,10 +39,17 @@ public function store(StoreCommentRequest $request)
$comment->content = $validatedData['content'];
$comment->user_id = Auth::id();

// Set the commentable type
$commentableType = $this->modelResolver->getModelClass($validatedData['commentable_type']);
$commentableId = $validatedData['commentable_id'];

// Ensure that the model exists
$model = $this->modelResolver->resolve($validatedData['commentable_type'], $commentableId);
if (!$model) {
return response()->json(['message' => 'Model not found'], 404);
}
Log::debug("Resolved model class: " . $commentableType);

// Set the commentable type
$comment->commentable_type = $commentableType;
$comment->commentable_id = $commentableId;

Expand Down
6 changes: 3 additions & 3 deletions app/Http/Controllers/UpvoteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rule;

class UpvoteController extends Controller
{
protected $modelResolver;
protected $upvotableTypes = 'review,resource,comment,edit';

function __construct(ModelResolverService $modelResolver)
{
Expand All @@ -28,7 +28,7 @@ public function upvote($type, $id)
'type' => $type,
],
[
'type' => 'required|in:' . $this->upvotableTypes,
'type' => ['required', Rule::in(config('upvotes.upvotable_types'))]
]
)->validate();

Expand Down Expand Up @@ -57,7 +57,7 @@ public function downvote($type, $id)
'type' => $type,
],
[
'type' => 'required|in:' . $this->upvotableTypes,
'type' => ['required', Rule::in(config('upvotes.upvotable_types'))]
]
)->validate();

Expand Down
17 changes: 2 additions & 15 deletions app/Http/Requests/Comment/StoreCommentRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use App\Services\ModelResolverService;
use Illuminate\Foundation\Http\FormRequest;
use Auth;
use Closure;
use Illuminate\Validation\Rule;

class StoreCommentRequest extends FormRequest
{
Expand Down Expand Up @@ -37,20 +37,7 @@ public function rules(): array
"commentable_type" => [
'required',
'string',
function (string $_attribute, mixed $value, Closure $fail) {
if (!in_array($value, config('comment.commentable_types')))
{
$fail("Not a valid commentable type");
}

$id = request('commentable_id');
$model = $this->modelResolver->resolve($value, $id);

if ($model == null)
{
$fail("commentable id and type does not exist.");
}
},
Rule::in(config('comment.commentable_types')),
],
"content" => ["required", "string", "max:4000"],
"parent_comment_id" => ["nullable", "exists:App\Models\Comment,id"]
Expand Down
3 changes: 3 additions & 0 deletions app/Services/ModelResolverService.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class ModelResolverService
/**
* Finds the model that exists for the given type and id
*
* @param $type, the colloquial name for the type
* @param $id, the id for the type
*
* returns null if no model exists, otherwise, it will return the model
*/
public function resolve($type, $id)
Expand Down
5 changes: 5 additions & 0 deletions config/upvotes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

return [
'upvotable_types' => ['review', 'comment', 'edit', 'resource']
];
72 changes: 30 additions & 42 deletions database/factories/CommentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
namespace Database\Factories;

use App\Events\CommentCreated;
use App\Models\ComputerScienceResource;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\User;
use App\Models\Comment;
use App\Models\ResourceReview;
use App\Services\ModelResolverService;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Comment>
Expand All @@ -19,55 +18,44 @@ class CommentFactory extends Factory
*
* @return array<string, mixed>
*/

// TODO: Double check this logic
public function definition(): array
{
// Set the random commentable type.
$commentableType = $this->faker->randomElement([
ResourceReview::class,
Comment::class,
ComputerScienceResource::class,
]);

// Create the commented type
$commenting = isset($models[$commentableType])
? $models[$commentableType]::inRandomOrder()->first() ?? $models[$commentableType]::factory()->create()
: null;
// Pick a random commentable type from config.
$commentableName = $this->faker->randomElement(['comment', 'resource']);
$modelResolver = app(ModelResolverService::class);
$modelClass = $modelResolver->getModelClass($commentableName);


$commentableId = $commenting->id;

// Get a random user (or create one if necessary).
$user = User::inRandomOrder()->first()
?? User::factory()->create();

// Randomly decide if this is a reply comment.
$isReply = $this->faker->boolean;

$parent = null;
if ($isReply) {
// Try to find an existing comment on the same resource.
$parent = Comment::where('commentable_type', $commentableType)
->where('commentable_id', $commentableId)
->inRandomOrder()
->first();
}
// Use an existing user or create one.
$user = User::inRandomOrder()->first() ?? User::factory()->create();

// If the commentable type is a Comment, it means this new comment is a reply.
if ($modelClass === Comment::class) {
// Get an existing comment or create one if none exists.
$existingComment = Comment::inRandomOrder()->first() ?? Comment::factory()->create();

if ($parent) {
// Set the parent comment id.
$commentableId = $existingComment->commentable_id;
$commentableType = $existingComment->commentable_type;

// Since it's a recursive comment, the existing comment becomes the parent.
$parent = $existingComment;
$parentCommentId = $parent->id;

// Get the parent's root, and set that as this comment's root, unless it is the root itself.
$rootCommentId = ($parent->depth == 0) ? $parent->id : $parent->root_comment_id;

// Set the new depth.
// If parent's depth is 1, it is the root; otherwise, use its stored root.
$rootCommentId = ($parent->depth == 1) ? $parent->id : $parent->root_comment_id;
$depth = $parent->depth + 1;
} else {
// Top-level comment.
// For non-comment targets, fetch or create the commentable model.
$commenting = $modelClass::inRandomOrder()->first() ?? $modelClass::factory()->create();
$commentableId = $commenting->id;
$commentableType = $modelClass;

// For non-comment targets we always create a top-level comment.
$parentCommentId = null;
$rootCommentId = null;
$depth = 0;
$depth = 1;
}

return [
'user_id' => $user->id,
'content' => $this->faker->paragraph,
Expand All @@ -79,7 +67,7 @@ public function definition(): array
'children_count' => 0,
];
}

public function configure()
{
return $this->afterCreating(function (Comment $comment) {
Expand Down
36 changes: 29 additions & 7 deletions database/factories/ResourceEditsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,44 @@

namespace Database\Factories;

use App\Models\ResourceEdits;
use App\Models\User;
use App\Models\ComputerScienceResource;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ResourceEdits>
* @extends Factory<ResourceEdits>
*/
class ResourceEditsFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
protected $model = ResourceEdits::class;

public function definition(): array
{
$platforms = config('computerScienceResource.platforms');
$difficulties = config('computerScienceResource.difficulties');
$pricings = config('computerScienceResource.pricings');

return [
//
'computer_science_resource_id' => ComputerScienceResource::factory(),
'user_id' => User::factory(),

'edit_title' => $this->faker->sentence,
'edit_description' => $this->faker->paragraph,

// Copied fields from the resource
'name' => $this->faker->name(),
'description' => $this->faker->realText(),
'image_url' => 'https://cdn.iconscout.com/icon/free/png-256/free-leetcode-logo-icon-download-in-svg-png-gif-file-formats--technology-social-media-company-vol-1-pack-logos-icons-3030025.png',
'page_url' => $this->faker->url(),

'platforms' => $this->faker->randomElements($platforms, rand(1, 3)),
'difficulty' => $this->faker->randomElement($difficulties),
'pricing' => $this->faker->randomElement($pricings),

'topic_tags' => ['data structures', 'algorithms'],
'programming_language_tags' => ['python'],
'general_tags' => ['interactive', 'challenging'],
];
}
}
2 changes: 1 addition & 1 deletion database/seeders/CommentSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class CommentSeeder extends Seeder
*/
public function run(): void
{
Comment::factory(10)->create();
Comment::factory(100)->create();
}
}
2 changes: 1 addition & 1 deletion database/seeders/ComputerScienceResourceSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ class ComputerScienceResourceSeeder extends Seeder
public function run(): void
{
Log::info('Running ComputerScienceResourceSeeder');
ComputerScienceResource::factory(50)->create();
ComputerScienceResource::factory(1)->create();
}
}
3 changes: 2 additions & 1 deletion database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace Database\Seeders;

use App\Models\ResourceEdits;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Database\Seeders\ComputerScienceResourceSeeder;
use Database\Seeders\UserSeeder;
use Database\Seeders\ResourceReviewSeeder;
use Database\Seeders\ResourceEditsSeeder;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
Expand All @@ -21,6 +21,7 @@ public function run(): void
UserSeeder::class,
ComputerScienceResourceSeeder::class,
ResourceReviewSeeder::class,
ResourceEditsSeeder::class,
CommentSeeder::class,
]);
}
Expand Down
3 changes: 2 additions & 1 deletion database/seeders/ResourceEditsSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Database\Seeders;

use App\Models\ResourceEdits;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

Expand All @@ -12,6 +13,6 @@ class ResourceEditsSeeder extends Seeder
*/
public function run(): void
{

ResourceEdits::factory(10)->create();
}
}
2 changes: 1 addition & 1 deletion database/seeders/ResourceReviewSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class ResourceReviewSeeder extends Seeder
*/
public function run(): void
{
ResourceReview::factory(50)->create();
ResourceReview::factory(5)->create();
}
}
41 changes: 39 additions & 2 deletions tests/Feature/CommentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Models\Comment;
use App\Models\User;
use App\Models\ComputerScienceResource;
use App\Services\ModelResolverService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

Expand Down Expand Up @@ -77,7 +78,43 @@ public function test_cannot_comment_on_non_existent_resource()
];

$response = $this->postJson(route('comments.store'), $payload);
$response->assertStatus(422);
$response->assertStatus(404);
}

/**
* Test that commenting works on all commentable types defined in config.
*/
// TODO:
public function test_can_comment_all_commentable_types()
{
$user = User::factory()->create();
$this->actingAs($user);

foreach (config('comment.commentable_types') as $typeKey) {
$modelClass = app(ModelResolverService::class)->getModelClass($typeKey);

// Skip comments
if ($modelClass === Comment::class) {
continue;
}

$commentable = $modelClass::factory()->create();
$payload = [
'content' => 'top level comment',
'commentable_type' => $typeKey,
'commentable_id' => $commentable->id,
'parent_comment_id' => null,
];

$response = $this->postJson(route('comments.store'), $payload);
$response->assertStatus(200, "Failed to comment on type {$typeKey}");

$this->assertDatabaseHas('comments', [
'content' => $payload['content'],
'commentable_type' => $modelClass,
'commentable_id' => $payload['commentable_id'],
]);
}
}

/**
Expand All @@ -97,7 +134,7 @@ public function test_cannot_reply_on_non_existent_comment()
];

$response = $this->postJson(route('comments.store'), $payload);
$response->assertStatus(422);
$response->assertStatus(404);
}

/**
Expand Down
Loading