Skip to content

Commit

Permalink
feat: added ability to add users to a specific map
Browse files Browse the repository at this point in the history
  • Loading branch information
mwargan committed Apr 3, 2024
1 parent 4e6654e commit cbd602f
Show file tree
Hide file tree
Showing 12 changed files with 460 additions and 1 deletion.
97 changes: 97 additions & 0 deletions app/Http/Controllers/MapUserController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace App\Http\Controllers;

use App\Http\Resources\UserResource;
use App\Models\Map;
use Illuminate\Http\Request;

class MapUserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request, Map $map)
{
// We'll use the "delete map" policy to check if the user can delete the map. If they can delete it, they can also view the users of it. We need to explicitly pass the policy name specifically
$this->authorize('delete', $map);

// Return the users of the map
return UserResource::collection($map->users);
}
/**
* Display a listing of the resource. The map uuid will be in the URL
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request, Map $map)
{
// We'll use the "delete map" policy to check if the user can delete the map. If they can delete it, they can also add users to it. We need to explicitly pass the policy name specifically
$this->authorize('delete', $map);

$request->validate([
'username' => 'required|exists:users,username',
'can_create_markers' => 'boolean',
]);

// Attach the user to the map. We need to find the user by the username
$user = \App\Models\User::where('username', $request->username)->firstOrFail();

// The map hasMany users, so we can use the users() relationship to attach the user to the map
$map->users()->attach($user->id, [
'can_create_markers' => $request->can_create_markers ?? false,
]);

// Return a 201 response
return response()->json(null, 201);
}

/**
* Update the specified resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Map $map, $username)
{
// We'll use the "delete map" policy to check if the user can delete the map. If they can delete it, they can also update users of it. We need to explicitly pass the policy name specifically
$this->authorize('delete', $map);

$request->validate([
'can_create_markers' => 'boolean',
]);

// Find the user by the username
$user = \App\Models\User::where('username', $username)->firstOrFail();

// Update the user's can_create_markers attribute
$map->users()->updateExistingPivot($user->id, [
'can_create_markers' => $request->can_create_markers ?? false,
]);

// Return a 204 response
return response()->json(null, 204);
}

/**
* Remove the specified resource from storage.
*
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request, Map $map, $username)
{
// We'll use the "delete map" policy to check if the user can delete the map. If they can delete it, they can also remove users from it. We need to explicitly pass the policy name specifically
$this->authorize('delete', $map);

// Find the user by the username
$user = \App\Models\User::where('username', $username)->firstOrFail();

// Detach the user from the map
$map->users()->detach($user->id);

// Return a 204 response
return response()->json(null, 204);
}
}
2 changes: 2 additions & 0 deletions app/Http/Controllers/MarkerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public function index(Request $request, Map $map)
*/
public function store(StoreMarkerRequest $request, Map $map)
{
$this->authorize('create', [Marker::class, $map, $request->input('map_token')]);

if (!$request->input('category')) {
$category = \App\Models\Category::firstOrCreate(
['slug' => Str::slug($request->input('category_name'))],
Expand Down
4 changes: 3 additions & 1 deletion app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Resources;

use App\Models\MapUser;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
Expand All @@ -15,7 +16,6 @@ class UserResource extends JsonResource
public function toArray($request)
{
$userBelongsToAuthenticatedUser = optional($request->user())->can('update', $this);

return [
'username' => $this->username,
'description' => $this->description,
Expand All @@ -27,6 +27,8 @@ public function toArray($request)
'created_at' => $this->created_at,
'updated_at' => $this->when($userBelongsToAuthenticatedUser, $this->updated_at),
'email_verified_at' => $this->email_verified_at,
// Sometimes we also load the pivot MapUser. If thats the case, we need to also show the can_create_markers
'can_create_markers' => $this->whenPivotLoaded(new MapUser, fn () => $this->pivot->can_create_markers),
];
}
}
6 changes: 6 additions & 0 deletions app/Models/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ public function user(): BelongsTo
return $this->belongsTo(\App\Models\User::class);
}

// The additional users that are attached to this map via MapUser model
public function users(): BelongsToMany
{
return $this->belongsToMany(\App\Models\User::class, 'map_users')->using(MapUser::class)->withPivot(['can_create_markers'])->withTimestamps();
}

public function contributors(): HasManyThrough
{
return $this->hasManyThrough(\App\Models\User::class, \App\Models\Marker::class, 'map_id', 'id', 'id', 'user_id')->groupBy([
Expand Down
54 changes: 54 additions & 0 deletions app/Models/MapUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\Pivot;

class MapUser extends Pivot
{
use HasFactory;

public $incrementing = true;

protected $table = 'map_users';

protected $fillable = [
'map_id',
'user_id',
'can_create_markers',
'added_by_user_id',
];

protected $hidden = ['added_by_user_id', 'map_id', 'user_id'];

protected $casts = [
'can_create_markers' => 'boolean',
];

// On create, by default we set the created_by_user_id to the user_id
protected static function booted()
{
static::creating(function ($mapUser) {
$mapUser->added_by_user_id = $mapUser->added_by_user_id ?? optional(request()->user())->id;
});
}

// Define the relationship with the Map model
public function map()
{
return $this->belongsTo(Map::class);
}

// Define the relationship with the User model
public function user()
{
return $this->belongsTo(User::class);
}

// Define the relationship with the User model for the user who added this map user
public function addedByUser()
{
return $this->belongsTo(User::class, 'added_by_user_id');
}
}
4 changes: 4 additions & 0 deletions app/Policies/MapPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public function view(?User $user, Map $map)
if ($user && $map->user_id == $user->id) {
return true;
}
// If the user is in the map->users relationship, they can view the map
if ($user && $map->users->contains($user)) {
return true;
}

return false;
}
Expand Down
4 changes: 4 additions & 0 deletions app/Policies/MarkerPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public function create(?User $user, Map $map, $token = null)
if ($user && $map->user_id == $user->id) {
return true;
}
// If the user is a member of the map and has the `can_create_markers` permission, they can create markers
if ($user && $map->users->contains($user) && $map->users->find($user->id)->pivot->can_create_markers) {
return true;
}

return false;
}
Expand Down
1 change: 1 addition & 0 deletions app/Providers/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AuthServiceProvider extends ServiceProvider
'Spatie\Permission\Models\Role' => \App\Policies\RolePolicy::class,
\App\Models\Marker::class => \App\Policies\MarkerPolicy::class,
\App\Models\Map::class => \App\Policies\MapPolicy::class,
\App\Models\MapUser::class => \App\Policies\MapPolicy::class,
];

/**
Expand Down
42 changes: 42 additions & 0 deletions database/migrations/2024_04_02_195445_create_map_users_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

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

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('map_users', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->unsigned();
$table->integer('map_id')->unsigned();
$table->integer('added_by_user_id')->unsigned();
$table->boolean('can_create_markers')->default(false);
$table->timestamps();

$table->foreign('user_id')->references('id')->on('users');
$table->foreign('map_id')->references('id')->on('maps');
$table->foreign('added_by_user_id')->references('id')->on('users');

// We need to make sure that the user_id and map_id are unique together
$table->unique(['user_id', 'map_id']);
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('map_users');
}
};
4 changes: 4 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
Route::delete('maps/{map}/claim', 'MapController@unclaim');

Route::apiResource('users', 'UserController')->except(['create', 'index', 'show']);

Route::get('maps/{map}/users', 'MapUserController@index');
Route::post('maps/{map}/users', 'MapUserController@store');
Route::delete('maps/{map}/users/{user}', 'MapUserController@destroy');
});

Route::get('{any}', function () {
Expand Down
Loading

0 comments on commit cbd602f

Please sign in to comment.