Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/internal/api/http/handler/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (

type ServiceHandler interface {
FindAllServices(c *gin.Context)
FindServiceDependencies(c *gin.Context)
CreateServiceDependency(c *gin.Context)
DeleteServiceDependency(c *gin.Context)
}

type serviceHandler struct {
Expand Down
160 changes: 160 additions & 0 deletions backend/internal/api/http/handler/service/service_dependency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package handler

import (
"net/http"

"devhub-backend/internal/domain/entity"
"devhub-backend/internal/domain/errs"
serviceUsecase "devhub-backend/internal/usecase/service"
"devhub-backend/internal/util/httpresponse"
"devhub-backend/internal/util/misc"

"github.com/gin-gonic/gin"
)

type serviceDependencyRequest struct {
DependsOnServiceID string `json:"depends_on_service_id" binding:"required"`
Type string `json:"type" binding:"required"`
Protocol string `json:"protocol"`
Port *int `json:"port"`
Path string `json:"path"`
Config map[string]any `json:"config"`
}

type serviceDependencyResponse struct {
ID string `json:"id"`
ServiceID string `json:"service_id"`
DependsOnServiceID string `json:"depends_on_service_id"`
DependsOnService *findAllServicesResponse `json:"depends_on_service,omitempty"`
Type string `json:"type"`
Protocol string `json:"protocol"`
Port *int `json:"port"`
Path string `json:"path"`
Config map[string]any `json:"config"`
CreatedBy string `json:"created_by"`
}

// @Summary List Service Dependencies
// @Description List services that the selected service depends on
// @Tags Service
// @Produce json
// @Success 200 {object} httpresponse.SuccessResponse{data=[]serviceDependencyResponse,metadata=nil} "List of service dependencies"
// @Failure 400 {object} httpresponse.ErrorResponse{data=nil} "Bad request"
// @Failure 500 {object} httpresponse.ErrorResponse{data=nil} "Internal server error"
// @Router /services/:service/dependencies [get]
func (h *serviceHandler) FindServiceDependencies(c *gin.Context) {
dependencies, err := h.serviceUsecase.FindServiceDependencies(c.Request.Context(), serviceUsecase.FindServiceDependenciesInput{
ServiceID: c.Param("service"),
})
if err != nil {
httpresponse.Error(c, err)
return
}

httpresponse.Success(c, h.newServiceDependencyResponses(dependencies))
}

// @Summary Create Service Dependency
// @Description Wire one service to another service in the same project
// @Tags Service
// @Accept json
// @Produce json
// @Param request body serviceDependencyRequest true "Service dependency input"
// @Success 201 {object} httpresponse.SuccessResponse{data=serviceDependencyResponse,metadata=nil} "Service dependency created"
// @Failure 400 {object} httpresponse.ErrorResponse{data=nil} "Bad request"
// @Failure 500 {object} httpresponse.ErrorResponse{data=nil} "Internal server error"
// @Router /services/:service/dependencies [post]
func (h *serviceHandler) CreateServiceDependency(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
httpresponse.Error(c, errs.NewBadRequestError("unauthorized", nil))
return
}

var input serviceDependencyRequest
if err := c.ShouldBindJSON(&input); err != nil {
httpresponse.Error(c, misc.WrapError(err, errs.NewBadRequestError("unable to parse request", map[string]string{"details": err.Error()})))
return
}

dependency, err := h.serviceUsecase.CreateServiceDependency(c.Request.Context(), serviceUsecase.CreateServiceDependencyInput{
ServiceID: c.Param("service"),
DependsOnServiceID: input.DependsOnServiceID,
Type: input.Type,
Protocol: input.Protocol,
Port: input.Port,
Path: input.Path,
Config: input.Config,
CreatedBy: userID.(string),
})
if err != nil {
httpresponse.Error(c, err)
return
}

httpresponse.SuccessWithStatus(c, http.StatusCreated, h.newServiceDependencyResponse(dependency))
}

// @Summary Delete Service Dependency
// @Description Remove a service-to-service wiring entry
// @Tags Service
// @Produce json
// @Success 200 {object} httpresponse.SuccessResponse{data=serviceDependencyResponse,metadata=nil} "Service dependency deleted"
// @Failure 400 {object} httpresponse.ErrorResponse{data=nil} "Bad request"
// @Failure 404 {object} httpresponse.ErrorResponse{data=nil} "Not found"
// @Failure 500 {object} httpresponse.ErrorResponse{data=nil} "Internal server error"
// @Router /services/:service/dependencies/:dependency [delete]
func (h *serviceHandler) DeleteServiceDependency(c *gin.Context) {
dependency, err := h.serviceUsecase.DeleteServiceDependency(c.Request.Context(), serviceUsecase.DeleteServiceDependencyInput{
ServiceID: c.Param("service"),
DependencyID: c.Param("dependency"),
})
if err != nil {
httpresponse.Error(c, err)
return
}

httpresponse.Success(c, h.newServiceDependencyResponse(dependency))
}

func (h *serviceHandler) newServiceDependencyResponses(dependencies entity.ServiceDependencies) []serviceDependencyResponse {
if len(dependencies) == 0 {
return []serviceDependencyResponse{}
}

response := make([]serviceDependencyResponse, 0, len(dependencies))
for _, dependency := range dependencies {
response = append(response, h.newServiceDependencyResponse(&dependency))
}

return response
}

func (h *serviceHandler) newServiceDependencyResponse(dependency *entity.ServiceDependency) serviceDependencyResponse {
if dependency == nil {
return serviceDependencyResponse{}
}

var dependsOn *findAllServicesResponse
if dependency.DependsOnService != nil {
dependsOn = &findAllServicesResponse{
ID: dependency.DependsOnService.ID.String(),
ProjectID: dependency.DependsOnService.ProjectID.String(),
Name: dependency.DependsOnService.Name,
RepoURL: dependency.DependsOnService.RepoURL,
}
}

return serviceDependencyResponse{
ID: dependency.ID.String(),
ServiceID: dependency.ServiceID.String(),
DependsOnServiceID: dependency.DependsOnServiceID.String(),
DependsOnService: dependsOn,
Type: dependency.Type,
Protocol: dependency.Protocol,
Port: dependency.Port,
Path: dependency.Path,
Config: dependency.Config,
CreatedBy: dependency.CreatedBy.String(),
}
}
9 changes: 9 additions & 0 deletions backend/internal/api/http/route/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ func (r *router) applyProjectRoutes(router *gin.Engine) {
r.Middleware.RequirePermissions(entity.PermissionReleaseWrite),
r.ReleaseHandler.CreateRelease,
)
serviceProtectedRoute.GET("/:service/dependencies", r.ServiceHandler.FindServiceDependencies)
serviceProtectedRoute.POST("/:service/dependencies",
r.Middleware.RequirePermissions(entity.PermissionProjectWrite),
r.ServiceHandler.CreateServiceDependency,
)
serviceProtectedRoute.DELETE("/:service/dependencies/:dependency",
r.Middleware.RequirePermissions(entity.PermissionProjectWrite),
r.ServiceHandler.DeleteServiceDependency,
)
}
}

Expand Down
17 changes: 17 additions & 0 deletions backend/internal/domain/entity/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,20 @@ type Service struct {
}

type Services []Service

type ServiceDependency struct {
ID uuid.UUID
ServiceID uuid.UUID
DependsOnServiceID uuid.UUID
DependsOnService *Service
Type string
Protocol string
Port *int
Path string
Config map[string]any
CreatedBy uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
}

type ServiceDependencies []ServiceDependency
3 changes: 3 additions & 0 deletions backend/internal/domain/repository/service_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type ServiceRepository interface {
FindOne(ctx context.Context, id uuid.UUID) (*entity.Service, error)
FindAll(ctx context.Context, filter FindAllServicesFilter) (*entity.Services, int64, error)
DeleteOne(ctx context.Context, id uuid.UUID) (*entity.Service, error)
CreateDependency(ctx context.Context, dependency *entity.ServiceDependency) (*entity.ServiceDependency, error)
FindDependencies(ctx context.Context, serviceID uuid.UUID) (*entity.ServiceDependencies, error)
DeleteDependency(ctx context.Context, serviceID uuid.UUID, dependencyID uuid.UUID) (*entity.ServiceDependency, error)
}

type FindAllServicesFilter struct {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading