A lot of things.
This commit is contained in:
@@ -4,9 +4,12 @@ namespace App\Auth;
|
|||||||
|
|
||||||
use App\Services\XenforoService;
|
use App\Services\XenforoService;
|
||||||
use App\XenForoDataTypes\XenForoData;
|
use App\XenForoDataTypes\XenForoData;
|
||||||
|
use Illuminate\Contracts\Auth\Access\Authorizable;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
class XenForoUser extends XenForoData implements Authenticatable {
|
class XenForoUser extends XenForoData implements Authenticatable, Authorizable {
|
||||||
|
|
||||||
|
use \Illuminate\Foundation\Auth\Access\Authorizable;
|
||||||
|
|
||||||
public ?array $permissions = null;
|
public ?array $permissions = null;
|
||||||
public function getAuthIdentifierName(): string
|
public function getAuthIdentifierName(): string
|
||||||
@@ -64,7 +67,7 @@ class XenForoUser extends XenForoData implements Authenticatable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function can(string $permissionGroup, string $permissionName): bool
|
public function _can(string $permissionGroup, string $permissionName): bool
|
||||||
{
|
{
|
||||||
if( !$this->permissions ){
|
if( !$this->permissions ){
|
||||||
$this->permissions = $this->services->getPermissions($this->data->user_id, $this->data->permission_combination_id);
|
$this->permissions = $this->services->getPermissions($this->data->user_id, $this->data->permission_combination_id);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
|
use App\Services\XenforoApiService;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class EntryHelpers {
|
class EntryHelpers {
|
||||||
@@ -56,4 +59,33 @@ class EntryHelpers {
|
|||||||
default => $fields['entry_title'],
|
default => $fields['entry_title'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getLatestComments(Entry $entry, int $limit = 20): array {
|
||||||
|
|
||||||
|
if( !$entry->comments_thread_id ){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheKey = "entry_comments_{$entry->id}";
|
||||||
|
return Cache::remember($cacheKey, now()->addDays(1), function () use ($entry, $limit) {
|
||||||
|
|
||||||
|
$service = app(XenforoApiService::class);
|
||||||
|
|
||||||
|
// Get thread infos and pagination.
|
||||||
|
$paginationInfos = $service->getThreadPosts($entry->comments_thread_id, 1);
|
||||||
|
$lastPage = $paginationInfos['pagination']['last_page'] ?? 1;
|
||||||
|
|
||||||
|
// Get last threads
|
||||||
|
$lastPageData = $lastPage > 1 ? $service->getThreadPosts($entry->comments_thread_id, $lastPage) : $paginationInfos;
|
||||||
|
$posts = $lastPageData['posts'] ?? [];
|
||||||
|
|
||||||
|
if( count( $posts ) < $limit && $lastPage > 1 ){
|
||||||
|
$previousPageData = $service->getThreadPosts($entry->comments_thread_id, $lastPage - 1 );
|
||||||
|
$posts = array_merge( $posts, $previousPageData['posts'] ?? [] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect( $posts )->slice(-$limit)->reverse()->values()->toArray();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\EntryHelpers;
|
||||||
use App\Models\Entry;
|
use App\Models\Entry;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
@@ -25,7 +26,9 @@ class EntryController extends Controller
|
|||||||
if( $entry->type !== $section )
|
if( $entry->type !== $section )
|
||||||
abort(404);
|
abort(404);
|
||||||
|
|
||||||
return view('entries.show', compact('entry', 'section'));
|
$comments = EntryHelpers::getLatestComments( $entry );
|
||||||
|
|
||||||
|
return view('entries.show', compact('entry', 'section', 'comments' ) );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
app/Http/Controllers/RedirectController.php
Normal file
17
app/Http/Controllers/RedirectController.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RedirectController extends Controller
|
||||||
|
{
|
||||||
|
public function entryReportRedirect( Request $request )
|
||||||
|
{
|
||||||
|
$id = $request->input('id');
|
||||||
|
$entry = Entry::findOrFail($id);
|
||||||
|
|
||||||
|
return redirect()->route('entries.show', ['section' => $entry->type, 'entry' => $entry])->with('success', "Your report has been sent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,164 +102,19 @@ class SubmissionController extends Controller
|
|||||||
|
|
||||||
public function update(StoreEntryRequest $request, string $section, Entry $entry)
|
public function update(StoreEntryRequest $request, string $section, Entry $entry)
|
||||||
{
|
{
|
||||||
if( $entry->type !== $section ) {
|
try {
|
||||||
abort(404);
|
$entry = $this->services->editEntry($request, $section, $entry);
|
||||||
|
|
||||||
|
return match ($entry->state) {
|
||||||
|
'published' => redirect()->route('entries.show', ['section' => $section, 'entry' => $entry->slug])->with('success', "Your entry has been published."),
|
||||||
|
'pending' => redirect()->route('home')->with('success', "Your entry has been submitted and is pending review."),
|
||||||
|
default => redirect()->route('home')->with('success', "Your entry has been saved as a draft.")
|
||||||
|
};
|
||||||
|
} catch ( SubmissionException $e ) {
|
||||||
|
return back()->withInput()->withErrors(['error' => $e->getMessage()]);
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return back()->withInput()->withErrors(['error' => 'Unknown error: '.$e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$gameId = null;
|
|
||||||
if( !$request->input('game_id') ){
|
|
||||||
if( $request->input('new-game-title') && $request->input('new-game-platform') && $request->input('new-game-genre') ){
|
|
||||||
$platform = Platform::find($request->input('new-game-platform'));
|
|
||||||
$genre = Genre::find($request->input('new-game-genre'));
|
|
||||||
$game = Game::create([
|
|
||||||
'name' => $request->input('new-game-title'),
|
|
||||||
'slug' => Str::slug($request->input('new-game-title')),
|
|
||||||
'platform_id' => $platform->id,
|
|
||||||
'genre_id' => $genre->id,
|
|
||||||
]);
|
|
||||||
$gameId = $game->id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$gameId = $request->input('game_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
$mainImage = $entry->main_image;
|
|
||||||
if ( $request->hasFile('main-image') ) {
|
|
||||||
if ( $mainImage ) {
|
|
||||||
Storage::disk('public')->delete($mainImage);
|
|
||||||
}
|
|
||||||
$mainImage = $request->file('main-image')->store('entries/main_images', 'public');
|
|
||||||
} elseif ( $request->input('remove_main_image') === '1' ) {
|
|
||||||
if ( $mainImage ) {
|
|
||||||
Storage::disk('public')->delete($mainImage);
|
|
||||||
}
|
|
||||||
$mainImage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$staffCredits = collect($request->input('credits', []))
|
|
||||||
->filter(fn($item) => isset($item['name']) || isset($item['description']))
|
|
||||||
->map(function ($item) {
|
|
||||||
$name = trim($item['name'] ?? '');
|
|
||||||
$description = trim($item['description'] ?? '');
|
|
||||||
if ($name === '' && $description === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return trim($name . ($name !== '' && $description !== '' ? ' — ' : '') . $description);
|
|
||||||
})
|
|
||||||
->filter()
|
|
||||||
->implode("\n");
|
|
||||||
|
|
||||||
$fields = [
|
|
||||||
'type' => $section,
|
|
||||||
'title' => $request->input('entry_title'),
|
|
||||||
'slug' => $request->input('slug') ?? Str::slug($request->input('entry_title', '')),
|
|
||||||
'description' => $request->input('description'),
|
|
||||||
'main_image' => $mainImage,
|
|
||||||
'state' => $request->input('submit-state', 'draft'),
|
|
||||||
'game_id' => $gameId,
|
|
||||||
'status_id' => $request->input('status'),
|
|
||||||
'version' => $request->input('version'),
|
|
||||||
'release_date' => $request->input('release-date'),
|
|
||||||
'staff_credits' => $staffCredits ?: null,
|
|
||||||
'relevant_link' => $request->input('release_site'),
|
|
||||||
'youtube_link' => $request->input('youtube_video'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$entry->update($fields);
|
|
||||||
|
|
||||||
$entry->hashes()->delete();
|
|
||||||
foreach ( $request->input('hashes', []) as $hash ) {
|
|
||||||
if( !isset($hash['filename'], $hash['crc32'], $hash['sha1'], $hash['verified']) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EntryHash::create([
|
|
||||||
'entry_id' => $entry->id,
|
|
||||||
'filename' => $hash['filename'],
|
|
||||||
'hash_crc32' => $hash['crc32'],
|
|
||||||
'hash_sha1' => $hash['sha1'],
|
|
||||||
'verified' => $hash['verified'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$authorIds = [];
|
|
||||||
foreach ( $request->input('authors', []) as $authorId ) {
|
|
||||||
$author = Author::find($authorId);
|
|
||||||
if( $author ) {
|
|
||||||
$authorIds[] = $author->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach( $request->input('new-authors', []) as $authorName ) {
|
|
||||||
$authorName = trim($authorName);
|
|
||||||
if ($authorName === '') continue;
|
|
||||||
|
|
||||||
$author = Author::firstOrCreate(
|
|
||||||
['slug' => Str::slug($authorName)],
|
|
||||||
['name' => $authorName],
|
|
||||||
);
|
|
||||||
$authorIds[] = $author->id;
|
|
||||||
}
|
|
||||||
$entry->authors()->sync(array_values(array_unique($authorIds)));
|
|
||||||
|
|
||||||
if( section_must_be( 'romhacks', $section ) ){
|
|
||||||
$entry->modifications()->sync($request->input('modifications', []));
|
|
||||||
} else {
|
|
||||||
$entry->modifications()->sync([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$entry->languages()->sync($request->input('languages', []));
|
|
||||||
|
|
||||||
$existingFileUuids = $request->input('existing_file_ids', []);
|
|
||||||
if (!is_array($existingFileUuids)) {
|
|
||||||
$existingFileUuids = [];
|
|
||||||
}
|
|
||||||
$entry->files()->whereNotIn('file_uuid', $existingFileUuids)->delete();
|
|
||||||
|
|
||||||
foreach ( $request->input('file_ids', []) as $file_uuid ) {
|
|
||||||
$fileData = Cache::pull("uploaded_file_{$file_uuid}");
|
|
||||||
if( ! $fileData ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EntryFile::create([
|
|
||||||
'entry_id' => $entry->id,
|
|
||||||
'file_uuid' => $fileData['uuid'],
|
|
||||||
'filename' => $fileData['filename'],
|
|
||||||
'filepath' => $fileData['filepath'],
|
|
||||||
'favorite_server' => $fileData['favorite_server'],
|
|
||||||
'favorite_at' => \DateTimeImmutable::createFromTimestamp( $fileData['favorite_at'] ),
|
|
||||||
'filesize' => $fileData['filesize'],
|
|
||||||
'state' => 'public'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$existingGalleryIds = $request->input('existing_gallery_ids', []);
|
|
||||||
if (!is_array($existingGalleryIds)) {
|
|
||||||
$existingGalleryIds = [];
|
|
||||||
}
|
|
||||||
$entry->gallery()->whereNotIn('id', $existingGalleryIds)->get()->each(function ($gallery) {
|
|
||||||
if ($gallery->image) {
|
|
||||||
Storage::disk('public')->delete($gallery->image);
|
|
||||||
}
|
|
||||||
$gallery->delete();
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach ( $request->file('gallery', [] ) as $galleryFile ){
|
|
||||||
if( !$galleryFile->isValid() ){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $galleryFile->store('entries/gallery/' . $entry->id, 'public');
|
|
||||||
EntryGallery::create([
|
|
||||||
'entry_id' => $entry->id,
|
|
||||||
'image' => $path
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return match( $entry->state ){
|
|
||||||
'published' => redirect()->route('entries.show', [ 'section' => $section, 'entry' => $entry->slug ])->with('success', "Your entry has been published."),
|
|
||||||
'pending' => redirect()->route('home')->with('success', "Your entry has been submitted and is pending review."),
|
|
||||||
default => redirect()->route('home')->with('success', "Your entry has been saved as a draft.")
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
app/Http/Controllers/WebhookController.php
Normal file
29
app/Http/Controllers/WebhookController.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class WebhookController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function XenForoNewPost(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
if( $request->header('XF-Webhook-Secret') !== env('WEBHOOK_SECRET') )
|
||||||
|
abort(403);
|
||||||
|
|
||||||
|
$threadId = $request->input('data.thread_id');
|
||||||
|
if( $threadId ){
|
||||||
|
$entry = Entry::where('comments_thread_id', $threadId)->first();
|
||||||
|
if( $entry ){
|
||||||
|
Cache::forget("entry_comments_{$entry->id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ class CheckXenForoPermissions
|
|||||||
foreach ($permissions as $permissionStr) {
|
foreach ($permissions as $permissionStr) {
|
||||||
[$group, $permission] = explode('.', $permissionStr);
|
[$group, $permission] = explode('.', $permissionStr);
|
||||||
|
|
||||||
if( !\Auth::user()->can($group, $permission) )
|
if( !\Auth::user()->_can($group, $permission) )
|
||||||
return $this->deny($request, $permission);
|
return $this->deny($request, $permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ class StoreEntryRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
// TODO: Change it by role.
|
$entry = $this->route('entry');
|
||||||
return true;
|
if( $entry )
|
||||||
|
return $this->user()->can('update', $entry);
|
||||||
|
|
||||||
|
return $this->user()->can('create', '\App\Models\Entry');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Services\TemporaryFileService;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ class TemporaryFileUploadRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
return true;
|
return $this->user()->can('create', TemporaryFileService::class );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class Entry extends Model
|
|||||||
'youtube_link',
|
'youtube_link',
|
||||||
'user_id',
|
'user_id',
|
||||||
'complete_title',
|
'complete_title',
|
||||||
|
'comments_thread_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Auth\XenForoGuard;
|
use App\Auth\XenForoGuard;
|
||||||
|
use App\Policies\TempFilePolicy;
|
||||||
|
use App\Services\TemporaryFileService;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -24,5 +26,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
\Auth::extend('xenforo', function ($app, $name, array $config) {
|
\Auth::extend('xenforo', function ($app, $name, array $config) {
|
||||||
return new XenForoGuard($app['request']);
|
return new XenForoGuard($app['request']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
\Gate::policy(TemporaryFileService::class, TempFilePolicy::class );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Services;
|
|||||||
use App\Exceptions\SubmissionException;
|
use App\Exceptions\SubmissionException;
|
||||||
use App\Helpers\EntryHelpers;
|
use App\Helpers\EntryHelpers;
|
||||||
use App\Http\Requests\StoreEntryRequest;
|
use App\Http\Requests\StoreEntryRequest;
|
||||||
|
use App\Jobs\CreateXenForoCommentsThread;
|
||||||
use App\Models\Author;
|
use App\Models\Author;
|
||||||
use App\Models\Entry;
|
use App\Models\Entry;
|
||||||
use App\Models\EntryFile;
|
use App\Models\EntryFile;
|
||||||
@@ -36,6 +37,12 @@ class SubmissionsService {
|
|||||||
*/
|
*/
|
||||||
private ?string $section = null;
|
private ?string $section = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry for edit.
|
||||||
|
* @var Entry|null
|
||||||
|
*/
|
||||||
|
private ?Entry $entry = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<FSFileData>
|
* @return list<FSFileData>
|
||||||
*/
|
*/
|
||||||
@@ -174,6 +181,9 @@ class SubmissionsService {
|
|||||||
$this->Step12b_MoveMainImage( $entry );
|
$this->Step12b_MoveMainImage( $entry );
|
||||||
$this->Step12c_SaveGalleryImages( $entry );
|
$this->Step12c_SaveGalleryImages( $entry );
|
||||||
|
|
||||||
|
// Step 13: Try to create the comments section.
|
||||||
|
$this->Step13_CreateCommentsThread( $entry );
|
||||||
|
|
||||||
return $entry;
|
return $entry;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -190,6 +200,13 @@ class SubmissionsService {
|
|||||||
return $this->request->input('game_id');
|
return $this->request->input('game_id');
|
||||||
|
|
||||||
// Need to create a game.
|
// Need to create a game.
|
||||||
|
$game = $this->createGameFromFormFields();
|
||||||
|
|
||||||
|
return $game->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createGameFromFormFields(): Game
|
||||||
|
{
|
||||||
if( !$this->request->input('new-game-title') || !$this->request->input('new-game-platform') || !$this->request->input('new-game-genre') )
|
if( !$this->request->input('new-game-title') || !$this->request->input('new-game-platform') || !$this->request->input('new-game-genre') )
|
||||||
throw new SubmissionException( "New game informations is missing" );
|
throw new SubmissionException( "New game informations is missing" );
|
||||||
|
|
||||||
@@ -201,14 +218,12 @@ class SubmissionsService {
|
|||||||
|
|
||||||
$gameSlug = EntryHelpers::uniqueSlug( $this->request->input('new-game-title'), Game::class );
|
$gameSlug = EntryHelpers::uniqueSlug( $this->request->input('new-game-title'), Game::class );
|
||||||
|
|
||||||
$game = Game::create([
|
return Game::create([
|
||||||
'name' => trim( $this->request->input('new-game-title') ),
|
'name' => trim( $this->request->input('new-game-title') ),
|
||||||
'slug' => $gameSlug,
|
'slug' => $gameSlug,
|
||||||
'platform_id' => $platform->id,
|
'platform_id' => $platform->id,
|
||||||
'genre_id' => $genre->id,
|
'genre_id' => $genre->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $game->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,7 +243,7 @@ class SubmissionsService {
|
|||||||
if( section_must_be( 'translations', $this->section ) ) {
|
if( section_must_be( 'translations', $this->section ) ) {
|
||||||
$fields['languages_string'] = Language::whereIn('id', $this->request->input('languages', []))->pluck('name')->implode(', ');
|
$fields['languages_string'] = Language::whereIn('id', $this->request->input('languages', []))->pluck('name')->implode(', ');
|
||||||
}
|
}
|
||||||
if( section_must_be(['romhacks', 'homebrew', 'lua-scripts', 'tutorials'], $this->section ) ) {
|
if( section_must_be(['romhacks', 'translations', 'homebrew', 'lua-scripts', 'tutorials'], $this->section ) ) {
|
||||||
// TODO: Add single platform ID compatibility.
|
// TODO: Add single platform ID compatibility.
|
||||||
$fields['platform_name'] = Game::find( $gameId )->platform->name;
|
$fields['platform_name'] = Game::find( $gameId )->platform->name;
|
||||||
}
|
}
|
||||||
@@ -242,12 +257,15 @@ class SubmissionsService {
|
|||||||
* @return void
|
* @return void
|
||||||
* @throws SubmissionException
|
* @throws SubmissionException
|
||||||
*/
|
*/
|
||||||
private function Step7_SaveEntryFiles( int $entryId ): void
|
private function Step7_SaveEntryFiles( int $entryId, ?array $uuidData = null ): void
|
||||||
{
|
{
|
||||||
foreach ( $this->request->input('files_uuid', [] ) as $uuid ) {
|
if( !$uuidData )
|
||||||
|
$uuidData = $this->request->input('files_uuid', [] );
|
||||||
|
|
||||||
|
foreach ( $uuidData as $uuid ) {
|
||||||
$fileData = Cache::pull("uploaded_file_{$uuid}");
|
$fileData = Cache::pull("uploaded_file_{$uuid}");
|
||||||
if( !$fileData )
|
if( !$fileData )
|
||||||
throw new SubmissionException( "File {$uuid} has expired. Please delete all your files and retry." );
|
throw new SubmissionException( "File {$uuid} has expired. Please delete all your files and retry. If it's an edition, delete all your new files and retry." );
|
||||||
|
|
||||||
EntryFile::create([
|
EntryFile::create([
|
||||||
'entry_id' => $entryId,
|
'entry_id' => $entryId,
|
||||||
@@ -293,6 +311,8 @@ class SubmissionsService {
|
|||||||
*/
|
*/
|
||||||
private function Step9_SaveAuthors( Entry $entry ): void
|
private function Step9_SaveAuthors( Entry $entry ): void
|
||||||
{
|
{
|
||||||
|
// TODO: Code fragment to be replaced by edit version.
|
||||||
|
|
||||||
// Existing authors.
|
// Existing authors.
|
||||||
foreach ( $this->request->input('authors', [] ) as $authorId ) {
|
foreach ( $this->request->input('authors', [] ) as $authorId ) {
|
||||||
$author = Author::find( $authorId );
|
$author = Author::find( $authorId );
|
||||||
@@ -323,6 +343,9 @@ class SubmissionsService {
|
|||||||
*/
|
*/
|
||||||
private function Step10_SaveRomhacksModifications( Entry $entry ): void
|
private function Step10_SaveRomhacksModifications( Entry $entry ): void
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// TODO: Replace by edit version
|
||||||
|
|
||||||
foreach ( $this->request->input('modifications', [] ) as $modificationId ) {
|
foreach ( $this->request->input('modifications', [] ) as $modificationId ) {
|
||||||
$modification = Modification::find( $modificationId );
|
$modification = Modification::find( $modificationId );
|
||||||
if( !$modification )
|
if( !$modification )
|
||||||
@@ -339,6 +362,8 @@ class SubmissionsService {
|
|||||||
*/
|
*/
|
||||||
private function Step11_SaveLanguages( Entry $entry ): void
|
private function Step11_SaveLanguages( Entry $entry ): void
|
||||||
{
|
{
|
||||||
|
// TODO: Replace by edit version.
|
||||||
|
|
||||||
foreach ( $this->request->input('languages', [] ) as $languageId ) {
|
foreach ( $this->request->input('languages', [] ) as $languageId ) {
|
||||||
$language = Language::find( $languageId );
|
$language = Language::find( $languageId );
|
||||||
if( !$language )
|
if( !$language )
|
||||||
@@ -385,4 +410,325 @@ class SubmissionsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function editEntry( StoreEntryRequest $request, string $section, Entry $entry ): Entry
|
||||||
|
{
|
||||||
|
|
||||||
|
// STEP 1: Prepare basic fields and keep in save some others fields.
|
||||||
|
$this->request = $request;
|
||||||
|
$this->section = $section;
|
||||||
|
$this->entry = $entry;
|
||||||
|
$user_id = 0; // TODO: Replace that.
|
||||||
|
|
||||||
|
$oldMainImage = $entry->main_image;
|
||||||
|
$galleryPaths = [];
|
||||||
|
|
||||||
|
$entry = DB::transaction( function() use ( $user_id, &$galleryPaths ){
|
||||||
|
|
||||||
|
// STEP 2: Create game if different.
|
||||||
|
$gameId = null;
|
||||||
|
if( section_must_be( ['romhacks', 'translations' ], $this->section ) ){
|
||||||
|
$gameId = $this->eStep2_VerifyCreateAndEditGameId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3: Recreate complete title and refresh slug if needed.
|
||||||
|
$completeTitle = $this->Step3_BuildCompleteTitle( $gameId );
|
||||||
|
if( $completeTitle !== $this->entry->complete_title ) {
|
||||||
|
$this->entry->complete_title = $completeTitle;
|
||||||
|
$this->entry->slug = EntryHelpers::uniqueSlug( $completeTitle, Entry::class, $this->entry->id );
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 4: Regenerate entry title.
|
||||||
|
|
||||||
|
if( section_must_be( 'translations', $this->section ) &&
|
||||||
|
!$this->request->input('entry_title') ){
|
||||||
|
$this->entry->title = Game::find($gameId)->name;
|
||||||
|
} else {
|
||||||
|
$this->entry->title = $this->request->input('entry_title');
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 5: Update entry fields.
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'type' => $this->section,
|
||||||
|
'title' => $this->entry->title, // Useless, I know.
|
||||||
|
'slug' => $this->entry->slug,
|
||||||
|
'description' => $this->request->input('description'),
|
||||||
|
'main_image' => $this->request->input('main-image'),
|
||||||
|
'state' => $this->request->input('submit-state'),
|
||||||
|
'game_id' => $gameId,
|
||||||
|
'status_id' => $this->request->input('status'),
|
||||||
|
'version' => $this->request->input('version'),
|
||||||
|
'release_date' => $this->request->input('release-date'),
|
||||||
|
'staff_credits' => $this->request->input('staff_credits'),
|
||||||
|
'relevant_link' => $this->request->input('release_site'),
|
||||||
|
'youtube_link' => $this->request->input('youtube_video'),
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'complete_title' => $completeTitle,
|
||||||
|
];
|
||||||
|
$this->entry->update( $fields );
|
||||||
|
|
||||||
|
// STEP 6: Update entry files.
|
||||||
|
$this->eStep6_UpdateEntryFiles( $this->entry->id );
|
||||||
|
|
||||||
|
// STEP 7: Update hashes.
|
||||||
|
$this->eStep7_UpdateHashes( $this->entry->id );
|
||||||
|
|
||||||
|
// STEP 8: Update Authors.
|
||||||
|
$this->eStep8_UpdateAuthors();
|
||||||
|
|
||||||
|
// STEP 9: Update romhacks modifications.
|
||||||
|
if( section_must_be( 'romhacks', $this->section ) ) {
|
||||||
|
$this->eStep9_UpdateRomhacksModifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 10: Update Languages.
|
||||||
|
$this->eStep10_UpdateLanguages();
|
||||||
|
|
||||||
|
// STEP 11: Prepare new gallery images and prepare deletion of others ones.
|
||||||
|
$galleryPaths = $this->eStep11a_UpdateGalleryImages();
|
||||||
|
|
||||||
|
// STEP 13: Try to create comments area if it doesn't exist.
|
||||||
|
$this->Step13_CreateCommentsThread( $this->entry );
|
||||||
|
|
||||||
|
return $this->entry;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// STEP 11 : Update main image if needed.
|
||||||
|
$this->eStep11b_UpdateMainImage( $oldMainImage );
|
||||||
|
|
||||||
|
// STEP 11 : Update gallery storage.
|
||||||
|
$this->eStep11c_UpdateGalleryImages( $galleryPaths );
|
||||||
|
|
||||||
|
return $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SubmissionException
|
||||||
|
*/
|
||||||
|
private function eStep2_VerifyCreateAndEditGameId(): int
|
||||||
|
{
|
||||||
|
// Already existing game.
|
||||||
|
if( $this->request->input('game_id') ){
|
||||||
|
|
||||||
|
if( $this->entry->game_id == $this->request->input('game_id') ){
|
||||||
|
|
||||||
|
return $this->entry->game_id; // No changes.
|
||||||
|
|
||||||
|
} else { // Change in game but already exist.
|
||||||
|
|
||||||
|
$game = Game::find( $this->request->input('game_id') );
|
||||||
|
if( !$game )
|
||||||
|
throw new SubmissionException( "Game {$this->request->input('game_id')} does not exist." );
|
||||||
|
$this->entry->game_id = $game->id;
|
||||||
|
return $this->entry->game_id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to create a game.
|
||||||
|
$game = $this->createGameFromFormFields();
|
||||||
|
|
||||||
|
$this->entry->game_id = $game->id;
|
||||||
|
return $this->entry->game_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SubmissionException
|
||||||
|
*/
|
||||||
|
private function eStep6_UpdateEntryFiles(int $entryId ): void
|
||||||
|
{
|
||||||
|
$requestUuids = $this->request->input('files_uuid', []);
|
||||||
|
$existingUuids = EntryFile::where( 'entry_id', $entryId )->pluck('file_uuid')->toArray();
|
||||||
|
|
||||||
|
$needDeletion = array_diff( $existingUuids, $requestUuids );
|
||||||
|
if( !empty( $needDeletion ) ){
|
||||||
|
EntryFile::where('entry_id', $entryId)->whereIn('file_uuid', $needDeletion)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$needAddition = array_diff( $requestUuids, $existingUuids );
|
||||||
|
|
||||||
|
if( !empty( $needAddition ) ){
|
||||||
|
$this->Step7_SaveEntryFiles( $this->entry->id, $needAddition ); // Same code.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function eStep7_UpdateHashes(int $entryId): void
|
||||||
|
{
|
||||||
|
$requestHashes = collect( $this->request->input('hashes', [] ) )
|
||||||
|
->filter( fn($h) => isset( $h['filename'], $h['hash_crc32'], $h['hash_sha1'], $h['verified'] ) )
|
||||||
|
->keyBy( 'hash_sha1' )
|
||||||
|
->toArray();
|
||||||
|
;
|
||||||
|
|
||||||
|
$existingHashes = EntryHash::where( 'entry_id', $entryId )->get()->keyBy( 'hash_sha1' );
|
||||||
|
|
||||||
|
$hashsToDelete = array_diff( $existingHashes->keys()->toArray(), array_keys( $requestHashes ) );
|
||||||
|
|
||||||
|
if( !empty( $hashsToDelete ) ){
|
||||||
|
EntryHash::where( 'entry_id', $entryId )->whereIn('hash_sha1', $hashsToDelete)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( $requestHashes as $sha1 => $hash ){
|
||||||
|
if( $existingHashes->has( $sha1 ) ){
|
||||||
|
$existingHashes->get( $sha1 )->update([
|
||||||
|
'filename' => $hash['filename'],
|
||||||
|
'hash_crc32' => $hash['hash_crc32'],
|
||||||
|
'hash_sha1' => $hash['hash_sha1'],
|
||||||
|
'verified' => $hash['verified'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
EntryHash::create([
|
||||||
|
'entry_id' => $entryId,
|
||||||
|
'filename' => $hash['filename'],
|
||||||
|
'hash_crc32' => $hash['hash_crc32'],
|
||||||
|
'hash_sha1' => $hash['hash_sha1'],
|
||||||
|
'verified' => $hash['verified'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws SubmissionException
|
||||||
|
*/
|
||||||
|
private function eStep8_UpdateAuthors(): void
|
||||||
|
{
|
||||||
|
$syncAuthorsId = [];
|
||||||
|
$requestAuthorsId = $this->request->input('authors', [] );
|
||||||
|
|
||||||
|
if( !empty( $requestAuthorsId ) ){
|
||||||
|
$valid = Author::whereIn( 'id', $requestAuthorsId )->pluck('id')->toArray();
|
||||||
|
|
||||||
|
if( count( $valid ) !== count( $requestAuthorsId ) ){
|
||||||
|
throw new SubmissionException( "One of the authors doesn't exist." );
|
||||||
|
}
|
||||||
|
|
||||||
|
$syncAuthorsId = array_merge( $syncAuthorsId, $requestAuthorsId );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $this->request->input('new-authors', [] ) as $authorName ) {
|
||||||
|
$authorName = trim($authorName);
|
||||||
|
if ($authorName === '')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$author = Author::firstOrCreate(
|
||||||
|
['slug' => EntryHelpers::uniqueSlug($authorName, Author::class)],
|
||||||
|
['name' => $authorName]
|
||||||
|
);
|
||||||
|
|
||||||
|
$syncAuthorsId[] = $author->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entry->authors()->sync( $syncAuthorsId );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws SubmissionException
|
||||||
|
*/
|
||||||
|
private function eStep9_UpdateRomhacksModifications(): void
|
||||||
|
{
|
||||||
|
$requestModifications = $this->request->input('modifications', [] );
|
||||||
|
if( !empty( $requestModifications ) ){
|
||||||
|
$valid = Modification::whereIn( 'id', $requestModifications )->pluck('id')->toArray();
|
||||||
|
|
||||||
|
if( count( $valid ) !== count( $requestModifications ) ){
|
||||||
|
throw new SubmissionException( "One of the modifications doesn't exist." );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entry->modifications()->sync( $requestModifications );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws SubmissionException
|
||||||
|
*/
|
||||||
|
private function eStep10_UpdateLanguages(): void
|
||||||
|
{
|
||||||
|
$requestLanguages = $this->request->input('languages', [] );
|
||||||
|
if( !empty( $requestLanguages ) ){
|
||||||
|
$valid = Language::whereIn( 'id', $requestLanguages )->pluck('id')->toArray();
|
||||||
|
if( count( $valid ) !== count( $requestLanguages ) ){
|
||||||
|
throw new SubmissionException( "One of the languages doesn't exist." );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entry->languages()->sync( $requestLanguages );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function eStep11a_UpdateGalleryImages(): array
|
||||||
|
{
|
||||||
|
$requestGallery = $this->request->input('gallery', [] );
|
||||||
|
$existingGalleryPaths = $this->entry->gallery->pluck('image')->toArray();
|
||||||
|
|
||||||
|
$needDeletion = array_diff( $existingGalleryPaths, $requestGallery );
|
||||||
|
|
||||||
|
if( !empty( $needDeletion ) ){
|
||||||
|
EntryGallery::where('entry_id', $this->entry->id)->whereIn('image', $needDeletion )->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$needAddition = array_diff( $requestGallery, $existingGalleryPaths );
|
||||||
|
$images = [];
|
||||||
|
foreach( $needAddition as $imagePath ){
|
||||||
|
$images[] = EntryGallery::create([
|
||||||
|
'entry_id' => $this->entry->id,
|
||||||
|
'image' => $imagePath,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ 'addition' => $images, 'deletion' => $needDeletion ];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function eStep11b_UpdateMainImage( ?string $oldMainImagePath ): void
|
||||||
|
{
|
||||||
|
$currentMainImagePath = $this->entry->main_image;
|
||||||
|
|
||||||
|
if( $currentMainImagePath === $oldMainImagePath )
|
||||||
|
return;
|
||||||
|
|
||||||
|
$newPath = 'entries/main-images/' . basename( $currentMainImagePath );
|
||||||
|
|
||||||
|
if( !Storage::disk('public')->move( $currentMainImagePath, $newPath ) ){
|
||||||
|
$this->entry->update(['main_image' => $oldMainImagePath]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entry->update(['main_image' => $newPath]);
|
||||||
|
if( $oldMainImagePath && Storage::disk('public')->exists($oldMainImagePath) )
|
||||||
|
Storage::disk('public')->delete($oldMainImagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function eStep11c_UpdateGalleryImages( array $pathsChanges ): void
|
||||||
|
{
|
||||||
|
foreach ( $pathsChanges['deletion'] as $deletePath ){
|
||||||
|
if( Storage::disk('public')->exists($deletePath) )
|
||||||
|
Storage::disk('public')->delete($deletePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $pathsChanges['addition'] as $galleryItem ){
|
||||||
|
$newPath = 'entries/gallery-images/' . $this->entry->id . '/' . basename( $galleryItem->image );
|
||||||
|
|
||||||
|
if( !Storage::disk('public')->move( $galleryItem->image, $newPath ) ){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$galleryItem->update(['image' => $newPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function Step13_CreateCommentsThread( Entry $entry ): void
|
||||||
|
{
|
||||||
|
if( !$entry->comments_thread_id )
|
||||||
|
CreateXenForoCommentsThread::dispatch( $entry );
|
||||||
|
// app(XenforoApiService::class)->createCommentsThread( $entry );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
use Illuminate\Http\Client\ConnectionException;
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
@@ -71,4 +72,40 @@ class XenforoApiService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createCommentsThread( Entry $entry ): bool
|
||||||
|
{
|
||||||
|
if( !$entry->comments_thread_id || $entry->comments_thread_id <= 0 ){
|
||||||
|
$data = [
|
||||||
|
'node_id' => config('xenforo.comments_node_id'),
|
||||||
|
'title' => $entry->complete_title,
|
||||||
|
'message' => $entry->description,
|
||||||
|
'prefix_id' => config('xenforo.comments_prefixes')[$entry->type] ?? 1,
|
||||||
|
'custom_fields' => [ 'entry_id' => $entry->id ],
|
||||||
|
'discussion_open' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: Flag must be removed.
|
||||||
|
$response = $this->post("threads?api_bypass_permissions=true", config('xenforo.bot_user_id'), $data );
|
||||||
|
if( $response['success'] === true ){
|
||||||
|
$commentsThreadId = $response['thread']['thread_id'];
|
||||||
|
$entry->update(['comments_thread_id' => $commentsThreadId]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConnectionException
|
||||||
|
*/
|
||||||
|
public function getThreadPosts(int $threadId, int $page = 1 ): array
|
||||||
|
{
|
||||||
|
$response = $this->get("threads/{$threadId}/posts?page=$page");
|
||||||
|
if( !isset( $response['posts'] ) || $response['posts'] === [] )
|
||||||
|
return [ 'posts' => [], 'pagination' => null ];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\View\Components;
|
namespace App\View\Components;
|
||||||
|
|
||||||
use App\Auth\XenForoUser;
|
use App\Auth\XenForoUser;
|
||||||
|
use App\Services\XenforoService;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
@@ -13,11 +14,13 @@ class XenForoAvatar extends Component
|
|||||||
* Create a new component instance.
|
* Create a new component instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ?XenForoUser $user = null,
|
public null|int|XenForoUser $user = null,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if( $this->user === null )
|
if( $this->user === null )
|
||||||
$this->user = \Auth::user();
|
$this->user = \Auth::user();
|
||||||
|
else if( is_int( $this->user ) )
|
||||||
|
$this->user = app(XenforoService::class)->getXfUser( $this->user );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,3 +11,9 @@ if( !function_exists( 'xfCsrfToken') ){
|
|||||||
return app(\App\Services\XenforoService::class)->getCSRFToken();
|
return app(\App\Services\XenforoService::class)->getCSRFToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( !function_exists( 'xfStyleVariationUrl' ) ){
|
||||||
|
function xfStyleVariationUrl( string $variation ): string {
|
||||||
|
return config('app.forum_url') . '/misc/style-variation?variation=' . $variation . "&t=" . xfCsrfToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->encryptCookies(except: ['xf_session','xf_user','xf_csrf']);
|
$middleware->encryptCookies(except: ['xf_session','xf_user','xf_csrf','theme','entries_per_page']);
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'xf.auth' => \App\Http\Middleware\CheckXenForoPermissions::class,
|
'xf.auth' => \App\Http\Middleware\CheckXenForoPermissions::class,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ return [
|
|||||||
],
|
],
|
||||||
'xenforo' => [
|
'xenforo' => [
|
||||||
'driver' => 'xenforo',
|
'driver' => 'xenforo',
|
||||||
|
'provider' => 'users',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ return [
|
|||||||
[
|
[
|
||||||
'name' => 'Contact Us',
|
'name' => 'Contact Us',
|
||||||
'icon' => 'at-sign',
|
'icon' => 'at-sign',
|
||||||
'route' => 'home'
|
'xf_route' => 'misc/contact'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Legal pages',
|
'name' => 'Legal pages',
|
||||||
|
|||||||
18
config/xenforo.php
Normal file
18
config/xenforo.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'bot_user_id' => 1,
|
||||||
|
|
||||||
|
'comments_node_id' => 4,
|
||||||
|
|
||||||
|
'comments_prefixes' => [
|
||||||
|
'translations' => 1,
|
||||||
|
'romhacks' => 2,
|
||||||
|
'homebrew' => 3,
|
||||||
|
'utilities' => 4,
|
||||||
|
'documents' => 5,
|
||||||
|
'lua-scripts' => 6,
|
||||||
|
'tutorials' => 7,
|
||||||
|
'news' => 8
|
||||||
|
],
|
||||||
|
];
|
||||||
185
extra.less
185
extra.less
@@ -71,6 +71,32 @@ ul {
|
|||||||
--menu-user-avatar-bg: #555;
|
--menu-user-avatar-bg: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.\$light-mode {
|
||||||
|
|
||||||
|
/* RHPZ color */
|
||||||
|
--rhpz-orange: #ff7300;
|
||||||
|
--rhpz-orange-hover: #e56700;
|
||||||
|
|
||||||
|
/* Background colors */
|
||||||
|
--bg: #f0f0f0;
|
||||||
|
--bg2: #ffffff;
|
||||||
|
--bg3: #e8e8e8;
|
||||||
|
--bg4: #dcdcdc;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text: #454545;
|
||||||
|
--text2: #737373;
|
||||||
|
--text3: #111111;
|
||||||
|
|
||||||
|
/* Elements */
|
||||||
|
--border: #d0d0d0;
|
||||||
|
--error: #e57373;
|
||||||
|
--info: #1976d2;
|
||||||
|
--success: #81c784;
|
||||||
|
--success2: #388e3c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* File: resources/css/components/cards.css */
|
/* File: resources/css/components/cards.css */
|
||||||
/* STAT CARDS */
|
/* STAT CARDS */
|
||||||
@@ -1695,6 +1721,165 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* File: resources/css/components/settings.css */
|
||||||
|
.\$settings-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
width: 240px;
|
||||||
|
background-color: var(--bg2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background-color: var(--bg3);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-section {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.7px;
|
||||||
|
color: var(--text2);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-separator {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-themes {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-theme-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text);
|
||||||
|
transition: transform 0.15s, border-color 0.15s;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$active {
|
||||||
|
border-color: var(--text);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-theme-toggle {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--typography);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-theme-toggle-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-theme-toggle-badge {
|
||||||
|
margin-left: auto;
|
||||||
|
background-color: var(--rhpz-orange);
|
||||||
|
color: #111;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-perpage {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-perpage-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 4px;
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--typography);
|
||||||
|
transition: all 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$active {
|
||||||
|
background-color: var(--rhpz-orange);
|
||||||
|
border-color: var(--rhpz-orange);
|
||||||
|
color: var(--text3);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border-color: var(--border);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.\$settings-link--danger {
|
||||||
|
color: var(--error);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(229, 115, 115, 0.08);
|
||||||
|
border-color: rgba(229, 115, 115, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* File: resources/css/layout/content.css */
|
/* File: resources/css/layout/content.css */
|
||||||
#main-wrapper {
|
#main-wrapper {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -6,6 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"easymde": "^2.21.0",
|
"easymde": "^2.21.0",
|
||||||
|
"js-cookie": "^3.0.7",
|
||||||
"lucide": "^1.14.0"
|
"lucide": "^1.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -971,6 +972,15 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/laravel-vite-plugin": {
|
"node_modules/laravel-vite-plugin": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.1.0.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"easymde": "^2.21.0",
|
"easymde": "^2.21.0",
|
||||||
|
"js-cookie": "^3.0.7",
|
||||||
"lucide": "^1.14.0"
|
"lucide": "^1.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
@import './components/database.css';
|
@import './components/database.css';
|
||||||
@import './components/hovercard.css';
|
@import './components/hovercard.css';
|
||||||
@import './components/notifications.css';
|
@import './components/notifications.css';
|
||||||
|
@import './components/settings.css';
|
||||||
|
|
||||||
@import './components/easymde.css';
|
@import './components/easymde.css';
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,29 @@
|
|||||||
--menu-size: 260px;
|
--menu-size: 260px;
|
||||||
--menu-user-avatar-bg: #555;
|
--menu-user-avatar-bg: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light-mode {
|
||||||
|
|
||||||
|
/* RHPZ color */
|
||||||
|
--rhpz-orange: #ff7300;
|
||||||
|
--rhpz-orange-hover: #e56700;
|
||||||
|
|
||||||
|
/* Background colors */
|
||||||
|
--bg: #f0f0f0;
|
||||||
|
--bg2: #ffffff;
|
||||||
|
--bg3: #e8e8e8;
|
||||||
|
--bg4: #dcdcdc;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--text: #454545;
|
||||||
|
--text2: #737373;
|
||||||
|
--text3: #111111;
|
||||||
|
|
||||||
|
/* Elements */
|
||||||
|
--border: #d0d0d0;
|
||||||
|
--error: #e57373;
|
||||||
|
--info: #1976d2;
|
||||||
|
--success: #81c784;
|
||||||
|
--success2: #388e3c;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,13 @@
|
|||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.block-success {
|
||||||
|
background-color: var(--success);
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
.block-error {
|
.block-error {
|
||||||
background-color: var(--error);
|
background-color: var(--error);
|
||||||
border: 1px solid var(--error);
|
border: 1px solid var(--error);
|
||||||
|
|||||||
156
resources/css/components/settings.css
Normal file
156
resources/css/components/settings.css
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
.settings-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
width: 240px;
|
||||||
|
background-color: var(--bg2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background-color: var(--bg3);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.7px;
|
||||||
|
color: var(--text2);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-separator {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-themes {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-theme-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text);
|
||||||
|
transition: transform 0.15s, border-color 0.15s;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border-color: var(--text);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-theme-toggle {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--typography);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-theme-toggle-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-theme-toggle-badge {
|
||||||
|
margin-left: auto;
|
||||||
|
background-color: var(--rhpz-orange);
|
||||||
|
color: #111;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-perpage {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-perpage-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 4px;
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--typography);
|
||||||
|
transition: all 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg4);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: var(--rhpz-orange);
|
||||||
|
border-color: var(--rhpz-orange);
|
||||||
|
color: var(--text3);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border-color: var(--border);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-link--danger {
|
||||||
|
color: var(--error);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(229, 115, 115, 0.08);
|
||||||
|
border-color: rgba(229, 115, 115, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#entry-container {
|
#entry-container, #comments-section, #reviews-section {
|
||||||
background-color: var(--bg2);
|
background-color: var(--bg2);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -128,3 +128,112 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: var( --text2 );
|
color: var( --text2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-block {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--bg4);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.comment-meta {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: var(--text2);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.comment-author {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--rhpz-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-date {
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-separator {
|
||||||
|
color: var(--border);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-body {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.5;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
&:last-child { margin-bottom: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--rhpz-orange);
|
||||||
|
&:hover {
|
||||||
|
color: var(--rhpz-orange-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote, .bbCodeBlock-blockquote {
|
||||||
|
background-color: var(--bg);
|
||||||
|
border-left: 3px solid var(--info);
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 12px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: var(--bg3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: var(--text2);
|
||||||
|
font-style: italic;
|
||||||
|
background-color: var(--bg);
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ export function GalleryManager() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
const IMG = GalleryImage();
|
const IMG = GalleryImage();
|
||||||
IMG.getOldImage( PATH );
|
IMG.serverFilePath = PATH;
|
||||||
this.images.push(IMG);
|
this.images.push(IMG);
|
||||||
|
this.images[this.images.length - 1].getOldImage( PATH );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ export function MainImageManager() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for gallery managament and indexation.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
key: crypto.randomUUID(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If an image has been uploaded or not.
|
* If an image has been uploaded or not.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|||||||
@@ -6,7 +6,16 @@ import { calculate as calculateHashes } from "./hashes.js";
|
|||||||
import hovercard from "./hovercard.js";
|
import hovercard from "./hovercard.js";
|
||||||
import notifications from "./notifications.js";
|
import notifications from "./notifications.js";
|
||||||
import conversations from "./conversations.js";
|
import conversations from "./conversations.js";
|
||||||
|
import settings from "./settings.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get config defined in meta.blade.php
|
||||||
|
* @param {string} key
|
||||||
|
* @return {string|null}
|
||||||
|
*/
|
||||||
|
window.getConfig = function( key ){
|
||||||
|
return document.querySelector('meta[name="config-' + key + '"]').getAttribute('content') ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
// Lucide icons.
|
// Lucide icons.
|
||||||
createIcons({ icons });
|
createIcons({ icons });
|
||||||
@@ -31,3 +40,6 @@ Alpine.store('notifications', notifications() );
|
|||||||
|
|
||||||
// Conversations
|
// Conversations
|
||||||
Alpine.store('conversations', conversations() );
|
Alpine.store('conversations', conversations() );
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
Alpine.store('settings', settings() );
|
||||||
|
|||||||
83
resources/js/settings.js
Normal file
83
resources/js/settings.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
export default function settings() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
start: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two keys, default and alternate.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
xfUrls: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number[]}
|
||||||
|
*/
|
||||||
|
entriesPerPage: [ 12, 30, 48 ],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
currentTheme: Cookies.get("theme") ?? 'default',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
currentEntriesPerPage: Cookies.get("entries_per_page") ?? 30,
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} newTheme default|alternate
|
||||||
|
*/
|
||||||
|
themeChanged( newTheme ){
|
||||||
|
if( newTheme !== "default" && newTheme !== "alternate" )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( newTheme === this.currentTheme )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.currentTheme = newTheme;
|
||||||
|
document.documentElement.classList.toggle('light-mode', this.currentTheme === 'alternate');
|
||||||
|
|
||||||
|
Cookies.set('theme', this.currentTheme, { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
|
||||||
|
this.syncXF();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async syncXF(){
|
||||||
|
await fetch(this.xfUrls[this.currentTheme ?? 'default'], { method: "GET", credentials: "include", mode: "no-cors" });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toggleTheme(){
|
||||||
|
this.themeChanged(this.currentTheme === 'default' ? 'alternate' : 'default');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
entriesPerPageChanged( n ){
|
||||||
|
if( !this.entriesPerPage.includes(n) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.entriesPerPage = n;
|
||||||
|
Cookies.set('entries_per_page', this.entriesPerPage, { expires: 365, path: '/', domain: window.getConfig('session-domain') } );
|
||||||
|
if( window.Livewire ){
|
||||||
|
Livewire.dispatch('entriesPerPageChanged', {n});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
open(){ this.start = !this.start; },
|
||||||
|
close(){ this.start = false; },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-card-info">
|
<div class="entry-card-info">
|
||||||
<div class="entry-card-title">{{ $entry->title }}</div>
|
<a href="{{ route('entries.show', [ 'section' => $entry->type, 'entry' => $entry ] ) }}" class="entry-card-title">{{ $entry->title }}</a>
|
||||||
<div class="entry-card-author">
|
<div class="entry-card-author">
|
||||||
@forelse( $entry->authors as $author)
|
@forelse( $entry->authors as $author)
|
||||||
@if($loop->first)By @endif
|
@if($loop->first)By @endif
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
@foreach( $entry->modifications as $modif )
|
@foreach( $entry->modifications as $modif )
|
||||||
<span class="badge orange">{{ $modif->name }}</span>
|
<span class="badge orange">{{ $modif->name }}</span>
|
||||||
@endforeach
|
@endforeach
|
||||||
@if( $entry->status_id )
|
|
||||||
<span class="badge">{{ $entry->status->name }}</span>
|
|
||||||
@endif
|
|
||||||
@foreach( $entry->languages as $lang )
|
|
||||||
<span class="badge">{{ $lang->name }}</span>
|
|
||||||
@endforeach
|
|
||||||
@endif
|
@endif
|
||||||
|
@if( $entry->status_id )
|
||||||
|
<span class="badge">{{ $entry->status->name }}</span>
|
||||||
|
@endif
|
||||||
|
@foreach( $entry->languages as $lang )
|
||||||
|
<span class="badge">{{ $lang->name }}</span>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
<div class="entry-card-meta">
|
<div class="entry-card-meta">
|
||||||
<span><i data-lucide="download" size="12"></i> x</span>
|
<span><i data-lucide="download" size="12"></i> x</span>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-gallery form-group level" style="flex:4;">
|
<div class="form-gallery form-group level" style="flex:4;">
|
||||||
<template x-for="(image,i) in images" :key="image.serverFilePath">
|
<template x-for="(image,i) in images" :key="image.key">
|
||||||
<div class="gallery-item">
|
<div class="gallery-item">
|
||||||
<div class="form-image-preview-wrap">
|
<div class="form-image-preview-wrap">
|
||||||
<img :src="image.preview" :alt="image.name">
|
<img :src="image.preview" :alt="image.name">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template x-for="(image, i) in images" :key="image.serverFilePath">
|
<template x-for="(image, i) in images" :key="image.key">
|
||||||
<input type="hidden" name="gallery[]" :value="image.serverFilePath">
|
<input type="hidden" name="gallery[]" :value="image.serverFilePath">
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php /** @var \App\Models\Language $language */ ?>
|
<?php /** @var \App\Models\Language $language */ ?>
|
||||||
<div class="languages-selector form-group level" x-data="{
|
<div class="languages-selector form-group level" x-data="{
|
||||||
search: '',
|
search: '',
|
||||||
selected: {{ JS::from( (array) $selected ) }},
|
selected: @js((array) $selected),
|
||||||
toggle(value){
|
toggle(value){
|
||||||
const i = this.selected.indexOf(value);
|
const i = this.selected.indexOf(value);
|
||||||
i === -1 ? this.selected.push(value) : this.selected.splice(i,1);
|
i === -1 ? this.selected.push(value) : this.selected.splice(i,1);
|
||||||
|
|||||||
72
resources/views/components/settings-dropdown.blade.php
Normal file
72
resources/views/components/settings-dropdown.blade.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<div
|
||||||
|
x-data
|
||||||
|
x-show="$store.settings.start"
|
||||||
|
x-cloak
|
||||||
|
class="settings-dropdown"
|
||||||
|
@keydown.escape.window="$store.settings.close()"
|
||||||
|
>
|
||||||
|
<div class="settings-header">
|
||||||
|
<span>Settings</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="settings-section-title">
|
||||||
|
<i data-lucide="sun-moon" size="14"></i>
|
||||||
|
Theme
|
||||||
|
</div>
|
||||||
|
<button type="button" class="settings-theme-toggle" @click="$store.settings.toggleTheme()">
|
||||||
|
<template x-if="$store.settings.currentTheme === 'default'">
|
||||||
|
<span class="settings-theme-toggle-inner">
|
||||||
|
<i data-lucide="moon" size="15"></i>
|
||||||
|
Dark
|
||||||
|
<span class="settings-theme-toggle-badge">ON</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template x-if="$store.settings.currentTheme === 'alternate'">
|
||||||
|
<span class="settings-theme-toggle-inner">
|
||||||
|
<i data-lucide="sun" size="15"></i>
|
||||||
|
Light
|
||||||
|
<span class="settings-theme-toggle-badge">ON</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-separator"></div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="settings-section-title">
|
||||||
|
<i data-lucide="layout-grid" size="14"></i>
|
||||||
|
Entries per page
|
||||||
|
</div>
|
||||||
|
<div class="settings-perpage">
|
||||||
|
<template x-for="n in $store.settings.entriesPerPage" :key="n">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="settings-perpage-btn"
|
||||||
|
:class="{ 'active': $store.settings.currentEntriesPerPage == n }"
|
||||||
|
@click="$store.settings.entriesPerPageChanged(n)"
|
||||||
|
x-text="n"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-separator"></div>
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="settings-section-title">
|
||||||
|
<i data-lucide="user-cog" size="14"></i>
|
||||||
|
Account
|
||||||
|
</div>
|
||||||
|
<a href="{{ xfRoute('account/account-details') }}" class="settings-link">
|
||||||
|
<i data-lucide="settings-2" size="14"></i>
|
||||||
|
XenForo settings
|
||||||
|
<i data-lucide="external-link" size="12" style="margin-left:auto"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@php $topbarModSeparator = false; $topBarAdminSeparator = false; @endphp
|
@php $topbarModSeparator = false; $topbarAdminSeparator = false; @endphp
|
||||||
<header id="topbar">
|
<header id="topbar">
|
||||||
<button class="mobile-toggle">
|
<button class="mobile-toggle">
|
||||||
<i data-lucide="menu"></i>
|
<i data-lucide="menu"></i>
|
||||||
@@ -43,11 +43,11 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- Users --}}
|
{{-- Users --}}
|
||||||
@if( !\Auth::guest() && \Auth::user()->can('romhackplaza', 'canSubmitEntry') )
|
@can('create','\App\Models\Entry')
|
||||||
<a href="#" class="btn">
|
<a href="#" class="btn">
|
||||||
<i data-lucide="hard-drive-upload" size="18"></i>
|
<i data-lucide="hard-drive-upload" size="18"></i>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endcan
|
||||||
@if( !\Auth::guest() )
|
@if( !\Auth::guest() )
|
||||||
<div x-data x-init="$store.notifications.unviewed = {{ \Auth::user()->alerts_unviewed }}" style="position:relative">
|
<div x-data x-init="$store.notifications.unviewed = {{ \Auth::user()->alerts_unviewed }}" style="position:relative">
|
||||||
<button type="button" class="btn" :class="{ 'active': $store.notifications.start }" @click="$store.notifications.open($el)" @click.outside="$store.notifications.close()">
|
<button type="button" class="btn" :class="{ 'active': $store.notifications.start }" @click="$store.notifications.open($el)" @click.outside="$store.notifications.close()">
|
||||||
@@ -76,9 +76,19 @@
|
|||||||
@include('components.conversations')
|
@include('components.conversations')
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<button class="btn">
|
<div x-data style="position: relative;" x-init="$store.settings.xfUrls = { 'default': '{{ xfStyleVariationUrl( 'default' ) }}', 'alternate': '{{ xfStyleVariationUrl( 'alternate' ) }}' }">
|
||||||
<i data-lucide="settings" size="18"></i>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="{ 'active': $store.settings.start }"
|
||||||
|
@click="$store.settings.open()"
|
||||||
|
@click.outside="$store.settings.close()"
|
||||||
|
>
|
||||||
|
<i data-lucide="settings" size="18"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@include('components.settings-dropdown')
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
38
resources/views/entries/comments.blade.php
Normal file
38
resources/views/entries/comments.blade.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<div class="grid-c2" style="margin-top:1%;">
|
||||||
|
<div id="reviews-section">
|
||||||
|
<div class="entry-content">
|
||||||
|
<x-entry-section-title label="Reviews" icon="star" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="comments-section">
|
||||||
|
<div class="entry-content">
|
||||||
|
<x-entry-section-title label="Last comments" icon="message-circle" />
|
||||||
|
@forelse( $comments as $comment )
|
||||||
|
@if($comment['user_id'] === config('xenforo.bot_user_id') && $comment['position'] == 0)
|
||||||
|
@continue
|
||||||
|
@else
|
||||||
|
<div class="comment-block">
|
||||||
|
<x-xen-foro-avatar :user="$comment['user_id']" />
|
||||||
|
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="comment-meta">
|
||||||
|
<span class="comment-author">{{ $comment['User']['username'] }}</span>
|
||||||
|
<span class="comment-separator"></span>
|
||||||
|
<span class="comment-date">{{ date('Y-m-d', $comment['post_date']) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-body">
|
||||||
|
{!! $comment['message'] !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@empty
|
||||||
|
<span class="whisper">Be the first to post a comment</span>
|
||||||
|
@endforelse
|
||||||
|
<a href="{{ xfRoute("threads/{$entry->comments_thread_id}/latest") }}" class="btn primary" style="margin-top: 1%;">
|
||||||
|
<i data-lucide="pen"></i> Post a comment
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -59,9 +59,19 @@
|
|||||||
<button class="btn primary" onclick="Livewire.dispatch('entryOpenFilesModal', { entryId: {{ $entry->id }} })">
|
<button class="btn primary" onclick="Livewire.dispatch('entryOpenFilesModal', { entryId: {{ $entry->id }} })">
|
||||||
<i data-lucide="download"></i> Download
|
<i data-lucide="download"></i> Download
|
||||||
</button>
|
</button>
|
||||||
|
@can('update',$entry)
|
||||||
|
<a href="{{ route('submit.edit', ['section' => $entry->type, 'entry' => $entry ] ) }}" class="btn primary">
|
||||||
|
<i data-lucide="edit"></i> Edit
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
<button class="btn">
|
<button class="btn">
|
||||||
<i data-lucide="message-square"></i> Comments
|
<i data-lucide="message-square"></i> Comments
|
||||||
</button>
|
</button>
|
||||||
|
@auth
|
||||||
|
<a href="{{ xfRoute("romhackplaza_entry/{$entry->id}/report") }}" class="btn">
|
||||||
|
<i data-lucide="flag"></i> Report / Claim Ownership
|
||||||
|
</a>
|
||||||
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,5 +104,6 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
@include('entries.comments')
|
||||||
@livewire('entry-files-modal')
|
@livewire('entry-files-modal')
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="{{ \Illuminate\Support\Facades\Cookie::get('theme', 'default') === 'alternate' ? 'light-mode' : '' }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@include('meta')
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
@livewireStyles
|
@livewireStyles
|
||||||
@stack('styles')
|
@stack('styles')
|
||||||
@@ -15,14 +16,14 @@
|
|||||||
|
|
||||||
<main id="main-wrapper">
|
<main id="main-wrapper">
|
||||||
@include('components.topbar')
|
@include('components.topbar')
|
||||||
@if(session('success'))
|
|
||||||
<x-success-block success-type="custom" :message="session('success')" />
|
|
||||||
@endif
|
|
||||||
@if(session('error'))
|
|
||||||
<x-error-block error-type="custom" :message="session('error')" />
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
@if(session('success'))
|
||||||
|
<x-success-block success-type="custom" :message="session('success')" />
|
||||||
|
@endif
|
||||||
|
@if(session('error'))
|
||||||
|
<x-error-block error-type="custom" :message="session('error')" />
|
||||||
|
@endif
|
||||||
@yield('content')
|
@yield('content')
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
1
resources/views/meta.blade.php
Normal file
1
resources/views/meta.blade.php
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<meta name="config-session-domain" content="{{ config('session.domain') }}">
|
||||||
@@ -84,6 +84,12 @@
|
|||||||
@if( section_must_be( 'translations', $section ) )
|
@if( section_must_be( 'translations', $section ) )
|
||||||
<x-form-field-title name="Languages" required="true" />
|
<x-form-field-title name="Languages" required="true" />
|
||||||
<x-languages-selector :selected="$oldLanguages" />
|
<x-languages-selector :selected="$oldLanguages" />
|
||||||
|
@error('languages')
|
||||||
|
<x-form-error-text message="{{ $message }}" />
|
||||||
|
@enderror
|
||||||
|
@error('languages.*')
|
||||||
|
<x-form-error-text message="{{ $message }}" />
|
||||||
|
@enderror
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="form-group" x-ref="descriptionField">
|
<div class="form-group" x-ref="descriptionField">
|
||||||
|
|||||||
37
routes/api.php
Normal file
37
routes/api.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// FileServerController
|
||||||
|
use App\Http\Controllers\WebhookController;
|
||||||
|
|
||||||
|
Route::name('fs.')->controller(\App\Http\Controllers\FileServerController::class)->group(function () {
|
||||||
|
Route::post('/fs/upload-chunk/{section}', 'uploadChunk' )->name('uploadchunk')
|
||||||
|
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ])
|
||||||
|
->middleware('xf.auth:romhackplaza.canSubmitEntry');
|
||||||
|
;
|
||||||
|
|
||||||
|
Route::get( '/fs/download/{entry_id}/{file:file_uuid}', 'download' )->name('download')
|
||||||
|
->where(['entry_id' => '[0-9]+']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// WebhookController
|
||||||
|
Route::name('webhook.')->controller(WebhookController::class)->group(function () {
|
||||||
|
Route::post('/webhook/xenforo-new-post', 'XenForoNewPost' )->name('xenforo_new_post');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TemporaryFileController
|
||||||
|
Route::name('tempfile.')->controller(\App\Http\Controllers\TemporaryFileController::class)->group(function () {
|
||||||
|
Route::post('/tempfile/upload', 'upload' )->name('upload')
|
||||||
|
->middleware(['xf.auth']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DynamicLoadController
|
||||||
|
Route::get( '/dynamic/hovercard/{user_id}', [ \App\Http\Controllers\DynamicLoadController::class, 'hovercard' ] )
|
||||||
|
->where(['user_id' => '[0-9]+'])
|
||||||
|
->name('dynamic.hovercard')
|
||||||
|
->middleware('throttle:60,1')
|
||||||
|
;
|
||||||
|
Route::middleware('xf.auth')->controller(\App\Http\Controllers\DynamicLoadController::class)->name('dynamic.')->prefix('/dynamic/')->group(function(){
|
||||||
|
Route::get('/notifications', 'getNotifications' )->name('notifications');
|
||||||
|
Route::post('/notifications/mark-all-read', 'markAllRead' )->name('markallread');
|
||||||
|
Route::get('/conversations', 'getConversations' )->name('conversations');
|
||||||
|
});
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\EntryController;
|
use App\Http\Controllers\EntryController;
|
||||||
|
use App\Http\Controllers\WebhookController;
|
||||||
|
use Illuminate\Routing\RedirectController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// HomeController.
|
// HomeController.
|
||||||
@@ -13,54 +15,28 @@ Route::name('entries.')->controller(EntryController::class)->group(function () {
|
|||||||
Route::get('/{section}/{entry:slug}', 'show' )->name('show')->where(
|
Route::get('/{section}/{entry:slug}', 'show' )->name('show')->where(
|
||||||
[
|
[
|
||||||
'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials',
|
'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials',
|
||||||
'entry' => '[a-zA-Z0-9\-]+'
|
'entry' => '[a-zA-Z0-9\-_]+'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// SubmissionController.
|
// SubmissionController.
|
||||||
Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware('xf.auth:romhackplaza.canSubmitEntry')->group(function () {
|
Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:create,\App\Models\Entry'])->group(function () {
|
||||||
Route::get('/{section}', 'create' )->name('create')
|
Route::get('/{section}', 'create' )->name('create')
|
||||||
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ]);
|
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ]);
|
||||||
|
|
||||||
Route::post('/{section}', 'store' )->name('store')
|
Route::post('/{section}', 'store' )->name('store')
|
||||||
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ]);
|
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ]);
|
||||||
});
|
});
|
||||||
Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware('xf.auth:romhackplaza.canSubmitEntry')->group(function () {
|
Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:update,entry'])->group(function () {
|
||||||
Route::get('/{section}/{entry:id}', 'edit' )->name('edit')
|
Route::get('/{section}/{entry:id}', 'edit' )->name('edit')
|
||||||
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials', 'entry' => '[0-9\-]+' ]);
|
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials', 'entry' => '[0-9\-]+' ]);
|
||||||
Route::post('/{section}/{entry:id}', 'update' )->name('update')
|
Route::post('/{section}/{entry:id}', 'update' )->name('update')
|
||||||
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials', 'entry' => '[0-9\-]+' ]);
|
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials', 'entry' => '[0-9\-]+' ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* API ROUTES */
|
// RedirectController
|
||||||
|
Route::name('redirect.')->controller(\App\Http\Controllers\RedirectController::class)->group(function () {
|
||||||
// FileServerController
|
Route::get('/entry/report_redirect', 'entryReportRedirect' )->name('entry_report');
|
||||||
Route::name('fs.')->controller(\App\Http\Controllers\FileServerController::class)->group(function () {
|
|
||||||
Route::post('/api/fs/upload-chunk/{section}', 'uploadChunk' )->name('uploadchunk')
|
|
||||||
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts|tutorials' ])
|
|
||||||
->middleware('xf.auth:romhackplaza.canSubmitEntry');
|
|
||||||
;
|
|
||||||
|
|
||||||
Route::get( '/api/fs/download/{entry_id}/{file:file_uuid}', 'download' )->name('download')
|
|
||||||
->where(['entry_id' => '[0-9]+']);
|
|
||||||
});
|
|
||||||
|
|
||||||
// TemporaryFileController
|
|
||||||
Route::name('tempfile.')->controller(\App\Http\Controllers\TemporaryFileController::class)->group(function () {
|
|
||||||
Route::post('/api/tempfile/upload', 'upload' )->name('upload')
|
|
||||||
->middleware('xf.auth:romhackplaza.canSubmitTempFile');
|
|
||||||
});
|
|
||||||
|
|
||||||
// DynamicLoadController
|
|
||||||
Route::get( '/api/dynamic/hovercard/{user_id}', [ \App\Http\Controllers\DynamicLoadController::class, 'hovercard' ] )
|
|
||||||
->where(['user_id' => '[0-9]+'])
|
|
||||||
->name('dynamic.hovercard')
|
|
||||||
->middleware('throttle:60,1')
|
|
||||||
;
|
|
||||||
Route::middleware('xf.auth')->controller(\App\Http\Controllers\DynamicLoadController::class)->name('dynamic.')->prefix('/api/dynamic/')->group(function(){
|
|
||||||
Route::get('/notifications', 'getNotifications' )->name('notifications');
|
|
||||||
Route::post('/notifications/mark-all-read', 'markAllRead' )->name('markallread');
|
|
||||||
Route::get('/conversations', 'getConversations' )->name('conversations');
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user