Initial Ladill Frontdesk release with deploy pipeline.
Deploy Ladill Frontdesk / deploy (push) Failing after 35s
Test / test (push) Failing after 2m45s

Visitor management app with SSO, kiosk, badges, reports, and Gitea CI deploy to frontdesk.ladill.com.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-27 20:37:15 +00:00
co-authored by Cursor
commit 9e2d79936c
284 changed files with 29134 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
*.sqlite*
@@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Local mirror of the platform identity, keyed by public_id (OIDC sub).
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->uuid('public_id')->unique();
$table->string('name')->nullable();
$table->string('email')->unique();
$table->string('avatar_url')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password')->nullable();
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration')->index();
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};
@@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};
@@ -0,0 +1,207 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Ladill Frontdesk core schema multi-tenant visitor management.
*
* Hierarchy: owner_ref Organization Branch Building ReceptionDesk Device
* Every record is scoped by owner_ref (Ladill platform user public_id).
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('frontdesk_organizations', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->string('name');
$table->string('slug')->index();
$table->string('logo_path')->nullable();
$table->string('timezone')->default('UTC');
$table->json('settings')->nullable();
$table->timestamps();
$table->softDeletes();
$table->unique(['owner_ref', 'slug']);
});
Schema::create('frontdesk_branches', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->string('name');
$table->string('code')->nullable();
$table->string('address')->nullable();
$table->string('phone')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_buildings', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('branch_id')->constrained('frontdesk_branches')->cascadeOnDelete();
$table->string('name');
$table->string('floor_count')->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_reception_desks', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('building_id')->constrained('frontdesk_buildings')->cascadeOnDelete();
$table->string('name');
$table->string('location')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_members', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->string('user_ref')->index(); // Ladill public_id
$table->string('role'); // super_admin, org_admin, branch_admin, receptionist, security_officer, host, auditor
$table->foreignId('branch_id')->nullable()->constrained('frontdesk_branches')->nullOnDelete();
$table->timestamps();
$table->unique(['organization_id', 'user_ref']);
});
Schema::create('frontdesk_hosts', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->foreignId('branch_id')->nullable()->constrained('frontdesk_branches')->nullOnDelete();
$table->string('name');
$table->string('department')->nullable();
$table->string('office')->nullable();
$table->string('phone')->nullable();
$table->string('email')->nullable();
$table->string('extension')->nullable();
$table->boolean('is_available')->default(true);
$table->string('user_ref')->nullable()->index(); // linked Ladill user if host has account
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_visitors', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->string('full_name');
$table->string('company')->nullable();
$table->string('phone')->nullable();
$table->string('email')->nullable();
$table->string('photo_path')->nullable();
$table->string('id_document_path')->nullable();
$table->string('watchlist_status')->default('allowed'); // allowed, requires_approval, blacklisted
$table->text('notes')->nullable();
$table->unsignedInteger('visit_count')->default(0);
$table->boolean('is_frequent')->default(false);
$table->timestamps();
$table->softDeletes();
$table->index(['owner_ref', 'full_name']);
$table->index(['owner_ref', 'phone']);
$table->index(['owner_ref', 'email']);
});
Schema::create('frontdesk_visits', function (Blueprint $table) {
$table->id();
$table->uuid('public_id')->unique();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->foreignId('branch_id')->nullable()->constrained('frontdesk_branches')->nullOnDelete();
$table->foreignId('reception_desk_id')->nullable()->constrained('frontdesk_reception_desks')->nullOnDelete();
$table->foreignId('visitor_id')->constrained('frontdesk_visitors')->cascadeOnDelete();
$table->foreignId('host_id')->nullable()->constrained('frontdesk_hosts')->nullOnDelete();
$table->string('visitor_type'); // visitor, contractor, vendor, interview_candidate, delivery
$table->string('status')->default('expected'); // scheduled, expected, waiting, checked_in, checked_out, cancelled, overdue
$table->string('purpose')->nullable();
$table->unsignedInteger('expected_duration_minutes')->nullable();
$table->timestamp('scheduled_at')->nullable();
$table->timestamp('checked_in_at')->nullable();
$table->timestamp('checked_out_at')->nullable();
$table->timestamp('badge_expires_at')->nullable();
$table->string('badge_code')->nullable()->index();
$table->string('qr_token')->nullable()->unique();
$table->string('photo_path')->nullable();
$table->string('signature_path')->nullable();
$table->boolean('policies_accepted')->default(false);
$table->json('vehicle_info')->nullable();
$table->json('contractor_details')->nullable();
$table->json('delivery_details')->nullable();
$table->json('allowed_areas')->nullable();
$table->text('notes')->nullable();
$table->string('checked_in_by')->nullable(); // user_ref of receptionist
$table->string('checked_out_by')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['owner_ref', 'status']);
$table->index(['owner_ref', 'checked_in_at']);
});
Schema::create('frontdesk_devices', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->foreignId('branch_id')->nullable()->constrained('frontdesk_branches')->nullOnDelete();
$table->foreignId('reception_desk_id')->nullable()->constrained('frontdesk_reception_desks')->nullOnDelete();
$table->string('name');
$table->string('type'); // reception_computer, kiosk, tablet, badge_printer, etc.
$table->string('status')->default('offline'); // online, offline, maintenance
$table->string('device_token')->nullable()->unique();
$table->json('config')->nullable();
$table->timestamp('last_online_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_watchlist_entries', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->foreignId('visitor_id')->nullable()->constrained('frontdesk_visitors')->nullOnDelete();
$table->string('full_name')->nullable();
$table->string('company')->nullable();
$table->string('status'); // allowed, requires_approval, blacklisted
$table->text('reason')->nullable();
$table->string('created_by')->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::create('frontdesk_audit_logs', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->nullable()->constrained('frontdesk_organizations')->nullOnDelete();
$table->string('actor_ref')->nullable();
$table->string('action');
$table->string('subject_type')->nullable();
$table->unsignedBigInteger('subject_id')->nullable();
$table->json('metadata')->nullable();
$table->string('ip_address')->nullable();
$table->timestamps();
$table->index(['owner_ref', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('frontdesk_audit_logs');
Schema::dropIfExists('frontdesk_watchlist_entries');
Schema::dropIfExists('frontdesk_devices');
Schema::dropIfExists('frontdesk_visits');
Schema::dropIfExists('frontdesk_visitors');
Schema::dropIfExists('frontdesk_hosts');
Schema::dropIfExists('frontdesk_members');
Schema::dropIfExists('frontdesk_reception_desks');
Schema::dropIfExists('frontdesk_buildings');
Schema::dropIfExists('frontdesk_branches');
Schema::dropIfExists('frontdesk_organizations');
}
};
@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('notifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('notifications');
}
};
@@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('frontdesk_visits', function (Blueprint $table) {
$table->string('external_ref')->nullable()->after('public_id');
$table->string('source')->nullable()->after('external_ref');
$table->json('integration_metadata')->nullable()->after('notes');
$table->index(['organization_id', 'external_ref', 'source']);
});
Schema::create('frontdesk_webhook_endpoints', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('frontdesk_organizations')->cascadeOnDelete();
$table->string('url');
$table->string('secret')->nullable();
$table->json('events')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Schema::create('frontdesk_offline_checkins', function (Blueprint $table) {
$table->id();
$table->foreignId('device_id')->nullable()->constrained('frontdesk_devices')->nullOnDelete();
$table->string('client_id')->unique();
$table->json('payload');
$table->timestamp('synced_at')->nullable();
$table->foreignId('visit_id')->nullable()->constrained('frontdesk_visits')->nullOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('frontdesk_offline_checkins');
Schema::dropIfExists('frontdesk_webhook_endpoints');
Schema::table('frontdesk_visits', function (Blueprint $table) {
$table->dropIndex(['organization_id', 'external_ref', 'source']);
$table->dropColumn(['external_ref', 'source', 'integration_metadata']);
});
}
};