Skip to content

Commit

Permalink
feat(api): allow querying for user profile by ULID (#3141)
Browse files Browse the repository at this point in the history
  • Loading branch information
wescopeland authored Feb 1, 2025
1 parent 5e93a28 commit 62672aa
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 6 deletions.
2 changes: 2 additions & 0 deletions app/Community/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Community\Commands\SyncForums;
use App\Community\Commands\SyncTickets;
use App\Community\Commands\SyncUserRelations;
use App\Community\Commands\SyncUserUlids;
use App\Community\Components\DeveloperGameStatsTable;
use App\Community\Components\ForumRecentActivity;
use App\Community\Components\MessageIcon;
Expand Down Expand Up @@ -55,6 +56,7 @@ public function boot(): void
SyncForums::class,
SyncTickets::class,
SyncUserRelations::class,
SyncUserUlids::class,

GenerateAnnualRecap::class,
]);
Expand Down
67 changes: 67 additions & 0 deletions app/Community/Commands/SyncUserUlids.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace App\Community\Commands;

use App\Models\User;
use DB;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

class SyncUserUlids extends Command
{
protected $signature = 'ra:sync:user-ulids';

protected $description = 'Sync user ulid field values';

public function handle(): void
{
$total = User::whereNull('ulid')->count();

if ($total === 0) {
$this->info('No records need ULIDs.');

return;
}

$progressBar = $this->output->createProgressBar($total);
$progressBar->start();

User::withTrashed()
->whereNull('ulid')
->chunkById(4000, function ($users) use ($progressBar) {
$updates = [];
$milliseconds = 0;

/** @var User $user */
foreach ($users as $user) {
$milliseconds += rand(1, 20);
$milliseconds %= 1000;

$timestamp = $user->Created->clone()->addMilliseconds($milliseconds);
$ulid = (string) Str::ulid($timestamp);

$updates[] = [
'ID' => $user->id,
'ulid' => $ulid,
];
}

// Perform a batch update for speed.
DB::table('UserAccounts')
->upsert(
$updates,
['ID'],
['ulid']
);

$progressBar->advance(count($updates));
});

$progressBar->finish();

$this->newLine();
$this->info('Done.');
}
}
1 change: 1 addition & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function definition(): array
{
return [
// required
'ulid' => (string) Str::ulid(),
'User' => $this->fakeUsername(),
'EmailAddress' => fake()->unique()->safeEmail,
'email_verified_at' => now(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class() extends Migration {
public function up(): void
{
Schema::table('UserAccounts', function (Blueprint $table) {
$table->ulid('ulid')->after('ID')->unique()->nullable();
});
}

public function down(): void
{
Schema::table('UserAccounts', function (Blueprint $table) {
$table->dropColumn('ulid');
});
}
};
12 changes: 9 additions & 3 deletions public/API/API_GetUserProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
/*
* API_GetUserProfile
* u : username
* i : ULID
*
* string User name of user
* string User non-stable name of user
* int ID unique identifier of the user
* string ULID queryable unique identifier of the user
* int TotalPoints number of hardcore points the user has
* int TotalSoftcorePoints number of softcore points the user has
* int TotalTruePoints number of RetroPoints ("white points") the user has
Expand All @@ -27,17 +29,21 @@
use Illuminate\Support\Facades\Validator;

$input = Validator::validate(Arr::wrap(request()->query()), [
'u' => ['required', 'min:2', 'max:20', new CtypeAlnum()],
'u' => ['required_without:i', 'min:2', 'max:20', new CtypeAlnum()],
'i' => ['required_without:u', 'string', 'size:26'],
]);

$user = User::whereName(request()->query('u'))->first();
$user = isset($input['i'])
? User::whereUlid($input['i'])->first()
: User::whereName($input['u'])->first();

if (!$user) {
return response()->json([], 404);
}

return response()->json([
'User' => $user->display_name,
'ULID' => $user->ulid,
'UserPic' => sprintf("/UserPic/%s.png", $user->username),
'MemberSince' => $user->created_at->toDateTimeString(),
'RichPresenceMsg' => empty($user->RichPresenceMsg) || $user->RichPresenceMsg === 'Unknown' ? null : $user->RichPresenceMsg,
Expand Down
6 changes: 4 additions & 2 deletions public/request/auth/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;

$input = Validator::validate(Arr::wrap(request()->post()), [
'username' => ValidNewUsername::get(),
Expand Down Expand Up @@ -41,10 +42,11 @@
}
}

$ulid = (string) Str::ulid();
$hashedPassword = Hash::make($pass);

$query = "INSERT INTO UserAccounts (User, display_name, Password, SaltedPass, EmailAddress, Permissions, RAPoints, fbUser, fbPrefs, cookie, appToken, appTokenExpiry, websitePrefs, LastLogin, LastActivityID, Motto, ContribCount, ContribYield, APIKey, APIUses, LastGameID, RichPresenceMsg, RichPresenceMsgDate, ManuallyVerified, UnreadMessageCount, TrueRAPoints, UserWallActive, PasswordResetToken, Untracked, email_backup)
VALUES ( '$username', '$username', '$hashedPassword', '', '$email', 0, 0, 0, 0, '', '', NULL, 127, null, 0, '', 0, 0, '', 0, 0, '', NULL, 0, 0, 0, 1, NULL, false, '$email')";
$query = "INSERT INTO UserAccounts (ulid, User, display_name, Password, SaltedPass, EmailAddress, Permissions, RAPoints, fbUser, fbPrefs, cookie, appToken, appTokenExpiry, websitePrefs, LastLogin, LastActivityID, Motto, ContribCount, ContribYield, APIKey, APIUses, LastGameID, RichPresenceMsg, RichPresenceMsgDate, ManuallyVerified, UnreadMessageCount, TrueRAPoints, UserWallActive, PasswordResetToken, Untracked, email_backup)
VALUES ('$ulid', '$username', '$username', '$hashedPassword', '', '$email', 0, 0, 0, 0, '', '', NULL, 127, null, 0, '', 0, 0, '', 0, 0, '', NULL, 0, 0, 0, 1, NULL, false, '$email')";
$dbResult = s_mysql_query($query);

if (!$dbResult) {
Expand Down
42 changes: 41 additions & 1 deletion tests/Feature/Api/V1/UserProfileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public function testItValidates(): void
$this->get($this->apiUrl('GetUserProfile'))
->assertJsonValidationErrors([
'u',
'i',
]);

$this->get($this->apiUrl('GetUserProfile', ['u' => 'username', 'i' => 'ulid']))
->assertJsonValidationErrors([
'i', // should fail size:26 validation.
]);
}

Expand All @@ -28,7 +34,14 @@ public function testGetUserProfileUnknownUser(): void
->assertJson([]);
}

public function testGetUserProfile(): void
public function testGetUserProfileUnknownUlid(): void
{
$this->get($this->apiUrl('GetUserProfile', ['i' => '01HNG49MXJA71KCVG3PXQS5B2C']))
->assertNotFound()
->assertJson([]);
}

public function testGetUserProfileByUsername(): void
{
/** @var User $user */
$user = User::factory()->create();
Expand All @@ -53,4 +66,31 @@ public function testGetUserProfile(): void
'Motto' => $user->Motto,
]);
}

public function testGetUserProfileByUlid(): void
{
/** @var User $user */
$user = User::factory()->create();

$this->get($this->apiUrl('GetUserProfile', ['i' => $user->ulid]))
->assertSuccessful()
->assertJson([
'User' => $user->User,
'ULID' => $user->ulid,
'UserPic' => sprintf("/UserPic/%s.png", $user->User),
'MemberSince' => $user->created_at->toDateTimeString(),
'RichPresenceMsg' => ($user->RichPresenceMsg) ? $user->RichPresenceMsg : null,
'LastGameID' => $user->LastGameID,
'ContribCount' => $user->ContribCount,
'ContribYield' => $user->ContribYield,
'TotalPoints' => $user->RAPoints,
'TotalSoftcorePoints' => $user->RASoftcorePoints,
'TotalTruePoints' => $user->TrueRAPoints,
'Permissions' => $user->getAttribute('Permissions'),
'Untracked' => $user->Untracked,
'ID' => $user->ID,
'UserWallActive' => $user->UserWallActive,
'Motto' => $user->Motto,
]);
}
}

0 comments on commit 62672aa

Please sign in to comment.