Initial commit
This commit is contained in:
132
app/Services/FileServersService.php
Normal file
132
app/Services/FileServersService.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\EntryFile;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class FileServersService {
|
||||
|
||||
public const FAVORITE_SERVER_TIME = 3600; // In seconds.
|
||||
private const ZEUS_TOKEN_EXPIRATION = 600; // In seconds.
|
||||
|
||||
private string $apiKey;
|
||||
private array $servers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiKey = config('fileservers.secret_key');
|
||||
$this->servers = config('fileservers.servers');
|
||||
}
|
||||
|
||||
public function generateZeusToken( int $user_id, string $to, string $action ){
|
||||
|
||||
$info = [
|
||||
'user_id' => $user_id,
|
||||
'to' => $to,
|
||||
'action' => $action,
|
||||
'generated_at' => time(),
|
||||
'expires_at' => time() + self::ZEUS_TOKEN_EXPIRATION,
|
||||
'romhackplaza' => bin2hex(random_bytes(16)),
|
||||
];
|
||||
|
||||
$json = json_encode( $info );
|
||||
$sig = hash_hmac( 'sha256', $json, $this->apiKey );
|
||||
|
||||
$end = base64_encode( $json ) . "|" . $sig;
|
||||
return $end;
|
||||
}
|
||||
|
||||
public function getRandomServerKey(): string
|
||||
{
|
||||
$keys = array_keys($this->servers);
|
||||
return $keys[array_rand($keys)];
|
||||
}
|
||||
|
||||
public function getEntryFileServerKey( EntryFile $file ): string
|
||||
{
|
||||
$serverKey = null;
|
||||
|
||||
if( $file->favorite_server !== null && $file->favorite_at !== null ) {
|
||||
if( time() < $file->favorite_at + static::FAVORITE_SERVER_TIME ) {
|
||||
$serverKey = $file->favorite_server;
|
||||
}
|
||||
}
|
||||
if( $serverKey === null || !isset( $this->servers[$serverKey] ) ) {
|
||||
$serverKey = $this->getRandomServerKey();
|
||||
}
|
||||
|
||||
return $serverKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download URL requires 'filepath' and 'filename'.
|
||||
*
|
||||
* @param EntryFile $file
|
||||
* @return string
|
||||
*/
|
||||
public function getDownloadFileUrl( EntryFile $file ): string
|
||||
{
|
||||
$serverKey = $this->getEntryFileServerKey( $file );
|
||||
$url = $this->servers[$serverKey]['download'] ?? "#";
|
||||
if( $url === "#" )
|
||||
return $url;
|
||||
|
||||
return $url . "&" . http_build_query( [ 'filename' => $file->filename, 'filepath' => $file->filepath ] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function uploadChunk(
|
||||
UploadedFile $chunk,
|
||||
string $fileUUID,
|
||||
int $currentChunk,
|
||||
int $totalChunks,
|
||||
string $filename,
|
||||
string $type
|
||||
){
|
||||
|
||||
// Define or get favorite server.
|
||||
if( $currentChunk === 0 ){
|
||||
$serverKey = $this->getRandomServerKey();
|
||||
Cache::put("favorite_server_{$fileUUID}", $serverKey, now()->addHours(2) );
|
||||
} else {
|
||||
$serverKey = Cache::get( "favorite_server_{$fileUUID}" );
|
||||
abort_if( !$serverKey, 422, "File upload expired, please retry." );
|
||||
}
|
||||
|
||||
$server = $this->servers[$serverKey];
|
||||
$filepath = $type . '/' . $fileUUID;
|
||||
|
||||
$response = Http::withHeaders([])
|
||||
->attach( 'file', file_get_contents( $chunk->getRealPath() ), $fileUUID )
|
||||
->post( $server['upload_chunk'], [
|
||||
'filepath' => $filepath,
|
||||
'filename' => $filename,
|
||||
'current_chunk' => $currentChunk,
|
||||
'total_chunks' => $totalChunks,
|
||||
// TODO : Must replace User ID
|
||||
'zeus' => $this->generateZeusToken( 0, $server['base_url'], "Uploadchunk" ),
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
throw new \RuntimeException( $response->body() );
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if( isset( $data['file'] ) && $data['file'] !== false ){
|
||||
Cache::forget( "favorite_server_{$fileUUID}" );
|
||||
}
|
||||
$data['favorite_server'] = $serverKey;
|
||||
$data['file_uuid'] = $fileUUID;
|
||||
$data['file_path'] = $filepath;
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
388
app/Services/SubmissionsService.php
Normal file
388
app/Services/SubmissionsService.php
Normal file
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\SubmissionException;
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Http\Requests\StoreEntryRequest;
|
||||
use App\Models\Author;
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\EntryGallery;
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Language;
|
||||
use App\Models\Modification;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type FSFileData from \App\Types\FSTypes
|
||||
*/
|
||||
class SubmissionsService {
|
||||
|
||||
/**
|
||||
* Request for store/edit.
|
||||
* @var StoreEntryRequest|null
|
||||
*/
|
||||
private ?StoreEntryRequest $request = null;
|
||||
|
||||
/**
|
||||
* Section for store/edit.
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $section = null;
|
||||
|
||||
/**
|
||||
* @return list<FSFileData>
|
||||
*/
|
||||
public function prepareOldFiles( ?Entry $entry = null ): array
|
||||
{
|
||||
if( $entry === null ){
|
||||
$files = old( 'files_uuid', [] );
|
||||
} else {
|
||||
$files = old( 'files_uuid', $entry->files->pluck('file_uuid')->toArray() );
|
||||
}
|
||||
|
||||
if( $files === [] )
|
||||
return [];
|
||||
|
||||
return array_map(
|
||||
function( string $uuid ) {
|
||||
$file = EntryFile::where('file_uuid', $uuid)->first();
|
||||
|
||||
if( $file )
|
||||
return [
|
||||
'name' => $file->filename,
|
||||
'totalChunks' => 0, // Already uploaded.
|
||||
'rawFile' => null,
|
||||
'progressValue' => 0,
|
||||
'currentChunk' => 0,
|
||||
'done' => true,
|
||||
'error' => null,
|
||||
'uuid' => $uuid
|
||||
];
|
||||
|
||||
$file = Cache::get("uploaded_file_{$uuid}");
|
||||
if( $file )
|
||||
return [
|
||||
'name' => $file['filename'],
|
||||
'totalChunks' => 0, // Already uploaded.
|
||||
'rawFile' => null,
|
||||
'progressValue' => 0,
|
||||
'currentChunk' => 0,
|
||||
'done' => true,
|
||||
'error' => null,
|
||||
'uuid' => $uuid
|
||||
];
|
||||
|
||||
return null;
|
||||
},
|
||||
$files );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StoreEntryRequest $request
|
||||
* @param string $section
|
||||
*
|
||||
* @return Entry
|
||||
* @throws SubmissionException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function storeEntry( StoreEntryRequest $request, string $section ){
|
||||
|
||||
// STEP 1 : Prepare basic fields.
|
||||
|
||||
$this->request = $request;
|
||||
$this->section = $section;
|
||||
$user_id = 0; // TODO: Replace that.
|
||||
|
||||
$entry = DB::transaction(function () use ( $user_id ) {
|
||||
|
||||
// STEP 2 : Create game.
|
||||
|
||||
$gameId = null;
|
||||
if( section_must_be( ['romhacks', 'translations'], $this->section ) ){
|
||||
$gameId = $this->Step2_CreateAndReturnGameId();
|
||||
}
|
||||
|
||||
// STEP 3 : Create Complete title.
|
||||
$completeTitle = $this->Step3_BuildCompleteTitle( $gameId );
|
||||
|
||||
// STEP 4 : Generate slug and entry title.
|
||||
$entrySlug = EntryHelpers::uniqueSlug( $completeTitle, Entry::class );
|
||||
|
||||
if( section_must_be( 'translations', $this->section ) &&
|
||||
!$this->request->input('entry_title') ){
|
||||
$entryTitle = Game::find($gameId)->name;
|
||||
} else {
|
||||
$entryTitle = $this->request->input('entry_title');
|
||||
}
|
||||
|
||||
// STEP 5 : Removed / Delayed.
|
||||
// $mainImage = $this->Step5_MoveMainImage();
|
||||
|
||||
// STEP 6 : Prepare entry fields and save entry.
|
||||
$fields = [
|
||||
'type' => $this->section,
|
||||
'title' => $entryTitle,
|
||||
'slug' => $entrySlug,
|
||||
'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,
|
||||
];
|
||||
|
||||
$entry = Entry::create( $fields );
|
||||
|
||||
// STEP 7 : Save entry fields.
|
||||
$this->Step7_SaveEntryFiles( $entry->id );
|
||||
|
||||
// STEP 8 : Save hashes.
|
||||
$this->Step8_SaveHashes( $entry->id );
|
||||
|
||||
// STEP 9 : Save Authors.
|
||||
$this->Step9_SaveAuthors( $entry );
|
||||
|
||||
// STEP 10 : Save Modifications.
|
||||
if( section_must_be( 'romhacks', $this->section ) ){
|
||||
$this->Step10_SaveRomhacksModifications( $entry );
|
||||
}
|
||||
|
||||
// STEP 11 : Save Languages
|
||||
$this->Step11_SaveLanguages( $entry );
|
||||
|
||||
// STEP 12 : Prepare Gallery images.
|
||||
$this->Step12a_PrepareGalleryImages( $entry );
|
||||
|
||||
return $entry;
|
||||
|
||||
});
|
||||
|
||||
// Step 12, Move main image and gallery.
|
||||
$this->Step12b_MoveMainImage( $entry );
|
||||
$this->Step12c_SaveGalleryImages( $entry );
|
||||
|
||||
return $entry;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step2_CreateAndReturnGameId(): int {
|
||||
|
||||
// Already existing game.
|
||||
if( $this->request->input('game_id') )
|
||||
return $this->request->input('game_id');
|
||||
|
||||
// Need to create a game.
|
||||
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" );
|
||||
|
||||
$platform = Platform::find( $this->request->input('new-game-platform') );
|
||||
$genre = Genre::find( $this->request->input('new-game-genre') );
|
||||
|
||||
if( !$platform || !$genre )
|
||||
throw new SubmissionException( "Incorrect game platform id" );
|
||||
|
||||
$gameSlug = EntryHelpers::uniqueSlug( $this->request->input('new-game-title'), Game::class );
|
||||
|
||||
$game = Game::create([
|
||||
'name' => trim( $this->request->input('new-game-title') ),
|
||||
'slug' => $gameSlug,
|
||||
'platform_id' => $platform->id,
|
||||
'genre_id' => $genre->id,
|
||||
]);
|
||||
|
||||
return $game->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and build complete title.
|
||||
*
|
||||
* @param int|null $gameId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function Step3_BuildCompleteTitle( ?int $gameId = null ): string {
|
||||
$fields = [];
|
||||
|
||||
$fields['entry_title'] = $this->request->input('entry_title') ?? null;
|
||||
if( section_must_be( [ 'homebrew', 'translations' ], $this->section ) && $gameId ){
|
||||
$fields['game_name'] = Game::find( $gameId )->name;
|
||||
}
|
||||
if( section_must_be( 'translations', $this->section ) ) {
|
||||
$fields['languages_string'] = Language::whereIn('id', $this->request->input('languages', []))->pluck('name')->implode(', ');
|
||||
}
|
||||
if( section_must_be(['romhacks', 'homebrew', 'lua-scripts', 'tutorials'], $this->section ) ) {
|
||||
// TODO: Add single platform ID compatibility.
|
||||
$fields['platform_name'] = Game::find( $gameId )->platform->name;
|
||||
}
|
||||
|
||||
return EntryHelpers::buildCompleteTitle( $this->section, $fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step7_SaveEntryFiles( int $entryId ): void
|
||||
{
|
||||
foreach ( $this->request->input('files_uuid', [] ) as $uuid ) {
|
||||
$fileData = Cache::pull("uploaded_file_{$uuid}");
|
||||
if( !$fileData )
|
||||
throw new SubmissionException( "File {$uuid} has expired. Please delete all your files and retry." );
|
||||
|
||||
EntryFile::create([
|
||||
'entry_id' => $entryId,
|
||||
'file_uuid' => $uuid,
|
||||
'filename' => $fileData['filename'],
|
||||
'filepath' => $fileData['filepath'],
|
||||
'favorite_server' => $fileData['favorite_server'],
|
||||
'favorite_at' => \DateTimeImmutable::createFromTimestamp( $fileData['favorite_at'] ),
|
||||
'filesize' => $fileData['filesize'],
|
||||
'state' => 'public'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function Step8_SaveHashes( int $entryId ): void
|
||||
{
|
||||
foreach ( $this->request->input('hashes', [] ) as $hash ) {
|
||||
if( !isset($hash['filename'], $hash['hash_crc32'], $hash['hash_sha1'], $hash['verified']) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EntryHash::create([
|
||||
'entry_id' => $entryId,
|
||||
'filename' => $hash['filename'],
|
||||
'hash_crc32' => $hash['hash_crc32'],
|
||||
'hash_sha1' => $hash['hash_sha1'],
|
||||
'verified' => $hash['verified'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step9_SaveAuthors( Entry $entry ): void
|
||||
{
|
||||
// Existing authors.
|
||||
foreach ( $this->request->input('authors', [] ) as $authorId ) {
|
||||
$author = Author::find( $authorId );
|
||||
if( !$author )
|
||||
throw new SubmissionException( "Author {$authorId} does not exist." );
|
||||
$entry->authors()->attach( $author->id );
|
||||
}
|
||||
|
||||
// New Authors
|
||||
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]
|
||||
);
|
||||
$entry->authors()->attach( $author->id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step10_SaveRomhacksModifications( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('modifications', [] ) as $modificationId ) {
|
||||
$modification = Modification::find( $modificationId );
|
||||
if( !$modification )
|
||||
throw new SubmissionException( "Modification {$modificationId} does not exist." );
|
||||
$entry->modifications()->attach( $modification->id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step11_SaveLanguages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('languages', [] ) as $languageId ) {
|
||||
$language = Language::find( $languageId );
|
||||
if( !$language )
|
||||
throw new SubmissionException( "Language {$languageId} does not exist." );
|
||||
$entry->languages()->attach( $language->id );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function Step12a_PrepareGalleryImages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('gallery', [] ) as $imagePath ) {
|
||||
EntryGallery::create([
|
||||
'entry_id' => $entry->id,
|
||||
'image' => $imagePath,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function Step12b_MoveMainImage( Entry $entry ): void {
|
||||
$mainImage = $entry->main_image;
|
||||
$newPath = 'entries/main-images/' . basename($mainImage);
|
||||
|
||||
if( !Storage::disk('public')->move($mainImage, $newPath) )
|
||||
return;
|
||||
|
||||
$entry->update(['main_image' => $newPath]);
|
||||
}
|
||||
|
||||
private function Step12c_SaveGalleryImages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $entry->gallery as $galleryItem ) {
|
||||
$newPath = 'entries/gallery-images/' . $entry->id . '/' . basename($galleryItem->image);
|
||||
|
||||
if( !Storage::disk('public')->move($galleryItem->image, $newPath) )
|
||||
continue;
|
||||
|
||||
$galleryItem->update(['image' => $newPath]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
app/Services/TemporaryFileService.php
Normal file
25
app/Services/TemporaryFileService.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class TemporaryFileService {
|
||||
|
||||
public const int NB_HOURS_FILES_KEPT = 6;
|
||||
|
||||
/**
|
||||
* Upload a file in the temporary path.
|
||||
*
|
||||
* @param UploadedFile|null $file
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function uploadFile(?UploadedFile $file ): string|bool {
|
||||
|
||||
if( !$file )
|
||||
return false;
|
||||
|
||||
return $file->store( 'temp', 'public' );
|
||||
}
|
||||
}
|
||||
36
app/Services/XenforoService.php
Normal file
36
app/Services/XenforoService.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class XenforoService {
|
||||
|
||||
private const array PERMISSIONS_KEPT = [ 'general', 'romhackplaza' ];
|
||||
private const int TTL_PERMISSIONS = 300;
|
||||
|
||||
public function getPermissions(int $userId, int $permissionCombinationId): array {
|
||||
|
||||
return Cache::remember("xf_permissions_{$userId}", self::TTL_PERMISSIONS, function() use($permissionCombinationId) {
|
||||
$row = \DB::connection('xenforo')
|
||||
->table('permission_combination')
|
||||
->where('permission_combination_id', $permissionCombinationId)
|
||||
->value('cache_value');
|
||||
|
||||
if( !$row )
|
||||
return [];
|
||||
|
||||
$data = json_decode($row, true);
|
||||
$data = array_intersect_key($data, array_flip(self::PERMISSIONS_KEPT));
|
||||
|
||||
return $data ?: [];
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function clearUserData(int $userId): void
|
||||
{
|
||||
Cache::forget("xf_permissions_{$userId}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user