diff --git a/app/Models/Post.php b/app/Models/Post.php index 338a2478..4d770ca0 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -4,15 +4,18 @@ namespace App\Models; +use App\DataTransferObjects\MediaItem; use App\Enums\Post\Status as PostStatus; use Database\Factories\PostFactory; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Collection; class Post extends Model { @@ -22,8 +25,9 @@ class Post extends Model protected $fillable = [ 'workspace_id', 'user_id', + 'content', + 'media', 'status', - 'synced', 'scheduled_at', 'published_at', ]; @@ -32,12 +36,24 @@ protected function casts(): array { return [ 'status' => PostStatus::class, - 'synced' => 'boolean', + 'media' => 'array', 'scheduled_at' => 'datetime', 'published_at' => 'datetime', ]; } + /** + * Get media items as a collection of MediaItem DTOs. + * + * @return Collection + */ + protected function mediaItems(): Attribute + { + return Attribute::make( + get: fn () => collect($this->media ?? [])->map(fn (array $item) => MediaItem::fromArray($item)), + ); + } + public function workspace(): BelongsTo { return $this->belongsTo(Workspace::class); @@ -53,6 +69,11 @@ public function postPlatforms(): HasMany return $this->hasMany(PostPlatform::class); } + public function comments(): HasMany + { + return $this->hasMany(PostComment::class); + } + public function labels(): BelongsToMany { return $this->belongsToMany(WorkspaceLabel::class); diff --git a/app/Models/PostComment.php b/app/Models/PostComment.php new file mode 100644 index 00000000..feec1498 --- /dev/null +++ b/app/Models/PostComment.php @@ -0,0 +1,65 @@ + */ + use HasFactory, HasUuids; + + protected $fillable = [ + 'post_id', + 'user_id', + 'parent_id', + 'body', + 'reactions', + ]; + + protected function casts(): array + { + return [ + 'reactions' => 'array', + ]; + } + + public function post(): BelongsTo + { + return $this->belongsTo(Post::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function parent(): BelongsTo + { + return $this->belongsTo(self::class, 'parent_id'); + } + + public function replies(): HasMany + { + return $this->hasMany(self::class, 'parent_id')->oldest(); + } + + public function addReaction(string $userId, string $emoji): void + { + $reactions = $this->reactions ?? []; + $reactions = array_filter($reactions, fn (array $r) => ! (data_get($r, 'user_id') === $userId && data_get($r, 'emoji') === $emoji)); + + if (count($reactions) === count($this->reactions ?? [])) { + $reactions[] = ['user_id' => $userId, 'emoji' => $emoji]; + } + + $this->update(['reactions' => array_values($reactions)]); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cd538b29..669a4316 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,6 +12,7 @@ use App\Models\NotificationPreference; use App\Models\Plan; use App\Models\Post; +use App\Models\PostComment; use App\Models\PostPlatform; use App\Models\SocialAccount; use App\Models\Subscription; @@ -95,6 +96,7 @@ protected function configureMorphMap(): void 'plan' => Plan::class, 'notificationPreference' => NotificationPreference::class, 'post' => Post::class, + 'postComment' => PostComment::class, 'postPlatform' => PostPlatform::class, 'socialAccount' => SocialAccount::class, 'subscription' => Subscription::class, diff --git a/database/factories/PostCommentFactory.php b/database/factories/PostCommentFactory.php new file mode 100644 index 00000000..5b0cf426 --- /dev/null +++ b/database/factories/PostCommentFactory.php @@ -0,0 +1,32 @@ + */ +class PostCommentFactory extends Factory +{ + public function definition(): array + { + return [ + 'post_id' => Post::factory(), + 'user_id' => User::factory(), + 'body' => $this->faker->sentence(), + 'reactions' => [], + ]; + } + + public function reply(PostComment $parent): static + { + return $this->state(fn () => [ + 'post_id' => $parent->post_id, + 'parent_id' => $parent->id, + ]); + } +} diff --git a/database/migrations/2026_04_15_000001_create_post_comments_table.php b/database/migrations/2026_04_15_000001_create_post_comments_table.php new file mode 100644 index 00000000..ee7769e7 --- /dev/null +++ b/database/migrations/2026_04_15_000001_create_post_comments_table.php @@ -0,0 +1,37 @@ +uuid('id')->primary(); + $table->uuid('post_id'); + $table->uuid('user_id')->nullable(); + $table->uuid('parent_id')->nullable(); + $table->text('body'); + $table->json('reactions')->default('[]'); + $table->timestamps(); + + $table->foreign('post_id')->references('id')->on('posts')->cascadeOnDelete(); + $table->foreign('user_id')->references('id')->on('users')->nullOnDelete(); + + $table->index(['post_id', 'created_at']); + }); + + Schema::table('post_comments', function (Blueprint $table) { + $table->foreign('parent_id')->references('id')->on('post_comments')->cascadeOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('post_comments'); + } +};