feat(api): mobile API Milestone 1+2 — Sanctum auth + offline sync vertical slice
Milestone 1 (auth foundation):
- Installed laravel/sanctum; HasApiTokens on User; published config + migration.
- routes/api.php with /api/v1; Sanctum 'ability' middleware alias registered.
- AuthController: POST login (long-lived revocable device token w/ ability
mobile-sync + devices table), GET me, POST logout. New Device model/table.
Milestone 2 (vertical slice, offline-first):
- progress_updates: +uuid (client-generated) +client_updated_at.
- ProjectApiController: GET projects (accessibleBy), GET projects/{id}/bundle
(project/phases/layers/features, membership-authorized).
- SyncController: POST sync — batch ops, idempotent by uuid, per-op result
(applied/duplicate/error), server-set user_id, authz by permission+membership.
Currently handles progress_update.create.
Tests: tests/Feature/Api/MobileApiTest (9 passing) — auth, accessible projects,
bundle authz, sync apply+idempotency, permission enforcement.
Also fixed a latent schema bug: projects.reference (and external_reference_1)
existed in the live DB but had no migration — added a guarded migration so fresh
installs match production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Device extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id', 'name', 'token_id', 'app_version', 'last_seen_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'last_seen_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,12 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class ProgressUpdate extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'phase_id', 'user_id', 'progress_percent', 'comment', 'location'
|
||||
'uuid', 'phase_id', 'user_id', 'progress_percent', 'comment', 'location', 'client_updated_at'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'location' => 'array', // Store as [lat, lng]
|
||||
'client_updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function phase()
|
||||
|
||||
+3
-2
@@ -7,12 +7,13 @@ use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable, HasRoles;
|
||||
use HasFactory, Notifiable, HasRoles, HasApiTokens;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
Reference in New Issue
Block a user