Skip to content

Commit bc52c54

Browse files
authored
Merge pull request #55 from mubbi/feature/missing-apis
Feature/missing apis
2 parents 509a543 + f72ff0d commit bc52c54

File tree

123 files changed

+12053
-47
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+12053
-47
lines changed

.cursor/rules/laravel-blog-api-rules.mdc

Lines changed: 899 additions & 0 deletions
Large diffs are not rendered by default.

app/Enums/ArticleStatus.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,40 @@
77
enum ArticleStatus: string
88
{
99
case DRAFT = 'draft';
10+
case REVIEW = 'review';
1011
case SCHEDULED = 'scheduled';
1112
case PUBLISHED = 'published';
1213
case ARCHIVED = 'archived';
14+
case TRASHED = 'trashed';
15+
16+
/**
17+
* Get the display name for the status
18+
*/
19+
public function displayName(): string
20+
{
21+
return match ($this) {
22+
self::DRAFT => 'Draft',
23+
self::REVIEW => 'Under Review',
24+
self::SCHEDULED => 'Scheduled',
25+
self::PUBLISHED => 'Published',
26+
self::ARCHIVED => 'Archived',
27+
self::TRASHED => 'Trashed',
28+
};
29+
}
30+
31+
/**
32+
* Check if the status is a published state
33+
*/
34+
public function isPublished(): bool
35+
{
36+
return in_array($this, [self::PUBLISHED, self::SCHEDULED]);
37+
}
38+
39+
/**
40+
* Check if the status is a draft state
41+
*/
42+
public function isDraft(): bool
43+
{
44+
return in_array($this, [self::DRAFT, self::REVIEW]);
45+
}
1346
}

app/Enums/CommentStatus.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Enums;
6+
7+
enum CommentStatus: string
8+
{
9+
case PENDING = 'pending';
10+
case APPROVED = 'approved';
11+
case REJECTED = 'rejected';
12+
case SPAM = 'spam';
13+
14+
/**
15+
* Get the display name for the status
16+
*/
17+
public function displayName(): string
18+
{
19+
return match ($this) {
20+
self::PENDING => 'Pending',
21+
self::APPROVED => 'Approved',
22+
self::REJECTED => 'Rejected',
23+
self::SPAM => 'Spam',
24+
};
25+
}
26+
27+
/**
28+
* Check if the status is a published state
29+
*/
30+
public function isPublished(): bool
31+
{
32+
return $this === self::APPROVED;
33+
}
34+
35+
/**
36+
* Check if the status is a draft state
37+
*/
38+
public function isDraft(): bool
39+
{
40+
return in_array($this, [self::PENDING, self::REJECTED]);
41+
}
42+
}

app/Enums/UserRole.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,23 @@
66

77
enum UserRole: string
88
{
9-
case ADMINISTRATOR = 'Administrator';
10-
case EDITOR = 'Editor';
11-
case AUTHOR = 'Author';
12-
case CONTRIBUTOR = 'Contributor';
13-
case SUBSCRIBER = 'Subscriber';
9+
case ADMINISTRATOR = 'administrator';
10+
case EDITOR = 'editor';
11+
case AUTHOR = 'author';
12+
case CONTRIBUTOR = 'contributor';
13+
case SUBSCRIBER = 'subscriber';
14+
15+
/**
16+
* Get the display name for the role
17+
*/
18+
public function displayName(): string
19+
{
20+
return match ($this) {
21+
self::ADMINISTRATOR => 'Administrator',
22+
self::EDITOR => 'Editor',
23+
self::AUTHOR => 'Author',
24+
self::CONTRIBUTOR => 'Contributor',
25+
self::SUBSCRIBER => 'Subscriber',
26+
};
27+
}
1428
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Api\V1\Admin\Article;
6+
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\V1\Admin\Article\ApproveArticleRequest;
9+
use App\Http\Resources\V1\Admin\Article\ArticleManagementResource;
10+
use App\Services\ArticleManagementService;
11+
use Dedoc\Scramble\Attributes\Group;
12+
use Illuminate\Http\JsonResponse;
13+
use Symfony\Component\HttpFoundation\Response;
14+
15+
#[Group('Admin - Article Management', weight: 2)]
16+
final class ApproveArticleController extends Controller
17+
{
18+
public function __construct(
19+
private readonly ArticleManagementService $articleManagementService
20+
) {}
21+
22+
/**
23+
* Approve Article
24+
*
25+
* Approve an article and publish it
26+
*
27+
* @response array{status: true, message: string, data: ArticleManagementResource}
28+
*/
29+
public function __invoke(int $id, ApproveArticleRequest $request): JsonResponse
30+
{
31+
try {
32+
$user = $request->user();
33+
assert($user !== null);
34+
35+
$article = $this->articleManagementService->approveArticle($id, $user->id);
36+
37+
return response()->apiSuccess(
38+
new ArticleManagementResource($article),
39+
__('common.article_approved_successfully')
40+
);
41+
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
42+
return response()->apiError(
43+
__('common.article_not_found'),
44+
Response::HTTP_NOT_FOUND
45+
);
46+
} catch (\Throwable $e) {
47+
return response()->apiError(
48+
__('common.something_went_wrong'),
49+
Response::HTTP_INTERNAL_SERVER_ERROR
50+
);
51+
}
52+
}
53+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Api\V1\Admin\Article;
6+
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\V1\Admin\Article\FeatureArticleRequest;
9+
use App\Http\Resources\V1\Admin\Article\ArticleManagementResource;
10+
use App\Services\ArticleManagementService;
11+
use Dedoc\Scramble\Attributes\Group;
12+
use Illuminate\Http\JsonResponse;
13+
use Symfony\Component\HttpFoundation\Response;
14+
15+
#[Group('Admin - Article Management', weight: 2)]
16+
final class FeatureArticleController extends Controller
17+
{
18+
public function __construct(
19+
private readonly ArticleManagementService $articleManagementService
20+
) {}
21+
22+
/**
23+
* Feature Article
24+
*
25+
* Mark an article as featured
26+
*
27+
* @response array{status: true, message: string, data: ArticleManagementResource}
28+
*/
29+
public function __invoke(int $id, FeatureArticleRequest $request): JsonResponse
30+
{
31+
try {
32+
$article = $this->articleManagementService->featureArticle($id);
33+
34+
return response()->apiSuccess(
35+
new ArticleManagementResource($article),
36+
__('common.article_featured_successfully')
37+
);
38+
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
39+
return response()->apiError(
40+
__('common.article_not_found'),
41+
Response::HTTP_NOT_FOUND
42+
);
43+
} catch (\Throwable $e) {
44+
return response()->apiError(
45+
__('common.something_went_wrong'),
46+
Response::HTTP_INTERNAL_SERVER_ERROR
47+
);
48+
}
49+
}
50+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Api\V1\Admin\Article;
6+
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\V1\Admin\Article\GetArticlesRequest;
9+
use App\Http\Resources\MetaResource;
10+
use App\Http\Resources\V1\Admin\Article\ArticleManagementResource;
11+
use App\Services\ArticleManagementService;
12+
use Dedoc\Scramble\Attributes\Group;
13+
use Illuminate\Http\JsonResponse;
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
#[Group('Admin - Article Management', weight: 2)]
17+
final class GetArticlesController extends Controller
18+
{
19+
public function __construct(
20+
private readonly ArticleManagementService $articleManagementService
21+
) {}
22+
23+
/**
24+
* Get Articles List
25+
*
26+
* Retrieve a paginated list of articles with admin filters and management capabilities
27+
*
28+
* @response array{status: true, message: string, data: array{articles: ArticleManagementResource[], meta: MetaResource}}
29+
*/
30+
public function __invoke(GetArticlesRequest $request): JsonResponse
31+
{
32+
try {
33+
$params = $request->withDefaults();
34+
$articles = $this->articleManagementService->getArticles($params);
35+
$articleCollection = ArticleManagementResource::collection($articles);
36+
$articleCollectionData = $articleCollection->response()->getData(true);
37+
38+
if (! is_array($articleCollectionData) || ! isset($articleCollectionData['data'], $articleCollectionData['meta'])) {
39+
throw new \RuntimeException(__('common.unexpected_response_format'));
40+
}
41+
42+
return response()->apiSuccess(
43+
[
44+
'articles' => $articleCollectionData['data'],
45+
'meta' => MetaResource::make($articleCollectionData['meta']),
46+
],
47+
__('common.success')
48+
);
49+
} catch (\Throwable $e) {
50+
return response()->apiError(
51+
__('common.something_went_wrong'),
52+
Response::HTTP_INTERNAL_SERVER_ERROR
53+
);
54+
}
55+
}
56+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Api\V1\Admin\Article;
6+
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\V1\Admin\Article\ReportArticleRequest;
9+
use App\Http\Resources\V1\Admin\Article\ArticleManagementResource;
10+
use App\Services\ArticleManagementService;
11+
use Dedoc\Scramble\Attributes\Group;
12+
use Illuminate\Http\JsonResponse;
13+
use Symfony\Component\HttpFoundation\Response;
14+
15+
#[Group('Admin - Article Management', weight: 2)]
16+
final class ReportArticleController extends Controller
17+
{
18+
public function __construct(
19+
private readonly ArticleManagementService $articleManagementService
20+
) {}
21+
22+
/**
23+
* Report Article
24+
*
25+
* Report an article with a reason
26+
*
27+
* @response array{status: true, message: string, data: ArticleManagementResource}
28+
*/
29+
public function __invoke(int $id, ReportArticleRequest $request): JsonResponse
30+
{
31+
try {
32+
$validated = $request->validated();
33+
/** @var string $reason */
34+
$reason = $validated['reason'] ?? 'No reason provided';
35+
$article = $this->articleManagementService->reportArticle($id, $reason);
36+
37+
return response()->apiSuccess(
38+
new ArticleManagementResource($article),
39+
__('common.article_reported_successfully')
40+
);
41+
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
42+
return response()->apiError(
43+
__('common.article_not_found'),
44+
Response::HTTP_NOT_FOUND
45+
);
46+
} catch (\Throwable $e) {
47+
return response()->apiError(
48+
__('common.something_went_wrong'),
49+
Response::HTTP_INTERNAL_SERVER_ERROR
50+
);
51+
}
52+
}
53+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Api\V1\Admin\Article;
6+
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Requests\V1\Admin\Article\ShowArticleRequest;
9+
use App\Http\Resources\V1\Admin\Article\ArticleManagementResource;
10+
use App\Models\Article;
11+
use Dedoc\Scramble\Attributes\Group;
12+
use Illuminate\Http\JsonResponse;
13+
use Symfony\Component\HttpFoundation\Response;
14+
15+
#[Group('Admin - Article Management', weight: 2)]
16+
final class ShowArticleController extends Controller
17+
{
18+
/**
19+
* Show Article
20+
*
21+
* Retrieve a single article with full admin management details
22+
*
23+
* @response array{status: true, message: string, data: ArticleManagementResource}
24+
*/
25+
public function __invoke(int $id, ShowArticleRequest $request): JsonResponse
26+
{
27+
try {
28+
$article = Article::query()
29+
->with(['author:id,name,email', 'categories:id,name,slug', 'tags:id,name,slug', 'comments.user:id,name,email'])
30+
->withCount(['comments', 'authors'])
31+
->findOrFail($id);
32+
33+
return response()->apiSuccess(
34+
new ArticleManagementResource($article),
35+
__('common.success')
36+
);
37+
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
38+
return response()->apiError(
39+
__('common.article_not_found'),
40+
Response::HTTP_NOT_FOUND
41+
);
42+
} catch (\Throwable $e) {
43+
return response()->apiError(
44+
__('common.something_went_wrong'),
45+
Response::HTTP_INTERNAL_SERVER_ERROR
46+
);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)