From 55a52086c1366e7b592dfb57eb0e6e5e73b5aa4b Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Tue, 26 Aug 2025 12:12:02 +0200 Subject: [PATCH] Models refactor + Basic functionnalities --- .../Controllers/ResumeComponentController.php | 66 ------------- .../ResumeComponentDataTypeController.php | 66 ------------- .../ResumeComponentPlacementController.php | 91 ++++++++++++++++++ app/Http/Controllers/ResumeController.php | 31 ++++-- .../StoreResumeComponentPlacementRequest.php | 28 ++++++ .../UpdateResumeComponentPlacementRequest.php | 35 +++++++ app/Models/Resume.php | 10 +- app/Models/ResumeComponent.php | 22 ++--- app/Models/ResumeComponentData.php | 25 +++++ app/Models/ResumeComponentInput.php | 4 +- app/Models/ResumeComponentInputData.php | 21 ++++ app/Models/ResumeComponentPlacement.php | 29 ++++++ app/Models/ResumeSlot.php | 32 ------ app/Models/ResumeSlotValue.php | 51 ---------- app/Models/User.php | 9 +- .../ResumeComponentPlacementPolicy.php | 58 +++++++++++ bootstrap/app.php | 6 ++ composer.json | 3 +- composer.lock | 66 ++++++++++++- config/sanctum.php | 84 ++++++++++++++++ .../2025_08_15_161427_create_resumes.php | 2 +- ..._08_16_110416_create_resume_components.php | 1 + ..._125232_create_resume_component_inputs.php | 3 +- ...eate_resume_component_placement_table.php} | 10 +- ...17_131739_create_resume_component_data.php | 33 +++++++ ...45_create_resume_component_input_data.php} | 10 +- ...32_create_personal_access_tokens_table.php | 33 +++++++ public/favicon.ico | Bin 4286 -> 4286 bytes resources/js/components/AppLogo.vue | 2 +- resources/js/components/AppSidebar.vue | 10 +- resources/js/components/SidebarResumeList.vue | 42 ++++++++ .../js/components/resume/ResumeComponent.vue | 20 ++++ .../components/resume/ResumeComponentEdit.vue | 74 ++++++++++++++ .../resume/ResumeComponentEditForm.vue | 28 ++++++ .../resume/ResumeComponentEditFormInput.vue | 36 +++++++ .../resume/ResumeComponentsList.vue | 33 +++++++ .../js/components/resume/ResumeEditPanel.vue | 33 +++++++ .../components/resume/ResumePreviewPanel.vue | 19 ++++ .../resume/resumeComponents/email.vue | 15 +++ .../resume/resumeComponents/name.vue | 15 +++ .../resumeComponentsFormInput/emailInput.vue | 19 ++++ .../resumeComponentsFormInput/textInput.vue | 19 ++++ resources/js/layouts/app/AppSidebarLayout.vue | 7 +- resources/js/lib/utils.ts | 30 ++++++ resources/js/pages/resumes/Edit.vue | 44 +++++++++ resources/js/types/resume.d.ts | 55 +++++++++++ resources/views/app.blade.php | 1 + routes/api.php | 10 ++ routes/web.php | 2 +- 49 files changed, 1074 insertions(+), 269 deletions(-) delete mode 100644 app/Http/Controllers/ResumeComponentController.php delete mode 100644 app/Http/Controllers/ResumeComponentDataTypeController.php create mode 100644 app/Http/Controllers/ResumeComponentPlacementController.php create mode 100644 app/Http/Requests/StoreResumeComponentPlacementRequest.php create mode 100644 app/Http/Requests/UpdateResumeComponentPlacementRequest.php create mode 100644 app/Models/ResumeComponentData.php create mode 100644 app/Models/ResumeComponentInputData.php create mode 100644 app/Models/ResumeComponentPlacement.php delete mode 100644 app/Models/ResumeSlot.php delete mode 100644 app/Models/ResumeSlotValue.php create mode 100644 app/Policies/ResumeComponentPlacementPolicy.php create mode 100644 config/sanctum.php rename database/migrations/{2025_08_16_112542_create_resume_slots.php => 2025_08_17_125311_create_resume_component_placement_table.php} (55%) create mode 100644 database/migrations/2025_08_17_131739_create_resume_component_data.php rename database/migrations/{2025_08_17_094123_create_resume_slot_values.php => 2025_08_17_132945_create_resume_component_input_data.php} (62%) create mode 100644 database/migrations/2025_08_18_113232_create_personal_access_tokens_table.php create mode 100644 resources/js/components/SidebarResumeList.vue create mode 100644 resources/js/components/resume/ResumeComponent.vue create mode 100644 resources/js/components/resume/ResumeComponentEdit.vue create mode 100644 resources/js/components/resume/ResumeComponentEditForm.vue create mode 100644 resources/js/components/resume/ResumeComponentEditFormInput.vue create mode 100644 resources/js/components/resume/ResumeComponentsList.vue create mode 100644 resources/js/components/resume/ResumeEditPanel.vue create mode 100644 resources/js/components/resume/ResumePreviewPanel.vue create mode 100644 resources/js/components/resume/resumeComponents/email.vue create mode 100644 resources/js/components/resume/resumeComponents/name.vue create mode 100644 resources/js/components/resume/resumeComponentsFormInput/emailInput.vue create mode 100644 resources/js/components/resume/resumeComponentsFormInput/textInput.vue create mode 100644 resources/js/pages/resumes/Edit.vue create mode 100644 resources/js/types/resume.d.ts create mode 100644 routes/api.php diff --git a/app/Http/Controllers/ResumeComponentController.php b/app/Http/Controllers/ResumeComponentController.php deleted file mode 100644 index c2bd474..0000000 --- a/app/Http/Controllers/ResumeComponentController.php +++ /dev/null @@ -1,66 +0,0 @@ -load('componentData.component', 'componentData.inputData'); + + $data = $request->validated(); + + // Update component placement + $componentPlacementData = collect($data)->except('component_data')->toArray(); + $componentPlacementData['resume_component_data_id'] = $data['component_data']['id'] ?? null; + $resumeComponentPlacement->update($componentPlacementData); + + // Update component data + $componentData = collect($data['component_data'])->except(['input_data', 'component'])->toArray(); + $componentData['resume_component_id'] = $data['component_data']['component']['id'] ?? null; + $componentData['resume_component_placement_id'] = $resumeComponentPlacement->id; + $resumeComponentPlacement->componentData()->update($componentData); + + // Update input data + $inputData = collect($data['component_data']['input_data'] ?? []); + foreach ($inputData as $inputDatum) { + $componentInput = $inputDatum['component_input']; + $inputDatum = collect($inputDatum)->except('component_input')->toArray(); + $inputDatum['resume_component_input_id'] = $componentInput['id'] ?? null; + $inputDatum['resume_component_data_id'] = $componentData['id'] ?? null; + + $resumeComponentPlacement->componentData->inputData()->updateOrCreate( + ['id' => $inputDatum['id'] ?? null], + $inputDatum + ); + } + + $resumeComponentPlacement->push(); + $resumeComponentPlacement->refresh(); + + return response()->json($resumeComponentPlacement); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(ResumeComponentPlacement $resumeComponentPlacement) + { + dd('hello'); + + // + } +} diff --git a/app/Http/Controllers/ResumeController.php b/app/Http/Controllers/ResumeController.php index 8f237c1..7290747 100644 --- a/app/Http/Controllers/ResumeController.php +++ b/app/Http/Controllers/ResumeController.php @@ -5,6 +5,9 @@ namespace App\Http\Controllers; use App\Http\Requests\StoreResumeRequest; use App\Http\Requests\UpdateResumeRequest; use App\Models\Resume; +use Illuminate\Http\JsonResponse; +use Inertia\Inertia; +use Illuminate\Http\Request; class ResumeController extends Controller { @@ -13,15 +16,24 @@ class ResumeController extends Controller */ public function index() { - // + $resumes = Resume::all(); + return new JsonResponse($resumes); } /** * Show the form for creating a new resource. */ - public function create() + public function create(Request $request) { - // + // Check if the user can create the resume + if ($request->user()->cannot('create', Resume::class)) { + abort(403); + } + + $newResume = new Resume(); + $newResume->save(); + // Redirect to the edit page for the new resume + return redirect()->route('resumes.edit', $newResume); } /** @@ -37,15 +49,22 @@ class ResumeController extends Controller */ public function show(Resume $resume) { - // + return redirect()->route('resumes.edit', $resume); } /** * Show the form for editing the specified resource. */ - public function edit(Resume $resume) + public function edit(Request $request, Resume $resume) { - // + // Check if the user can edit the resume + if ($request->user()->cannot('update', $resume)) { + abort(403); + } + + return Inertia::render('resumes/Edit', [ + 'resume' => $resume->load('componentsPlacements.componentData.component', 'componentsPlacements.componentData.inputData.componentInput.dataType')->toArray() + ]); } /** diff --git a/app/Http/Requests/StoreResumeComponentPlacementRequest.php b/app/Http/Requests/StoreResumeComponentPlacementRequest.php new file mode 100644 index 0000000..21cde9a --- /dev/null +++ b/app/Http/Requests/StoreResumeComponentPlacementRequest.php @@ -0,0 +1,28 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/UpdateResumeComponentPlacementRequest.php b/app/Http/Requests/UpdateResumeComponentPlacementRequest.php new file mode 100644 index 0000000..ca7c5cc --- /dev/null +++ b/app/Http/Requests/UpdateResumeComponentPlacementRequest.php @@ -0,0 +1,35 @@ +user()->can('update', $this->resume_component_placement); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'id' => 'required|exists:resume_component_placements,id', + 'order' => 'required|integer', + 'component_data.id' => 'required|exists:resume_component_data,id', + 'component_data.component.id' => 'required|exists:resume_components,id', + 'component_data.input_data' => 'required|array', + 'component_data.input_data.*.id' => 'required|exists:resume_component_input_data,id', + 'component_data.input_data.*.value' => 'required|string', + 'component_data.input_data.*.component_input.id' => 'required|exists:resume_component_inputs,id', + ]; + } +} diff --git a/app/Models/Resume.php b/app/Models/Resume.php index d34f455..7ffa5d8 100644 --- a/app/Models/Resume.php +++ b/app/Models/Resume.php @@ -26,15 +26,13 @@ class Resume extends Model return $this->belongsTo(User::class, 'creator_id'); } - public function slots(): HasMany + public function componentsPlacements(): HasMany { - return $this->hasMany(ResumeSlot::class); + return $this->hasMany(ResumeComponentPlacement::class); } - public function components(): BelongsToMany + public function componentsData() { - return $this->belongsToMany(ResumeComponent::class) - ->using(ResumeSlot::class) - ->orderBy('resume_slots.order'); + return $this->hasManyThrough(ResumeComponentData::class, ResumeComponentPlacement::class); } } diff --git a/app/Models/ResumeComponent.php b/app/Models/ResumeComponent.php index d63e916..d82e777 100644 --- a/app/Models/ResumeComponent.php +++ b/app/Models/ResumeComponent.php @@ -16,30 +16,24 @@ class ResumeComponent extends Model use HasFactory; protected $fillable = [ + 'name', 'vue_component_name', ]; public function resumes(): BelongsToMany { - return $this->belongsToMany(Resume::class, 'resume_resume_component') - ->using(ResumeSlot::class) - ->withTimestamps(); + return $this->belongsToMany(Resume::class) + ->using(ResumeComponentData::class); } - public function slots(): HasMany + public function data(): HasMany { - return $this->hasMany(ResumeSlot::class); + return $this->hasMany(ResumeComponentData::class); } - public function inputs(): HasMany + public function placements(): BelongsToMany { - return $this->hasMany(ResumeComponentInput::class); - } - - public function dataTypes(): BelongsToMany - { - return $this->belongsToMany(ResumeComponentDataType::class) - ->using(ResumeComponentInput::class) - ->withTimestamps(); + return $this->belongsToMany(ResumeComponentPlacement::class) + ->using(ResumeComponentData::class); } } diff --git a/app/Models/ResumeComponentData.php b/app/Models/ResumeComponentData.php new file mode 100644 index 0000000..06c86c8 --- /dev/null +++ b/app/Models/ResumeComponentData.php @@ -0,0 +1,25 @@ +belongsTo(ResumeComponent::class, 'resume_component_id'); + } + + public function componentPlacements() + { + return $this->hasMany(ResumeComponentPlacement::class); + } + + public function inputData() + { + return $this->hasMany(ResumeComponentInputData::class, 'resume_component_data_id'); + } +} diff --git a/app/Models/ResumeComponentInput.php b/app/Models/ResumeComponentInput.php index 1126c40..be69a66 100644 --- a/app/Models/ResumeComponentInput.php +++ b/app/Models/ResumeComponentInput.php @@ -15,6 +15,8 @@ class ResumeComponentInput extends Pivot /** @use HasFactory<\Database\Factories\ResumeComponentFactory> */ use HasFactory; + protected $table = 'resume_component_inputs'; + protected $fillable = [ 'resume_component_id', 'resume_component_data_type_id', @@ -34,6 +36,6 @@ class ResumeComponentInput extends Pivot public function dataType(): BelongsTo { - return $this->belongsTo(ResumeComponentDataType::class); + return $this->belongsTo(ResumeComponentDataType::class, 'resume_component_data_type_id'); } } diff --git a/app/Models/ResumeComponentInputData.php b/app/Models/ResumeComponentInputData.php new file mode 100644 index 0000000..52fb330 --- /dev/null +++ b/app/Models/ResumeComponentInputData.php @@ -0,0 +1,21 @@ +belongsTo(ResumeComponentData::class, 'resume_component_data_id'); + } + + public function componentInput() + { + return $this->belongsTo(ResumeComponentInput::class, 'resume_component_input_id'); + } +} diff --git a/app/Models/ResumeComponentPlacement.php b/app/Models/ResumeComponentPlacement.php new file mode 100644 index 0000000..cb23422 --- /dev/null +++ b/app/Models/ResumeComponentPlacement.php @@ -0,0 +1,29 @@ +belongsTo(ResumeComponentData::class, 'resume_component_data_id'); + } + + public function resume(): BelongsTo + { + return $this->belongsTo(Resume::class); + } + + public function component(): BelongsTo + { + return $this->componentData->component(); + } +} diff --git a/app/Models/ResumeSlot.php b/app/Models/ResumeSlot.php deleted file mode 100644 index 92c2b0f..0000000 --- a/app/Models/ResumeSlot.php +++ /dev/null @@ -1,32 +0,0 @@ - */ - use HasFactory; - - protected $fillable = [ - 'resume_id', - 'resume_component_id', - 'order', - ]; - - public function resume() - { - return $this->belongsTo(Resume::class); - } - - public function component() - { - return $this->belongsTo(ResumeComponent::class); - } -} diff --git a/app/Models/ResumeSlotValue.php b/app/Models/ResumeSlotValue.php deleted file mode 100644 index a95e5f8..0000000 --- a/app/Models/ResumeSlotValue.php +++ /dev/null @@ -1,51 +0,0 @@ - */ - use HasFactory; - - protected $fillable = [ - 'resume_slot_id', - 'resume_component_input_id', - 'value' - ]; - - public function resume(): BelongsTo - { - return $this->belongsTo(Resume::class) - ->through(ResumeSlot::class); - } - - public function component(): BelongsTo - { - return $this->belongsTo(ResumeComponent::class) - ->through(ResumeSlot::class); - } - - public function dataType(): BelongsTo - { - return $this->belongsTo(ResumeComponentDataType::class) - ->through(ResumeComponentInput::class); - } - - public function slot(): BelongsTo - { - return $this->belongsTo(ResumeSlot::class); - } - - public function componentInput(): BelongsTo - { - return $this->belongsTo(ResumeComponentInput::class); - } -} diff --git a/app/Models/User.php b/app/Models/User.php index 749c7b7..a0c4459 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,13 +4,15 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasApiTokens; /** * The attributes that are mass assignable. @@ -45,4 +47,9 @@ class User extends Authenticatable 'password' => 'hashed', ]; } + + public function resumes(): HasMany + { + return $this->hasMany(Resume::class, 'creator_id'); + } } diff --git a/app/Policies/ResumeComponentPlacementPolicy.php b/app/Policies/ResumeComponentPlacementPolicy.php new file mode 100644 index 0000000..7016feb --- /dev/null +++ b/app/Policies/ResumeComponentPlacementPolicy.php @@ -0,0 +1,58 @@ +id === $componentPlacement->load('resume')->resume->creator_id + ? Response::allow() + : Response::deny('You do not own the resume of the component placement.'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, ResumeComponentPlacement $componentPlacement): Response + { + return $this->isCreator($user, $componentPlacement); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return true; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ResumeComponentPlacement $componentPlacement): Response + { + return $this->isCreator($user, $componentPlacement); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ResumeComponentPlacement $componentPlacement): Response + { + return $this->isCreator($user, $componentPlacement); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, ResumeComponentPlacement $componentPlacement): Response + { + return $this->isCreator($user, $componentPlacement); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 134581a..bd683b6 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -10,6 +10,7 @@ use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) @@ -21,6 +22,11 @@ return Application::configure(basePath: dirname(__DIR__)) HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, ]); + + $middleware->api([ + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/composer.json b/composer.json index 107cfa8..895ea87 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "php": "^8.2", "inertiajs/inertia-laravel": "^2.0", "laravel/framework": "^12.0", + "laravel/sanctum": "^4.0", "laravel/tinker": "^2.10.1", "tightenco/ziggy": "^2.4" }, @@ -82,4 +83,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index d062873..3b1a81b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "238758776cef75e40b61f11e6f2e00e2", + "content-hash": "d8582846588d6533069ca5f9a52ee9f7", "packages": [ { "name": "brick/math", @@ -1399,6 +1399,70 @@ }, "time": "2025-07-07T14:17:42+00:00" }, + { + "name": "laravel/sanctum", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/contracts": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "php": "^8.2", + "symfony/console": "^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.0|^10.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2025-07-09T19:45:24+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.4", diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000..5110c29 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,84 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,localhost:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + // Sanctum::currentRequestHost(), + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. This will override any values set in the token's + | "expires_at" attribute, but first-party sessions are not affected. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Token Prefix + |-------------------------------------------------------------------------- + | + | Sanctum can prefix new tokens in order to take advantage of numerous + | security scanning initiatives maintained by open source platforms + | that notify developers if they commit tokens into repositories. + | + | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning + | + */ + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + ], + +]; diff --git a/database/migrations/2025_08_15_161427_create_resumes.php b/database/migrations/2025_08_15_161427_create_resumes.php index b8eba4d..b8b71bc 100644 --- a/database/migrations/2025_08_15_161427_create_resumes.php +++ b/database/migrations/2025_08_15_161427_create_resumes.php @@ -15,7 +15,7 @@ return new class extends Migration Schema::create('resumes', function (Blueprint $table) { $table->id(); - $table->string('name', 255); + $table->string('name', 255)->nullable(); $table->foreignIdFor(User::class, "creator_id")->constrained()->onDelete('cascade'); $table->timestamps(); diff --git a/database/migrations/2025_08_16_110416_create_resume_components.php b/database/migrations/2025_08_16_110416_create_resume_components.php index 8e50b71..7f57fac 100644 --- a/database/migrations/2025_08_16_110416_create_resume_components.php +++ b/database/migrations/2025_08_16_110416_create_resume_components.php @@ -14,6 +14,7 @@ return new class extends Migration Schema::create('resume_components', function (Blueprint $table) { $table->id(); + $table->string('name'); $table->string('vue_component_name')->unique(); $table->timestamps(); diff --git a/database/migrations/2025_08_16_125232_create_resume_component_inputs.php b/database/migrations/2025_08_16_125232_create_resume_component_inputs.php index aa87fa7..dba8e50 100644 --- a/database/migrations/2025_08_16_125232_create_resume_component_inputs.php +++ b/database/migrations/2025_08_16_125232_create_resume_component_inputs.php @@ -20,7 +20,8 @@ return new class extends Migration $table->foreignIdFor(ResumeComponentDataType::class)->constrained()->onDelete('cascade'); $table->string('name'); - $table->string("placeholder"); + $table->string('label')->nullable(); + $table->string("placeholder")->nullable(); $table->timestamps(); }); diff --git a/database/migrations/2025_08_16_112542_create_resume_slots.php b/database/migrations/2025_08_17_125311_create_resume_component_placement_table.php similarity index 55% rename from database/migrations/2025_08_16_112542_create_resume_slots.php rename to database/migrations/2025_08_17_125311_create_resume_component_placement_table.php index 516e509..717b9e8 100644 --- a/database/migrations/2025_08_16_112542_create_resume_slots.php +++ b/database/migrations/2025_08_17_125311_create_resume_component_placement_table.php @@ -1,7 +1,7 @@ id(); - $table->foreignIdFor(Resume::class)->constrained()->onDelete('cascade'); - $table->foreignIdFor(ResumeComponent::class)->constrained()->onDelete('cascade'); + $table->foreignIdFor(Resume::class, 'resume_id')->constrained()->onDelete('cascade'); + $table->foreignIdFor(ResumeComponentData::class, 'resume_component_data_id')->constrained()->onDelete('cascade'); $table->integer('order')->default(0); @@ -30,6 +30,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('resume_slots'); + Schema::dropIfExists('resume_component_placements'); } }; diff --git a/database/migrations/2025_08_17_131739_create_resume_component_data.php b/database/migrations/2025_08_17_131739_create_resume_component_data.php new file mode 100644 index 0000000..c568dfb --- /dev/null +++ b/database/migrations/2025_08_17_131739_create_resume_component_data.php @@ -0,0 +1,33 @@ +id(); + + $table->foreignIdFor(ResumeComponent::class, 'resume_component_id')->constrained()->onDelete('cascade'); + // $table->foreignIdFor(ResumeComponentPlacement::class, 'resume_component_placement_id')->constrained()->onDelete('cascade'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('resume_component_data'); + } +}; diff --git a/database/migrations/2025_08_17_094123_create_resume_slot_values.php b/database/migrations/2025_08_17_132945_create_resume_component_input_data.php similarity index 62% rename from database/migrations/2025_08_17_094123_create_resume_slot_values.php rename to database/migrations/2025_08_17_132945_create_resume_component_input_data.php index 1198c01..cef2d42 100644 --- a/database/migrations/2025_08_17_094123_create_resume_slot_values.php +++ b/database/migrations/2025_08_17_132945_create_resume_component_input_data.php @@ -1,7 +1,7 @@ id(); - $table->foreignIdFor(ResumeSlot::class, 'resume_slot_id')->constrained()->onDelete('cascade'); + $table->foreignIdFor(ResumeComponentData::class, 'resume_component_data_id')->constrained()->onDelete('cascade'); $table->foreignIdFor(ResumeComponentInput::class, 'resume_component_input_id')->constrained()->onDelete('cascade'); - $table->string('value'); + $table->string('value')->nullable(); $table->timestamps(); }); @@ -30,6 +30,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('resume_slot_values'); + Schema::dropIfExists('resume_component_input_data'); } }; diff --git a/database/migrations/2025_08_18_113232_create_personal_access_tokens_table.php b/database/migrations/2025_08_18_113232_create_personal_access_tokens_table.php new file mode 100644 index 0000000..40ff706 --- /dev/null +++ b/database/migrations/2025_08_18_113232_create_personal_access_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/public/favicon.ico b/public/favicon.ico index 236fadb94188e9189807c50aa8480b3070a0cc8d..11f50a8d1a25572b03606b37e4ce6c5ab141c971 100644 GIT binary patch literal 4286 zcmb`KTZmOv7{~Xh<1IBSFC?1B=;A{&j#(p-fkK6Lu^!UuVY)aL5{6(#kez0FFiWsZ zg%BD=eQ3H^Hr+KHO$)mC5-381P;Z3@4y2>*R=@xD`%e4pIcGd)I$M6TF5l(9*IAdn zjG{XJ<#Lh!MxA4$XhIZ4(}AvNE~sBmJGOy7xu#H;YX}Xn6FOm5ZbqmZXufLV(3$|N z-~b$h2V;D*PhhZ;h%(I~lUWZwr-(o+s;5(|C-2M5IZ!p{r(v-H| zVWeUxUxKw(A5Q&2_zFIRxiF-^+?TQue;ur!5u!l(Jd97r&@SkEKYdrh3OEcKVS4VS z>edFopXWG4{H8>|v2PA+ee_fBk$rb2cV!b{~Nw@POO1CAP;w z>v(Y5mdY~-+s)7c+J8^Mlc06K9d3m|idBr~P@R6A8C#*%`?DoBjk}~i{ZlQE#@r5* zJ)Zg`&{~jfZCYDCDSf^7K=pqiw##$5wwKI_#@8Ub`8-4WiW=(wNxp;7s{F2-ElvAQ z{62;+V4?d{|3Bl`+-c2q3DX%`N?(2WjD{zm+kBg;Z-XV-7;j_iZ~nJf-pldRx;toj z^&LvSJw8s)Xa1ASPiM&=uq!>?wffp~uUdYcn_6!l!Cjzl zkQL`(AiRzzVZr<*EBTV<)FR#1GK;>vuS@8t$*5+;T4eY z9H>~|a%%j^_t)P+C4HOW1=aPG)0YiDeRH%|bw1Zhe*?KzD!-nLe$oEJPxGSn@F-NI zuYAgjf6@3;S_S=-ujb}6c$u@Z@O`0OQ~lICB;&WS>$@uLuR&+%7H9;miK_H{Jmto3 z{Toa-`5vI5b>0eN;2Ky1-+_L=sZjR!S02mNk((LnD%5|2oJUfzg>Pl@evQdqy9FPe z$NS(oEQX@_r+rx}$5DHa!aWLmpZT}(>;Bi{vFa=J;G?nYjQAby0O{`l-EY)>-*b3L zpYBnM?G98T7)8kOWgPv_<;(E6EHNz6cfLgtBS z$Kiaq5PFQ0A#VFE#N8bsKCv&vr{GLJ#OLA=_d*P(;Z*)2C-&m^EB=3l-tL~z+tw4# Oww(`WLob;{@$)aPOfh-@ literal 4286 zcmcJSYiv|S6vqb^Sj9>!kD_?#USEjFuB*k05m`ulKhQ)0{ZR3Nj~ES!#h9qrHiD>$ zFCbA83#r6d--z!Q-dZre@B@j4kBxrw(=r7ZdN&YYP!=ggUN z&s>a|z(3zN`fK`{jA=H;%m+kf8A$GajE&{z=dAJbc7mU^S=a@A(CVLujIm?&L%;DC zLe@6JdI;b*I0WCqqi~9wGmd_s)3`n7>{QyT;Y-k-FM>{Z23kR}*bD8AB1Y}dT<%4|D*yFG{ipz1dZ-md_9asjFVAL*Xjeow=eI@kwWU_SmSU;ft*cgCUqSjPbCwmKa-bR^DI`k=CZ?Ca;^-d5Ll<&7I3$q~lW zumU~>#c()c$ zznq=I*j4Zy`~=s7e6N}x_jxK`Zh-@E10+?J+sM~|@>qVCnUcYu{D&HP^q=+0M}32-cM_!3Ciyi+W;gJQ1O$7`Uq{%qEf zQ&BAEGXHHD1l564qg*VjMHc7h8MJr9F#HPgp`07>0%S*IU-OwO9s`}T@>+3T1B!b* zKArZ1un%4W-3_mTYTL(f9k{v?=^U+ny>m|ylSw>PH%q64pG5ofa4((kv;&-?dWdAkLy`uhG@CDoiOQ8?+ z9kmLbnp3s^M6T<5NBhy**WqFab58X;{Fc}ICS9`EhD_t_k?aMYs+oF+X2x}$w)C!s zub>y+giUTd`u=o2$VPbg%w}A5K=FGP&VnVNeaN?!pjv4BMJ}c{)BXz-le3+UDBqmk zP^aQH6SlzLumSXcGEJ%aDelxC<@f^F20w!KrQDna?i}Mq^k;zX6#3f)bNvO(k)BF^ zHzB_V0(cothr0F?dSz!aw81>*Q(|8=^W<+HdO$YTVt*Rwysv=|;RCn=q%&SVZcpiq z>|ITJ5A23ruovR@SbG{bo3)A(@mAf`d#C(4&3${+iCyjbvGi?Vt?lZx!zN>grl)PO zquU0hL7OTS3ys0yLdGmDv^2C7yp|~iFVkJ{(t`!Bd#K
- Laravel Starter Kit + CVAtron
diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index 6b35083..c84cd3d 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -7,14 +7,6 @@ import { type NavItem } from '@/types'; import { Link } from '@inertiajs/vue3'; import { LayoutGrid } from 'lucide-vue-next'; import AppLogo from './AppLogo.vue'; - -const mainNavItems: NavItem[] = [ - { - title: 'Dashboard', - href: '/dashboard', - icon: LayoutGrid, - }, -];