mirror of
https://github.com/grokability/snipe-it.git
synced 2026-03-12 17:52:00 +08:00
Merge pull request #17492 from ischooluw/17448-feature-notes-api-endpoints
Fixes #17448: feat(api) - API endpoint for Adding Ad-Hoc Notes to Assets
This commit is contained in:
95
app/Http/Controllers/Api/NotesController.php
Normal file
95
app/Http/Controllers/Api/NotesController.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* This class controls all API actions related to notes for
|
||||
* the Snipe-IT Asset Management application.
|
||||
*/
|
||||
class NotesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Retrieve a list of manual notes (action logs) for a given asset.
|
||||
*
|
||||
* Checks authorization to view assets, attempts to find the asset by ID,
|
||||
* and fetches related action log entries of type 'note added', including
|
||||
* user information for each note. Returns a JSON response with the notes or errors.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The incoming HTTP request.
|
||||
* @param Asset $asset The ID of the asset whose notes to retrieve.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function index(Asset $asset): JsonResponse
|
||||
{
|
||||
$this->authorize('view', $asset);
|
||||
|
||||
// Get the manual notes for the asset
|
||||
$notes = ActionLog::with('user:id,username')
|
||||
->where('item_type', Asset::class)
|
||||
->where('item_id', $asset->id)
|
||||
->where('action_type', 'note added')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get(['id', 'created_at', 'note', 'created_by', 'item_id', 'item_type', 'action_type', 'target_id', 'target_type']);
|
||||
|
||||
$notesArray = $notes->map(function ($note) {
|
||||
return [
|
||||
'id' => $note->id,
|
||||
'created_at' => $note->created_at,
|
||||
'note' => $note->note,
|
||||
'created_by' => $note->created_by,
|
||||
'username' => $note->user?->username, // adding the username
|
||||
'item_id' => $note->item_id,
|
||||
'item_type' => $note->item_type,
|
||||
'action_type' => $note->action_type,
|
||||
];
|
||||
});
|
||||
|
||||
// Return a success response
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['notes' => $notesArray, 'asset_id' => $asset->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a manual note on a specified asset and log the action.
|
||||
*
|
||||
* Checks authorization for updating assets, validates the presence of the 'note',
|
||||
* attempts to find the asset by ID, and creates a new ActionLog entry if successful.
|
||||
* Returns JSON responses indicating success or failure with appropriate HTTP status codes.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The incoming HTTP request containing the 'note'.
|
||||
* @param Asset $asset The ID of the asset to attach the note to.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function store(Request $request, Asset $asset): JsonResponse
|
||||
{
|
||||
$this->authorize('update', $asset);
|
||||
|
||||
if ($request->input('note', '') == '') {
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, trans('validation.required', ['attribute' => 'note'])), 422);
|
||||
}
|
||||
|
||||
// Create the note
|
||||
$logaction = new ActionLog();
|
||||
$logaction->item_type = get_class($asset);
|
||||
$logaction->created_by = Auth::id();
|
||||
$logaction->item_id = $asset->id;
|
||||
$logaction->note = $request->input('note', '');
|
||||
|
||||
if ($logaction->logaction('note added')) {
|
||||
// Return a success response
|
||||
return response()->json(Helper::formatStandardApiResponse('success', ['note' => $logaction->note, 'item_id' => $asset->id], trans('general.note_added')));
|
||||
}
|
||||
|
||||
// Return an error response if something went wrong
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'Something went wrong'), 500);
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,28 @@ class ActionlogFactory extends Factory
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets up an ActionLog representing a manually added note tied to an Asset,
|
||||
* with an optional User as the creator. If no User is provided, one is generated.
|
||||
*
|
||||
* @param User|null $user Optional user to associate as the creator of the note.
|
||||
* @return \Illuminate\Database\Eloquent\Factories\Factory<ActionLog>
|
||||
*/
|
||||
public function assetNote(?User $user=null)
|
||||
{
|
||||
return $this
|
||||
->state(function () use ($user) {
|
||||
return [
|
||||
'action_type' => 'note added',
|
||||
'item_type' => Asset::class,
|
||||
'target_type' => 'asset',
|
||||
'note' => 'Factory-generated manual note',
|
||||
'created_by' => $user?->id ?? User::factory(),
|
||||
];
|
||||
})
|
||||
->for($user ?? User::factory(), 'user');
|
||||
}
|
||||
|
||||
public function licenseCheckoutToUser()
|
||||
{
|
||||
return $this->state(function () {
|
||||
|
||||
@@ -841,6 +841,28 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'api-throttle:api']], fu
|
||||
); // end asset models API routes
|
||||
|
||||
|
||||
/**
|
||||
* Asset notes API routes
|
||||
*/
|
||||
Route::group(['prefix' => 'notes'], function () {
|
||||
|
||||
Route::post(
|
||||
'{asset}/store',
|
||||
[
|
||||
Api\NotesController::class,
|
||||
'store'
|
||||
]
|
||||
)->name('api.notes.store');
|
||||
|
||||
Route::get(
|
||||
'{asset}/index',
|
||||
[
|
||||
Api\NotesController::class,
|
||||
'index'
|
||||
]
|
||||
)->name('api.notes.index');
|
||||
}
|
||||
); // end asset notes API routes
|
||||
|
||||
/**
|
||||
* Settings API routes
|
||||
|
||||
88
tests/Feature/Assets/Api/AssetNotesTest.php
Normal file
88
tests/Feature/Assets/Api/AssetNotesTest.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Assets\Api;
|
||||
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\User;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AssetNotesTest extends TestCase
|
||||
{
|
||||
public function testThatANonExistentAssetIdReturnsError()
|
||||
{
|
||||
$this->actingAsForApi(User::factory()->editAssets()->create())
|
||||
->postJson(route('api.notes.store', ['asset' => 123456789]))
|
||||
->assertStatusMessageIs('error');
|
||||
}
|
||||
|
||||
public function testRequiresPermissionToAddNoteToAssetAsset()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
|
||||
$this->actingAsForApi(User::factory()->create())
|
||||
->postJson(route('api.notes.store', $asset), [
|
||||
'note' => 'test'
|
||||
])
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function testAssetNoteIsSaved()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
|
||||
$this->actingAsForApi(User::factory()->editAssets()->create())
|
||||
->postJson(route('api.notes.store', $asset), [
|
||||
'note' => 'This is a test note.'
|
||||
])
|
||||
->assertStatusMessageIs('success')
|
||||
->assertJson([
|
||||
'messages' => trans('general.note_added'),
|
||||
'payload' => [
|
||||
'note' => 'This is a test note.',
|
||||
'item_id' => e($asset->id),
|
||||
],
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$note = ActionLog::where('item_id', $asset->id)
|
||||
->where('action_type', 'note added')
|
||||
->first();
|
||||
|
||||
$this->assertNotNull($note, 'The note was not saved in the database.');
|
||||
$this->assertEquals('This is a test note.', $note->note, 'The note content does not match.');
|
||||
}
|
||||
|
||||
public function testAssetNotesAreRetrievable()
|
||||
{
|
||||
$asset = Asset::factory()->create();
|
||||
|
||||
$user = User::factory()->viewAssets()->create();
|
||||
|
||||
$assetNote = Actionlog::factory()
|
||||
->assetNote($user)
|
||||
->create([
|
||||
'item_id' => $asset->id,
|
||||
'note' => 'This is a test note.',
|
||||
]);
|
||||
|
||||
$this->actingAsForApi($user)
|
||||
->getJson(route('api.notes.index', $asset))
|
||||
->assertOk()
|
||||
->assertJson([
|
||||
'messages' => null,
|
||||
'payload' => [
|
||||
'notes' => [
|
||||
[
|
||||
'note' => 'This is a test note.',
|
||||
'created_by' => $assetNote->created_by,
|
||||
'username' => $user->username,
|
||||
'item_id' => $assetNote->item_id,
|
||||
'item_type' => Asset::class,
|
||||
'action_type' => 'note added',
|
||||
]
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user