Files
laralog/app/Http/Controllers/Api/ApiController.php
T

244 lines
8.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Blog\Services\PostContentRenderer;
use App\Models\Category;
use App\Models\Comment;
use App\Models\Post;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Spatie\Tags\Tag;
/**
* 对外 API:为小程序/第三方平台提供数据与评论能力。
*/
class ApiController extends Controller
{
/**
* 站点信息。
*
* @response array{name: string, description: string, icp: string, url: string}
*/
public function site(): JsonResponse
{
return response()->json([
'name' => Setting::get('site_name', config('blog.name')),
'description' => Setting::get('site_description', config('blog.description')),
'icp' => Setting::get('site_icp', config('blog.icp')),
'url' => url('/'),
'rss' => route('feed.rss'),
'api_version' => '1.0',
]);
}
/**
* 文章列表(分页)。
*
* @queryParam page int 页码 Example: 1
* @queryParam per_page int 每页数量(默认 10,最大 50Example: 10
* @queryParam category string 分类 slug Example: tech
* @queryParam tag string 标签名 Example: laravel
* @queryParam q string 关键词搜索 Example: laravel
*
* @response array{data: array, meta: array}
*/
public function posts(Request $request): JsonResponse
{
$query = Post::published()->with('category:id,name,slug')->with('tags:id,name');
if ($category = $request->query('category')) {
$query->whereHas('category', fn ($q) => $q->where('slug', $category)->orWhere('name', $category));
}
if ($tag = $request->query('tag')) {
$query->whereHas('tags', fn ($q) => $q->where('name', $tag)->orWhere('slug', $tag));
}
if ($q = trim((string) $request->query('q', ''))) {
$query->search($q);
}
$perPage = min((int) $request->query('per_page', 10), 50);
$posts = $query->orderByDesc('published_at')->paginate($perPage);
return response()->json([
'data' => $posts->through(fn (Post $post) => $this->postSummary($post))->items(),
'meta' => [
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
'per_page' => $posts->perPage(),
'total' => $posts->total(),
],
]);
}
/**
* 文章详情。
*
* @urlParam slug string required 文章 slug 或 ID Example: post-1
*
* @response array{id: int, title: string, content_html: string, ...}
*/
public function post(string $slug): JsonResponse
{
$post = Post::query()
->where('slug', $slug)
->orWhere('id', (int) $slug)
->first();
abort_unless($post && $post->status === 'published', 404);
// 会员/付费内容过滤与前台一致
$contentHtml = app(PostContentRenderer::class)->render($post);
return response()->json([
'id' => $post->id,
'title' => $post->title,
'slug' => $post->slug,
'excerpt' => $post->excerpt_or_fallback,
'content_html' => $contentHtml,
'content_format' => $post->content_format,
'keywords' => $post->keywords,
'category' => $post->category ? ['id' => $post->category->id, 'name' => $post->category->name, 'slug' => $post->category->slug] : null,
'tags' => $post->tags->map(fn ($t) => ['id' => $t->id, 'name' => $t->name, 'slug' => $t->slug])->values(),
'views' => $post->views,
'comment_count' => $post->comment_count,
'published_at' => $post->published_at?->toIso8601String(),
'url' => route('posts.show', $post->slug ?: $post->id),
]);
}
/**
* 分类列表。
*
* @response array{data: array}
*/
public function categories(): JsonResponse
{
return response()->json([
'data' => Category::query()->orderBy('display_order')->orderBy('name')->get(['id', 'name', 'slug', 'post_count']),
]);
}
/**
* 标签列表。
*
* @response array{data: array}
*/
public function tags(): JsonResponse
{
$counts = DB::table('taggables')
->selectRaw('tag_id, COUNT(*) as total')
->where('taggable_type', Post::class)
->groupBy('tag_id')
->pluck('total', 'tag_id');
return response()->json([
'data' => Tag::query()->where('type', 'post')->get()
->map(fn (Tag $tag) => [
'id' => $tag->id,
'name' => $tag->name,
'slug' => $tag->slug,
'post_count' => (int) ($counts[$tag->id] ?? 0),
])
->values(),
]);
}
/**
* 提交评论(公开)。
*
* @bodyParam author_name string required 昵称 Example: 访客
* @bodyParam author_email string 邮箱 Example: guest@example.com
* @bodyParam author_url string 网站 Example: https://example.com
* @bodyParam content string required 评论内容 Example: 写得很好
* @bodyParam website string 蜜罐字段,留空 Example:
*/
public function storeComment(Request $request): JsonResponse
{
$data = $request->validate([
'post_id' => ['required', 'integer', 'exists:posts,id'],
'author_name' => ['required', 'string', 'max:50'],
'author_email' => ['nullable', 'email', 'max:255'],
'author_url' => ['nullable', 'url', 'max:255'],
'content' => ['required', 'string', 'min:'.(int) Setting::get('comment_min_len', 2), 'max:'.(int) Setting::get('comment_max_len', 6000)],
]);
// 蜜罐
if ($request->filled('website')) {
return response()->json(['message' => '提交失败'], 422);
}
$post = Post::findOrFail((int) $data['post_id']);
if ($post->status !== 'published' || $post->close_comment) {
return response()->json(['message' => '该文章不允许评论'], 422);
}
$status = (int) Setting::get('comment_audit', 0) === 1 ? 'pending' : 'published';
$comment = $post->comments()->create([
'user_id' => $request->user()?->id,
'author_name' => $data['author_name'],
'author_email' => $data['author_email'] ?? null,
'author_url' => $data['author_url'] ?? null,
'content' => $data['content'],
'ip' => $request->ip(),
'status' => $status,
]);
if ($status === 'published') {
$post->increment('comment_count');
}
app(\App\Blog\Support\PluginManager::class)->doAction('comment.created', $comment);
return response()->json([
'message' => $status === 'published' ? '评论已发布' : '评论已提交,等待审核',
'data' => ['id' => $comment->id, 'status' => $status],
], 201);
}
/**
* 当前登录用户(需 Bearer Token)。
*
* @authenticated
*
* @response array{id: int, name: string, email: string, roles: array}
*/
public function me(Request $request): JsonResponse
{
$user = $request->user();
return response()->json([
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'roles' => $user->getRoleNames(),
'url' => $user->url,
]);
}
private function postSummary(Post $post): array
{
return [
'id' => $post->id,
'title' => $post->title,
'slug' => $post->slug,
'excerpt' => $post->excerpt_or_fallback,
'category' => $post->category?->name,
'tags' => $post->tags->pluck('name')->values(),
'views' => $post->views,
'comment_count' => $post->comment_count,
'published_at' => $post->published_at?->toIso8601String(),
'url' => route('posts.show', $post->slug ?: $post->id),
];
}
}