feat: use xpier media library for uploads and markdown embeds
Build and Deploy / verify (push) Successful in 35s
Build and Deploy / deploy (push) Successful in 33s

Store media:ID in article bodies instead of CDN URLs, and keep sablog attach tokens as a fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
gouki
2026-09-16 19:03:52 +00:00
co-authored by Cursor
parent 0b0110a681
commit 64655ced1e
49 changed files with 5615 additions and 2132 deletions
+5
View File
@@ -74,6 +74,11 @@ ATTACHMENTS_URL_PREFIX=attachments
# local = storage/app/attachments (dev without MinIO/S3); s3 = object storage
ATTACHMENTS_DRIVER=local
# Platform media library (xpier/filament-media-library). Body stores media:ID, never CDN URLs.
MEDIA_DISK=public
MEDIA_DIRECTORY=media
MEDIA_VISIBILITY=public
# Sablog source DB for: php artisan sablog:import --mode=raw|markdown
SABLOG_DB_DRIVER=mysql
SABLOG_DB_HOST=127.0.0.1
+1
View File
@@ -0,0 +1 @@
24
+12
View File
@@ -0,0 +1,12 @@
# Changelog
## 2026-09-16
- Switched new attachments/covers to `xpier/filament-media-library`.
- Markdown body now inserts `![alt](media:ID)` (files: `[name](media:ID)`); URLs are resolved at render time. Legacy `attach:` / `[attach=ID]` stay for sablog content.
- Schema snapshot: `docs/database/mysql-schema-20260916-131822.sql`.
## 2026-09-07
- Synced this repository from Gitea to local Herd for development (`http://larablog.test`).
- Local schema snapshot: `docs/database/schema-20260907-223917.sql`.
+21
View File
@@ -0,0 +1,21 @@
# LaraBlog Features
Laravel blog platform with sablog `.shtml` URLs, Filament admin, plugins, themes, and `/api/v1`.
## Local development
- Site: http://larablog.test
- Admin: http://larablog.test/admin
- Account: `admin@larablog.test` / `password`
- Database: MySQL `larablog` (Herd, root / empty password)
- Remote: `git@gitea.neatcn.com:gouki/larablog.git`
See `README.md` and `docs/ops/install.md` for full setup. Import old sablog data with `docs/ops/import.md` (do not seed demo data on a migration target).
## Media library (xpier)
- Package: `xpier/filament-media-library`. New files/covers go through Media Library; sablog `attachments` stay for import.
- **Markdown body stores `![alt](media:ID)` / `[name](media:ID)`**. HTML stores `[media=ID]`. Never paste CDN URLs into the body.
- Insert flow: pick a file → **Insert into body**. Cover: `cover_media_id`.
- Legacy: `![alt](attach:ID)` and `[attach=ID]` still resolve to `/attachment.php?id=`.
- Details: `docs/media.md`.
+13 -11
View File
@@ -20,21 +20,23 @@ php artisan serve
- 前台:`/`、`/show-{id}.shtml`、`/login.shtml` 等
- 后台:`/admin`(`admin@larablog.test` / `password`)
- **Markdown 新图**:`![alt](media:ID)`;文件 `[name](media:ID)`。HTML:`[media=ID]`。不要把 CDN 地址写进正文。见 [docs/media.md](docs/media.md)。
- API:`/api/v1/*`(OpenAPI:`/docs/api/openapi.yaml`)
## 文档索引
| 文档 | 内容 |
|---|---|
| [docs/ops/install.md](docs/ops/install.md) | 本地安装、演示账号、插件 |
| [docs/ops/import.md](docs/ops/import.md) | sablog 导入(ID / 附件 / 旧密码) |
| [docs/ops/deploy.md](docs/ops/deploy.md) | 生产部署:nginx、PM2、Redis、S3 |
| [docs/architecture.md](docs/architecture.md) | 架构、分层、开发思想 |
| [docs/themes.md](docs/themes.md) | 皮肤目录格式与开发 |
| [docs/plugins.md](docs/plugins.md) | 插件格式 / Hook |
| [docs/routing.md](docs/routing.md) | `.shtml` 路由策略 |
| [docs/api/](docs/api/) | OpenAPI + 小程序向只读 API |
| [docs/specs/larablog-platform/](docs/specs/larablog-platform/) | SPEC / CHECKLIST / TESTPLAN |
| 文档 | 内容 |
| -------------------------------------------------------------- | ----------------------------------- |
| [docs/ops/install.md](docs/ops/install.md) | 本地安装、演示账号、插件 |
| [docs/ops/import.md](docs/ops/import.md) | sablog 导入(ID / 附件 / 旧密码) |
| [docs/ops/deploy.md](docs/ops/deploy.md) | 生产部署:nginx、PM2、Redis、S3 |
| [docs/architecture.md](docs/architecture.md) | 架构、分层、开发思想 |
| [docs/themes.md](docs/themes.md) | 皮肤目录格式与开发 |
| [docs/plugins.md](docs/plugins.md) | 插件格式 / Hook |
| [docs/routing.md](docs/routing.md) | `.shtml` 路由策略 |
| [docs/api/](docs/api/) | OpenAPI + 小程序向只读 API |
| [docs/media.md](docs/media.md) | Media library + Markdown `media:ID` |
| [docs/specs/larablog-platform/](docs/specs/larablog-platform/) | SPEC / CHECKLIST / TESTPLAN |
## 进程与 CI
+2
View File
@@ -36,12 +36,14 @@ class ContentRenderer
if ($format === ContentFormat::MARKDOWN) {
$content = $this->attachEmbed->legacyToMarkdownReference($content, $articleId);
$content = $this->attachEmbed->expandMarkdownAttachProtocol($content);
$content = app(\App\Domain\Media\MarkdownMediaEmbed::class)->expandMarkdown($content);
$html = $this->markdownToHtml($content);
} else {
$html = $content;
}
$html = $this->attachEmbed->hydrateHtml($html, $articleId);
$html = app(\App\Domain\Media\MarkdownMediaEmbed::class)->hydrateHtml($html);
$html = Purifier::clean($html, 'article');
return $this->applyTocAnchors($html);
+11
View File
@@ -117,6 +117,17 @@ class ArticleCoverService
{
$content = (string) $article->content;
if (preg_match('/!\[[^\]]*\]\(media:(\d+)\)/i', $content, $matches) === 1) {
$media = app(MarkdownMediaEmbed::class)->find((int) $matches[1]);
if ($media !== null && app(MarkdownMediaEmbed::class)->isImage($media) && filled($media->url)) {
return [
'disk' => 'url',
'path' => (string) $media->url,
'source' => self::SOURCE_CONTENT_IMAGE,
];
}
}
if (preg_match('/!\[[^\]]*\]\(attach:(\d+)\)/i', $content, $matches) === 1
|| preg_match('/\[attach=(\d+)\]/i', $content, $matches) === 1) {
$attachment = Attachment::query()->find((int) $matches[1]);
+131
View File
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Domain\Media;
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
/**
* Markdown/HTML body must store library references, never CDN URLs.
*
* Markdown: ![alt](media:12) or [file.pdf](media:12)
* HTML: [media=12]
*
* Legacy sablog tokens ([attach=ID] / attach:ID) stay on the Attachment table.
*/
class MarkdownMediaEmbed
{
public const PROTOCOL = 'media:';
public function snippet(MediaLibrary $media, string $format = 'markdown'): string
{
$id = (int) $media->getKey();
$isImage = $this->isImage($media);
if ($format === 'html') {
return '[media='.$id.']';
}
$label = $this->escapeMdLabel($this->label($media, $isImage));
return $isImage
? '!['.$label.']('.self::PROTOCOL.$id.')'
: '['.$label.']('.self::PROTOCOL.$id.')';
}
public function expandMarkdown(string $markdown): string
{
return preg_replace_callback(
'/\]\(media:(\d+)\)/i',
function (array $matches): string {
$url = $this->publicUrl((int) $matches[1]);
return $url !== null ? ']('.$url.')' : $matches[0];
},
$markdown
) ?? $markdown;
}
public function hydrateHtml(string $html): string
{
$html = preg_replace_callback(
'/\b(src|href)=([\'"])media:(\d+)\2/i',
function (array $matches): string {
$url = $this->publicUrl((int) $matches[3]);
if ($url === null) {
return $matches[0];
}
return $matches[1].'='.$matches[2].$url.$matches[2];
},
$html
) ?? $html;
return preg_replace_callback(
'/\[media=(\d+)\]/i',
function (array $matches): string {
$id = (int) $matches[1];
$media = $this->find($id);
$url = $this->publicUrl($id);
if ($url === null) {
return '';
}
if ($media && $this->isImage($media)) {
$alt = e($this->label($media, true));
return '<img src="'.e($url).'" alt="'.$alt.'" />';
}
$label = e($media ? $this->label($media, false) : 'file-'.$id);
return '<a href="'.e($url).'">'.$label.'</a>';
},
$html
) ?? $html;
}
public function find(int $id): ?MediaLibrary
{
try {
return MediaLibrary::query()->find($id);
} catch (\Throwable) {
return null;
}
}
public function publicUrl(int $id): ?string
{
$media = $this->find($id);
$url = $media?->url;
return filled($url) ? (string) $url : null;
}
public function isImage(MediaLibrary $media): bool
{
if ($media->type === MediaLibrary::TYPE_IMAGE) {
return true;
}
return is_string($media->mime_type) && str_starts_with($media->mime_type, 'image/');
}
protected function label(MediaLibrary $media, bool $isImage): string
{
if (filled($media->alt_text)) {
return (string) $media->alt_text;
}
$name = (string) ($media->original_name ?: basename((string) $media->path) ?: 'media-'.$media->getKey());
return $isImage ? (pathinfo($name, PATHINFO_FILENAME) ?: $name) : $name;
}
protected function escapeMdLabel(string $label): string
{
return str_replace(['[', ']', '!'], ['\\[', '\\]', '\\!'], $label);
}
}
@@ -5,15 +5,22 @@ declare(strict_types=1);
namespace App\Filament\Resources\Articles\Schemas;
use App\Domain\Blog\ContentFormat;
use App\Domain\Media\MarkdownMediaEmbed;
use App\Domain\Plugin\Hook;
use App\Settings\GeneralSettings;
use Filament\Actions\Action;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Xpier\FilamentMediaLibrary\Components\MediaPicker;
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
class ArticleForm
{
@@ -57,6 +64,37 @@ class ArticleForm
->required()
->rows(18)
->columnSpanFull(),
MediaPicker::make('insert_media_id')
->label(__('admin.fields.insert_media'))
->module('articles')
->mediaType(null)
->dehydrated(false)
->helperText(__('admin.helpers.insert_media'))
->columnSpanFull(),
Actions::make([
Action::make('insertSelectedMedia')
->label(__('admin.actions.insert_media_into_content'))
->action(function (Get $get, Set $set): void {
$mediaId = $get('insert_media_id');
$media = is_numeric($mediaId)
? MediaLibrary::query()->find((int) $mediaId)
: null;
if (! $media instanceof MediaLibrary) {
Notification::make()->title(__('admin.notifications.pick_media_first'))->warning()->send();
return;
}
$snippet = app(MarkdownMediaEmbed::class)->snippet(
$media,
(string) ($get('content_format') ?: ContentFormat::MARKDOWN)
);
$set('content', rtrim((string) $get('content'))."\n\n".$snippet."\n");
$set('insert_media_id', null);
Notification::make()->title(__('admin.notifications.media_inserted'))->success()->send();
}),
])->columnSpanFull(),
TextInput::make('description')
->label(__('admin.fields.description')),
TextInput::make('keywords')
@@ -82,6 +120,12 @@ class ArticleForm
->label(__('admin.fields.cover_path'))
->helperText(__('admin.helpers.cover_path'))
->columnSpanFull(),
MediaPicker::make('cover_media_id')
->label(__('admin.fields.cover_media'))
->module('articles')
->mediaType('image')
->relationship('coverMedia')
->columnSpanFull(),
TextInput::make('cover_source')
->label(__('admin.fields.cover_source'))
->disabled()
@@ -31,6 +31,11 @@ class AttachmentResource extends Resource
return 'attachments';
}
public static function shouldRegisterNavigation(): bool
{
return false;
}
protected static function modelKey(): string
{
return 'attachment';
+16
View File
@@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
class Article extends Model
{
@@ -40,6 +41,7 @@ class Article extends Model
'cover_path',
'cover_source',
'cover_status',
'cover_media_id',
'cover_generated_at',
];
@@ -60,11 +62,20 @@ class Article extends Model
public function hasCover(): bool
{
if ($this->cover_media_id) {
return true;
}
return $this->cover_status === 'ready' && filled($this->cover_path);
}
public function coverUrl(): ?string
{
$libraryUrl = $this->coverMedia?->url;
if (filled($libraryUrl)) {
return (string) $libraryUrl;
}
return app(ArticleCoverService::class)->publicUrl($this);
}
@@ -123,6 +134,11 @@ class Article extends Model
return $this->hasMany(Attachment::class);
}
public function coverMedia(): BelongsTo
{
return $this->belongsTo(MediaLibrary::class, 'cover_media_id');
}
public function scopeVisible(Builder $query): Builder
{
return $query->where('visible', true);
+8 -1
View File
@@ -6,6 +6,8 @@ namespace App\Models;
use App\Support\LegacyPassword;
use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -13,11 +15,16 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
class User extends Authenticatable implements FilamentUser
{
/** @use HasFactory<UserFactory> */
use HasFactory, HasRoles, Notifiable;
public function canAccessPanel(Panel $panel): bool
{
return $this->hasRole(['admin', 'editor']);
}
protected $fillable = [
'name',
'username',
+4
View File
@@ -46,6 +46,10 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
config([
'filament-media-library.navigation_group' => __('admin.groups.content'),
]);
Auth::provider('larablog', function ($app, array $config) {
return new LaraBlogUserProvider($app['hash'], $config['model'] ?? User::class);
});
@@ -26,6 +26,7 @@ use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Xpier\FilamentMediaLibrary\Filament\MediaLibraryPlugin;
class AdminPanelProvider extends PanelProvider
{
@@ -58,6 +59,10 @@ class AdminPanelProvider extends PanelProvider
->widgets([
AccountWidget::class,
])
->plugins([
MediaLibraryPlugin::make()
->navigationGroup((string) (__('admin.groups.content') ?: '内容')),
])
->renderHook(
PanelsRenderHook::GLOBAL_SEARCH_AFTER,
fn (): string => Blade::render('@livewire(\'admin.clear-cache-button\')'),
+13 -3
View File
@@ -3,7 +3,10 @@
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
@@ -20,7 +23,8 @@
"spatie/laravel-permission": "^6.25",
"spatie/laravel-settings": "^3.9",
"spatie/laravel-sitemap": "^8.0",
"workerman/workerman": "^5.2"
"workerman/workerman": "^5.2",
"xpier/filament-media-library": "^1.5"
},
"require-dev": {
"fakerphp/faker": "^1.23",
@@ -101,5 +105,11 @@
}
},
"minimum-stability": "stable",
"prefer-stable": true
"prefer-stable": true,
"repositories": [
{
"type": "vcs",
"url": "https://github.com/ffqa/xpier-filament-media-library.git"
}
]
}
Generated
+441 -1956
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasColumn('articles', 'cover_media_id')) {
Schema::table('articles', function (Blueprint $table): void {
$table->unsignedBigInteger('cover_media_id')->nullable()->after('cover_status');
});
}
}
public function down(): void
{
if (Schema::hasColumn('articles', 'cover_media_id')) {
Schema::table('articles', function (Blueprint $table): void {
$table->dropColumn('cover_media_id');
});
}
}
};
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('media_folders')) {
return;
}
$now = now();
$folders = [
['code' => 'general', 'name' => 'General', 'sort' => 0],
['code' => 'articles', 'name' => 'Articles', 'sort' => 1],
['code' => 'banners', 'name' => 'Banners', 'sort' => 2],
];
foreach ($folders as $folder) {
$exists = DB::table('media_folders')->where('code', $folder['code'])->exists();
if ($exists) {
continue;
}
DB::table('media_folders')->insert([
'parent_id' => null,
'code' => $folder['code'],
'name' => $folder['name'],
'sort' => $folder['sort'],
'is_active' => true,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function down(): void
{
if (! Schema::hasTable('media_folders')) {
return;
}
DB::table('media_folders')
->whereIn('code', ['general', 'articles', 'banners'])
->whereNull('parent_id')
->delete();
}
};
+619
View File
@@ -0,0 +1,619 @@
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `activity_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `activity_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`log_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci NOT NULL,
`subject_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`event` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`subject_id` bigint unsigned DEFAULT NULL,
`causer_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`causer_id` bigint unsigned DEFAULT NULL,
`properties` json DEFAULT NULL,
`batch_uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `subject` (`subject_type`,`subject_id`),
KEY `causer` (`causer_type`,`causer_id`),
KEY `activity_log_log_name_index` (`log_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_membership`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_membership` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`required_plan_id` bigint unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_membership_article_id_unique` (`article_id`),
KEY `article_membership_required_plan_id_foreign` (`required_plan_id`),
CONSTRAINT `article_membership_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_membership_required_plan_id_foreign` FOREIGN KEY (`required_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_products`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_products` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`price` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`trial_mode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'chars',
`trial_value` int unsigned NOT NULL DEFAULT '200',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_products_article_id_unique` (`article_id`),
CONSTRAINT `article_products_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_tag` (
`article_id` bigint unsigned NOT NULL,
`tag_id` bigint unsigned NOT NULL,
UNIQUE KEY `article_tag_article_id_tag_id_unique` (`article_id`,`tag_id`),
KEY `article_tag_tag_id_foreign` (`tag_id`),
CONSTRAINT `article_tag_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_tag_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `articles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `articles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`category_id` bigint unsigned NOT NULL,
`user_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`content_format` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'html',
`description` text COLLATE utf8mb4_unicode_ci,
`keywords` text COLLATE utf8mb4_unicode_ci,
`published_at` timestamp NULL DEFAULT NULL,
`views` bigint unsigned NOT NULL DEFAULT '0',
`comments_count` int unsigned NOT NULL DEFAULT '0',
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`stick` tinyint(1) NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`ai_summary` text COLLATE utf8mb4_unicode_ci,
`ai_suggestions` json DEFAULT NULL,
`ai_polished_content` longtext COLLATE utf8mb4_unicode_ci,
`legacy_attachments` json DEFAULT NULL,
`cover_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_source` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_media_id` bigint unsigned DEFAULT NULL,
`cover_generated_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `articles_slug_unique` (`slug`),
KEY `articles_user_id_foreign` (`user_id`),
KEY `articles_visible_published_at_index` (`visible`,`published_at`),
KEY `articles_category_id_index` (`category_id`),
CONSTRAINT `articles_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
CONSTRAINT `articles_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `attachments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `attachments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned DEFAULT NULL,
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'attachments',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`thumb_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`mime` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size` bigint unsigned NOT NULL DEFAULT '0',
`checksum` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`visibility` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
`legacy_filepath` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`synced_at` timestamp NULL DEFAULT NULL,
`downloads` bigint unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `attachments_article_id_foreign` (`article_id`),
KEY `attachments_legacy_filepath_index` (`legacy_filepath`),
CONSTRAINT `attachments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache_locks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache_locks` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_locks_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `categories` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`intro` text COLLATE utf8mb4_unicode_ci,
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`display_order` int NOT NULL DEFAULT '0',
`articles_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `comments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `comments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`author` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`content` text COLLATE utf8mb4_unicode_ci NOT NULL,
`ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`moderation_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
`published_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `comments_article_id_foreign` (`article_id`),
CONSTRAINT `comments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `entitlements`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `entitlements` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`source_order_id` bigint unsigned DEFAULT NULL,
`granted_at` timestamp NOT NULL,
`revoked_at` timestamp NULL DEFAULT NULL,
`expires_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `entitlements_user_id_product_type_product_id_unique` (`user_id`,`product_type`,`product_id`),
CONSTRAINT `entitlements_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `job_batches` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`total_jobs` int NOT NULL,
`pending_jobs` int NOT NULL,
`failed_jobs` int NOT NULL,
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`options` mediumtext COLLATE utf8mb4_unicode_ci,
`cancelled_at` int DEFAULT NULL,
`created_at` int NOT NULL,
`finished_at` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `links`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `links` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci,
`display_order` int NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `media_folders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `media_folders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`parent_id` bigint unsigned DEFAULT NULL,
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`sort` int NOT NULL DEFAULT '0',
`is_active` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `media_folders_code_unique` (`code`),
KEY `media_folders_parent_id_foreign` (`parent_id`),
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `media_library`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `media_library` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned DEFAULT NULL,
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size` bigint unsigned NOT NULL DEFAULT '0',
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_properties` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `media_library_path_unique` (`path`),
KEY `media_library_user_id_foreign` (`user_id`),
KEY `media_library_type_index` (`type`),
KEY `media_library_folder_index` (`folder`),
KEY `media_library_source_index` (`source`),
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `membership_plans`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `membership_plans` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`slug` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`price` decimal(10,2) NOT NULL,
`currency` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`duration_days` int unsigned DEFAULT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`sort_order` int NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`batch` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_roles` (
`role_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `order_items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_items` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_items_order_id_foreign` (`order_id`),
CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `orders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`paid_at` timestamp NULL DEFAULT NULL,
`meta` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `orders_user_id_foreign` (`user_id`),
CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `password_reset_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_reset_tokens` (
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `payment_transactions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `payment_transactions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`external_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`payload` json DEFAULT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `payment_transactions_order_id_foreign` (`order_id`),
CONSTRAINT `payment_transactions_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `permissions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `plugins`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `plugins` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`version` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1.0.0',
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`config` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `plugins_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `role_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `role_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`role_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`),
KEY `role_has_permissions_role_id_foreign` (`role_id`),
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `roles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sessions` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint unsigned DEFAULT NULL,
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_agent` text COLLATE utf8mb4_unicode_ci,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`last_activity` int NOT NULL,
PRIMARY KEY (`id`),
KEY `sessions_user_id_index` (`user_id`),
KEY `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `settings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `settings` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`group` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`locked` tinyint(1) NOT NULL DEFAULT '0',
`payload` json NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `settings_group_name_unique` (`group`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `stylevars`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `stylevars` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` text COLLATE utf8mb4_unicode_ci,
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tags` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`use_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tags_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password_legacy` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_count` int unsigned NOT NULL DEFAULT '0',
`login_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_at` timestamp NULL DEFAULT NULL,
`reg_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_post_at` timestamp NULL DEFAULT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`),
UNIQUE KEY `users_username_unique` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2022_12_14_083707_create_settings_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092526_create_permission_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092528_create_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_092529_add_event_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_092530_add_batch_uuid_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100000_create_blog_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100001_create_larablog_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100002_add_attachments_url_prefix_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100003_drop_trackback_legacy',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100003_remove_trackback_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_100004_add_article_content_format',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_11_100004_add_content_format_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_11_100005_add_article_cover_placeholders',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_11_220000_expand_blog_comment_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_11_230000_create_snippet_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_001000_create_orders_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_12_001001_create_order_items_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_12_001002_create_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_12_001003_create_payment_transactions_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_12_002000_create_article_products_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_08_12_013201_add_article_ai_polished_content',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_08_12_034500_add_expires_at_to_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_08_12_034600_create_membership_plans_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (27,'2026_08_12_034601_create_article_membership_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (28,'2026_08_13_015800_add_category_seo_fields',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (29,'2026_08_15_000001_create_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (30,'2026_08_15_000002_create_media_folders_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (31,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (32,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (33,'2026_09_16_000001_add_cover_media_id_to_articles',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (34,'2026_09_16_000002_seed_default_media_folders',2);
+18 -12
View File
@@ -1,9 +1,11 @@
# LaraBlog 架构与开发思想
## 目标
把 sablog 内容与 SEO 权重迁到现代 Laravel 栈:**不克隆过时能力**(Trackback/WAP/旧 Admin),用主题/插件/对象存储/AI 队列替换。
## 分层
```
HTTP (legacy .shtml + /admin Filament + /api/v1)
→ Controllers / Filament Pages
@@ -13,32 +15,36 @@ HTTP (legacy .shtml + /admin Filament + /api/v1)
```
## 关键约定
| 区域 | 路径 |
|---|---|
| Legacy 前台 URL | `routes/legacy.php` |
| Web 辅助 | `routes/web.php` |
| JSON API(小程序/其他端) | `routes/api.php` → `/api/v1/*` |
| 领域逻辑 | `app/Domain/*` |
| 主题 | `themes/{slug}/` |
| 插件 | `plugins/{vendor}/{name}/` |
| 规格 | `docs/specs/larablog-platform/` |
| 安装 / 导入 / 部署 | `docs/ops/install.md`、`import.md`、`deploy.md` |
| OpenAPI | `docs/api/openapi.yaml` |
| 区域 | 路径 |
| ------------------------- | ----------------------------------------------- |
| Legacy 前台 URL | `routes/legacy.php` |
| Web 辅助 | `routes/web.php` |
| JSON API(小程序/其他端) | `routes/api.php` → `/api/v1/*` |
| 领域逻辑 | `app/Domain/*` |
| 主题 | `themes/{slug}/` |
| 插件 | `plugins/{vendor}/{name}/` |
| 规格 | `docs/specs/larablog-platform/` |
| 安装 / 导入 / 部署 | `docs/ops/install.md`、`import.md`、`deploy.md` |
| OpenAPI | `docs/api/openapi.yaml` |
## 开发模式
1. **规格先行**:非琐碎功能写 SPEC / CHECKLIST / TESTPLAN
2. **兼容优先**:文章/分类 ID、`.shtml` 内容 URL 保持可达
3. **引用不写死 CDN**:正文存 `[attach=id]` / `attach:id`,渲染期转 `/attachment.php?id=`
3. **引用不写死 CDN**:新内容正文存 `media:ID` / `[media=ID]`;老内容仍用 `[attach=id]` / `attach:id`。渲染期再换成当前公开地址。见 [media.md](media.md)。
4. **异步 AI**:Web 不阻塞 LLM;队列 / Workerman 消费
5. **默认中文**:`APP_LOCALE=zh_CN`,文案走 `lang/zh_CN/*`
6. **测试**:PHPUnit Feature sqlite `:memory:`(见 `phpunit.xml`);CI 同配置
## PHP / 代码风格
- 目标运行时:**PHP 8.2+**(`composer.json`)
- 应用代码逐步统一 `declare(strict_types=1);`
- 类型提示、返回类型、枚举优先;避免软依赖全局状态
## 一期 vs 二期
- **一期已做**:核心博客、主题引擎、插件框架、SEO/GEO、导入、AI stub 管道、前台皮肤、后台 Filament
- **一期骨架**:支付 / 会员 / 双商城(非真实结算)
- **二期**:自动配图、真实支付会员、小程序完整写接口与鉴权深化
@@ -0,0 +1,565 @@
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `activity_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `activity_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`log_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci NOT NULL,
`subject_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`event` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`subject_id` bigint unsigned DEFAULT NULL,
`causer_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`causer_id` bigint unsigned DEFAULT NULL,
`properties` json DEFAULT NULL,
`batch_uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `subject` (`subject_type`,`subject_id`),
KEY `causer` (`causer_type`,`causer_id`),
KEY `activity_log_log_name_index` (`log_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_membership`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_membership` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`required_plan_id` bigint unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_membership_article_id_unique` (`article_id`),
KEY `article_membership_required_plan_id_foreign` (`required_plan_id`),
CONSTRAINT `article_membership_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_membership_required_plan_id_foreign` FOREIGN KEY (`required_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_products`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_products` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`price` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`trial_mode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'chars',
`trial_value` int unsigned NOT NULL DEFAULT '200',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_products_article_id_unique` (`article_id`),
CONSTRAINT `article_products_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_tag` (
`article_id` bigint unsigned NOT NULL,
`tag_id` bigint unsigned NOT NULL,
UNIQUE KEY `article_tag_article_id_tag_id_unique` (`article_id`,`tag_id`),
KEY `article_tag_tag_id_foreign` (`tag_id`),
CONSTRAINT `article_tag_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_tag_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `articles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `articles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`category_id` bigint unsigned NOT NULL,
`user_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`content_format` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'html',
`description` text COLLATE utf8mb4_unicode_ci,
`keywords` text COLLATE utf8mb4_unicode_ci,
`published_at` timestamp NULL DEFAULT NULL,
`views` bigint unsigned NOT NULL DEFAULT '0',
`comments_count` int unsigned NOT NULL DEFAULT '0',
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`stick` tinyint(1) NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`ai_summary` text COLLATE utf8mb4_unicode_ci,
`ai_suggestions` json DEFAULT NULL,
`ai_polished_content` longtext COLLATE utf8mb4_unicode_ci,
`legacy_attachments` json DEFAULT NULL,
`cover_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_source` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_generated_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `articles_slug_unique` (`slug`),
KEY `articles_user_id_foreign` (`user_id`),
KEY `articles_visible_published_at_index` (`visible`,`published_at`),
KEY `articles_category_id_index` (`category_id`),
CONSTRAINT `articles_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
CONSTRAINT `articles_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `attachments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `attachments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned DEFAULT NULL,
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'attachments',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`thumb_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`mime` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size` bigint unsigned NOT NULL DEFAULT '0',
`checksum` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`visibility` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
`legacy_filepath` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`synced_at` timestamp NULL DEFAULT NULL,
`downloads` bigint unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `attachments_article_id_foreign` (`article_id`),
KEY `attachments_legacy_filepath_index` (`legacy_filepath`),
CONSTRAINT `attachments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache_locks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache_locks` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_locks_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `categories` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`intro` text COLLATE utf8mb4_unicode_ci,
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`display_order` int NOT NULL DEFAULT '0',
`articles_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `comments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `comments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`author` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`content` text COLLATE utf8mb4_unicode_ci NOT NULL,
`ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`moderation_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
`published_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `comments_article_id_foreign` (`article_id`),
CONSTRAINT `comments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `entitlements`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `entitlements` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`source_order_id` bigint unsigned DEFAULT NULL,
`granted_at` timestamp NOT NULL,
`revoked_at` timestamp NULL DEFAULT NULL,
`expires_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `entitlements_user_id_product_type_product_id_unique` (`user_id`,`product_type`,`product_id`),
CONSTRAINT `entitlements_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `job_batches` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`total_jobs` int NOT NULL,
`pending_jobs` int NOT NULL,
`failed_jobs` int NOT NULL,
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`options` mediumtext COLLATE utf8mb4_unicode_ci,
`cancelled_at` int DEFAULT NULL,
`created_at` int NOT NULL,
`finished_at` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `links`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `links` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci,
`display_order` int NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `membership_plans`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `membership_plans` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`slug` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`price` decimal(10,2) NOT NULL,
`currency` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`duration_days` int unsigned DEFAULT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`sort_order` int NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`batch` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_roles` (
`role_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `order_items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_items` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_items_order_id_foreign` (`order_id`),
CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `orders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`paid_at` timestamp NULL DEFAULT NULL,
`meta` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `orders_user_id_foreign` (`user_id`),
CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `password_reset_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_reset_tokens` (
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `payment_transactions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `payment_transactions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`external_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`payload` json DEFAULT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `payment_transactions_order_id_foreign` (`order_id`),
CONSTRAINT `payment_transactions_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `permissions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `plugins`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `plugins` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`version` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1.0.0',
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`config` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `plugins_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `role_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `role_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`role_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`),
KEY `role_has_permissions_role_id_foreign` (`role_id`),
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `roles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sessions` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint unsigned DEFAULT NULL,
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_agent` text COLLATE utf8mb4_unicode_ci,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`last_activity` int NOT NULL,
PRIMARY KEY (`id`),
KEY `sessions_user_id_index` (`user_id`),
KEY `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `settings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `settings` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`group` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`locked` tinyint(1) NOT NULL DEFAULT '0',
`payload` json NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `settings_group_name_unique` (`group`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `stylevars`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `stylevars` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` text COLLATE utf8mb4_unicode_ci,
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tags` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`use_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tags_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password_legacy` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_count` int unsigned NOT NULL DEFAULT '0',
`login_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_at` timestamp NULL DEFAULT NULL,
`reg_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_post_at` timestamp NULL DEFAULT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`),
UNIQUE KEY `users_username_unique` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2022_12_14_083707_create_settings_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092526_create_permission_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092528_create_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_092529_add_event_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_092530_add_batch_uuid_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100000_create_blog_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100001_create_larablog_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100002_add_attachments_url_prefix_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100003_drop_trackback_legacy',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100003_remove_trackback_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_100004_add_article_content_format',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_11_100004_add_content_format_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_11_100005_add_article_cover_placeholders',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_11_220000_expand_blog_comment_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_11_230000_create_snippet_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_001000_create_orders_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_12_001001_create_order_items_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_12_001002_create_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_12_001003_create_payment_transactions_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_12_002000_create_article_products_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_08_12_013201_add_article_ai_polished_content',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_08_12_034500_add_expires_at_to_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_08_12_034600_create_membership_plans_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (27,'2026_08_12_034601_create_article_membership_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (28,'2026_08_13_015800_add_category_seo_fields',1);
@@ -0,0 +1,619 @@
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `activity_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `activity_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`log_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`description` text COLLATE utf8mb4_unicode_ci NOT NULL,
`subject_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`event` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`subject_id` bigint unsigned DEFAULT NULL,
`causer_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`causer_id` bigint unsigned DEFAULT NULL,
`properties` json DEFAULT NULL,
`batch_uuid` char(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `subject` (`subject_type`,`subject_id`),
KEY `causer` (`causer_type`,`causer_id`),
KEY `activity_log_log_name_index` (`log_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_membership`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_membership` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`required_plan_id` bigint unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_membership_article_id_unique` (`article_id`),
KEY `article_membership_required_plan_id_foreign` (`required_plan_id`),
CONSTRAINT `article_membership_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_membership_required_plan_id_foreign` FOREIGN KEY (`required_plan_id`) REFERENCES `membership_plans` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_products`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_products` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`price` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`trial_mode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'chars',
`trial_value` int unsigned NOT NULL DEFAULT '200',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_products_article_id_unique` (`article_id`),
CONSTRAINT `article_products_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `article_tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `article_tag` (
`article_id` bigint unsigned NOT NULL,
`tag_id` bigint unsigned NOT NULL,
UNIQUE KEY `article_tag_article_id_tag_id_unique` (`article_id`,`tag_id`),
KEY `article_tag_tag_id_foreign` (`tag_id`),
CONSTRAINT `article_tag_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_tag_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `articles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `articles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`category_id` bigint unsigned NOT NULL,
`user_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`content_format` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'html',
`description` text COLLATE utf8mb4_unicode_ci,
`keywords` text COLLATE utf8mb4_unicode_ci,
`published_at` timestamp NULL DEFAULT NULL,
`views` bigint unsigned NOT NULL DEFAULT '0',
`comments_count` int unsigned NOT NULL DEFAULT '0',
`slug` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`stick` tinyint(1) NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`close_comment` tinyint(1) NOT NULL DEFAULT '0',
`read_password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`ai_summary` text COLLATE utf8mb4_unicode_ci,
`ai_suggestions` json DEFAULT NULL,
`ai_polished_content` longtext COLLATE utf8mb4_unicode_ci,
`legacy_attachments` json DEFAULT NULL,
`cover_disk` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_source` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'none',
`cover_media_id` bigint unsigned DEFAULT NULL,
`cover_generated_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `articles_slug_unique` (`slug`),
KEY `articles_user_id_foreign` (`user_id`),
KEY `articles_visible_published_at_index` (`visible`,`published_at`),
KEY `articles_category_id_index` (`category_id`),
CONSTRAINT `articles_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
CONSTRAINT `articles_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `attachments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `attachments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned DEFAULT NULL,
`disk` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'attachments',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`thumb_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`mime` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size` bigint unsigned NOT NULL DEFAULT '0',
`checksum` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`visibility` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
`legacy_filepath` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`synced_at` timestamp NULL DEFAULT NULL,
`downloads` bigint unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `attachments_article_id_foreign` (`article_id`),
KEY `attachments_legacy_filepath_index` (`legacy_filepath`),
CONSTRAINT `attachments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `cache_locks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `cache_locks` (
`key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`expiration` int NOT NULL,
PRIMARY KEY (`key`),
KEY `cache_locks_expiration_index` (`expiration`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `categories` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`intro` text COLLATE utf8mb4_unicode_ci,
`keywords` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`cover_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`display_order` int NOT NULL DEFAULT '0',
`articles_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `comments`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `comments` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`article_id` bigint unsigned NOT NULL,
`author` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`content` text COLLATE utf8mb4_unicode_ci NOT NULL,
`ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`moderation_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending',
`published_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `comments_article_id_foreign` (`article_id`),
CONSTRAINT `comments_article_id_foreign` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `entitlements`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `entitlements` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`source_order_id` bigint unsigned DEFAULT NULL,
`granted_at` timestamp NOT NULL,
`revoked_at` timestamp NULL DEFAULT NULL,
`expires_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `entitlements_user_id_product_type_product_id_unique` (`user_id`,`product_type`,`product_id`),
CONSTRAINT `entitlements_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `job_batches` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`total_jobs` int NOT NULL,
`pending_jobs` int NOT NULL,
`failed_jobs` int NOT NULL,
`failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`options` mediumtext COLLATE utf8mb4_unicode_ci,
`cancelled_at` int DEFAULT NULL,
`created_at` int NOT NULL,
`finished_at` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `jobs` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`attempts` tinyint unsigned NOT NULL,
`reserved_at` int unsigned DEFAULT NULL,
`available_at` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `links`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `links` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci,
`display_order` int NOT NULL DEFAULT '0',
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `media_folders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `media_folders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`parent_id` bigint unsigned DEFAULT NULL,
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`sort` int NOT NULL DEFAULT '0',
`is_active` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `media_folders_code_unique` (`code`),
KEY `media_folders_parent_id_foreign` (`parent_id`),
CONSTRAINT `media_folders_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `media_folders` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `media_library`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `media_library` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned DEFAULT NULL,
`disk` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'public',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`original_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`extension` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`mime_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`size` bigint unsigned NOT NULL DEFAULT '0',
`type` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'image',
`source` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin',
`folder` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`alt_text` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`custom_properties` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `media_library_path_unique` (`path`),
KEY `media_library_user_id_foreign` (`user_id`),
KEY `media_library_type_index` (`type`),
KEY `media_library_folder_index` (`folder`),
KEY `media_library_source_index` (`source`),
CONSTRAINT `media_library_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `membership_plans`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `membership_plans` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`slug` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`price` decimal(10,2) NOT NULL,
`currency` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`duration_days` int unsigned DEFAULT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`sort_order` int NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `membership_plans_slug_unique` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`batch` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`model_id`,`model_type`),
KEY `model_has_permissions_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `model_has_roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `model_has_roles` (
`role_id` bigint unsigned NOT NULL,
`model_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`model_id` bigint unsigned NOT NULL,
PRIMARY KEY (`role_id`,`model_id`,`model_type`),
KEY `model_has_roles_model_id_model_type_index` (`model_id`,`model_type`),
CONSTRAINT `model_has_roles_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `order_items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order_items` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`product_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`product_id` bigint unsigned NOT NULL,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_items_order_id_foreign` (`order_id`),
CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `orders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint unsigned NOT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`amount` decimal(10,2) NOT NULL,
`currency` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'CNY',
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`paid_at` timestamp NULL DEFAULT NULL,
`meta` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `orders_user_id_foreign` (`user_id`),
CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `password_reset_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_reset_tokens` (
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `payment_transactions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `payment_transactions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`gateway` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`external_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`payload` json DEFAULT NULL,
`status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `payment_transactions_order_id_foreign` (`order_id`),
CONSTRAINT `payment_transactions_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `permissions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `permissions_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `plugins`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `plugins` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`version` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1.0.0',
`enabled` tinyint(1) NOT NULL DEFAULT '0',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`config` json DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `plugins_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `role_has_permissions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `role_has_permissions` (
`permission_id` bigint unsigned NOT NULL,
`role_id` bigint unsigned NOT NULL,
PRIMARY KEY (`permission_id`,`role_id`),
KEY `role_has_permissions_role_id_foreign` (`role_id`),
CONSTRAINT `role_has_permissions_permission_id_foreign` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE,
CONSTRAINT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `roles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `roles` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`guard_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `roles_name_guard_name_unique` (`name`,`guard_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `sessions` (
`id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint unsigned DEFAULT NULL,
`ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_agent` text COLLATE utf8mb4_unicode_ci,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`last_activity` int NOT NULL,
PRIMARY KEY (`id`),
KEY `sessions_user_id_index` (`user_id`),
KEY `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `settings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `settings` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`group` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`locked` tinyint(1) NOT NULL DEFAULT '0',
`payload` json NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `settings_group_name_unique` (`group`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `stylevars`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `stylevars` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`value` text COLLATE utf8mb4_unicode_ci,
`visible` tinyint(1) NOT NULL DEFAULT '1',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tags` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`use_count` int unsigned NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tags_name_unique` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password_legacy` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`url` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_count` int unsigned NOT NULL DEFAULT '0',
`login_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`login_at` timestamp NULL DEFAULT NULL,
`reg_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_post_at` timestamp NULL DEFAULT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`),
UNIQUE KEY `users_username_unique` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (1,'0001_01_01_000000_create_users_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (2,'0001_01_01_000001_create_cache_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (3,'0001_01_01_000002_create_jobs_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (4,'2022_12_14_083707_create_settings_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (5,'2026_08_11_092526_create_permission_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (6,'2026_08_11_092528_create_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (7,'2026_08_11_092529_add_event_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (8,'2026_08_11_092530_add_batch_uuid_column_to_activity_log_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (9,'2026_08_11_100000_create_blog_tables',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (10,'2026_08_11_100001_create_larablog_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (11,'2026_08_11_100002_add_attachments_url_prefix_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (12,'2026_08_11_100003_drop_trackback_legacy',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (13,'2026_08_11_100003_remove_trackback_setting',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (14,'2026_08_11_100004_add_article_content_format',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (15,'2026_08_11_100004_add_content_format_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (16,'2026_08_11_100005_add_article_cover_placeholders',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (17,'2026_08_11_220000_expand_blog_comment_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (18,'2026_08_11_230000_create_snippet_settings',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (19,'2026_08_12_001000_create_orders_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (20,'2026_08_12_001001_create_order_items_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (21,'2026_08_12_001002_create_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (22,'2026_08_12_001003_create_payment_transactions_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (23,'2026_08_12_002000_create_article_products_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (24,'2026_08_12_013201_add_article_ai_polished_content',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (25,'2026_08_12_034500_add_expires_at_to_entitlements_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (26,'2026_08_12_034600_create_membership_plans_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (27,'2026_08_12_034601_create_article_membership_table',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (28,'2026_08_13_015800_add_category_seo_fields',1);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (29,'2026_08_15_000001_create_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (30,'2026_08_15_000002_create_media_folders_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (31,'2026_08_15_000003_add_soft_deletes_to_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (32,'2026_08_15_000004_add_custom_properties_to_media_library_table',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (33,'2026_09_16_000001_add_cover_media_id_to_articles',2);
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (34,'2026_09_16_000002_seed_default_media_folders',2);
+32
View File
@@ -0,0 +1,32 @@
# Media library (xpier)
New uploads, covers, and in-body images/files use `xpier/filament-media-library`. The sablog `attachments` table and `attach:` protocol stay for import/legacy content.
## Markdown body (do not paste CDN URLs)
Store a **library id**, never a bucket/CDN URL. Rendering expands it to the current public URL.
| Format | Image | File |
| -------- | ------------------ | ----------------------- |
| Markdown | `![alt](media:12)` | `[notes.pdf](media:12)` |
| HTML | `[media=12]` | `[media=12]` |
Admin flow: pick a file, then **Insert into body**. Markdown keeps `media:ID`; HTML keeps `[media=ID]`.
Legacy tokens stay valid: `![alt](attach:ID)`, `[attach=ID]`, `/attachment.php?id=ID`.
## Admin
- Library: `/admin/media-library`
- Folders: `/admin/media-folders` (defaults: `general`, `articles`, `banners`)
- Cover: `cover_media_id` via `MediaPicker` (`coverMedia` relation). `cover_path` remains for older auto-covers.
## Config
```env
MEDIA_DISK=public
MEDIA_DIRECTORY=media
MEDIA_VISIBILITY=public
```
Run `php artisan storage:link` when using the `public` disk locally.
+15 -14
View File
@@ -4,14 +4,14 @@
## 环境
| 项 | 要求 |
|---|---|
| PHP | **8.2+**(`composer.json`) |
| 扩展 | `mbstring`、`openssl`、`pdo`、`tokenizer`、`xml`、`ctype`、`json`、`fileinfo`、`gd`(封面/图片)、`zip` |
| Composer | 2.x |
| 数据库 | 开发可用 SQLite;也可用 MySQL / MariaDB |
| 可选 | Redis(队列/缓存);Node.js 仅在改前端资源时需要 |
| 可选 | `pcntl` + `posix`(本机跑 `workerman:ai`;没有就用 `queue:ai`) |
| 项 | 要求 |
| -------- | ------------------------------------------------------------------------------------------------------- |
| PHP | **8.2+**(`composer.json`) |
| 扩展 | `mbstring`、`openssl`、`pdo`、`tokenizer`、`xml`、`ctype`、`json`、`fileinfo`、`gd`(封面/图片)、`zip` |
| Composer | 2.x |
| 数据库 | 开发可用 SQLite;也可用 MySQL / MariaDB |
| 可选 | Redis(队列/缓存);Node.js 仅在改前端资源时需要 |
| 可选 | `pcntl` + `posix`(本机跑 `workerman:ai`;没有就用 `queue:ai`) |
macOS 可用 Laravel Herd / Valet,指向仓库的 `public/`。也可用 `php artisan serve`。
@@ -41,6 +41,7 @@ DB_CONNECTION=sqlite
# 开发附件落本地,不必配 S3
ATTACHMENTS_DRIVER=local
MEDIA_DISK=public
QUEUE_CONNECTION=database
CACHE_STORE=database
@@ -107,12 +108,12 @@ php artisan queue:ai
## 5. 入口与账号
| 入口 | 地址 |
|---|---|
| 前台 | `/`、`/show-1.shtml`、`/login.shtml` |
| 后台 | `/admin` |
| API | `/api/v1/meta`、`/api/v1/articles` |
| OpenAPI | `/docs/api/openapi.yaml` |
| 入口 | 地址 |
| ------- | ------------------------------------ |
| 前台 | `/`、`/show-1.shtml`、`/login.shtml` |
| 后台 | `/admin` |
| API | `/api/v1/meta`、`/api/v1/articles` |
| OpenAPI | `/docs/api/openapi.yaml` |
演示管理员(`DemoBlogSeeder`):
+57 -37
View File
@@ -1,16 +1,19 @@
# LaraBlog 平台一期
## 状态
- 状态:已定稿
- 创建:2026-08-11 17:14
- 最近更新:2026-08-11 17:53
## 背景
SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移目标不是「功能克隆」,而是:**保留有价值的内容、SEO 权重与核心写作/阅读体验**,用当代博客实践替换过时、产垃圾或维护成本不合理的设计。
参考源:https://github.com/neatstudio/sablog-archive
## 现代化取舍原则
1. **内容与 SEO 优先**:文章/分类/标签 ID 与关键旧 URL 保留,避免权重归零
2. **不迁垃圾协议与死技术**:Trackback/Pingback、WAP、旧 searchindex、旧 PHP Admin、Feedsky 等一律不实现
3. **用当代能力替换**:AI 评论审核代替巨型敏感词表为主力;对象存储代替本地附件目录;Workerman 承载 LLM;Filament 后台
@@ -18,6 +21,7 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
5. **旧入口若被爬虫打到**:对明确废弃路径返回 **410 Gone**(不实现业务)
## 目标
- 可运行的现代博客:前台阅读/评论/搜索/归档 + Filament 5 后台
- 严格兼容 sablog **内容向**伪静态与关键 query URL;导入后文章/分类 ID 一致
- 多主题、插件框架、SEO/GEO、S3 兼容附件、Workerman AI 运行时
@@ -26,6 +30,7 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
## 范围
### In scope
1. Laravel 12 + Livewire 4 + Filament 5 + Spatie(permission、settings、feed、sitemap、activitylog)
2. 博客核心:文章、分类、标签、评论、附件、友情链接、用户/角色、站点片段(stylevars)
3. Legacy **内容 URL** + 新 canonical(见 URL 矩阵)
@@ -40,6 +45,7 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
12. **正文格式双轨**:`content_format = html|markdown`;站点默认新作为 Markdown;导入默认保留 HTML;可选 HTML→Markdown 转换开关
### Out of scope(明确不迁移 / 不复刻)
- **Trackback / Pingback**(含表、后台、前台、开启开关);`/tburl.php`、`/trackback.php` 仅 410
- WAP、旧 `searchindex` 搜索缓存表、自建 sessions 表、Feedsky/旧 JS 挂件
- 旧 `/admin/*` PHP 后台原样兼容(新后台 Filament `/admin`;旧路径 410)
@@ -49,10 +55,12 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
- 以巨型政治/广告敏感词表为唯一防垃圾手段(可保留极简词表作预过滤,主力为 AI + rate limit)
### 延期(二期+,本期只预留)
**自动配图 / 由正文生成封面**(老 sablog 无现代「特色图」概念):
- 能力意向:
1. **自动配图**:从正文首图 / 附件图 / OG 候选中选封面
2. **生成配图**:用标题+摘要(或 LLM 提示)调用文生图 / 模板渲染,产出封面,写入对象存储
1. **自动配图**:从正文首图 / 附件图 / OG 候选中选封面
2. **生成配图**:用标题+摘要(或 LLM 提示)调用文生图 / 模板渲染,产出封面,写入对象存储
- 执行方式:Workerman / 队列 Job + 可重跑 Artisan 脚本,**批量慢生成**,不堵发布主路径
- 本期只做:表字段与状态枚举预留、`GenerateArticleCoverJob` 空壳、SPEC/清单占位;**不做**真实选图/生图对接
- 封面文件同样只存对象存储,规则同附件
@@ -60,31 +68,34 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
## 方案要点
### 技术栈
| 层 | 选型 |
|---|---|
| 框架 | Laravel 12、PHP 8.2+、Livewire 4 |
| 后台 | Filament 5 |
| Spatie | permission、settings、feed、sitemap、activitylog |
| 附件 | 自建 `attachments` + S3 disk(不用 medialibrary) |
| AI | Workerman + Redis;`LlmProvider`(OpenAI 兼容 + Stub) |
| 层 | 选型 |
| --------- | --------------------------------------------------------- |
| 框架 | Laravel 12、PHP 8.2+、Livewire 4 |
| 后台 | Filament 5 |
| Spatie | permission、settings、feed、sitemap、activitylog |
| 附件 | 自建 `attachments` + S3 disk(不用 medialibrary) |
| AI | Workerman + Redis;`LlmProvider`(OpenAI 兼容 + Stub) |
| 净化 / 图 | mews/purifier;Intervention Image(临时处理后退对象存储) |
### 数据模型
- `articles`:id、category_id、user_id、title、content、**content_format(`html`|`markdown`)**、description、keywords、slug(nullable)、published_at、views、comments_count、stick、visible、close_comment、read_password(nullable,兼容旧私密文)、ai_summary、ai_suggestions、legacy_attachments、**cover_***(二期配图预留,见下)
- 封面预留字段:`cover_disk`、`cover_path`、`cover_source`(`none`|`manual`|`attachment`|`content_image`|`generated`)、`cover_status`(`none`|`pending`|`ready`|`failed`)、`cover_generated_at`
- **无** `trackbacks` 表;**无** `close_trackback` / `trackbacks_count`
### 正文格式(HTML / Markdown)
- **原则**:旧 sablog 正文是 HTML;**新写作默认 Markdown**(未来主路径)
- **文章级开关**:`articles.content_format`;后台编辑器按格式切换(Markdown 文本域 / HTML 富文本)
- **导入双模式**(两份能力,同一命令开关):
1. **`raw`(默认)**:正文保持 HTML,`content_format=html`,保留 sablog `[attach=id]`
2. **`markdown`**:HTML→Markdown,`content_format=markdown`;因 `[]` 是 Markdown 保留字符,**导入时把 `[attach=id]` 改写成 Markdown 安全引用**,不把对象存储 URL 写进库
1. **`raw`(默认)**:正文保持 HTML,`content_format=html`,保留 sablog `[attach=id]`
2. **`markdown`**:HTML→Markdown,`content_format=markdown`;因 `[]` 是 Markdown 保留字符,**导入时把 `[attach=id]` 改写成 Markdown 安全引用**,不把对象存储 URL 写进库
- **附件引用策略(已定)**:
| 场景 | 数据库保存 | 预览/前台输出 |
|---|---|---|
| raw/HTML | `[attach=123]`(原样) | 渲染期解析为 `<img>`/`<a>`,href/src 用稳定入口 `/attachment.php?id=123`(再 302 到 S3) |
| markdown | 图片 `![name](attach:123)`;文件 `[name](attach:123)` | CommonMark 后把 `attach:123` 换成 `/attachment.php?id=123` |
| 场景 | 数据库保存 | 预览/前台输出 |
| -------- | -------------------------------------------------------- | --------------------------------------------------------------------------------- |
| raw/HTML | `[attach=123]`(原样);新媒体 `[media=123]` | 渲染期解析为 `<img>`/`<a>`;老附件走 `/attachment.php?id=123`,新媒体走媒体库 URL |
| markdown | 老附件 `![name](attach:123)`;新媒体 `![alt](media:123)` | CommonMark 后把 `attach:` / `media:` 换成当前公开地址 |
- **不在导入时写死 S3/CDN 路径**:避免换桶/换域名后正文失效;**平时 DB 只存引用,输出时转换**
- **站点设置**:`default_content_format=markdown`;导入模式由 CLI `--mode=raw|markdown` 决定(可覆盖 settings)
- **渲染**:`ContentRenderer::toHtml()` = 格式转换 + attach 水合 + Purifier
@@ -92,20 +103,22 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
- Settings:站点、SEO、active_theme、LLM、存储、正文格式默认/导入策略(**无** trackback_enabled)
#### 导入映射(摘要)
| 源 | 目标 | 规则 |
|---|---|---|
| articles 核心字段 | articles.* | 保留 id/cid/uid/views/close_comment/read_password 等 |
| articles.content | content + content_format | 默认 `html`;若开启转换则转为 Markdown 且 format=`markdown` |
| articles.trackbacks / closetrackback | — | **忽略** |
| articles.attachments serialize | legacy_attachments + attachments 表 | 文件上云 |
| comments.visible | moderation_status | 1→approved,0→pending |
| tags.aids | article_tag | 拆 pivot |
| users.password | password_legacy | 无盐 MD5 → 登录升级 bcrypt |
| attachments.* | attachments.* | 逐条上云,保留 downloads |
| links / stylevars / settings | 对应表/Settings | 直通或键映射 |
| sablog_trackbacks / trackbacklog / searchindex / sessions | — | **跳过并计入报告** |
| 源 | 目标 | 规则 |
| --------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------- |
| articles 核心字段 | articles.* | 保留 id/cid/uid/views/close_comment/read_password 等 |
| articles.content | content + content_format | 默认 `html`;若开启转换则转为 Markdown 且 format=`markdown` |
| articles.trackbacks / closetrackback | — | **忽略** |
| articles.attachments serialize | legacy_attachments + attachments 表 | 文件上云 |
| comments.visible | moderation_status | 1→approved,0→pending |
| tags.aids | article_tag | 拆 pivot |
| users.password | password_legacy | 无盐 MD5 → 登录升级 bcrypt |
| attachments.* | attachments.* | 逐条上云,保留 downloads |
| links / stylevars / settings | 对应表/Settings | 直通或键映射 |
| sablog_trackbacks / trackbacklog / searchindex / sessions | — | **跳过并计入报告** |
### 附件与对象存储
- 一律 S3 兼容;临时文件用完即删
- `/attachment.php?id=`、`/attachments/{path}` → 去重计数后 302
- 正文 `[attach=id]` 解析为对象存储 URL
@@ -113,22 +126,27 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
### URL 矩阵
#### Legacy 内容向(必须业务可达)
`/`、`/index.php?action=…`、`/show-{id}[-{page}].shtml`、`/category-{cid}[-{page}].shtml`、`/archives…`、`/tagslist…`、`/index.php?action=tags&item=`、`/comments…`、`/search…`、`/links.shtml`、`/reg.shtml`、`/login.shtml`、`/rss.xml[?cid=]`、`/sitemap.xml`、`/attachment.php`、`/attachments/{path}`、`/post.php`(登录/注册/评论/搜索)
#### 新 canonical
- `/tag/{name}`(自旧 tags query 301)
- `/llms.txt`
- 可选 `/posts/{slug}`(不取代 show-id)
#### 明确废弃(410,无业务)
- `/tburl.php`、`/trackback.php`
- 旧 `/admin/*.php`(非 Filament 面板)
- WAP 相关路径(若出现)
### 主题 / 插件 / Workerman / 导入
同前:主题回退、插件生命周期、AI 双队列、`sablog:import` 幂等与「跳过废弃表」报告;不删源数据。
## 验收标准
- [x] 内容向 Legacy URL 可达;文章/分类 ID 与源一致(`upsertWithId` + fixture 回归)
- [x] **无** Trackback 功能/表/设置;tb 入口 410
- [x] 导入报告含「跳过 trackbacks/searchindex/sessions」计数
@@ -140,17 +158,19 @@ SaBlog-X 1.6(约二十年前设计)需迁移到现代 Laravel 栈。迁移
- [x] 导入幂等且不改源
## 开放问题
- [x] 现代化:剔除 Trackback 等过时能力 — 已定
- [ ] 部署环境对象存储凭据;开发可用 MinIO / fake
## 变更记录
| 时间 | 原因 | 变更 |
|------|------|------|
| 2026-08-11 17:14 | 初稿 | 创建 SPEC |
| 2026-08-11 17:20 | qodercli A1–A8 | URL/附件/映射等 |
| 2026-08-11 17:25 | qodercli 映射缺口 | 补字段 |
| 2026-08-11 17:26 | 三轮无阻塞;用户确认 | 定稿 |
| 2026-08-11 17:35 | 用户:勿迁 trackback 等过时设计 | 增加现代化取舍;删除 Trackback 全链路;废弃入口 410;导入跳过垃圾表 |
| 2026-08-11 17:42 | 用户:HTML 旧文 / Markdown 未来 | 增加 content_format 双轨、站点/导入开关、可选 HTML→MD 转换 |
| 2026-08-11 17:50 | 用户:导入双份 + attach 与 MD 保留字 | 明确 DB 存引用、渲染期解析;`--mode=raw|markdown`;attach: 协议 |
| 2026-08-11 17:53 | 用户:自动配图/生成配图可后推 | 延期章节 + cover 字段/Job 空壳预留 |
| 时间 | 原因 | 变更 |
| ---------------- | ------------------------------------ | ------------------------------------------------------------------- |
| 2026-08-11 17:14 | 初稿 | 创建 SPEC |
| 2026-08-11 17:20 | qodercli A1–A8 | URL/附件/映射等 |
| 2026-08-11 17:25 | qodercli 映射缺口 | 补字段 |
| 2026-08-11 17:26 | 三轮无阻塞;用户确认 | 定稿 |
| 2026-08-11 17:35 | 用户:勿迁 trackback 等过时设计 | 增加现代化取舍;删除 Trackback 全链路;废弃入口 410;导入跳过垃圾表 |
| 2026-08-11 17:42 | 用户:HTML 旧文 / Markdown 未来 | 增加 content_format 双轨、站点/导入开关、可选 HTML→MD 转换 |
| 2026-08-11 17:50 | 用户:导入双份 + attach 与 MD 保留字 | 明确 DB 存引用、渲染期解析;`--mode=raw | markdown`;attach: 协议 |
| 2026-08-11 17:53 | 用户:自动配图/生成配图可后推 | 延期章节 + cover 字段/Job 空壳预留 |
+10 -1
View File
@@ -66,6 +66,7 @@ return [
'confirm' => 'Confirm',
'mark_paid' => 'Mark as paid',
'close' => 'Close',
'insert_media_into_content' => 'Insert into body',
],
'fields' => [
@@ -104,6 +105,8 @@ return [
'ai_polished_content' => 'AI polished draft',
'ai_suggestions' => 'AI writing tips',
'cover_path' => 'Cover path / URL',
'cover_media' => 'Cover (media library)',
'insert_media' => 'Insert from media library',
'cover_source' => 'Cover source',
'cover_status' => 'Cover status',
'display_order' => 'Order',
@@ -164,7 +167,8 @@ return [
],
'helpers' => [
'article_content' => 'For Markdown use ![alt](attach:ID) or [name](attach:ID); HTML may keep [attach=ID]. Resolved at render time — do not paste object-storage URLs into the body.',
'article_content' => 'For new images use ![alt](media:ID); legacy attachments stay ![alt](attach:ID). HTML uses [media=ID] / [attach=ID]. Do not paste CDN URLs into the body.',
'insert_media' => 'Pick a file, then click “Insert into body”. Markdown stores a media:ID reference resolved at render time.',
'attachment_upload' => 'Files go to object storage (or the local attachments disk), not long-term app storage.',
'attachments_prefix' => 'Used for /attachments/{path} resolution; disk is controlled by attachment driver env vars.',
'analytics_extra' => 'Can coexist with plugin scripts.',
@@ -416,4 +420,9 @@ return [
'description' => 'Newsreader + amber accent; second theme demo.',
],
],
'notifications' => [
'pick_media_first' => 'Pick a file from the media library first',
'media_inserted' => 'Media reference inserted into the body',
],
];
+10 -1
View File
@@ -66,6 +66,7 @@ return [
'confirm' => '确认',
'mark_paid' => '标记已支付',
'close' => '关闭',
'insert_media_into_content' => '插入到正文',
],
'fields' => [
@@ -104,6 +105,8 @@ return [
'ai_polished_content' => 'AI 润色稿',
'ai_suggestions' => 'AI 写作建议',
'cover_path' => '封面路径/URL',
'cover_media' => '封面图(媒体库)',
'insert_media' => '从媒体库插入正文',
'cover_source' => '封面来源',
'cover_status' => '封面状态',
'display_order' => '排序',
@@ -164,7 +167,8 @@ return [
],
'helpers' => [
'article_content' => 'Markdown 附件请用 ![alt](attach:ID) 或 [name](attach:ID);HTML 可保留 [attach=ID]。输出时再解析为附件地址,勿把对象存储直链写进正文。',
'article_content' => 'Markdown 新图请用 ![说明](media:ID),旧附件仍可用 ![alt](attach:ID)。HTML 用 [media=ID] / [attach=ID]。不要把 CDN 地址写进正文。',
'insert_media' => '选好媒体后点「插入到正文」。Markdown 写入 media:ID 引用,前台渲染时再换成真实地址。',
'attachment_upload' => '文件写入对象存储(或本地附件驱动),不在应用侧长期落盘。',
'attachments_prefix' => '对应 /attachments/{path} 解析;磁盘由环境变量中的附件驱动配置控制。',
'analytics_extra' => '可与插件脚本并存。',
@@ -416,4 +420,9 @@ return [
'description' => 'Newsreader + 琥珀强调,演示第二套皮肤切换。',
],
],
'notifications' => [
'pick_media_first' => '请先从媒体库选择文件',
'media_inserted' => '已写入正文引用',
],
];
+2561
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -36,5 +36,6 @@
<env name="NIGHTWATCH_ENABLED" value="false"/>
<env name="ATTACHMENTS_DRIVER" value="local"/>
<env name="AI_PROVIDER" value="stub"/>
<env name="MEDIA_DISK" value="public"/>
</php>
</phpunit>
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-SP7Z6XGK.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-DR6K5BQD.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-A2J6H3EX.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-ZABHMKQG.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-VWEHJHBA.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-ZIT2UBIY.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-DIHYUR35.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
(()=>{var o=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var a=function(i,t,n){let e=i;if(t.startsWith("/")&&(n=!0,t=t.slice(1)),n)return t;for(;t.startsWith("../");)e=e.includes(".")?e.slice(0,e.lastIndexOf(".")):null,t=t.slice(3);return["",null,void 0].includes(e)?t:["",null,void 0].includes(t)?e:`${e}.${t}`},d=i=>{let t=Alpine.findClosest(i,n=>n.__livewire);if(!t)throw"Could not find Livewire component in DOM tree.";return t.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i,schemaKey:t})=>({handleFormValidationError(n){n.detail.livewireId===i&&this.$nextTick(()=>{let e=this.$el.querySelector("[data-validation-error]");if(!e)return;let r=e;for(;r;)r.dispatchEvent(new CustomEvent("expand")),r=r.parentNode;setTimeout(()=>e.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},handleClientSideStateReset(n){n.detail.livewireId!==i||n.detail.schemaKey!==t||this.$nextTick(()=>{let e=this.$el.querySelectorAll("[autofocus]");for(let r of e)if(r.offsetParent!==null&&(r.focus(),document.activeElement===r))break})},isStateChanged(n,e){if(n===void 0)return!1;try{return JSON.stringify(n)!==JSON.stringify(e)}catch{return n!==e}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:t,$wire:n})=>({$statePath:i,$get:(e,r)=>n.$get(a(t,e,r)),$set:(e,r,s,l=!1)=>n.$set(a(t,e,s),r,l),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",o),Livewire.interceptMessage(({message:i,onSuccess:t})=>{t(({payload:n})=>{n.effects?.dispatches?.forEach(e=>{if(!e.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${e.params.awaitSchemaComponent}"]`)).filter(s=>d(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${e.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${i.component.id}-${e.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(e.name,{detail:e.params}))},{once:!0})}})})})});})();
(()=>{var a=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var l=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},c=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire},o=!1;document.addEventListener("invalid",i=>{let e=i.target;if(!e.closest("[data-field-wrapper]")||e.offsetParent!==null&&getComputedStyle(e).visibility!=="hidden"||(i.preventDefault(),o))return;o=!0;let n=e;for(;n;)n.dispatchEvent(new CustomEvent("expand")),n=n.parentNode;requestAnimationFrame(()=>requestAnimationFrame(()=>{e.form?.reportValidity(),setTimeout(()=>o=!1,100)}))},!0);document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i,schemaKey:e,isLoadingDeferred:n=!1})=>({intersectionObserver:null,isLoadingDeferredSchema:!1,init(){n&&(this.intersectionObserver=new IntersectionObserver(async t=>{if(!(!t[0]?.isIntersecting||this.isLoadingDeferredSchema)){this.isLoadingDeferredSchema=!0;try{await this.$wire.loadDeferredSchema(e),this.intersectionObserver?.disconnect()}catch{}finally{this.isLoadingDeferredSchema=!1}}}),this.intersectionObserver.observe(this.$el))},destroy(){this.intersectionObserver?.disconnect()},handleFormValidationError(t){t.detail.livewireId===i&&this.$nextTick(()=>{let r=this.$el.querySelector("[data-validation-error]");if(!r)return;let s=r;for(;s;)s.dispatchEvent(new CustomEvent("expand")),s=s.parentNode;setTimeout(()=>r.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},handleClientSideStateReset(t){t.detail.livewireId!==i||t.detail.schemaKey!==e||this.$nextTick(()=>{let r=this.$el.querySelectorAll("[autofocus]");for(let s of r)if(s.offsetParent!==null&&(s.focus(),document.activeElement===s))break})},isStateChanged(t,r){if(t===void 0)return!1;try{return JSON.stringify(t)!==JSON.stringify(r)}catch{return t!==r}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(l(e,t,r)),$set:(t,r,s,d=!1)=>n.$set(l(e,t,s),r,d),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",a),Livewire.interceptMessage(({message:i,onSuccess:e})=>{e(({payload:n})=>{n.effects?.dispatches?.forEach(t=>{if(!t.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${t.params.awaitSchemaComponent}"]`)).filter(s=>c(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${t.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${i.component.id}-${t.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(t.name,{detail:t.params}))},{once:!0})}})})})});})();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+143
View File
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Domain\Blog\ContentFormat;
use App\Domain\Blog\ContentRenderer;
use App\Domain\Media\ArticleCoverService;
use App\Models\Article;
use App\Models\Category;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
class MarkdownMediaLibraryTest extends TestCase
{
use RefreshDatabase;
public function test_markdown_body_renders_media_protocol_to_image_without_storing_url(): void
{
$media = $this->createLibraryImage(12, 'Hero shot');
$article = $this->makeArticle([
'content' => "Intro\n\n![Hero shot](media:{$media->id})\n",
'content_format' => ContentFormat::MARKDOWN,
]);
$html = app(ContentRenderer::class)->toHtml(
(string) $article->content,
ContentFormat::MARKDOWN,
$article->id,
);
$this->assertStringContainsString((string) $media->url, $html);
$this->assertStringContainsString('<img', $html);
$this->assertStringNotContainsString('media:'.$media->id, $html);
$this->assertStringContainsString("![Hero shot](media:{$media->id})", (string) $article->fresh()->content);
}
public function test_html_body_hydrates_media_shortcode(): void
{
$media = $this->createLibraryImage(21, 'Inline');
$html = app(ContentRenderer::class)->toHtml(
'<p>see [media='.$media->id.']</p>',
ContentFormat::HTML,
);
$this->assertStringContainsString((string) $media->url, $html);
$this->assertStringNotContainsString('[media='.$media->id.']', $html);
}
public function test_article_page_shows_library_image_and_cover(): void
{
$media = $this->createLibraryImage(33, 'Cover art');
$article = $this->makeArticle([
'content' => "![Cover art](media:{$media->id})",
'content_format' => ContentFormat::MARKDOWN,
'cover_media_id' => $media->id,
]);
$this->get('/show-'.$article->id.'.shtml')
->assertOk()
->assertSee((string) $media->url, false)
->assertDontSee('media:'.$media->id, false);
$article->refresh();
$this->assertTrue($article->hasCover());
$this->assertSame((string) $media->url, $article->coverUrl());
}
public function test_auto_cover_prefers_media_protocol_in_markdown(): void
{
$media = $this->createLibraryImage(44, 'First image');
$article = $this->makeArticle([
'content' => "Hello\n\n![First image](media:{$media->id})\n",
'content_format' => ContentFormat::MARKDOWN,
]);
app(ArticleCoverService::class)->apply($article, 'from_content');
$article->refresh();
$this->assertTrue($article->hasCover());
$this->assertSame(ArticleCoverService::SOURCE_CONTENT_IMAGE, $article->cover_source);
$this->assertSame((string) $media->url, $article->cover_path);
}
public function test_admin_media_library_pages_load(): void
{
Role::findOrCreate('admin');
$admin = User::factory()->create([
'email' => 'admin-media@larablog.test',
]);
$admin->assignRole('admin');
$this->actingAs($admin)->get('/admin/media-library')->assertOk();
$this->actingAs($admin)->get('/admin/media-folders')->assertOk();
$this->actingAs($admin)->get('/admin/articles/create')
->assertOk()
->assertSee('media:ID', false);
}
/**
* @param array<string, mixed> $overrides
*/
protected function makeArticle(array $overrides = []): Article
{
$user = User::factory()->create();
$category = Category::query()->create(['name' => 'Media', 'display_order' => 0]);
return Article::query()->create(array_merge([
'category_id' => $category->id,
'user_id' => $user->id,
'title' => 'Media post',
'content' => 'Body',
'content_format' => ContentFormat::MARKDOWN,
'published_at' => now()->subMinute(),
'visible' => true,
], $overrides));
}
private function createLibraryImage(int $id, string $alt): MediaLibrary
{
$media = new MediaLibrary;
$media->forceFill([
'id' => $id,
'disk' => 'public',
'path' => 'media/library/articles/image/test-'.$id.'.jpg',
'original_name' => 'test-'.$id.'.jpg',
'extension' => 'jpg',
'mime_type' => 'image/jpeg',
'size' => 128,
'type' => MediaLibrary::TYPE_IMAGE,
'source' => MediaLibrary::SOURCE_ADMIN,
'folder' => 'articles',
'alt_text' => $alt,
]);
$media->save();
return $media->fresh();
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Domain\Media\MarkdownMediaEmbed;
use Tests\TestCase;
use Xpier\FilamentMediaLibrary\Models\MediaLibrary;
class MarkdownMediaEmbedTest extends TestCase
{
public function test_markdown_image_snippet_uses_media_protocol_not_url(): void
{
$media = new MediaLibrary([
'alt_text' => 'Hero shot',
'type' => MediaLibrary::TYPE_IMAGE,
'mime_type' => 'image/jpeg',
'original_name' => 'hero.jpg',
'path' => 'media/library/articles/hero.jpg',
]);
$media->id = 12;
$snippet = app(MarkdownMediaEmbed::class)->snippet($media, 'markdown');
$this->assertSame('![Hero shot](media:12)', $snippet);
$this->assertStringNotContainsString('http', $snippet);
$this->assertStringNotContainsString('/storage/', $snippet);
}
public function test_markdown_file_snippet_is_a_link_reference(): void
{
$media = new MediaLibrary([
'type' => MediaLibrary::TYPE_FILE,
'mime_type' => 'application/pdf',
'original_name' => 'notes.pdf',
'path' => 'media/library/articles/notes.pdf',
]);
$media->id = 8;
$this->assertSame('[notes.pdf](media:8)', app(MarkdownMediaEmbed::class)->snippet($media, 'markdown'));
}
public function test_html_snippet_uses_shortcode(): void
{
$media = new MediaLibrary([
'type' => MediaLibrary::TYPE_IMAGE,
'mime_type' => 'image/png',
'original_name' => 'pic.png',
'path' => 'pic.png',
]);
$media->id = 3;
$this->assertSame('[media=3]', app(MarkdownMediaEmbed::class)->snippet($media, 'html'));
}
}