validatedCatalog($catalog); $rules = [ 'q' => ['nullable', 'string', 'max:100'], 'version' => ['nullable', 'integer', 'min:1'], 'page' => ['nullable', 'integer', 'min:1'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], 'parent_id' => ['nullable', 'uuid'], 'jurisdiction_id' => ['nullable', 'uuid'], ]; if ($catalog === 'jurisdictions') { $rules['type'] = ['required', 'in:country,province,district,admin_post,locality']; $rules['parent_id'][] = 'prohibited_if:type,country'; $rules['parent_id'][] = 'required_if:type,district,admin_post,locality'; } if ($catalog === 'economic-activities') { $rules['sector_id'] = ['nullable', 'integer', 'exists:sectors,id']; } $data = $request->validate($rules); $versionQuery = DB::table('reference_catalog_versions')->where('catalog_code', $catalog)->where('locale', 'pt-MZ'); if (isset($data['version'])) { $versionQuery->where('version_no', $data['version'])->whereIn('status', ['published', 'retired']); } else { $versionQuery->where('status', 'published') ->where(fn (Builder $query) => $query->whereNull('valid_from')->orWhere('valid_from', '<=', today()->toDateString())) ->where(fn (Builder $query) => $query->whereNull('valid_to')->orWhere('valid_to', '>', today()->toDateString())); } $version = $versionQuery->orderByDesc('version_no')->first(); if (isset($data['version']) && ! $version) { abort(404, 'Versão publicada do catálogo não encontrada.'); } if (! $version) { return response()->json([ 'data' => [], 'links' => ['next' => null, 'previous' => null], 'next_page_url' => null, 'prev_page_url' => null, 'current_page' => 1, 'per_page' => $data['per_page'] ?? 20, 'total' => 0, 'last_page' => 1, 'meta' => ['catalog' => $catalog, 'version' => null, 'locale' => 'pt-MZ', 'status' => 'not_published', 'current_page' => 1, 'per_page' => $data['per_page'] ?? 20, 'total' => 0, 'last_page' => 1], ])->header('Cache-Control', 'public, max-age=60'); } $query = match ($catalog) { 'authorities' => $this->authorities((string) $version->id, $data), 'jurisdictions' => $this->jurisdictions($data), 'economic-activities' => $this->economicActivities((string) $version->id, $data), }; $page = $query->orderBy('code')->orderBy('id')->paginate($data['per_page'] ?? 20)->withQueryString(); $items = collect($page->items())->map(fn (object $row): array => $this->present($catalog, (array) $row))->values(); return response()->json([ 'data' => $items, 'links' => ['next' => $page->nextPageUrl(), 'previous' => $page->previousPageUrl()], 'next_page_url' => $page->nextPageUrl(), 'prev_page_url' => $page->previousPageUrl(), 'current_page' => $page->currentPage(), 'per_page' => $page->perPage(), 'total' => $page->total(), 'last_page' => $page->lastPage(), 'meta' => ['catalog' => $catalog, 'version' => (int) $version->version_no, 'locale' => $version->locale, 'source_version' => $version->source_version, 'valid_from' => $version->valid_from, 'valid_to' => $version->valid_to, 'current_page' => $page->currentPage(), 'per_page' => $page->perPage(), 'total' => $page->total(), 'last_page' => $page->lastPage()], ])->header('Cache-Control', 'public, max-age=300') ->header('ETag', 'W/"catalog-'.substr(hash('sha256', $catalog.'|'.$version->version_no.'|'.json_encode($data, JSON_THROW_ON_ERROR)), 0, 32).'"'); } /** @param array $data */ private function authorities(string $versionId, array $data): Builder { $query = DB::table('authorities as a')->join('organizations as o', 'o.id', '=', 'a.organization_id') ->leftJoin('geo_locations as g', 'g.id', '=', 'a.jurisdiction_id') ->where('a.catalog_version_id', $versionId)->where('a.status', 'published') ->where('o.data_classification', 'public')->whereNull('o.deleted_at') ->where('a.valid_from', '<=', today()->toDateString()) ->where(fn (Builder $builder) => $builder->whereNull('a.valid_to')->orWhere('a.valid_to', '>', today()->toDateString())) ->select(['a.id', 'a.code', 'a.authority_type', 'a.label_pt_mz as name', 'a.organization_id', 'a.jurisdiction_id', 'g.name as jurisdiction_name', 'a.version_no']); if (! empty($data['jurisdiction_id'])) { $query->where('a.jurisdiction_id', $data['jurisdiction_id']); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['a.code', 'a.label_pt_mz', 'o.name']); } return $query; } /** @param array $data */ private function jurisdictions(array $data): Builder { $query = DB::table('geo_locations')->where('source_system', 'cartografia-workbook') ->where('data_classification', 'public')->whereNull('deleted_at')->where('location_type', $data['type']) ->select(['id', 'code', 'name', 'parent_id', 'location_type', 'source_code', 'version_no']); $parentType = ['province' => 'country', 'district' => 'province', 'admin_post' => 'district', 'locality' => 'admin_post'][$data['type']] ?? null; if ($data['type'] !== 'country' && ! empty($data['parent_id'])) { $validParent = DB::table('geo_locations')->where('id', $data['parent_id'])->where('location_type', $parentType) ->where('source_system', 'cartografia-workbook')->where('data_classification', 'public')->whereNull('deleted_at')->exists(); if (! $validParent) { throw ValidationException::withMessages(['parent_id' => 'Seleccione um território pai activo do nível anterior.']); } $query->where('parent_id', $data['parent_id']); } elseif (in_array($data['type'], ['district', 'admin_post', 'locality'], true)) { throw ValidationException::withMessages(['parent_id' => 'Seleccione primeiro o território pai.']); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['code', 'name', 'source_code']); } return $query; } /** @param array $data */ private function economicActivities(string $versionId, array $data): Builder { $query = DB::table('economic_activities')->where('catalog_version_id', $versionId)->where('status', 'published') ->where('valid_from', '<=', today()->toDateString()) ->where(fn (Builder $builder) => $builder->whereNull('valid_to')->orWhere('valid_to', '>', today()->toDateString())) ->select(['id', 'code', 'label_pt_mz as name', 'parent_id', 'sector_id', 'version_no']); if (array_key_exists('parent_id', $data) && $data['parent_id'] !== null) { $query->where('parent_id', $data['parent_id']); } else { $query->whereNull('parent_id'); } if (! empty($data['sector_id'])) { $query->where('sector_id', $data['sector_id']); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['code', 'label_pt_mz']); } return $query; } /** @param list $columns */ private function search(Builder $query, string $search, array $columns): void { $term = '%'.addcslashes($search, '%_\\').'%'; $query->where(function (Builder $nested) use ($columns, $term): void { foreach ($columns as $index => $column) { if ($index === 0) { $nested->whereRaw($column.' ILIKE ?', [$term]); } else { $nested->orWhereRaw($column.' ILIKE ?', [$term]); } } }); } /** @return 'authorities'|'jurisdictions'|'economic-activities' */ private function validatedCatalog(string $catalog): string { abort_unless(in_array($catalog, self::CATALOGS, true), 404); return $catalog; } /** * @param 'authorities'|'jurisdictions'|'economic-activities' $catalog * @param array $row * @return array */ private function present(string $catalog, array $row): array { return match ($catalog) { 'jurisdictions' => ['id' => $row['id'], 'value' => $row['id'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'type' => $row['location_type'], 'source_code' => $row['source_code'], 'version_no' => $row['version_no']], 'economic-activities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'sector_id' => $row['sector_id'], 'version_no' => $row['version_no']], 'authorities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'authority_type' => $row['authority_type'], 'organization_id' => $row['organization_id'], 'jurisdiction_id' => $row['jurisdiction_id'], 'jurisdiction_name' => $row['jurisdiction_name'], 'version_no' => $row['version_no']], }; } }