Auth: convert auth views and verification email to Nova layout
This commit is contained in:
@@ -4,7 +4,6 @@ namespace App\Services;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\Category;
|
||||
use App\Models\ContentType;
|
||||
use App\Models\ArtworkFeature;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Contracts\Pagination\CursorPaginator;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
@@ -220,14 +219,48 @@ class ArtworkService
|
||||
}
|
||||
}
|
||||
|
||||
$categoryIds = $this->categoryAndDescendantIds($current);
|
||||
|
||||
$query = $this->browseQuery($sort)
|
||||
->whereHas('categories', function ($q) use ($current) {
|
||||
$q->where('categories.id', $current->id);
|
||||
->whereHas('categories', function ($q) use ($categoryIds) {
|
||||
$q->whereIn('categories.id', $categoryIds);
|
||||
});
|
||||
|
||||
return $query->cursorPaginate($perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect category id plus all descendant category ids.
|
||||
*
|
||||
* @return array<int, int>
|
||||
*/
|
||||
private function categoryAndDescendantIds(Category $category): array
|
||||
{
|
||||
$allIds = [(int) $category->id];
|
||||
$frontier = [(int) $category->id];
|
||||
|
||||
while (! empty($frontier)) {
|
||||
$children = Category::whereIn('parent_id', $frontier)
|
||||
->pluck('id')
|
||||
->map(static fn ($id): int => (int) $id)
|
||||
->all();
|
||||
|
||||
if (empty($children)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$newIds = array_values(array_diff($children, $allIds));
|
||||
if (empty($newIds)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$allIds = array_values(array_unique(array_merge($allIds, $newIds)));
|
||||
$frontier = $newIds;
|
||||
}
|
||||
|
||||
return $allIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get featured artworks ordered by featured_at DESC, optionally filtered by type.
|
||||
* Uses artwork_features table and applies public/approved/published filters.
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -120,19 +119,16 @@ class LegacyService
|
||||
public function forumNews(): array
|
||||
{
|
||||
try {
|
||||
return DB::table('forum_topics as t1')
|
||||
->leftJoin('users as t2', 't1.user_id', '=', 't2.user_id')
|
||||
->select(
|
||||
't1.topic_id',
|
||||
't1.topic',
|
||||
't1.views',
|
||||
't1.post_date',
|
||||
't1.preview',
|
||||
't2.uname'
|
||||
)
|
||||
->where('t1.root_id', 2876)
|
||||
->where('t1.privilege', '<', 4)
|
||||
->orderByDesc('t1.post_date')
|
||||
return DB::table('forum_threads as t1')
|
||||
->leftJoin('users as t2', 't1.user_id', '=', 't2.id')
|
||||
->leftJoin('forum_categories as c', 't1.category_id', '=', 'c.id')
|
||||
->selectRaw('t1.id as topic_id, t1.title as topic, t1.views, t1.created_at as post_date, t1.content as preview, COALESCE(t2.name, ?) as uname', ['Unknown'])
|
||||
->whereNull('t1.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->where('t1.category_id', 2876)
|
||||
->orWhereIn('c.slug', ['news', 'forum-news']);
|
||||
})
|
||||
->orderByDesc('t1.created_at')
|
||||
->limit(8)
|
||||
->get()
|
||||
->toArray();
|
||||
@@ -170,17 +166,25 @@ class LegacyService
|
||||
public function latestForumActivity(): array
|
||||
{
|
||||
try {
|
||||
return DB::table('forum_topics as t1')
|
||||
->select(
|
||||
't1.topic_id',
|
||||
't1.topic',
|
||||
DB::raw('(SELECT COUNT(*) FROM forum_posts WHERE topic_id = t1.topic_id) AS numPosts')
|
||||
)
|
||||
->where('t1.root_id', '<>', 0)
|
||||
->where('t1.root_id', '<>', 2876)
|
||||
->where('t1.privilege', '<', 4)
|
||||
->orderByDesc('t1.last_update')
|
||||
->orderByDesc('t1.post_date')
|
||||
return DB::table('forum_threads as t1')
|
||||
->leftJoin('forum_categories as c', 't1.category_id', '=', 'c.id')
|
||||
->leftJoin('forum_posts as p', function ($join) {
|
||||
$join->on('p.thread_id', '=', 't1.id')
|
||||
->whereNull('p.deleted_at');
|
||||
})
|
||||
->selectRaw('t1.id as topic_id, t1.title as topic, COUNT(p.id) AS numPosts')
|
||||
->whereNull('t1.deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->where('t1.category_id', '<>', 2876)
|
||||
->orWhereNull('t1.category_id');
|
||||
})
|
||||
->where(function ($query) {
|
||||
$query->whereNull('c.slug')
|
||||
->orWhereNotIn('c.slug', ['news', 'forum-news']);
|
||||
})
|
||||
->groupBy('t1.id', 't1.title')
|
||||
->orderByDesc('t1.last_post_at')
|
||||
->orderByDesc('t1.created_at')
|
||||
->limit(10)
|
||||
->get()
|
||||
->toArray();
|
||||
@@ -266,7 +270,7 @@ class LegacyService
|
||||
$row->encoded = $encoded;
|
||||
// Prefer new files.skinbase.org when possible
|
||||
try {
|
||||
$art = \App\Models\Artwork::find($row->id);
|
||||
$art = Artwork::find($row->id);
|
||||
$present = \App\Services\ThumbnailPresenter::present($art ?: (array) $row, 'md');
|
||||
$row->thumb_url = $present['url'];
|
||||
$row->thumb_srcset = $present['srcset'];
|
||||
@@ -402,126 +406,6 @@ class LegacyService
|
||||
];
|
||||
}
|
||||
|
||||
public function forumIndex()
|
||||
{
|
||||
try {
|
||||
$topics = DB::table('forum_topics as t')
|
||||
->select(
|
||||
't.topic_id',
|
||||
't.topic',
|
||||
't.discuss',
|
||||
't.last_update',
|
||||
't.privilege',
|
||||
DB::raw('(SELECT COUNT(*) FROM forum_posts p WHERE p.topic_id IN (SELECT topic_id FROM forum_topics st WHERE st.root_id = t.topic_id)) AS num_posts'),
|
||||
DB::raw('(SELECT COUNT(*) FROM forum_topics st WHERE st.root_id = t.topic_id) AS num_subtopics')
|
||||
)
|
||||
->where('t.root_id', 0)
|
||||
->where('t.privilege', '<', 4)
|
||||
->orderByDesc('t.last_update')
|
||||
->limit(100)
|
||||
->get();
|
||||
} catch (\Throwable $e) {
|
||||
$topics = collect();
|
||||
}
|
||||
|
||||
return [
|
||||
'topics' => $topics,
|
||||
'page_title' => 'Forum',
|
||||
'page_meta_description' => 'Skinbase forum threads.',
|
||||
'page_meta_keywords' => 'forum, discussions, topics, skinbase',
|
||||
];
|
||||
}
|
||||
|
||||
public function forumTopic(int $topic_id, int $page = 1)
|
||||
{
|
||||
try {
|
||||
$topic = DB::table('forum_topics')->where('topic_id', $topic_id)->first();
|
||||
} catch (\Throwable $e) {
|
||||
$topic = null;
|
||||
}
|
||||
|
||||
if (! $topic) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$subtopics = DB::table('forum_topics as t')
|
||||
->leftJoin('users as u', 't.user_id', '=', 'u.user_id')
|
||||
->select(
|
||||
't.topic_id',
|
||||
't.topic',
|
||||
't.discuss',
|
||||
't.post_date',
|
||||
't.last_update',
|
||||
'u.uname',
|
||||
DB::raw('(SELECT COUNT(*) FROM forum_posts p WHERE p.topic_id = t.topic_id) AS num_posts')
|
||||
)
|
||||
->where('t.root_id', $topic->topic_id)
|
||||
->orderByDesc('t.last_update')
|
||||
->paginate(50)
|
||||
->withQueryString();
|
||||
} catch (\Throwable $e) {
|
||||
$subtopics = null;
|
||||
}
|
||||
|
||||
if ($subtopics && $subtopics->total() > 0) {
|
||||
return [
|
||||
'type' => 'subtopics',
|
||||
'topic' => $topic,
|
||||
'subtopics' => $subtopics,
|
||||
'page_title' => $topic->topic,
|
||||
'page_meta_description' => \Illuminate\Support\Str::limit(strip_tags($topic->discuss ?? 'Forum topic'), 160),
|
||||
'page_meta_keywords' => 'forum, topic, skinbase',
|
||||
];
|
||||
}
|
||||
|
||||
$sort = strtolower(request()->query('sort', 'desc')) === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
try {
|
||||
$posts = DB::table('forum_posts as p')
|
||||
->leftJoin('users as u', 'p.user_id', '=', 'u.user_id')
|
||||
->select('p.id', 'p.message', 'p.post_date', 'u.uname', 'u.user_id', 'u.icon', 'u.eicon')
|
||||
->where('p.topic_id', $topic->topic_id)
|
||||
->orderBy('p.post_date', $sort)
|
||||
->paginate(50)
|
||||
->withQueryString();
|
||||
} catch (\Throwable $e) {
|
||||
$posts = null;
|
||||
}
|
||||
|
||||
if (! $posts || $posts->total() === 0) {
|
||||
try {
|
||||
$posts = DB::table('forum_posts as p')
|
||||
->leftJoin('users as u', 'p.user_id', '=', 'u.user_id')
|
||||
->select('p.id', 'p.message', 'p.post_date', 'u.uname', 'u.user_id', 'u.icon', 'u.eicon')
|
||||
->where('p.tid', $topic->topic_id)
|
||||
->orderBy('p.post_date', $sort)
|
||||
->paginate(50)
|
||||
->withQueryString();
|
||||
} catch (\Throwable $e) {
|
||||
$posts = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure $posts is always a LengthAwarePaginator so views can iterate and render pagination safely
|
||||
if (! $posts) {
|
||||
$currentPage = max(1, (int) request()->query('page', $page));
|
||||
$items = collect();
|
||||
$posts = new LengthAwarePaginator($items, 0, 50, $currentPage, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'posts',
|
||||
'topic' => $topic,
|
||||
'posts' => $posts,
|
||||
'page_title' => $topic->topic,
|
||||
'page_meta_description' => \Illuminate\Support\Str::limit(strip_tags($topic->discuss ?? 'Forum topic'), 160),
|
||||
'page_meta_keywords' => 'forum, topic, skinbase',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single artwork by id with author and category
|
||||
* Returns null on failure.
|
||||
@@ -555,7 +439,7 @@ class LegacyService
|
||||
$thumb_600 = $appUrl . '/files/thumb/' . $nid_new . '/600_' . $row->id . '.jpg';
|
||||
// Prefer new CDN when possible
|
||||
try {
|
||||
$art = \App\Models\Artwork::find($row->id);
|
||||
$art = Artwork::find($row->id);
|
||||
$present = \App\Services\ThumbnailPresenter::present($art ?: (array) $row, 'md');
|
||||
$thumb_file = $present['url'];
|
||||
$thumb_file_300 = $present['srcset'] ? explode(' ', explode(',', $present['srcset'])[0])[0] : ($present['url'] ?? null);
|
||||
|
||||
51
app/Services/Security/RecaptchaVerifier.php
Normal file
51
app/Services/Security/RecaptchaVerifier.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Security;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class RecaptchaVerifier
|
||||
{
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return (bool) config('services.recaptcha.enabled', false);
|
||||
}
|
||||
|
||||
public function verify(string $token, ?string $ip = null): bool
|
||||
{
|
||||
if (! $this->isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$secret = (string) config('services.recaptcha.secret', '');
|
||||
if ($secret === '' || $token === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var \Illuminate\Http\Client\Response $response */
|
||||
$response = Http::asForm()
|
||||
->timeout((int) config('services.recaptcha.timeout', 5))
|
||||
->post((string) config('services.recaptcha.verify_url'), [
|
||||
'secret' => $secret,
|
||||
'response' => $token,
|
||||
'remoteip' => $ip,
|
||||
]);
|
||||
|
||||
if ($response->status() < 200 || $response->status() >= 300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = json_decode((string) $response->body(), true);
|
||||
|
||||
return (bool) data_get(is_array($payload) ? $payload : [], 'success', false);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('recaptcha verification request failed', [
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
app/Services/UsernameApprovalService.php
Normal file
48
app/Services/UsernameApprovalService.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\UsernamePolicy;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
final class UsernameApprovalService
|
||||
{
|
||||
public function submit(?User $user, string $username, string $context, array $payload = []): ?int
|
||||
{
|
||||
if (! Schema::hasTable('username_approval_requests')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = UsernamePolicy::normalize($username);
|
||||
$similar = UsernamePolicy::similarReserved($normalized);
|
||||
if ($similar === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$existingId = DB::table('username_approval_requests')
|
||||
->where('requested_username', $normalized)
|
||||
->where('context', $context)
|
||||
->where('status', 'pending')
|
||||
->when($user !== null, fn ($q) => $q->where('user_id', (int) $user->id), fn ($q) => $q->whereNull('user_id'))
|
||||
->value('id');
|
||||
|
||||
if ($existingId) {
|
||||
return (int) $existingId;
|
||||
}
|
||||
|
||||
return (int) DB::table('username_approval_requests')->insertGetId([
|
||||
'user_id' => $user?->id,
|
||||
'requested_username' => $normalized,
|
||||
'context' => $context,
|
||||
'similar_to' => $similar,
|
||||
'status' => 'pending',
|
||||
'payload' => $payload === [] ? null : json_encode($payload),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user