новости в админке для логин скрина
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\SaveLoginScreenNewsRequest;
|
||||
use App\Models\LoginScreenNews;
|
||||
use App\Services\LoginScreenNewsService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class LoginScreenNewsController extends Controller
|
||||
{
|
||||
public function index(LoginScreenNewsService $news): View
|
||||
{
|
||||
return view('admin.login-screen-news.index', [
|
||||
'newsItems' => $news->listForAdmin(),
|
||||
'newsStats' => $news->stats(),
|
||||
'feedPreview' => $news->renderFeed(),
|
||||
'feedUrl' => route('launcher.login-news.show'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(SaveLoginScreenNewsRequest $request, LoginScreenNewsService $news): RedirectResponse
|
||||
{
|
||||
$news->create($request->validated(), $request->user());
|
||||
|
||||
return back()->with('status', 'Новость для логин-экрана добавлена.');
|
||||
}
|
||||
|
||||
public function update(
|
||||
LoginScreenNews $newsItem,
|
||||
SaveLoginScreenNewsRequest $request,
|
||||
LoginScreenNewsService $news,
|
||||
): RedirectResponse {
|
||||
$news->update($newsItem, $request->validated(), $request->user());
|
||||
|
||||
return back()->with('status', 'Новость для логин-экрана обновлена.');
|
||||
}
|
||||
|
||||
public function destroy(LoginScreenNews $newsItem, LoginScreenNewsService $news): RedirectResponse
|
||||
{
|
||||
$news->delete($newsItem);
|
||||
|
||||
return back()->with('status', 'Новость для логин-экрана удалена.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\LoginScreenNewsService;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class WowLoginNewsFeedController extends Controller
|
||||
{
|
||||
public function show(LoginScreenNewsService $news): Response
|
||||
{
|
||||
return response($news->renderFeed(), 200, [
|
||||
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SaveLoginScreenNewsRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_published' => $this->boolean('is_published'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'string', 'max:120'],
|
||||
'body' => ['required', 'string', 'max:20000'],
|
||||
'sort_order' => ['required', 'integer', 'min:0', 'max:9999'],
|
||||
'is_published' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'title.required' => 'Укажи название новости для админки.',
|
||||
'title.max' => 'Название новости не должно быть длиннее 120 символов.',
|
||||
'body.required' => 'Заполни текст новости для логин-экрана.',
|
||||
'body.max' => 'Текст новости слишком длинный.',
|
||||
'sort_order.required' => 'Укажи порядок вывода.',
|
||||
'sort_order.integer' => 'Порядок вывода должен быть числом.',
|
||||
'sort_order.min' => 'Порядок вывода не может быть отрицательным.',
|
||||
'sort_order.max' => 'Порядок вывода слишком большой.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LoginScreenNews extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'body',
|
||||
'sort_order',
|
||||
'is_published',
|
||||
'created_by_account_id',
|
||||
'created_by_username',
|
||||
'updated_by_account_id',
|
||||
'updated_by_username',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_published' => 'bool',
|
||||
];
|
||||
}
|
||||
|
||||
public function scopeOrdered(Builder $query): Builder
|
||||
{
|
||||
return $query
|
||||
->orderBy('sort_order')
|
||||
->orderByDesc('id');
|
||||
}
|
||||
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_published', true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\GameAccountUser;
|
||||
use App\Models\LoginScreenNews;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LoginScreenNewsService
|
||||
{
|
||||
public const FEED_HEADER = 'SERVERALERT:';
|
||||
|
||||
public const SECTION_SEPARATOR = '----------------------------------------------------------';
|
||||
|
||||
public const EMPTY_MESSAGE = '|cFFB7C1CFСкоро здесь появятся новости сервера.|r';
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection<int, \App\Models\LoginScreenNews>
|
||||
*/
|
||||
public function listForAdmin(): Collection
|
||||
{
|
||||
return LoginScreenNews::query()
|
||||
->ordered()
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{total: int, published: int, draft: int}
|
||||
*/
|
||||
public function stats(): array
|
||||
{
|
||||
$total = LoginScreenNews::query()->count();
|
||||
$published = LoginScreenNews::query()->published()->count();
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'published' => $published,
|
||||
'draft' => $total - $published,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{title: string, body: string, sort_order: int, is_published?: bool} $attributes
|
||||
*/
|
||||
public function create(array $attributes, ?GameAccountUser $actor): LoginScreenNews
|
||||
{
|
||||
return LoginScreenNews::query()->create([
|
||||
...$this->normalizeAttributes($attributes),
|
||||
'created_by_account_id' => $actor?->id,
|
||||
'created_by_username' => $actor?->username,
|
||||
'updated_by_account_id' => $actor?->id,
|
||||
'updated_by_username' => $actor?->username,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{title: string, body: string, sort_order: int, is_published?: bool} $attributes
|
||||
*/
|
||||
public function update(LoginScreenNews $newsItem, array $attributes, ?GameAccountUser $actor): LoginScreenNews
|
||||
{
|
||||
$newsItem->forceFill([
|
||||
...$this->normalizeAttributes($attributes),
|
||||
'updated_by_account_id' => $actor?->id,
|
||||
'updated_by_username' => $actor?->username,
|
||||
])->save();
|
||||
|
||||
return $newsItem->refresh();
|
||||
}
|
||||
|
||||
public function delete(LoginScreenNews $newsItem): void
|
||||
{
|
||||
$newsItem->delete();
|
||||
}
|
||||
|
||||
public function renderFeed(): string
|
||||
{
|
||||
$sections = LoginScreenNews::query()
|
||||
->published()
|
||||
->ordered()
|
||||
->pluck('body')
|
||||
->map(fn (mixed $body): string => $this->normalizeBody((string) $body))
|
||||
->filter(fn (string $body): bool => $body !== '')
|
||||
->values();
|
||||
|
||||
$payload = $sections->isNotEmpty()
|
||||
? $sections->implode("\n".self::SECTION_SEPARATOR."\n")
|
||||
: self::EMPTY_MESSAGE;
|
||||
|
||||
return self::FEED_HEADER."\n\n".$payload."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{title: string, body: string, sort_order: int, is_published?: bool} $attributes
|
||||
* @return array{title: string, body: string, sort_order: int, is_published: bool}
|
||||
*/
|
||||
private function normalizeAttributes(array $attributes): array
|
||||
{
|
||||
return [
|
||||
'title' => trim($attributes['title']),
|
||||
'body' => $this->normalizeBody($attributes['body']),
|
||||
'sort_order' => (int) $attributes['sort_order'],
|
||||
'is_published' => (bool) ($attributes['is_published'] ?? false),
|
||||
];
|
||||
}
|
||||
|
||||
private function normalizeBody(string $body): string
|
||||
{
|
||||
$normalized = trim(str_replace(["\r\n", "\r"], "\n", $body));
|
||||
|
||||
if (Str::startsWith($normalized, self::FEED_HEADER)) {
|
||||
$normalized = trim(Str::after($normalized, self::FEED_HEADER));
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user