mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
Hub Imports of User Avatars and Cover Photos
TODO: Cover photos need to be added to the profile page so users can edit them.
This commit is contained in:
parent
11453db596
commit
55273e5a90
@ -36,6 +36,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
// Stream some data locally so that we don't have to keep accessing the Hub's database. Use MySQL temporary
|
||||
// tables to store the data to save on memory; we don't want this to be a hog.
|
||||
$this->bringUserAvatarLocal();
|
||||
$this->bringFileAuthorsLocal();
|
||||
$this->bringFileOptionsLocal();
|
||||
$this->bringFileContentLocal();
|
||||
@ -58,6 +59,34 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
Artisan::call('cache:clear');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring the user avatar table from the Hub database to the local database temporary table.
|
||||
*/
|
||||
protected function bringUserAvatarLocal(): void
|
||||
{
|
||||
DB::statement('DROP TEMPORARY TABLE IF EXISTS temp_user_avatar');
|
||||
DB::statement('CREATE TEMPORARY TABLE temp_user_avatar (
|
||||
avatarID INT,
|
||||
avatarExtension VARCHAR(255),
|
||||
userID INT,
|
||||
fileHash VARCHAR(255)
|
||||
)');
|
||||
|
||||
DB::connection('mysql_hub')
|
||||
->table('wcf1_user_avatar')
|
||||
->orderBy('avatarID')
|
||||
->chunk(200, function ($avatars) {
|
||||
foreach ($avatars as $avatar) {
|
||||
DB::table('temp_user_avatar')->insert([
|
||||
'avatarID' => (int) $avatar->avatarID,
|
||||
'avatarExtension' => $avatar->avatarExtension,
|
||||
'userID' => (int) $avatar->userID,
|
||||
'fileHash' => $avatar->fileHash,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring the file authors from the Hub database to the local database temporary table.
|
||||
*/
|
||||
@ -172,15 +201,33 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
*/
|
||||
protected function importUsers(): void
|
||||
{
|
||||
// Initialize a cURL handler for downloading mod thumbnails.
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
DB::connection('mysql_hub')
|
||||
->table('wcf1_user as u')
|
||||
->select('u.userID', 'u.username', 'u.email', 'u.password', 'u.registrationDate', 'u.banned', 'u.banReason', 'u.banExpires', 'u.rankID', 'r.rankTitle')
|
||||
->select(
|
||||
'u.userID',
|
||||
'u.username',
|
||||
'u.email',
|
||||
'u.password',
|
||||
'u.registrationDate',
|
||||
'u.banned',
|
||||
'u.banReason',
|
||||
'u.banExpires',
|
||||
'u.coverPhotoHash',
|
||||
'u.coverPhotoExtension',
|
||||
'u.rankID',
|
||||
'r.rankTitle',
|
||||
)
|
||||
->leftJoin('wcf1_user_rank as r', 'u.rankID', '=', 'r.rankID')
|
||||
->chunkById(250, function (Collection $users) {
|
||||
->chunkById(250, function (Collection $users) use ($curl) {
|
||||
$userData = $bannedUsers = $userRanks = [];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$userData[] = $this->collectUserData($user);
|
||||
$userData[] = $this->collectUserData($curl, $user);
|
||||
|
||||
$bannedUserData = $this->collectBannedUserData($user);
|
||||
if ($bannedUserData) {
|
||||
@ -197,15 +244,20 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
$this->handleBannedUsers($bannedUsers);
|
||||
$this->handleUserRoles($userRanks);
|
||||
}, 'userID');
|
||||
|
||||
// Close the cURL handler.
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
protected function collectUserData($user): array
|
||||
protected function collectUserData(CurlHandle $curl, object $user): array
|
||||
{
|
||||
return [
|
||||
'hub_id' => (int) $user->userID,
|
||||
'name' => $user->username,
|
||||
'email' => mb_convert_case($user->email, MB_CASE_LOWER, 'UTF-8'),
|
||||
'email' => Str::lower($user->email),
|
||||
'password' => $this->cleanPasswordHash($user->password),
|
||||
'profile_photo_path' => $this->fetchUserAvatar($curl, $user),
|
||||
'cover_photo_path' => $this->fetchUserCoverPhoto($curl, $user),
|
||||
'created_at' => $this->cleanRegistrationDate($user->registrationDate),
|
||||
'updated_at' => now('UTC')->toDateTimeString(),
|
||||
];
|
||||
@ -224,6 +276,75 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
return str_starts_with($clean, '$2') ? $clean : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserAvatar(CurlHandle $curl, object $user): string
|
||||
{
|
||||
// Fetch the user's avatar data from the temporary table.
|
||||
$avatar = DB::table('temp_user_avatar')->where('userID', $user->userID)->first();
|
||||
|
||||
if (! $avatar) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hashShort = substr($avatar->fileHash, 0, 2);
|
||||
$fileName = $avatar->fileHash.'.'.$avatar->avatarExtension;
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/avatars/'.$hashShort.'/'.$avatar->avatarID.'-'.$fileName;
|
||||
$relativePath = 'user-avatars/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and store an image from the Hub.
|
||||
*/
|
||||
protected function fetchAndStoreImage(CurlHandle $curl, string $hubUrl, string $relativePath): string
|
||||
{
|
||||
// Determine the disk to use based on the environment.
|
||||
$disk = match (config('app.env')) {
|
||||
'production' => 'r2', // Cloudflare R2 Storage
|
||||
default => 'public', // Local
|
||||
};
|
||||
|
||||
// Check to make sure the image doesn't already exist.
|
||||
if (Storage::disk($disk)->exists($relativePath)) {
|
||||
return $relativePath; // Already exists, return the path.
|
||||
}
|
||||
|
||||
// Download the image using the cURL handler.
|
||||
curl_setopt($curl, CURLOPT_URL, $hubUrl);
|
||||
$image = curl_exec($curl);
|
||||
|
||||
if ($image === false) {
|
||||
Log::error('There was an error attempting to download the image. cURL error: '.curl_error($curl));
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Store the image on the disk.
|
||||
Storage::disk($disk)->put($relativePath, $image);
|
||||
|
||||
return $relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user avatar from the Hub and store it anew.
|
||||
*/
|
||||
protected function fetchUserCoverPhoto(CurlHandle $curl, object $user): string
|
||||
{
|
||||
if (empty($user->coverPhotoHash) || empty($user->coverPhotoExtension)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hashShort = substr($user->coverPhotoHash, 0, 2);
|
||||
$fileName = $user->coverPhotoHash.'.'.$user->coverPhotoExtension;
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/images/coverPhotos/'.$hashShort.'/'.$user->userID.'-'.$fileName;
|
||||
$relativePath = 'user-covers/'.$fileName;
|
||||
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the registration date from the Hub database.
|
||||
*/
|
||||
@ -295,9 +416,6 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an array of user rank data ready to be inserted into the local database.
|
||||
*/
|
||||
protected function collectUserRankData($user): ?array
|
||||
{
|
||||
if ($user->rankID && $user->rankTitle) {
|
||||
@ -590,31 +708,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
$hubUrl = 'https://hub.sp-tarkov.com/files/images/file/'.$hashShort.'/'.$fileName;
|
||||
$relativePath = 'mods/'.$fileName;
|
||||
|
||||
// Determine the disk to use based on the environment.
|
||||
$disk = match (config('app.env')) {
|
||||
'production' => 'r2', // Cloudflare R2 Storage
|
||||
default => 'public', // Local
|
||||
};
|
||||
|
||||
// Check to make sure the image doesn't already exist.
|
||||
if (Storage::disk($disk)->exists($relativePath)) {
|
||||
return $relativePath; // Already exists, return the path.
|
||||
}
|
||||
|
||||
// Download the image using the cURL handler.
|
||||
curl_setopt($curl, CURLOPT_URL, $hubUrl);
|
||||
$image = curl_exec($curl);
|
||||
|
||||
if ($image === false) {
|
||||
Log::error('There was an error attempting to download a mod thumbnail. cURL error: '.curl_error($curl));
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Store the image on the disk.
|
||||
Storage::disk($disk)->put($relativePath, $image);
|
||||
|
||||
return $relativePath;
|
||||
return $this->fetchAndStoreImage($curl, $hubUrl, $relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -693,6 +787,7 @@ class ImportHubData implements ShouldBeUnique, ShouldQueue
|
||||
public function failed(Exception $exception): void
|
||||
{
|
||||
// Explicitly drop the temporary tables.
|
||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_user_avatar');
|
||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_file_author');
|
||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_file_option_values');
|
||||
DB::unprepared('DROP TEMPORARY TABLE IF EXISTS temp_file_content');
|
||||
|
@ -65,14 +65,6 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return ! is_null($this->email_verified_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a user and their role.
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserRole::class, 'user_role_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has the role of a moderator.
|
||||
*/
|
||||
@ -124,6 +116,24 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return Str::lower(Str::slug($this->name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a role to the user.
|
||||
*/
|
||||
public function assignRole(UserRole $role): bool
|
||||
{
|
||||
$this->role()->associate($role);
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* The relationship between a user and their role.
|
||||
*/
|
||||
public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserRole::class, 'user_role_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
|
@ -207,7 +207,7 @@ return [
|
||||
'maxJobs' => 0,
|
||||
'memory' => 256,
|
||||
'tries' => 1,
|
||||
'timeout' => 900, // 15 Minutes
|
||||
'timeout' => 1500, // 25 Minutes
|
||||
'nice' => 0,
|
||||
],
|
||||
],
|
||||
|
@ -29,7 +29,8 @@ return new class extends Migration
|
||||
->nullOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->rememberToken();
|
||||
$table->string('profile_photo_path', 2048)->nullable();
|
||||
$table->string('profile_photo_path', 2048)->nullable()->default(null);
|
||||
$table->string('cover_photo_path', 2048)->nullable()->default(null);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user