Initial baseline: LaraBlog core with plugin commerce surface.

Captures the current working tree after theme slots, ArticleAccess, and the payment / paid-content plugins so subsequent work has a reviewable git history.
This commit is contained in:
ak
2026-08-12 01:15:38 +08:00
commit 263b98b218
337 changed files with 31393 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Domain\Blog\ContentFormat;
use App\Domain\Blog\ContentRenderer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Article extends Model
{
protected $fillable = [
'category_id',
'user_id',
'title',
'content',
'content_format',
'description',
'keywords',
'published_at',
'views',
'comments_count',
'slug',
'stick',
'visible',
'close_comment',
'read_password',
'ai_summary',
'ai_suggestions',
'legacy_attachments',
'cover_disk',
'cover_path',
'cover_source',
'cover_status',
'cover_generated_at',
];
protected function casts(): array
{
return [
'published_at' => 'datetime',
'views' => 'integer',
'comments_count' => 'integer',
'stick' => 'boolean',
'visible' => 'boolean',
'close_comment' => 'boolean',
'ai_suggestions' => 'array',
'legacy_attachments' => 'array',
'cover_generated_at' => 'datetime',
];
}
public function hasCover(): bool
{
return $this->cover_status === 'ready' && filled($this->cover_path);
}
protected function contentFormat(): Attribute
{
return Attribute::make(
get: fn (?string $value): string => ContentFormat::normalize($value, ContentFormat::HTML),
set: fn (?string $value): string => ContentFormat::normalize($value, ContentFormat::HTML),
);
}
public function renderedHtml(): string
{
return $this->rendered()['html'];
}
/**
* @return array{html: string, toc: list<array{level: int, id: string, text: string}>}
*/
public function rendered(): array
{
return app(ContentRenderer::class)->render(
(string) $this->content,
$this->content_format,
$this->id,
);
}
public function isMarkdown(): bool
{
return $this->content_format === ContentFormat::MARKDOWN;
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
public function attachments(): HasMany
{
return $this->hasMany(Attachment::class);
}
public function scopeVisible(Builder $query): Builder
{
return $query->where('visible', true);
}
public function scopePublished(Builder $query): Builder
{
return $query
->whereNotNull('published_at')
->where('published_at', '<=', now());
}
}