Skip to content

Commit

Permalink
[Feat] Community interest backend (#12411)
Browse files Browse the repository at this point in the history
* add model with seeder and policy

* update graphql schema with validation, policy

* remove community interest seeder

* add unique validation, delete test

* fix test case names

* improve create test stability

* fix casing

* add unique constraints to db

* fix test for unique values

* remove community from update input

* write schema updates
  • Loading branch information
esizer authored Jan 6, 2025
1 parent 48a31b4 commit a3fe42a
Show file tree
Hide file tree
Showing 13 changed files with 596 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\GraphQL\Validators;

use App\Models\WorkStream;
use Database\Helpers\ApiErrorEnums;
use Illuminate\Validation\Rule;
use Nuwave\Lighthouse\Validation\Validator;

final class CreateCommunityInterestInputValidator extends Validator
{
/**
* Return the validation rules.
*
* @return array<string, array<mixed>>
*/
public function rules(): array
{
$communityId = $this->arg('community.connect');
$workStreams = $communityId ? WorkStream::where('community_id', $communityId)->get('id')->pluck('id') : [];

return [
'userId' => ['uuid', 'required', 'exists:users,id'],
'community.connect' => ['uuid', 'required', 'exists:communities,id', Rule::unique('community_interests', 'community_id')->where(function ($query) {
return $query->where('user_id', $this->arg('userId'));
})],
'workStreams.sync.*' => ['uuid', 'exists:work_streams,id', Rule::in($workStreams)],
'jobInterest' => ['nullable', 'boolean'],
'trainingInterest' => ['nullable', 'boolean'],
'additionalInformation' => ['nullable', 'string'],
];
}

public function messages(): array
{
return [
'community.connect.exists' => ApiErrorEnums::COMMUNITY_NOT_FOUND,
'community.connect.unique' => ApiErrorEnums::COMMUNITY_INTEREST_EXISTS,
'workStreams.sync.*.in' => ApiErrorEnums::WORK_STREAM_NOT_IN_COMMUNITY,
'workStreams.sync.*.exists' => ApiErrorEnums::WORK_STREAM_NOT_FOUND,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\GraphQL\Validators;

use App\Models\CommunityInterest;
use App\Models\WorkStream;
use Database\Helpers\ApiErrorEnums;
use Illuminate\Validation\Rule;
use Nuwave\Lighthouse\Validation\Validator;

final class UpdateCommunityInterestInputValidator extends Validator
{
/**
* Return the validation rules.
*
* @return array<string, array<mixed>>
*/
public function rules(): array
{
$communityId = CommunityInterest::with('community')->find($this->arg('id'))?->community?->id;
$workStreams = $communityId ? WorkStream::where('community_id', $communityId)->get('id')->pluck('id') : [];

return [
'workStreams.sync.*' => ['uuid', 'exists:work_streams,id', Rule::in($workStreams)],
'jobInterest' => ['nullable', 'boolean'],
'trainingInterest' => ['nullable', 'boolean'],
'additionalInformation' => ['nullable', 'string'],
];
}

public function messages(): array
{
return [
'community.connect.exists' => ApiErrorEnums::COMMUNITY_NOT_FOUND,
'workStreams.sync.*.in' => ApiErrorEnums::WORK_STREAM_NOT_IN_COMMUNITY,
'workStreams.sync.*.exists' => ApiErrorEnums::WORK_STREAM_NOT_FOUND,
];
}
}
32 changes: 32 additions & 0 deletions api/app/Models/CommunityInterest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

/**
* Class CommunityInterest
*
* @property string $id
*/
class CommunityInterest extends Model
{
use HasFactory;

protected $keyType = 'string';

/** @return BelongsTo<Community, $this> */
public function community(): BelongsTo
{
return $this->belongsTo(Community::class);
}

/** @return BelongsToMany<WorkStream, $this> */
public function workStreams(): BelongsToMany
{
return $this->belongsToMany(WorkStream::class);
}
}
7 changes: 7 additions & 0 deletions api/app/Models/EmployeeProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
Expand Down Expand Up @@ -70,4 +71,10 @@ public function userPublicProfile(): HasOne
{
return $this->hasOne(User::class, 'id')->select(['email', 'firstName', 'lastName']);
}

/** @return HasMany<CommunityInterest, $this> */
public function communityInterests(): HasMany
{
return $this->hasMany(CommunityInterest::class, 'user_id');
}
}
47 changes: 47 additions & 0 deletions api/app/Policies/CommunityInterestPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Policies;

use App\Models\CommunityInterest;
use App\Models\User;

class CommunityInterestPolicy
{
/**
* Determine whether the user can view the model.
*/
public function view(User $user, CommunityInterest $communityInterest): bool
{
return $user->isAbleTo('view-own-employeeProfile') && $user->id === $communityInterest->user_id;
}

/**
* Determine whether the user can create models.
*/
public function create(User $user, array $args): bool
{

$targetUserId = isset($args['user_id']) ? $args['user_id'] : null;
if ($targetUserId) {
return $user->isAbleTo('update-own-employeeProfile') && $user->id === $targetUserId;
}

return false;
}

/**
* Determine whether the user can update the model.
*/
public function update(User $user, CommunityInterest $communityInterest): bool
{
return $user->isAbleTo('update-own-employeeProfile') && $user->id === $communityInterest->user_id;
}

/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, CommunityInterest $communityInterest): bool
{
return $user->isAbleTo('update-own-employeeProfile') && $user->id === $communityInterest->user_id;
}
}
55 changes: 55 additions & 0 deletions api/database/factories/CommunityInterestFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Database\Factories;

use App\Models\Community;
use App\Models\CommunityInterest;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\CommunityInterest>
*/
class CommunityInterestFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{

$user = User::inRandomOrder()->first();
if (! $user) {
$user = User::factory()->create();
}

$community = Community::inRandomOrder()->first();
if (! $community) {
$community = Community::factory()->withWorkStreams()->create();
}

return [
'user_id' => $user,
'community_id' => $community,
'job_interest' => $this->faker->boolean(),
'training_interest' => $this->faker->boolean(),
'additional_information' => $this->faker->paragraph(),
];
}

/**
* Create many work stream relationships
*/
public function withWorkStreams(int $limit = 3)
{
$count = $this->faker->numberBetween(1, $limit);

return $this->afterCreating(function (CommunityInterest $model) use ($count) {
$workStreams = $model->community->workStreams()->limit($count)->get()->pluck('id');

$model->workStreams()->attach($workStreams);
});
}
}
16 changes: 16 additions & 0 deletions api/database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use App\Models\Classification;
use App\Models\Community;
use App\Models\CommunityExperience;
use App\Models\CommunityInterest;
use App\Models\Department;
use App\Models\EducationExperience;
use App\Models\PersonalExperience;
Expand All @@ -30,6 +31,7 @@
use App\Models\UserSkill;
use App\Models\WorkExperience;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\Sequence;

class UserFactory extends Factory
{
Expand Down Expand Up @@ -249,6 +251,20 @@ public function withEmployeeProfile()
});
}

public function withCommunityInterests(int $limit = 3, int $workStreamLimit = 3)
{
$count = $this->faker->numberBetween(1, $limit);
$workStreamCount = $this->faker->numberBetween(1, $workStreamLimit);

return $this->afterCreating(function (User $user) use ($count, $workStreamCount) {
CommunityInterest::factory()->withWorkStreams($workStreamCount)->count($count)
->state(new Sequence(fn () => ['community_id' => Community::factory()->withWorkStreams()->create()]))
->create([
'user_id' => $user->id,
]);
});
}

public function configure()
{
return $this->afterCreating(function (User $user) {
Expand Down
2 changes: 2 additions & 0 deletions api/database/helpers/ApiErrorEnums.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class ApiErrorEnums
// Employee profile validation
const COMMUNITY_NOT_FOUND = 'CommunityNotFound';

const COMMUNITY_INTEREST_EXISTS = 'CommunityInterestExists';

const CLASSIFICATION_NOT_FOUND = 'ClassificationNotFound';

const DEPARTMENT_NOT_FOUND = 'DepartmentNotFound';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

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

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('community_interests', function (Blueprint $table) {
$table->uuid('id')->primary()->default(new Expression('public.gen_random_uuid()'));
$table->foreignUuid('user_id')
->constrained()
->onDelete('cascade');
$table->foreignUuid('community_id')
->constrained()
->onDelete('cascade');
$table->unique(['user_id', 'community_id']);
$table->boolean('job_interest')->nullable();
$table->boolean('training_interest')->nullable();
$table->text('additional_information')->nullable();
$table->timestamps();
});

Schema::create('community_interest_work_stream', function (Blueprint $table) {
$table->uuid('id')->primary()->default(new Expression('public.gen_random_uuid()'));
$table->foreignUuid('community_interest_id')
->constrained()
->onDelete('cascade');
$table->foreignUuid('work_stream_id')
->constrained()
->onDelete('cascade');
$table->unique(['community_interest_id', 'work_stream_id']);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('community_interests');
Schema::dropIfExists('communinty_interest_work_stream');
}
};
1 change: 1 addition & 0 deletions api/database/seeders/UserTestSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public function run()
->asApplicant()
->withSkillsAndExperiences()
->withEmployeeProfile()
->withCommunityInterests()
->create([
'first_name' => 'Gul',
'last_name' => 'Fields',
Expand Down
Loading

0 comments on commit a3fe42a

Please sign in to comment.