laravel + livewire + tailwind - データベース作り直し

(2025-01-11)

データベースが外部キーでがんじがらめになっていてテーマを削除できないため、データベースを作り直すことになりました。

chatGPT が作ってくれたデータベース構造は外部キーが複雑に絡み合ってテーマの削除がプログラムでは簡単にできません。

それと、やはり sqlite3 ではなくて mysql の方が遥かに扱いやすいので mysql にします。

migration テーブルの構造

database/migration/2025_01_01_160704_add_role_to_users_table

database/migration/2025_01_01_160704_add_role_to_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddRoleToUsersTable extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')->default('user');
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
}

database/migration/2024_12_30_155645_create_themes_table.php”

database/migration/2024_12_30_155645_create_themes_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateThemesTable extends Migration
{
public function up()
{
Schema::create('themes', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('title');
$table->string('presen');
$table->timestamps();
});
}
public function down()
{
// 'themes' テーブルを削除する
Schema::dropIfExists('themes');
}
}

database/migration/2024_12_30_160420_create_messages_table.php

database/migration/2024_12_30_160420_create_messages_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMessagesTable extends Migration
{
public function up()
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->foreignId('theme_id')->constrained('themes')->onDelete('cascade'); // themeが削除されると関連するmessageも削除される
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->text('content');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('messages');
}
}

database/migration/2025_01_03_010159__create_zip_files_table.php

database/migration/2025_01_03_010159__create_zip_files_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateZipFilesTable extends Migration
{
public function up()
{
Schema::create('zip_files', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->timestamps();
$table->foreignId('theme_id')->constrained('themes')->onDelete('cascade'); // themeが削除されるとzip_fileも削除される
$table->string('hash');
$table->unique(['theme_id', 'hash']);
});
}
public function down()
{
Schema::dropIfExists('zip_files');
}
}

database/migration/2024_12_30_155837_create_images_table.php

database/migration/2024_12_30_155837_create_images_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateImagesTable extends Migration
{
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->timestamps();
$table->foreignId('theme_id')->nullable()->constrained('themes')->onDelete('cascade'); // themeが削除されると関連するimageも削除される
});
}
public function down()
{
Schema::dropIfExists('images');
}
}

マイグレーション

php artisan migrate

Admin seeder と .env は省略。

Admin に theme 削除を追加

livewire コンポーネント作成

php artisan make:livewire Admin/ThemeDelete
php artisan make:livewire Admin/ThemeDeleteList

編集

app/Livewire/Admin/ThemeDeleteList.php
<?php
namespace App\Livewire\Admin;
use Livewire\Component;
use App\Models\Theme;
class ThemeDeleteList extends Component
{
public $selectedThemes = [];
public function deleteSelected()
{
// 選択されたテーマを削除
Theme::whereIn('id', $this->selectedThemes)->delete();
// セッションメッセージを設定
session()->flash('message', '選択したテーマが削除されました。');
// 選択リストをリセット
$this->selectedThemes = [];
}
public function render()
{
return view('livewire.admin.theme-delete-list', [
'themes' => Theme::all()
]);
}
}

app/Livewire/Admin/ThemeDelete.php

app/Livewire/Admin/ThemeDelete.php
<?php
namespace App\Livewire\Admin;
use Livewire\Component;
use App\Models\Theme;
class ThemeDelete extends Component
{
public $themeId;
public function mount($themeId)
{
$this->themeId = $themeId;
}
public function deleteTheme()
{
$theme = Theme::find($this->themeId);
if ($theme) {
$theme->delete();
session()->flash('message', 'テーマが削除されました。');
} else {
session()->flash('error', '指定されたテーマが見つかりません。');
}
return redirect()->route('admin.theme-delete-list');
}
public function render()
{
return view('livewire.admin.theme-delete');
}
}

blade

resourece/views/livewire/admin/theme-delete-list.php
<div class="m-3">
<h1 class="text-xl font-bold mb-4 border border-gray-500 bg-slate-200 px-4 py-2">テーマ削除一覧</h1>
<p class="border border-slate-300 p-2 rounded shadow-md bg-gray-50 leading-7">
テーマを削除します。<br>
テーマを削除するとそれに関連する画像もすべて削除されます。
</p>
@if (session()->has('message'))
<div class="bg-green-100 text-green-700 p-2 rounded mb-4">
{{ session('message') }}
</div>
@endif
<div class="mt-5 mx-3"><a href="{{ route('dashboard') }}" class="px-4 py-2 bg-orange-900 text-white rounded hover:bg-orange-700">トップへ</a></div>
<h2 class="text-lg font-bold border border-gray-400 p-2 bg-blue-50 my-5">テーマ一覧</h2>
<form wire:submit.prevent="deleteSelected">
<table class="border-collapse border border-gray-300">
<thead>
<tr>
<th class="border border-gray-300 px-4 py-1 bg-sky-900 text-white"></th>
<th class="border border-gray-300 px-4 py-1 bg-sky-900 text-white">テーマID</th>
<th class="border border-gray-300 px-4 py-1 bg-sky-900 text-white">テーマ名</th>
<th class="border border-gray-300 px-4 py-1 bg-sky-900 text-white">作成者</th>
<th class="border border-gray-300 px-4 py-1 bg-sky-900 text-white">作成日</th>
</tr>
</thead>
<tbody>
@foreach ($themes as $theme)
<tr>
<td class="border border-gray-300 px-4 py-1">
<input type="checkbox" wire:model="selectedThemes" value="{{ $theme->id }}">
</td>
<td class="border border-gray-300 px-4 py-1 text-right">{{ $theme->id }}</td>
<td class="border border-gray-300 px-4 py-1">{{ $theme->title }}</td>
<td class="border border-gray-300 px-4 py-1">{{ $theme->user->name }}</td>
<td class="border border-gray-300 px-4 py-1">{{ $theme->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>
<button type="submit" class="mt-4 bg-orange-800 text-white px-4 py-2 rounded hover:bg-orange-700">
選択したテーマを削除
</button>
</form>
</div>

resourece/views/livewire/admin/theme-delete.php

resourece/views/livewire/admin/theme-delete.php
<div>
<p>本当にこのテーマを削除しますか?</p>
<button wire:click="deleteTheme" class="bg-red-500 text-white px-4 py-2 rounded">削除</button>
<a href="{{ route('admin.theme-delete-list') }}" class="bg-gray-300 text-black px-4 py-2 rounded">キャンセル</a>
</div>

あれ?、これ使ってないような気がしますが。一応残しておきます。

routing

routes/web.php

routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Livewire\ThemeCreate;
use App\Livewire\ThemeList;
use App\Livewire\ThemeShow;
use App\Livewire\ZipImage;
use App\Livewire\ThemeImage;
use App\Http\Middleware\AdminMiddleware;
use App\Livewire\Admin\AdminDashboard;
#use App\Livewire\Admin\CreateMember;
use App\Http\Controllers\ProfileController;
use App\Mail\ThemeCreatedNotification;
use Illuminate\Support\Facades\Log;
use App\Livewire\Admin\ThemeDeleteList;
use App\Livewire\Admin\ThemeDelete;
use App\Livewire\SendWriteRequest;
Route::get('/', function () {
return view('welcome');
})->name('home');
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/theme/create', ThemeCreate::class)->name('theme.create');
Route::get('/theme/list', ThemeList::class)->name('theme.list');
Route::get('/theme/{theme_id}', ThemeShow::class)->name('theme.show');
Route::get('/zip-image', ZipImage::class)->name('zip.image');
Route::get('/theme/{theme_id}/images/{folderName?}', ThemeImage::class)->name('theme.image');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::get('/send-request', SendWriteRequest::class)->name('send.request');
});
Route::middleware(['auth', AdminMiddleware::class])->group(function () {
Route::get('/admin/dashboard', AdminDashboard::class)->name('admin.dashboard');
Route::get('/admin/create-member', \App\Livewire\Admin\CreateMember::class)->name('admin.create-member');
Route::get('/admin/delete-member', \App\Livewire\Admin\DeleteMember::class)->name('admin.delete-member');
Route::get('/admin/member-list', \App\Livewire\Admin\MemberEditList::class)->name('admin.member-list');
Route::get('/admin/edit-member/{userId}', \App\Livewire\Admin\EditMember::class)->name('admin.edit-member');
Route::get('/admin/theme-delete-list', ThemeDeleteList::class)->name('admin.theme-delete-list');
Route::get('/admin/theme-delete/{themeId}', ThemeDelete::class)->name('admin.theme-delete');
});
require __DIR__.'/auth.php';

ローカルでビルドされた tailwind のアップロード

コマンド一行で済ませます。

scp -r -i ~/.ssh/moheno.key -P 10022 ~/laraconf/public/build moheno@sv11123.xserver.jp:~/laraconf.site/laraconf/public/

zip ファイルの削除

zip ファイルはアップロードされれば必要ないので削除します。

これがとてもややこしい。

つまり、laravel と livewire で一時保存先が異なっており、それぞれに削除しなければなりません。

$zipPath = $zipFile->store('temp', 'local');
$zipFilePath = storage_path('app/' . $zipPath);
...
Storage::delete($zipPath);

これは laravel の一時ファイルを削除するもの。

そして livewire の一時ファイルは次のようにして削除します。

use Illuminate\Support\Facades\File;
...
File::cleanDirectory(storage_path('app/livewire-tmp'));