diff --git a/colaflow-api/src/ColaFlow.API/Controllers/EpicsController.cs b/colaflow-api/src/ColaFlow.API/Controllers/EpicsController.cs
index 0b5fa88..754b4e4 100644
--- a/colaflow-api/src/ColaFlow.API/Controllers/EpicsController.cs
+++ b/colaflow-api/src/ColaFlow.API/Controllers/EpicsController.cs
@@ -40,11 +40,32 @@ public class EpicsController(IMediator mediator) : ControllerBase
{
var query = new GetEpicByIdQuery(id);
var result = await _mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
///
- /// Create a new epic
+ /// Create a new epic (independent endpoint)
+ ///
+ [HttpPost("epics")]
+ [ProducesResponseType(typeof(EpicDto), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task CreateEpicIndependent(
+ [FromBody] CreateEpicCommand command,
+ CancellationToken cancellationToken = default)
+ {
+ var result = await _mediator.Send(command, cancellationToken);
+ return CreatedAtAction(nameof(GetEpic), new { id = result.Id }, result);
+ }
+
+ ///
+ /// Create a new epic (nested endpoint)
///
[HttpPost("projects/{projectId:guid}/epics")]
[ProducesResponseType(typeof(EpicDto), StatusCodes.Status201Created)]
@@ -87,6 +108,12 @@ public class EpicsController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
}
diff --git a/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs b/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs
index ecc636e..82b6166 100644
--- a/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs
+++ b/colaflow-api/src/ColaFlow.API/Controllers/ProjectsController.cs
@@ -43,6 +43,12 @@ public class ProjectsController(IMediator mediator) : ControllerBase
{
var query = new GetProjectByIdQuery(id);
var result = await _mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -85,6 +91,12 @@ public class ProjectsController(IMediator mediator) : ControllerBase
{
var commandWithId = command with { ProjectId = id };
var result = await _mediator.Send(commandWithId, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
diff --git a/colaflow-api/src/ColaFlow.API/Controllers/StoriesController.cs b/colaflow-api/src/ColaFlow.API/Controllers/StoriesController.cs
index 224e2fd..9757c98 100644
--- a/colaflow-api/src/ColaFlow.API/Controllers/StoriesController.cs
+++ b/colaflow-api/src/ColaFlow.API/Controllers/StoriesController.cs
@@ -30,6 +30,12 @@ public class StoriesController(IMediator mediator) : ControllerBase
{
var query = new GetStoryByIdQuery(id);
var result = await _mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -60,7 +66,22 @@ public class StoriesController(IMediator mediator) : ControllerBase
}
///
- /// Create a new story
+ /// Create a new story (independent endpoint)
+ ///
+ [HttpPost("stories")]
+ [ProducesResponseType(typeof(StoryDto), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task CreateStoryIndependent(
+ [FromBody] CreateStoryCommand command,
+ CancellationToken cancellationToken = default)
+ {
+ var result = await _mediator.Send(command, cancellationToken);
+ return CreatedAtAction(nameof(GetStory), new { id = result.Id }, result);
+ }
+
+ ///
+ /// Create a new story (nested endpoint)
///
[HttpPost("epics/{epicId:guid}/stories")]
[ProducesResponseType(typeof(StoryDto), StatusCodes.Status201Created)]
@@ -110,6 +131,12 @@ public class StoriesController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -146,6 +173,12 @@ public class StoriesController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
}
diff --git a/colaflow-api/src/ColaFlow.API/Controllers/TasksController.cs b/colaflow-api/src/ColaFlow.API/Controllers/TasksController.cs
index 2b131ac..a98e803 100644
--- a/colaflow-api/src/ColaFlow.API/Controllers/TasksController.cs
+++ b/colaflow-api/src/ColaFlow.API/Controllers/TasksController.cs
@@ -31,6 +31,12 @@ public class TasksController(IMediator mediator) : ControllerBase
{
var query = new GetTaskByIdQuery(id);
var result = await _mediator.Send(query, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -70,7 +76,22 @@ public class TasksController(IMediator mediator) : ControllerBase
}
///
- /// Create a new task
+ /// Create a new task (independent endpoint)
+ ///
+ [HttpPost("tasks")]
+ [ProducesResponseType(typeof(TaskDto), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task CreateTaskIndependent(
+ [FromBody] CreateTaskCommand command,
+ CancellationToken cancellationToken = default)
+ {
+ var result = await _mediator.Send(command, cancellationToken);
+ return CreatedAtAction(nameof(GetTask), new { id = result.Id }, result);
+ }
+
+ ///
+ /// Create a new task (nested endpoint)
///
[HttpPost("stories/{storyId:guid}/tasks")]
[ProducesResponseType(typeof(TaskDto), StatusCodes.Status201Created)]
@@ -120,6 +141,12 @@ public class TasksController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -156,6 +183,12 @@ public class TasksController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
@@ -178,6 +211,12 @@ public class TasksController(IMediator mediator) : ControllerBase
};
var result = await _mediator.Send(command, cancellationToken);
+
+ if (result == null)
+ {
+ return NotFound();
+ }
+
return Ok(result);
}
}
diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQuery.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQuery.cs
index ad858e7..44697e6 100644
--- a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQuery.cs
+++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQuery.cs
@@ -6,4 +6,4 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetProjectById;
///
/// Query to get a project by its ID
///
-public sealed record GetProjectByIdQuery(Guid ProjectId) : IRequest;
+public sealed record GetProjectByIdQuery(Guid ProjectId) : IRequest;
diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQueryHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQueryHandler.cs
index 9cdba37..85dc0b3 100644
--- a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQueryHandler.cs
+++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Queries/GetProjectById/GetProjectByIdQueryHandler.cs
@@ -11,11 +11,11 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetProjectById;
/// Handler for GetProjectByIdQuery
///
public sealed class GetProjectByIdQueryHandler(IProjectRepository projectRepository)
- : IRequestHandler
+ : IRequestHandler
{
private readonly IProjectRepository _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
- public async Task Handle(GetProjectByIdQuery request, CancellationToken cancellationToken)
+ public async Task Handle(GetProjectByIdQuery request, CancellationToken cancellationToken)
{
// Use read-only method for query (AsNoTracking for better performance)
var project = await _projectRepository.GetProjectByIdReadOnlyAsync(
@@ -24,7 +24,7 @@ public sealed class GetProjectByIdQueryHandler(IProjectRepository projectReposit
if (project == null)
{
- throw new DomainException($"Project with ID '{request.ProjectId}' not found");
+ return null; // Return null instead of throwing exception - Controller will convert to 404
}
return MapToDto(project);