|
|
<?php
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
use App\Models\DictItem;
|
|
|
use App\Models\DictType;
|
|
|
use App\Models\News;
|
|
|
use App\Support\ApiResponse;
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
|
use Illuminate\Validation\Rule;
|
|
|
|
|
|
class NewsController extends Controller
|
|
|
{
|
|
|
use ApiResponse;
|
|
|
|
|
|
public function index(Request $request): JsonResponse
|
|
|
{
|
|
|
$query = News::query()->with(['categoryItem']);
|
|
|
|
|
|
if ($kw = $request->query('keyword')) {
|
|
|
$query->where(function ($q) use ($kw) {
|
|
|
$q->where('title', 'like', "%{$kw}%")
|
|
|
->orWhere('source', 'like', "%{$kw}%");
|
|
|
});
|
|
|
}
|
|
|
if ($request->filled('category_dict_item_id')) {
|
|
|
$query->where('category_dict_item_id', (int) $request->query('category_dict_item_id'));
|
|
|
}
|
|
|
if ($request->filled('status')) {
|
|
|
$query->where('status', (int) $request->query('status'));
|
|
|
}
|
|
|
if ($request->filled('crawl_job_id')) {
|
|
|
$query->where('crawl_job_id', (int) $request->query('crawl_job_id'));
|
|
|
}
|
|
|
if ($request->filled('has_cover')) {
|
|
|
$has = (int) $request->query('has_cover');
|
|
|
if ($has === 1) {
|
|
|
$query->whereNotNull('cover_url')->where('cover_url', '!=', '');
|
|
|
} else {
|
|
|
$query->where(function ($q) {
|
|
|
$q->whereNull('cover_url')->orWhere('cover_url', '');
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$paginator = $query
|
|
|
->orderByDesc('published_at')
|
|
|
->orderByDesc('id')
|
|
|
->paginate((int) $request->query('page_size', 20))
|
|
|
->withQueryString();
|
|
|
|
|
|
$paginator->getCollection()->transform(fn (News $n) => $this->serializeList($n));
|
|
|
|
|
|
return $this->paginated($paginator);
|
|
|
}
|
|
|
|
|
|
public function store(Request $request): JsonResponse
|
|
|
{
|
|
|
if (! DictType::query()->where('code', 'news_category')->where('status', 1)->exists()) {
|
|
|
return $this->fail('字典「资讯分类」未配置,请执行 NewsDictionarySeeder 或在字典中维护 news_category', 422);
|
|
|
}
|
|
|
|
|
|
$data = $this->validatedNews($request);
|
|
|
$row = News::query()->create($data);
|
|
|
|
|
|
return $this->ok(['id' => $row->id], '已创建');
|
|
|
}
|
|
|
|
|
|
public function show(int $news): JsonResponse
|
|
|
{
|
|
|
$model = News::query()->with(['categoryItem'])->findOrFail($news);
|
|
|
|
|
|
return $this->ok($this->serializeDetail($model));
|
|
|
}
|
|
|
|
|
|
public function update(Request $request, int $news): JsonResponse
|
|
|
{
|
|
|
$model = News::query()->findOrFail($news);
|
|
|
$data = $this->validatedNews($request, partial: true);
|
|
|
$model->fill($data);
|
|
|
$model->save();
|
|
|
|
|
|
return $this->ok(null, '已保存');
|
|
|
}
|
|
|
|
|
|
public function destroy(int $news): JsonResponse
|
|
|
{
|
|
|
News::query()->findOrFail($news)->delete();
|
|
|
|
|
|
return $this->ok(null, '已删除');
|
|
|
}
|
|
|
|
|
|
/** 发布:status=1 并写入发布时间 */
|
|
|
public function publish(Request $request, int $news): JsonResponse
|
|
|
{
|
|
|
$data = $request->validate([
|
|
|
'status' => ['required', 'integer', 'in:0,1'],
|
|
|
'published_at' => ['nullable', 'date'],
|
|
|
]);
|
|
|
$model = News::query()->findOrFail($news);
|
|
|
$model->status = (int) $data['status'];
|
|
|
if ($model->status === 1) {
|
|
|
$model->published_at = isset($data['published_at'])
|
|
|
? \Carbon\Carbon::parse($data['published_at'])
|
|
|
: ($model->published_at ?? now());
|
|
|
} else {
|
|
|
$model->published_at = null;
|
|
|
}
|
|
|
$model->save();
|
|
|
|
|
|
return $this->ok(null, '已更新发布状态');
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return array<string, mixed>
|
|
|
*/
|
|
|
protected function validatedNews(Request $request, bool $partial = false): array
|
|
|
{
|
|
|
$categoryTypeId = DictType::query()->where('code', 'news_category')->where('status', 1)->value('id');
|
|
|
|
|
|
$rules = [
|
|
|
'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'],
|
|
|
'category_dict_item_id' => $this->dictItemRules($categoryTypeId, $partial ? 'sometimes' : 'required'),
|
|
|
'source' => ['nullable', 'string', 'max:128'],
|
|
|
'cover_url' => ['nullable', 'string', 'max:512'],
|
|
|
'content_html' => [$partial ? 'sometimes' : 'required', 'string'],
|
|
|
'published_at' => [$partial ? 'sometimes' : 'required', 'date'],
|
|
|
'status' => ['nullable', 'integer', 'in:0,1'],
|
|
|
];
|
|
|
|
|
|
return $request->validate($rules);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return array<int, \Illuminate\Contracts\Validation\Rule|string>
|
|
|
*/
|
|
|
protected function dictItemRules(?int $dictTypeId, string $presence): array
|
|
|
{
|
|
|
if (! $dictTypeId) {
|
|
|
return match ($presence) {
|
|
|
'required' => ['required', 'integer'],
|
|
|
'sometimes' => ['sometimes', 'nullable', 'integer'],
|
|
|
default => ['nullable', 'integer'],
|
|
|
};
|
|
|
}
|
|
|
|
|
|
$exists = Rule::exists('dict_items', 'id')->where(
|
|
|
fn ($q) => $q->where('dict_type_id', $dictTypeId)->where('status', 1)
|
|
|
);
|
|
|
|
|
|
return match ($presence) {
|
|
|
'required' => ['required', 'integer', $exists],
|
|
|
'sometimes' => ['sometimes', 'nullable', 'integer', $exists],
|
|
|
default => ['nullable', 'integer', $exists],
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return array<string, mixed>
|
|
|
*/
|
|
|
protected function serializeList(News $n): array
|
|
|
{
|
|
|
return [
|
|
|
'id' => $n->id,
|
|
|
'title' => $n->title,
|
|
|
'category_dict_item_id' => $n->category_dict_item_id,
|
|
|
'category_item' => $this->serializeDictItem($n->categoryItem),
|
|
|
'source' => $n->source,
|
|
|
'source_url' => $n->source_url,
|
|
|
'cover_url' => $n->cover_url,
|
|
|
'has_cover' => $n->cover_url ? 1 : 0,
|
|
|
'status' => (int) $n->status,
|
|
|
'published_at' => $n->published_at?->toIso8601String(),
|
|
|
'created_at' => $n->created_at?->toIso8601String(),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return array<string, mixed>
|
|
|
*/
|
|
|
protected function serializeDetail(News $n): array
|
|
|
{
|
|
|
$row = $this->serializeList($n);
|
|
|
$row['content_html'] = $n->content_html;
|
|
|
|
|
|
return $row;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @return array{id:int,label:string,value:string}|null
|
|
|
*/
|
|
|
protected function serializeDictItem(?DictItem $item): ?array
|
|
|
{
|
|
|
if (! $item) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
'id' => $item->id,
|
|
|
'label' => $item->label,
|
|
|
'value' => $item->value,
|
|
|
];
|
|
|
}
|
|
|
}
|