diff --git a/app/Conductors/PostConductor.php b/app/Conductors/ArticleConductor.php similarity index 86% rename from app/Conductors/PostConductor.php rename to app/Conductors/ArticleConductor.php index 4862dc4..437c323 100644 --- a/app/Conductors/PostConductor.php +++ b/app/Conductors/ArticleConductor.php @@ -13,13 +13,13 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use LogicException; -class PostConductor extends Conductor +class ArticleConductor extends Conductor { /** * The Model Class * @var string */ - protected $class = '\App\Models\Post'; + protected $class = '\App\Models\Article'; /** * The default sorting field @@ -44,7 +44,7 @@ class PostConductor extends Conductor public function scope(Builder $builder) { $user = auth()->user(); - if ($user === null || $user->hasPermission('admin/posts') === false) { + if ($user === null || $user->hasPermission('admin/articles') === false) { $builder ->where('publish_at', '<=', now()); } @@ -60,7 +60,7 @@ class PostConductor extends Conductor { if (Carbon::parse($model->publish_at)->isFuture() === true) { $user = auth()->user(); - if ($user === null || $user->hasPermission('admin/posts') === false) { + if ($user === null || $user->hasPermission('admin/articles') === false) { return false; } } @@ -76,7 +76,7 @@ class PostConductor extends Conductor public static function creatable() { $user = auth()->user(); - return ($user !== null && $user->hasPermission('admin/posts') === true); + return ($user !== null && $user->hasPermission('admin/articles') === true); } /** @@ -88,7 +88,7 @@ class PostConductor extends Conductor public static function updatable(Model $model) { $user = auth()->user(); - return ($user !== null && $user->hasPermission('admin/posts') === true); + return ($user !== null && $user->hasPermission('admin/articles') === true); } /** @@ -100,7 +100,7 @@ class PostConductor extends Conductor public static function destroyable(Model $model) { $user = auth()->user(); - return ($user !== null && $user->hasPermission('admin/posts') === true); + return ($user !== null && $user->hasPermission('admin/articles') === true); } /** diff --git a/app/Http/Controllers/Api/PostController.php b/app/Http/Controllers/Api/ArticleController.php similarity index 58% rename from app/Http/Controllers/Api/PostController.php rename to app/Http/Controllers/Api/ArticleController.php index c205557..5562936 100644 --- a/app/Http/Controllers/Api/PostController.php +++ b/app/Http/Controllers/Api/ArticleController.php @@ -3,11 +3,11 @@ namespace App\Http\Controllers\Api; use App\Conductors\MediaConductor; -use App\Conductors\PostConductor; +use App\Conductors\ArticleConductor; use App\Enum\HttpResponseCodes; -use App\Http\Requests\PostRequest; +use App\Http\Requests\ArticleRequest; use App\Models\Media; -use App\Models\Post; +use App\Models\Article; use Illuminate\Http\JsonResponse; use Carbon\Exceptions\InvalidFormatException; use Illuminate\Contracts\Container\BindingResolutionException; @@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\InvalidCastException; use Illuminate\Database\Eloquent\MassAssignmentException; use Illuminate\Http\Request; -class PostController extends ApiController +class ArticleController extends ApiController { /** * ApplicationController constructor. @@ -38,12 +38,13 @@ class PostController extends ApiController */ public function index(Request $request) { - list($collection, $total) = PostConductor::request($request); + list($collection, $total) = ArticleConductor::request($request); return $this->respondAsResource( $collection, ['isCollection' => true, - 'appendData' => ['total' => $total]] + 'appendData' => ['total' => $total] + ] ); } @@ -51,13 +52,13 @@ class PostController extends ApiController * Display the specified resource. * * @param \Illuminate\Http\Request $request The endpoint request. - * @param \App\Models\Post $post The post model. + * @param \App\Models\Article $article The article model. * @return \Illuminate\Http\Response */ - public function show(Request $request, Post $post) + public function show(Request $request, Article $article) { - if (PostConductor::viewable($post) === true) { - return $this->respondAsResource(PostConductor::model($request, $post)); + if (ArticleConductor::viewable($article) === true) { + return $this->respondAsResource(ArticleConductor::model($request, $article)); } return $this->respondForbidden(); @@ -66,15 +67,15 @@ class PostController extends ApiController /** * Store a newly created resource in storage. * - * @param \App\Http\Requests\PostRequest $request The user request. + * @param \App\Http\Requests\ArticleRequest $request The user request. * @return \Illuminate\Http\Response */ - public function store(PostRequest $request) + public function store(ArticleRequest $request) { - if (PostConductor::creatable() === true) { - $post = Post::create($request->all()); + if (ArticleConductor::creatable() === true) { + $article = Article::create($request->all()); return $this->respondAsResource( - PostConductor::model($request, $post), + ArticleConductor::model($request, $article), ['respondCode' => HttpResponseCodes::HTTP_CREATED] ); } else { @@ -85,15 +86,15 @@ class PostController extends ApiController /** * Update the specified resource in storage. * - * @param \App\Http\Requests\PostRequest $request The post update request. - * @param \App\Models\Post $post The specified post. + * @param \App\Http\Requests\ArticleRequest $request The article update request. + * @param \App\Models\Article $article The specified article. * @return \Illuminate\Http\Response */ - public function update(PostRequest $request, Post $post) + public function update(ArticleRequest $request, Article $article) { - if (PostConductor::updatable($post) === true) { - $post->update($request->all()); - return $this->respondAsResource(PostConductor::model($request, $post)); + if (ArticleConductor::updatable($article) === true) { + $article->update($request->all()); + return $this->respondAsResource(ArticleConductor::model($request, $article)); } return $this->respondForbidden(); @@ -102,13 +103,13 @@ class PostController extends ApiController /** * Remove the specified resource from storage. * - * @param \App\Models\Post $post The specified post. + * @param \App\Models\Article $article The specified article. * @return \Illuminate\Http\Response */ - public function destroy(Post $post) + public function destroy(Article $article) { - if (PostConductor::destroyable($post) === true) { - $post->delete(); + if (ArticleConductor::destroyable($article) === true) { + $article->delete(); return $this->respondNoContent(); } else { return $this->respondForbidden(); @@ -117,18 +118,18 @@ class PostController extends ApiController /** * Get a list of attachments related to this model. - * + * * @param Request $request The user request. - * @param Post $post The post model. - * @return JsonResponse Returns the post attachments. - * @throws InvalidFormatException - * @throws BindingResolutionException - * @throws InvalidCastException + * @param Article $article The article model. + * @return JsonResponse Returns the article attachments. + * @throws InvalidFormatException + * @throws BindingResolutionException + * @throws InvalidCastException */ - public function getAttachments(Request $request, Post $post) + public function getAttachments(Request $request, Article $article) { - if (PostConductor::viewable($post) === true) { - $medium = $post->attachments->map(function ($attachment) { + if (ArticleConductor::viewable($article) === true) { + $medium = $article->attachments->map(function ($attachment) { return $attachment->media; }); @@ -140,18 +141,18 @@ class PostController extends ApiController /** * Store an attachment related to this model. - * + * * @param Request $request The user request. - * @param Post $post The post model. + * @param Article $article The article model. * @return JsonResponse The response. - * @throws BindingResolutionException - * @throws MassAssignmentException + * @throws BindingResolutionException + * @throws MassAssignmentException */ - public function storeAttachment(Request $request, Post $post) + public function storeAttachment(Request $request, Article $article) { - if (PostConductor::updatable($post) === true) { - if($request->has("medium") && Media::find($request->medium)) { - $post->attachments()->create(['media_id' => $request->medium]); + if (ArticleConductor::updatable($article) === true) { + if ($request->has("medium") && Media::find($request->medium)) { + $article->attachments()->create(['media_id' => $request->medium]); return $this->respondCreated(); } @@ -163,67 +164,67 @@ class PostController extends ApiController /** * Update/replace attachments related to this model. - * + * * @param Request $request The user request. - * @param Post $post The related model. + * @param Article $article The related model. * @return JsonResponse - * @throws BindingResolutionException - * @throws MassAssignmentException + * @throws BindingResolutionException + * @throws MassAssignmentException */ - public function updateAttachments(Request $request, Post $post) + public function updateAttachments(Request $request, Article $article) { - if (PostConductor::updatable($post) === true) { + if (ArticleConductor::updatable($article) === true) { $mediaIds = $request->attachments; - if(is_array($mediaIds) === false) { + if (is_array($mediaIds) === false) { $mediaIds = explode(',', $request->attachments); } - + $mediaIds = array_map('trim', $mediaIds); // trim each media ID - $attachments = $post->attachments; - + $attachments = $article->attachments; + // Delete attachments that are not in $mediaIds foreach ($attachments as $attachment) { if (!in_array($attachment->media_id, $mediaIds)) { $attachment->delete(); } } - - // Create new attachments for media IDs that are not already in $post->attachments() + + // Create new attachments for media IDs that are not already in $article->attachments() foreach ($mediaIds as $mediaId) { $found = false; - + foreach ($attachments as $attachment) { if ($attachment->media_id == $mediaId) { $found = true; break; } } - + if (!$found) { - $post->attachments()->create(['media_id' => $mediaId]); + $article->attachments()->create(['media_id' => $mediaId]); } } - + return $this->respondNoContent(); - } - + }//end if + return $this->respondForbidden(); } /** * Delete a specific related attachment. * @param Request $request The user request. - * @param Post $post The model. - * @param Media $medium The attachment medium. - * @return JsonResponse - * @throws BindingResolutionException + * @param Article $article The model. + * @param Media $medium The attachment medium. + * @return JsonResponse + * @throws BindingResolutionException */ - public function deleteAttachment(Request $request, Post $post, Media $medium) + public function deleteAttachment(Request $request, Article $article, Media $medium) { - if (PostConductor::updatable($post) === true) { - $attachments = $post->attachments; + if (ArticleConductor::updatable($article) === true) { + $attachments = $article->attachments; $deleted = false; - + foreach ($attachments as $attachment) { if ($attachment->media_id === $medium->id) { $attachment->delete(); @@ -231,7 +232,7 @@ class PostController extends ApiController break; } } - + if ($deleted) { // Attachment was deleted successfully return $this->respondNoContent(); diff --git a/app/Http/Controllers/Api/EventController.php b/app/Http/Controllers/Api/EventController.php index 686acdb..9145278 100644 --- a/app/Http/Controllers/Api/EventController.php +++ b/app/Http/Controllers/Api/EventController.php @@ -111,8 +111,8 @@ class EventController extends ApiController * Get a list of attachments related to this model. * * @param Request $request The user request. - * @param Post $post The post model. - * @return JsonResponse Returns the post attachments. + * @param Article $article The article model. + * @return JsonResponse Returns the article attachments. * @throws InvalidFormatException * @throws BindingResolutionException * @throws InvalidCastException @@ -134,7 +134,7 @@ class EventController extends ApiController * Store an attachment related to this model. * * @param Request $request The user request. - * @param Post $post The post model. + * @param Article $article The article model. * @return JsonResponse The response. * @throws BindingResolutionException * @throws MassAssignmentException @@ -157,7 +157,7 @@ class EventController extends ApiController * Update/replace attachments related to this model. * * @param Request $request The user request. - * @param Post $post The related model. + * @param Article $article The related model. * @return JsonResponse * @throws BindingResolutionException * @throws MassAssignmentException @@ -180,7 +180,7 @@ class EventController extends ApiController } } - // Create new attachments for media IDs that are not already in $post->attachments() + // Create new attachments for media IDs that are not already in $article->attachments() foreach ($mediaIds as $mediaId) { $found = false; @@ -205,7 +205,7 @@ class EventController extends ApiController /** * Delete a specific related attachment. * @param Request $request The user request. - * @param Post $post The model. + * @param Article $article The model. * @param Media $medium The attachment medium. * @return JsonResponse * @throws BindingResolutionException diff --git a/app/Http/Requests/PostRequest.php b/app/Http/Requests/ArticleRequest.php similarity index 85% rename from app/Http/Requests/PostRequest.php rename to app/Http/Requests/ArticleRequest.php index b8593c5..fb2ac4b 100644 --- a/app/Http/Requests/PostRequest.php +++ b/app/Http/Requests/ArticleRequest.php @@ -4,7 +4,7 @@ namespace App\Http\Requests; use Illuminate\Validation\Rule; -class PostRequest extends BaseRequest +class ArticleRequest extends BaseRequest { /** * Get the validation rules that apply to POST requests. @@ -14,7 +14,7 @@ class PostRequest extends BaseRequest public function postRules() { return [ - 'slug' => 'required|string|min:6|unique:posts', + 'slug' => 'required|string|min:6|unique:articles', 'title' => 'required|string|min:6|max:255', 'publish_at' => 'required|date', 'user_id' => 'required|uuid|exists:users,id', @@ -34,7 +34,7 @@ class PostRequest extends BaseRequest 'slug' => [ 'string', 'min:6', - Rule::unique('posts')->ignoreModel($this->post), + Rule::unique('articles')->ignoreModel($this->article), ], 'title' => 'string|min:6|max:255', 'publish_at' => 'date', diff --git a/app/Models/Post.php b/app/Models/Article.php similarity index 88% rename from app/Models/Post.php rename to app/Models/Article.php index e2a6e0e..4df85b3 100644 --- a/app/Models/Post.php +++ b/app/Models/Article.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphMany; -class Post extends Model +class Article extends Model { use HasFactory; use Uuids; @@ -28,7 +28,7 @@ class Post extends Model /** - * Get the post user + * Get the article user * * @return BelongsTo */ @@ -38,7 +38,7 @@ class Post extends Model } /** - * Get all of the post's attachments. + * Get all of the article's attachments. * * @return MorphMany */ diff --git a/app/Models/Event.php b/app/Models/Event.php index 21946da..895736a 100644 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -34,7 +34,7 @@ class Event extends Model /** - * Get all of the post's attachments. + * Get all of the article's attachments. */ public function attachments() { diff --git a/app/Models/User.php b/app/Models/User.php index 8c15811..7187110 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -142,7 +142,7 @@ class User extends Authenticatable implements Auditable * Revoke permissions from the user * * @param string|array $permissions The permission(s) to revoke. - * @return int + * @return integer */ public function revokePermission($permissions) { @@ -170,9 +170,9 @@ class User extends Authenticatable implements Auditable * * @return HasMany */ - public function posts() + public function articles() { - return $this->hasMany(Post::class); + return $this->hasMany(Article::class); } /** diff --git a/database/factories/PostFactory.php b/database/factories/ArticleFactory.php similarity index 95% rename from database/factories/PostFactory.php rename to database/factories/ArticleFactory.php index 80405e2..56ee276 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/ArticleFactory.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event> */ -class PostFactory extends Factory +class ArticleFactory extends Factory { /** * Define the model's default state. diff --git a/database/migrations/2023_04_25_235615_update_posts_table.php b/database/migrations/2023_04_25_235615_update_posts_table.php new file mode 100644 index 0000000..228561d --- /dev/null +++ b/database/migrations/2023_04_25_235615_update_posts_table.php @@ -0,0 +1,35 @@ +select('id', 'permission')->where('permission', 'admin/posts')->update(['permission' => 'admin/articles']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::rename('articles', 'posts'); + + // Update permissions to use posts instead of articles + DB::table('permissions')->select('id', 'permission')->where('permission', 'admin/articles')->update(['permission' => 'admin/posts']); + } +}; diff --git a/public/uploadabiwAz b/public/uploadabiwAz deleted file mode 100644 index 00313cc..0000000 Binary files a/public/uploadabiwAz and /dev/null differ diff --git a/resources/js/components/SMHero.vue b/resources/js/components/SMHero.vue index 9d970f6..7960f72 100644 --- a/resources/js/components/SMHero.vue +++ b/resources/js/components/SMHero.vue @@ -33,7 +33,7 @@ diff --git a/resources/js/views/dashboard/Dashboard.vue b/resources/js/views/dashboard/Dashboard.vue index 4a56a93..57d8f7a 100644 --- a/resources/js/views/dashboard/Dashboard.vue +++ b/resources/js/views/dashboard/Dashboard.vue @@ -7,9 +7,9 @@

My Details

+ v-if="userStore.permissions.includes('admin/articles')" + :to="{ name: 'dashboard-article-list' }" + class="admin-card articles">

Articles

diff --git a/resources/js/views/dashboard/UserEdit.vue b/resources/js/views/dashboard/UserEdit.vue index b7df184..76877cc 100644 --- a/resources/js/views/dashboard/UserEdit.vue +++ b/resources/js/views/dashboard/UserEdit.vue @@ -41,7 +41,7 @@ faker = FakerFactory::create(); + } + + public function testAnyUserCanViewArticle() + { + // Create an event + $article = Article::factory()->create([ + 'publish_at' => $this->faker->dateTimeBetween('-2 months', '-1 month'), + ]); + + // Create a future event + $futureArticle = Article::factory()->create([ + 'publish_at' => $this->faker->dateTimeBetween('+1 month', '+2 months'), + ]); + + // Send GET request to the /api/articles endpoint + $response = $this->getJson('/api/articles'); + $response->assertStatus(200); + + // Assert that the event is in the response data + $response->assertJsonCount(1, 'articles'); + $response->assertJsonFragment([ + 'id' => $article->id, + 'title' => $article->title, + 'content' => $article->content, + ]); + + $response->assertJsonMissing([ + 'id' => $futureArticle->id, + 'title' => $futureArticle->title, + 'content' => $futureArticle->content, + ]); + } + + public function testAdminCanCreateUpdateDeleteArticle() + { + // Create a user with the admin/events permission + $adminUser = User::factory()->create(); + $adminUser->givePermission('admin/articles'); + + // Create media data + $media = Media::factory()->create(['user_id' => $adminUser->id]); + + // Create event data + $articleData = Article::factory()->make([ + 'user_id' => $adminUser->id, + 'hero' => $media->id, + ])->toArray(); + + // Test creating event + $response = $this->actingAs($adminUser)->postJson('/api/articles', $articleData); + $response->assertStatus(201); + $this->assertDatabaseHas('articles', [ + 'title' => $articleData['title'], + 'content' => $articleData['content'], + ]); + + // Test viewing event + $article = Article::where('title', $articleData['title'])->first(); + $response = $this->get("/api/articles/$article->id"); + $response->assertStatus(200); + $response->assertJsonStructure([ + 'article' => [ + 'id', + 'title', + 'content', + ] + ]); + + // Test updating event + $articleData['title'] = 'Updated Article'; + $response = $this->actingAs($adminUser)->putJson("/api/articles/$article->id", $articleData); + $response->assertStatus(200); + $this->assertDatabaseHas('articles', [ + 'title' => 'Updated Article', + ]); + + // Test deleting event + $response = $this->actingAs($adminUser)->delete("/api/articles/$article->id"); + $response->assertStatus(204); + $this->assertDatabaseMissing('articles', [ + 'title' => 'Updated Article', + ]); + } + + public function testNonAdminCannotCreateUpdateDeleteArticle() + { + // Create a user without admin/events permission + $user = User::factory()->create(); + + // Authenticate as the user + $this->actingAs($user); + + // Try to create a new article + $media = Media::factory()->create(['user_id' => $user->id]); + + $newArticleData = Article::factory()->make(['user_id' => $user->id, 'hero' => $media->id])->toArray(); + + $response = $this->postJson('/api/articles', $newArticleData); + $response->assertStatus(403); + + // Try to update an event + $article = Article::factory()->create(); + $updatedArticleData = [ + 'title' => 'Updated Event', + 'content' => 'This is an updated event.', + // Add more fields as needed + ]; + $response = $this->putJson('/api/articles/' . $article->id, $updatedArticleData); + $response->assertStatus(403); + + // Try to delete an event + $article = Article::factory()->create(); + $response = $this->deleteJson('/api/articles/' . $article->id); + $response->assertStatus(403); + } +} diff --git a/tests/Feature/PostsApiTest.php b/tests/Feature/PostsApiTest.php deleted file mode 100644 index c77a086..0000000 --- a/tests/Feature/PostsApiTest.php +++ /dev/null @@ -1,134 +0,0 @@ -faker = FakerFactory::create(); - } - - public function testAnyUserCanViewPost() - { - // Create an event - $post = Post::factory()->create([ - 'publish_at' => $this->faker->dateTimeBetween('-2 months', '-1 month'), - ]); - - // Create a future event - $futurePost = Post::factory()->create([ - 'publish_at' => $this->faker->dateTimeBetween('+1 month', '+2 months'), - ]); - - // Send GET request to the /api/posts endpoint - $response = $this->getJson('/api/posts'); - $response->assertStatus(200); - - // Assert that the event is in the response data - $response->assertJsonCount(1, 'posts'); - $response->assertJsonFragment([ - 'id' => $post->id, - 'title' => $post->title, - 'content' => $post->content, - ]); - - $response->assertJsonMissing([ - 'id' => $futurePost->id, - 'title' => $futurePost->title, - 'content' => $futurePost->content, - ]); - } - - public function testAdminCanCreateUpdateDeletePost() - { - // Create a user with the admin/events permission - $adminUser = User::factory()->create(); - $adminUser->givePermission('admin/posts'); - - // Create media data - $media = Media::factory()->create(['user_id' => $adminUser->id]); - - // Create event data - $postData = Post::factory()->make([ - 'user_id' => $adminUser->id, - 'hero' => $media->id, - ])->toArray(); - - // Test creating event - $response = $this->actingAs($adminUser)->postJson('/api/posts', $postData); - $response->assertStatus(201); - $this->assertDatabaseHas('posts', [ - 'title' => $postData['title'], - 'content' => $postData['content'], - ]); - - // Test viewing event - $post = Post::where('title', $postData['title'])->first(); - $response = $this->get("/api/posts/$post->id"); - $response->assertStatus(200); - $response->assertJsonStructure([ - 'post' => [ - 'id', - 'title', - 'content', - ] - ]); - - // Test updating event - $postData['title'] = 'Updated Post'; - $response = $this->actingAs($adminUser)->putJson("/api/posts/$post->id", $postData); - $response->assertStatus(200); - $this->assertDatabaseHas('posts', [ - 'title' => 'Updated Post', - ]); - - // Test deleting event - $response = $this->actingAs($adminUser)->delete("/api/posts/$post->id"); - $response->assertStatus(204); - $this->assertDatabaseMissing('posts', [ - 'title' => 'Updated Post', - ]); - } - - public function testNonAdminCannotCreateUpdateDeletePost() - { - // Create a user without admin/events permission - $user = User::factory()->create(); - - // Authenticate as the user - $this->actingAs($user); - - // Try to create a new post - $media = Media::factory()->create(['user_id' => $user->id]); - - $newPostData = Post::factory()->make(['user_id' => $user->id, 'hero' => $media->id])->toArray(); - - $response = $this->postJson('/api/posts', $newPostData); - $response->assertStatus(403); - - // Try to update an event - $post = Post::factory()->create(); - $updatedPostData = [ - 'title' => 'Updated Event', - 'content' => 'This is an updated event.', - // Add more fields as needed - ]; - $response = $this->putJson('/api/posts/' . $post->id, $updatedPostData); - $response->assertStatus(403); - - // Try to delete an event - $post = Post::factory()->create(); - $response = $this->deleteJson('/api/posts/' . $post->id); - $response->assertStatus(403); - } -}