Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TM-941] Audit Log request support #275

Merged
merged 10 commits into from
Jun 12, 2024
16 changes: 16 additions & 0 deletions app/Helpers/GeometryHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\V2\PolygonGeometry;
use App\Models\V2\Projects\Project;
use App\Models\V2\Sites\CriteriaSite;
use Illuminate\Support\Facades\Log;

class GeometryHelper
Expand Down Expand Up @@ -117,4 +118,19 @@ public static function getPolygonsBbox($polygonsIds)

return [$minX, $minY, $maxX, $maxY];
}

public static function getCriteriaDataForPolygonGeometry($polygonGeometry)
{
return CriteriaSite::whereIn(
'id',
$polygonGeometry
->criteriaSite()
->groupBy('criteria_id')
->selectRaw('max(id) as latest_id')
)->get([
'criteria_id',
'valid',
'created_at as latest_created_at',
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace App\Http\Controllers\V2\Auditable;

use App\Helpers\GeometryHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\V2\AuditStatus\AuditStatusUpdateRequest;
use App\Models\Traits\SaveAuditStatusTrait;
use App\Models\V2\AuditableModel;
use App\Models\V2\AuditStatus\AuditStatus;
use App\Models\V2\Sites\SitePolygon;
use App\Services\PolygonService;

class UpdateAuditableStatusController extends Controller
{
use SaveAuditStatusTrait;

public function __invoke(AuditStatusUpdateRequest $request, AuditableModel $auditable)
{
$this->authorize('update', $auditable);

if (! $this->canChangeStatus($auditable, $request->status)) {
return response()->json(['message' => 'Cannot change status'], 400);
}

$body = $request->all();
$status = $body['status'];

$auditable->status = $status;
$auditable->save();

if (isset($body['status'])) {
$this->saveAuditStatus(get_class($auditable), $auditable->id, $status, $body['comment'], $body['type']);
} elseif (isset($body['is_active'])) {
AuditStatus::where('auditable_id', $auditable->id)
->where('type', $body['type'])
->where('is_active', true)
->update(['is_active' => false]);
$this->saveAuditStatus(get_class($auditable), $auditable->id, $status, $body['comment'], $body['type'], $body['is_active'], $body['request_removed']);
}

return $auditable;
}

private function canChangeStatus($auditable, $status): bool
{
switch(get_class($auditable)) {
case 'App\Models\V2\Sites\Site':
return $this->canChangeSiteStatusTo($auditable, $status);
case 'App\Models\V2\Sites\SitePolygon':
return $this->canChangeSitePolygonStatusTo($auditable, $status);
default:
return true;
}
}

private function canChangeSiteStatusTo($auditable, $status)
{
if ($status === 'approved') {
return ! SitePolygon::where('site_id', $auditable->id)->where('status', 'approved')->exists();
}

return true;
}

private function canChangeSitePolygonStatusTo($sitePolygon, $status)
{
if ($status === 'approved') {
$geometry = $sitePolygon->polygonGeometry()->first();

if ($geometry === null) {
return false;
}

$criteriaList = GeometryHelper::getCriteriaDataForPolygonGeometry($geometry);

if (empty($criteriaList)) {
return false;
}

$criteriaList = array_filter($criteriaList, function ($criteria) {
return $criteria['criteria_id'] !== PolygonService::ESTIMATED_AREA_CRITERIA_ID;
});

$canApprove = true;
foreach ($criteriaList as $criteria) {
if (! $criteria['valid']) {
$canApprove = false;

break;
}
}

return $canApprove;
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace App\Http\Controllers\V2\Terrafund;

use App\Helpers\GeometryHelper;
use App\Http\Controllers\Controller;
use App\Models\V2\PolygonGeometry;
use App\Models\V2\Sites\CriteriaSite;
use App\Models\V2\Sites\SitePolygon;
use App\Models\V2\WorldCountryGeneralized;
use App\Services\PolygonService;
Expand Down Expand Up @@ -313,30 +313,12 @@ public function getCriteriaData(Request $request)
return response()->json(['error' => 'Polygon not found for the given UUID'], 404);
}

// Fetch data from criteria_site with distinct criteria_id based on the latest created_at
$criteriaDataQuery = 'SELECT criteria_id, MAX(created_at) AS latest_created_at
FROM criteria_site
WHERE polygon_id = ?
GROUP BY criteria_id';
$criteriaList = GeometryHelper::getCriteriaDataForPolygonGeometry($geometry);

$criteriaData = DB::select($criteriaDataQuery, [$uuid]);

if (empty($criteriaData)) {
if (empty($criteriaList)) {
return response()->json(['error' => 'Criteria data not found for the given polygon ID'], 404);
}

// Determine the validity of each criteria
$criteriaList = [];
foreach ($criteriaData as $criteria) {
$criteriaId = $criteria->criteria_id;
$valid = CriteriaSite::where(['polygon_id' => $uuid, 'criteria_id' => $criteriaId])->orderBy('created_at', 'desc')->select('valid')->first()?->valid;
$criteriaList[] = [
'criteria_id' => $criteriaId,
'latest_created_at' => $criteria->latest_created_at,
'valid' => $valid,
];
}

return response()->json(['polygon_id' => $uuid, 'criteria_list' => $criteriaList]);
}

Expand Down
24 changes: 24 additions & 0 deletions app/Http/Requests/V2/AuditStatus/AuditStatusUpdateRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\Requests\V2\AuditStatus;

use Illuminate\Foundation\Http\FormRequest;

class AuditStatusUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => 'sometimes|nullable|string',
'comment' => 'sometimes|nullable|string',
'status' => 'sometimes|nullable|string',
'is_active' => 'sometimes|nullable|boolean',
'request_removed' => 'sometimes|nullable|boolean',
];
}
}
27 changes: 27 additions & 0 deletions app/Policies/V2/Sites/SitePolygonPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Policies\V2\Sites;

use App\Models\User;
use App\Models\V2\Sites\Site;
use App\Models\V2\Sites\SitePolygon;
use App\Policies\Policy;

class SitePolygonPolicy extends Policy
{
public function update(?User $user, ?SitePolygon $sitePolygon = null): bool
{
$site = $sitePolygon->site()->first();

if ($user->can('framework-' . $site->framework_key)) {
return true;
}

return $user->can('manage-own') && $this->isTheirs($user, $site);
}

protected function isTheirs(?User $user, ?Site $site = null): bool
{
return $user->organisation_id == $site->project->organisation_id || $user->projects->contains($site->project_id);
}
}
13 changes: 13 additions & 0 deletions openapi-src/V2/definitions/AuditStatusUpdateRequest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
title: AuditStatusUpdateRequest
type: object
properties:
type:
type: string
comment:
type: string
status:
type: string
is_active:
type: boolean
request_removed:
type: boolean
2 changes: 2 additions & 0 deletions openapi-src/V2/definitions/_index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ GeojsonData:
$ref: './GeojsonData.yml'
EntityTypeResponse:
$ref: './EntityTypeResponse.yml'
AuditStatusUpdateRequest:
$ref: './AuditStatusUpdateRequest.yml'
SitePolygonResource:
$ref: './SitePolygonResource.yml'
SiteCheckApproveResponse:
Expand Down
19 changes: 19 additions & 0 deletions openapi-src/V2/paths/Entity/put-v2-entity-uuid-status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

summary: Update the status of a specific entity
tags:
- V2 Projects
- V2 Sites
- V2 SitePolygons
parameters:
- type: string
name: ENTITY
in: path
required: true
description: allowed values project/site/site-polygons
- type: string
name: UUID
in: path
required: true
responses:
'200':
description: OK
3 changes: 3 additions & 0 deletions openapi-src/V2/paths/_index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2621,6 +2621,9 @@
/v2/type-entity:
get:
$ref: './Entity/get-v2-type-entity.yml'
/v2/{ENTITY}/{UUID}/status:
put:
$ref: './Entity/put-v2-entity-uuid-status.yml'
/v2/projects/{UUID}/site-polygons/all:
get:
$ref: './Projects/get-v2-projects-uuid-site-polygons-all.yml'
Expand Down
34 changes: 34 additions & 0 deletions resources/docs/swagger-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44733,6 +44733,20 @@ definitions:
items:
type: number
description: Bounding box of the entity
AuditStatusUpdateRequest:
title: AuditStatusUpdateRequest
type: object
properties:
type:
type: string
comment:
type: string
status:
type: string
is_active:
type: boolean
request_removed:
type: boolean
SitePolygonResource:
title: SitePolygonResource
type: object
Expand Down Expand Up @@ -96170,6 +96184,26 @@ paths:
error:
type: string
description: Error message
'/v2/{ENTITY}/{UUID}/status':
put:
summary: Update the status of a specific entity
tags:
- V2 Projects
- V2 Sites
- V2 SitePolygons
parameters:
- type: string
name: ENTITY
in: path
required: true
description: allowed values project/site/site-polygons
- type: string
name: UUID
in: path
required: true
responses:
'200':
description: OK
'/v2/projects/{UUID}/site-polygons/all':
get:
summary: Get all the SitePolygons from all sites belonging to a specific project
Expand Down
5 changes: 5 additions & 0 deletions routes/api_v2.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Http\Controllers\V2\Applications\ExportApplicationController;
use App\Http\Controllers\V2\Applications\ViewApplicationController;
use App\Http\Controllers\V2\Applications\ViewMyApplicationController;
use App\Http\Controllers\V2\Auditable\UpdateAuditableStatusController;
use App\Http\Controllers\V2\Audits\AdminIndexAuditsController;
use App\Http\Controllers\V2\AuditStatus\GetAuditStatusController;
use App\Http\Controllers\V2\AuditStatus\StoreAuditStatusController;
Expand Down Expand Up @@ -682,6 +683,10 @@ function () {
Route::get('/{auditable}', GetAuditStatusController::class);
}, prefix: 'audit-status');

ModelInterfaceBindingMiddleware::with(AuditableModel::class, function () {
Route::put('/{auditable}/status', UpdateAuditableStatusController::class);
});

Route::prefix('dashboard')->group(function () {
Route::get('/restoration-strategy', ViewRestorationStrategyController::class);
Route::get('/jobs-created', GetJobsCreatedController::class);
Expand Down