package v1 import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/response" ) // LinearIntegrationHandler handles Linear integration endpoints. // Reference: Chatwoot Integrations::LinearController type LinearIntegrationHandler struct { svc *service.LinearIntegrationService } // NewLinearIntegrationHandler creates a new LinearIntegrationHandler. func NewLinearIntegrationHandler(svc *service.LinearIntegrationService) *LinearIntegrationHandler { return &LinearIntegrationHandler{svc: svc} } // Delete removes a Linear integration for an account. // DELETE /api/v1/accounts/:account_id/integrations/linear func (h *LinearIntegrationHandler) Delete(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } if svcErr := h.svc.Delete(c.Request.Context(), accountID); svcErr != nil { handleServiceError(c, svcErr) return } c.Status(http.StatusOK) } // GetTeams retrieves available Linear teams. // GET /api/v1/accounts/:account_id/integrations/linear/teams func (h *LinearIntegrationHandler) GetTeams(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } teams, svcErr := h.svc.GetTeams(c.Request.Context(), accountID) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, teams) } // GetTeamEntities retrieves entities from a Linear team. // GET /api/v1/accounts/:account_id/integrations/linear/team_entities func (h *LinearIntegrationHandler) GetTeamEntities(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } entities, svcErr := h.svc.GetTeamEntities(c.Request.Context(), accountID, c.Query("team_id")) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, entities) } // CreateIssue creates a Linear issue from a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/create_issue func (h *LinearIntegrationHandler) CreateIssue(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req service.CreateIssueRequest if err := c.ShouldBind(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } result, svcErr := h.svc.CreateIssue(c.Request.Context(), accountID, req, getUserID(c)) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, result) } // LinkIssue links a Linear issue to a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/link_issue func (h *LinearIntegrationHandler) LinkIssue(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req service.LinkIssueRequest if err := c.ShouldBind(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } result, svcErr := h.svc.LinkIssue(c.Request.Context(), accountID, req, getUserID(c)) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, result) } // UnlinkIssue unlinks a Linear issue from a conversation. // POST /api/v1/accounts/:account_id/integrations/linear/unlink_issue func (h *LinearIntegrationHandler) UnlinkIssue(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req service.UnlinkIssueRequest if err := c.ShouldBind(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } result, svcErr := h.svc.UnlinkIssue(c.Request.Context(), accountID, req, getUserID(c)) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, result) } // SearchIssue searches Linear issues. // GET /api/v1/accounts/:account_id/integrations/linear/search_issue func (h *LinearIntegrationHandler) SearchIssue(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } query := c.Query("q") if strings.TrimSpace(query) == "" { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Specify search string with parameter q"}) return } issues, svcErr := h.svc.SearchIssue(c.Request.Context(), accountID, query) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, issues) } // GetLinkedIssues retrieves Linear issues linked to a conversation. // GET /api/v1/accounts/:account_id/integrations/linear/linked_issues func (h *LinearIntegrationHandler) GetLinkedIssues(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseOptionalUintQueryParam(c, "conversation_id") if err != nil || conversationID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } issues, svcErr := h.svc.GetLinkedIssues(c.Request.Context(), accountID, conversationID) if svcErr != nil { handleLinearServiceError(c, svcErr) return } c.JSON(http.StatusOK, issues) } // RegisterLinearIntegrationRoutes registers Linear integration routes. func RegisterLinearIntegrationRoutes(g *gin.RouterGroup, h *LinearIntegrationHandler) { g.DELETE("/linear", h.Delete) linear := g.Group("/linear") { linear.DELETE("/", h.Delete) linear.GET("/teams", h.GetTeams) linear.GET("/team_entities", h.GetTeamEntities) linear.POST("/create_issue", h.CreateIssue) linear.POST("/link_issue", h.LinkIssue) linear.POST("/unlink_issue", h.UnlinkIssue) linear.GET("/search_issue", h.SearchIssue) linear.GET("/linked_issues", h.GetLinkedIssues) } } func handleLinearServiceError(c *gin.Context, err error) { var providerErr *service.LinearProviderError if errors.As(err, &providerErr) { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": providerErr.Message}) return } handleServiceError(c, err) }