Skip to content

Commit 715043d

Browse files
committed
feat: 投稿の権限確認機能を追加し、APIルートを整理
1 parent e567dde commit 715043d

File tree

5 files changed

+170
-35
lines changed

5 files changed

+170
-35
lines changed

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,46 @@ mysql> desc users;
8484
+-------------------+---------------------+------+-----+---------+----------------+
8585
9 rows in set (0.01 sec)
8686

87+
mysql> desc roles;
88+
+------------+---------------------+------+-----+---------+----------------+
89+
| Field | Type | Null | Key | Default | Extra |
90+
+------------+---------------------+------+-----+---------+----------------+
91+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
92+
| name | varchar(255) | NO | UNI | NULL | |
93+
| label | varchar(255) | YES | | NULL | |
94+
| created_at | timestamp | YES | | NULL | |
95+
| updated_at | timestamp | YES | | NULL | |
96+
+------------+---------------------+------+-----+---------+----------------+
97+
5 rows in set (0.00 sec)
98+
99+
```
100+
101+
### API ルート
102+
103+
```php
104+
wida@LAPTOP-2C4PL9J8:~/dev/laravel-rds$ ./vendor/bin/sail artisan route:list
105+
WARN[0000] The "MYSQL_EXTRA_OPTIONS" variable is not set. Defaulting to a blank string.
106+
WARN[0000] The "MYSQL_EXTRA_OPTIONS" variable is not set. Defaulting to a blank string.
107+
108+
GET|HEAD / .................................................................................................................
109+
GET|HEAD api/auth/google ......................................................... Api\GoogleAuthController@redirectToGoogle
110+
GET|HEAD api/auth/google/callback ............................................ Api\GoogleAuthController@handleGoogleCallback
111+
POST api/login ........................................................................ login › Api\AuthController@login
112+
POST api/logout .............................................................................. Api\AuthController@logout
113+
GET|HEAD api/posts .................................................................. posts.index › Api\PostController@index
114+
POST api/posts .................................................................. posts.store › Api\PostController@store
115+
GET|HEAD api/posts/{post} ............................................................. posts.show › Api\PostController@show
116+
PUT|PATCH api/posts/{post} ......................................................... posts.update › Api\PostController@update
117+
DELETE api/posts/{post} ....................................................... posts.destroy › Api\PostController@destroy
118+
GET|HEAD api/profile ............................................................................... Api\UserController@show
119+
PUT api/profile ............................................................................. Api\UserController@update
120+
GET|HEAD api/test ..........................................................................................................
121+
GET|HEAD api/user ..........................................................................................................
122+
GET|HEAD sanctum/csrf-cookie ............................. sanctum.csrf-cookie › Laravel\Sanctum › CsrfCookieController@show
123+
GET|HEAD storage/{path} ...................................................................................... storage.local
124+
GET|HEAD up ................................................................................................................
125+
126+
Showing [17] routes
87127
```
88128

89129
## 手順

app/Http/Controllers/Api/PostController.php

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,40 @@
66
use App\Models\Post;
77
use Illuminate\Http\Request;
88

9+
910
class PostController extends Controller
1011
{
12+
// 投稿の権限を確認するメソッド
13+
private function authorizePost(Request $request)
14+
{
15+
$roleNames = $request->user()->roles->pluck('name');
16+
17+
if (!$roleNames->contains('admin') && !$roleNames->contains('paid')) {
18+
return response()->json(['message' => 'この操作を行う権限がありません'], 403);
19+
}
20+
21+
return null;
22+
}
23+
1124
// 一覧取得
1225
public function index(Request $request)
1326
{
14-
// $request->user()で認証済みユーザーを取得できる理由:
15-
// 1. このルートは auth:sanctum ミドルウェアで保護されている(routes/api.php参照)
16-
// 2. リクエストヘッダーのBearerトークンが検証され、認証済みユーザーがリクエストにバインドされる
17-
// 3. 未認証の場合は401エラーが返されるため、ここでは常に認証済みユーザーが存在する
18-
$currentUserId = $request->user()->id;
27+
// ログインしている場合はユーザーIDを取得、未ログインの場合はnull
28+
// $request->user()は認証済みユーザーを返すが、未認証の場合はnullを返す
29+
$currentUserId = $request->user()->id ?? null;
1930

20-
$posts = Post::with('user')
21-
->where(function ($query) use ($currentUserId) {
22-
// 条件: 「公開」または「自分の投稿」
23-
$query->where('status', 'published')
24-
->orWhere('user_id', $currentUserId);
31+
$posts = Post::with('user') // ユーザー情報も一緒に取得(N+1問題の回避)
32+
->when($currentUserId, function ($query) use ($currentUserId) {
33+
// ログイン時: 公開記事 + 自分の投稿(下書き含む)を表示
34+
$query->where(function ($q) use ($currentUserId) {
35+
$q->where('status', 'published') // 公開記事
36+
->orWhere('user_id', $currentUserId); // または自分の投稿(下書きも含む)
37+
});
38+
}, function ($query) {
39+
// 未ログイン時: 公開記事のみを表示
40+
$query->where('status', 'published');
2541
})
26-
->latest() // 新しい順に並べる(任意
42+
->latest() // 新しい順に並べる(created_at DESC
2743
->get();
2844

2945
return $posts;
@@ -32,6 +48,9 @@ public function index(Request $request)
3248
// 作成
3349
public function store(Request $request)
3450
{
51+
if ($res = $this->authorizePost($request)) {
52+
return $res;
53+
}
3554
// 認証済みユーザーに紐づけて投稿を作成
3655
// $request->user(): 認証済みのユーザーを取得
3756
// ->posts(): Userモデルのposts()リレーションを使用
@@ -41,11 +60,12 @@ public function store(Request $request)
4160
return response()->json($post->load('user'), 201);
4261
}
4362

44-
// 詳細取得(ここも重要!)
63+
// 詳細取得
4564
public function show(Request $request, Post $post)
4665
{
66+
$currentUserId = $request->user()->id ?? null;
4767
// 「下書き」かつ「自分の投稿でない」場合は閲覧禁止 (403 Forbidden)
48-
if ($post->status !== 'published' && $post->user_id !== $request->user()->id) {
68+
if ($post->status !== 'published' && $post->user_id !== $currentUserId) {
4969
abort(403, 'この投稿を閲覧する権限がありません。');
5070
}
5171

@@ -55,6 +75,9 @@ public function show(Request $request, Post $post)
5575
// 更新
5676
public function update(Request $request, Post $post)
5777
{
78+
if ($res = $this->authorizePost($request)) {
79+
return $res;
80+
}
5881
// 他人の投稿は更新禁止
5982
if ($post->user_id !== $request->user()->id) {
6083
abort(403, '更新権限がありません。');
@@ -67,6 +90,9 @@ public function update(Request $request, Post $post)
6790
// 削除
6891
public function destroy(Request $request, Post $post)
6992
{
93+
if ($res = $this->authorizePost($request)) {
94+
return $res;
95+
}
7096
// 他人の投稿は削除禁止
7197
if ($post->user_id !== $request->user()->id) {
7298
abort(403, '削除権限がありません。');

database/seeders/DatabaseSeeder.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,28 @@ public function run(): void
2323

2424
// freeユーザー
2525
$freeUser = User::factory()->create([
26-
'name' => 'Free User',
27-
'email' => 'free@example.com',
26+
'name' => 'たなか',
27+
'email' => 'tanaka@example.com',
2828
'password' => Hash::make('password'),
2929
]);
3030
$freeUser->roles()->attach($freeRoleId);
3131

3232
// paidユーザー
3333
$paidUser = User::factory()->create([
34-
'name' => 'Paid User',
35-
'email' => 'paid@example.com',
34+
'name' => 'わたなべ',
35+
'email' => 'watanabe@example.com',
3636
'password' => Hash::make('password'),
3737
]);
3838
$paidUser->roles()->attach($paidRoleId);
3939

40+
// paidユーザー2
41+
$paidUser2 = User::factory()->create([
42+
'name' => 'まつもと',
43+
'email' => 'matsumoto@example.com',
44+
'password' => Hash::make('password'),
45+
]);
46+
$paidUser2->roles()->attach($paidRoleId);
47+
4048
// adminユーザー
4149
$adminUser = User::factory()->create([
4250
'name' => 'Admin User',

database/seeders/PostSeeder.php

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,91 @@
99

1010
class PostSeeder extends Seeder
1111
{
12+
/**
13+
* 1~5行のランダムなcontentを生成
14+
*/
15+
private function generateContent(int $lineCount, string $userName, int $postNumber, string $type = '投稿'): string
16+
{
17+
$lines = [];
18+
for ($i = 1; $i <= $lineCount; $i++) {
19+
$lines[] = "これは{$userName}{$type} {$postNumber}{$i}行目です。";
20+
}
21+
return implode("\n", $lines);
22+
}
23+
1224
public function run(): void
1325
{
14-
$paidUser = User::where('email', 'paid@example.com')->first();
26+
$paidUser = User::where('email', 'watanabe@example.com')->first();
27+
$paidUser2 = User::where('email', 'matsumoto@example.com')->first();
1528
$adminUser = User::where('email', 'admin@example.com')->first();
1629

17-
// paidユーザーに 5 投稿
30+
// paidユーザー(わたなべ)に 5 投稿(公開)
1831
if ($paidUser) {
1932
for ($i = 1; $i <= 5; $i++) {
33+
$lineCount = rand(1, 5);
34+
Post::create([
35+
'user_id' => $paidUser->id,
36+
'title' => "わたなべの投稿 {$i}",
37+
'content' => $this->generateContent($lineCount, 'わたなべ', $i, '投稿'),
38+
'status' => 'published',
39+
]);
40+
}
41+
// paidユーザー(わたなべ)に 3 下書き投稿
42+
for ($i = 1; $i <= 3; $i++) {
43+
$lineCount = rand(1, 5);
2044
Post::create([
2145
'user_id' => $paidUser->id,
22-
'title' => "Paidユーザーの投稿 {$i}",
23-
'content' => "これはPaidユーザーの投稿 {$i} です。",
46+
'title' => "わたなべの下書き {$i}",
47+
'content' => $this->generateContent($lineCount, 'わたなべ', $i, '下書き'),
48+
'status' => 'draft',
49+
]);
50+
}
51+
}
52+
53+
// paidユーザー2(まつもと)に 5 投稿(公開)
54+
if ($paidUser2) {
55+
for ($i = 1; $i <= 5; $i++) {
56+
$lineCount = rand(1, 5);
57+
Post::create([
58+
'user_id' => $paidUser2->id,
59+
'title' => "まつもとの投稿 {$i}",
60+
'content' => $this->generateContent($lineCount, 'まつもと', $i, '投稿'),
2461
'status' => 'published',
2562
]);
2663
}
64+
// paidユーザー2(まつもと)に 3 下書き投稿
65+
for ($i = 1; $i <= 3; $i++) {
66+
$lineCount = rand(1, 5);
67+
Post::create([
68+
'user_id' => $paidUser2->id,
69+
'title' => "まつもとの下書き {$i}",
70+
'content' => $this->generateContent($lineCount, 'まつもと', $i, '下書き'),
71+
'status' => 'draft',
72+
]);
73+
}
2774
}
2875

29-
// adminユーザーに 3 投稿
76+
// adminユーザーに 3 投稿(公開)
3077
if ($adminUser) {
3178
for ($i = 1; $i <= 3; $i++) {
79+
$lineCount = rand(1, 5);
3280
Post::create([
3381
'user_id' => $adminUser->id,
3482
'title' => "Adminユーザーの投稿 {$i}",
35-
'content' => "これはAdminユーザーの投稿 {$i} です。",
83+
'content' => $this->generateContent($lineCount, 'Adminユーザー', $i, '投稿'),
3684
'status' => 'published',
3785
]);
3886
}
87+
// adminユーザーに 2 下書き投稿
88+
for ($i = 1; $i <= 2; $i++) {
89+
$lineCount = rand(1, 5);
90+
Post::create([
91+
'user_id' => $adminUser->id,
92+
'title' => "Adminユーザーの下書き {$i}",
93+
'content' => $this->generateContent($lineCount, 'Adminユーザー', $i, '下書き'),
94+
'status' => 'draft',
95+
]);
96+
}
3997
}
4098
}
4199
}

routes/api.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,6 @@
4545
// Google OAuth(認証不要)
4646
Route::get('/auth/google', [GoogleAuthController::class, 'redirectToGoogle']);
4747
Route::get('/auth/google/callback', [GoogleAuthController::class, 'handleGoogleCallback']);
48-
49-
// 認証が必要なルート
50-
Route::middleware('auth:sanctum')->group(function () {
51-
Route::get('/user', function (Request $request) {
52-
return $request->user();
53-
});
54-
55-
Route::post('/logout', [AuthController::class, 'logout']);
56-
57-
Route::get('/profile', [UserController::class, 'show']);
58-
Route::put('/profile', [UserController::class, 'update']);
5948

6049
/**
6150
* APIリソースルート(RESTful API用)
@@ -74,5 +63,19 @@
7463
* 5. PATCH /api/posts/{post} → PostController::update()
7564
* 6. DELETE /api/posts/{post} → PostController::destroy()
7665
*/
77-
Route::apiResource('posts', PostController::class);
66+
// 認証不要で投稿一覧と詳細を取得できるようにする
67+
Route::apiResource('posts', PostController::class)->only(['index', 'show']);
68+
69+
// 認証が必要なルート
70+
Route::middleware('auth:sanctum')->group(function () {
71+
Route::get('/user', function (Request $request) {
72+
return $request->user();
73+
});
74+
75+
Route::post('/logout', [AuthController::class, 'logout']);
76+
77+
Route::get('/profile', [UserController::class, 'show']);
78+
Route::put('/profile', [UserController::class, 'update']);
79+
80+
Route::apiResource('posts', PostController::class)->only(['store', 'update', 'destroy']);
7881
});

0 commit comments

Comments
 (0)