more fixes
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -14,11 +17,18 @@ class ArtworkDownload extends Model
|
||||
{
|
||||
protected $table = 'artwork_downloads';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
const CREATED_AT = 'created_at';
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $fillable = [
|
||||
'artwork_id',
|
||||
'user_id',
|
||||
'ip',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'referer',
|
||||
];
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
|
||||
@@ -87,7 +87,7 @@ class ForumCategory extends Model
|
||||
}
|
||||
|
||||
if ($slug !== '') {
|
||||
return '/images/forum/defaults/' . $slug . '.jpg';
|
||||
return '/images/forum/' . $slug . '.webp';
|
||||
}
|
||||
|
||||
return $default;
|
||||
|
||||
@@ -15,7 +15,18 @@ class ForumPost extends Model
|
||||
protected $table = 'forum_posts';
|
||||
|
||||
protected $fillable = [
|
||||
'id','thread_id','user_id','content','is_edited','edited_at'
|
||||
'id',
|
||||
'thread_id',
|
||||
'topic_id',
|
||||
'user_id',
|
||||
'content',
|
||||
'is_edited',
|
||||
'edited_at',
|
||||
'spam_score',
|
||||
'quality_score',
|
||||
'flagged',
|
||||
'flagged_reason',
|
||||
'moderation_checked',
|
||||
];
|
||||
|
||||
public $incrementing = true;
|
||||
@@ -23,6 +34,10 @@ class ForumPost extends Model
|
||||
protected $casts = [
|
||||
'is_edited' => 'boolean',
|
||||
'edited_at' => 'datetime',
|
||||
'spam_score' => 'integer',
|
||||
'quality_score' => 'integer',
|
||||
'flagged' => 'boolean',
|
||||
'moderation_checked' => 'boolean',
|
||||
];
|
||||
|
||||
public function thread(): BelongsTo
|
||||
|
||||
42
app/Models/Notification.php
Normal file
42
app/Models/Notification.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Notification extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'notifications';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'type',
|
||||
'data',
|
||||
'read_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
'read_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function markAsRead(): void
|
||||
{
|
||||
if ($this->read_at === null) {
|
||||
$this->forceFill(['read_at' => now()])->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\StoryLike;
|
||||
use App\Models\StoryView;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Story — editorial content replacing the legacy Interviews module.
|
||||
* Creator Story model.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $slug
|
||||
@@ -16,10 +21,10 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property string|null $excerpt
|
||||
* @property string|null $content
|
||||
* @property string|null $cover_image
|
||||
* @property int|null $author_id
|
||||
* @property int|null $creator_id
|
||||
* @property int $views
|
||||
* @property bool $featured
|
||||
* @property string $status draft|published
|
||||
* @property string $status draft|pending_review|published|scheduled|archived|rejected
|
||||
* @property \Carbon\Carbon|null $published_at
|
||||
* @property int|null $legacy_interview_id
|
||||
*/
|
||||
@@ -35,38 +40,81 @@ class Story extends Model
|
||||
'excerpt',
|
||||
'content',
|
||||
'cover_image',
|
||||
'author_id',
|
||||
'creator_id',
|
||||
'story_type',
|
||||
'reading_time',
|
||||
'views',
|
||||
'likes_count',
|
||||
'comments_count',
|
||||
'featured',
|
||||
'status',
|
||||
'published_at',
|
||||
'scheduled_for',
|
||||
'meta_title',
|
||||
'meta_description',
|
||||
'canonical_url',
|
||||
'og_image',
|
||||
'submitted_for_review_at',
|
||||
'reviewed_at',
|
||||
'reviewed_by_id',
|
||||
'rejected_reason',
|
||||
'legacy_interview_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'featured' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
'scheduled_for' => 'datetime',
|
||||
'submitted_for_review_at' => 'datetime',
|
||||
'reviewed_at' => 'datetime',
|
||||
'views' => 'integer',
|
||||
'likes_count' => 'integer',
|
||||
'comments_count' => 'integer',
|
||||
'reading_time' => 'integer',
|
||||
];
|
||||
|
||||
// ── Relations ────────────────────────────────────────────────────────
|
||||
|
||||
public function author()
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(StoryAuthor::class, 'author_id');
|
||||
return $this->belongsTo(User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function tags()
|
||||
// Legacy alias used by older views/controllers.
|
||||
public function author(): BelongsTo
|
||||
{
|
||||
return $this->belongsToMany(StoryTag::class, 'stories_tag_relation', 'story_id', 'tag_id');
|
||||
return $this->creator();
|
||||
}
|
||||
|
||||
public function tags(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(StoryTag::class, 'relation_story_tags', 'story_id', 'tag_id');
|
||||
}
|
||||
|
||||
public function storyViews(): HasMany
|
||||
{
|
||||
return $this->hasMany(StoryView::class, 'story_id');
|
||||
}
|
||||
|
||||
public function storyLikes(): HasMany
|
||||
{
|
||||
return $this->hasMany(StoryLike::class, 'story_id');
|
||||
}
|
||||
|
||||
// ── Scopes ───────────────────────────────────────────────────────────
|
||||
|
||||
public function scopePublished($query)
|
||||
{
|
||||
return $query->where('status', 'published')
|
||||
->where(fn ($q) => $q->whereNull('published_at')->orWhere('published_at', '<=', now()));
|
||||
return $query
|
||||
->where(function ($q): void {
|
||||
$q->where('status', 'published')
|
||||
->orWhere(function ($scheduled): void {
|
||||
$scheduled->where('status', 'scheduled')
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
});
|
||||
})
|
||||
->where(fn ($q) => $q->whereNull('published_at')->orWhere('published_at', '<=', now()));
|
||||
}
|
||||
|
||||
public function scopeFeatured($query)
|
||||
@@ -95,6 +143,10 @@ class Story extends Model
|
||||
*/
|
||||
public function getReadingTimeAttribute(): int
|
||||
{
|
||||
if (! empty($this->attributes['reading_time'])) {
|
||||
return max(1, (int) $this->attributes['reading_time']);
|
||||
}
|
||||
|
||||
$wordCount = str_word_count(strip_tags((string) $this->content));
|
||||
|
||||
return max(1, (int) ceil($wordCount / 200));
|
||||
|
||||
32
app/Models/StoryLike.php
Normal file
32
app/Models/StoryLike.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class StoryLike extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const UPDATED_AT = null;
|
||||
|
||||
protected $fillable = [
|
||||
'story_id',
|
||||
'user_id',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
public function story(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Story::class, 'story_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ declare(strict_types=1);
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Story Tag — editorial tag for the Stories system.
|
||||
* Story tag entity for creator stories.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $slug
|
||||
@@ -18,7 +19,7 @@ class StoryTag extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'stories_tags';
|
||||
protected $table = 'story_tags';
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
@@ -27,9 +28,9 @@ class StoryTag extends Model
|
||||
|
||||
// ── Relations ────────────────────────────────────────────────────────
|
||||
|
||||
public function stories()
|
||||
public function stories(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Story::class, 'stories_tag_relation', 'tag_id', 'story_id');
|
||||
return $this->belongsToMany(Story::class, 'relation_story_tags', 'tag_id', 'story_id');
|
||||
}
|
||||
|
||||
// ── Accessors ────────────────────────────────────────────────────────
|
||||
|
||||
33
app/Models/StoryView.php
Normal file
33
app/Models/StoryView.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class StoryView extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const UPDATED_AT = null;
|
||||
|
||||
protected $fillable = [
|
||||
'story_id',
|
||||
'user_id',
|
||||
'ip_address',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
public function story(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Story::class, 'story_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Models\SocialAccount;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\ConversationParticipant;
|
||||
use App\Models\Message;
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -30,6 +31,7 @@ class User extends Authenticatable
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'username_changed_at',
|
||||
'last_username_change_at',
|
||||
'onboarding_step',
|
||||
'name',
|
||||
'email',
|
||||
@@ -38,6 +40,10 @@ class User extends Authenticatable
|
||||
'verification_send_window_started_at',
|
||||
'is_active',
|
||||
'needs_password_reset',
|
||||
'cover_hash',
|
||||
'cover_ext',
|
||||
'cover_position',
|
||||
'trust_score',
|
||||
'password',
|
||||
'role',
|
||||
'allow_messages_from',
|
||||
@@ -66,7 +72,10 @@ class User extends Authenticatable
|
||||
'verification_send_window_started_at' => 'datetime',
|
||||
'verification_send_count_24h' => 'integer',
|
||||
'username_changed_at' => 'datetime',
|
||||
'last_username_change_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
'cover_position' => 'integer',
|
||||
'trust_score' => 'integer',
|
||||
'password' => 'hashed',
|
||||
'allow_messages_from' => 'string',
|
||||
];
|
||||
@@ -139,6 +148,19 @@ class User extends Authenticatable
|
||||
return $this->hasMany(Message::class, 'sender_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Skinbase notifications are keyed by user_id (non-polymorphic table).
|
||||
*/
|
||||
public function notifications(): HasMany
|
||||
{
|
||||
return $this->hasMany(Notification::class, 'user_id')->latest();
|
||||
}
|
||||
|
||||
public function unreadNotifications(): HasMany
|
||||
{
|
||||
return $this->notifications()->whereNull('read_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this user allows receiving messages from the given user.
|
||||
*/
|
||||
@@ -221,6 +243,11 @@ class User extends Authenticatable
|
||||
return $this->hasMany(Post::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function stories(): HasMany
|
||||
{
|
||||
return $this->hasMany(Story::class, 'creator_id')->orderByDesc('published_at');
|
||||
}
|
||||
|
||||
// ─── Meilisearch ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,12 +30,22 @@ class UserProfile extends Model
|
||||
'gender',
|
||||
'website',
|
||||
'auto_post_upload',
|
||||
'email_notifications',
|
||||
'upload_notifications',
|
||||
'follower_notifications',
|
||||
'comment_notifications',
|
||||
'newsletter',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'birthdate' => 'date',
|
||||
'avatar_updated_at'=> 'datetime',
|
||||
'auto_post_upload' => 'boolean',
|
||||
'email_notifications' => 'boolean',
|
||||
'upload_notifications' => 'boolean',
|
||||
'follower_notifications' => 'boolean',
|
||||
'comment_notifications' => 'boolean',
|
||||
'newsletter' => 'boolean',
|
||||
];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
Reference in New Issue
Block a user