diff --git a/app/Clients/Consumer/ConsumerClient.php b/app/Clients/Consumer/ConsumerClient.php
index 2bd1a5d..326b102 100644
--- a/app/Clients/Consumer/ConsumerClient.php
+++ b/app/Clients/Consumer/ConsumerClient.php
@@ -25,18 +25,18 @@ public function __construct()
]);
}
- public function updateUser(User $user): void
+ public function updateUser(User $user, Settings $settings): void
{
$uri = $this->baseVersionedUrl.'/settings';
/** @var ConnectedAccount $account */
- $account = $user->accounts()->where('provider', 'twitch')->first();
- /** @var Settings $settings */
- $settings = $user->settings()->with(['occupation', 'effect', 'color'])->first();
+ $account = $user->accounts->first(fn ($account) => $account->provider == 'twitch');
$payload = [
'user_id' => (int) $account->provider_user_id,
'locale' => $settings->locale,
+ 'enabled' => $settings->enabled,
+ 'channel_id' => $settings->channel_id,
'occupation' => [
'name' => $settings->occupation->name,
'translation_key' => $settings->occupation->translation_key,
diff --git a/app/DTO/AuthenticationDTO.php b/app/DTO/AuthenticationDTO.php
index 350b4ae..01f17de 100644
--- a/app/DTO/AuthenticationDTO.php
+++ b/app/DTO/AuthenticationDTO.php
@@ -13,6 +13,10 @@ public function __construct(
public static function factory(AuthorizationDTO $authorization, User $user): AuthenticationDTO
{
+ // TODO: remove after releasing the next version. this is a workaround to not break the actual implementation
+ // of settings feature
+ $user->settings = $user->settings->first(fn ($settings) => $settings->channel_id = 'global');
+
return new AuthenticationDTO(
authorization: $authorization,
user: $user,
diff --git a/app/Http/Controllers/Api/V1/AuthenticatedUserController.php b/app/Http/Controllers/Api/V1/AuthenticatedUserController.php
index ac1f292..60f8dcf 100644
--- a/app/Http/Controllers/Api/V1/AuthenticatedUserController.php
+++ b/app/Http/Controllers/Api/V1/AuthenticatedUserController.php
@@ -6,23 +6,54 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\SettingsRequest;
use App\Models\Settings\Settings;
+use App\Models\User;
use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
class AuthenticatedUserController extends Controller
{
public function __construct(private ConsumerClient $client) {}
+ public function getSettings(Request $request): JsonResponse
+ {
+ /** @var User $user */
+ $user = $request->user();
+
+ $settingsQuery = $user->settings();
+ $fetcheableSettings = ['global'];
+
+ if ($channelId = $request->get('channel_id')) {
+ $fetcheableSettings[] = $channelId;
+ }
+
+ $response = $settingsQuery->whereIn('channel_id', $fetcheableSettings)
+ ->paginate();
+
+ return response()->json($response);
+ }
+
public function putSettings(SettingsRequest $request): JsonResponse
{
$validatedSettings = $request->validated();
- $userSettings = $request->user()->settings();
- $userSettings->update($validatedSettings);
- /** @var Settings $response */
- $this->client->updateUser($request->user()->refresh());
+ $request
+ ->user()
+ ->settings()
+ ->updateOrCreate([
+ 'channel_id' => $validatedSettings['channel_id'],
+ ], $validatedSettings);
- $response = $userSettings->with(['occupation', 'color', 'effect'])->first();
+ /** @var User $user */
+ $user = $request
+ ->user()
+ ->refresh()
+ ->with(['accounts', 'settings.occupation', 'settings.effect', 'settings.color'])
+ ->first();
- return response()->json($response);
+ $settings = $user->settings->first(fn (Settings $settings) => $settings->channel_id == $validatedSettings['channel_id']);
+
+ $this->client->updateUser($user, $settings);
+
+ return response()->json($settings);
}
}
diff --git a/app/Http/Requests/SettingsRequest.php b/app/Http/Requests/SettingsRequest.php
index afab9fd..5553a2d 100644
--- a/app/Http/Requests/SettingsRequest.php
+++ b/app/Http/Requests/SettingsRequest.php
@@ -12,8 +12,12 @@ public function rules(): array
return [
'occupation_id' => ['exists:occupations,id'],
+ 'channel_id' => ['required', 'string'],
+ 'enabled' => ['required'],
'color_id' => ['exists:settings_colors,id'],
'effect_id' => ['exists:settings_effects,id'],
+ 'timezone' => ['string'],
+ 'locale' => ['string'],
'pronouns' => ['string', 'in:'.$acceptedPronouns],
];
}
diff --git a/app/Models/Settings/Color.php b/app/Models/Settings/Color.php
index df7c57d..2e6f75e 100644
--- a/app/Models/Settings/Color.php
+++ b/app/Models/Settings/Color.php
@@ -2,8 +2,10 @@
namespace App\Models\Settings;
+use Database\Factories\ColorFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
class Color extends Model
{
@@ -17,4 +19,14 @@ class Color extends Model
'translation_key',
'hex',
];
+
+ public function settings(): HasMany
+ {
+ return $this->hasMany(Settings::class);
+ }
+
+ protected static function newFactory(): ColorFactory
+ {
+ return ColorFactory::new();
+ }
}
diff --git a/app/Models/Settings/Effect.php b/app/Models/Settings/Effect.php
index abc2e77..b9303f5 100644
--- a/app/Models/Settings/Effect.php
+++ b/app/Models/Settings/Effect.php
@@ -2,8 +2,10 @@
namespace App\Models\Settings;
+use Database\Factories\EffectFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
class Effect extends Model
{
@@ -18,4 +20,14 @@ class Effect extends Model
'class_name',
'hex',
];
+
+ public function settings(): HasMany
+ {
+ return $this->hasMany(Settings::class);
+ }
+
+ protected static function newFactory(): EffectFactory
+ {
+ return EffectFactory::new();
+ }
}
diff --git a/app/Models/Settings/Occupation.php b/app/Models/Settings/Occupation.php
index f6d734f..ab1cd12 100644
--- a/app/Models/Settings/Occupation.php
+++ b/app/Models/Settings/Occupation.php
@@ -2,10 +2,15 @@
namespace App\Models\Settings;
+use Database\Factories\OccupationFactory;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
class Occupation extends Model
{
+ use HasFactory;
+
protected $fillable = ['name', 'slug', 'translation_key'];
public function getImageUrlAttribute(): string
@@ -16,4 +21,14 @@ public function getImageUrlAttribute(): string
protected $appends = [
'image_url',
];
+
+ public function settings(): HasMany
+ {
+ return $this->hasMany(Settings::class);
+ }
+
+ protected static function newFactory(): OccupationFactory
+ {
+ return OccupationFactory::new();
+ }
}
diff --git a/app/Models/Settings/Settings.php b/app/Models/Settings/Settings.php
index 78012d8..a709357 100644
--- a/app/Models/Settings/Settings.php
+++ b/app/Models/Settings/Settings.php
@@ -3,16 +3,23 @@
namespace App\Models\Settings;
use App\Models\User;
+use Database\Factories\SettingsFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
+/**
+ * @property string $channel_id
+ * @property bool $enabled
+ */
class Settings extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
+ 'enabled',
+ 'channel_id',
'color_id',
'effect_id',
'occupation_id',
@@ -24,6 +31,7 @@ class Settings extends Model
protected $casts = [
'is_developer' => 'boolean',
+ 'enabled' => 'boolean',
];
public function getPronounsAttribute(): array
@@ -50,4 +58,9 @@ public function effect(): BelongsTo
{
return $this->belongsTo(Effect::class);
}
+
+ protected static function newFactory(): SettingsFactory
+ {
+ return SettingsFactory::new();
+ }
}
diff --git a/app/Models/User.php b/app/Models/User.php
index 1f96a44..378d327 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -12,7 +12,6 @@
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
-use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
@@ -81,8 +80,8 @@ public function accounts(): HasMany
return $this->hasMany(ConnectedAccount::class);
}
- public function settings(): HasOne
+ public function settings(): HasMany
{
- return $this->hasOne(Settings::class);
+ return $this->hasMany(Settings::class);
}
}
diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php
index 35b1ef4..42605f9 100644
--- a/app/Observers/UserObserver.php
+++ b/app/Observers/UserObserver.php
@@ -15,6 +15,7 @@ public function created(User $user): void
$user->settings()->create([
'occupation_id' => 1, // none
+ 'channel_id' => 'global',
'color_id' => 1, // none
'effect_id' => 1, // none
'pronouns' => 'none', // none
diff --git a/database/factories/EffectFactory.php b/database/factories/EffectFactory.php
index ed5885c..2edb6b4 100644
--- a/database/factories/EffectFactory.php
+++ b/database/factories/EffectFactory.php
@@ -18,7 +18,7 @@ public function definition(): array
'name' => $this->faker->name(),
'slug' => $this->faker->slug(),
'translation_key' => $this->faker->word(),
- 'class' => $this->faker->word(),
+ 'class_name' => $this->faker->word(),
'hex' => $this->faker->word(),
];
}
diff --git a/database/factories/OccupationFactory.php b/database/factories/OccupationFactory.php
new file mode 100644
index 0000000..0a47dbf
--- /dev/null
+++ b/database/factories/OccupationFactory.php
@@ -0,0 +1,20 @@
+ $this->faker->name,
+ 'slug' => $this->faker->name,
+ 'translation_key' => $this->faker->name,
+ ];
+ }
+}
diff --git a/database/factories/SettingsFactory.php b/database/factories/SettingsFactory.php
index d1156a3..ad98687 100644
--- a/database/factories/SettingsFactory.php
+++ b/database/factories/SettingsFactory.php
@@ -2,6 +2,8 @@
namespace Database\Factories;
+use App\Models\Settings\Color;
+use App\Models\Settings\Effect;
use App\Models\Settings\Occupation;
use App\Models\Settings\Settings;
use App\Models\User;
@@ -12,15 +14,23 @@ class SettingsFactory extends Factory
{
protected $model = Settings::class;
- public function definition()
+ public function definition(): array
{
- return [
- 'created_at' => Carbon::now(),
- 'updated_at' => Carbon::now(),
- 'pronouns' => $this->faker->word(),
+ $pronouns = collect(config('extension.pronouns'))->keys()->shuffle()->first();
+ return [
'user_id' => User::factory(),
+ 'channel_id' => 'global',
+ 'enabled' => true,
+ 'color_id' => Color::factory(),
+ 'effect_id' => Effect::factory(),
'occupation_id' => Occupation::factory(),
+ 'pronouns' => $pronouns,
+ 'timezone' => $this->faker->timezone,
+ 'locale' => $this->faker->locale(),
+ 'is_developer' => false,
+ 'created_at' => Carbon::now(),
+ 'updated_at' => Carbon::now(),
];
}
}
diff --git a/database/migrations/2024_08_10_210719_create_settings_table.php b/database/migrations/2024_08_10_210719_create_settings_table.php
index cd76fb2..c486576 100644
--- a/database/migrations/2024_08_10_210719_create_settings_table.php
+++ b/database/migrations/2024_08_10_210719_create_settings_table.php
@@ -12,15 +12,17 @@ public function up()
$table->id();
$table->foreignId('user_id')->constrained('users');
$table->foreignId('occupation_id')->constrained('occupations');
- $table->string('pronouns');
- $table->string('timezone');
- $table->string('locale');
+ $table->boolean('enabled')->default(true);
+ $table->string('channel_id')->default('global');
+ $table->string('pronouns')->nullable();
+ $table->string('timezone')->nullable();
+ $table->string('locale')->nullable();
$table->boolean('is_developer')->default(false);
$table->timestamps();
});
}
- public function down()
+ public function down(): void
{
Schema::dropIfExists('settings');
}
diff --git a/phpunit.xml b/phpunit.xml
index 61c031c..98ec861 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/routes/api.php b/routes/api.php
index 65d1755..4cdbdbc 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -13,6 +13,8 @@
Route::post('/authenticate/{provider}', [OAuthController::class, 'authenticateWithOAuth']);
Route::middleware(['auth:sanctum', 'throttle:30,1'])->group(function () {
+ Route::get('/me/settings', [AuthenticatedUserController::class, 'getSettings'])
+ ->name('auth.my-settings');
Route::put('/me/update-settings', [AuthenticatedUserController::class, 'putSettings'])
->name('auth.update-settings');
});
diff --git a/tests/Feature/Http/Controllers/Api/V1/AuthenticatedUserControllerTest.php b/tests/Feature/Http/Controllers/Api/V1/AuthenticatedUserControllerTest.php
index 3432388..a0d9457 100644
--- a/tests/Feature/Http/Controllers/Api/V1/AuthenticatedUserControllerTest.php
+++ b/tests/Feature/Http/Controllers/Api/V1/AuthenticatedUserControllerTest.php
@@ -3,23 +3,23 @@
namespace Tests\Feature\Http\Controllers\Api\V1;
use App\Clients\Consumer\ConsumerClient;
+use App\Models\Settings\Color;
+use App\Models\Settings\Effect;
+use App\Models\Settings\Occupation;
+use App\Models\Settings\Settings;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AuthenticatedUserControllerTest extends TestCase
{
use RefreshDatabase;
- protected function setUp(): void
- {
- parent::setUp(); // TODO: Change the autogenerated stu
- }
-
public function testPutSettings()
{
// Arrange
- $this->artisan('db:seed');
+
$user = User::factory()->create();
/**
@@ -27,10 +27,9 @@ public function testPutSettings()
* the Rust + ScyllaDB (and also Postgres, Redis) just to run the tests,
* at the same time I'm not a php dev, so there may well be a better solution for this.
*/
- $this->partialMock(ConsumerClient::class, function ($mock) use ($user) {
+ $this->partialMock(ConsumerClient::class, function ($mock) {
$mock->shouldReceive('updateUser')
->once()
- ->with($user)
->andReturn(true);
});
@@ -50,7 +49,11 @@ public function testPutSettings()
$payload = [
'user_id' => $user->id,
'occupation_id' => 2,
+ 'enabled' => true,
+ 'channel_id' => 'danielhe4rt',
'pronouns' => 'she-her',
+ 'effect_id' => 1,
+ 'color_id' => 1,
];
// Act
@@ -71,4 +74,45 @@ public function testPutSettings()
'pronouns' => 'she-her',
]);
}
+
+ #[DataProvider('settingsDataProvider')]
+ public function testGetSettings(?string $payload, int $count)
+ {
+ // Arrange
+ $user = User::factory()->create();
+
+ if ($payload) {
+ Settings::factory()->create([
+ 'user_id' => $user->id,
+ 'channel_id' => $payload,
+ 'occupation_id' => Occupation::factory(),
+ 'color_id' => Color::factory(),
+ 'effect_id' => Effect::factory(),
+ ]);
+ }
+
+ // Act
+ $response = $this
+ ->actingAs($user)
+ ->get(route('auth.my-settings', ['channel_id' => $payload]));
+
+ // Assert
+ $response
+ ->assertOk()
+ ->assertJsonCount($count, 'data');
+ }
+
+ public static function settingsDataProvider()
+ {
+ return [
+ 'only_global' => [
+ 'payload' => null,
+ 'count' => 1,
+ ],
+ 'with_channel' => [
+ 'payload' => 'danielhe4rt',
+ 'count' => 2,
+ ],
+ ];
+ }
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index fe1ffc2..b124225 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -6,5 +6,5 @@
abstract class TestCase extends BaseTestCase
{
- //
+ public $seed = true;
}