feat(cms): sync site assets, revalidate webhook, and document download naming

This commit is contained in:
2026-02-10 23:38:31 +08:00
parent c4969cd4d2
commit b6e18a83ec
27 changed files with 1019 additions and 26 deletions

View File

@@ -8,6 +8,8 @@ use App\Models\ArticleAttachment;
use App\Models\ArticleCategory;
use App\Models\ArticleTag;
use App\Models\AuditLog;
use App\Services\SiteAssetSyncService;
use App\Services\SiteRevalidationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@@ -117,7 +119,15 @@ class ArticleController extends Controller
];
if ($request->hasFile('featured_image')) {
$articleData['featured_image_path'] = $request->file('featured_image')->store('articles/images', 'public');
$storagePath = $request->file('featured_image')->store('articles/images', 'public');
$nextPublicPath = SiteAssetSyncService::syncPublicDiskFileToNext($storagePath);
if ($nextPublicPath) {
$articleData['featured_image_path'] = $nextPublicPath;
$articleData['featured_image_storage_path'] = $storagePath;
} else {
$articleData['featured_image_path'] = $storagePath;
}
}
$article = Article::create($articleData);
@@ -148,6 +158,10 @@ class ArticleController extends Controller
'ip_address' => request()->ip(),
]);
if ($validated['save_action'] === 'publish') {
SiteRevalidationService::revalidateArticle($article->slug);
}
$message = $validated['save_action'] === 'publish' ? '文章已成功發布' : '文章已儲存為草稿';
return redirect()
@@ -227,13 +241,35 @@ class ArticleController extends Controller
}
if ($request->hasFile('featured_image')) {
if ($article->featured_image_path) {
// Remove old copies (best-effort). If we synced to Next.js, the DB path is relative (uploads/...).
if ($article->featured_image_storage_path) {
Storage::disk('public')->delete($article->featured_image_storage_path);
} elseif ($article->featured_image_path && str_starts_with($article->featured_image_path, 'articles/')) {
Storage::disk('public')->delete($article->featured_image_path);
}
$updateData['featured_image_path'] = $request->file('featured_image')->store('articles/images', 'public');
SiteAssetSyncService::deleteNextPublicFile($article->featured_image_path);
$storagePath = $request->file('featured_image')->store('articles/images', 'public');
$nextPublicPath = SiteAssetSyncService::syncPublicDiskFileToNext($storagePath);
if ($nextPublicPath) {
$updateData['featured_image_path'] = $nextPublicPath;
$updateData['featured_image_storage_path'] = $storagePath;
} else {
$updateData['featured_image_path'] = $storagePath;
$updateData['featured_image_storage_path'] = null;
}
} elseif ($request->boolean('remove_featured_image') && $article->featured_image_path) {
Storage::disk('public')->delete($article->featured_image_path);
if ($article->featured_image_storage_path) {
Storage::disk('public')->delete($article->featured_image_storage_path);
} elseif (str_starts_with($article->featured_image_path, 'articles/')) {
Storage::disk('public')->delete($article->featured_image_path);
}
SiteAssetSyncService::deleteNextPublicFile($article->featured_image_path);
$updateData['featured_image_path'] = null;
$updateData['featured_image_storage_path'] = null;
}
$article->update($updateData);
@@ -264,6 +300,10 @@ class ArticleController extends Controller
'ip_address' => request()->ip(),
]);
if ($article->isPublished()) {
SiteRevalidationService::revalidateArticle($article->slug);
}
return redirect()
->route('admin.articles.show', $article)
->with('status', '文章已成功更新');
@@ -276,6 +316,8 @@ class ArticleController extends Controller
}
$title = $article->title;
$slug = $article->slug;
$wasPublished = $article->isPublished();
$article->delete();
AuditLog::create([
@@ -285,6 +327,10 @@ class ArticleController extends Controller
'ip_address' => request()->ip(),
]);
if ($wasPublished) {
SiteRevalidationService::revalidateArticle($slug);
}
return redirect()
->route('admin.articles.index')
->with('status', '文章已成功刪除');
@@ -313,6 +359,8 @@ class ArticleController extends Controller
'ip_address' => request()->ip(),
]);
SiteRevalidationService::revalidateArticle($article->slug);
return back()->with('status', '文章已成功發布');
}
@@ -339,6 +387,8 @@ class ArticleController extends Controller
'ip_address' => request()->ip(),
]);
SiteRevalidationService::revalidateArticle($article->slug);
return back()->with('status', '文章已成功歸檔');
}

View File

@@ -7,6 +7,7 @@ use App\Models\AuditLog;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Models\DocumentVersion;
use App\Support\DownloadFile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
@@ -238,9 +239,10 @@ class DocumentController extends Controller
// Log access
$document->logAccess('download', auth()->user());
return Storage::disk('private')->download(
$version->file_path,
$version->original_filename
return DownloadFile::fromDisk(
disk: 'private',
path: $version->file_path,
downloadName: $version->original_filename
);
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLog;
use App\Models\Page;
use App\Services\SiteRevalidationService;
use Illuminate\Http\Request;
class PageController extends Controller
@@ -72,6 +73,10 @@ class PageController extends Controller
'ip_address' => request()->ip(),
]);
if ($validated['status'] === 'published') {
SiteRevalidationService::revalidatePage($page->slug);
}
return redirect()
->route('admin.pages.show', $page)
->with('status', '頁面已成功建立');
@@ -135,6 +140,10 @@ class PageController extends Controller
'ip_address' => request()->ip(),
]);
if ($page->isPublished()) {
SiteRevalidationService::revalidatePage($page->slug);
}
return redirect()
->route('admin.pages.show', $page)
->with('status', '頁面已成功更新');
@@ -147,6 +156,8 @@ class PageController extends Controller
}
$title = $page->title;
$slug = $page->slug;
$wasPublished = $page->isPublished();
$page->delete();
AuditLog::create([
@@ -156,6 +167,10 @@ class PageController extends Controller
'ip_address' => request()->ip(),
]);
if ($wasPublished) {
SiteRevalidationService::revalidatePage($slug);
}
return redirect()
->route('admin.pages.index')
->with('status', '頁面已成功刪除');
@@ -176,6 +191,8 @@ class PageController extends Controller
'ip_address' => request()->ip(),
]);
SiteRevalidationService::revalidatePage($page->slug);
return back()->with('status', '頁面已成功發布');
}
}

View File

@@ -7,8 +7,8 @@ use App\Http\Resources\ArticleCollectionResource;
use App\Http\Resources\ArticleResource;
use App\Models\Article;
use App\Models\ArticleAttachment;
use App\Support\DownloadFile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class ArticleController extends Controller
{
@@ -87,9 +87,10 @@ class ArticleController extends Controller
$attachment->incrementDownloadCount();
return Storage::disk('public')->download(
$attachment->file_path,
$attachment->original_filename
return DownloadFile::fromDisk(
disk: 'public',
path: $attachment->file_path,
downloadName: $attachment->original_filename
);
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Support\DownloadFile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
@@ -109,9 +110,10 @@ class PublicDocumentController extends Controller
// Log access
$document->logAccess('download', $user);
return Storage::disk('private')->download(
$currentVersion->file_path,
$currentVersion->original_filename
return DownloadFile::fromDisk(
disk: 'private',
path: $currentVersion->file_path,
downloadName: $currentVersion->original_filename
);
}
@@ -139,9 +141,10 @@ class PublicDocumentController extends Controller
// Log access
$document->logAccess('download', $user);
return Storage::disk('private')->download(
$version->file_path,
$version->original_filename
return DownloadFile::fromDisk(
disk: 'private',
path: $version->file_path,
downloadName: $version->original_filename
);
}