change posts to articles
This commit is contained in:
@@ -13,13 +13,13 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
|
|
||||||
class PostConductor extends Conductor
|
class ArticleConductor extends Conductor
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\Post';
|
protected $class = '\App\Models\Article';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default sorting field
|
* The default sorting field
|
||||||
@@ -44,7 +44,7 @@ class PostConductor extends Conductor
|
|||||||
public function scope(Builder $builder)
|
public function scope(Builder $builder)
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/posts') === false) {
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
$builder
|
$builder
|
||||||
->where('publish_at', '<=', now());
|
->where('publish_at', '<=', now());
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ class PostConductor extends Conductor
|
|||||||
{
|
{
|
||||||
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/posts') === false) {
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ class PostConductor extends Conductor
|
|||||||
public static function creatable()
|
public static function creatable()
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$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)
|
public static function updatable(Model $model)
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$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)
|
public static function destroyable(Model $model)
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/posts') === true);
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Conductors\MediaConductor;
|
use App\Conductors\MediaConductor;
|
||||||
use App\Conductors\PostConductor;
|
use App\Conductors\ArticleConductor;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\PostRequest;
|
use App\Http\Requests\ArticleRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\Post;
|
use App\Models\Article;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Carbon\Exceptions\InvalidFormatException;
|
use Carbon\Exceptions\InvalidFormatException;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
@@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\InvalidCastException;
|
|||||||
use Illuminate\Database\Eloquent\MassAssignmentException;
|
use Illuminate\Database\Eloquent\MassAssignmentException;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class PostController extends ApiController
|
class ArticleController extends ApiController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* ApplicationController constructor.
|
* ApplicationController constructor.
|
||||||
@@ -38,12 +38,13 @@ class PostController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
list($collection, $total) = PostConductor::request($request);
|
list($collection, $total) = ArticleConductor::request($request);
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['isCollection' => true,
|
||||||
'appendData' => ['total' => $total]]
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +52,13 @@ class PostController extends ApiController
|
|||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
* @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
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, Post $post)
|
public function show(Request $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::viewable($post) === true) {
|
if (ArticleConductor::viewable($article) === true) {
|
||||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -66,15 +67,15 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* 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
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function store(PostRequest $request)
|
public function store(ArticleRequest $request)
|
||||||
{
|
{
|
||||||
if (PostConductor::creatable() === true) {
|
if (ArticleConductor::creatable() === true) {
|
||||||
$post = Post::create($request->all());
|
$article = Article::create($request->all());
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
PostConductor::model($request, $post),
|
ArticleConductor::model($request, $article),
|
||||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -85,15 +86,15 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\PostRequest $request The post update request.
|
* @param \App\Http\Requests\ArticleRequest $request The article update request.
|
||||||
* @param \App\Models\Post $post The specified post.
|
* @param \App\Models\Article $article The specified article.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(PostRequest $request, Post $post)
|
public function update(ArticleRequest $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$post->update($request->all());
|
$article->update($request->all());
|
||||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -102,13 +103,13 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* 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
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy(Post $post)
|
public function destroy(Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::destroyable($post) === true) {
|
if (ArticleConductor::destroyable($article) === true) {
|
||||||
$post->delete();
|
$article->delete();
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
} else {
|
} else {
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -117,18 +118,18 @@ class PostController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of attachments related to this model.
|
* Get a list of attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse Returns the post attachments.
|
* @return JsonResponse Returns the article attachments.
|
||||||
* @throws InvalidFormatException
|
* @throws InvalidFormatException
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws InvalidCastException
|
* @throws InvalidCastException
|
||||||
*/
|
*/
|
||||||
public function getAttachments(Request $request, Post $post)
|
public function getAttachments(Request $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::viewable($post) === true) {
|
if (ArticleConductor::viewable($article) === true) {
|
||||||
$medium = $post->attachments->map(function ($attachment) {
|
$medium = $article->attachments->map(function ($attachment) {
|
||||||
return $attachment->media;
|
return $attachment->media;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,18 +141,18 @@ class PostController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Store an attachment related to this model.
|
* Store an attachment related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse The response.
|
* @return JsonResponse The response.
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @throws MassAssignmentException
|
||||||
*/
|
*/
|
||||||
public function storeAttachment(Request $request, Post $post)
|
public function storeAttachment(Request $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
if($request->has("medium") && Media::find($request->medium)) {
|
if ($request->has("medium") && Media::find($request->medium)) {
|
||||||
$post->attachments()->create(['media_id' => $request->medium]);
|
$article->attachments()->create(['media_id' => $request->medium]);
|
||||||
return $this->respondCreated();
|
return $this->respondCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,67 +164,67 @@ class PostController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update/replace attachments related to this model.
|
* Update/replace attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The related model.
|
* @param Article $article The related model.
|
||||||
* @return JsonResponse
|
* @return JsonResponse
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @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;
|
$mediaIds = $request->attachments;
|
||||||
if(is_array($mediaIds) === false) {
|
if (is_array($mediaIds) === false) {
|
||||||
$mediaIds = explode(',', $request->attachments);
|
$mediaIds = explode(',', $request->attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||||
$attachments = $post->attachments;
|
$attachments = $article->attachments;
|
||||||
|
|
||||||
// Delete attachments that are not in $mediaIds
|
// Delete attachments that are not in $mediaIds
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
if (!in_array($attachment->media_id, $mediaIds)) {
|
if (!in_array($attachment->media_id, $mediaIds)) {
|
||||||
$attachment->delete();
|
$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) {
|
foreach ($mediaIds as $mediaId) {
|
||||||
$found = false;
|
$found = false;
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
if ($attachment->media_id == $mediaId) {
|
if ($attachment->media_id == $mediaId) {
|
||||||
$found = true;
|
$found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
$post->attachments()->create(['media_id' => $mediaId]);
|
$article->attachments()->create(['media_id' => $mediaId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
}
|
}//end if
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a specific related attachment.
|
* Delete a specific related attachment.
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The model.
|
* @param Article $article The model.
|
||||||
* @param Media $medium The attachment medium.
|
* @param Media $medium The attachment medium.
|
||||||
* @return JsonResponse
|
* @return JsonResponse
|
||||||
* @throws BindingResolutionException
|
* @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) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$attachments = $post->attachments;
|
$attachments = $article->attachments;
|
||||||
$deleted = false;
|
$deleted = false;
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
if ($attachment->media_id === $medium->id) {
|
if ($attachment->media_id === $medium->id) {
|
||||||
$attachment->delete();
|
$attachment->delete();
|
||||||
@@ -231,7 +232,7 @@ class PostController extends ApiController
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
// Attachment was deleted successfully
|
// Attachment was deleted successfully
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
@@ -111,8 +111,8 @@ class EventController extends ApiController
|
|||||||
* Get a list of attachments related to this model.
|
* Get a list of attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse Returns the post attachments.
|
* @return JsonResponse Returns the article attachments.
|
||||||
* @throws InvalidFormatException
|
* @throws InvalidFormatException
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws InvalidCastException
|
* @throws InvalidCastException
|
||||||
@@ -134,7 +134,7 @@ class EventController extends ApiController
|
|||||||
* Store an attachment related to this model.
|
* Store an attachment related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse The response.
|
* @return JsonResponse The response.
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @throws MassAssignmentException
|
||||||
@@ -157,7 +157,7 @@ class EventController extends ApiController
|
|||||||
* Update/replace attachments related to this model.
|
* Update/replace attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The related model.
|
* @param Article $article The related model.
|
||||||
* @return JsonResponse
|
* @return JsonResponse
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @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) {
|
foreach ($mediaIds as $mediaId) {
|
||||||
$found = false;
|
$found = false;
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ class EventController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Delete a specific related attachment.
|
* Delete a specific related attachment.
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The model.
|
* @param Article $article The model.
|
||||||
* @param Media $medium The attachment medium.
|
* @param Media $medium The attachment medium.
|
||||||
* @return JsonResponse
|
* @return JsonResponse
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace App\Http\Requests;
|
|||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class PostRequest extends BaseRequest
|
class ArticleRequest extends BaseRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to POST requests.
|
* Get the validation rules that apply to POST requests.
|
||||||
@@ -14,7 +14,7 @@ class PostRequest extends BaseRequest
|
|||||||
public function postRules()
|
public function postRules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'slug' => 'required|string|min:6|unique:posts',
|
'slug' => 'required|string|min:6|unique:articles',
|
||||||
'title' => 'required|string|min:6|max:255',
|
'title' => 'required|string|min:6|max:255',
|
||||||
'publish_at' => 'required|date',
|
'publish_at' => 'required|date',
|
||||||
'user_id' => 'required|uuid|exists:users,id',
|
'user_id' => 'required|uuid|exists:users,id',
|
||||||
@@ -34,7 +34,7 @@ class PostRequest extends BaseRequest
|
|||||||
'slug' => [
|
'slug' => [
|
||||||
'string',
|
'string',
|
||||||
'min:6',
|
'min:6',
|
||||||
Rule::unique('posts')->ignoreModel($this->post),
|
Rule::unique('articles')->ignoreModel($this->article),
|
||||||
],
|
],
|
||||||
'title' => 'string|min:6|max:255',
|
'title' => 'string|min:6|max:255',
|
||||||
'publish_at' => 'date',
|
'publish_at' => 'date',
|
||||||
@@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
|
||||||
class Post extends Model
|
class Article extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
@@ -28,7 +28,7 @@ class Post extends Model
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the post user
|
* Get the article user
|
||||||
*
|
*
|
||||||
* @return BelongsTo
|
* @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
|
* @return MorphMany
|
||||||
*/
|
*/
|
||||||
@@ -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()
|
public function attachments()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class User extends Authenticatable implements Auditable
|
|||||||
* Revoke permissions from the user
|
* Revoke permissions from the user
|
||||||
*
|
*
|
||||||
* @param string|array $permissions The permission(s) to revoke.
|
* @param string|array $permissions The permission(s) to revoke.
|
||||||
* @return int
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function revokePermission($permissions)
|
public function revokePermission($permissions)
|
||||||
{
|
{
|
||||||
@@ -170,9 +170,9 @@ class User extends Authenticatable implements Auditable
|
|||||||
*
|
*
|
||||||
* @return HasMany
|
* @return HasMany
|
||||||
*/
|
*/
|
||||||
public function posts()
|
public function articles()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Post::class);
|
return $this->hasMany(Article::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||||||
/**
|
/**
|
||||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
||||||
*/
|
*/
|
||||||
class PostFactory extends Factory
|
class ArticleFactory extends Factory
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Define the model's default state.
|
* Define the model's default state.
|
||||||
35
database/migrations/2023_04_25_235615_update_posts_table.php
Normal file
35
database/migrations/2023_04_25_235615_update_posts_table.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::rename('posts', 'articles');
|
||||||
|
|
||||||
|
// Update permissions to use articles instead of posts
|
||||||
|
DB::table('permissions')->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']);
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -33,7 +33,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
import { api, getApiResultData } from "../helpers/api";
|
import { api, getApiResultData } from "../helpers/api";
|
||||||
import { PostCollection } from "../helpers/api.types";
|
import { ArticleCollection } from "../helpers/api.types";
|
||||||
import { mediaGetVariantUrl } from "../helpers/media";
|
import { mediaGetVariantUrl } from "../helpers/media";
|
||||||
import { excerpt } from "../helpers/string";
|
import { excerpt } from "../helpers/string";
|
||||||
import SMButton from "./SMButton.vue";
|
import SMButton from "./SMButton.vue";
|
||||||
@@ -70,30 +70,31 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
const handleLoad = async () => {
|
const handleLoad = async () => {
|
||||||
try {
|
try {
|
||||||
let postsResult = await api.get({
|
let articlesResult = await api.get({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
params: {
|
params: {
|
||||||
limit: 3,
|
limit: 3,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const postsData = getApiResultData<PostCollection>(postsResult);
|
const articlesData =
|
||||||
|
getApiResultData<ArticleCollection>(articlesResult);
|
||||||
|
|
||||||
if (postsData && postsData.posts) {
|
if (articlesData && articlesData.articles) {
|
||||||
const randomIndex = Math.floor(
|
const randomIndex = Math.floor(
|
||||||
Math.random() * postsData.posts.length
|
Math.random() * articlesData.articles.length
|
||||||
);
|
);
|
||||||
heroTitle.value = postsData.posts[randomIndex].title;
|
heroTitle.value = articlesData.articles[randomIndex].title;
|
||||||
heroExcerpt.value = excerpt(
|
heroExcerpt.value = excerpt(
|
||||||
postsData.posts[randomIndex].content,
|
articlesData.articles[randomIndex].content,
|
||||||
200
|
200
|
||||||
);
|
);
|
||||||
heroImageUrl.value = mediaGetVariantUrl(
|
heroImageUrl.value = mediaGetVariantUrl(
|
||||||
postsData.posts[randomIndex].hero,
|
articlesData.articles[randomIndex].hero,
|
||||||
"large"
|
"large"
|
||||||
);
|
);
|
||||||
heroImageTitle = postsData.posts[randomIndex].hero.title;
|
heroImageTitle = articlesData.articles[randomIndex].hero.title;
|
||||||
heroSlug.value = postsData.posts[randomIndex].slug;
|
heroSlug.value = articlesData.articles[randomIndex].slug;
|
||||||
|
|
||||||
heroStyles.value.backgroundImage = `linear-gradient(to right, rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2)),url('${heroImageUrl.value}')`;
|
heroStyles.value.backgroundImage = `linear-gradient(to right, rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2)),url('${heroImageUrl.value}')`;
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export interface Article {
|
|||||||
attachments: Array<Media>;
|
attachments: Array<Media>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Post {
|
export interface Article {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -80,12 +80,12 @@ export interface Post {
|
|||||||
attachments: Array<Media>;
|
attachments: Array<Media>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostResponse {
|
export interface ArticleResponse {
|
||||||
post: Post;
|
article: Article;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostCollection {
|
export interface ArticleCollection {
|
||||||
posts: Array<Post>;
|
articles: Array<Article>;
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,37 +197,37 @@ export const routes = [
|
|||||||
component: () => import("@/views/dashboard/Dashboard.vue"),
|
component: () => import("@/views/dashboard/Dashboard.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "posts",
|
path: "articles",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "dashboard-post-list",
|
name: "dashboard-article-list",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Posts",
|
title: "Articles",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/dashboard/PostList.vue"),
|
import("@/views/dashboard/ArticleList.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "create",
|
path: "create",
|
||||||
name: "dashboard-post-create",
|
name: "dashboard-article-create",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Create Post",
|
title: "Create Article",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/dashboard/PostEdit.vue"),
|
import("@/views/dashboard/ArticleEdit.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":id",
|
path: ":id",
|
||||||
name: "dashboard-post-edit",
|
name: "dashboard-article-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Edit Post",
|
title: "Edit Article",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/views/dashboard/PostEdit.vue"),
|
import("@/views/dashboard/ArticleEdit.vue"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -258,7 +258,7 @@ export const routes = [
|
|||||||
path: ":id",
|
path: ":id",
|
||||||
name: "dashboard-event-edit",
|
name: "dashboard-event-edit",
|
||||||
meta: {
|
meta: {
|
||||||
title: "Event Post",
|
title: "Event",
|
||||||
middleware: "authenticated",
|
middleware: "authenticated",
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
class="thumbnail"
|
class="thumbnail"
|
||||||
:style="{ backgroundImage: `url('${backgroundImageUrl}')` }"></div>
|
:style="{ backgroundImage: `url('${backgroundImageUrl}')` }"></div>
|
||||||
<SMContainer narrow>
|
<SMContainer narrow>
|
||||||
<h1 class="title">{{ post.title }}</h1>
|
<h1 class="title">{{ article.title }}</h1>
|
||||||
<div class="author">By {{ post.user.username }}</div>
|
<div class="author">By {{ article.user.username }}</div>
|
||||||
<div class="date">{{ formattedDate(post.publish_at) }}</div>
|
<div class="date">{{ formattedDate(article.publish_at) }}</div>
|
||||||
<SMHTML :html="post.content" class="content" />
|
<SMHTML :html="article.content" class="content" />
|
||||||
<SMAttachments :attachments="post.attachments || []" />
|
<SMAttachments :attachments="article.attachments || []" />
|
||||||
</SMContainer>
|
</SMContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ import { useRoute } from "vue-router";
|
|||||||
import SMAttachments from "../components/SMAttachments.vue";
|
import SMAttachments from "../components/SMAttachments.vue";
|
||||||
import SMHTML from "../components/SMHTML.vue";
|
import SMHTML from "../components/SMHTML.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
import { Post, PostCollection, User } from "../helpers/api.types";
|
import { Article, ArticleCollection, User } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
import { useApplicationStore } from "../store/ApplicationStore";
|
import { useApplicationStore } from "../store/ApplicationStore";
|
||||||
import { mediaGetVariantUrl } from "../helpers/media";
|
import { mediaGetVariantUrl } from "../helpers/media";
|
||||||
@@ -25,9 +25,9 @@ import { mediaGetVariantUrl } from "../helpers/media";
|
|||||||
const applicationStore = useApplicationStore();
|
const applicationStore = useApplicationStore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The post data.
|
* The article data.
|
||||||
*/
|
*/
|
||||||
let post: Ref<Post> = ref({
|
let article: Ref<Article> = ref({
|
||||||
title: "",
|
title: "",
|
||||||
user: { username: "" },
|
user: { username: "" },
|
||||||
});
|
});
|
||||||
@@ -43,9 +43,9 @@ let pageError = ref(200);
|
|||||||
let pageLoading = ref(false);
|
let pageLoading = ref(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post user.
|
* Article user.
|
||||||
*/
|
*/
|
||||||
let postUser: User | null = null;
|
let articleUser: User | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thumbnail image URL.
|
* Thumbnail image URL.
|
||||||
@@ -62,25 +62,30 @@ const handleLoad = async () => {
|
|||||||
try {
|
try {
|
||||||
if (slug.length > 0) {
|
if (slug.length > 0) {
|
||||||
let result = await api.get({
|
let result = await api.get({
|
||||||
url: "/posts/",
|
url: "/articles",
|
||||||
params: {
|
params: {
|
||||||
slug: `=${slug}`,
|
slug: `=${slug}`,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = result.data as PostCollection;
|
const data = result.data as ArticleCollection;
|
||||||
|
|
||||||
if (data && data.posts && data.total && data.total > 0) {
|
if (data && data.articles && data.total && data.total > 0) {
|
||||||
post.value = data.posts[0];
|
article.value = data.articles[0];
|
||||||
|
|
||||||
post.value.publish_at = new SMDate(post.value.publish_at, {
|
article.value.publish_at = new SMDate(
|
||||||
format: "ymd",
|
article.value.publish_at,
|
||||||
utc: true,
|
{
|
||||||
}).format("yyyy/MM/dd HH:mm:ss");
|
format: "ymd",
|
||||||
|
utc: true,
|
||||||
|
}
|
||||||
|
).format("yyyy/MM/dd HH:mm:ss");
|
||||||
|
|
||||||
backgroundImageUrl.value = mediaGetVariantUrl(post.value.hero);
|
backgroundImageUrl.value = mediaGetVariantUrl(
|
||||||
applicationStore.setDynamicTitle(post.value.title);
|
article.value.hero
|
||||||
|
);
|
||||||
|
applicationStore.setDynamicTitle(article.value.title);
|
||||||
} else {
|
} else {
|
||||||
pageError.value = 404;
|
pageError.value = 404;
|
||||||
}
|
}
|
||||||
@@ -140,7 +145,7 @@ handleLoad();
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
.page-post-view .heading-image {
|
.page-article-view .heading-image {
|
||||||
height: #{calc(map-get($spacing, 3) * 10)};
|
height: #{calc(map-get($spacing, 3) * 10)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,34 +16,34 @@
|
|||||||
/></template>
|
/></template>
|
||||||
</SMInput>
|
</SMInput>
|
||||||
<SMLoading v-if="pageLoading" large />
|
<SMLoading v-if="pageLoading" large />
|
||||||
<SMNoItems v-else-if="posts.length == 0" text="No Articles Found" />
|
<SMNoItems v-else-if="articles.length == 0" text="No Articles Found" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<SMPagination
|
<SMPagination
|
||||||
v-if="postsTotal > postsPerPage"
|
v-if="articlesTotal > articlesPerPage"
|
||||||
v-model="postsPage"
|
v-model="articlesPage"
|
||||||
:total="postsTotal"
|
:total="articlesTotal"
|
||||||
:per-page="postsPerPage" />
|
:per-page="articlesPerPage" />
|
||||||
<div class="posts">
|
<div class="articles">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'article', params: { slug: post.slug } }"
|
:to="{ name: 'article', params: { slug: article.slug } }"
|
||||||
class="article-card"
|
class="article-card"
|
||||||
v-for="(post, idx) in posts"
|
v-for="(article, idx) in articles"
|
||||||
:key="idx">
|
:key="idx">
|
||||||
<div
|
<div
|
||||||
class="thumbnail"
|
class="thumbnail"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${mediaGetVariantUrl(
|
backgroundImage: `url(${mediaGetVariantUrl(
|
||||||
post.hero,
|
article.hero,
|
||||||
'medium'
|
'medium'
|
||||||
)})`,
|
)})`,
|
||||||
}"></div>
|
}"></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
{{ post.user.display_name }} -
|
{{ article.user.display_name }} -
|
||||||
{{ computedDate(post.publish_at) }}
|
{{ computedDate(article.publish_at) }}
|
||||||
</div>
|
</div>
|
||||||
<h3 class="title">{{ post.title }}</h3>
|
<h3 class="title">{{ article.title }}</h3>
|
||||||
<p class="content">
|
<p class="content">
|
||||||
{{ excerpt(post.content) }}
|
{{ excerpt(article.content) }}
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
import { Ref, ref, watch } from "vue";
|
import { Ref, ref, watch } from "vue";
|
||||||
import SMPagination from "../components/SMPagination.vue";
|
import SMPagination from "../components/SMPagination.vue";
|
||||||
import { api } from "../helpers/api";
|
import { api } from "../helpers/api";
|
||||||
import { Post, PostCollection } from "../helpers/api.types";
|
import { Article, ArticleCollection } from "../helpers/api.types";
|
||||||
import { SMDate } from "../helpers/datetime";
|
import { SMDate } from "../helpers/datetime";
|
||||||
import { mediaGetVariantUrl } from "../helpers/media";
|
import { mediaGetVariantUrl } from "../helpers/media";
|
||||||
import SMMastHead from "../components/SMMastHead.vue";
|
import SMMastHead from "../components/SMMastHead.vue";
|
||||||
@@ -67,16 +67,16 @@ import SMNoItems from "../components/SMNoItems.vue";
|
|||||||
|
|
||||||
const message = ref("");
|
const message = ref("");
|
||||||
const pageLoading = ref(true);
|
const pageLoading = ref(true);
|
||||||
const posts: Ref<Post[]> = ref([]);
|
const articles: Ref<Article[]> = ref([]);
|
||||||
|
|
||||||
const postsPerPage = 24;
|
const articlesPerPage = 24;
|
||||||
let postsPage = ref(1);
|
let articlesPage = ref(1);
|
||||||
let postsTotal = ref(0);
|
let articlesTotal = ref(0);
|
||||||
|
|
||||||
let searchInput = ref("");
|
let searchInput = ref("");
|
||||||
|
|
||||||
const handleClickSearch = () => {
|
const handleClickSearch = () => {
|
||||||
postsPage.value = 1;
|
articlesPage.value = 1;
|
||||||
handleLoad();
|
handleLoad();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,11 +86,11 @@ const handleClickSearch = () => {
|
|||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
message.value = "";
|
message.value = "";
|
||||||
pageLoading.value = true;
|
pageLoading.value = true;
|
||||||
posts.value = [];
|
articles.value = [];
|
||||||
|
|
||||||
let params = {
|
let params = {
|
||||||
limit: postsPerPage,
|
limit: articlesPerPage,
|
||||||
page: postsPage.value,
|
page: articlesPage.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (searchInput.value.length > 0) {
|
if (searchInput.value.length > 0) {
|
||||||
@@ -100,16 +100,16 @@ const handleLoad = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.get({
|
api.get({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
params: params,
|
params: params,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const data = result.data as PostCollection;
|
const data = result.data as ArticleCollection;
|
||||||
|
|
||||||
posts.value = data.posts;
|
articles.value = data.articles;
|
||||||
postsTotal.value = data.total;
|
articlesTotal.value = data.total;
|
||||||
posts.value.forEach((post) => {
|
articles.value.forEach((article) => {
|
||||||
post.publish_at = new SMDate(post.publish_at, {
|
article.publish_at = new SMDate(article.publish_at, {
|
||||||
format: "ymd",
|
format: "ymd",
|
||||||
utc: true,
|
utc: true,
|
||||||
}).format("yyyy/MM/dd HH:mm:ss");
|
}).format("yyyy/MM/dd HH:mm:ss");
|
||||||
@@ -132,7 +132,7 @@ const computedDate = (date) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => postsPage.value,
|
() => articlesPage.value,
|
||||||
() => {
|
() => {
|
||||||
handleLoad();
|
handleLoad();
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ handleLoad();
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.page-blog {
|
.page-blog {
|
||||||
.posts {
|
.articles {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
@@ -188,13 +188,13 @@ handleLoad();
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.page-blog .posts {
|
.page-blog .articles {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.page-blog .posts {
|
.page-blog .articles {
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage
|
<SMPage
|
||||||
class="page-post-edit"
|
class="page-article-edit"
|
||||||
:page-error="pageError"
|
:page-error="pageError"
|
||||||
permission="admin/posts">
|
permission="admin/articles">
|
||||||
<template #container>
|
<template #container>
|
||||||
<h1>{{ page_title }}</h1>
|
<h1>{{ page_title }}</h1>
|
||||||
<SMForm
|
<SMForm
|
||||||
@@ -74,7 +74,7 @@ import SMButtonRow from "../../components/SMButtonRow.vue";
|
|||||||
import SMInput from "../../components/SMInput.vue";
|
import SMInput from "../../components/SMInput.vue";
|
||||||
import SMInputAttachments from "../../components/SMInputAttachments.vue";
|
import SMInputAttachments from "../../components/SMInputAttachments.vue";
|
||||||
import { api } from "../../helpers/api";
|
import { api } from "../../helpers/api";
|
||||||
import { PostResponse, UserCollection } from "../../helpers/api.types";
|
import { ArticleResponse, UserCollection } from "../../helpers/api.types";
|
||||||
import { SMDate } from "../../helpers/datetime";
|
import { SMDate } from "../../helpers/datetime";
|
||||||
import { Form, FormControl } from "../../helpers/form";
|
import { Form, FormControl } from "../../helpers/form";
|
||||||
import { And, DateTime, Min, Required } from "../../helpers/validate";
|
import { And, DateTime, Min, Required } from "../../helpers/validate";
|
||||||
@@ -84,7 +84,7 @@ import { useUserStore } from "../../store/UserStore";
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const page_title = route.params.id ? "Edit Post" : "Create New Post";
|
const page_title = route.params.id ? "Edit Article" : "Create New Article";
|
||||||
let pageError = ref(200);
|
let pageError = ref(200);
|
||||||
const authors = ref({});
|
const authors = ref({});
|
||||||
const attachments = ref([]);
|
const attachments = ref([]);
|
||||||
@@ -122,7 +122,7 @@ const updateSlug = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await api.get({
|
await api.get({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
params: {
|
params: {
|
||||||
slug: slug,
|
slug: slug,
|
||||||
},
|
},
|
||||||
@@ -149,33 +149,33 @@ const loadData = async () => {
|
|||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
form.loading(true);
|
form.loading(true);
|
||||||
let result = await api.get({
|
let result = await api.get({
|
||||||
url: "/posts/{id}",
|
url: "/articles/{id}",
|
||||||
params: {
|
params: {
|
||||||
id: route.params.id,
|
id: route.params.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = result.data as PostResponse;
|
const data = result.data as ArticleResponse;
|
||||||
|
|
||||||
if (data && data.post) {
|
if (data && data.article) {
|
||||||
form.controls.title.value = data.post.title;
|
form.controls.title.value = data.article.title;
|
||||||
form.controls.slug.value = data.post.slug;
|
form.controls.slug.value = data.article.slug;
|
||||||
form.controls.user_id.value = data.post.user.id;
|
form.controls.user_id.value = data.article.user.id;
|
||||||
form.controls.content.value = data.post.content;
|
form.controls.content.value = data.article.content;
|
||||||
form.controls.publish_at.value = data.post.publish_at
|
form.controls.publish_at.value = data.article.publish_at
|
||||||
? new SMDate(data.post.publish_at, {
|
? new SMDate(data.article.publish_at, {
|
||||||
format: "yMd",
|
format: "yMd",
|
||||||
utc: true,
|
utc: true,
|
||||||
}).format("dd/MM/yyyy HH:mm")
|
}).format("dd/MM/yyyy HH:mm")
|
||||||
: "";
|
: "";
|
||||||
form.controls.content.value = data.post.content;
|
form.controls.content.value = data.article.content;
|
||||||
form.controls.hero.value = data.post.hero.id;
|
form.controls.hero.value = data.article.hero.id;
|
||||||
|
|
||||||
attachments.value = (data.post.attachments || []).map(function (
|
attachments.value = (data.article.attachments || []).map(
|
||||||
attachment
|
function (attachment) {
|
||||||
) {
|
return attachment.id.toString();
|
||||||
return attachment.id.toString();
|
}
|
||||||
});
|
);
|
||||||
} else {
|
} else {
|
||||||
pageError.value = 404;
|
pageError.value = 404;
|
||||||
}
|
}
|
||||||
@@ -201,12 +201,12 @@ const handleSubmit = async () => {
|
|||||||
hero: form.controls.hero.value,
|
hero: form.controls.hero.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_id = "";
|
let article_id = "";
|
||||||
|
|
||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
post_id = route.params.id as string;
|
article_id = route.params.id as string;
|
||||||
await api.put({
|
await api.put({
|
||||||
url: `/posts/{id}`,
|
url: `/articles/{id}`,
|
||||||
params: {
|
params: {
|
||||||
id: route.params.id,
|
id: route.params.id,
|
||||||
},
|
},
|
||||||
@@ -214,32 +214,32 @@ const handleSubmit = async () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let result = await api.post({
|
let result = await api.post({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
body: data,
|
body: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
const data = result.data as PostResponse;
|
const data = result.data as ArticleResponse;
|
||||||
post_id = data.post.id;
|
article_id = data.article.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.put({
|
await api.put({
|
||||||
url: `/posts/${post_id}/attachments`,
|
url: `/articles/${article_id}/attachments`,
|
||||||
body: {
|
body: {
|
||||||
attachments: attachments.value,
|
attachments: attachments.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: route.params.id ? "Post Updated" : "Post Created",
|
title: route.params.id ? "Article Updated" : "Article Created",
|
||||||
content: route.params.id
|
content: route.params.id
|
||||||
? "The post has been updated."
|
? "The article has been updated."
|
||||||
: "The post has been created.",
|
: "The article has been created.",
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push({ name: "dashboard-post-list" });
|
router.push({ name: "dashboard-article-list" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
form.apiErrors(error);
|
form.apiErrors(error);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<SMPage permission="admin/posts" :page-error="pageError">
|
<SMPage permission="admin/articles" :page-error="pageError">
|
||||||
<template #container>
|
<template #container>
|
||||||
<SMToolbar>
|
<SMToolbar>
|
||||||
<template #left>
|
<template #left>
|
||||||
<SMButton
|
<SMButton
|
||||||
type="primary"
|
type="primary"
|
||||||
label="Create Post"
|
label="Create Article"
|
||||||
:small="true"
|
:small="true"
|
||||||
@click="handleCreate" />
|
@click="handleCreate" />
|
||||||
</template>
|
</template>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<template #item-title="item">
|
<template #item-title="item">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'dashboard-post-edit',
|
name: 'dashboard-article-edit',
|
||||||
params: { id: item.id },
|
params: { id: item.id },
|
||||||
}"
|
}"
|
||||||
>{{ item.title }}</router-link
|
>{{ item.title }}</router-link
|
||||||
@@ -64,7 +64,7 @@ import SMInput from "../../components/SMInput.vue";
|
|||||||
import SMLoadingIcon from "../../components/SMLoadingIcon.vue";
|
import SMLoadingIcon from "../../components/SMLoadingIcon.vue";
|
||||||
import SMToolbar from "../../components/SMToolbar.vue";
|
import SMToolbar from "../../components/SMToolbar.vue";
|
||||||
import { api } from "../../helpers/api";
|
import { api } from "../../helpers/api";
|
||||||
import { PostCollection, PostResponse } from "../../helpers/api.types";
|
import { ArticleCollection, ArticleResponse } from "../../helpers/api.types";
|
||||||
import { SMDate } from "../../helpers/datetime";
|
import { SMDate } from "../../helpers/datetime";
|
||||||
import { debounce } from "../../helpers/debounce";
|
import { debounce } from "../../helpers/debounce";
|
||||||
import { useToastStore } from "../../store/ToastStore";
|
import { useToastStore } from "../../store/ToastStore";
|
||||||
@@ -103,7 +103,7 @@ const handleClick = (item, extra: string): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the post data from the server.
|
* Load the article data from the server.
|
||||||
*/
|
*/
|
||||||
const loadFromServer = async () => {
|
const loadFromServer = async () => {
|
||||||
formLoading.value = true;
|
formLoading.value = true;
|
||||||
@@ -128,17 +128,17 @@ const loadFromServer = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.get({
|
const result = await api.get({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
params: params,
|
params: params,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = result.data as PostCollection;
|
const data = result.data as ArticleCollection;
|
||||||
|
|
||||||
if (!data || !data.posts) {
|
if (!data || !data.articles) {
|
||||||
throw new Error("The server is currently not available");
|
throw new Error("The server is currently not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
items.value = data.posts;
|
items.value = data.articles;
|
||||||
|
|
||||||
items.value.forEach((row) => {
|
items.value.forEach((row) => {
|
||||||
if (row.created_at !== "undefined") {
|
if (row.created_at !== "undefined") {
|
||||||
@@ -185,15 +185,15 @@ watch(search, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClickRow = (item) => {
|
const handleClickRow = (item) => {
|
||||||
router.push({ name: "dashboard-post-edit", params: { id: item.id } });
|
router.push({ name: "dashboard-article-edit", params: { id: item.id } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push({ name: "dashboard-post-create" });
|
router.push({ name: "dashboard-article-create" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (item) => {
|
const handleEdit = (item) => {
|
||||||
router.push({ name: "dashboard-post-edit", params: { id: item.id } });
|
router.push({ name: "dashboard-article-edit", params: { id: item.id } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicate = async (item) => {
|
const handleDuplicate = async (item) => {
|
||||||
@@ -223,7 +223,7 @@ const handleDuplicate = async (item) => {
|
|||||||
const slug = `${originalSlug}-${number}`;
|
const slug = `${originalSlug}-${number}`;
|
||||||
try {
|
try {
|
||||||
await api.get({
|
await api.get({
|
||||||
url: `/posts/?slug=${slug}`,
|
url: `/articles/?slug=${slug}`,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 404) {
|
if (err.status === 404) {
|
||||||
@@ -233,7 +233,7 @@ const handleDuplicate = async (item) => {
|
|||||||
} else {
|
} else {
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: "Server error",
|
title: "Server error",
|
||||||
content: "The post could not be duplicated.",
|
content: "The article could not be duplicated.",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -245,28 +245,28 @@ const handleDuplicate = async (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.post({
|
const result = await api.post({
|
||||||
url: "/posts",
|
url: "/articles",
|
||||||
body: item,
|
body: item,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = result.data as PostResponse;
|
const data = result.data as ArticleResponse;
|
||||||
|
|
||||||
loadFromServer();
|
loadFromServer();
|
||||||
|
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: "Post duplicated",
|
title: "Article duplicated",
|
||||||
content: "The post was duplicated successfully.",
|
content: "The article was duplicated successfully.",
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: "dashboard-post-edit",
|
name: "dashboard-article-edit",
|
||||||
params: { id: data.post.id },
|
params: { id: data.article.id },
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
useToastStore().addToast({
|
useToastStore().addToast({
|
||||||
title: "Server error",
|
title: "Server error",
|
||||||
content: "The post could not be duplicated.",
|
content: "The article could not be duplicated.",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -274,24 +274,24 @@ const handleDuplicate = async (item) => {
|
|||||||
|
|
||||||
const handleDelete = async (item) => {
|
const handleDelete = async (item) => {
|
||||||
let result = await openDialog(SMDialogConfirm, {
|
let result = await openDialog(SMDialogConfirm, {
|
||||||
title: "Delete Post?",
|
title: "Delete Article?",
|
||||||
text: `Are you sure you want to delete the post <strong>${item.title}</strong>?`,
|
text: `Are you sure you want to delete the article <strong>${item.title}</strong>?`,
|
||||||
cancel: {
|
cancel: {
|
||||||
type: "secondary",
|
type: "secondary",
|
||||||
label: "Cancel",
|
label: "Cancel",
|
||||||
},
|
},
|
||||||
confirm: {
|
confirm: {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
label: "Delete Post",
|
label: "Delete Article",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
try {
|
try {
|
||||||
await api.delete(`posts${item.id}`);
|
await api.delete(`articles${item.id}`);
|
||||||
loadFromServer();
|
loadFromServer();
|
||||||
|
|
||||||
formMessage.value.message = "Post deleted successfully";
|
formMessage.value.message = "Article deleted successfully";
|
||||||
formMessage.value.type = "success";
|
formMessage.value.type = "success";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
formMessage.value.message = err.response?.data?.message;
|
formMessage.value.message = err.response?.data?.message;
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SMPage>
|
|
||||||
<SMForm v-model="form" @submit="handleSubmit">
|
|
||||||
<SMRow>
|
|
||||||
<SMInput control="title" />
|
|
||||||
</SMRow>
|
|
||||||
<SMRow>
|
|
||||||
<SMEditor
|
|
||||||
id="content"
|
|
||||||
v-model="form.content.value"
|
|
||||||
@file-accept="fileAccept"
|
|
||||||
@attachment-add="attachmentAdd" />
|
|
||||||
</SMRow>
|
|
||||||
<SMRow>
|
|
||||||
<SMButton type="submit" label="Save" />
|
|
||||||
</SMRow>
|
|
||||||
</SMForm>
|
|
||||||
</SMPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive } from "vue";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import SMButton from "../../components/SMButton.vue";
|
|
||||||
import SMForm from "../../components/SMForm.vue";
|
|
||||||
import SMInput from "../../components/SMInput.vue";
|
|
||||||
|
|
||||||
import { api } from "../../helpers/api";
|
|
||||||
import { Form, FormControl } from "../../helpers/form";
|
|
||||||
import { And, Min, Required } from "../../helpers/validate";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
let form = reactive(
|
|
||||||
Form({
|
|
||||||
title: FormControl("", And([Required(), Min(2)])),
|
|
||||||
content: FormControl("", Required()),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// const getPostById = async () => {
|
|
||||||
// try {
|
|
||||||
// if (isValidated(formData)) {
|
|
||||||
// let res = await axios.get("posts/" + route.params.id);
|
|
||||||
|
|
||||||
// formData.title.value = res.data.title;
|
|
||||||
// formData.content.value = res.data.content;
|
|
||||||
// }
|
|
||||||
// } catch (err) {
|
|
||||||
// formMessage.icon = "";
|
|
||||||
// formMessage.type = "error";
|
|
||||||
// formMessage.message = "";
|
|
||||||
// restParseErrors(formData, [formMessage, "message"], err);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
await api.post({
|
|
||||||
url: "/posts",
|
|
||||||
body: {
|
|
||||||
title: form.title.value,
|
|
||||||
content: form.content.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
form.message("The post has been saved", "success");
|
|
||||||
} catch (error) {
|
|
||||||
form.apiError(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileAccept = (event) => {
|
|
||||||
if (event.file.type != "image/png") {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const createStorageKey = (file) => {
|
|
||||||
var date = new Date();
|
|
||||||
var day = date.toISOString().slice(0, 10);
|
|
||||||
var name = date.getTime() + "-" + file.name;
|
|
||||||
return ["tmp", day, name].join("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const attachmentAdd = async (event) => {
|
|
||||||
if (event.attachment.file) {
|
|
||||||
const key = createStorageKey(event.attachment.file);
|
|
||||||
|
|
||||||
var fileFormData = new FormData();
|
|
||||||
fileFormData.append("key", key);
|
|
||||||
fileFormData.append("Content-Type", event.attachment.file.type);
|
|
||||||
fileFormData.append("file", event.attachment.file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res = await axios.post("upload", fileFormData, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
onUploadProgress: (progressEvent) =>
|
|
||||||
event.attachment.setUploadProgress(
|
|
||||||
(progressEvent.loaded * progressEvent.total) / 100
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
event.attachment.setAttributes({
|
|
||||||
url: res.data.url,
|
|
||||||
href: res.data.url,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
<h3>My Details</h3>
|
<h3>My Details</h3>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="userStore.permissions.includes('admin/posts')"
|
v-if="userStore.permissions.includes('admin/articles')"
|
||||||
:to="{ name: 'dashboard-post-list' }"
|
:to="{ name: 'dashboard-article-list' }"
|
||||||
class="admin-card posts">
|
class="admin-card articles">
|
||||||
<ion-icon name="newspaper-outline" />
|
<ion-icon name="newspaper-outline" />
|
||||||
<h3>Articles</h3>
|
<h3>Articles</h3>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<SMColumn
|
<SMColumn
|
||||||
><SMInput
|
><SMInput
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
label="Edit Posts"
|
label="Edit Articles"
|
||||||
v-model="permissions.users"
|
v-model="permissions.users"
|
||||||
/></SMColumn>
|
/></SMColumn>
|
||||||
<SMColumn
|
<SMColumn
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use App\Http\Controllers\Api\EventController;
|
|||||||
use App\Http\Controllers\Api\LogController;
|
use App\Http\Controllers\Api\LogController;
|
||||||
use App\Http\Controllers\Api\MediaController;
|
use App\Http\Controllers\Api\MediaController;
|
||||||
use App\Http\Controllers\Api\OCRController;
|
use App\Http\Controllers\Api\OCRController;
|
||||||
use App\Http\Controllers\Api\PostController;
|
use App\Http\Controllers\Api\ArticleController;
|
||||||
use App\Http\Controllers\Api\SubscriptionController;
|
use App\Http\Controllers\Api\SubscriptionController;
|
||||||
use App\Http\Controllers\Api\UserController;
|
use App\Http\Controllers\Api\UserController;
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ Route::post('/users/verifyEmail', [UserController::class, 'verifyEmail']);
|
|||||||
Route::apiResource('media', MediaController::class);
|
Route::apiResource('media', MediaController::class);
|
||||||
Route::get('media/{medium}/download', [MediaController::class, 'download']);
|
Route::get('media/{medium}/download', [MediaController::class, 'download']);
|
||||||
|
|
||||||
Route::apiResource('posts', PostController::class);
|
Route::apiResource('articles', ArticleController::class);
|
||||||
Route::apiAttachmentResource('posts', PostController::class);
|
Route::apiAttachmentResource('articles', ArticleController::class);
|
||||||
|
|
||||||
Route::apiResource('events', EventController::class);
|
Route::apiResource('events', EventController::class);
|
||||||
Route::apiAttachmentResource('events', EventController::class);
|
Route::apiAttachmentResource('events', EventController::class);
|
||||||
|
|||||||
136
tests/Feature/ArticlesApiTest.php
Normal file
136
tests/Feature/ArticlesApiTest.php
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\Article;
|
||||||
|
use Faker\Factory as FakerFactory;
|
||||||
|
|
||||||
|
class ArticlesApiTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected $faker;
|
||||||
|
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Tests\TestCase;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Media;
|
|
||||||
use App\Models\Post;
|
|
||||||
use Faker\Factory as FakerFactory;
|
|
||||||
|
|
||||||
class PostsApiTest extends TestCase
|
|
||||||
{
|
|
||||||
use RefreshDatabase;
|
|
||||||
|
|
||||||
protected $faker;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user