chore: cleanup dead code + format with Pint
- Remove unused FeaturesController (empty stubs, no routes) - Remove ConvertSpatialFile CLI command (unused; service used in LayerManager) - Remove MigrateGeojsonToFeatures CLI command (one-shot migration, not referenced) - Remove .claude/worktrees/ (11 old agent worktrees from June) - Apply Laravel Pint formatting across 219 files (style only, no functional changes) Tests: 101 passing (319 assertions) API routes: unchanged (8 routes intact)
This commit is contained in:
@@ -17,8 +17,8 @@ class AuthController extends Controller
|
||||
public function login(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
'device_name' => ['required', 'string', 'max:255'],
|
||||
'app_version' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
@@ -39,15 +39,15 @@ class AuthController extends Controller
|
||||
Device::updateOrCreate(
|
||||
['user_id' => $user->id, 'name' => $data['device_name']],
|
||||
[
|
||||
'token_id' => $token->accessToken->id,
|
||||
'app_version' => $data['app_version'] ?? null,
|
||||
'token_id' => $token->accessToken->id,
|
||||
'app_version' => $data['app_version'] ?? null,
|
||||
'last_seen_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'token' => $token->plainTextToken,
|
||||
'user' => $this->userPayload($user),
|
||||
'user' => $this->userPayload($user),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ class AuthController extends Controller
|
||||
private function userPayload(User $user): array
|
||||
{
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'roles' => $user->getRoleNames(),
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'roles' => $user->getRoleNames(),
|
||||
'permissions' => $user->getAllPermissions()->pluck('name')->values(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,31 +13,31 @@ use App\Models\Phase;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class MediaController extends Controller
|
||||
{
|
||||
private array $map = [
|
||||
'feature' => Feature::class,
|
||||
'issue' => Issue::class,
|
||||
'issue_task' => IssueTask::class,
|
||||
'feature' => Feature::class,
|
||||
'issue' => Issue::class,
|
||||
'issue_task' => IssueTask::class,
|
||||
'issue_comment' => IssueComment::class,
|
||||
'project' => Project::class,
|
||||
'phase' => Phase::class,
|
||||
'layer' => Layer::class,
|
||||
'project' => Project::class,
|
||||
'phase' => Phase::class,
|
||||
'layer' => Layer::class,
|
||||
];
|
||||
|
||||
/** Upload a file (multipart) and attach it to a parent record. Idempotent by uuid. */
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'uuid' => ['required', 'uuid'],
|
||||
'uuid' => ['required', 'uuid'],
|
||||
'parent_entity' => ['required', Rule::in(array_keys($this->map))],
|
||||
'parent_id' => ['required', 'integer'],
|
||||
'file' => ['required', 'file', 'max:20480'], // 20 MB
|
||||
'category' => ['nullable', 'in:image,document,other'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'parent_id' => ['required', 'integer'],
|
||||
'file' => ['required', 'file', 'max:20480'], // 20 MB
|
||||
'category' => ['nullable', 'in:image,document,other'],
|
||||
'description' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
// Idempotency: same uuid already uploaded → return it.
|
||||
@@ -59,15 +59,15 @@ class MediaController extends Controller
|
||||
$mime = $file->getClientMimeType();
|
||||
|
||||
$media = $parent->media()->create([
|
||||
'uuid' => $data['uuid'],
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $path,
|
||||
'file_type' => $mime,
|
||||
'file_extension' => $file->getClientOriginalExtension(),
|
||||
'file_size' => $file->getSize(),
|
||||
'category' => $data['category'] ?? (Str::startsWith($mime, 'image/') ? 'image' : 'document'),
|
||||
'description' => $data['description'] ?? null,
|
||||
'uploaded_by' => $user->id,
|
||||
'uuid' => $data['uuid'],
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $path,
|
||||
'file_type' => $mime,
|
||||
'file_extension' => $file->getClientOriginalExtension(),
|
||||
'file_size' => $file->getSize(),
|
||||
'category' => $data['category'] ?? (Str::startsWith($mime, 'image/') ? 'image' : 'document'),
|
||||
'description' => $data['description'] ?? null,
|
||||
'uploaded_by' => $user->id,
|
||||
'client_updated_at' => $request->input('client_updated_at'),
|
||||
]);
|
||||
|
||||
@@ -77,14 +77,14 @@ class MediaController extends Controller
|
||||
private function projectOf(string $entity, $parent): ?Project
|
||||
{
|
||||
return match ($entity) {
|
||||
'project' => $parent,
|
||||
'phase' => $parent->project,
|
||||
'layer' => $parent->phase?->project,
|
||||
'feature' => $parent->layer?->phase?->project,
|
||||
'issue' => $parent->project,
|
||||
'issue_task' => $parent->issue?->project,
|
||||
'project' => $parent,
|
||||
'phase' => $parent->project,
|
||||
'layer' => $parent->phase?->project,
|
||||
'feature' => $parent->layer?->phase?->project,
|
||||
'issue' => $parent->project,
|
||||
'issue_task' => $parent->issue?->project,
|
||||
'issue_comment' => $parent->issue?->project,
|
||||
default => null,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ class MediaController extends Controller
|
||||
if (! $project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -100,12 +101,12 @@ class MediaController extends Controller
|
||||
private function payload(Media $m): array
|
||||
{
|
||||
return [
|
||||
'id' => $m->id,
|
||||
'uuid' => $m->uuid,
|
||||
'url' => $m->url,
|
||||
'name' => $m->name,
|
||||
'file_type' => $m->file_type,
|
||||
'category' => $m->category,
|
||||
'id' => $m->id,
|
||||
'uuid' => $m->uuid,
|
||||
'url' => $m->url,
|
||||
'name' => $m->name,
|
||||
'file_type' => $m->file_type,
|
||||
'category' => $m->category,
|
||||
'updated_at' => $m->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Feature;
|
||||
use App\Models\FeatureType;
|
||||
use App\Models\Inspection;
|
||||
use App\Models\InspectionTemplate;
|
||||
use App\Models\Issue;
|
||||
@@ -53,35 +54,35 @@ class ProjectApiController extends Controller
|
||||
$templates = $changed(InspectionTemplate::whereHas('projects', fn ($q) => $q->where('projects.id', $project->id)))->get();
|
||||
|
||||
$allIssueIds = Issue::withTrashed()->where('project_id', $project->id)->pluck('id');
|
||||
$issueTasks = $changed(IssueTask::whereIn('issue_id', $allIssueIds))->get();
|
||||
$issueTasks = $changed(IssueTask::whereIn('issue_id', $allIssueIds))->get();
|
||||
$issueComments = $changed(IssueComment::whereIn('issue_id', $allIssueIds))->get();
|
||||
|
||||
$featureIds = Feature::whereIn('layer_id', $allLayerIds)->pluck('id');
|
||||
$issueIds = Issue::where('project_id', $project->id)->pluck('id');
|
||||
$taskIds = IssueTask::whereIn('issue_id', $allIssueIds)->pluck('id');
|
||||
$issueIds = Issue::where('project_id', $project->id)->pluck('id');
|
||||
$taskIds = IssueTask::whereIn('issue_id', $allIssueIds)->pluck('id');
|
||||
$commentIds = IssueComment::whereIn('issue_id', $allIssueIds)->pluck('id');
|
||||
$media = $changed(Media::where(function ($q) use ($project, $featureIds, $issueIds, $taskIds, $commentIds) {
|
||||
$q->where(fn ($w) => $w->where('mediable_type', Project::class)->where('mediable_id', $project->id))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', Feature::class)->whereIn('mediable_id', $featureIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', IssueTask::class)->whereIn('mediable_id', $taskIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', IssueComment::class)->whereIn('mediable_id', $commentIds));
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', Feature::class)->whereIn('mediable_id', $featureIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', Issue::class)->whereIn('mediable_id', $issueIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', IssueTask::class)->whereIn('mediable_id', $taskIds))
|
||||
->orWhere(fn ($w) => $w->where('mediable_type', IssueComment::class)->whereIn('mediable_id', $commentIds));
|
||||
}))->get();
|
||||
|
||||
return response()->json([
|
||||
'server_time' => now()->toIso8601String(),
|
||||
'project' => $this->mapProject($project),
|
||||
'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(),
|
||||
'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(),
|
||||
'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(),
|
||||
'feature_types' => \App\Models\FeatureType::orderBy('name')->get(['id', 'name', 'color'])->values(),
|
||||
'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(),
|
||||
'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(),
|
||||
'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(),
|
||||
'server_time' => now()->toIso8601String(),
|
||||
'project' => $this->mapProject($project),
|
||||
'phases' => $phases->map(fn ($p) => $this->mapPhase($p))->values(),
|
||||
'layers' => $layers->map(fn ($l) => $this->mapLayer($l))->values(),
|
||||
'features' => $features->map(fn ($f) => $this->mapFeature($f))->values(),
|
||||
'feature_types' => FeatureType::orderBy('name')->get(['id', 'name', 'color'])->values(),
|
||||
'inspections' => $inspections->map(fn ($i) => $this->mapInspection($i))->values(),
|
||||
'issues' => $issues->map(fn ($i) => $this->mapIssue($i))->values(),
|
||||
'issue_tasks' => $issueTasks->map(fn ($t) => $this->mapIssueTask($t))->values(),
|
||||
'issue_comments' => $issueComments->map(fn ($c) => $this->mapIssueComment($c))->values(),
|
||||
'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(),
|
||||
'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(),
|
||||
'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds, $allIssueIds) : (object) [],
|
||||
'templates' => $templates->map(fn ($t) => $this->mapTemplate($t))->values(),
|
||||
'media' => $media->map(fn ($m) => $this->mapMedia($m))->values(),
|
||||
'deleted' => $since ? $this->tombstones($since, $project, $allPhaseIds, $allLayerIds, $allIssueIds) : (object) [],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -118,12 +119,12 @@ class ProjectApiController extends Controller
|
||||
private function tombstones(Carbon $since, Project $project, $allPhaseIds, $allLayerIds, $allIssueIds): array
|
||||
{
|
||||
return [
|
||||
'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'issue_tasks' => IssueTask::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'phases' => Phase::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'layers' => Layer::onlyTrashed()->whereIn('phase_id', $allPhaseIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'features' => Feature::onlyTrashed()->whereIn('layer_id', $allLayerIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'inspections' => Inspection::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'issues' => Issue::onlyTrashed()->where('project_id', $project->id)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'issue_tasks' => IssueTask::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
'issue_comments' => IssueComment::onlyTrashed()->whereIn('issue_id', $allIssueIds)->where('deleted_at', '>', $since)->pluck('id')->values(),
|
||||
];
|
||||
}
|
||||
@@ -209,7 +210,7 @@ class ProjectApiController extends Controller
|
||||
'id' => $t->id, 'project_id' => $t->project_id, 'phase_id' => $t->phase_id,
|
||||
'name' => $t->name, 'description' => $t->description, 'fields' => $t->fields,
|
||||
'version' => $t->updated_at?->timestamp,
|
||||
'hash' => md5(json_encode($t->fields) . $t->name),
|
||||
'hash' => md5(json_encode($t->fields).$t->name),
|
||||
'updated_at' => $t->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
@@ -217,10 +218,10 @@ class ProjectApiController extends Controller
|
||||
private function mapMedia(Media $m): array
|
||||
{
|
||||
$entity = [
|
||||
Project::class => 'project',
|
||||
Feature::class => 'feature',
|
||||
Issue::class => 'issue',
|
||||
IssueTask::class => 'issue_task',
|
||||
Project::class => 'project',
|
||||
Feature::class => 'feature',
|
||||
Issue::class => 'issue',
|
||||
IssueTask::class => 'issue_task',
|
||||
IssueComment::class => 'issue_comment',
|
||||
][$m->mediable_type] ?? class_basename($m->mediable_type);
|
||||
|
||||
@@ -232,4 +233,3 @@ class ProjectApiController extends Controller
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ class SyncController extends Controller
|
||||
public function sync(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'operations' => ['required', 'array'],
|
||||
'operations.*.entity' => ['required', 'string'],
|
||||
'operations.*.op' => ['required', 'string'],
|
||||
'operations.*.uuid' => ['required', 'uuid'],
|
||||
'operations.*.data' => ['required', 'array'],
|
||||
'operations.*.client_updated_at' => ['nullable', 'date'],
|
||||
'operations' => ['required', 'array'],
|
||||
'operations.*.entity' => ['required', 'string'],
|
||||
'operations.*.op' => ['required', 'string'],
|
||||
'operations.*.uuid' => ['required', 'uuid'],
|
||||
'operations.*.data' => ['required', 'array'],
|
||||
'operations.*.client_updated_at' => ['nullable', 'date'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
@@ -74,16 +74,16 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
try {
|
||||
$result = match ($op['entity'] . '.' . $op['op']) {
|
||||
$result = match ($op['entity'].'.'.$op['op']) {
|
||||
'progress_update.create' => $this->progressUpdateCreate($user, $uuid, $op),
|
||||
'inspection.create' => $this->inspectionCreate($user, $uuid, $op),
|
||||
'issue.create' => $this->issueCreate($user, $uuid, $op),
|
||||
'issue.update' => $this->issueUpdate($user, $uuid, $op),
|
||||
'issue_task.create' => $this->issueTaskCreate($user, $uuid, $op),
|
||||
'issue_task.update' => $this->issueTaskUpdate($user, $uuid, $op),
|
||||
'issue_comment.create' => $this->issueCommentCreate($user, $uuid, $op),
|
||||
'feature.update' => $this->featureUpdate($user, $uuid, $op),
|
||||
default => $this->error($uuid, 'unsupported entity/op: ' . $op['entity'] . '.' . $op['op']),
|
||||
'inspection.create' => $this->inspectionCreate($user, $uuid, $op),
|
||||
'issue.create' => $this->issueCreate($user, $uuid, $op),
|
||||
'issue.update' => $this->issueUpdate($user, $uuid, $op),
|
||||
'issue_task.create' => $this->issueTaskCreate($user, $uuid, $op),
|
||||
'issue_task.update' => $this->issueTaskUpdate($user, $uuid, $op),
|
||||
'issue_comment.create' => $this->issueCommentCreate($user, $uuid, $op),
|
||||
'feature.update' => $this->featureUpdate($user, $uuid, $op),
|
||||
default => $this->error($uuid, 'unsupported entity/op: '.$op['entity'].'.'.$op['op']),
|
||||
};
|
||||
} catch (\Throwable $e) {
|
||||
$result = $this->error($uuid, $e->getMessage());
|
||||
@@ -92,11 +92,11 @@ class SyncController extends Controller
|
||||
// Record only terminal successes so conflicts/errors can be safely retried.
|
||||
if ($result['status'] === 'applied') {
|
||||
SyncLog::create([
|
||||
'user_id' => $user->id,
|
||||
'op_uuid' => $uuid,
|
||||
'entity' => $op['entity'],
|
||||
'op' => $op['op'],
|
||||
'status' => 'applied',
|
||||
'user_id' => $user->id,
|
||||
'op_uuid' => $uuid,
|
||||
'entity' => $op['entity'],
|
||||
'op' => $op['op'],
|
||||
'status' => 'applied',
|
||||
'server_id' => $result['server_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
@@ -115,11 +115,11 @@ class SyncController extends Controller
|
||||
$v = Validator::make($op['data'], [
|
||||
'phase_id' => ['required', 'integer', 'exists:phases,id'],
|
||||
'progress' => ['required', 'integer', 'min:0', 'max:100'],
|
||||
'comment' => ['nullable', 'string'],
|
||||
'comment' => ['nullable', 'string'],
|
||||
'location' => ['nullable', 'array'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -129,12 +129,12 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$pu = ProgressUpdate::create([
|
||||
'uuid' => $uuid,
|
||||
'phase_id' => $phase->id,
|
||||
'user_id' => $user->id,
|
||||
'progress_percent' => $d['progress'],
|
||||
'comment' => $d['comment'] ?? null,
|
||||
'location' => $d['location'] ?? null,
|
||||
'uuid' => $uuid,
|
||||
'phase_id' => $phase->id,
|
||||
'user_id' => $user->id,
|
||||
'progress_percent' => $d['progress'],
|
||||
'comment' => $d['comment'] ?? null,
|
||||
'location' => $d['location'] ?? null,
|
||||
'client_updated_at' => $op['client_updated_at'] ?? null,
|
||||
]);
|
||||
|
||||
@@ -153,15 +153,15 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$v = Validator::make($op['data'], [
|
||||
'feature_id' => ['required', 'integer', 'exists:features,id'],
|
||||
'feature_id' => ['required', 'integer', 'exists:features,id'],
|
||||
'template_id' => ['nullable', 'integer', 'exists:inspection_templates,id'],
|
||||
'data' => ['nullable', 'array'],
|
||||
'status' => ['nullable', 'string'],
|
||||
'result' => ['nullable', 'string'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'data' => ['nullable', 'array'],
|
||||
'status' => ['nullable', 'string'],
|
||||
'result' => ['nullable', 'string'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -172,16 +172,16 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$inspection = Inspection::create([
|
||||
'uuid' => $uuid,
|
||||
'project_id' => $project->id,
|
||||
'layer_id' => $feature->layer_id,
|
||||
'feature_id' => $feature->id,
|
||||
'template_id' => $d['template_id'] ?? null,
|
||||
'user_id' => $user->id,
|
||||
'data' => $d['data'] ?? [],
|
||||
'status' => $d['status'] ?? 'completed',
|
||||
'result' => $d['result'] ?? null,
|
||||
'notes' => $d['notes'] ?? null,
|
||||
'uuid' => $uuid,
|
||||
'project_id' => $project->id,
|
||||
'layer_id' => $feature->layer_id,
|
||||
'feature_id' => $feature->id,
|
||||
'template_id' => $d['template_id'] ?? null,
|
||||
'user_id' => $user->id,
|
||||
'data' => $d['data'] ?? [],
|
||||
'status' => $d['status'] ?? 'completed',
|
||||
'result' => $d['result'] ?? null,
|
||||
'notes' => $d['notes'] ?? null,
|
||||
'client_updated_at' => $op['client_updated_at'] ?? null,
|
||||
]);
|
||||
|
||||
@@ -197,16 +197,16 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$v = Validator::make($op['data'], [
|
||||
'project_id' => ['required', 'integer', 'exists:projects,id'],
|
||||
'feature_id' => ['nullable', 'integer', 'exists:features,id'],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'project_id' => ['required', 'integer', 'exists:projects,id'],
|
||||
'feature_id' => ['nullable', 'integer', 'exists:features,id'],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'priority' => ['nullable', 'in:' . implode(',', Issue::PRIORITIES)],
|
||||
'status' => ['nullable', 'in:' . implode(',', Issue::STATUSES)],
|
||||
'type' => ['nullable', 'in:' . implode(',', Issue::TYPES)],
|
||||
'priority' => ['nullable', 'in:'.implode(',', Issue::PRIORITIES)],
|
||||
'status' => ['nullable', 'in:'.implode(',', Issue::STATUSES)],
|
||||
'type' => ['nullable', 'in:'.implode(',', Issue::TYPES)],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -216,15 +216,15 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$issue = Issue::create([
|
||||
'uuid' => $uuid,
|
||||
'project_id' => $project->id,
|
||||
'feature_id' => $d['feature_id'] ?? null,
|
||||
'title' => $d['title'],
|
||||
'description' => $d['description'] ?? null,
|
||||
'priority' => $d['priority'] ?? 'medium',
|
||||
'status' => $d['status'] ?? 'open',
|
||||
'type' => $d['type'] ?? 'other',
|
||||
'reported_by' => $user->id,
|
||||
'uuid' => $uuid,
|
||||
'project_id' => $project->id,
|
||||
'feature_id' => $d['feature_id'] ?? null,
|
||||
'title' => $d['title'],
|
||||
'description' => $d['description'] ?? null,
|
||||
'priority' => $d['priority'] ?? 'medium',
|
||||
'status' => $d['status'] ?? 'open',
|
||||
'type' => $d['type'] ?? 'other',
|
||||
'reported_by' => $user->id,
|
||||
'client_updated_at' => $op['client_updated_at'] ?? null,
|
||||
]);
|
||||
|
||||
@@ -234,17 +234,17 @@ class SyncController extends Controller
|
||||
private function issueUpdate(User $user, string $uuid, array $op): array
|
||||
{
|
||||
$v = Validator::make($op['data'], [
|
||||
'id' => ['required', 'integer', 'exists:issues,id'],
|
||||
'title' => ['nullable', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'priority' => ['nullable', 'in:' . implode(',', Issue::PRIORITIES)],
|
||||
'status' => ['nullable', 'in:' . implode(',', Issue::STATUSES)],
|
||||
'type' => ['nullable', 'in:' . implode(',', Issue::TYPES)],
|
||||
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'id' => ['required', 'integer', 'exists:issues,id'],
|
||||
'title' => ['nullable', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'priority' => ['nullable', 'in:'.implode(',', Issue::PRIORITIES)],
|
||||
'status' => ['nullable', 'in:'.implode(',', Issue::STATUSES)],
|
||||
'type' => ['nullable', 'in:'.implode(',', Issue::TYPES)],
|
||||
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'resolution_notes' => ['nullable', 'string'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -276,14 +276,14 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$v = Validator::make($op['data'], [
|
||||
'issue_id' => ['required', 'integer', 'exists:issues,id'],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'issue_id' => ['required', 'integer', 'exists:issues,id'],
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'due_date' => ['nullable', 'date'],
|
||||
'is_done' => ['nullable', 'boolean'],
|
||||
'due_date' => ['nullable', 'date'],
|
||||
'is_done' => ['nullable', 'boolean'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -294,15 +294,15 @@ class SyncController extends Controller
|
||||
|
||||
$done = $d['is_done'] ?? false;
|
||||
$task = IssueTask::create([
|
||||
'uuid' => $uuid,
|
||||
'issue_id' => $issue->id,
|
||||
'title' => $d['title'],
|
||||
'assigned_to' => $d['assigned_to'] ?? null,
|
||||
'due_date' => $d['due_date'] ?? null,
|
||||
'is_done' => $done,
|
||||
'done_at' => $done ? now() : null,
|
||||
'done_by' => $done ? $user->id : null,
|
||||
'order' => ((int) $issue->tasks()->max('order')) + 1,
|
||||
'uuid' => $uuid,
|
||||
'issue_id' => $issue->id,
|
||||
'title' => $d['title'],
|
||||
'assigned_to' => $d['assigned_to'] ?? null,
|
||||
'due_date' => $d['due_date'] ?? null,
|
||||
'is_done' => $done,
|
||||
'done_at' => $done ? now() : null,
|
||||
'done_by' => $done ? $user->id : null,
|
||||
'order' => ((int) $issue->tasks()->max('order')) + 1,
|
||||
'client_updated_at' => $op['client_updated_at'] ?? null,
|
||||
]);
|
||||
|
||||
@@ -312,14 +312,14 @@ class SyncController extends Controller
|
||||
private function issueTaskUpdate(User $user, string $uuid, array $op): array
|
||||
{
|
||||
$v = Validator::make($op['data'], [
|
||||
'id' => ['required', 'integer', 'exists:issue_tasks,id'],
|
||||
'title' => ['nullable', 'string', 'max:255'],
|
||||
'id' => ['required', 'integer', 'exists:issue_tasks,id'],
|
||||
'title' => ['nullable', 'string', 'max:255'],
|
||||
'assigned_to' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'due_date' => ['nullable', 'date'],
|
||||
'is_done' => ['nullable', 'boolean'],
|
||||
'due_date' => ['nullable', 'date'],
|
||||
'is_done' => ['nullable', 'boolean'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -354,10 +354,10 @@ class SyncController extends Controller
|
||||
|
||||
$v = Validator::make($op['data'], [
|
||||
'issue_id' => ['required', 'integer', 'exists:issues,id'],
|
||||
'body' => ['required', 'string', 'max:5000'],
|
||||
'body' => ['required', 'string', 'max:5000'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -367,10 +367,10 @@ class SyncController extends Controller
|
||||
}
|
||||
|
||||
$comment = IssueComment::create([
|
||||
'uuid' => $uuid,
|
||||
'issue_id' => $issue->id,
|
||||
'user_id' => $user->id,
|
||||
'body' => $d['body'],
|
||||
'uuid' => $uuid,
|
||||
'issue_id' => $issue->id,
|
||||
'user_id' => $user->id,
|
||||
'body' => $d['body'],
|
||||
'client_updated_at' => $op['client_updated_at'] ?? null,
|
||||
]);
|
||||
|
||||
@@ -382,15 +382,15 @@ class SyncController extends Controller
|
||||
private function featureUpdate(User $user, string $uuid, array $op): array
|
||||
{
|
||||
$v = Validator::make($op['data'], [
|
||||
'id' => ['required', 'integer', 'exists:features,id'],
|
||||
'status' => ['nullable', 'string'],
|
||||
'progress' => ['nullable', 'integer', 'min:0', 'max:100'],
|
||||
'responsible' => ['nullable', 'string'],
|
||||
'is_active' => ['nullable', 'boolean'],
|
||||
'id' => ['required', 'integer', 'exists:features,id'],
|
||||
'status' => ['nullable', 'string'],
|
||||
'progress' => ['nullable', 'integer', 'min:0', 'max:100'],
|
||||
'responsible' => ['nullable', 'string'],
|
||||
'is_active' => ['nullable', 'boolean'],
|
||||
'feature_type_id' => ['nullable', 'integer', 'exists:feature_types,id'],
|
||||
]);
|
||||
if ($v->fails()) {
|
||||
return $this->error($uuid, 'validation: ' . $v->errors()->first());
|
||||
return $this->error($uuid, 'validation: '.$v->errors()->first());
|
||||
}
|
||||
$d = $v->validated();
|
||||
|
||||
@@ -424,6 +424,7 @@ class SyncController extends Controller
|
||||
if (! $project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user->can('manage all')
|
||||
|| $project->users()->where('user_id', $user->id)->exists();
|
||||
}
|
||||
@@ -440,11 +441,12 @@ class SyncController extends Controller
|
||||
$clientAt = Carbon::parse($op['client_updated_at']);
|
||||
if ($model->updated_at && $model->updated_at->gt($clientAt)) {
|
||||
return [
|
||||
'uuid' => $uuid,
|
||||
'uuid' => $uuid,
|
||||
'status' => 'conflict',
|
||||
'server' => $model->fresh()->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\features;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FeaturesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(features $features)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(features $features)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, features $features)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(features $features)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
@@ -26,7 +24,7 @@ class ProfileController extends Controller
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255|unique:users,email,' . $user->id,
|
||||
'email' => 'required|email|max:255|unique:users,email,'.$user->id,
|
||||
]);
|
||||
|
||||
$user->update($validated);
|
||||
@@ -52,4 +50,4 @@ class ProfileController extends Controller
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
@@ -16,6 +15,7 @@ class ProjectController extends Controller
|
||||
public function index()
|
||||
{
|
||||
Gate::authorize('view projects');
|
||||
|
||||
return view('projects.index');
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ class ProjectController extends Controller
|
||||
|
||||
// Assign creator as supervisor in project
|
||||
$project->users()->attach(Auth::id(), ['role_in_project' => 'supervisor']);
|
||||
|
||||
return redirect()->route('projects.map', $project)->with('success', 'Proyecto creado');
|
||||
}
|
||||
|
||||
@@ -94,4 +95,4 @@ class ProjectController extends Controller
|
||||
// (lo validaremos dentro del componente Livewire)
|
||||
return view('projects.map', compact('project'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Inspection;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ProjectReportController extends Controller
|
||||
@@ -10,7 +12,7 @@ class ProjectReportController extends Controller
|
||||
public function show(Project $project)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user->can('manage all') && !$project->users()->where('user_id', $user->id)->exists()) {
|
||||
if (! $user->can('manage all') && ! $project->users()->where('user_id', $user->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
@@ -20,11 +22,11 @@ class ProjectReportController extends Controller
|
||||
->get();
|
||||
|
||||
$stats = [
|
||||
'total_features' => $phases->flatMap(fn($p) => $p->layers)->flatMap(fn($l) => $l->features)->count(),
|
||||
'completed_features' => $phases->flatMap(fn($p) => $p->layers)->flatMap(fn($l) => $l->features)->where('status', 'completed')->count(),
|
||||
'total_inspections' => \App\Models\Inspection::where('project_id', $project->id)->count(),
|
||||
'open_issues' => \App\Models\Issue::where('project_id', $project->id)->where('status', 'open')->count(),
|
||||
'avg_progress' => round($phases->avg('progress_percent') ?? 0),
|
||||
'total_features' => $phases->flatMap(fn ($p) => $p->layers)->flatMap(fn ($l) => $l->features)->count(),
|
||||
'completed_features' => $phases->flatMap(fn ($p) => $p->layers)->flatMap(fn ($l) => $l->features)->where('status', 'completed')->count(),
|
||||
'total_inspections' => Inspection::where('project_id', $project->id)->count(),
|
||||
'open_issues' => Issue::where('project_id', $project->id)->where('status', 'open')->count(),
|
||||
'avg_progress' => round($phases->avg('progress_percent') ?? 0),
|
||||
];
|
||||
|
||||
$pdf_data = compact('project', 'phases', 'stats');
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DTO\ReportFilters;
|
||||
use App\Exports\ProjectReportExport;
|
||||
use App\Models\Project;
|
||||
use App\Services\ReportGenerator;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\ProjectReportExport;
|
||||
|
||||
class ReportController extends Controller
|
||||
{
|
||||
@@ -19,7 +19,7 @@ class ReportController extends Controller
|
||||
{
|
||||
$this->authorizeProjectAccess($project);
|
||||
|
||||
return \Livewire\Livewire::mount(\App\Livewire\Reports\ReportBuilder::class, [
|
||||
return view('reports.builder', [
|
||||
'project' => $project,
|
||||
]);
|
||||
}
|
||||
@@ -65,8 +65,8 @@ class ReportController extends Controller
|
||||
protected function downloadExcel(Project $project, ReportFilters $filters, array $data)
|
||||
{
|
||||
$export = new ProjectReportExport($project, $filters, $data);
|
||||
$filename = 'informe_' . $project->name . '_' . now()->format('Ymd_His') . '.xlsx';
|
||||
|
||||
$filename = 'informe_'.$project->name.'_'.now()->format('Ymd_His').'.xlsx';
|
||||
|
||||
return Excel::download($export, $filename);
|
||||
}
|
||||
|
||||
@@ -76,13 +76,13 @@ class ReportController extends Controller
|
||||
protected function authorizeProjectAccess(Project $project): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
|
||||
if ($user->can('manage all')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$project->users()->where('user_id', $user->id)->exists()) {
|
||||
if (! $project->users()->where('user_id', $user->id)->exists()) {
|
||||
abort(403, 'No tienes acceso a este proyecto.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use App\Models\Phase;
|
||||
use App\Models\Inspection;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\ProjectsExport;
|
||||
use App\Exports\PhasesExport;
|
||||
use App\Exports\InspectionsExport;
|
||||
use App\Exports\PhasesExport;
|
||||
use App\Exports\ProjectsExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class ExportController extends Controller
|
||||
{
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Project;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Project;
|
||||
|
||||
class BindProjectModel
|
||||
{
|
||||
@@ -18,6 +18,7 @@ class BindProjectModel
|
||||
$route->setParameter('project', $project);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class SetLocale
|
||||
}
|
||||
|
||||
// 2. From session
|
||||
if (!$locale && Session::has('locale')) {
|
||||
if (! $locale && Session::has('locale')) {
|
||||
$sessionLocale = Session::get('locale');
|
||||
if (in_array($sessionLocale, $allowedLocales)) {
|
||||
$locale = $sessionLocale;
|
||||
@@ -35,7 +35,7 @@ class SetLocale
|
||||
}
|
||||
|
||||
// 3. From browser Accept-Language
|
||||
if (!$locale) {
|
||||
if (! $locale) {
|
||||
$browserLang = substr($request->server('HTTP_ACCEPT_LANGUAGE', 'en'), 0, 2);
|
||||
if (in_array($browserLang, $allowedLocales)) {
|
||||
$locale = $browserLang;
|
||||
@@ -43,7 +43,7 @@ class SetLocale
|
||||
}
|
||||
|
||||
// 4. Default to app locale
|
||||
if (!$locale) {
|
||||
if (! $locale) {
|
||||
$locale = config('app.locale', 'en');
|
||||
}
|
||||
|
||||
@@ -52,4 +52,4 @@ class SetLocale
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user