Assets
Introduction
The Assets module manages network assets that belong to organizations. Assets represent protected infrastructure (servers, networks, services) that are monitored by sensors. Each asset can be linked to an organization, a sensor, and a person-in-charge (PIC) user. Business logic is encapsulated in AssetService with data access via AssetRepository.
Controller
File: API/Controllers/Assets/AssetsController.cs
Route: api/assets
Inherits: ControllerBase
Auth: [Authorize] (all endpoints require authentication)
Delegates to: IAssetService
Endpoints
| Endpoint | Method | Description |
|---|---|---|
api/assets | GET | List all assets in current org |
api/assets/{id} | GET | Get single asset by ID |
api/assets | POST | Create new asset |
api/assets/{id} | PUT | Update existing asset |
api/assets/{id} | DELETE | Delete asset |
Organization Scoping
The list endpoint extracts the organization ID from JWT claims:
var organizationId = UserHelper.GetOrganizationId(User);
if (!organizationId.HasValue)
{
return BadRequest(ApiEnvelope.Error("Organization ID not found in token", HttpContext.TraceIdentifier));
}
var assets = await _assetService.GetAssetsAsync(organizationId.Value);
Response Format
All endpoints use the standard ApiEnvelope response:
{
"success": true,
"data": {
"items": [...],
"count": 5
},
"errors": []
}
Entity
File: Domain/Entities/Asset.cs
Table: assets
Namespace: Ravenxcope.Backend.Domain.Entities
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
name | varchar(255) | Required | Asset display name |
description | text | Nullable | Asset description |
location | varchar(255) | Nullable | Physical location |
as_number | varchar(255) | Nullable | Autonomous System number |
dns | varchar(255) | Nullable | DNS name or IP |
organization_id | uuid | FK (nullable) | Owning organization |
sensor_id | uuid | FK (nullable) | Associated sensor |
pic_id | uuid | FK (nullable) | Person in charge (User) |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Navigation Properties
[ForeignKey("OrganizationId")]
public Organization? Organization { get; set; }
[ForeignKey("SensorId")]
public Sensor? Sensor { get; set; }
[ForeignKey("PicId")]
public User? Pic { get; set; }
Foreign Key Behavior
| Relationship | Behavior |
|---|---|
| Organization → Asset | SetNull |
| Sensor → Asset | SetNull |
| User (PIC) → Asset | SetNull |
All FKs use SetNull on delete — deleting the parent entity sets the FK to null rather than cascading the delete.
Service
File: Infrastructure/Services/AssetService.cs
Interface: IAssetService
Lifetime: Scoped
Dependencies
| Dependency | Purpose |
|---|---|
IAssetRepository | Asset CRUD operations |
IOrganizationRepository | Validate organization exists on create |
ISensorRepository | Validate sensor exists on create/update |
IUserRepository | Validate PIC user exists on create/update |
Service Methods
| Method | Returns | Description |
|---|---|---|
GetAssetsAsync(Guid orgId) | IReadOnlyList<AssetDto> | List assets by organization |
GetAssetAsync(Guid id) | AssetDto | Get single asset with relations |
CreateAssetAsync(CreateAssetRequest) | AssetDto | Create asset with FK validation |
UpdateAssetAsync(Guid, UpdateAssetRequest) | AssetDto | Update asset fields with FK validation |
DeleteAssetAsync(Guid id) | void | Hard delete asset |
Validation
The ValidateReferencesAsync method validates FK references before create/update:
- If
organizationIdis provided → verify organization exists - If
sensorIdis provided → verify sensor exists - If
picIdis provided → verify user exists
Throws ArgumentException with descriptive message on validation failure.
Repository
File: Infrastructure/Repositories/AssetRepository.cs
Interface: IAssetRepository
Base: EfRepository<Asset>
Lifetime: Scoped
Custom Query Methods
| Method | Returns | Description |
|---|---|---|
GetByOrganizationIdAsync(Guid) | IReadOnlyList<Asset> | Assets with Organization, Sensor, PIC included |
GetByIdWithRelationsAsync(Guid) | Asset? | Single asset with all navigation properties loaded |
DTOs
Request DTOs
File: Application/DTOs/CreateAssetRequest.cs
| Property | Type | Required | Description |
|---|---|---|---|
Name | string | Yes | Asset name |
Description | string? | No | Asset description |
Location | string? | No | Physical location |
AsNumber | string? | No | AS number |
Dns | string? | No | DNS name or IP |
OrganizationId | Guid? | No | Owning organization |
SensorId | Guid? | No | Associated sensor |
PicId | Guid? | No | Person in charge |
File: Application/DTOs/UpdateAssetRequest.cs
Same fields as CreateAssetRequest except OrganizationId (only Name, Description, Location, AsNumber, Dns, SensorId, PicId). All fields are nullable for partial updates.
Response DTO
File: Application/DTOs/AssetDtos.cs
public sealed record AssetDto(
Guid Id,
string Name,
string? Description,
string? Location,
string? AsNumber,
string? Dns,
Guid? OrganizationId,
string? OrganizationName, // Resolved from Organization navigation
Guid? SensorId,
string? SensorName, // Resolved from Sensor navigation
Guid? PicId,
string? PicName, // Resolved from User (PIC) navigation
DateTime CreatedAt,
DateTime UpdatedAt);
Seeded Permissions
Asset CRUD permissions are seeded in ApplicationDbContext.OnModelCreating:
| Permission | Slug |
|---|---|
| Create Asset | create-asset |
| Edit Asset | edit-asset |
| Delete Asset | delete-asset |
| Read Asset | read-asset |
DI Registration
In ServiceCollectionExtensions.AddBackendServicesAsync:
services.AddScoped<IAssetRepository, AssetRepository>();
services.AddScoped<IAssetService, AssetService>();
Future Improvements
Completed
- Permission-based authorization on write endpoints (
manage-assetspolicy).
Open
- Add pagination to asset listing endpoint.
- Add bulk import/export for assets.
- Add asset status tracking (active, decommissioned, etc.).