feat(api): mobile API Milestones 5+6 — media upload, sync_logs idempotency, OpenAPI
Milestone 5 (media): - POST /api/v1/media — multipart upload, attaches to feature/issue/project/ phase/layer, idempotent by uuid, authz member + 'upload media'. Added uuid+client_updated_at to media. - Bundle now includes a 'media' array (URLs) for the project's project/feature/ issue attachments (delta-aware). Milestone 6 (hardening + docs): - sync_logs table/model: every applied op is logged; /sync short-circuits on a repeated op uuid -> 'duplicate' (true idempotency for updates too, not just creates). - Rate limiting on login (10/min), sync (60/min), media (120/min). - docs/openapi.yaml: OpenAPI 3 contract for the mobile team. Tests: 18 passing (added media upload idempotency + sync_logs idempotency). The mobile API (Milestones 1-6) is now feature-complete on the webapp side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?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('media', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('media', 'uuid')) {
|
||||
$table->uuid('uuid')->nullable()->unique()->after('id');
|
||||
}
|
||||
if (! Schema::hasColumn('media', 'client_updated_at')) {
|
||||
$table->timestamp('client_updated_at')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('media', function (Blueprint $table) {
|
||||
foreach (['uuid', 'client_updated_at'] as $col) {
|
||||
if (Schema::hasColumn('media', $col)) {
|
||||
$table->dropColumn($col);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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('sync_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->uuid('op_uuid')->index(); // idempotency key of the operation
|
||||
$table->string('entity');
|
||||
$table->string('op');
|
||||
$table->string('status'); // applied | duplicate | conflict | error
|
||||
$table->unsignedBigInteger('server_id')->nullable();
|
||||
$table->text('error')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// One processed result per (entity, op, op_uuid).
|
||||
$table->unique(['entity', 'op', 'op_uuid']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sync_logs');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user